add sborka
This commit is contained in:
258
garrysmod/addons/lvs_base/lua/entities/lvs_armor.lua
Normal file
258
garrysmod/addons/lvs_base/lua/entities/lvs_armor.lua
Normal file
@@ -0,0 +1,258 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.DoNotDuplicate = true
|
||||
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar( "Entity",0, "Base" )
|
||||
|
||||
self:NetworkVar( "Float",0, "HP" )
|
||||
self:NetworkVar( "Float",1, "MaxHP" )
|
||||
self:NetworkVar( "Float",2, "IgnoreForce" )
|
||||
|
||||
self:NetworkVar( "Vector",0, "Mins" )
|
||||
self:NetworkVar( "Vector",1, "Maxs" )
|
||||
|
||||
self:NetworkVar( "Bool",0, "Destroyed" )
|
||||
|
||||
self:NetworkVar( "String",0, "Label" )
|
||||
|
||||
if SERVER then
|
||||
self:SetMaxHP( 100 )
|
||||
self:SetHP( 100 )
|
||||
self:SetLabel( "Armor Plate" )
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetMoveType( MOVETYPE_NONE )
|
||||
self:SetSolid( SOLID_NONE )
|
||||
self:DrawShadow( false )
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:OnHealthChanged( dmginfo, old, new )
|
||||
if old == new then return end
|
||||
end
|
||||
|
||||
function ENT:OnRepaired()
|
||||
end
|
||||
|
||||
function ENT:OnDestroyed( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:TakeTransmittedDamage( dmginfo )
|
||||
local Force = dmginfo:GetDamageForce()
|
||||
|
||||
local Damage = dmginfo:GetDamage()
|
||||
local DamageForce = Force:Length()
|
||||
local IsBlastDamage = dmginfo:IsDamageType( DMG_BLAST )
|
||||
|
||||
local CurHealth = self:GetHP()
|
||||
|
||||
local pos = dmginfo:GetDamagePosition()
|
||||
local dir = Force:GetNormalized()
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
-- translate force value to armor penetration value is Force * 0.1
|
||||
-- mm to inch is * 0.0393701
|
||||
-- so correct value is * 0.00393701
|
||||
local pLength = DamageForce * 0.00393701
|
||||
|
||||
local TraceData = {
|
||||
start = pos - dir * pLength,
|
||||
endpos = pos + dir * pLength,
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( TraceData )
|
||||
|
||||
-- parent stays the same
|
||||
local parent = trace.Entity
|
||||
local parentPos = trace.HitPos
|
||||
local parentDir = trace.HitNormal
|
||||
|
||||
-- only one extra iteration should be enough ...
|
||||
if IsValid( trace.Entity ) and isfunction( trace.Entity.GetBase ) and trace.Entity:GetBase() == base then
|
||||
|
||||
TraceData.filter = trace.Entity
|
||||
|
||||
local FilteredTrace = util.TraceLine( TraceData )
|
||||
|
||||
if FilteredTrace.Hit then
|
||||
trace = FilteredTrace
|
||||
end
|
||||
|
||||
trace.Entity = base
|
||||
end
|
||||
|
||||
local DotHitNormal = math.Clamp( trace.HitNormal:Dot( dir ) ,-1,1)
|
||||
|
||||
local Armor = self:GetIgnoreForce()
|
||||
local ArmorEffective = Armor / math.abs( DotHitNormal )
|
||||
|
||||
if math.abs( DotHitNormal ) > 0.9 then
|
||||
ArmorEffective = Armor
|
||||
end
|
||||
|
||||
local DisableBounce = false
|
||||
|
||||
local Inflictor = dmginfo:GetInflictor()
|
||||
|
||||
if IsValid( Inflictor ) then
|
||||
if Inflictor.DisableBallistics or Inflictor:IsNPC() or Inflictor:IsNextBot() then
|
||||
DisableBounce = true
|
||||
end
|
||||
end
|
||||
|
||||
if DamageForce <= ArmorEffective and not IsBlastDamage then
|
||||
local T = CurTime()
|
||||
|
||||
if trace.Entity ~= base then
|
||||
self._NextBounce = T + 1
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local Ax = math.acos( DotHitNormal )
|
||||
local HitAngle = 90 - (180 - math.deg( Ax ))
|
||||
|
||||
if HitAngle > 30 then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetNormal( -dir )
|
||||
util.Effect( "manhacksparks", effectdata, true, true )
|
||||
|
||||
self._NextBounce = T + 1
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local NewDir = dir - trace.HitNormal * math.cos( Ax ) * 2
|
||||
|
||||
if (self._NextBounce or 0) > T or DisableBounce then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetNormal( NewDir:GetNormalized() * 0.25 )
|
||||
util.Effect( "manhacksparks", effectdata, true, true )
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
self._NextBounce = T + 1
|
||||
|
||||
local hit_decal = ents.Create( "lvs_armor_bounce" )
|
||||
hit_decal:SetPos( trace.HitPos )
|
||||
hit_decal:SetAngles( NewDir:Angle() )
|
||||
hit_decal:Spawn()
|
||||
hit_decal:Activate()
|
||||
hit_decal:EmitSound("lvs/armor_rico"..math.random(1,6)..".wav", 95, 100, math.min( dmginfo:GetDamage() / 1000, 1 ) )
|
||||
|
||||
local PhysObj = hit_decal:GetPhysicsObject()
|
||||
if not IsValid( PhysObj ) then return false end
|
||||
|
||||
PhysObj:EnableDrag( false )
|
||||
PhysObj:SetVelocityInstantaneous( NewDir * 2000 + Vector(0,0,250) )
|
||||
PhysObj:SetAngleVelocityInstantaneous( VectorRand() * 250 )
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() )
|
||||
|
||||
self:OnHealthChanged( dmginfo, CurHealth, NewHealth )
|
||||
self:SetHP( NewHealth )
|
||||
|
||||
if NewHealth <= 0 and not self:GetDestroyed() then
|
||||
self:SetDestroyed( true )
|
||||
self:OnDestroyed( dmginfo )
|
||||
end
|
||||
|
||||
local hit_decal = ents.Create( "lvs_armor_penetrate" )
|
||||
hit_decal:SetPos( parentPos + parentDir * 0.2 )
|
||||
hit_decal:SetAngles( parentDir:Angle() + Angle(90,0,0) )
|
||||
hit_decal:Spawn()
|
||||
hit_decal:Activate()
|
||||
hit_decal:SetParent( parent )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
end
|
||||
|
||||
|
||||
function ENT:Draw()
|
||||
end
|
||||
|
||||
local function DrawText( pos, text, col )
|
||||
cam.Start2D()
|
||||
local data2D = pos:ToScreen()
|
||||
|
||||
if not data2D.visible then cam.End2D() return end
|
||||
|
||||
local font = "TargetIDSmall"
|
||||
|
||||
local x = data2D.x
|
||||
local y = data2D.y
|
||||
|
||||
draw.DrawText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER )
|
||||
draw.DrawText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER )
|
||||
draw.DrawText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER )
|
||||
cam.End2D()
|
||||
end
|
||||
|
||||
local LVS = LVS
|
||||
local BoxMat = Material("models/wireframe")
|
||||
local ColorSelect = Color(0,127,255,150)
|
||||
local ColorNormal = Color(50,50,50,150)
|
||||
local ColorTransBlack = Color(0,0,0,150)
|
||||
local OutlineThickness = Vector(0.5,0.5,0.5)
|
||||
local ColorText = Color(255,0,0,255)
|
||||
|
||||
function ENT:DrawTranslucent()
|
||||
if not LVS.DeveloperEnabled then return end
|
||||
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) or ply:InVehicle() or not ply:KeyDown( IN_SPEED ) then return end
|
||||
|
||||
local boxOrigin = self:GetPos()
|
||||
local boxAngles = self:GetAngles()
|
||||
local boxMins = self:GetMins()
|
||||
local boxMaxs = self:GetMaxs()
|
||||
|
||||
local HitPos, _, _ = util.IntersectRayWithOBB( ply:GetShootPos(), ply:GetAimVector() * 1000, boxOrigin, boxAngles, boxMins, boxMaxs )
|
||||
|
||||
local InRange = isvector( HitPos )
|
||||
|
||||
local Col = InRange and ColorSelect or ColorNormal
|
||||
|
||||
render.SetColorMaterial()
|
||||
render.DrawBox( boxOrigin, boxAngles, boxMins, boxMaxs, Col )
|
||||
render.DrawBox( boxOrigin, boxAngles, boxMaxs + OutlineThickness, boxMins - OutlineThickness, ColorTransBlack )
|
||||
|
||||
local boxCenter = (self:LocalToWorld( boxMins ) + self:LocalToWorld( boxMaxs )) * 0.5
|
||||
|
||||
if not InRange then return end
|
||||
|
||||
DrawText( boxCenter, "Armor: "..(self:GetIgnoreForce() / 100).."mm\nHealth:"..self:GetHP().."/"..self:GetMaxHP(), ColorText )
|
||||
end
|
||||
124
garrysmod/addons/lvs_base/lua/entities/lvs_armor_bounce.lua
Normal file
124
garrysmod/addons/lvs_base/lua/entities/lvs_armor_bounce.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
|
||||
ENT.PrintName = "88mm Round"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS] - Cars - Items"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminOnly = false
|
||||
|
||||
ENT.LifeTime = 10
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel( "models/misc/88mm_projectile.mdl" )
|
||||
self:SetMoveType( MOVETYPE_VPHYSICS )
|
||||
self:PhysicsInit( SOLID_VPHYSICS)
|
||||
|
||||
self.DieTime = CurTime() + self.LifeTime
|
||||
|
||||
self:SetCollisionGroup( COLLISION_GROUP_WORLD )
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if self.MarkForRemove then self:Remove() return false end
|
||||
|
||||
self:NextThink( CurTime() + 0.1 )
|
||||
|
||||
if (self.DieTime or 0) > CurTime() then return true end
|
||||
|
||||
self:Remove()
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function ENT:PhysicsCollide( data, physobj )
|
||||
self.MarkForRemove = true
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data.HitPos )
|
||||
effectdata:SetNormal( -data.HitNormal )
|
||||
effectdata:SetMagnitude( 0.5 )
|
||||
util.Effect( "lvs_bullet_impact", effectdata )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
ENT.MatSmoke = {
|
||||
"particle/smokesprites_0001",
|
||||
"particle/smokesprites_0002",
|
||||
"particle/smokesprites_0003",
|
||||
"particle/smokesprites_0004",
|
||||
"particle/smokesprites_0005",
|
||||
"particle/smokesprites_0006",
|
||||
"particle/smokesprites_0007",
|
||||
"particle/smokesprites_0008",
|
||||
"particle/smokesprites_0009",
|
||||
"particle/smokesprites_0010",
|
||||
"particle/smokesprites_0011",
|
||||
"particle/smokesprites_0012",
|
||||
"particle/smokesprites_0013",
|
||||
"particle/smokesprites_0014",
|
||||
"particle/smokesprites_0015",
|
||||
"particle/smokesprites_0016"
|
||||
}
|
||||
|
||||
function ENT:Initialize()
|
||||
self.DieTime = CurTime() + self.LifeTime
|
||||
|
||||
self.emitter = ParticleEmitter( self:GetPos(), false )
|
||||
end
|
||||
|
||||
function ENT:Smoke()
|
||||
local T = CurTime()
|
||||
|
||||
if (self.DieTime or 0) < T then return end
|
||||
|
||||
if not IsValid( self.emitter ) then return end
|
||||
|
||||
if (self.NextFX or 0) < T then
|
||||
self.NextFX = T + 0.02
|
||||
|
||||
local Timed = 1 - (self.DieTime - T) / self.LifeTime
|
||||
local Scale = math.max(math.min(2 - Timed * 2,1),0)
|
||||
|
||||
local Pos = self:GetPos()
|
||||
|
||||
local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], Pos )
|
||||
|
||||
local VecCol = (render.GetLightColor( Pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255
|
||||
|
||||
if particle then
|
||||
particle:SetVelocity( VectorRand() * 10 )
|
||||
particle:SetDieTime( math.Rand(0.5,1) )
|
||||
particle:SetAirResistance( 100 )
|
||||
particle:SetStartAlpha( 100 * Scale )
|
||||
particle:SetEndAlpha( 0 )
|
||||
particle:SetStartSize( 10 )
|
||||
particle:SetEndSize( 20 )
|
||||
particle:SetRollDelta( 1 )
|
||||
particle:SetColor( VecCol.r, VecCol.g, VecCol.b )
|
||||
particle:SetGravity( Vector( 0, 0, 200 ) )
|
||||
particle:SetCollide( false )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:Smoke()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
if not self.emitter then return end
|
||||
|
||||
self.emitter:Finish()
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
149
garrysmod/addons/lvs_base/lua/entities/lvs_armor_penetrate.lua
Normal file
149
garrysmod/addons/lvs_base/lua/entities/lvs_armor_penetrate.lua
Normal file
@@ -0,0 +1,149 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
ENT.LifeTime = 15
|
||||
|
||||
if SERVER then
|
||||
local CountTotal = {}
|
||||
|
||||
function ENT:Initialize()
|
||||
CountTotal[ self:EntIndex() ] = true
|
||||
|
||||
local Num = table.Count( CountTotal )
|
||||
|
||||
if (Num > 30 and math.random(1,2) == 1) or Num > 60 then
|
||||
self:Remove()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetMoveType( MOVETYPE_NONE )
|
||||
self:SetSolid( SOLID_NONE )
|
||||
self:DrawShadow( false )
|
||||
self.DieTime = CurTime() + self.LifeTime
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
CountTotal[ self:EntIndex() ] = nil
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:NextThink( CurTime() + 0.1 )
|
||||
|
||||
if not IsValid( self:GetParent() ) then self:Remove() return end
|
||||
|
||||
if (self.DieTime or 0) > CurTime() then return true end
|
||||
|
||||
self:Remove()
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
ENT.GlowMat1 = Material( "particle/particle_ring_wave_8" )
|
||||
ENT.GlowMat2 = Material( "sprites/light_glow02_add" )
|
||||
ENT.DecalMat = Material( "particle/particle_noisesphere" )
|
||||
ENT.MatSmoke = {
|
||||
"particle/smokesprites_0001",
|
||||
"particle/smokesprites_0002",
|
||||
"particle/smokesprites_0003",
|
||||
"particle/smokesprites_0004",
|
||||
"particle/smokesprites_0005",
|
||||
"particle/smokesprites_0006",
|
||||
"particle/smokesprites_0007",
|
||||
"particle/smokesprites_0008",
|
||||
"particle/smokesprites_0009",
|
||||
"particle/smokesprites_0010",
|
||||
"particle/smokesprites_0011",
|
||||
"particle/smokesprites_0012",
|
||||
"particle/smokesprites_0013",
|
||||
"particle/smokesprites_0014",
|
||||
"particle/smokesprites_0015",
|
||||
"particle/smokesprites_0016"
|
||||
}
|
||||
|
||||
local CountTotal = {}
|
||||
|
||||
function ENT:Initialize()
|
||||
CountTotal[ self:EntIndex() ] = true
|
||||
|
||||
self.RandomAng = math.random(0,360)
|
||||
self.DieTime = CurTime() + self.LifeTime
|
||||
|
||||
local Pos = self:GetPos()
|
||||
local Dir = self:GetUp()
|
||||
|
||||
self.emitter = ParticleEmitter( Pos, false )
|
||||
|
||||
self:EmitSound( "lvs/armor_pen_"..math.random(1,3)..".wav", 95 )
|
||||
end
|
||||
|
||||
function ENT:Smoke()
|
||||
local T = CurTime()
|
||||
|
||||
if (self.DieTime or 0) < T then return end
|
||||
|
||||
if not IsValid( self.emitter ) then return end
|
||||
|
||||
if (self.NextFX or 0) < T then
|
||||
self.NextFX = T + 0.2 + table.Count( CountTotal ) / 50
|
||||
|
||||
local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], self:GetPos() )
|
||||
|
||||
if particle then
|
||||
particle:SetVelocity( self:GetUp() * 60 + VectorRand() * 30 )
|
||||
particle:SetDieTime( math.Rand(1.5,2) )
|
||||
particle:SetAirResistance( 100 )
|
||||
particle:SetStartAlpha( 30 )
|
||||
particle:SetEndAlpha( 0 )
|
||||
particle:SetStartSize( 0 )
|
||||
particle:SetEndSize( 60 )
|
||||
particle:SetRollDelta( math.Rand( -1, 1 ) )
|
||||
particle:SetColor( 50,50,50 )
|
||||
particle:SetGravity( Vector( 0, 0, 200 ) )
|
||||
particle:SetCollide( false )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:Smoke()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
CountTotal[ self:EntIndex() ] = nil
|
||||
|
||||
if not IsValid(self.emitter) then return end
|
||||
|
||||
self.emitter:Finish()
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
local Timed = 1 - (self.DieTime - CurTime()) / self.LifeTime
|
||||
local Scale = math.max(math.min(2 - Timed * 2,1),0)
|
||||
|
||||
local Scale02 = math.max(Scale - 0.8,0) / 0.2
|
||||
|
||||
cam.Start3D2D( self:GetPos() + self:GetAngles():Up(), self:GetAngles(), 1 )
|
||||
surface.SetDrawColor( 255 * Scale02, (93 + 50 * Scale) * Scale02, (50 * Scale) * Scale02, (200 * Scale) * Scale02 )
|
||||
|
||||
surface.SetMaterial( self.GlowMat1 )
|
||||
surface.DrawTexturedRectRotated( 0, 0, 8 , 8 , self.RandomAng )
|
||||
|
||||
surface.SetMaterial( self.GlowMat2 )
|
||||
surface.DrawTexturedRectRotated( 0, 0, 16 , 16 , self.RandomAng )
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 255 )
|
||||
surface.SetMaterial( self.DecalMat )
|
||||
surface.DrawTexturedRectRotated( 0, 0, 16 , 16 , self.RandomAng )
|
||||
cam.End3D2D()
|
||||
end
|
||||
|
||||
function ENT:DrawTranslucent()
|
||||
self:Draw()
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
function ENT:CreateBonePoseParameter( name, bone, ang_min, ang_max, pos_min, pos_max )
|
||||
if not istable( self._BonePoseParameters ) then self._BonePoseParameters = {} end
|
||||
|
||||
self._BonePoseParameters[ name ] = {
|
||||
bone = (bone or -1),
|
||||
ang_min = ang_min or angle_zero,
|
||||
ang_max = ang_max or angle_zero,
|
||||
pos_min = pos_min or vector_origin,
|
||||
pos_max = pos_max or vector_origin,
|
||||
}
|
||||
end
|
||||
|
||||
function ENT:SetBonePoseParameter( name, value )
|
||||
if name and string.StartsWith( name, "!" ) then
|
||||
name = string.Replace( name, "!", "" )
|
||||
end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not istable( EntTable._BonePoseParameters ) or not EntTable._BonePoseParameters[ name ] then return end
|
||||
|
||||
local data = EntTable._BonePoseParameters[ name ]
|
||||
|
||||
local ang = LerpAngle( value, data.ang_min, data.ang_max )
|
||||
local pos = LerpVector( value, data.pos_min, data.pos_max )
|
||||
|
||||
self:ManipulateBoneAngles( data.bone, ang )
|
||||
self:ManipulateBonePosition( data.bone, pos )
|
||||
end
|
||||
142
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_effects.lua
Normal file
142
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_effects.lua
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
function ENT:StartWindSounds()
|
||||
if not LVS.ShowEffects then return end
|
||||
|
||||
self:StopWindSounds()
|
||||
|
||||
if LocalPlayer():lvsGetVehicle() ~= self then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable._WindSFX = CreateSound( self, "LVS.Physics.Wind" )
|
||||
EntTable._WindSFX:PlayEx(0,100)
|
||||
|
||||
EntTable._WaterSFX = CreateSound( self, "LVS.Physics.Water" )
|
||||
EntTable._WaterSFX:PlayEx(0,100)
|
||||
end
|
||||
|
||||
function ENT:StopWindSounds()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if EntTable._WindSFX then
|
||||
EntTable._WindSFX:Stop()
|
||||
EntTable._WindSFX = nil
|
||||
end
|
||||
|
||||
if EntTable._WaterSFX then
|
||||
EntTable._WaterSFX:Stop()
|
||||
EntTable._WaterSFX = nil
|
||||
end
|
||||
end
|
||||
|
||||
ENT.DustEffectSurfaces = {
|
||||
["sand"] = true,
|
||||
["dirt"] = true,
|
||||
["grass"] = true,
|
||||
}
|
||||
|
||||
ENT.GroundEffectsMultiplier = 1
|
||||
|
||||
function ENT:DoVehicleFX()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if EntTable.GroundEffectsMultiplier <= 0 or not LVS.ShowEffects then self:StopWindSounds() return end
|
||||
|
||||
local Vel = self:GetVelocity():Length() * EntTable.GroundEffectsMultiplier
|
||||
|
||||
if EntTable._WindSFX then EntTable._WindSFX:ChangeVolume( math.Clamp( (Vel - 1200) / 2800,0,1 ), 0.25 ) end
|
||||
|
||||
if Vel < 1500 then
|
||||
if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 0, 0.25 ) end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if (EntTable.nextFX or 0) < CurTime() then
|
||||
EntTable.nextFX = CurTime() + 0.05
|
||||
|
||||
local LCenter = self:OBBCenter()
|
||||
LCenter.z = self:OBBMins().z
|
||||
|
||||
local CenterPos = self:LocalToWorld( LCenter )
|
||||
|
||||
local trace = util.TraceLine( {
|
||||
start = CenterPos + Vector(0,0,25),
|
||||
endpos = CenterPos - Vector(0,0,450),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
} )
|
||||
|
||||
local traceWater = util.TraceLine( {
|
||||
start = CenterPos + Vector(0,0,25),
|
||||
endpos = CenterPos - Vector(0,0,450),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
mask = MASK_WATER,
|
||||
} )
|
||||
|
||||
if EntTable._WaterSFX then EntTable._WaterSFX:ChangePitch( math.Clamp((Vel / 1000) * 50,80,150), 0.5 ) end
|
||||
|
||||
if traceWater.Hit and trace.HitPos.z < traceWater.HitPos.z then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( traceWater.HitPos )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_physics_water", effectdata )
|
||||
|
||||
if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 1 - math.Clamp(traceWater.Fraction,0,1), 0.5 ) end
|
||||
else
|
||||
if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 0, 0.25 ) end
|
||||
end
|
||||
|
||||
if trace.Hit and EntTable.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_physics_dust", effectdata )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetParticleEmitter( Pos )
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if IsValid( EntTable.Emitter ) and (EntTable.EmitterTime or 0) > T then
|
||||
return EntTable.Emitter
|
||||
end
|
||||
|
||||
self:StopEmitter()
|
||||
|
||||
EntTable.Emitter = ParticleEmitter( Pos, false )
|
||||
EntTable.EmitterTime = T + 2
|
||||
|
||||
return EntTable.Emitter
|
||||
end
|
||||
|
||||
function ENT:StopEmitter()
|
||||
if IsValid( self.Emitter ) then
|
||||
self.Emitter:Finish()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetParticleEmitter3D( Pos )
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if IsValid( EntTable.Emitter3D ) and (EntTable.EmitterTime3D or 0) > T then
|
||||
return EntTable.Emitter3D
|
||||
end
|
||||
|
||||
self:StopEmitter3D()
|
||||
|
||||
EntTable.Emitter3D = ParticleEmitter( Pos, true )
|
||||
EntTable.EmitterTime3D = T + 2
|
||||
|
||||
return EntTable.Emitter3D
|
||||
end
|
||||
|
||||
function ENT:StopEmitter3D()
|
||||
if IsValid( self.Emitter3D ) then
|
||||
self.Emitter3D:Finish()
|
||||
end
|
||||
end
|
||||
253
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_hud.lua
Normal file
253
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_hud.lua
Normal file
@@ -0,0 +1,253 @@
|
||||
|
||||
LVS:AddHudEditor( "VehicleHealth", 10, ScrH() - 85, 220, 75, 220, 75, "VEHICLE HEALTH",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintVehicleHealth then return end
|
||||
|
||||
vehicle:LVSHudPaintVehicleHealth( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
LVS:AddHudEditor( "VehicleInfo", ScrW() - 460, ScrH() - 85, 220, 75, 220, 75, "VEHICLE INFORMATION",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintInfoText then return end
|
||||
|
||||
vehicle:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
function ENT:LVSHudPaintVehicleHealth( X, Y, W, H, ScrX, ScrY, ply )
|
||||
draw.DrawText( "HEALTH ", "LVS_FONT", X + 102, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( math.Round( self:GetHP(), 0 ), "LVS_FONT_HUD_LARGE", X + 102, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
ENT.VehicleIdentifierRange = 10000
|
||||
|
||||
function ENT:LVSHudPaintVehicleIdentifier( X, Y, In_Col )
|
||||
local HP = self:GetHP()
|
||||
|
||||
surface.SetDrawColor( In_Col.r, In_Col.g, In_Col.b, In_Col.a )
|
||||
LVS:DrawDiamond( X + 1, Y + 1, 20, HP / self:GetMaxHP() )
|
||||
|
||||
if self:GetMaxShield() > 0 and HP > 0 then
|
||||
surface.SetDrawColor( 200, 200, 255, In_Col.a )
|
||||
LVS:DrawDiamond( X + 1, Y + 1, 24, self:GetShield() / self:GetMaxShield() )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSPreHudPaint( X, Y, ply )
|
||||
return true
|
||||
end
|
||||
|
||||
local zoom = 0
|
||||
local zoom_mat = Material( "vgui/zoom" )
|
||||
local zoom_switch = 0
|
||||
local zoom_blinder = 0
|
||||
local TargetZoom = 0
|
||||
|
||||
ENT.ZoomInSound = "weapons/sniper/sniper_zoomin.wav"
|
||||
ENT.ZoomOutSound = "weapons/sniper/sniper_zoomout.wav"
|
||||
|
||||
function ENT:GetZoom()
|
||||
return TargetZoom
|
||||
end
|
||||
|
||||
function ENT:PaintZoom( X, Y, ply )
|
||||
TargetZoom = ply:lvsKeyDown( "ZOOM" ) and 1 or 0
|
||||
|
||||
zoom = zoom + (TargetZoom - zoom) * RealFrameTime() * 10
|
||||
|
||||
if self.OpticsEnable then
|
||||
if self:GetOpticsEnabled() then
|
||||
if zoom_switch ~= TargetZoom then
|
||||
zoom_switch = TargetZoom
|
||||
|
||||
zoom_blinder = 1
|
||||
|
||||
if TargetZoom == 1 then
|
||||
surface.PlaySound( self.ZoomInSound )
|
||||
else
|
||||
surface.PlaySound( self.ZoomOutSound )
|
||||
end
|
||||
end
|
||||
|
||||
zoom_blinder = zoom_blinder - zoom_blinder * RealFrameTime() * 5
|
||||
|
||||
surface.SetDrawColor( Color(0,0,0,255 * zoom_blinder) )
|
||||
surface.DrawRect( 0, 0, X, Y )
|
||||
|
||||
self.ZoomFov = self.OpticsFov
|
||||
else
|
||||
self.ZoomFov = nil
|
||||
end
|
||||
end
|
||||
|
||||
X = X * 0.5
|
||||
Y = Y * 0.5
|
||||
|
||||
surface.SetDrawColor( Color(255,255,255,255 * zoom) )
|
||||
surface.SetMaterial(zoom_mat )
|
||||
surface.DrawTexturedRectRotated( X + X * 0.5, Y * 0.5, X, Y, 0 )
|
||||
surface.DrawTexturedRectRotated( X + X * 0.5, Y + Y * 0.5, Y, X, 270 )
|
||||
surface.DrawTexturedRectRotated( X * 0.5, Y * 0.5, Y, X, 90 )
|
||||
surface.DrawTexturedRectRotated( X * 0.5, Y + Y * 0.5, X, Y, 180 )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaint( X, Y, ply )
|
||||
if not self:LVSPreHudPaint( X, Y, ply ) then return end
|
||||
|
||||
self:PaintZoom( X, Y, ply )
|
||||
end
|
||||
|
||||
function ENT:HurtMarker( intensity )
|
||||
LocalPlayer():EmitSound( "lvs/hit_receive"..math.random(1,2)..".wav", 75, math.random(95,105), 0.25 + intensity * 0.75, CHAN_STATIC )
|
||||
util.ScreenShake( Vector(0, 0, 0), 25 * intensity, 25 * intensity, 0.5, 1 )
|
||||
end
|
||||
|
||||
function ENT:KillMarker()
|
||||
self.LastKillMarker = CurTime() + 0.5
|
||||
|
||||
LocalPlayer():EmitSound( "lvs/hit_kill.wav", 85, 100, 0.4, CHAN_VOICE )
|
||||
end
|
||||
|
||||
local LastMarker = 0
|
||||
function ENT:ArmorMarker( IsDamage )
|
||||
local T = CurTime()
|
||||
|
||||
local DontHurtEars = math.Clamp( T - LastMarker, 0, 1 ) ^ 2
|
||||
|
||||
LastMarker = T
|
||||
|
||||
local ArmorFailed = IsDamage and "takedamage" or "pen"
|
||||
local Volume = IsDamage and (0.3 * DontHurtEars) or 1
|
||||
|
||||
LocalPlayer():EmitSound( "lvs/armor_"..ArmorFailed.."_"..math.random(1,3)..".wav", 85, math.random(95,105), Volume, CHAN_ITEM2 )
|
||||
end
|
||||
|
||||
function ENT:HitMarker()
|
||||
self.LastHitMarker = CurTime() + 0.15
|
||||
|
||||
LocalPlayer():EmitSound( "lvs/hit.wav", 85, math.random(95,105), 0.4, CHAN_ITEM )
|
||||
end
|
||||
|
||||
function ENT:CritMarker()
|
||||
self.LastCritMarker = CurTime() + 0.15
|
||||
|
||||
LocalPlayer():EmitSound( "lvs/hit_crit.wav", 85, math.random(95,105), 0.4, CHAN_ITEM2 )
|
||||
end
|
||||
|
||||
function ENT:GetHitMarker()
|
||||
return self.LastHitMarker or 0
|
||||
end
|
||||
|
||||
function ENT:GetCritMarker()
|
||||
return self.LastCritMarker or 0
|
||||
end
|
||||
|
||||
function ENT:GetKillMarker()
|
||||
return self.LastKillMarker or 0
|
||||
end
|
||||
|
||||
function ENT:LVSPaintHitMarker( scr )
|
||||
local T = CurTime()
|
||||
|
||||
local aV = math.cos( math.rad( math.max(((self:GetHitMarker() - T) / 0.15) * 360,0) ) )
|
||||
if aV ~= 1 then
|
||||
local Start = 12 + (1 - aV) * 8
|
||||
local dst = 10
|
||||
|
||||
surface.SetDrawColor( 255, 255, 0, 255 )
|
||||
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start, scr.y + Start - dst )
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start - dst, scr.y + Start )
|
||||
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start, scr.y - Start + dst )
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start - dst, scr.y - Start )
|
||||
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start, scr.y + Start - dst )
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start + dst, scr.y + Start )
|
||||
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start, scr.y - Start + dst )
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start + dst, scr.y - Start )
|
||||
|
||||
scr.x = scr.x + 1
|
||||
scr.y = scr.y + 1
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 80 )
|
||||
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start, scr.y + Start - dst )
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start - dst, scr.y + Start )
|
||||
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start, scr.y - Start + dst )
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start - dst, scr.y - Start )
|
||||
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start, scr.y + Start - dst )
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start + dst, scr.y + Start )
|
||||
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start, scr.y - Start + dst )
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start + dst, scr.y - Start )
|
||||
end
|
||||
|
||||
local aV = math.sin( math.rad( math.max(((self:GetCritMarker() - T) / 0.15) * 180,0) ) )
|
||||
if aV > 0.01 then
|
||||
local Start = 10 + aV * 40
|
||||
local End = 20 + aV * 45
|
||||
|
||||
surface.SetDrawColor( 255, 100, 0, 255 )
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + End, scr.y + End )
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - End, scr.y + End )
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + End, scr.y - End )
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - End, scr.y - End )
|
||||
|
||||
draw.NoTexture()
|
||||
surface.DrawTexturedRectRotated( scr.x + Start, scr.y + Start, 3, 20, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x - Start, scr.y + Start, 20, 3, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x + Start, scr.y - Start, 20, 3, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x - Start, scr.y - Start, 3, 20, 45 )
|
||||
end
|
||||
|
||||
local aV = math.sin( math.rad( math.sin( math.rad( math.max(((self:GetKillMarker() - T) / 0.2) * 90,0) ) ) * 90 ) )
|
||||
if aV > 0.01 then
|
||||
surface.SetDrawColor( 255, 255, 255, 15 * (aV ^ 4) )
|
||||
surface.DrawRect( 0, 0, ScrW(), ScrH() )
|
||||
|
||||
local Start = 10 + aV * 40
|
||||
local End = 20 + aV * 45
|
||||
surface.SetDrawColor( 255, 0, 0, 255 )
|
||||
surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + End, scr.y + End )
|
||||
surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - End, scr.y + End )
|
||||
surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + End, scr.y - End )
|
||||
surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - End, scr.y - End )
|
||||
|
||||
draw.NoTexture()
|
||||
surface.DrawTexturedRectRotated( scr.x + Start, scr.y + Start, 5, 20, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x - Start, scr.y + Start, 20, 5, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x + Start, scr.y - Start, 20, 5, 45 )
|
||||
surface.DrawTexturedRectRotated( scr.x - Start, scr.y - Start, 5, 20, 45 )
|
||||
end
|
||||
end
|
||||
|
||||
local Circles = {
|
||||
[1] = {r = -1, col = Color(0,0,0,200)},
|
||||
[2] = {r = 0, col = Color(255,255,255,200)},
|
||||
[3] = {r = 1, col = Color(255,255,255,255)},
|
||||
[4] = {r = 2, col = Color(255,255,255,200)},
|
||||
[5] = {r = 3, col = Color(0,0,0,200)},
|
||||
}
|
||||
|
||||
function ENT:LVSDrawCircle( X, Y, target_radius, value )
|
||||
local endang = 360 * value
|
||||
|
||||
if endang == 0 then return end
|
||||
|
||||
for i = 1, #Circles do
|
||||
local data = Circles[ i ]
|
||||
local radius = target_radius + data.r
|
||||
local segmentdist = endang / ( math.pi * radius / 2 )
|
||||
|
||||
for a = 0, endang, segmentdist do
|
||||
surface.SetDrawColor( data.col )
|
||||
|
||||
surface.DrawLine( X - math.sin( math.rad( a ) ) * radius, Y + math.cos( math.rad( a ) ) * radius, X - math.sin( math.rad( a + segmentdist ) ) * radius, Y + math.cos( math.rad( a + segmentdist ) ) * radius )
|
||||
end
|
||||
end
|
||||
end
|
||||
224
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_init.lua
Normal file
224
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_init.lua
Normal file
@@ -0,0 +1,224 @@
|
||||
include("shared.lua")
|
||||
include("sh_weapons.lua")
|
||||
include("sh_velocity_changer.lua")
|
||||
include("sh_variables.lua")
|
||||
include("cl_effects.lua")
|
||||
include("cl_hud.lua")
|
||||
include("cl_optics.lua")
|
||||
include("cl_seatswitcher.lua")
|
||||
include("cl_trailsystem.lua")
|
||||
include("cl_boneposeparemeter.lua")
|
||||
|
||||
local Zoom = 0
|
||||
|
||||
function ENT:LVSCalcFov( fov, ply )
|
||||
|
||||
local TargetZoom = ply:lvsKeyDown( "ZOOM" ) and 0 or 1
|
||||
|
||||
Zoom = Zoom + (TargetZoom - Zoom) * RealFrameTime() * 10
|
||||
|
||||
local newfov = fov * Zoom + (self.ZoomFov or 40) * (1 - Zoom)
|
||||
|
||||
return newfov
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:PreDraw( flags )
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PreDrawTranslucent( flags )
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PostDraw( flags )
|
||||
end
|
||||
|
||||
function ENT:PostDrawTranslucent( flags )
|
||||
end
|
||||
|
||||
function ENT:Draw( flags )
|
||||
if self:PreDraw( flags ) then
|
||||
if self.lvsLegacyDraw then
|
||||
self:DrawModel() -- ugly, but required in order to fix old addons. Refract wont work on these.
|
||||
else
|
||||
self:DrawModel( flags )
|
||||
end
|
||||
end
|
||||
|
||||
self:PostDraw( flags )
|
||||
end
|
||||
|
||||
function ENT:DrawTranslucent( flags )
|
||||
self:DrawTrail()
|
||||
|
||||
if self:PreDrawTranslucent( flags ) then
|
||||
self:DrawModel( flags )
|
||||
else
|
||||
self.lvsLegacyDraw = true -- insert puke simley
|
||||
end
|
||||
|
||||
self:PostDrawTranslucent( flags )
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
self:OnSpawn()
|
||||
|
||||
if not istable( self.GibModels ) then return end
|
||||
|
||||
for _, modelName in ipairs( self.GibModels ) do
|
||||
util.PrecacheModel( modelName )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnSpawn()
|
||||
end
|
||||
|
||||
function ENT:OnFrameActive()
|
||||
end
|
||||
|
||||
function ENT:OnFrame()
|
||||
end
|
||||
|
||||
function ENT:OnEngineActiveChanged( Active )
|
||||
end
|
||||
|
||||
function ENT:OnActiveChanged( Active )
|
||||
end
|
||||
|
||||
ENT._oldActive = false
|
||||
ENT._oldEnActive = false
|
||||
|
||||
function ENT:HandleActive()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Active = self:GetActive()
|
||||
local EngineActive = self:GetEngineActive()
|
||||
local ActiveChanged = false
|
||||
|
||||
if EntTable._oldActive ~= Active then
|
||||
EntTable._oldActive = Active
|
||||
EntTable:OnActiveChanged( Active )
|
||||
ActiveChanged = true
|
||||
end
|
||||
|
||||
if EntTable._oldEnActive ~= EngineActive then
|
||||
EntTable._oldEnActive = EngineActive
|
||||
self:OnEngineActiveChanged( EngineActive )
|
||||
ActiveChanged = true
|
||||
end
|
||||
|
||||
if ActiveChanged then
|
||||
if Active or EngineActive then
|
||||
self:StartWindSounds()
|
||||
else
|
||||
self:StopWindSounds()
|
||||
end
|
||||
end
|
||||
|
||||
if Active or EngineActive then
|
||||
self:DoVehicleFX()
|
||||
end
|
||||
|
||||
self:FlyByThink()
|
||||
|
||||
return EngineActive
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if not self:IsInitialized() then return end
|
||||
|
||||
if self:HandleActive() then
|
||||
self:OnFrameActive()
|
||||
end
|
||||
|
||||
self:HandleTrail()
|
||||
self:OnFrame()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
self:StopEmitter()
|
||||
self:StopEmitter3D()
|
||||
self:StopWindSounds()
|
||||
self:StopFlyBy()
|
||||
self:StopDeathSound()
|
||||
|
||||
self:OnRemoved()
|
||||
end
|
||||
|
||||
function ENT:OnRemoved()
|
||||
end
|
||||
|
||||
function ENT:CalcDoppler( Ent )
|
||||
if not IsValid( Ent ) then return 1 end
|
||||
|
||||
if Ent:IsPlayer() then
|
||||
local ViewEnt = Ent:GetViewEntity()
|
||||
local Vehicle = Ent:lvsGetVehicle()
|
||||
|
||||
if IsValid( Vehicle ) then
|
||||
if Ent == ViewEnt then
|
||||
Ent = Vehicle
|
||||
end
|
||||
else
|
||||
if IsValid( ViewEnt ) then
|
||||
Ent = ViewEnt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sVel = self:GetVelocity()
|
||||
local oVel = Ent:GetVelocity()
|
||||
|
||||
local SubVel = oVel - sVel
|
||||
local SubPos = self:GetPos() - Ent:GetPos()
|
||||
|
||||
local DirPos = SubPos:GetNormalized()
|
||||
local DirVel = SubVel:GetNormalized()
|
||||
|
||||
local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) )
|
||||
|
||||
return (1 + math.cos( A ) * SubVel:Length() / 13503.9)
|
||||
end
|
||||
|
||||
function ENT:GetCrosshairFilterEnts()
|
||||
if not self:IsInitialized() or not LVS.MapDoneLoading then return { self } end -- wait for the server to be ready
|
||||
|
||||
if not istable( self.CrosshairFilterEnts ) then
|
||||
self.CrosshairFilterEnts = {self}
|
||||
|
||||
timer.Simple(1, function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
-- lets ask the server to build the filter for us because it has access to constraint.GetAllConstrainedEntities()
|
||||
net.Start( "lvs_player_request_filter" )
|
||||
net.WriteEntity( self )
|
||||
net.SendToServer()
|
||||
end)
|
||||
end
|
||||
|
||||
return self.CrosshairFilterEnts
|
||||
end
|
||||
|
||||
function ENT:FlyByThink()
|
||||
end
|
||||
|
||||
function ENT:StopFlyBy()
|
||||
end
|
||||
|
||||
function ENT:StopDeathSound()
|
||||
end
|
||||
|
||||
function ENT:OnDestroyed()
|
||||
end
|
||||
|
||||
net.Receive( "lvs_vehicle_destroy", function( len )
|
||||
local ent = net.ReadEntity()
|
||||
|
||||
if not IsValid( ent ) or not isfunction( ent.OnDestroyed ) then return end
|
||||
|
||||
ent:OnDestroyed()
|
||||
end )
|
||||
208
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_optics.lua
Normal file
208
garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_optics.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
ENT.OpticsFov = 30
|
||||
ENT.OpticsEnable = false
|
||||
ENT.OpticsZoomOnly = true
|
||||
ENT.OpticsFirstPerson = true
|
||||
ENT.OpticsThirdPerson = true
|
||||
ENT.OpticsPodIndex = {
|
||||
[1] = true,
|
||||
}
|
||||
|
||||
ENT.OpticsCrosshairMaterial = Material( "vgui/circle" )
|
||||
ENT.OpticsCrosshairColor = Color(0,0,0,255)
|
||||
ENT.OpticsCrosshairSize = 5
|
||||
|
||||
function ENT:PaintOpticsCrosshair( Pos2D )
|
||||
if not Pos2D.visible then return end
|
||||
|
||||
local size = self.OpticsCrosshairSize
|
||||
|
||||
surface.SetMaterial( self.OpticsCrosshairMaterial )
|
||||
surface.SetDrawColor( self.OpticsCrosshairColor )
|
||||
surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size )
|
||||
end
|
||||
|
||||
function ENT:CalcOpticsCrosshairDot( Pos2D )
|
||||
self:PaintOpticsCrosshair( Pos2D )
|
||||
end
|
||||
|
||||
function ENT:GetOpticsEnabled()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.OpticsEnable then return false end
|
||||
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return false end
|
||||
|
||||
local pod = ply:GetVehicle()
|
||||
local PodIndex = pod:lvsGetPodIndex()
|
||||
if pod == self:GetDriverSeat() then
|
||||
PodIndex = 1
|
||||
end
|
||||
|
||||
if EntTable.OpticsPodIndex[ PodIndex ] then
|
||||
if pod:GetThirdPersonMode() then
|
||||
return EntTable.OpticsThirdPerson
|
||||
else
|
||||
return EntTable.OpticsFirstPerson
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:UseOptics()
|
||||
if self.OpticsZoomOnly and self:GetZoom() ~= 1 then return false end
|
||||
|
||||
return self:GetOpticsEnabled()
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairCenter( Pos2D, Col )
|
||||
if self:UseOptics() then
|
||||
if self.OpticsScreenCentered then
|
||||
self:CalcOpticsCrosshairDot( Pos2D )
|
||||
|
||||
local ScreenCenter2D = {
|
||||
x = ScrW() * 0.5,
|
||||
y = ScrH() * 0.5,
|
||||
visible = true,
|
||||
}
|
||||
|
||||
self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 1 )
|
||||
else
|
||||
self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 1 )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not Col then
|
||||
Col = Color( 255, 255, 255, 255 )
|
||||
end
|
||||
|
||||
local Alpha = Col.a / 255
|
||||
local Shadow = Color( 0, 0, 0, 80 * Alpha )
|
||||
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 4, Shadow )
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 5, Col )
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 6, Shadow )
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairOuter( Pos2D, Col )
|
||||
if self:UseOptics() then
|
||||
if self.OpticsScreenCentered then
|
||||
self:CalcOpticsCrosshairDot( Pos2D )
|
||||
|
||||
local ScreenCenter2D = {
|
||||
x = ScrW() * 0.5,
|
||||
y = ScrH() * 0.5,
|
||||
visible = true,
|
||||
}
|
||||
|
||||
self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 2 )
|
||||
else
|
||||
self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 2 )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not Col then
|
||||
Col = Color( 255, 255, 255, 255 )
|
||||
end
|
||||
|
||||
local Alpha = Col.a / 255
|
||||
local Shadow = Color( 0, 0, 0, 80 * Alpha )
|
||||
|
||||
surface.DrawCircle( Pos2D.x,Pos2D.y, 17, Shadow )
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 18, Col )
|
||||
|
||||
if LVS.AntiAliasingEnabled then
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 19, Color( Col.r, Col.g, Col.b, 150 * Alpha ) )
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 20, Shadow )
|
||||
else
|
||||
surface.DrawCircle( Pos2D.x, Pos2D.y, 19, Shadow )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairSquare( Pos2D, Col )
|
||||
if self:UseOptics() then
|
||||
if self.OpticsScreenCentered then
|
||||
self:CalcOpticsCrosshairDot( Pos2D )
|
||||
|
||||
local ScreenCenter2D = {
|
||||
x = ScrW() * 0.5,
|
||||
y = ScrH() * 0.5,
|
||||
visible = true,
|
||||
}
|
||||
|
||||
self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 3 )
|
||||
else
|
||||
self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 3 )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not Col then
|
||||
Col = Color( 255, 255, 255, 255 )
|
||||
end
|
||||
|
||||
local X = Pos2D.x + 1
|
||||
local Y = Pos2D.y + 1
|
||||
|
||||
local Size = 20
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 80 )
|
||||
surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size )
|
||||
surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size )
|
||||
surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 )
|
||||
surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 )
|
||||
surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 )
|
||||
surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 )
|
||||
surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size )
|
||||
surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size )
|
||||
|
||||
if Col then
|
||||
surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a )
|
||||
else
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
end
|
||||
|
||||
X = Pos2D.x
|
||||
Y = Pos2D.y
|
||||
|
||||
surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size )
|
||||
surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size )
|
||||
surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 )
|
||||
surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 )
|
||||
surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 )
|
||||
surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 )
|
||||
surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size )
|
||||
surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size )
|
||||
end
|
||||
|
||||
function ENT:DrawRotatedText( text, x, y, font, color, ang)
|
||||
render.PushFilterMag( TEXFILTER.ANISOTROPIC )
|
||||
render.PushFilterMin( TEXFILTER.ANISOTROPIC )
|
||||
|
||||
local m = Matrix()
|
||||
m:Translate( Vector( x, y, 0 ) )
|
||||
m:Rotate( Angle( 0, ang, 0 ) )
|
||||
|
||||
surface.SetFont( font )
|
||||
local w, h = surface.GetTextSize( text )
|
||||
|
||||
m:Translate( -Vector( w / 2, h / 2, 0 ) )
|
||||
|
||||
cam.PushModelMatrix( m )
|
||||
draw.DrawText( text, font, 0, 0, color )
|
||||
cam.PopModelMatrix()
|
||||
|
||||
render.PopFilterMag()
|
||||
render.PopFilterMin()
|
||||
end
|
||||
|
||||
function ENT:PaintOptics( Pos2D, Col, PodIndex, Type )
|
||||
end
|
||||
@@ -0,0 +1,147 @@
|
||||
|
||||
ENT.IconVehicleLocked = Material( "lvs/locked.png" )
|
||||
|
||||
LVS:AddHudEditor( "SeatSwitcher", ScrW() - 360, 10, 350, 60, 350, 60, "SEAT SWITCHER",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintSeatSwitcher then return end
|
||||
|
||||
vehicle:LVSHudPaintSeatSwitcher( X, Y, W, 30, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
function ENT:LVSHudPaintSeatSwitcher( X, Y, w, h, ScrX, ScrY, ply )
|
||||
local pSeats = table.Copy( self:GetPassengerSeats() )
|
||||
local SeatCount = table.Count( pSeats )
|
||||
|
||||
if SeatCount <= 0 then return end
|
||||
|
||||
pSeats[0] = self:GetDriverSeat()
|
||||
|
||||
draw.NoTexture()
|
||||
|
||||
local HasAI = self:GetAI()
|
||||
local HasAIGunners = self:GetAIGunners()
|
||||
|
||||
local MySeat = ply:GetVehicle():lvsGetPodIndex()
|
||||
|
||||
local Passengers = {}
|
||||
for _, player in pairs( player.GetAll() ) do
|
||||
if player:lvsGetVehicle() == self then
|
||||
local Pod = player:GetVehicle()
|
||||
Passengers[ Pod:lvsGetPodIndex() ] = player:GetName()
|
||||
end
|
||||
end
|
||||
|
||||
if HasAI then
|
||||
Passengers[1] = "[AI] "..self.PrintName
|
||||
end
|
||||
|
||||
if HasAIGunners then
|
||||
for _, Pod in pairs( self:GetPassengerSeats() ) do
|
||||
if IsValid( Pod:GetDriver() ) then continue end
|
||||
|
||||
local weapon = Pod:lvsGetWeapon()
|
||||
|
||||
if not IsValid( weapon ) then continue end
|
||||
|
||||
Passengers[ weapon:GetPodIndex() ] = "[AI] Gunner"
|
||||
end
|
||||
end
|
||||
|
||||
ply.SwitcherTime = ply.SwitcherTime or 0
|
||||
ply._lvsoldPassengers = ply._lvsoldPassengers or {}
|
||||
|
||||
local Time = CurTime()
|
||||
for k, v in pairs( Passengers ) do
|
||||
if ply._lvsoldPassengers[k] ~= v then
|
||||
ply._lvsoldPassengers[k] = v
|
||||
ply.SwitcherTime = Time + 2
|
||||
end
|
||||
end
|
||||
for k, v in pairs( ply._lvsoldPassengers ) do
|
||||
if not Passengers[k] then
|
||||
ply._lvsoldPassengers[k] = nil
|
||||
ply.SwitcherTime = Time + 2
|
||||
end
|
||||
end
|
||||
for _, v in pairs( LVS.pSwitchKeysInv ) do
|
||||
if input.IsKeyDown(v) then
|
||||
ply.SwitcherTime = Time + 2
|
||||
end
|
||||
end
|
||||
|
||||
local Hide = ply.SwitcherTime > Time
|
||||
|
||||
ply.smHider = ply.smHider and (ply.smHider + ((Hide and 1 or 0) - ply.smHider) * RealFrameTime() * 15) or 0
|
||||
|
||||
local Alpha1 = 135 + 110 * ply.smHider
|
||||
local HiderOffset = 270 * ply.smHider
|
||||
local xPos = w - 35
|
||||
local yPos = Y - (SeatCount + 1) * 30 + h + 5
|
||||
|
||||
local SwapY = false
|
||||
local SwapX = false
|
||||
|
||||
local xHider = HiderOffset
|
||||
|
||||
if X < (ScrX * 0.5 - w * 0.5) then
|
||||
SwapX = true
|
||||
xPos = 0
|
||||
xHider = 0
|
||||
end
|
||||
|
||||
if Y < (ScrY * 0.5 - h * 0.5) then
|
||||
SwapY = true
|
||||
yPos = Y - h
|
||||
end
|
||||
|
||||
for _, Pod in pairs( pSeats ) do
|
||||
if not IsValid( Pod ) then continue end
|
||||
|
||||
local I = Pod:lvsGetPodIndex()
|
||||
|
||||
if I <= 0 then continue end
|
||||
|
||||
if not Hide and I > 10 then continue end
|
||||
|
||||
if I == MySeat then
|
||||
draw.RoundedBox(5, X + xPos - xHider, yPos + I * 30, 35 + HiderOffset, 25, Color(LVS.ThemeColor.r, LVS.ThemeColor.g, LVS.ThemeColor.b,100 + 50 * ply.smHider) )
|
||||
else
|
||||
draw.RoundedBox(5, X + xPos - xHider, yPos + I * 30, 35 + HiderOffset, 25, Color(0,0,0,100 + 50 * ply.smHider) )
|
||||
end
|
||||
|
||||
if Hide then
|
||||
if Passengers[I] then
|
||||
draw.DrawText( Passengers[I], "LVS_FONT_SWITCHER", X + 40 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_LEFT )
|
||||
else
|
||||
draw.DrawText( "-", "LVS_FONT_SWITCHER", X + 40 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
draw.DrawText( "["..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER )
|
||||
else
|
||||
if Passengers[I] then
|
||||
draw.DrawText( "[^"..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER )
|
||||
else
|
||||
draw.DrawText( "["..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
if not self:GetlvsLockedStatus() then continue end
|
||||
|
||||
local xLocker = SwapX and 35 + HiderOffset or -25 - HiderOffset
|
||||
|
||||
if SwapY then
|
||||
if I == 1 then
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
surface.SetMaterial( self.IconVehicleLocked )
|
||||
surface.DrawTexturedRect( X + xPos + xLocker, yPos + I * 30, 25, 25 )
|
||||
end
|
||||
else
|
||||
if I == SeatCount then
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
surface.SetMaterial( self.IconVehicleLocked )
|
||||
surface.DrawTexturedRect( X + xPos + xLocker, yPos + I * 30, 25, 25 )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,147 @@
|
||||
|
||||
ENT.TrailMaterial = Material( "trails/smoke" )
|
||||
ENT.TrailRed = 255
|
||||
ENT.TrailGreen = 255
|
||||
ENT.TrailBlue = 255
|
||||
ENT.TrailAlpha = 100
|
||||
|
||||
function ENT:OnTrail( active, id )
|
||||
end
|
||||
|
||||
function ENT:HandleTrail()
|
||||
if not self.RegisteredTrailPositions then return end
|
||||
|
||||
local FT = RealFrameTime()
|
||||
|
||||
local pos = self:GetPos()
|
||||
local vel = self:GetVelocity()
|
||||
local vel_length = vel:Length()
|
||||
|
||||
for id, data in pairs( self.RegisteredTrailPositions ) do
|
||||
local cur_pos = self:LocalToWorld( data.pos )
|
||||
local cur_vel = (cur_pos - data.oldpos) / FT
|
||||
|
||||
local cur_velL = math.abs( self:WorldToLocal( pos + cur_vel ).z )
|
||||
|
||||
if cur_velL > data.activation_speed and vel_length > data.min_flight_speed then
|
||||
if not data.id then
|
||||
data.id = self:StartTrail( data.pos, data.startsize, data.endsize, data.lifetime )
|
||||
self:OnTrail( true, data.id )
|
||||
end
|
||||
else
|
||||
if data.id then
|
||||
self:OnTrail( false, data.id )
|
||||
self:FinishTrail( data.id )
|
||||
data.id = nil
|
||||
end
|
||||
end
|
||||
|
||||
data.oldpos = cur_pos
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:RegisterTrail( Pos, StartSize, EndSize, LifeTime, min_flight_speed, activation_speed )
|
||||
if not istable( self.RegisteredTrailPositions ) then
|
||||
self.RegisteredTrailPositions = {}
|
||||
end
|
||||
|
||||
local data = {
|
||||
pos = Pos,
|
||||
oldpos = self:LocalToWorld( Pos ),
|
||||
startsize = StartSize,
|
||||
endsize = EndSize,
|
||||
lifetime = LifeTime,
|
||||
min_flight_speed = min_flight_speed,
|
||||
activation_speed = activation_speed,
|
||||
}
|
||||
|
||||
table.insert( self.RegisteredTrailPositions, data )
|
||||
end
|
||||
|
||||
function ENT:StartTrail( Pos, StartSize, EndSize, LifeTime )
|
||||
if not LVS.ShowTraileffects then return end
|
||||
|
||||
if not istable( self.TrailActive ) then
|
||||
self.TrailActive = {}
|
||||
end
|
||||
|
||||
local ID = 1
|
||||
for _,_ in ipairs( self.TrailActive ) do
|
||||
ID = ID + 1
|
||||
end
|
||||
|
||||
self.TrailActive[ ID ] = {
|
||||
lifetime = LifeTime,
|
||||
start_size = StartSize,
|
||||
end_size = EndSize,
|
||||
pos = Pos,
|
||||
active = true,
|
||||
positions = {},
|
||||
}
|
||||
|
||||
return ID
|
||||
end
|
||||
|
||||
function ENT:FinishTrail( ID )
|
||||
self.TrailActive[ ID ].active = false
|
||||
end
|
||||
|
||||
function ENT:DrawTrail()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.TrailActive then return end
|
||||
|
||||
local Time = CurTime()
|
||||
|
||||
EntTable._NextTrail = EntTable._NextTrail or 0
|
||||
|
||||
local Set = EntTable._NextTrail < Time
|
||||
|
||||
render.SetMaterial( EntTable.TrailMaterial )
|
||||
|
||||
for ID, data in pairs( EntTable.TrailActive ) do
|
||||
|
||||
for pos_id, pos_data in pairs( data.positions ) do
|
||||
if Time - pos_data.time > data.lifetime then
|
||||
data.positions[ pos_id ] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if Set then
|
||||
if data.active then
|
||||
local cur_pos = {
|
||||
time = Time,
|
||||
pos = self:LocalToWorld( data.pos ),
|
||||
}
|
||||
|
||||
table.insert( data.positions, cur_pos )
|
||||
table.sort( data.positions, function( a, b ) return a.time > b.time end )
|
||||
end
|
||||
end
|
||||
|
||||
local num = #data.positions
|
||||
|
||||
if num == 0 then
|
||||
if not data.active then
|
||||
EntTable.TrailActive[ ID ] = nil
|
||||
end
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
render.StartBeam( num )
|
||||
|
||||
for _, pos_data in ipairs( data.positions ) do
|
||||
local Scale = (pos_data.time + data.lifetime - Time) / data.lifetime
|
||||
local InvScale = 1 - Scale
|
||||
|
||||
render.AddBeam( pos_data.pos, data.start_size * Scale + data.end_size * InvScale, pos_data.time * 50, Color( EntTable.TrailRed, EntTable.TrailGreen, EntTable.TrailBlue, EntTable.TrailAlpha * Scale ^ 2 ) )
|
||||
end
|
||||
|
||||
render.EndBeam()
|
||||
end
|
||||
|
||||
if Set then
|
||||
EntTable._NextTrail = Time + 0.025
|
||||
end
|
||||
end
|
||||
509
garrysmod/addons/lvs_base/lua/entities/lvs_base/init.lua
Normal file
509
garrysmod/addons/lvs_base/lua/entities/lvs_base/init.lua
Normal file
@@ -0,0 +1,509 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "sh_weapons.lua" )
|
||||
AddCSLuaFile( "sh_velocity_changer.lua" )
|
||||
AddCSLuaFile( "sh_variables.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_effects.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "cl_optics.lua" )
|
||||
AddCSLuaFile( "cl_trailsystem.lua" )
|
||||
AddCSLuaFile( "cl_seatswitcher.lua" )
|
||||
AddCSLuaFile( "cl_boneposeparemeter.lua" )
|
||||
include("shared.lua")
|
||||
include("sh_weapons.lua")
|
||||
include("sh_velocity_changer.lua")
|
||||
include("sh_variables.lua")
|
||||
include("sv_ai.lua")
|
||||
include("sv_cppi.lua")
|
||||
include("sv_duping.lua")
|
||||
include("sv_pod.lua")
|
||||
include("sv_engine.lua")
|
||||
include("sv_physics.lua")
|
||||
include("sv_physics_damagesystem.lua")
|
||||
include("sv_damagesystem.lua")
|
||||
include("sv_shieldsystem.lua")
|
||||
include("sv_doorsystem.lua")
|
||||
|
||||
ENT.WaterLevelPreventStart = 1
|
||||
ENT.WaterLevelAutoStop = 2
|
||||
ENT.WaterLevelDestroyAI = 2
|
||||
|
||||
function ENT:SpawnFunction( ply, tr, ClassName )
|
||||
|
||||
if ply:InVehicle() then
|
||||
local ent = ents.Create( ClassName )
|
||||
ent:StoreCPPI( ply )
|
||||
ent:SetPos( ply:GetPos() + Vector(0,0,100 + ent.SpawnNormalOffset) )
|
||||
ent:SetAngles( Angle(0, ply:EyeAngles().y + ent.SpawnAngleOffset, 0 ) )
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return ent
|
||||
else
|
||||
if not tr.Hit then return end
|
||||
|
||||
local ent = ents.Create( ClassName )
|
||||
ent:StoreCPPI( ply )
|
||||
ent:SetPos( tr.HitPos + tr.HitNormal * ent.SpawnNormalOffset )
|
||||
ent:SetAngles( Angle(0, ply:EyeAngles().y + ent.SpawnAngleOffset, 0 ) )
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return ent
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
self:SetModel( self.MDL )
|
||||
|
||||
self:PhysicsInit( SOLID_VPHYSICS, self.MassCenterOverride )
|
||||
self:SetMoveType( MOVETYPE_VPHYSICS )
|
||||
self:SetSolid( SOLID_VPHYSICS )
|
||||
self:SetUseType( SIMPLE_USE )
|
||||
self:SetRenderMode( RENDERMODE_NORMAL )
|
||||
|
||||
-- this is so vj npcs can still see us
|
||||
self:AddEFlags( EFL_DONTBLOCKLOS )
|
||||
|
||||
-- this is for our npc relationship system to work
|
||||
self:AddFlags( FL_OBJECT )
|
||||
|
||||
local PObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PObj ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: missing model. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
PObj:SetMaterial( "default_silent" )
|
||||
PObj:EnableMotion( false )
|
||||
PObj:EnableDrag( false )
|
||||
|
||||
timer.Simple(0, function()
|
||||
if not IsValid( self ) or not IsValid( PObj ) then print("LVS: ERROR couldn't initialize vehicle.") return end
|
||||
|
||||
self:PostInitialize( PObj )
|
||||
end)
|
||||
|
||||
if not istable( self.GibModels ) then return end
|
||||
|
||||
for _, modelName in ipairs( self.GibModels ) do
|
||||
util.PrecacheModel( modelName )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PostInitialize( PObj )
|
||||
local SpawnSuccess, ErrorMsg = pcall( function() self:OnSpawn( PObj ) end )
|
||||
|
||||
if not SpawnSuccess then
|
||||
ErrorNoHalt( "\n[ERROR] "..ErrorMsg.."\n\n" )
|
||||
end
|
||||
|
||||
self:StartMotionController()
|
||||
|
||||
self:AutoAI()
|
||||
|
||||
self:CallOnRemove( "finish_weapons_on_delete", function( ent )
|
||||
ent:WeaponsFinish()
|
||||
|
||||
for _, pod in pairs( ent:GetPassengerSeats() ) do
|
||||
if not IsValid( pod ) then continue end
|
||||
|
||||
local weapon = pod:lvsGetWeapon()
|
||||
|
||||
if not IsValid( weapon ) or not weapon._activeWeapon then continue end
|
||||
|
||||
local CurWeapon = self.WEAPONS[ weapon:GetPodIndex() ][ weapon._activeWeapon ]
|
||||
|
||||
if not CurWeapon then continue end
|
||||
|
||||
if CurWeapon.FinishAttack then
|
||||
CurWeapon.FinishAttack( weapon )
|
||||
end
|
||||
end
|
||||
|
||||
ent:WeaponsOnRemove()
|
||||
end)
|
||||
|
||||
self:SetlvsReady( true )
|
||||
self:GetCrosshairFilterEnts()
|
||||
|
||||
self:OnSpawnFinish( PObj )
|
||||
end
|
||||
|
||||
function ENT:OnSpawnFinish( PObj )
|
||||
if GetConVar( "developer" ):GetInt() ~= 1 then
|
||||
PObj:EnableMotion( true )
|
||||
end
|
||||
|
||||
self:PhysWake()
|
||||
end
|
||||
|
||||
function ENT:OnSpawn( PObj )
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:NextThink( CurTime() )
|
||||
|
||||
if not self:IsInitialized() then return true end
|
||||
|
||||
self:HandleActive()
|
||||
self:HandleStart()
|
||||
self:PhysicsThink()
|
||||
self:DamageThink()
|
||||
self:WeaponsThink()
|
||||
self:ShieldThink()
|
||||
|
||||
if self:GetAI() then self:RunAI() end
|
||||
|
||||
self:OnTick()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:OnDriverChanged( Old, New, VehicleIsActive )
|
||||
self:OnPassengerChanged( Old, New, 1 )
|
||||
end
|
||||
|
||||
function ENT:OnPassengerChanged( Old, New, PodIndex )
|
||||
end
|
||||
|
||||
function ENT:OnSwitchSeat( ply, oldpod, newpod )
|
||||
end
|
||||
|
||||
function ENT:OnTick()
|
||||
end
|
||||
|
||||
function ENT:OnRemoved()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
self:OnRemoved()
|
||||
end
|
||||
|
||||
function ENT:Lock()
|
||||
for _, Handler in pairs( self:GetDoorHandlers() ) do
|
||||
if not IsValid( Handler ) then continue end
|
||||
|
||||
Handler:Close( ply )
|
||||
end
|
||||
|
||||
if self:GetlvsLockedStatus() then return end
|
||||
|
||||
self:SetlvsLockedStatus( true )
|
||||
self:EmitSound( "doors/latchlocked2.wav" )
|
||||
end
|
||||
|
||||
function ENT:UnLock()
|
||||
if not self:GetlvsLockedStatus() then return end
|
||||
|
||||
self:SetlvsLockedStatus( false )
|
||||
self:EmitSound( "doors/latchunlocked1.wav" )
|
||||
end
|
||||
|
||||
function ENT:IsUseAllowed( ply )
|
||||
if not IsValid( ply ) then return false end
|
||||
|
||||
if (ply._lvsNextUse or 0) > CurTime() then return false end
|
||||
|
||||
if self:GetlvsLockedStatus() or (LVS.TeamPassenger and ((self:GetAITEAM() ~= ply:lvsGetAITeam()) and ply:lvsGetAITeam() ~= 0 and self:GetAITEAM() ~= 0)) then
|
||||
self:EmitSound( "doors/default_locked.wav" )
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:Use( ply )
|
||||
if not self:IsUseAllowed( ply ) then return end
|
||||
|
||||
if not istable( self._DoorHandlers ) then
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if ply:GetMoveType() == MOVETYPE_NOCLIP then
|
||||
ply._lvsNextExit = CurTime() + 0.5
|
||||
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if ply:KeyDown( IN_SPEED ) then return end
|
||||
|
||||
local Handler = self:GetDoorHandler( ply )
|
||||
|
||||
if not IsValid( Handler ) then
|
||||
if self:HasDoorSystem() and ply:GetMoveType() == MOVETYPE_WALK then
|
||||
return
|
||||
end
|
||||
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local Pod = Handler:GetLinkedSeat()
|
||||
|
||||
if not IsValid( Pod ) then Handler:Use( ply ) return end
|
||||
|
||||
if not Handler:IsOpen() then Handler:Open( ply ) return end
|
||||
|
||||
if Handler:IsOpen() then
|
||||
Handler:Close( ply )
|
||||
else
|
||||
Handler:OpenAndClose( ply )
|
||||
end
|
||||
|
||||
if ply:KeyDown( IN_WALK ) then
|
||||
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local DriverSeat = self:GetDriverSeat()
|
||||
|
||||
if Pod ~= self:GetDriverSeat() then
|
||||
if IsValid( Pod:GetDriver() ) then
|
||||
self:SetPassenger( ply )
|
||||
else
|
||||
ply:EnterVehicle( Pod )
|
||||
self:AlignView( ply )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if self:GetAI() then
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if IsValid( Pod:GetDriver() ) then
|
||||
self:SetPassenger( ply )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if hook.Run( "LVS.CanPlayerDrive", ply, self ) ~= false then
|
||||
ply:EnterVehicle( Pod )
|
||||
self:AlignView( ply )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
else
|
||||
hook.Run( "LVS.OnPlayerCannotDrive", ply, self )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo )
|
||||
self:CalcShieldDamage( dmginfo )
|
||||
self:CalcDamage( dmginfo )
|
||||
self:TakePhysicsDamage( dmginfo )
|
||||
self:OnAITakeDamage( dmginfo )
|
||||
self:RemoveAllDecals()
|
||||
end
|
||||
|
||||
function ENT:OnMaintenance()
|
||||
end
|
||||
|
||||
function ENT:UpdateTransmitState()
|
||||
return TRANSMIT_ALWAYS
|
||||
end
|
||||
|
||||
function ENT:GetMissileOffset()
|
||||
return self:OBBCenter()
|
||||
end
|
||||
|
||||
function ENT:RebuildCrosshairFilterEnts()
|
||||
self.CrosshairFilterEnts = nil
|
||||
|
||||
local CrosshairFilterEnts = table.Copy( self:GetCrosshairFilterEnts() )
|
||||
|
||||
for id, entity in pairs( CrosshairFilterEnts ) do
|
||||
if not IsValid( entity ) or entity:GetNoDraw() then
|
||||
CrosshairFilterEnts[ id ] = nil
|
||||
end
|
||||
end
|
||||
|
||||
net.Start( "lvs_player_request_filter" )
|
||||
net.WriteEntity( self )
|
||||
net.WriteTable( CrosshairFilterEnts )
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
function ENT:GetCrosshairFilterEnts()
|
||||
if not self:IsInitialized() then return { self } end
|
||||
|
||||
if not istable( self.CrosshairFilterEnts ) then
|
||||
self.CrosshairFilterEnts = {}
|
||||
|
||||
for _, Entity in pairs( constraint.GetAllConstrainedEntities( self ) ) do
|
||||
if not IsValid( Entity ) then continue end
|
||||
|
||||
table.insert( self.CrosshairFilterEnts , Entity )
|
||||
end
|
||||
|
||||
for _, Parent in pairs( self.CrosshairFilterEnts ) do
|
||||
for _, Child in pairs( Parent:GetChildren() ) do
|
||||
if not IsValid( Child ) then continue end
|
||||
|
||||
table.insert( self.CrosshairFilterEnts , Child )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self.CrosshairFilterEnts
|
||||
end
|
||||
|
||||
function ENT:LVSFireBullet( data )
|
||||
data.Entity = self
|
||||
data.Velocity = data.Velocity + self:GetVelocity():Length()
|
||||
data.SrcEntity = self:WorldToLocal( data.Src )
|
||||
|
||||
LVS:FireBullet( data )
|
||||
end
|
||||
|
||||
function ENT:AddSoundEmitter( pos, snd, snd_interior )
|
||||
local Emitter = ents.Create( "lvs_soundemitter" )
|
||||
|
||||
if not IsValid( Emitter ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create sound emitter entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Emitter:SetPos( self:LocalToWorld( pos ) )
|
||||
Emitter:SetAngles( self:GetAngles() )
|
||||
Emitter:Spawn()
|
||||
Emitter:Activate()
|
||||
Emitter:SetParent( self )
|
||||
Emitter:SetBase( self )
|
||||
|
||||
if snd and not snd_interior then
|
||||
Emitter:SetSound( snd )
|
||||
Emitter:SetSoundInterior( snd )
|
||||
else
|
||||
Emitter:SetSound( snd or "" )
|
||||
Emitter:SetSoundInterior( snd_interior )
|
||||
end
|
||||
|
||||
self:DeleteOnRemove( Emitter )
|
||||
|
||||
self:TransferCPPI( Emitter )
|
||||
|
||||
return Emitter
|
||||
end
|
||||
|
||||
function ENT:AddFlameEmitter( target, attachment )
|
||||
if not IsValid( target ) then return end
|
||||
|
||||
local FlameEmitter = ents.Create( "lvs_firestreamemitter" )
|
||||
FlameEmitter:AttachTo( target, attachment )
|
||||
FlameEmitter:Spawn()
|
||||
FlameEmitter:Activate()
|
||||
|
||||
return FlameEmitter
|
||||
end
|
||||
|
||||
function ENT:AddAmmoRack( pos, fxpos, ang, mins, maxs, target )
|
||||
local AmmoRack = ents.Create( "lvs_wheeldrive_ammorack" )
|
||||
|
||||
if not IsValid( AmmoRack ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create fueltank entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not target then target = self end
|
||||
|
||||
AmmoRack:SetPos( target:LocalToWorld( pos ) )
|
||||
AmmoRack:SetAngles( target:GetAngles() )
|
||||
AmmoRack:Spawn()
|
||||
AmmoRack:Activate()
|
||||
AmmoRack:SetParent( target )
|
||||
AmmoRack:SetBase( self )
|
||||
AmmoRack:SetEffectPosition( fxpos )
|
||||
|
||||
self:DeleteOnRemove( AmmoRack )
|
||||
|
||||
self:TransferCPPI( AmmoRack )
|
||||
|
||||
mins = mins or Vector(-30,-30,-30)
|
||||
maxs = maxs or Vector(30,30,30)
|
||||
|
||||
debugoverlay.BoxAngles( target:LocalToWorld( pos ), mins, maxs, target:LocalToWorldAngles( ang ), 15, Color( 255, 0, 0, 255 ) )
|
||||
|
||||
self:AddDS( {
|
||||
pos = pos,
|
||||
ang = ang,
|
||||
mins = mins,
|
||||
maxs = maxs,
|
||||
entity = target,
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
if not IsValid( AmmoRack ) then return end
|
||||
|
||||
AmmoRack:TakeTransmittedDamage( dmginfo )
|
||||
|
||||
if AmmoRack:GetDestroyed() then return end
|
||||
|
||||
local OriginalDamage = dmginfo:GetDamage()
|
||||
|
||||
dmginfo:SetDamage( math.min( 2, OriginalDamage ) )
|
||||
end
|
||||
} )
|
||||
|
||||
return AmmoRack
|
||||
end
|
||||
|
||||
function ENT:AddTrailerHitch( pos, hitchtype )
|
||||
if not hitchtype then
|
||||
|
||||
hitchtype = LVS.HITCHTYPE_MALE
|
||||
|
||||
end
|
||||
|
||||
local TrailerHitch = ents.Create( "lvs_wheeldrive_trailerhitch" )
|
||||
|
||||
if not IsValid( TrailerHitch ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create trailerhitch entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
TrailerHitch:SetPos( self:LocalToWorld( pos ) )
|
||||
TrailerHitch:SetAngles( self:GetAngles() )
|
||||
TrailerHitch:Spawn()
|
||||
TrailerHitch:Activate()
|
||||
TrailerHitch:SetParent( self )
|
||||
TrailerHitch:SetBase( self )
|
||||
TrailerHitch:SetHitchType( hitchtype )
|
||||
|
||||
self:TransferCPPI( TrailerHitch )
|
||||
|
||||
return TrailerHitch
|
||||
end
|
||||
|
||||
function ENT:OnCoupleChanged( targetVehicle, targetHitch, active )
|
||||
end
|
||||
|
||||
function ENT:OnStartDrag( caller, activator )
|
||||
end
|
||||
|
||||
function ENT:OnStopDrag( caller, activator )
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
function ENT:HasQuickVar( name )
|
||||
name = "_smValue"..name
|
||||
|
||||
return self[ name ] ~= nil
|
||||
end
|
||||
|
||||
function ENT:GetQuickVar( name )
|
||||
name = "_smValue"..name
|
||||
|
||||
if not self[ name ] then return 0 end
|
||||
|
||||
return self[ name ]
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:QuickLerp( name, target, rate )
|
||||
name = "_smValue"..name
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable[ name ] then EntTable[ name ] = 0 end
|
||||
|
||||
EntTable[ name ] = EntTable[ name ] + (target - EntTable[ name ]) * math.min( RealFrameTime() * (rate or 10), 1 )
|
||||
|
||||
return EntTable[ name ]
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ENT:QuickLerp( name, target, rate )
|
||||
name = "_smValue"..name
|
||||
|
||||
if not self[ name ] then self[ name ] = 0 end
|
||||
|
||||
self[ name ] = self[ name ] + (target - self[ name ]) * math.min( FrameTime() * (rate or 10), 1 )
|
||||
|
||||
return self[ name ]
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
if CLIENT then
|
||||
hook.Add( "InitPostEntity", "!!!lvsUpdateMaxVelocity", function()
|
||||
net.Start( "lvs_maxvelocity_updater" )
|
||||
net.SendToServer()
|
||||
|
||||
hook.Remove( "InitPostEntity", "!!!lvsUpdateMaxVelocity" )
|
||||
end )
|
||||
|
||||
net.Receive( "lvs_maxvelocity_updater", function( len, ply )
|
||||
for _, data in pairs( net.ReadTable() ) do
|
||||
local entity = data.ent
|
||||
|
||||
if not IsValid( entity ) or not entity.LVS or not entity.MaxVelocity then continue end
|
||||
|
||||
entity.MaxVelocity = data.vel
|
||||
end
|
||||
end )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
util.AddNetworkString( "lvs_maxvelocity_updater" )
|
||||
|
||||
local UpdatedVehicles = {}
|
||||
|
||||
net.Receive( "lvs_maxvelocity_updater", function( len, ply )
|
||||
if ply._lvsAlreadyAskedForVelocityUpdate then return end
|
||||
|
||||
ply._lvsAlreadyAskedForVelocityUpdate = true
|
||||
|
||||
local TableSend = {}
|
||||
|
||||
for id, data in pairs( UpdatedVehicles ) do
|
||||
local entity = data.ent
|
||||
|
||||
if IsValid( entity ) and entity.LVS and isfunction( entity.ChangeVelocity ) then
|
||||
table.insert( TableSend, data )
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
UpdatedVehicles[ id ] = nil
|
||||
end
|
||||
|
||||
if #TableSend == 0 then return end
|
||||
|
||||
net.Start( "lvs_maxvelocity_updater" )
|
||||
net.WriteTable( TableSend )
|
||||
net.Send( ply )
|
||||
end )
|
||||
|
||||
function ENT:ChangeVelocity( new )
|
||||
new = math.Clamp( new, 1, physenv.GetPerformanceSettings().MaxVelocity )
|
||||
|
||||
self.MaxVelocity = new
|
||||
|
||||
timer.Simple(0, function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
local data = { ent = self, vel = new }
|
||||
|
||||
table.insert( UpdatedVehicles, data )
|
||||
|
||||
net.Start( "lvs_maxvelocity_updater" )
|
||||
net.WriteTable( {data} )
|
||||
net.Broadcast()
|
||||
end)
|
||||
end
|
||||
663
garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_weapons.lua
Normal file
663
garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_weapons.lua
Normal file
@@ -0,0 +1,663 @@
|
||||
|
||||
ENT.WEAPONS = {
|
||||
[1] = {},
|
||||
}
|
||||
|
||||
function ENT:InitWeapons()
|
||||
end
|
||||
|
||||
function ENT:AddWeapon( weaponData, PodID )
|
||||
if not istable( weaponData ) then print("[LVS] couldn't register weapon") return end
|
||||
|
||||
local data = table.Copy( weaponData )
|
||||
|
||||
if not PodID or PodID <= 1 then
|
||||
PodID = 1
|
||||
end
|
||||
|
||||
if not self.WEAPONS[ PodID ] then
|
||||
self.WEAPONS[ PodID ] = {}
|
||||
end
|
||||
|
||||
local default = LVS:GetWeaponPreset( "DEFAULT" )
|
||||
|
||||
data.Icon = data.Icon or Material("lvs/weapons/bullet.png")
|
||||
data.Ammo = data.Ammo or -1
|
||||
data.Delay = data.Delay or FrameTime()
|
||||
|
||||
if isnumber( data.Clip ) and data.Clip > 0 then
|
||||
data.HeatIsClip = true
|
||||
|
||||
local ShootDelay = data.Delay
|
||||
|
||||
local Clip = data.Clip
|
||||
local ReloadSpeed = data.ReloadSpeed or 2
|
||||
|
||||
data.HeatRateUp = 1.00001 / (ShootDelay * Clip)
|
||||
data.HeatRateDown = 1 / ReloadSpeed
|
||||
data.OnReload = data.OnReload or default.OnReload
|
||||
else
|
||||
data.HeatRateUp = data.HeatRateUp or default.HeatRateUp
|
||||
data.HeatRateDown = data.HeatRateDown or default.HeatRateDown
|
||||
end
|
||||
|
||||
data.Attack = data.Attack or default.Attack
|
||||
data.StartAttack = data.StartAttack or default.StartAttack
|
||||
data.FinishAttack = data.FinishAttack or default.FinishAttack
|
||||
data.OnSelect = data.OnSelect or default.OnSelect
|
||||
data.OnDeselect = data.OnDeselect or default.OnDeselect
|
||||
data.OnThink = data.OnThink or default.OnThink
|
||||
data.OnOverheat = data.OnOverheat or default.OnOverheat
|
||||
data.OnRemove = data.OnRemove or default.OnRemove
|
||||
data.UseableByAI = data.UseableByAI ~= false
|
||||
|
||||
table.insert( self.WEAPONS[ PodID ], data )
|
||||
end
|
||||
|
||||
function ENT:UpdateWeapon( PodID, WeaponID, weaponData )
|
||||
if not self.WEAPONS[ PodID ] then return end
|
||||
|
||||
if not self.WEAPONS[ PodID ][ WeaponID ] then return end
|
||||
|
||||
table.Merge( self.WEAPONS[ PodID ][ WeaponID ], weaponData )
|
||||
end
|
||||
|
||||
function ENT:HasWeapon( ID )
|
||||
return istable( self.WEAPONS[1][ ID ] )
|
||||
end
|
||||
|
||||
function ENT:AIHasWeapon( ID )
|
||||
local weapon = self.WEAPONS[1][ ID ]
|
||||
if not istable( weapon ) then return false end
|
||||
|
||||
return weapon.UseableByAI
|
||||
end
|
||||
|
||||
function ENT:GetActiveWeapon()
|
||||
local SelectedID = self:GetSelectedWeapon()
|
||||
local CurWeapon = self.WEAPONS[1][ SelectedID ]
|
||||
|
||||
return CurWeapon, SelectedID
|
||||
end
|
||||
|
||||
function ENT:GetMaxAmmo()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return -1 end
|
||||
|
||||
return CurWeapon.Ammo or -1
|
||||
end
|
||||
|
||||
function ENT:GetClip()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return 0 end
|
||||
|
||||
local HeatIncrement = (CurWeapon.HeatRateUp or 0.2) * math.max(CurWeapon.Delay or 0, FrameTime())
|
||||
|
||||
local Ammo = self:GetNWAmmo()
|
||||
|
||||
if self:GetMaxAmmo() <= 0 and CurWeapon.Clip then
|
||||
Ammo = CurWeapon.Clip
|
||||
end
|
||||
|
||||
return math.min( math.ceil( math.Round( (1 - self:GetNWHeat()) / HeatIncrement, 1 ) ), Ammo )
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function ENT:WeaponRestoreAmmo()
|
||||
local AmmoIsSet = false
|
||||
|
||||
for PodID, data in pairs( self.WEAPONS ) do
|
||||
for id, weapon in pairs( data ) do
|
||||
local MaxAmmo = weapon.Ammo or -1
|
||||
local CurAmmo = weapon._CurAmmo or -1
|
||||
|
||||
if CurAmmo == MaxAmmo then continue end
|
||||
|
||||
self.WEAPONS[PodID][ id ]._CurAmmo = MaxAmmo
|
||||
|
||||
AmmoIsSet = true
|
||||
end
|
||||
end
|
||||
|
||||
if AmmoIsSet then
|
||||
self:SetNWAmmo( self:GetAmmo() )
|
||||
|
||||
for _, pod in pairs( self:GetPassengerSeats() ) do
|
||||
local weapon = pod:lvsGetWeapon()
|
||||
|
||||
if not IsValid( weapon ) then continue end
|
||||
|
||||
weapon:SetNWAmmo( weapon:GetAmmo() )
|
||||
end
|
||||
end
|
||||
|
||||
return AmmoIsSet
|
||||
end
|
||||
|
||||
function ENT:WeaponsOnRemove()
|
||||
for _, data in pairs( self.WEAPONS ) do
|
||||
for ID, Weapon in pairs( data ) do
|
||||
if not Weapon.OnRemove then continue end
|
||||
|
||||
Weapon.OnRemove( self )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:WeaponsFinish()
|
||||
if not self._activeWeapon then return end
|
||||
|
||||
local CurWeapon = self.WEAPONS[1][ self._activeWeapon ]
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
if CurWeapon.FinishAttack then
|
||||
CurWeapon.FinishAttack( self )
|
||||
end
|
||||
|
||||
self._activeWeapon = nil
|
||||
self.OldAttack = false
|
||||
end
|
||||
|
||||
function ENT:GetAmmo()
|
||||
if self:GetAI() then return self:GetMaxAmmo() end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return -1 end
|
||||
|
||||
return CurWeapon._CurAmmo or self:GetMaxAmmo()
|
||||
end
|
||||
|
||||
function ENT:TakeAmmo( num )
|
||||
if self:GetMaxAmmo() <= 0 then return end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
CurWeapon._CurAmmo = math.max( self:GetAmmo() - (num or 1), 0 )
|
||||
|
||||
self:SetNWAmmo( CurWeapon._CurAmmo )
|
||||
end
|
||||
|
||||
function ENT:GetHeat( weaponid )
|
||||
local CurWeapon
|
||||
|
||||
if isnumber( weaponid ) and weaponid > 0 then
|
||||
CurWeapon = self.WEAPONS[1][ weaponid ]
|
||||
else
|
||||
CurWeapon = self:GetActiveWeapon()
|
||||
end
|
||||
|
||||
if not CurWeapon then return 0 end
|
||||
|
||||
return (CurWeapon._CurHeat or 0)
|
||||
end
|
||||
|
||||
function ENT:GetOverheated()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return false end
|
||||
|
||||
return CurWeapon.Overheated == true
|
||||
end
|
||||
|
||||
function ENT:SetOverheated( overheat )
|
||||
if self:GetOverheated() == overheat then return end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
CurWeapon.Overheated = overheat
|
||||
|
||||
self:SetNWOverheated( overheat )
|
||||
|
||||
if self:GetHeat() == 0 then return end
|
||||
|
||||
if CurWeapon.OnOverheat then
|
||||
CurWeapon.OnOverheat( self )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetHeat( heat )
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
heat = math.Clamp( heat, 0, 1 )
|
||||
|
||||
CurWeapon._CurHeat = heat
|
||||
|
||||
if self:GetNWHeat() == heat then return end
|
||||
|
||||
self:SetNWHeat( heat )
|
||||
end
|
||||
|
||||
function ENT:CanAttack()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
return (CurWeapon._NextFire or 0) < CurTime()
|
||||
end
|
||||
|
||||
function ENT:SetNextAttack( time )
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
CurWeapon._NextFire = time
|
||||
end
|
||||
|
||||
function ENT:WeaponsShouldFire()
|
||||
if self:GetAI() then return self._AIFireInput end
|
||||
|
||||
local ply = self:GetDriver()
|
||||
|
||||
if not IsValid( ply ) then return false end
|
||||
|
||||
return ply:lvsKeyDown( "ATTACK" )
|
||||
end
|
||||
|
||||
function ENT:WeaponsThink()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local T = CurTime()
|
||||
local FT = FrameTime()
|
||||
local CurWeapon, SelectedID = self:GetActiveWeapon()
|
||||
|
||||
for ID, Weapon in pairs( EntTable.WEAPONS[1] ) do
|
||||
local IsActive = ID == SelectedID
|
||||
|
||||
if Weapon.OnThink then Weapon.OnThink( self, IsActive ) end
|
||||
|
||||
if IsActive then continue end
|
||||
|
||||
if Weapon.HeatIsClip and not Weapon.Overheated and Weapon._CurHeat ~= 0 then
|
||||
Weapon.Overheated = true
|
||||
Weapon._CurHeat = 1
|
||||
|
||||
if Weapon.OnReload then Weapon.OnReload( self ) end
|
||||
end
|
||||
|
||||
-- cool all inactive weapons down
|
||||
Weapon._CurHeat = Weapon._CurHeat and Weapon._CurHeat - math.min( Weapon._CurHeat, (Weapon.HeatRateDown or 0.25) * FT ) or 0
|
||||
end
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
local ShouldFire = self:WeaponsShouldFire()
|
||||
local CurHeat = self:GetHeat()
|
||||
|
||||
if self:GetOverheated() then
|
||||
if CurHeat <= 0 then
|
||||
self:SetOverheated( false )
|
||||
else
|
||||
ShouldFire = false
|
||||
end
|
||||
else
|
||||
if CurHeat >= 1 then
|
||||
self:SetOverheated( true )
|
||||
ShouldFire = false
|
||||
if CurWeapon.OnReload then CurWeapon.OnReload( self ) end
|
||||
end
|
||||
end
|
||||
|
||||
if self:GetMaxAmmo() > 0 then
|
||||
if self:GetAmmo() <= 0 then
|
||||
ShouldFire = false
|
||||
end
|
||||
end
|
||||
|
||||
if ShouldFire ~= EntTable.OldAttack then
|
||||
EntTable.OldAttack = ShouldFire
|
||||
|
||||
if ShouldFire then
|
||||
if CurWeapon.StartAttack then
|
||||
CurWeapon.StartAttack( self )
|
||||
end
|
||||
EntTable._activeWeapon = SelectedID
|
||||
else
|
||||
self:WeaponsFinish()
|
||||
end
|
||||
end
|
||||
|
||||
if ShouldFire then
|
||||
if not self:CanAttack() then return end
|
||||
|
||||
local ShootDelay = (CurWeapon.Delay or 0)
|
||||
|
||||
self:SetNextAttack( T + ShootDelay )
|
||||
self:SetHeat( CurHeat + (CurWeapon.HeatRateUp or 0.2) * math.max(ShootDelay, FT) )
|
||||
|
||||
if not CurWeapon.Attack then return end
|
||||
|
||||
if CurWeapon.Attack( self ) then
|
||||
self:SetHeat( CurHeat - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) )
|
||||
self:SetNextAttack( T )
|
||||
end
|
||||
|
||||
EntTable._lvsNextActiveWeaponCoolDown = T + 0.25
|
||||
else
|
||||
if (EntTable._lvsNextActiveWeaponCoolDown or 0) > T then return end
|
||||
|
||||
if CurWeapon.HeatIsClip and not CurWeapon.Overheated then
|
||||
|
||||
self:SetHeat( self:GetHeat() )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetHeat( self:GetHeat() - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SelectWeapon( ID )
|
||||
if not isnumber( ID ) then return end
|
||||
|
||||
if self.WEAPONS[1][ ID ] then
|
||||
self:SetSelectedWeapon( ID )
|
||||
end
|
||||
|
||||
local ply = self:GetDriver()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
net.Start( "lvs_select_weapon" )
|
||||
net.Send( ply )
|
||||
end
|
||||
|
||||
function ENT:OnWeaponChanged( name, old, new)
|
||||
if new == old then return end
|
||||
|
||||
self:WeaponsFinish()
|
||||
|
||||
local PrevWeapon = self.WEAPONS[1][ old ]
|
||||
if PrevWeapon and PrevWeapon.OnDeselect then
|
||||
PrevWeapon.OnDeselect( self, new )
|
||||
end
|
||||
|
||||
local NextWeapon = self.WEAPONS[1][ new ]
|
||||
if NextWeapon and NextWeapon.OnSelect then
|
||||
NextWeapon.OnSelect( self, old )
|
||||
self:SetNWAmmo( NextWeapon._CurAmmo or NextWeapon.Ammo or -1 )
|
||||
self:SetNWOverheated( NextWeapon.Overheated == true )
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ENT:DrawWeaponIcon( PodID, ID, x, y, width, height, IsSelected, IconColor )
|
||||
end
|
||||
|
||||
function ENT:SelectWeapon( ID )
|
||||
if not isnumber( ID ) then return end
|
||||
|
||||
net.Start( "lvs_select_weapon" )
|
||||
net.WriteInt( ID, 5 )
|
||||
net.WriteBool( false )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function ENT:NextWeapon()
|
||||
net.Start( "lvs_select_weapon" )
|
||||
net.WriteInt( 1, 5 )
|
||||
net.WriteBool( true )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function ENT:PrevWeapon()
|
||||
net.Start( "lvs_select_weapon" )
|
||||
net.WriteInt( -1, 5 )
|
||||
net.WriteBool( true )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
LVS:AddHudEditor( "WeaponSwitcher", ScrW() - 210, ScrH() - 165, 200, 68, 200, 68, "WEAPON SELECTOR",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintWeapons then return end
|
||||
vehicle:LVSHudPaintWeapons( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
LVS:AddHudEditor( "WeaponInfo", ScrW() - 230, ScrH() - 85, 220, 75, 220, 75, "WEAPON INFO",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintWeaponInfo then return end
|
||||
|
||||
vehicle:LVSHudPaintWeaponInfo( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
function ENT:GetAmmoID( ID )
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local Base = ply:lvsGetWeaponHandler()
|
||||
|
||||
if not IsValid( Base ) then return -1 end
|
||||
|
||||
local selected = Base:GetSelectedWeapon()
|
||||
local weapon = self.WEAPONS[ Base:GetPodIndex() ][ ID ]
|
||||
|
||||
if ID == selected then
|
||||
weapon._CurAmmo = Base:GetNWAmmo()
|
||||
else
|
||||
weapon._CurAmmo = weapon._CurAmmo or weapon.Ammo or -1
|
||||
end
|
||||
|
||||
return weapon._CurAmmo
|
||||
end
|
||||
|
||||
|
||||
local Circles = {
|
||||
[1] = {r = -1, col = Color(0,0,0,200)},
|
||||
[2] = {r = 0, col = Color(255,255,255,200)},
|
||||
[3] = {r = 1, col = Color(255,255,255,255)},
|
||||
[4] = {r = 2, col = Color(255,255,255,200)},
|
||||
[5] = {r = 3, col = Color(0,0,0,200)},
|
||||
}
|
||||
|
||||
local function DrawCircle( X, Y, target_radius, heatvalue, overheated )
|
||||
local endang = 360 * heatvalue
|
||||
|
||||
if endang == 0 then return end
|
||||
|
||||
for i = 1, #Circles do
|
||||
local data = Circles[ i ]
|
||||
local radius = target_radius + data.r
|
||||
local segmentdist = endang / ( math.pi * radius / 2 )
|
||||
|
||||
for a = 0, endang, segmentdist do
|
||||
local r = data.col.r
|
||||
local g = data.col.g * (1 - math.min(a / 270,1))
|
||||
local b = data.col.b * (1 - math.min(a / 90,1))
|
||||
|
||||
surface.SetDrawColor( r, g, b, data.col.a )
|
||||
|
||||
surface.DrawLine( X - math.sin( math.rad( a ) ) * radius, Y + math.cos( math.rad( a ) ) * radius, X - math.sin( math.rad( a + segmentdist ) ) * radius, Y + math.cos( math.rad( a + segmentdist ) ) * radius )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ENT.HeatMat = Material( "lvs/heat.png" )
|
||||
ENT.HeatIsClipMat = Material( "lvs/3d2dmats/refil.png" )
|
||||
|
||||
local color_white = color_white
|
||||
local color_red = Color(255,0,0,255)
|
||||
|
||||
function ENT:LVSHudPaintWeaponInfo( X, Y, w, h, ScrX, ScrY, ply )
|
||||
local Base = ply:lvsGetWeaponHandler()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local ID = Base:GetSelectedWeapon()
|
||||
|
||||
if not Base:HasWeapon( ID ) then return end
|
||||
|
||||
local Weapon = Base:GetActiveWeapon()
|
||||
local Heat = Base:GetNWHeat()
|
||||
local OverHeated = Base:GetNWOverheated()
|
||||
local Ammo = Base:GetNWAmmo()
|
||||
|
||||
if Weapon and Weapon.HeatIsClip then
|
||||
local Pod = ply:GetVehicle()
|
||||
|
||||
if not IsValid( Pod ) then return end
|
||||
|
||||
local PodID = Base:GetPodIndex()
|
||||
|
||||
local FT = FrameTime()
|
||||
local ShootDelay = math.max(Weapon.Delay or 0, FT)
|
||||
local HeatIncrement = (Weapon.HeatRateUp or 0.2) * ShootDelay
|
||||
|
||||
local Clip = Base:GetClip()
|
||||
|
||||
if OverHeated then
|
||||
Clip = 0
|
||||
|
||||
local hX = X + w - h * 0.5
|
||||
local hY = Y + h * 0.25 + h * 0.25
|
||||
|
||||
surface.SetMaterial( self.HeatIsClipMat )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 3, hY + 1, h, h, 0 )
|
||||
surface.SetDrawColor( 255, 0, 0, 255 )
|
||||
surface.DrawTexturedRectRotated( hX + 1, hY - 1, h, h, 0 )
|
||||
|
||||
DrawCircle( hX, hY, h * 0.35, Heat )
|
||||
end
|
||||
|
||||
Ammo = Ammo - Clip
|
||||
|
||||
local ColDyn = (Clip == 0 or OverHeated) and color_red or color_white
|
||||
|
||||
draw.DrawText( "AMMO ", "LVS_FONT", X + 72, Y + 35, ColDyn, TEXT_ALIGN_RIGHT )
|
||||
|
||||
draw.DrawText( Clip, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, ColDyn, TEXT_ALIGN_LEFT )
|
||||
|
||||
if Base:GetMaxAmmo() <= 0 then return end
|
||||
|
||||
local ColDyn2 = Ammo <= Weapon.Clip and color_red or color_white
|
||||
|
||||
X = X + math.max( (#string.Explode( "", Clip ) - 1) * 18, 0 )
|
||||
|
||||
draw.DrawText( "/", "LVS_FONT_HUD_LARGE", X + 96, Y + 30, ColDyn2, TEXT_ALIGN_LEFT )
|
||||
|
||||
draw.DrawText( Ammo, "LVS_FONT", X + 110, Y + 40, ColDyn2, TEXT_ALIGN_LEFT )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local hX = X + w - h * 0.5
|
||||
local hY = Y + h * 0.25 + h * 0.25
|
||||
local hAng = math.cos( CurTime() * 50 ) * 5 * (OverHeated and 1 or Heat ^ 2)
|
||||
|
||||
surface.SetMaterial( self.HeatMat )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, h * 0.5, h * 0.5, hAng )
|
||||
|
||||
if OverHeated then
|
||||
surface.SetDrawColor( 255, 0, 0, 255 )
|
||||
else
|
||||
surface.SetDrawColor( 255, 255 * (1 - Heat), 255 * math.max(1 - Heat * 1.5,0), 255 )
|
||||
end
|
||||
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, h * 0.5, h * 0.5, hAng )
|
||||
|
||||
DrawCircle( hX, hY, h * 0.35, Heat )
|
||||
|
||||
if Base:GetMaxAmmo() <= 0 then return end
|
||||
|
||||
draw.DrawText( "AMMO ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( Ammo, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintWeapons( X, Y, w, h, ScrX, ScrY, ply )
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Base = ply:lvsGetWeaponHandler()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local Pod = ply:GetVehicle()
|
||||
|
||||
if not IsValid( Pod ) then return end
|
||||
|
||||
local PodID = Base:GetPodIndex()
|
||||
|
||||
local num = #self.WEAPONS[ PodID ]
|
||||
|
||||
if num <= 1 then return end
|
||||
|
||||
local CenterY = (Y + h * 0.5)
|
||||
local CenterX = (X + w * 0.5)
|
||||
|
||||
local FlatSelector = CenterX > ScrX * 0.333 and CenterX < ScrX * 0.666
|
||||
|
||||
local T = CurTime()
|
||||
local FT = RealFrameTime()
|
||||
|
||||
local gap = 4
|
||||
local SizeY = h - gap
|
||||
|
||||
local Selected = Base:GetSelectedWeapon()
|
||||
if Selected ~= EntTable._OldSelected then
|
||||
EntTable._OldSelected = Selected
|
||||
Pod._SelectActiveTime = T + 2
|
||||
end
|
||||
|
||||
local tAlpha = (Pod._SelectActiveTime or 0) > T and 1 or 0
|
||||
local tAlphaRate = FT * 15
|
||||
|
||||
EntTable.smAlphaSW = EntTable.smAlphaSW and (EntTable.smAlphaSW + math.Clamp(tAlpha - EntTable.smAlphaSW,-tAlphaRate,tAlphaRate)) or 0
|
||||
|
||||
if EntTable.smAlphaSW > 0.95 then
|
||||
EntTable._DisplaySelected = Selected
|
||||
else
|
||||
EntTable._DisplaySelected = EntTable._DisplaySelected or Selected
|
||||
end
|
||||
|
||||
local A255 = 255 * EntTable.smAlphaSW
|
||||
local A150 = 150 * EntTable.smAlphaSW
|
||||
|
||||
local Col = Color(0,0,0,A150)
|
||||
local ColSelect = Color(255,255,255,A150)
|
||||
|
||||
local SwapY = 0
|
||||
|
||||
if Y < (ScrY * 0.5 - h * 0.5) then
|
||||
SwapY = 1
|
||||
end
|
||||
|
||||
for ID = 1, num do
|
||||
local IsSelected = EntTable._DisplaySelected == ID
|
||||
local n = num - ID
|
||||
local xPos = FlatSelector and X + (w + gap) * (ID - 1) - ((w + gap) * 0.5 * num - w * 0.5) or X
|
||||
local yPos = FlatSelector and Y - h * math.min(SwapY,0) or Y - h * n + (num - 1) * h * SwapY
|
||||
|
||||
draw.RoundedBox(5, xPos, yPos, w, SizeY, IsSelected and ColSelect or Col )
|
||||
|
||||
if IsSelected then
|
||||
surface.SetDrawColor( 0, 0, 0, A255 )
|
||||
else
|
||||
surface.SetDrawColor( 255, 255, 255, A255 )
|
||||
end
|
||||
|
||||
if isbool( EntTable.WEAPONS[PodID][ID].Icon ) then
|
||||
local col = IsSelected and Color(255,255,255,A255) or Color(0,0,0,A255)
|
||||
self:DrawWeaponIcon( PodID, ID, xPos, yPos, SizeY * 2, SizeY, IsSelected, col )
|
||||
else
|
||||
surface.SetMaterial( self.WEAPONS[PodID][ID].Icon )
|
||||
surface.DrawTexturedRect( xPos, yPos, SizeY * 2, SizeY )
|
||||
end
|
||||
|
||||
local ammo = self:GetAmmoID( ID )
|
||||
|
||||
if ammo > -1 then
|
||||
draw.DrawText( ammo, "LVS_FONT_HUD", xPos + w - 10, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT )
|
||||
else
|
||||
draw.DrawText( "O", "LVS_FONT_HUD", xPos + w - 19, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( "O", "LVS_FONT_HUD", xPos + w - 10, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT )
|
||||
end
|
||||
end
|
||||
end
|
||||
352
garrysmod/addons/lvs_base/lua/entities/lvs_base/shared.lua
Normal file
352
garrysmod/addons/lvs_base/lua/entities/lvs_base/shared.lua
Normal file
@@ -0,0 +1,352 @@
|
||||
ENT.Type = "anim"
|
||||
|
||||
ENT.PrintName = "LBaseEntity"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.VJ_ID_Destructible = true
|
||||
|
||||
ENT.AutomaticFrameAdvance = true
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
ENT.Editable = true
|
||||
|
||||
ENT.LVS = true
|
||||
|
||||
ENT.MDL = "models/props_c17/trappropeller_engine.mdl"
|
||||
|
||||
ENT.AITEAM = 0
|
||||
|
||||
ENT.MaxHealth = 100
|
||||
ENT.MaxShield = 0
|
||||
|
||||
ENT.SpawnNormalOffset = 15
|
||||
ENT.SpawnAngleOffset = 0
|
||||
|
||||
ENT.HitGroundLength = 10
|
||||
|
||||
ENT.lvsDisableZoom = true
|
||||
|
||||
function ENT:AddDT( type, name, data )
|
||||
if not self.DTlist then self.DTlist = {} end
|
||||
|
||||
if self.DTlist[ type ] then
|
||||
self.DTlist[ type ] = self.DTlist[ type ] + 1
|
||||
else
|
||||
self.DTlist[ type ] = 0
|
||||
end
|
||||
|
||||
self:NetworkVar( type, self.DTlist[ type ], name, data )
|
||||
end
|
||||
|
||||
function ENT:CreateBaseDT()
|
||||
local InitWeaponsSuccess, ErrorMsg = pcall( function() self:InitWeapons() end )
|
||||
|
||||
if not InitWeaponsSuccess then
|
||||
ErrorNoHalt( "\n[ERROR] "..ErrorMsg.."\n\n" )
|
||||
end
|
||||
|
||||
self:AddDT( "Entity", "Driver" )
|
||||
self:AddDT( "Entity", "DriverSeat" )
|
||||
|
||||
self:AddDT( "Bool", "Active" )
|
||||
self:AddDT( "Bool", "EngineActive" )
|
||||
self:AddDT( "Bool", "AI", { KeyName = "aicontrolled", Edit = { type = "Boolean", order = 1, category = "AI"} } )
|
||||
|
||||
local ShowAIGunnerInMenu = false
|
||||
|
||||
if istable( self.WEAPONS ) then
|
||||
for id, _ in pairs( self.WEAPONS ) do
|
||||
if id == 1 then continue end
|
||||
|
||||
ShowAIGunnerInMenu = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if ShowAIGunnerInMenu then
|
||||
self:AddDT( "Bool", "AIGunners", { KeyName = "aigunners", Edit = { type = "Boolean", order = 2, category = "AI"} } )
|
||||
else
|
||||
self:AddDT( "Bool", "AIGunners" )
|
||||
end
|
||||
|
||||
self:AddDT( "Bool", "lvsLockedStatus" )
|
||||
self:AddDT( "Bool", "lvsReady" )
|
||||
self:AddDT( "Bool", "NWOverheated" )
|
||||
|
||||
self:AddDT( "Int", "AITEAM", { KeyName = "aiteam", Edit = { type = "Int", order = 2,min = 0, max = 3, category = "AI"} } )
|
||||
self:AddDT( "Int", "SelectedWeapon" )
|
||||
self:AddDT( "Int", "NWAmmo" )
|
||||
|
||||
self:AddDT( "Float", "HP", { KeyName = "health", Edit = { type = "Float", order = 2,min = 0, max = self.MaxHealth, category = "Misc"} } )
|
||||
self:AddDT( "Float", "Shield" )
|
||||
self:AddDT( "Float", "NWHeat" )
|
||||
|
||||
self:TurretSystemDT()
|
||||
self:OnSetupDataTables()
|
||||
|
||||
if SERVER then
|
||||
self:NetworkVarNotify( "AI", self.OnToggleAI )
|
||||
self:NetworkVarNotify( "HP", self.PDSHealthValueChanged )
|
||||
self:NetworkVarNotify( "SelectedWeapon", self.OnWeaponChanged )
|
||||
|
||||
self:SetAITEAM( self.AITEAM )
|
||||
self:SetHP( self.MaxHealth )
|
||||
self:SetShield( self.MaxShield )
|
||||
self:SetSelectedWeapon( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TurretSystemDT()
|
||||
end
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:CreateBaseDT()
|
||||
end
|
||||
|
||||
function ENT:OnSetupDataTables()
|
||||
end
|
||||
|
||||
function ENT:CalcMainActivity( ply )
|
||||
end
|
||||
|
||||
function ENT:GetPlayerBoneManipulation( ply, PodID )
|
||||
return self.PlayerBoneManipulate[ PodID ] or {}
|
||||
end
|
||||
|
||||
function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed )
|
||||
ply:SetPlaybackRate( 1 )
|
||||
|
||||
if CLIENT then
|
||||
GAMEMODE:GrabEarAnimation( ply )
|
||||
GAMEMODE:MouthMoveAnimation( ply )
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
end
|
||||
|
||||
function ENT:HitGround()
|
||||
local trace = util.TraceLine( {
|
||||
start = self:LocalToWorld( self:OBBCenter() ),
|
||||
endpos = self:LocalToWorld( Vector(0,0,self:OBBMins().z - self.HitGroundLength) ),
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
return trace.Hit
|
||||
end
|
||||
|
||||
function ENT:Sign( n )
|
||||
if n > 0 then return 1 end
|
||||
|
||||
if n < 0 then return -1 end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function ENT:VectorSubtractNormal( Normal, Velocity )
|
||||
local VelForward = Velocity:GetNormalized()
|
||||
|
||||
local Ax = math.acos( math.Clamp( Normal:Dot( VelForward ) ,-1,1) )
|
||||
|
||||
local Fx = math.cos( Ax ) * Velocity:Length()
|
||||
|
||||
local NewVelocity = Velocity - Normal * math.abs( Fx )
|
||||
|
||||
return NewVelocity
|
||||
end
|
||||
|
||||
function ENT:VectorSplitNormal( Normal, Velocity )
|
||||
return math.cos( math.acos( math.Clamp( Normal:Dot( Velocity:GetNormalized() ) ,-1,1) ) ) * Velocity:Length()
|
||||
end
|
||||
|
||||
function ENT:AngleBetweenNormal( Dir1, Dir2 )
|
||||
return math.deg( math.acos( math.Clamp( Dir1:Dot( Dir2 ) ,-1,1) ) )
|
||||
end
|
||||
|
||||
function ENT:GetMaxShield()
|
||||
return self.MaxShield
|
||||
end
|
||||
|
||||
function ENT:GetShieldPercent()
|
||||
return self:GetShield() / self:GetMaxShield()
|
||||
end
|
||||
|
||||
function ENT:GetMaxHP()
|
||||
return self.MaxHealth
|
||||
end
|
||||
|
||||
function ENT:IsInitialized()
|
||||
if not self.GetlvsReady then return false end -- in case this is called BEFORE setupdatatables
|
||||
|
||||
return self:GetlvsReady()
|
||||
end
|
||||
|
||||
function ENT:GetWeaponHandler( num )
|
||||
if num == 1 then return self end
|
||||
|
||||
local pod = self:GetPassengerSeat( num )
|
||||
|
||||
if not IsValid( pod ) then return NULL end
|
||||
|
||||
return pod:lvsGetWeapon()
|
||||
end
|
||||
|
||||
function ENT:GetPassengerSeat( num )
|
||||
if num == 1 then
|
||||
return self:GetDriverSeat()
|
||||
else
|
||||
for _, Pod in pairs( self:GetPassengerSeats() ) do
|
||||
|
||||
if not IsValid( Pod ) then continue end
|
||||
|
||||
local id = Pod:lvsGetPodIndex()
|
||||
|
||||
if id == -1 then continue end
|
||||
|
||||
if id == num then
|
||||
return Pod
|
||||
end
|
||||
end
|
||||
|
||||
return NULL
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetPassengerSeats()
|
||||
if not self:IsInitialized() then return {} end
|
||||
|
||||
if not istable( self.pSeats ) then
|
||||
self.pSeats = {}
|
||||
|
||||
local DriverSeat = self:GetDriverSeat()
|
||||
|
||||
for _, v in pairs( self:GetChildren() ) do
|
||||
if v ~= DriverSeat and v:GetClass():lower() == "prop_vehicle_prisoner_pod" then
|
||||
table.insert( self.pSeats, v )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self.pSeats
|
||||
end
|
||||
|
||||
function ENT:HasActiveSoundEmitters()
|
||||
local active = false
|
||||
|
||||
for _, emitter in ipairs( self:GetChildren() ) do
|
||||
if emitter:GetClass() ~= "lvs_soundemitter" then continue end
|
||||
|
||||
if not IsValid( emitter ) or not emitter.GetActive or not emitter.GetActiveVisible then continue end
|
||||
|
||||
if emitter:GetActive() and emitter:GetActiveVisible() then
|
||||
active = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return active
|
||||
end
|
||||
|
||||
function ENT:GetPassenger( num )
|
||||
if num == 1 then
|
||||
return self:GetDriver()
|
||||
else
|
||||
for _, Pod in pairs( self:GetPassengerSeats() ) do
|
||||
|
||||
if not IsValid( Pod ) then
|
||||
return NULL
|
||||
end
|
||||
|
||||
local id = Pod:lvsGetPodIndex()
|
||||
|
||||
if id == -1 then continue end
|
||||
|
||||
if id == num then
|
||||
return Pod:GetDriver()
|
||||
end
|
||||
end
|
||||
|
||||
return NULL
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetEveryone()
|
||||
local plys = {}
|
||||
|
||||
local Pilot = self:GetDriver()
|
||||
if IsValid( Pilot ) then
|
||||
table.insert( plys, Pilot )
|
||||
end
|
||||
|
||||
for _, Pod in pairs( self:GetPassengerSeats() ) do
|
||||
if not IsValid( Pod ) then continue end
|
||||
|
||||
local ply = Pod:GetDriver()
|
||||
|
||||
if not IsValid( ply ) then continue end
|
||||
|
||||
table.insert( plys, ply )
|
||||
end
|
||||
|
||||
return plys
|
||||
end
|
||||
|
||||
function ENT:GetPodIndex()
|
||||
return 1
|
||||
end
|
||||
|
||||
function ENT:PlayAnimation( animation, playbackrate )
|
||||
playbackrate = playbackrate or 1
|
||||
|
||||
local sequence = self:LookupSequence( animation )
|
||||
|
||||
self:ResetSequence( sequence )
|
||||
self:SetPlaybackRate( playbackrate )
|
||||
self:SetSequence( sequence )
|
||||
end
|
||||
|
||||
function ENT:GetVehicle()
|
||||
return self
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "LBaseEntity"
|
||||
end
|
||||
|
||||
function ENT:GetBoneInfo( BoneName )
|
||||
local BoneID = self:LookupBone( BoneName )
|
||||
local numHitBoxSets = self:GetHitboxSetCount()
|
||||
|
||||
if not BoneID then
|
||||
goto SkipLoop
|
||||
end
|
||||
|
||||
for hboxset = 0, numHitBoxSets - 1 do
|
||||
local numHitBoxes = self:GetHitBoxCount( hboxset )
|
||||
|
||||
for hitbox=0, numHitBoxes - 1 do
|
||||
local bone = self:GetHitBoxBone( hitbox, hboxset )
|
||||
local name = self:GetBoneName( bone )
|
||||
|
||||
if BoneName ~= name then continue end
|
||||
|
||||
local mins, maxs = self:GetHitBoxBounds( hitbox, hboxset )
|
||||
local pos, ang = self:GetBonePosition( BoneID )
|
||||
|
||||
return self:WorldToLocal( pos ), self:WorldToLocalAngles( ang ), mins, maxs
|
||||
end
|
||||
end
|
||||
|
||||
:: SkipLoop ::
|
||||
|
||||
return vector_origin, angle_zero, vector_origin, vector_origin
|
||||
end
|
||||
230
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_ai.lua
Normal file
230
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_ai.lua
Normal file
@@ -0,0 +1,230 @@
|
||||
|
||||
function ENT:RunAI()
|
||||
end
|
||||
|
||||
function ENT:AutoAI()
|
||||
if not IsValid( self._OwnerEntLVS ) then return end
|
||||
|
||||
if self._OwnerEntLVS:InVehicle() then
|
||||
if self._OwnerEntLVS:IsAdmin() then
|
||||
self:SetAI( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
end
|
||||
|
||||
function ENT:OnToggleAI( name, old, new )
|
||||
if new == old then return end
|
||||
|
||||
if not self:IsInitialized() then
|
||||
timer.Simple( FrameTime(), function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
self:OnToggleAI( name, old, new )
|
||||
end )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetAIGunners( new )
|
||||
|
||||
if new == true then
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
Driver:ExitVehicle()
|
||||
end
|
||||
|
||||
self:SetActive( true )
|
||||
self:OnCreateAI()
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
else
|
||||
self:SetActive( false )
|
||||
self:OnRemoveAI()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnAITakeDamage( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:AITargetInFront( ent, range )
|
||||
if not IsValid( ent ) then return false end
|
||||
|
||||
if not range then range = 45 end
|
||||
|
||||
if range >= 180 then return true end
|
||||
|
||||
local DirToTarget = (ent:GetPos() - self:GetPos()):GetNormalized()
|
||||
|
||||
local InFront = math.deg( math.acos( math.Clamp( self:GetForward():Dot( DirToTarget ) ,-1,1) ) ) < range
|
||||
|
||||
return InFront
|
||||
end
|
||||
|
||||
function ENT:AICanSee( otherEnt )
|
||||
if not IsValid( otherEnt ) then return false end
|
||||
|
||||
local PhysObj = otherEnt:GetPhysicsObject()
|
||||
|
||||
if IsValid( PhysObj ) then
|
||||
local trace = {
|
||||
start = self:LocalToWorld( self:OBBCenter() ),
|
||||
endpos = otherEnt:LocalToWorld( PhysObj:GetMassCenter() ),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
return util.TraceLine( trace ).Entity == otherEnt
|
||||
end
|
||||
|
||||
local trace = {
|
||||
start = self:LocalToWorld( self:OBBCenter() ),
|
||||
endpos = otherEnt:LocalToWorld( otherEnt:OBBCenter() ),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
return util.TraceLine( trace ).Entity == otherEnt
|
||||
end
|
||||
|
||||
function ENT:AIGetTarget( viewcone )
|
||||
if (self._lvsNextAICheck or 0) > CurTime() then return self._LastAITarget end
|
||||
|
||||
self._lvsNextAICheck = CurTime() + 2
|
||||
|
||||
local MyPos = self:GetPos()
|
||||
local MyTeam = self:GetAITEAM()
|
||||
|
||||
if MyTeam == 0 then self._LastAITarget = NULL return NULL end
|
||||
|
||||
local ClosestTarget = NULL
|
||||
local TargetDistance = 60000
|
||||
|
||||
if not LVS.IgnorePlayers then
|
||||
for _, ply in pairs( player.GetAll() ) do
|
||||
if not ply:Alive() then continue end
|
||||
|
||||
if ply:IsFlagSet( FL_NOTARGET ) then continue end
|
||||
|
||||
local Dist = (ply:GetPos() - MyPos):Length()
|
||||
|
||||
if Dist > TargetDistance then continue end
|
||||
|
||||
local Veh = ply:lvsGetVehicle()
|
||||
|
||||
if IsValid( Veh ) then
|
||||
if self:AICanSee( Veh ) and Veh ~= self then
|
||||
local HisTeam = Veh:GetAITEAM()
|
||||
|
||||
if HisTeam == 0 then continue end
|
||||
|
||||
if self.AISearchCone then
|
||||
if not self:AITargetInFront( Veh, self.AISearchCone ) then continue end
|
||||
end
|
||||
|
||||
if HisTeam ~= MyTeam or HisTeam == 3 then
|
||||
ClosestTarget = Veh
|
||||
TargetDistance = Dist
|
||||
end
|
||||
end
|
||||
else
|
||||
local HisTeam = ply:lvsGetAITeam()
|
||||
if not ply:IsLineOfSightClear( self ) or HisTeam == 0 then continue end
|
||||
|
||||
if self.AISearchCone then
|
||||
if not self:AITargetInFront( ply, self.AISearchCone ) then continue end
|
||||
end
|
||||
|
||||
if HisTeam ~= MyTeam or HisTeam == 3 then
|
||||
ClosestTarget = ply
|
||||
TargetDistance = Dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not LVS.IgnoreNPCs then
|
||||
for _, npc in pairs( LVS:GetNPCs() ) do
|
||||
local HisTeam = LVS:GetNPCRelationship( npc:GetClass() )
|
||||
|
||||
if HisTeam == 0 or (HisTeam == MyTeam and HisTeam ~= 3) then continue end
|
||||
|
||||
local Dist = (npc:GetPos() - MyPos):Length()
|
||||
|
||||
if Dist > TargetDistance or not self:AICanSee( npc ) then continue end
|
||||
|
||||
if self.AISearchCone then
|
||||
if not self:AITargetInFront( npc, self.AISearchCone ) then continue end
|
||||
end
|
||||
|
||||
ClosestTarget = npc
|
||||
TargetDistance = Dist
|
||||
end
|
||||
end
|
||||
|
||||
for _, veh in pairs( LVS:GetVehicles() ) do
|
||||
if veh:IsDestroyed() then continue end
|
||||
|
||||
if veh == self then continue end
|
||||
|
||||
local Dist = (veh:GetPos() - MyPos):Length()
|
||||
|
||||
if Dist > TargetDistance or not self:AITargetInFront( veh, (viewcone or 100) ) then continue end
|
||||
|
||||
local HisTeam = veh:GetAITEAM()
|
||||
|
||||
if HisTeam == 0 then continue end
|
||||
|
||||
if HisTeam == self:GetAITEAM() then
|
||||
if HisTeam ~= 3 then continue end
|
||||
end
|
||||
|
||||
if self.AISearchCone then
|
||||
if not self:AITargetInFront( veh, self.AISearchCone ) then continue end
|
||||
end
|
||||
|
||||
if self:AICanSee( veh ) then
|
||||
ClosestTarget = veh
|
||||
TargetDistance = Dist
|
||||
end
|
||||
end
|
||||
|
||||
self._LastAITarget = ClosestTarget
|
||||
|
||||
return ClosestTarget
|
||||
end
|
||||
|
||||
function ENT:IsEnemy( ent )
|
||||
if not IsValid( ent ) then return false end
|
||||
|
||||
local HisTeam = 0
|
||||
|
||||
if ent:IsNPC() then
|
||||
HisTeam = LVS:GetNPCRelationship( ent:GetClass() )
|
||||
end
|
||||
|
||||
if ent:IsPlayer() then
|
||||
if ent:IsFlagSet( FL_NOTARGET ) then return false end
|
||||
|
||||
local veh = ent:lvsGetVehicle()
|
||||
if IsValid( veh ) then
|
||||
HisTeam = veh:GetAITEAM()
|
||||
else
|
||||
HisTeam = ent:lvsGetAITeam()
|
||||
end
|
||||
end
|
||||
|
||||
if ent.LVS and ent.GetAITEAM then
|
||||
HisTeam = ent:GetAITEAM()
|
||||
end
|
||||
|
||||
if HisTeam == 0 then return false end
|
||||
|
||||
if HisTeam == 3 then return true end
|
||||
|
||||
return HisTeam ~= self:GetAITEAM()
|
||||
end
|
||||
18
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_cppi.lua
Normal file
18
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_cppi.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
function ENT:StoreCPPI( owner )
|
||||
self._OwnerEntLVS = owner
|
||||
end
|
||||
|
||||
function ENT:TransferCPPI( target )
|
||||
if not IsEntity( target ) or not IsValid( target ) then return end
|
||||
|
||||
if not CPPI then return end
|
||||
|
||||
local Owner = self._OwnerEntLVS
|
||||
|
||||
if not IsEntity( Owner ) then return end
|
||||
|
||||
if IsValid( Owner ) then
|
||||
target:CPPISetOwner( Owner )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,426 @@
|
||||
|
||||
ENT._armorParts = {}
|
||||
ENT._dmgParts = {}
|
||||
|
||||
ENT.DSArmorDamageReduction = 0.1
|
||||
ENT.DSArmorDamageReductionType = DMG_BULLET + DMG_CLUB
|
||||
|
||||
ENT.DSArmorIgnoreDamageType = DMG_SONIC
|
||||
ENT.DSArmorIgnoreForce = 0
|
||||
|
||||
ENT.DSArmorBulletPenetrationAdd = 250
|
||||
|
||||
function ENT:AddDS( data )
|
||||
if not data then return end
|
||||
|
||||
data.pos = data.pos or Vector(0,0,0)
|
||||
data.ang = data.ang or Angle(0,0,0)
|
||||
data.mins = data.mins or Vector(-1,-1,-1)
|
||||
data.maxs = data.maxs or Vector(1,1,1)
|
||||
data.entity = data.entity or self
|
||||
data.Callback = data.Callback or function( tbl, ent, dmginfo ) end
|
||||
|
||||
debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, data.entity:LocalToWorldAngles( data.ang ), 5, Color( 50, 0, 50, 150 ) )
|
||||
|
||||
table.insert( self._dmgParts, data )
|
||||
end
|
||||
|
||||
function ENT:AddDSArmor( data )
|
||||
if not data then return end
|
||||
|
||||
data.pos = data.pos or Vector(0,0,0)
|
||||
data.ang = data.ang or Angle(0,0,0)
|
||||
data.mins = data.mins or Vector(-1,-1,-1)
|
||||
data.maxs = data.maxs or Vector(1,1,1)
|
||||
data.entity = data.entity or self
|
||||
data.Callback = data.Callback or function( tbl, ent, dmginfo ) end
|
||||
|
||||
debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, data.entity:LocalToWorldAngles( data.ang ), 5, Color( 0, 50, 50, 150 ) )
|
||||
|
||||
table.insert( self._armorParts, data )
|
||||
end
|
||||
|
||||
function ENT:CalcComponentDamage( dmginfo )
|
||||
local Len = self:BoundingRadius()
|
||||
local dmgPos = dmginfo:GetDamagePosition()
|
||||
local dmgDir = dmginfo:GetDamageForce():GetNormalized()
|
||||
local dmgPenetration = dmgDir * self.DSArmorBulletPenetrationAdd
|
||||
|
||||
debugoverlay.Line( dmgPos - dmgDir * self.DSArmorBulletPenetrationAdd, dmgPos + dmgPenetration, 4, Color( 0, 0, 255 ) )
|
||||
|
||||
local closestPart
|
||||
local closestDist = Len * 2
|
||||
local HitDistance
|
||||
|
||||
for index, part in ipairs( self._armorParts ) do
|
||||
local target = part.entity
|
||||
|
||||
if not IsValid( target ) then continue end
|
||||
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = target:LocalToWorld( part.pos )
|
||||
local ang = target:LocalToWorldAngles( part.ang )
|
||||
|
||||
local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( dmgPos, dmgPenetration, pos, ang, mins, maxs )
|
||||
|
||||
if HitPos then
|
||||
debugoverlay.Cross( HitPos, 50, 4, Color( 255, 0, 255 ) )
|
||||
|
||||
local dist = (HitPos - dmgPos):Length()
|
||||
|
||||
if closestDist > dist then
|
||||
closestPart = part
|
||||
closestDist = dist
|
||||
HitDistance = (HitPos - dmgPos):Length()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lastPartDS
|
||||
local closestPartDS
|
||||
local closestDistDS = Len * 2
|
||||
for index, part in ipairs( self._dmgParts ) do
|
||||
local target = part.entity
|
||||
|
||||
if not IsValid( target ) then continue end
|
||||
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = target:LocalToWorld( part.pos )
|
||||
local ang = target:LocalToWorldAngles( part.ang )
|
||||
|
||||
local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( dmgPos, dmgPenetration, pos, ang, mins, maxs )
|
||||
|
||||
if HitPos and HitDistance then
|
||||
lastPartDS = part
|
||||
|
||||
if HitDistance < (HitPos - dmgPos):Length() then continue end
|
||||
|
||||
closestPart = nil
|
||||
closestDist = Len * 2
|
||||
end
|
||||
|
||||
if not HitPos then continue end
|
||||
|
||||
debugoverlay.Cross( HitPos, 50, 4, Color( 255, 0, 255 ) )
|
||||
|
||||
local dist = (HitPos - pos):Length()
|
||||
|
||||
if closestDistDS > dist then
|
||||
closestPartDS = part
|
||||
closestDistDS = dist
|
||||
end
|
||||
end
|
||||
|
||||
local Hit = false
|
||||
for index, part in pairs( self._dmgParts ) do
|
||||
local target = part.entity
|
||||
|
||||
if not IsValid( target ) then continue end
|
||||
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = target:LocalToWorld( part.pos )
|
||||
local ang = target:LocalToWorldAngles( part.ang )
|
||||
|
||||
if part == closestPartDS then
|
||||
Hit = true
|
||||
part:Callback( self, dmginfo )
|
||||
debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 0, 0, 150 ) )
|
||||
end
|
||||
end
|
||||
|
||||
for index, part in pairs( self._armorParts ) do
|
||||
local target = part.entity
|
||||
|
||||
if not IsValid( target ) then continue end
|
||||
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = target:LocalToWorld( part.pos )
|
||||
local ang = target:LocalToWorldAngles( part.ang )
|
||||
|
||||
if part == closestPart then
|
||||
if not part:Callback( self, dmginfo ) then
|
||||
lastPartDS = nil
|
||||
end
|
||||
|
||||
debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 0, 150, 0, 150 ) )
|
||||
end
|
||||
end
|
||||
|
||||
if lastPartDS then
|
||||
lastPartDS:Callback( self, dmginfo )
|
||||
|
||||
local target = lastPartDS.entity
|
||||
|
||||
local mins = lastPartDS.mins
|
||||
local maxs = lastPartDS.maxs
|
||||
local pos = target:LocalToWorld( lastPartDS.pos )
|
||||
local ang = target:LocalToWorldAngles( lastPartDS.ang )
|
||||
|
||||
debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 0, 0, 150 ) )
|
||||
|
||||
Hit = false
|
||||
end
|
||||
|
||||
return Hit
|
||||
end
|
||||
|
||||
function ENT:CalcDamage( dmginfo )
|
||||
if dmginfo:IsDamageType( self.DSArmorIgnoreDamageType ) then return end
|
||||
|
||||
if dmginfo:IsDamageType( self.DSArmorDamageReductionType ) then
|
||||
if dmginfo:GetDamage() ~= 0 then
|
||||
dmginfo:ScaleDamage( self.DSArmorDamageReduction )
|
||||
|
||||
dmginfo:SetDamage( math.max(dmginfo:GetDamage(),1) )
|
||||
end
|
||||
end
|
||||
|
||||
local IsFireDamage = dmginfo:IsDamageType( DMG_BURN )
|
||||
local IsCollisionDamage = dmginfo:GetDamageType() == (DMG_CRUSH + DMG_VEHICLE)
|
||||
local CriticalHit = false
|
||||
|
||||
if dmginfo:GetDamageForce():Length() < self.DSArmorIgnoreForce and not IsFireDamage then return end
|
||||
|
||||
if not IsCollisionDamage then
|
||||
CriticalHit = self:CalcComponentDamage( dmginfo )
|
||||
end
|
||||
|
||||
local Damage = dmginfo:GetDamage()
|
||||
|
||||
if Damage <= 0 then return end
|
||||
|
||||
local CurHealth = self:GetHP()
|
||||
|
||||
local NewHealth = math.Clamp( CurHealth - Damage, -self:GetMaxHP(), self:GetMaxHP() )
|
||||
|
||||
self:SetHP( NewHealth )
|
||||
|
||||
if self:IsDestroyed() then return end
|
||||
|
||||
local Attacker = dmginfo:GetAttacker()
|
||||
|
||||
if IsValid( Attacker ) and Attacker:IsPlayer() and not IsFireDamage then
|
||||
net.Start( "lvs_hitmarker" )
|
||||
net.WriteBool( CriticalHit )
|
||||
net.Send( Attacker )
|
||||
end
|
||||
|
||||
if Damage > 1 and not IsCollisionDamage and not IsFireDamage then
|
||||
net.Start( "lvs_hurtmarker" )
|
||||
net.WriteFloat( math.min( Damage / 50, 1 ) )
|
||||
net.Send( self:GetEveryone() )
|
||||
end
|
||||
|
||||
if NewHealth <= 0 then
|
||||
self.FinalAttacker = dmginfo:GetAttacker()
|
||||
self.FinalInflictor = dmginfo:GetInflictor()
|
||||
|
||||
self:SetDestroyed( IsCollisionDamage )
|
||||
|
||||
self:ClearPDS()
|
||||
|
||||
local Attacker = self.FinalAttacker
|
||||
if IsValid( Attacker ) and Attacker:IsPlayer() then
|
||||
net.Start( "lvs_killmarker" )
|
||||
net.Send( Attacker )
|
||||
end
|
||||
|
||||
local ExplodeTime = self:PreExplode( math.Clamp((self:GetVelocity():Length() - 200) / 200,1.5,16) )
|
||||
|
||||
timer.Simple( ExplodeTime, function()
|
||||
if not IsValid( self ) then return end
|
||||
self:Explode()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PreExplode( ExplodeTime )
|
||||
self:OnStartExplosion()
|
||||
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) then return 0 end
|
||||
|
||||
self:OnStartFireTrail( PhysObj, ExplodeTime )
|
||||
|
||||
return ExplodeTime
|
||||
end
|
||||
|
||||
function ENT:FindDS( PosToCheck, RadiusAdd )
|
||||
|
||||
if not isnumber( RadiusAdd ) then
|
||||
RadiusAdd = 1
|
||||
end
|
||||
|
||||
local closestPart
|
||||
local closestDist = 50000
|
||||
|
||||
local ToCenter = (self:LocalToWorld( self:OBBCenter() ) - PosToCheck):GetNormalized()
|
||||
|
||||
debugoverlay.Cross( PosToCheck, 50, 4, Color( 255, 255, 0 ) )
|
||||
|
||||
for _, tbl in ipairs( { self._armorParts, self._dmgParts } ) do
|
||||
for index, part in ipairs( tbl ) do
|
||||
local target = part.entity
|
||||
|
||||
if not IsValid( target ) then continue end
|
||||
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = target:LocalToWorld( part.pos )
|
||||
local ang = target:LocalToWorldAngles( part.ang )
|
||||
|
||||
local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( PosToCheck, ToCenter * RadiusAdd, pos, ang, mins, maxs )
|
||||
|
||||
if HitPos then
|
||||
local dist = (HitPos - PosToCheck):Length()
|
||||
|
||||
if closestDist > dist then
|
||||
closestPart = part
|
||||
closestDist = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if closestPart then
|
||||
local target = closestPart.entity
|
||||
local mins = closestPart.mins
|
||||
local maxs = closestPart.maxs
|
||||
local pos = target:LocalToWorld( closestPart.pos )
|
||||
local ang = target:LocalToWorldAngles( closestPart.ang )
|
||||
debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 255, 0, 150 ) )
|
||||
end
|
||||
|
||||
return closestPart
|
||||
end
|
||||
|
||||
function ENT:DamageThink()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if EntTable.MarkForDestruction then
|
||||
self:Explode()
|
||||
end
|
||||
|
||||
if EntTable._pdsPartsAutoProgress then
|
||||
if self:PDSThink( EntTable._pdsPartsAutoProgress ) then
|
||||
EntTable._pdsPartsAutoProgress = nil
|
||||
end
|
||||
end
|
||||
|
||||
if self:IsDestroyed() then
|
||||
if self:GetVelocity():Length() < 800 then
|
||||
self:Explode()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:HurtPlayer( ply, dmg, attacker, inflictor )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
if not IsValid( attacker ) then
|
||||
attacker = game.GetWorld()
|
||||
end
|
||||
|
||||
if not IsValid( inflictor ) then
|
||||
inflictor = game.GetWorld()
|
||||
end
|
||||
|
||||
local dmginfo = DamageInfo()
|
||||
dmginfo:SetDamage( dmg )
|
||||
dmginfo:SetAttacker( attacker )
|
||||
dmginfo:SetInflictor( inflictor )
|
||||
dmginfo:SetDamageType( DMG_DIRECT )
|
||||
|
||||
ply:TakeDamageInfo( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:Explode()
|
||||
if self.ExplodedAlready then return end
|
||||
|
||||
self.ExplodedAlready = true
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
self:HurtPlayer( Driver, Driver:Health() + Driver:Armor(), self.FinalAttacker, self.FinalInflictor )
|
||||
end
|
||||
|
||||
if istable( self.pSeats ) then
|
||||
for _, pSeat in pairs( self.pSeats ) do
|
||||
if not IsValid( pSeat ) then continue end
|
||||
|
||||
local psgr = pSeat:GetDriver()
|
||||
if not IsValid( psgr ) then continue end
|
||||
|
||||
self:HurtPlayer( psgr, psgr:Health() + psgr:Armor(), self.FinalAttacker, self.FinalInflictor )
|
||||
end
|
||||
end
|
||||
|
||||
self:OnFinishExplosion()
|
||||
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
function ENT:OnStartExplosion()
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( self:GetPos() )
|
||||
util.Effect( "lvs_explosion_nodebris", effectdata )
|
||||
end
|
||||
|
||||
function ENT:OnFinishExplosion()
|
||||
local ent = ents.Create( "lvs_destruction" )
|
||||
|
||||
if not IsValid( ent ) then return end
|
||||
|
||||
ent:SetModel( self:GetModel() )
|
||||
ent:SetPos( self:GetPos() )
|
||||
ent:SetAngles( self:GetAngles() )
|
||||
ent.GibModels = self.GibModels
|
||||
ent.Vel = self:GetVelocity()
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
end
|
||||
|
||||
function ENT:OnStartFireTrail( PhysObj, ExplodeTime )
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( self:GetPos() )
|
||||
effectdata:SetStart( PhysObj:GetMassCenter() )
|
||||
effectdata:SetEntity( self )
|
||||
effectdata:SetScale( (self.FireTrailScale or 1) )
|
||||
effectdata:SetMagnitude( ExplodeTime )
|
||||
util.Effect( "lvs_firetrail", effectdata )
|
||||
end
|
||||
|
||||
function ENT:IsDestroyed()
|
||||
return self.Destroyed == true
|
||||
end
|
||||
|
||||
function ENT:OnDestroyed()
|
||||
end
|
||||
|
||||
util.AddNetworkString( "lvs_vehicle_destroy" )
|
||||
|
||||
function ENT:SetDestroyed( SuppressOnDestroy )
|
||||
if self.Destroyed then return end
|
||||
|
||||
self.Destroyed = true
|
||||
|
||||
hook.Run( "LVS.OnVehicleDestroyed", self, self.FinalAttacker, self.FinalInflictor )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
|
||||
if SuppressOnDestroy then return end
|
||||
|
||||
self:OnDestroyed()
|
||||
|
||||
net.Start("lvs_vehicle_destroy")
|
||||
net.WriteEntity( self )
|
||||
net.SendPAS( self:GetPos() )
|
||||
end
|
||||
|
||||
include("sv_damagesystem_armor.lua")
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
ENT.DSArmorBulletPenetrationType = DMG_AIRBOAT + DMG_SNIPER
|
||||
|
||||
function ENT:AddArmor( pos, ang, mins, maxs, health, minforce, target )
|
||||
local Armor = ents.Create( "lvs_armor" )
|
||||
|
||||
if not IsValid( Armor ) then return end
|
||||
|
||||
if not target then target = self end
|
||||
|
||||
Armor:SetPos( target:LocalToWorld( pos ) )
|
||||
Armor:SetAngles( target:LocalToWorldAngles( ang ) )
|
||||
Armor:Spawn()
|
||||
Armor:Activate()
|
||||
Armor:SetParent( target )
|
||||
Armor:SetBase( self )
|
||||
Armor:SetMaxHP( health )
|
||||
Armor:SetHP( health )
|
||||
Armor:SetMins( mins )
|
||||
Armor:SetMaxs( maxs )
|
||||
|
||||
if isnumber( minforce ) then
|
||||
Armor:SetIgnoreForce( minforce + self.DSArmorIgnoreForce )
|
||||
else
|
||||
Armor:SetIgnoreForce( self.DSArmorIgnoreForce )
|
||||
end
|
||||
|
||||
self:DeleteOnRemove( Armor )
|
||||
|
||||
self:TransferCPPI( Armor )
|
||||
|
||||
self:AddDSArmor( {
|
||||
pos = pos,
|
||||
ang = ang,
|
||||
mins = mins,
|
||||
maxs = maxs,
|
||||
entity = target,
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
if not IsValid( Armor ) or not dmginfo:IsDamageType( self.DSArmorBulletPenetrationType + DMG_BLAST ) then return true end
|
||||
|
||||
local MaxHealth = self:GetMaxHP()
|
||||
local MaxArmor = Armor:GetMaxHP()
|
||||
local Damage = dmginfo:GetDamage()
|
||||
|
||||
local ArmoredHealth = MaxHealth + MaxArmor
|
||||
local NumShotsToKill = ArmoredHealth / Damage
|
||||
|
||||
local ScaleDamage = math.Clamp( MaxHealth / (NumShotsToKill * Damage),0,1)
|
||||
|
||||
local DidDamage = Armor:TakeTransmittedDamage( dmginfo )
|
||||
|
||||
if DidDamage then
|
||||
if dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then
|
||||
dmginfo:ScaleDamage( 0.05 )
|
||||
end
|
||||
|
||||
local Attacker = dmginfo:GetAttacker()
|
||||
|
||||
if IsValid( Attacker ) and Attacker:IsPlayer() then
|
||||
local NonLethal = self:GetHP() > Damage * ScaleDamage
|
||||
|
||||
if not ent._preventArmorMarker then
|
||||
net.Start( "lvs_armormarker" )
|
||||
net.WriteBool( NonLethal )
|
||||
net.Send( Attacker )
|
||||
|
||||
if not NonLethal then
|
||||
ent._preventArmorMarker = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dmginfo:ScaleDamage( ScaleDamage )
|
||||
else
|
||||
dmginfo:ScaleDamage( 0 )
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
} )
|
||||
|
||||
return Armor
|
||||
end
|
||||
|
||||
function ENT:OnArmorMaintenance()
|
||||
local Repaired = false
|
||||
|
||||
for _, part in pairs( self:GetCrosshairFilterEnts() ) do
|
||||
if not IsValid( part ) then continue end
|
||||
|
||||
if part:GetClass() ~= "lvs_armor" then continue end
|
||||
|
||||
part:OnRepaired()
|
||||
|
||||
if part:GetHP() ~= part:GetMaxHP() then
|
||||
part:SetHP( part:GetMaxHP() )
|
||||
|
||||
if part:GetDestroyed() then part:SetDestroyed( false ) end
|
||||
|
||||
Repaired = true
|
||||
end
|
||||
end
|
||||
|
||||
return Repaired
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
function ENT:AddDoorHandler( poseparameter, pos, ang, mins, maxs, openmins, openmaxs )
|
||||
if not isstring( poseparameter ) then return end
|
||||
|
||||
if not isvector( pos ) or not isangle( ang ) or not isvector( mins ) or not isvector( maxs ) then
|
||||
pos = vector_origin
|
||||
ang = angle_zero
|
||||
mins = self:OBBMins()
|
||||
maxs = self:OBBMaxs()
|
||||
end
|
||||
|
||||
if not isvector( openmins ) then
|
||||
openmins = mins
|
||||
end
|
||||
|
||||
if not isvector( openmaxs ) then
|
||||
openmaxs = maxs
|
||||
end
|
||||
|
||||
local Handler = ents.Create( "lvs_base_doorhandler" )
|
||||
|
||||
if not IsValid( Handler ) then
|
||||
return
|
||||
end
|
||||
|
||||
Handler:SetPos( self:LocalToWorld( pos ) )
|
||||
Handler:SetAngles( self:LocalToWorldAngles( ang ) )
|
||||
Handler:Spawn()
|
||||
Handler:Activate()
|
||||
Handler:SetParent( self )
|
||||
Handler:SetBase( self )
|
||||
Handler:SetMins( mins )
|
||||
Handler:SetMinsOpen( openmins )
|
||||
Handler:SetMinsClosed( mins )
|
||||
|
||||
Handler:SetMaxs( maxs )
|
||||
Handler:SetMaxsOpen( openmaxs )
|
||||
Handler:SetMaxsClosed( maxs )
|
||||
|
||||
Handler:SetPoseName( poseparameter )
|
||||
|
||||
self:DeleteOnRemove( Handler )
|
||||
|
||||
self:TransferCPPI( Handler )
|
||||
|
||||
if not istable( self._DoorHandlers ) then
|
||||
self._DoorHandlers = {}
|
||||
end
|
||||
|
||||
table.insert( self._DoorHandlers, Handler )
|
||||
|
||||
return Handler
|
||||
end
|
||||
|
||||
function ENT:GetDoorHandler( ply )
|
||||
if not IsValid( ply ) or not istable( self._DoorHandlers ) then return NULL end
|
||||
|
||||
local ShootPos = ply:GetShootPos()
|
||||
local AimVector = ply:GetAimVector()
|
||||
|
||||
local radius = 99999999999
|
||||
local target = NULL
|
||||
|
||||
for _, doorHandler in pairs( self._DoorHandlers ) do
|
||||
if not IsValid( doorHandler ) then continue end
|
||||
|
||||
local boxOrigin = doorHandler:GetPos()
|
||||
local boxAngles = doorHandler:GetAngles()
|
||||
local boxMins = doorHandler:GetMins()
|
||||
local boxMaxs = doorHandler:GetMaxs()
|
||||
|
||||
local HitPos, _, _ = util.IntersectRayWithOBB( ShootPos, AimVector * doorHandler.UseRange, boxOrigin, boxAngles, boxMins, boxMaxs )
|
||||
|
||||
local InRange = isvector( HitPos )
|
||||
|
||||
if not InRange then continue end
|
||||
|
||||
local dist = (ShootPos - HitPos):Length()
|
||||
|
||||
if dist < radius then
|
||||
target = doorHandler
|
||||
radius = dist
|
||||
end
|
||||
end
|
||||
|
||||
return target
|
||||
end
|
||||
|
||||
function ENT:GetDoorHandlers()
|
||||
if istable( self._DoorHandlers ) then return self._DoorHandlers end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
function ENT:HasDoorSystem()
|
||||
local HasDoorSystem = false
|
||||
|
||||
for _, Handler in pairs( self:GetDoorHandlers() ) do
|
||||
if not IsValid( Handler ) then continue end
|
||||
|
||||
if IsValid( Handler:GetLinkedSeat() ) then
|
||||
HasDoorSystem = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return HasDoorSystem
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
-- attempt at fixing dupe support
|
||||
|
||||
function ENT:PostEntityPaste( Player, Ent, CreatedEntities )
|
||||
|
||||
if isnumber( self._DuplicatorRestoreMaxHealthTo ) then
|
||||
self.MaxHealth = self._DuplicatorRestoreMaxHealthTo
|
||||
end
|
||||
|
||||
if self.SetlvsReady then
|
||||
self:SetlvsReady( false )
|
||||
end
|
||||
|
||||
if self.SetActive then
|
||||
self:SetActive( false )
|
||||
end
|
||||
|
||||
if self.SetEngineActive then
|
||||
self:SetEngineActive( false )
|
||||
end
|
||||
|
||||
if not self.SetAI then return end
|
||||
|
||||
if IsValid( Player ) and Player:IsAdmin() then return end
|
||||
|
||||
self:SetAI( false )
|
||||
end
|
||||
|
||||
function ENT:OnEntityCopyTableFinish( data )
|
||||
data.CrosshairFilterEnts = nil
|
||||
data.pPodKeyIndex = nil
|
||||
data.pSeats = nil
|
||||
data.WEAPONS = nil
|
||||
|
||||
-- everything with "_" at the start, usually temporary variables or variables used for timing. This will fix vehicles that are saved at a high curtime and then being spawned on a fresh server with low curtime
|
||||
for id, _ in pairs( data ) do
|
||||
if not string.StartsWith( id, "_" ) then continue end
|
||||
|
||||
data[ id ] = nil
|
||||
end
|
||||
|
||||
data._DuplicatorRestoreMaxHealthTo = self:GetMaxHP()
|
||||
|
||||
-- all functions need to go
|
||||
for id, entry in pairs( data ) do
|
||||
if not isfunction( entry ) then continue end
|
||||
|
||||
data[ id ] = nil
|
||||
end
|
||||
|
||||
-- stuff below is things like constraints or DeleteOnRemove still referencing the old object. These need to go
|
||||
data.OnDieFunctions = nil
|
||||
data.Constraints = nil
|
||||
data._children = nil
|
||||
end
|
||||
|
||||
hook.Add("AdvDupe_FinishPasting", "!!lvs_vehicle_dupefinished_initialize", function( data, entry )
|
||||
for _, v in pairs( data[ entry ].CreatedEntities ) do
|
||||
|
||||
if not IsValid( v ) or not isentity( v ) or not v.LVS then continue end
|
||||
|
||||
timer.Simple(1, function()
|
||||
if not IsValid( v ) then return end
|
||||
|
||||
if v.SetlvsReady and not v:GetlvsReady() then
|
||||
v:SetlvsReady( true )
|
||||
end
|
||||
|
||||
if v.RebuildCrosshairFilterEnts then
|
||||
v:RebuildCrosshairFilterEnts()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end )
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
function ENT:HandleStart()
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
local KeyReload = Driver:lvsKeyDown( "ENGINE" )
|
||||
|
||||
if self.OldKeyReload ~= KeyReload then
|
||||
self.OldKeyReload = KeyReload
|
||||
|
||||
if KeyReload then
|
||||
self:ToggleEngine()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:ToggleEngine()
|
||||
if self:GetEngineActive() then
|
||||
self:StopEngine()
|
||||
else
|
||||
self:StartEngine()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:IsEngineStartAllowed()
|
||||
if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end
|
||||
|
||||
if self:WaterLevel() > self.WaterLevelPreventStart then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:OnEngineActiveChanged( Active )
|
||||
end
|
||||
|
||||
function ENT:StartEngine()
|
||||
if self:GetEngineActive() or not self:IsEngineStartAllowed() then return end
|
||||
|
||||
self:PhysWake()
|
||||
|
||||
self:SetEngineActive( true )
|
||||
self:OnEngineActiveChanged( true )
|
||||
end
|
||||
|
||||
function ENT:StopEngine()
|
||||
if not self:GetEngineActive() then return end
|
||||
|
||||
self:SetEngineActive( false )
|
||||
self:OnEngineActiveChanged( false )
|
||||
end
|
||||
177
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics.lua
Normal file
177
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics.lua
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
function ENT:GetWorldGravity()
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) or not PhysObj:IsGravityEnabled() then return 0 end
|
||||
|
||||
return physenv.GetGravity():Length()
|
||||
end
|
||||
|
||||
function ENT:GetWorldUp()
|
||||
local Gravity = physenv.GetGravity()
|
||||
|
||||
if Gravity:Length() > 0 then
|
||||
return -Gravity:GetNormalized()
|
||||
else
|
||||
return Vector(0,0,1)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
end
|
||||
|
||||
function ENT:PhysicsStopScape()
|
||||
if self._lvsScrapeData then
|
||||
if self._lvsScrapeData.sound then
|
||||
self._lvsScrapeData.sound:Stop()
|
||||
end
|
||||
end
|
||||
|
||||
self._lvsScrapeData = nil
|
||||
end
|
||||
|
||||
function ENT:PhysicsStartScrape( pos, dir )
|
||||
local startpos = self:LocalToWorld( pos )
|
||||
|
||||
local trace = util.TraceLine( {
|
||||
start = startpos - dir * 5,
|
||||
endpos = startpos + dir * 5,
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
if trace.Hit then
|
||||
local sound
|
||||
|
||||
if self._lvsScrapeData and self._lvsScrapeData.sound then
|
||||
sound = self._lvsScrapeData.sound
|
||||
else
|
||||
sound = CreateSound( self, "LVS.Physics.Scrape" )
|
||||
sound:PlayEx( 0, 90 + math.min( (self:GetVelocity():Length() / 2000) * 10,10) )
|
||||
end
|
||||
|
||||
self._lvsScrapeData = {
|
||||
dir = dir,
|
||||
pos = pos,
|
||||
sound = sound,
|
||||
finishtime = CurTime() + 2,
|
||||
}
|
||||
|
||||
self:CallOnRemove( "stop_scraping", function( self )
|
||||
self:PhysicsStopScape()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PhysicsThink()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable._lvsScrapeData then return end
|
||||
|
||||
if EntTable._lvsScrapeData.finishtime < CurTime() then
|
||||
self:PhysicsStopScape()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local startpos = self:LocalToWorld( EntTable._lvsScrapeData.pos )
|
||||
|
||||
local trace = util.TraceLine( {
|
||||
start = startpos - EntTable._lvsScrapeData.dir,
|
||||
endpos = startpos + EntTable._lvsScrapeData.dir * 5,
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
local Vel = self:GetVelocity():Length()
|
||||
|
||||
if trace.Hit and Vel > 25 then
|
||||
local vol = math.min(math.max(Vel - 50,0) / 1000,1)
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos + trace.HitNormal )
|
||||
effectdata:SetNormal( trace.HitNormal )
|
||||
effectdata:SetMagnitude( vol )
|
||||
util.Effect( "lvs_physics_scrape", effectdata, true, true )
|
||||
|
||||
EntTable._lvsScrapeData.sound:ChangeVolume( vol, 0.1 )
|
||||
else
|
||||
self:PhysicsStopScape()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TakeCollisionDamage( damage, attacker )
|
||||
if not IsValid( attacker ) then
|
||||
attacker = game.GetWorld()
|
||||
end
|
||||
|
||||
local dmginfo = DamageInfo()
|
||||
dmginfo:SetDamage( damage )
|
||||
dmginfo:SetAttacker( attacker )
|
||||
dmginfo:SetInflictor( attacker )
|
||||
dmginfo:SetDamageType( DMG_CRUSH + DMG_VEHICLE ) -- this will communicate to the damage system to handle this kind of damage differently.
|
||||
self:TakeDamageInfo( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:OnCollision( data, physobj )
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:OnSkyCollide( data, physobj )
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PhysicsCollide( data, physobj )
|
||||
local HitEnt = data.HitEntity
|
||||
|
||||
if not IsValid( HitEnt ) and util.GetSurfacePropName( data.TheirSurfaceProps ) == "default_silent" then
|
||||
if self:OnSkyCollide( data, physobj ) then return end
|
||||
end
|
||||
|
||||
if self:IsDestroyed() then
|
||||
self.MarkForDestruction = true
|
||||
end
|
||||
|
||||
if self:OnCollision( data, physobj ) then return end
|
||||
|
||||
self:PhysicsStartScrape( self:WorldToLocal( data.HitPos ), data.HitNormal )
|
||||
|
||||
if IsValid( HitEnt ) then
|
||||
if HitEnt:IsPlayer() or HitEnt:IsNPC() then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if self:GetAI() and not self:IsPlayerHolding() then
|
||||
if self:WaterLevel() >= self.WaterLevelDestroyAI then
|
||||
self:SetDestroyed( true )
|
||||
self.MarkForDestruction = true
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local VelDif = (data.OurOldVelocity - data.OurNewVelocity):Length()
|
||||
|
||||
if VelDif > 60 and data.DeltaTime > 0.2 then
|
||||
self:CalcPDS( data )
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data.HitPos )
|
||||
effectdata:SetNormal( -data.HitNormal )
|
||||
util.Effect( "lvs_physics_impact", effectdata, true, true )
|
||||
|
||||
if VelDif > 1000 then
|
||||
local damage = math.max( VelDif - 1000, 0 )
|
||||
|
||||
if not self:IsPlayerHolding() and damage > 0 then
|
||||
self:EmitSound( "lvs/physics/impact_hard.wav", 85, 100 + math.random(-3,3), 1 )
|
||||
self:TakeCollisionDamage( VelDif, HitEnt )
|
||||
end
|
||||
else
|
||||
if VelDif > 800 then
|
||||
self:EmitSound( "lvs/physics/impact_hard"..math.random(1,3)..".wav", 75, 100 + math.random(-3,3), 1 )
|
||||
else
|
||||
self:EmitSound( "lvs/physics/impact_soft"..math.random(1,5)..".wav", 75, 100 + math.random(-6,6), math.min( VelDif / 600, 1 ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,267 @@
|
||||
|
||||
ENT._pdsParts = {}
|
||||
|
||||
ENT.PDSDamageVelocity = 100
|
||||
ENT.PDSDamageMultiplier = 0.05
|
||||
|
||||
function ENT:ClearPDS()
|
||||
if not istable( self._pdsParts ) then return end
|
||||
|
||||
table.Empty( self._pdsParts )
|
||||
end
|
||||
|
||||
function ENT:PDSHealthValueChanged( name, old, new)
|
||||
if new == old then return end
|
||||
|
||||
if not self:IsInitialized() or not istable( self._pdsParts ) or new ~= self:GetMaxHP() then return end
|
||||
|
||||
self._pdsPartsAutoProgress = nil
|
||||
|
||||
for _, part in pairs( self._pdsParts ) do
|
||||
part:SetStage( 0 )
|
||||
|
||||
if not part._group or not part._subgroup then continue end
|
||||
|
||||
self:SetBodygroup( part._group, part._subgroup )
|
||||
|
||||
part._group = nil
|
||||
part._subgroup = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function DamagePart( ent, part, speed )
|
||||
if ent._pdsPartsAutoProgress and ent._pdsPartsAutoProgress.part == part then
|
||||
ent._pdsPartsAutoProgress = nil
|
||||
end
|
||||
|
||||
if not speed then
|
||||
speed = 0
|
||||
end
|
||||
|
||||
local stage = part:GetStage() + 1
|
||||
|
||||
part:SetStage( stage )
|
||||
|
||||
local data = part:GetStageData()
|
||||
|
||||
if isfunction( data.Callback ) then
|
||||
data:Callback( ent, part, speed )
|
||||
end
|
||||
|
||||
if istable( data.bodygroup ) then
|
||||
for group, subgroup in pairs( data.bodygroup ) do
|
||||
if not part._group or not part._subgroup then
|
||||
part._group = group
|
||||
part._subgroup = ent:GetBodygroup( group )
|
||||
end
|
||||
|
||||
ent:SetBodygroup( group, subgroup )
|
||||
end
|
||||
end
|
||||
|
||||
if isstring( data.sound ) then
|
||||
ent:EmitSound( data.sound, 75, 100, math.min(0.1 + speed / 700,1) )
|
||||
end
|
||||
|
||||
if isnumber( data.maxvelocity ) then
|
||||
ent._pdsPartsAutoProgress = {
|
||||
part = part,
|
||||
velocity = data.maxvelocity,
|
||||
}
|
||||
end
|
||||
|
||||
if isstring( data.effect ) then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( ent:LocalToWorld( part.pos ) )
|
||||
util.Effect( data.effect, effectdata, true, true )
|
||||
end
|
||||
|
||||
if not istable( data.gib ) or not data.gib.mdl then return end
|
||||
|
||||
timer.Simple(0, function()
|
||||
if not IsValid( ent ) then return end
|
||||
|
||||
local InvAttach = isstring( data.gib.target )
|
||||
|
||||
local pos
|
||||
local ang
|
||||
|
||||
if InvAttach then
|
||||
pos = vector_origin
|
||||
ang = angle_zero
|
||||
else
|
||||
if isvector( data.gib.pos ) and isangle( data.gib.ang ) then
|
||||
pos = ent:LocalToWorld( data.gib.pos )
|
||||
ang = ent:LocalToWorldAngles( data.gib.ang )
|
||||
end
|
||||
end
|
||||
|
||||
local gib = ents.Create( "prop_physics" )
|
||||
gib:SetModel( data.gib.mdl )
|
||||
gib:SetPos( pos )
|
||||
gib:SetAngles( ang )
|
||||
gib:Spawn()
|
||||
gib:Activate()
|
||||
gib:SetCollisionGroup( COLLISION_GROUP_DEBRIS )
|
||||
gib:SetSkin( ent:GetSkin() )
|
||||
|
||||
if InvAttach then
|
||||
local att = gib:GetAttachment( gib:LookupAttachment( data.gib.target ) )
|
||||
|
||||
if att then
|
||||
local newpos = ent:LocalToWorld( -att.Pos )
|
||||
local newang = ent:LocalToWorldAngles( -att.Ang )
|
||||
|
||||
gib:SetPos( newpos )
|
||||
gib:SetAngles( newang )
|
||||
end
|
||||
end
|
||||
|
||||
gib:SetOwner( ent )
|
||||
gib:SetColor( ent:GetColor() )
|
||||
gib:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
|
||||
ent:DeleteOnRemove( gib )
|
||||
ent:TransferCPPI( gib )
|
||||
|
||||
timer.Simple( 59.5, function()
|
||||
if not IsValid( gib ) then return end
|
||||
gib:SetRenderFX( kRenderFxFadeFast )
|
||||
end)
|
||||
|
||||
timer.Simple( 60, function()
|
||||
if not IsValid( gib ) then return end
|
||||
gib:Remove()
|
||||
end)
|
||||
|
||||
local PhysObj = gib:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) then return end
|
||||
|
||||
PhysObj:SetVelocityInstantaneous( ent:GetVelocity() + Vector(0,0,250) )
|
||||
PhysObj:AddAngleVelocity( VectorRand() * 500 )
|
||||
end)
|
||||
end
|
||||
|
||||
function ENT:AddPDS( data )
|
||||
if not data then return end
|
||||
|
||||
if self._pdsPartsID then
|
||||
self._pdsPartsID = self._pdsPartsID + 1
|
||||
else
|
||||
self._pdsPartsID = 1
|
||||
end
|
||||
|
||||
data.pos = data.pos or Vector(0,0,0)
|
||||
data.ang = data.ang or Angle(0,0,0)
|
||||
data.mins = data.mins or Vector(-1,-1,-1)
|
||||
data.maxs = data.maxs or Vector(1,1,1)
|
||||
data.stages = data.stages or {}
|
||||
data.GetStage = function( self )
|
||||
if not self._curstage then
|
||||
self._curstage = 0
|
||||
end
|
||||
|
||||
return self._curstage
|
||||
end
|
||||
data.SetStage = function( self, stage )
|
||||
self._curstage = stage
|
||||
end
|
||||
|
||||
data.GetStageData = function( self, stage )
|
||||
return self.stages[ self:GetStage() ] or {}
|
||||
end
|
||||
|
||||
debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, self:LocalToWorldAngles( data.ang ), 8, Color( 50, 50, 0, 150 ) )
|
||||
|
||||
self._pdsParts[ self._pdsPartsID ] = data
|
||||
|
||||
if data.allow_damage then
|
||||
local id = self._pdsPartsID
|
||||
|
||||
self:AddDS( {
|
||||
pos = data.pos,
|
||||
ang = data.ang,
|
||||
mins = data.mins,
|
||||
maxs = data.maxs,
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
if not IsValid( ent ) then return end
|
||||
|
||||
local part = ent._pdsParts[ id ]
|
||||
|
||||
if not part then return end
|
||||
|
||||
DamagePart( ent, part, 1000 )
|
||||
end
|
||||
} )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:FindPDS( PosToCheck, RadiusAdd )
|
||||
if not isnumber( RadiusAdd ) then
|
||||
RadiusAdd = 1
|
||||
end
|
||||
|
||||
if InfMap then
|
||||
PosToCheck = InfMap.unlocalize_vector( PosToCheck, self.CHUNK_OFFSET )
|
||||
end
|
||||
|
||||
local Parts = {}
|
||||
|
||||
debugoverlay.Cross( PosToCheck, 50, 4, Color( 255, 255, 0 ) )
|
||||
|
||||
for index, part in ipairs( self._pdsParts ) do
|
||||
local mins = part.mins
|
||||
local maxs = part.maxs
|
||||
local pos = self:LocalToWorld( part.pos )
|
||||
local ang = self:LocalToWorldAngles( part.ang )
|
||||
local dir = (pos - PosToCheck):GetNormalized()
|
||||
|
||||
local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( PosToCheck, dir * RadiusAdd, pos, ang, mins, maxs )
|
||||
|
||||
if HitPos then
|
||||
table.insert( Parts, part )
|
||||
end
|
||||
end
|
||||
|
||||
for _, closestPart in ipairs( Parts ) do
|
||||
local mins = closestPart.mins
|
||||
local maxs = closestPart.maxs
|
||||
local pos = self:LocalToWorld( closestPart.pos )
|
||||
local ang = self:LocalToWorldAngles( closestPart.ang )
|
||||
debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 255, 0, 150 ) )
|
||||
end
|
||||
|
||||
return Parts
|
||||
end
|
||||
|
||||
function ENT:CalcPDS( physdata )
|
||||
local VelDif = (physdata.OurOldVelocity - physdata.OurNewVelocity):Length()
|
||||
|
||||
if VelDif < self.PDSDamageVelocity then return end
|
||||
|
||||
local parts = self:FindPDS( physdata.HitPos, (VelDif - self.PDSDamageVelocity) * self.PDSDamageMultiplier )
|
||||
|
||||
if #parts == 0 then return end
|
||||
|
||||
local HP = self:GetHP()
|
||||
local MaxHP = self:GetMaxHP()
|
||||
|
||||
if HP == MaxHP then
|
||||
self:SetHP( math.max( MaxHP - 0.1, 1 ) )
|
||||
end
|
||||
|
||||
for _, part in pairs( parts ) do
|
||||
DamagePart( self, part, VelDif )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PDSThink( data )
|
||||
local vel = self:GetVelocity():Length()
|
||||
|
||||
if vel < data.velocity then return end
|
||||
|
||||
DamagePart( self, data.part, vel )
|
||||
|
||||
return true
|
||||
end
|
||||
199
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_pod.lua
Normal file
199
garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_pod.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
|
||||
ENT.DriverActiveSound = "vehicles/atv_ammo_close.wav"
|
||||
ENT.DriverInActiveSound = "vehicles/atv_ammo_open.wav"
|
||||
|
||||
function ENT:AlignView( ply, SetZero )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
timer.Simple( 0, function()
|
||||
if not IsValid( ply ) or not IsValid( self ) then return end
|
||||
local Ang = Angle(0,90,0)
|
||||
|
||||
if not SetZero then
|
||||
Ang = self:GetAngles()
|
||||
Ang.r = 0
|
||||
end
|
||||
|
||||
ply:SetEyeAngles( Ang )
|
||||
end)
|
||||
end
|
||||
|
||||
function ENT:HandleActive()
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( Pod ) then
|
||||
self:SetActive( false )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local Driver = Pod:GetDriver()
|
||||
local Active = self:GetActive()
|
||||
|
||||
if Driver ~= self:GetDriver() then
|
||||
local NewDriver = Driver
|
||||
local OldDriver = self:GetDriver()
|
||||
local IsActive = IsValid( Driver )
|
||||
|
||||
self:SetDriver( Driver )
|
||||
self:SetActive( IsActive )
|
||||
|
||||
self:OnDriverChanged( OldDriver, NewDriver, IsActive )
|
||||
|
||||
if IsActive then
|
||||
Driver:lvsBuildControls()
|
||||
self:AlignView( Driver )
|
||||
|
||||
if self.DriverActiveSound then self:EmitSound( self.DriverActiveSound ) end
|
||||
else
|
||||
self:WeaponsFinish()
|
||||
|
||||
if self.DriverInActiveSound then self:EmitSound( self.DriverInActiveSound ) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetPassenger( ply )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local AI = self:GetAI()
|
||||
local DriverSeat = self:GetDriverSeat()
|
||||
local AllowedToBeDriver = hook.Run( "LVS.CanPlayerDrive", ply, self ) ~= false
|
||||
|
||||
if IsValid( DriverSeat ) and not IsValid( DriverSeat:GetDriver() ) and not ply:KeyDown( IN_WALK ) and not AI and AllowedToBeDriver then
|
||||
ply:EnterVehicle( DriverSeat )
|
||||
self:AlignView( ply )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
else
|
||||
local Seat = NULL
|
||||
local Dist = 500000
|
||||
|
||||
for _, v in pairs( self:GetPassengerSeats() ) do
|
||||
if not IsValid( v ) or IsValid( v:GetDriver() ) then continue end
|
||||
if v:GetNWInt( "pPodIndex" ) == -1 then continue end
|
||||
|
||||
local cDist = (v:GetPos() - ply:GetPos()):Length()
|
||||
|
||||
if cDist < Dist then
|
||||
Seat = v
|
||||
Dist = cDist
|
||||
end
|
||||
end
|
||||
|
||||
if IsValid( Seat ) then
|
||||
ply:EnterVehicle( Seat )
|
||||
self:AlignView( ply, true )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
else
|
||||
if IsValid( DriverSeat ) then
|
||||
if not IsValid( self:GetDriver() ) and not AI then
|
||||
if AllowedToBeDriver then
|
||||
ply:EnterVehicle( DriverSeat )
|
||||
self:AlignView( ply )
|
||||
|
||||
hook.Run( "LVS.UpdateRelationship", self )
|
||||
else
|
||||
hook.Run( "LVS.OnPlayerCannotDrive", ply, self )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:AddDriverSeat( Pos, Ang )
|
||||
if IsValid( self:GetDriverSeat() ) then return self:GetDriverSeat() end
|
||||
|
||||
local Pod = ents.Create( "prop_vehicle_prisoner_pod" )
|
||||
|
||||
if not IsValid( Pod ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create driverseat. Vehicle terminated.")
|
||||
|
||||
return
|
||||
else
|
||||
self:SetDriverSeat( Pod )
|
||||
|
||||
local DSPhys = Pod:GetPhysicsObject()
|
||||
|
||||
Pod:SetMoveType( MOVETYPE_NONE )
|
||||
Pod:SetModel( "models/nova/airboat_seat.mdl" )
|
||||
Pod:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" )
|
||||
Pod:SetKeyValue( "limitview", 0 )
|
||||
Pod:SetPos( self:LocalToWorld( Pos ) )
|
||||
Pod:SetAngles( self:LocalToWorldAngles( Ang ) )
|
||||
Pod:SetOwner( self )
|
||||
Pod:Spawn()
|
||||
Pod:Activate()
|
||||
Pod:SetParent( self )
|
||||
Pod:SetNotSolid( true )
|
||||
Pod:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
Pod:DrawShadow( false )
|
||||
Pod.DoNotDuplicate = true
|
||||
|
||||
Pod:lvsSetPodIndex( 1 )
|
||||
|
||||
Pod:PhysicsDestroy()
|
||||
|
||||
debugoverlay.BoxAngles( Pod:GetPos(), Pod:OBBMins(), Pod:OBBMaxs(), Pod:GetAngles(), 5, Color( 255, 93, 0, 200 ) )
|
||||
|
||||
self:DeleteOnRemove( Pod )
|
||||
|
||||
self:TransferCPPI( Pod )
|
||||
end
|
||||
|
||||
return Pod
|
||||
end
|
||||
|
||||
function ENT:AddPassengerSeat( Pos, Ang )
|
||||
if not isvector( Pos ) or not isangle( Ang ) then return NULL end
|
||||
|
||||
local Pod = ents.Create( "prop_vehicle_prisoner_pod" )
|
||||
|
||||
if not IsValid( Pod ) then return NULL end
|
||||
|
||||
Pod:SetMoveType( MOVETYPE_NONE )
|
||||
Pod:SetModel( "models/nova/airboat_seat.mdl" )
|
||||
Pod:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" )
|
||||
Pod:SetKeyValue( "limitview", 0 )
|
||||
Pod:SetPos( self:LocalToWorld( Pos ) )
|
||||
Pod:SetAngles( self:LocalToWorldAngles( Ang ) )
|
||||
Pod:SetOwner( self )
|
||||
Pod:Spawn()
|
||||
Pod:Activate()
|
||||
Pod:SetParent( self )
|
||||
Pod:SetNotSolid( true )
|
||||
Pod:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
|
||||
debugoverlay.BoxAngles( Pod:GetPos(), Pod:OBBMins(), Pod:OBBMaxs(), Pod:GetAngles(), 5, Color( 100, 65, 127, 200 ) )
|
||||
|
||||
Pod:DrawShadow( false )
|
||||
Pod.DoNotDuplicate = true
|
||||
|
||||
self.pPodKeyIndex = self.pPodKeyIndex and self.pPodKeyIndex + 1 or 2
|
||||
|
||||
if self.WEAPONS[ self.pPodKeyIndex ] then
|
||||
local weapon = Pod:lvsAddWeapon( self.pPodKeyIndex )
|
||||
|
||||
if IsValid( weapon ) then
|
||||
self:TransferCPPI( weapon )
|
||||
self:DeleteOnRemove( weapon )
|
||||
end
|
||||
end
|
||||
|
||||
Pod:lvsSetPodIndex( self.pPodKeyIndex )
|
||||
|
||||
Pod:PhysicsDestroy()
|
||||
|
||||
self:DeleteOnRemove( Pod )
|
||||
self:TransferCPPI( Pod )
|
||||
|
||||
if not istable( self.pSeats ) then self.pSeats = {} end
|
||||
|
||||
table.insert( self.pSeats, Pod )
|
||||
|
||||
return Pod
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
ENT.ShieldRechargeDelay = 5
|
||||
ENT.ShieldRechargeRate = 1
|
||||
ENT.ShieldBlockableTypes = {
|
||||
[1] = DMG_BULLET,
|
||||
[2] = DMG_AIRBOAT,
|
||||
[3] = DMG_BUCKSHOT,
|
||||
[4] = DMG_SNIPER,
|
||||
[5] = DMG_BLAST,
|
||||
}
|
||||
|
||||
function ENT:CalcShieldDamage( dmginfo )
|
||||
local MaxShield = self:GetMaxShield()
|
||||
|
||||
if MaxShield <= 0 then return end
|
||||
|
||||
local DMG_ENUM = DMG_GENERIC
|
||||
for _, ENUM in ipairs( self.ShieldBlockableTypes ) do
|
||||
DMG_ENUM = DMG_ENUM + ENUM
|
||||
end
|
||||
|
||||
if not dmginfo:IsDamageType( DMG_ENUM ) then return end
|
||||
|
||||
self:DelayNextShieldRecharge( self.ShieldRechargeDelay )
|
||||
|
||||
local DamageRemaining = self:TakeShieldDamage( dmginfo:GetDamage() )
|
||||
|
||||
dmginfo:SetDamage( DamageRemaining )
|
||||
|
||||
self:OnTakeShieldDamage( dmginfo )
|
||||
end
|
||||
|
||||
function ENT:CanShieldRecharge()
|
||||
return (self.NextShieldRecharge or 0) < CurTime()
|
||||
end
|
||||
|
||||
function ENT:DelayNextShieldRecharge( delay )
|
||||
self.NextShieldRecharge = CurTime() + delay
|
||||
end
|
||||
|
||||
function ENT:ShieldThink()
|
||||
local MaxShield = self:GetMaxShield()
|
||||
|
||||
if MaxShield <= 0 or self:GetShieldPercent() == 1 then return end
|
||||
|
||||
if not self:CanShieldRecharge() then return end
|
||||
|
||||
local Cur = self:GetShield()
|
||||
local Rate = FrameTime() * 20 * self.ShieldRechargeRate
|
||||
|
||||
self:SetShield( Cur + math.Clamp(MaxShield - Cur,-Rate,Rate) )
|
||||
end
|
||||
|
||||
function ENT:TakeShieldDamage( damage )
|
||||
local cur = self:GetShield()
|
||||
local sub = cur - damage
|
||||
local new = math.Clamp( sub , 0, self:GetMaxShield() )
|
||||
|
||||
self:SetShield( new )
|
||||
|
||||
if sub < 0 then
|
||||
return math.abs( sub )
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnTakeShieldDamage( dmginfo )
|
||||
if dmginfo:GetDamage() ~= 0 then return end
|
||||
|
||||
local dmgNormal = -dmginfo:GetDamageForce():GetNormalized()
|
||||
local dmgPos = dmginfo:GetDamagePosition()
|
||||
|
||||
dmginfo:SetDamagePosition( dmgPos + dmgNormal * 250 * self:GetShieldPercent() )
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( dmginfo:GetDamagePosition() )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_shield_impact", effectdata )
|
||||
end
|
||||
453
garrysmod/addons/lvs_base/lua/entities/lvs_base_doorhandler.lua
Normal file
453
garrysmod/addons/lvs_base/lua/entities/lvs_base_doorhandler.lua
Normal file
@@ -0,0 +1,453 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.DoNotDuplicate = true
|
||||
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
ENT.UseRange = 75
|
||||
|
||||
ENT._UseTargetAllowed = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar( "Entity",0, "Base" )
|
||||
self:NetworkVar( "Entity",1, "LinkedSeat" )
|
||||
|
||||
self:NetworkVar( "Bool",0, "Active" )
|
||||
|
||||
self:NetworkVar( "String",0, "PoseName" )
|
||||
|
||||
self:NetworkVar( "Vector",0, "Mins" )
|
||||
self:NetworkVar( "Vector",1, "Maxs" )
|
||||
|
||||
self:NetworkVar( "Float",0, "Rate" )
|
||||
self:NetworkVar( "Float",1, "RateExponent" )
|
||||
|
||||
self:NetworkVar( "Float",2, "PoseMin" )
|
||||
self:NetworkVar( "Float",3, "PoseMax" )
|
||||
|
||||
if SERVER then
|
||||
self:SetRate( 10 )
|
||||
self:SetRateExponent( 2 )
|
||||
|
||||
self:SetPoseMax( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:IsServerSide()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if isbool( EntTable._IsServerSide ) then return EntTable._IsServerSide end
|
||||
|
||||
local PoseName = self:GetPoseName()
|
||||
|
||||
if PoseName == "" then return false end
|
||||
|
||||
local IsServerSide = string.StartsWith( PoseName, "^" )
|
||||
|
||||
EntTable._IsServerSide = IsServerSide
|
||||
|
||||
return IsServerSide
|
||||
end
|
||||
|
||||
function ENT:IsOpen()
|
||||
return self:GetActive()
|
||||
end
|
||||
|
||||
function ENT:InRange( ply, Range )
|
||||
local boxOrigin = self:GetPos()
|
||||
local boxAngles = self:GetAngles()
|
||||
local boxMins = self:GetMins()
|
||||
local boxMaxs = self:GetMaxs()
|
||||
|
||||
local HitPos, _, _ = util.IntersectRayWithOBB( ply:GetShootPos(), ply:GetAimVector() * Range, boxOrigin, boxAngles, boxMins, boxMaxs )
|
||||
|
||||
return isvector( HitPos )
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
AccessorFunc(ENT, "soundopen", "SoundOpen", FORCE_STRING)
|
||||
AccessorFunc(ENT, "soundclose", "SoundClose", FORCE_STRING)
|
||||
|
||||
AccessorFunc(ENT, "maxsopen", "MaxsOpen", FORCE_VECTOR)
|
||||
AccessorFunc(ENT, "minsopen", "MinsOpen", FORCE_VECTOR)
|
||||
|
||||
AccessorFunc(ENT, "maxsclosed", "MaxsClosed", FORCE_VECTOR)
|
||||
AccessorFunc(ENT, "minsclosed", "MinsClosed", FORCE_VECTOR)
|
||||
|
||||
|
||||
util.AddNetworkString( "lvs_doorhandler_interact" )
|
||||
|
||||
net.Receive( "lvs_doorhandler_interact", function( length, ply )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local ent = net.ReadEntity()
|
||||
|
||||
if not IsValid( ent ) or not ent._UseTargetAllowed or not ent.UseRange or ply:InVehicle() then return end
|
||||
|
||||
local Range = ent.UseRange * 2
|
||||
|
||||
if (ply:GetPos() - ent:GetPos()):Length() > Range then return end
|
||||
|
||||
if not ent:InRange( ply, Range ) then return end
|
||||
|
||||
ent:Use( ply, ply )
|
||||
end)
|
||||
|
||||
function ENT:LinkToSeat( ent )
|
||||
if not IsValid( ent ) or not ent:IsVehicle() then
|
||||
|
||||
ErrorNoHalt( "[LVS] Couldn't link seat to doorsystem. Entity expected, got "..tostring( ent ).."\n" )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetLinkedSeat( ent )
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
self:SetMoveType( MOVETYPE_NONE )
|
||||
self:SetSolid( SOLID_NONE )
|
||||
self:SetUseType( SIMPLE_USE )
|
||||
self:DrawShadow( false )
|
||||
debugoverlay.Cross( self:GetPos(), 15, 5, Color( 255, 223, 127 ) )
|
||||
end
|
||||
|
||||
function ENT:Use( ply )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local Base = self:GetBase()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
if not Base:IsUseAllowed( ply ) then return end
|
||||
|
||||
if self:IsOpen() then
|
||||
self:Close( ply )
|
||||
else
|
||||
self:Open( ply )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnOpen( ply )
|
||||
end
|
||||
|
||||
function ENT:OnClosed( ply )
|
||||
end
|
||||
|
||||
function ENT:OpenAndClose( ply )
|
||||
self:Open( ply )
|
||||
|
||||
self._PreventClosing = true
|
||||
|
||||
timer.Simple(0.5, function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
self:Close( ply )
|
||||
|
||||
self._PreventClosing = false
|
||||
end )
|
||||
end
|
||||
|
||||
function ENT:DisableOnBodyGroup( group, subgroup )
|
||||
self._BodyGroupDisable = group
|
||||
self._BodySubGroupDisable = subgroup
|
||||
end
|
||||
|
||||
function ENT:IsBodyGroupDisabled()
|
||||
if not self._BodyGroupDisable or not self._BodySubGroupDisable then return false end
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) then return false end
|
||||
|
||||
return base:GetBodygroup( self._BodyGroupDisable ) == self._BodySubGroupDisable
|
||||
end
|
||||
|
||||
function ENT:Open( ply )
|
||||
if self:IsOpen() then return end
|
||||
|
||||
self:SetActive( true )
|
||||
self:SetMins( self:GetMinsOpen() )
|
||||
self:SetMaxs( self:GetMaxsOpen() )
|
||||
|
||||
if self:IsBodyGroupDisabled() then return end
|
||||
|
||||
self:OnOpen( ply )
|
||||
|
||||
local snd = self:GetSoundOpen()
|
||||
|
||||
if not snd then return end
|
||||
|
||||
self:EmitSound( snd )
|
||||
end
|
||||
|
||||
function ENT:Close( ply )
|
||||
if not self:IsOpen() then
|
||||
if self:IsBodyGroupDisabled() then
|
||||
self:Open( ply )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if self:IsBodyGroupDisabled() then return end
|
||||
|
||||
self:SetActive( false )
|
||||
self:SetMins( self:GetMinsClosed() )
|
||||
self:SetMaxs( self:GetMaxsClosed() )
|
||||
|
||||
self:OnClosed( ply )
|
||||
|
||||
local snd = self:GetSoundClose()
|
||||
|
||||
if not snd then return end
|
||||
|
||||
self:EmitSound( snd )
|
||||
end
|
||||
|
||||
function ENT:OnDriverChanged( oldDriver, newDriver, pod )
|
||||
if self._PreventClosing then return end
|
||||
|
||||
if IsValid( newDriver ) then
|
||||
if self:IsOpen() then
|
||||
self:Close( newDriver )
|
||||
end
|
||||
else
|
||||
timer.Simple( FrameTime() * 2, function()
|
||||
if not IsValid( self ) or not IsValid( oldDriver ) or IsValid( self._Driver ) then return end
|
||||
|
||||
if oldDriver:lvsGetVehicle() == self:GetBase() then return end
|
||||
|
||||
if not self:IsOpen() then
|
||||
self:OpenAndClose()
|
||||
end
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetPoseParameterSV()
|
||||
local Base = self:GetBase()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local Target = self:GetActive() and self:GetPoseMax() or self:GetPoseMin()
|
||||
local poseName = self:GetPoseName()
|
||||
|
||||
if poseName == "" then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.sm_pp = EntTable.sm_pp and EntTable.sm_pp + (Target - EntTable.sm_pp) * FrameTime() * self:GetRate() or 0
|
||||
|
||||
local value = EntTable.sm_pp ^ self:GetRateExponent()
|
||||
|
||||
Base:SetPoseParameter( string.Replace(poseName, "^", ""), value )
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
local LinkedSeat = self:GetLinkedSeat()
|
||||
|
||||
if IsValid( LinkedSeat ) then
|
||||
local Driver = LinkedSeat:GetDriver()
|
||||
|
||||
if self._Driver ~= Driver then
|
||||
|
||||
self:OnDriverChanged( self._Driver, Driver, LinkedSeat )
|
||||
|
||||
self._Driver = Driver
|
||||
end
|
||||
end
|
||||
|
||||
if self:IsServerSide() then
|
||||
self:SetPoseParameterSV()
|
||||
|
||||
self:NextThink( CurTime() )
|
||||
else
|
||||
self:NextThink( CurTime() + 0.25 )
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if self:IsServerSide() then return end
|
||||
|
||||
local Base = self:GetBase()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local Target = self:GetActive() and self:GetPoseMax() or self:GetPoseMin()
|
||||
local poseName = self:GetPoseName()
|
||||
|
||||
if poseName == "" then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.sm_pp = EntTable.sm_pp and EntTable.sm_pp + (Target - EntTable.sm_pp) * RealFrameTime() * self:GetRate() or 0
|
||||
|
||||
local value = EntTable.sm_pp ^ self:GetRateExponent()
|
||||
|
||||
if string.StartsWith( poseName, "!" ) then
|
||||
Base:SetBonePoseParameter( poseName, value )
|
||||
else
|
||||
Base:SetPoseParameter( poseName, value )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
end
|
||||
|
||||
local LVS = LVS
|
||||
ENT.ColorSelect = Color(127,255,127,150)
|
||||
ENT.ColorNormal = Color(255,0,0,150)
|
||||
ENT.ColorTransBlack = Color(0,0,0,150)
|
||||
ENT.ColorBlack = Color(75,75,75,255)
|
||||
ENT.OutlineThickness = Vector(0.5,0.5,0.5)
|
||||
|
||||
local function DrawText( pos, text, align, col )
|
||||
if not align then align = TEXT_ALIGN_CENTER end
|
||||
|
||||
cam.Start2D()
|
||||
local data2D = pos:ToScreen()
|
||||
|
||||
if not data2D.visible then cam.End2D() return end
|
||||
|
||||
local font = "TargetIDSmall"
|
||||
|
||||
local x = data2D.x
|
||||
local y = data2D.y
|
||||
|
||||
draw.DrawText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), align )
|
||||
draw.DrawText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), align )
|
||||
draw.DrawText( text, font, x, y, col or color_white, align )
|
||||
cam.End2D()
|
||||
end
|
||||
|
||||
local function bezier(p0, p1, p2, p3, t)
|
||||
local e = p0 + t * (p1 - p0)
|
||||
local f = p1 + t * (p2 - p1)
|
||||
local g = p2 + t * (p3 - p2)
|
||||
|
||||
local h = e + t * (f - e)
|
||||
local i = f + t * (g - f)
|
||||
|
||||
local p = h + t * (i - h)
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
local function DrawBezier( startpos, midpos, endpos )
|
||||
cam.Start2D()
|
||||
local p0 = LocalPlayer():GetPos()
|
||||
local p1 = startpos
|
||||
local p2 = midpos
|
||||
local p3 = endpos
|
||||
|
||||
local oldpos = p0:ToScreen()
|
||||
|
||||
if not oldpos.visible then cam.End2D() return end
|
||||
|
||||
local num = 100
|
||||
for i = 0, num do
|
||||
local newpos = bezier(p0,p1,p2,p3, i / num):ToScreen()
|
||||
|
||||
if not newpos.visible then
|
||||
oldpos = newpos
|
||||
continue
|
||||
end
|
||||
|
||||
surface.SetDrawColor( 255, 255, 255, math.abs( math.cos( -CurTime() * 5 + i * 0.05 ) ) * 255 )
|
||||
|
||||
surface.DrawLine( oldpos.x, oldpos.y, newpos.x, newpos.y )
|
||||
|
||||
oldpos = newpos
|
||||
end
|
||||
|
||||
cam.End2D()
|
||||
end
|
||||
|
||||
function ENT:DrawTranslucent()
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) or ply:InVehicle() then return end
|
||||
|
||||
local KeySprint = ply:KeyDown( IN_SPEED )
|
||||
local InRange = self:InRange( ply, self.UseRange )
|
||||
|
||||
if LVS.ShowDoorInfo and InRange then
|
||||
local pos = (self:LocalToWorld( self:GetMins() ) + self:LocalToWorld( self:GetMaxs() )) * 0.5
|
||||
|
||||
local NameKeyUse = "["..(input.LookupBinding( "+use" ) or "+use is not bound to a key").."]"
|
||||
local NameKeySprint = "["..(input.LookupBinding( "+speed" ) or "+speed is not bound to a key").."]"
|
||||
|
||||
local boxOrigin = self:GetPos()
|
||||
local boxAngles = self:GetAngles()
|
||||
local boxMins = self:GetMins()
|
||||
local boxMaxs = self:GetMaxs()
|
||||
|
||||
if self:IsOpen() then
|
||||
local LinkedSeat = self:GetLinkedSeat()
|
||||
|
||||
if IsValid( LinkedSeat ) then
|
||||
if not KeySprint then
|
||||
DrawText( LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() ), NameKeyUse.." \n".."Enter ", TEXT_ALIGN_CENTER )
|
||||
DrawText( pos, " "..NameKeySprint.."\n".." Close", TEXT_ALIGN_CENTER, self.ColorBlack )
|
||||
DrawBezier( Vector(pos.x,pos.y,self:LocalToWorld( Vector(0,0,self:GetMaxs().z) ).z), Vector(pos.x,pos.y,self:LocalToWorld( Vector(0,0,self:GetMins().z) ).z), LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() - Vector(0,0,5) ) )
|
||||
else
|
||||
DrawText( LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() ), NameKeySprint.." \n".."Enter ", TEXT_ALIGN_CENTER, self.ColorBlack )
|
||||
DrawText( pos, " "..NameKeyUse.."\n".." Close", TEXT_ALIGN_CENTER )
|
||||
end
|
||||
else
|
||||
DrawText( pos, NameKeyUse.."\n".."Close", TEXT_ALIGN_CENTER )
|
||||
end
|
||||
else
|
||||
DrawText( pos, NameKeyUse.."\n".."Open", TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
if not KeySprint then return end
|
||||
|
||||
if InRange then
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Use = ply:KeyDown( IN_USE )
|
||||
|
||||
if EntTable.old_Use ~= Use then
|
||||
EntTable.old_Use = Use
|
||||
|
||||
if Use then
|
||||
net.Start( "lvs_doorhandler_interact" )
|
||||
net.WriteEntity( self )
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not LVS.DeveloperEnabled then return end
|
||||
|
||||
local boxOrigin = self:GetPos()
|
||||
local boxAngles = self:GetAngles()
|
||||
local boxMins = self:GetMins()
|
||||
local boxMaxs = self:GetMaxs()
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Col = InRange and EntTable.ColorSelect or EntTable.ColorNormal
|
||||
|
||||
render.SetColorMaterial()
|
||||
render.DrawBox( boxOrigin, boxAngles, boxMins, boxMaxs, Col )
|
||||
render.DrawBox( boxOrigin, boxAngles, boxMaxs + EntTable.OutlineThickness, boxMins - EntTable.OutlineThickness, EntTable.ColorTransBlack )
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
|
||||
return pos, angles, fov
|
||||
end
|
||||
|
||||
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
local view = {}
|
||||
view.origin = pos
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = ply:EyeAngles()
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight())
|
||||
local WallOffset = 4
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = view.origin,
|
||||
endpos = TargetOrigin,
|
||||
filter = function( e )
|
||||
local c = e:GetClass()
|
||||
local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS
|
||||
|
||||
return collide
|
||||
end,
|
||||
mins = Vector( -WallOffset, -WallOffset, -WallOffset ),
|
||||
maxs = Vector( WallOffset, WallOffset, WallOffset ),
|
||||
} )
|
||||
|
||||
view.origin = tr.HitPos
|
||||
|
||||
if tr.Hit and not tr.StartSolid then
|
||||
view.origin = view.origin + tr.HitNormal * WallOffset
|
||||
end
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
|
||||
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
|
||||
|
||||
if self:GetDriverSeat() == pod then
|
||||
return self:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
ENT.IconEngine = Material( "lvs/engine.png" )
|
||||
|
||||
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0)
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local hX = X + W - H * 0.5
|
||||
local hY = Y + H * 0.25 + H * 0.25
|
||||
|
||||
surface.SetMaterial( self.IconEngine )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
self:LVSDrawCircle( hX, hY, H * 0.35, self:GetThrottle() )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
include("shared.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("cl_camera.lua")
|
||||
include("cl_hud.lua")
|
||||
@@ -0,0 +1,90 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_camera.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "sh_camera_eyetrace.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_controls.lua")
|
||||
include("sv_components.lua")
|
||||
include("sv_vehiclespecific.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("sv_ai.lua")
|
||||
|
||||
DEFINE_BASECLASS( "lvs_base" )
|
||||
|
||||
function ENT:OnDriverChanged( Old, New, VehicleIsActive )
|
||||
if VehicleIsActive then
|
||||
if not self:GetEngineActive() and self:IsEngineStartAllowed() then
|
||||
self:SetEngineActive( true )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetEngineActive( false )
|
||||
self:SetMove( 0, 0 )
|
||||
end
|
||||
|
||||
function ENT:StartEngine()
|
||||
for _, wheel in pairs( self:GetWheels() ) do
|
||||
if not IsValid( wheel ) then continue end
|
||||
|
||||
wheel:PhysWake()
|
||||
end
|
||||
|
||||
BaseClass.StartEngine( self )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
if self:GetEngineActive() then phys:Wake() end
|
||||
|
||||
local OnGroundMul = self:HitGround() and 1 or 0
|
||||
|
||||
local VelL = phys:WorldToLocal( phys:GetPos() + phys:GetVelocity() )
|
||||
|
||||
local InputMove = self:GetMove()
|
||||
|
||||
self._smMove = self._smMove and (self._smMove + (Vector(InputMove.x,InputMove.y,0):GetNormalized() - self._smMove) * deltatime * self.ForceLinearRate * 10) or InputMove
|
||||
|
||||
local MoveX = (self.MaxVelocityX + self.BoostAddVelocityX * InputMove.z) * self._smMove.x
|
||||
local MoveY = (self.MaxVelocityY + self.BoostAddVelocityY * InputMove.z) * self._smMove.y
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
self:SetSteerTo( Ang.y )
|
||||
self.smY = Ang.y
|
||||
end
|
||||
|
||||
self.smY = self.smY and math.ApproachAngle( self.smY, self:GetSteerTo(), self.MaxTurnRate * deltatime * 100 ) or Ang.y
|
||||
|
||||
local Steer = self:WorldToLocalAngles( Angle(Ang.p,self.smY,Ang.y) ).y
|
||||
|
||||
local ForceLinear = ((Vector( MoveX, MoveY, 0 ) - Vector(VelL.x,VelL.y,0)) * self.ForceLinearMultiplier) * OnGroundMul * deltatime * 500
|
||||
local ForceAngle = (Vector(0,0,Steer) * self.ForceAngleMultiplier * 2 - phys:GetAngleVelocity() * self.ForceAngleDampingMultiplier) * OnGroundMul * deltatime * 600
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, self:GetDisabled() and SIM_NOTHING or SIM_LOCAL_ACCELERATION )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate )
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
|
||||
function ENT:IsEngineStartAllowed()
|
||||
if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end
|
||||
|
||||
if self:GetDisabled() then return false end
|
||||
|
||||
if self:WaterLevel() > self.WaterLevelPreventStart then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:OnDisabled( name, old, new)
|
||||
if new == old then return end
|
||||
|
||||
if new then
|
||||
if not self:GetEngineActive() then return end
|
||||
self:SetEngineActive( false )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
function ENT:GetEyeTrace( trace_forward )
|
||||
local startpos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
startpos = pod:LocalToWorld( pod:OBBCenter() )
|
||||
end
|
||||
|
||||
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
|
||||
|
||||
local data = {
|
||||
start = startpos,
|
||||
endpos = (startpos + AimVector * 50000),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( data )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetAIAimVector()
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if not IsValid( Driver ) then return self:GetForward() end
|
||||
|
||||
if SERVER then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
else
|
||||
return Driver:EyeAngles():Forward()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
ENT.Base = "lvs_base"
|
||||
|
||||
ENT.PrintName = "[LVS] Generic Fake Hover"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.ForceAngleMultiplier = 1
|
||||
ENT.ForceAngleDampingMultiplier = 1
|
||||
|
||||
ENT.ForceLinearMultiplier = 1
|
||||
ENT.ForceLinearRate = 1
|
||||
|
||||
ENT.MaxVelocityX = 300
|
||||
ENT.MaxVelocityY = 300
|
||||
|
||||
ENT.MaxTurnRate = 1
|
||||
|
||||
ENT.BoostAddVelocityX = 200
|
||||
ENT.BoostAddVelocityY = 200
|
||||
|
||||
ENT.GroundTraceHitWater = true
|
||||
ENT.GroundTraceLength = 50
|
||||
ENT.GroundTraceHull = 100
|
||||
|
||||
ENT.DisableBallistics = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:AddDT( "Vector", "AIAimVector" )
|
||||
self:AddDT( "Bool", "Disabled" )
|
||||
|
||||
if SERVER then
|
||||
self:NetworkVarNotify( "Disabled", self.OnDisabled )
|
||||
end
|
||||
|
||||
self:CreateBaseDT()
|
||||
end
|
||||
|
||||
function ENT:HitGround()
|
||||
local data = {
|
||||
start = self:LocalToWorld( self:OBBCenter() ),
|
||||
endpos = self:LocalToWorld( Vector(0,0,self:OBBMins().z - self.GroundTraceLength) ),
|
||||
mins = Vector( -self.GroundTraceHull, -self.GroundTraceHull, 0 ),
|
||||
maxs = Vector( self.GroundTraceHull, self.GroundTraceHull, 0 ),
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
}
|
||||
|
||||
local trace = util.TraceHull( data )
|
||||
|
||||
data.mask = MASK_WATER
|
||||
|
||||
local traceWater = util.TraceHull( data )
|
||||
|
||||
return ((trace.Hit or (traceWater.Hit and self.GroundTraceHitWater)) and not trace.HitSky)
|
||||
end
|
||||
|
||||
function ENT:GetThrottle()
|
||||
return math.min( self:GetVelocity():Length() / math.abs( self.MaxVelocityX + self.BoostAddVelocityX, self.MaxVelocityY + self.BoostAddVelocityY ), 1 )
|
||||
end
|
||||
|
||||
function ENT:GetMaxThrottle()
|
||||
return 1
|
||||
end
|
||||
|
||||
function ENT:GetThrustStrenght()
|
||||
return 0
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "fakehover"
|
||||
end
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
local RangerLength = 25000
|
||||
|
||||
local Target = self:AIGetTarget( 180 )
|
||||
|
||||
local StartPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local TraceFilter = self:GetCrosshairFilterEnts()
|
||||
|
||||
local Front = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } )
|
||||
local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,15,0) ):Forward() * RangerLength } )
|
||||
local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-15,0) ):Forward() * RangerLength } )
|
||||
local FrontLeft1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,60,0) ):Forward() * RangerLength } )
|
||||
local FrontRight1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-60,0) ):Forward() * RangerLength } )
|
||||
local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,85,0) ):Forward() * RangerLength } )
|
||||
local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-85,0) ):Forward() * RangerLength } )
|
||||
|
||||
local TargetPos = (Front.HitPos + FrontLeft.HitPos + FrontRight.HitPos + FrontLeft1.HitPos + FrontRight1.HitPos + FrontLeft2.HitPos + FrontRight2.HitPos) / 7
|
||||
|
||||
self._AIFireInput = false
|
||||
|
||||
if IsValid( self:GetHardLockTarget() ) then
|
||||
TargetPos = self:GetHardLockTarget():GetPos()
|
||||
if self:AITargetInFront( self:GetHardLockTarget(), 65 ) then
|
||||
self._AIFireInput = true
|
||||
end
|
||||
else
|
||||
if IsValid( Target ) then
|
||||
TargetPos = Target:LocalToWorld( Target:OBBCenter() )
|
||||
|
||||
if self:AITargetInFront( Target, 65 ) then
|
||||
self._AIFireInput = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self._AIFireInput then
|
||||
local CurHeat = self:GetNWHeat()
|
||||
local CurWeapon = self:GetSelectedWeapon()
|
||||
|
||||
if CurWeapon > 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
else
|
||||
if CurHeat > 0.9 then
|
||||
if CurWeapon == 1 and self:AIHasWeapon( 2 ) then
|
||||
self:AISelectWeapon( 2 )
|
||||
|
||||
elseif CurWeapon == 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
else
|
||||
if CurHeat == 0 and math.cos( CurTime() ) > 0 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local DistToTarget = (TargetPos - self:GetPos()):Length()
|
||||
local LocalMove = self:WorldToLocal( TargetPos )
|
||||
|
||||
if DistToTarget < 1000 then
|
||||
LocalMove.x = -1
|
||||
end
|
||||
|
||||
if DistToTarget > 800 and DistToTarget < 1200 then
|
||||
LocalMove.y = math.sin( CurTime() * 1.5 + self:EntIndex() * 1337 ) * 10
|
||||
end
|
||||
|
||||
self:SetMove( LocalMove.x, LocalMove.y )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return end
|
||||
|
||||
local AimVector = (TargetPos - pod:LocalToWorld( Vector(0,0,33) )):GetNormalized()
|
||||
|
||||
self:SetAIAimVector( AimVector )
|
||||
|
||||
self:SetSteerTo( AimVector:Angle().y )
|
||||
end
|
||||
|
||||
function ENT:OnAITakeDamage( dmginfo )
|
||||
local attacker = dmginfo:GetAttacker()
|
||||
|
||||
if not IsValid( attacker ) then return end
|
||||
|
||||
if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then
|
||||
self:SetHardLockTarget( attacker )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:AISelectWeapon( ID )
|
||||
if ID == self:GetSelectedWeapon() then return end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (self._nextAISwitchWeapon or 0) > T then return end
|
||||
|
||||
self._nextAISwitchWeapon = T + 1
|
||||
|
||||
self:SelectWeapon( ID )
|
||||
end
|
||||
|
||||
function ENT:SetHardLockTarget( target )
|
||||
if not self:IsEnemy( target ) then return end
|
||||
|
||||
self._HardLockTarget = target
|
||||
self._HardLockTime = CurTime() + 4
|
||||
end
|
||||
|
||||
function ENT:GetHardLockTarget()
|
||||
if (self._HardLockTime or 0) < CurTime() then return NULL end
|
||||
|
||||
return self._HardLockTarget
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
ENT._WheelEnts = {}
|
||||
|
||||
function ENT:GetWheels()
|
||||
for id, ent in pairs( self._WheelEnts ) do
|
||||
if IsValid( ent ) then continue end
|
||||
|
||||
self._WheelEnts[ id ] = nil
|
||||
end
|
||||
|
||||
return self._WheelEnts
|
||||
end
|
||||
|
||||
function ENT:AddWheel( pos, radius, mass, buoyancyratio, brakeforce )
|
||||
if not isvector( pos ) or not isnumber( radius ) or not isnumber( mass ) then return end
|
||||
|
||||
local wheel = ents.Create( "lvs_fakehover_wheel" )
|
||||
|
||||
if not IsValid( wheel ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize wheel. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local WheelPos = self:LocalToWorld( pos )
|
||||
local CenterPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
debugoverlay.Sphere( WheelPos, radius, 5, Color(150,150,150), true )
|
||||
debugoverlay.Line( CenterPos, WheelPos, 5, Color(150,150,150), true )
|
||||
|
||||
wheel:SetPos( WheelPos )
|
||||
wheel:SetAngles( self:LocalToWorldAngles( Angle(0,90,0) ) )
|
||||
wheel:Spawn()
|
||||
wheel:Activate()
|
||||
wheel:SetBase( self )
|
||||
wheel:SetNoDraw( true )
|
||||
wheel:DrawShadow( false )
|
||||
wheel.DoNotDuplicate = true
|
||||
wheel:Define(
|
||||
{
|
||||
radius = radius,
|
||||
mass = mass,
|
||||
buoyancyratio = buoyancyratio or 0,
|
||||
}
|
||||
)
|
||||
|
||||
table.insert( self._WheelEnts, wheel )
|
||||
|
||||
local PhysObj = wheel:GetPhysicsObject()
|
||||
if not IsValid( PhysObj ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize wheel phys model. Vehicle terminated.")
|
||||
return
|
||||
end
|
||||
|
||||
if PhysObj:GetMaterial() ~= "gmod_silent" then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize physprop material on wheel. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:DeleteOnRemove( wheel )
|
||||
self:TransferCPPI( wheel )
|
||||
|
||||
local ballsocket = constraint.AdvBallsocket(wheel, self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -1, -1, -1, 1, 1, 1, 0, 0, 0, 0, 1)
|
||||
ballsocket.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket )
|
||||
|
||||
local nocollide = constraint.NoCollide( wheel, self, 0, 0 )
|
||||
nocollide.DoNotDuplicate = true
|
||||
self:TransferCPPI( nocollide )
|
||||
|
||||
PhysObj:EnableMotion( true )
|
||||
|
||||
return wheel
|
||||
end
|
||||
|
||||
function ENT:AddEngineSound( pos )
|
||||
local EngineSND = ents.Create( "lvs_fakehover_soundemitter" )
|
||||
|
||||
if not IsValid( EngineSND ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine sound entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
EngineSND:SetPos( self:LocalToWorld( pos ) )
|
||||
EngineSND:SetAngles( self:GetAngles() )
|
||||
EngineSND:Spawn()
|
||||
EngineSND:Activate()
|
||||
EngineSND:SetParent( self )
|
||||
EngineSND:SetBase( self )
|
||||
|
||||
self:DeleteOnRemove( EngineSND )
|
||||
|
||||
self:TransferCPPI( EngineSND )
|
||||
|
||||
return EngineSND
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
local KeyJump = ply:lvsKeyDown( "VSPEC" )
|
||||
|
||||
if self._lvsOldKeyJump ~= KeyJump then
|
||||
self._lvsOldKeyJump = KeyJump
|
||||
|
||||
if KeyJump then
|
||||
self:ToggleVehicleSpecific()
|
||||
end
|
||||
end
|
||||
|
||||
local Forward = cmd:KeyDown( IN_FORWARD )
|
||||
local Back = cmd:KeyDown( IN_BACK )
|
||||
local Left = cmd:KeyDown( IN_MOVELEFT )
|
||||
local Right = cmd:KeyDown( IN_MOVERIGHT )
|
||||
local Boost = cmd:KeyDown( IN_SPEED )
|
||||
|
||||
local X = (Forward and 1 or 0) - (Back and 1 or 0)
|
||||
local Y = (Left and 1 or 0) - (Right and 1 or 0)
|
||||
|
||||
self:SetMove( X, Y, Boost )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return end
|
||||
|
||||
if ply:lvsKeyDown( "FREELOOK" ) then
|
||||
self:SetSteerTo( self:GetAngles().y)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetSteerTo( pod:WorldToLocalAngles( ply:EyeAngles() ).y )
|
||||
end
|
||||
|
||||
function ENT:SetSteerTo( Steer )
|
||||
if not isnumber( Steer ) then return end
|
||||
|
||||
self._steer = Steer
|
||||
end
|
||||
|
||||
function ENT:GetSteerTo()
|
||||
if not self:GetEngineActive() then return self:GetAngles().y end
|
||||
|
||||
return (self._steer or self:GetAngles().y)
|
||||
end
|
||||
|
||||
function ENT:SetMove( X, Y, Boost )
|
||||
if not isnumber( X ) or not isnumber( Y ) then return end
|
||||
|
||||
X = math.Clamp( X, -1, 1 )
|
||||
Y = math.Clamp( Y, -1, 1 )
|
||||
Z = Boost and 1 or 0
|
||||
|
||||
self._move = Vector( X, Y, Z )
|
||||
end
|
||||
|
||||
function ENT:GetMove()
|
||||
if not self:GetEngineActive() then return Vector(0,0,0) end
|
||||
|
||||
return (self._move or Vector(0,0,0))
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
function ENT:ToggleVehicleSpecific()
|
||||
self._VSPEC = not self._VSPEC
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:EnableVehicleSpecific()
|
||||
if self._VSPEC then return end
|
||||
|
||||
self._VSPEC = true
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:DisableVehicleSpecific()
|
||||
if not self._VSPEC then return end
|
||||
|
||||
self._VSPEC = false
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:OnVehicleSpecificToggled( IsActive )
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
|
||||
ENT._lvsSmoothFreeLook = 0
|
||||
|
||||
function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
local ViewPosL = pod:WorldToLocal( pos )
|
||||
|
||||
local view = {}
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = self:GetAngles()
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
local Zoom = ply:lvsKeyDown( "ZOOM" )
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
if FreeLook then
|
||||
view.angles = pod:LocalToWorldAngles( ply:EyeAngles() )
|
||||
|
||||
self._lvsSmoothFreeLook = 1
|
||||
self._lvsSmoothFreeLookAngles = self:WorldToLocalAngles( view.angles )
|
||||
else
|
||||
if self._lvsSmoothFreeLook and self._lvsSmoothFreeLookAngles then
|
||||
|
||||
view.angles = self:LocalToWorldAngles( self._lvsSmoothFreeLookAngles * self._lvsSmoothFreeLook )
|
||||
|
||||
if self._lvsSmoothFreeLook <= 0 then
|
||||
self._lvsSmoothFreeLookAngles = nil
|
||||
self._lvsSmoothFreeLook = nil
|
||||
else
|
||||
self._lvsSmoothFreeLook = self._lvsSmoothFreeLook - self._lvsSmoothFreeLook * RealFrameTime() * 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local Dividor = math.abs( velL.x )
|
||||
local SideForce = math.Clamp( velL.y / Dividor, -1, 1)
|
||||
local UpForce = math.Clamp( velL.z / Dividor, -1, 1)
|
||||
|
||||
local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1))
|
||||
if Zoom then
|
||||
ViewPunch = Vector(0,0,0)
|
||||
end
|
||||
|
||||
pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0)
|
||||
pod._lerpPos = pos
|
||||
|
||||
view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5
|
||||
view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1
|
||||
view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
pod._lerpPos = pod._lerpPos or self:GetPos()
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
if FreeLook then
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local SideForce = math.Clamp(velL.y / 10,-250,250)
|
||||
local UpForce = math.Clamp(velL.z / 10,-250,250)
|
||||
|
||||
pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0)
|
||||
pod._lerpPos = self:LocalToWorld( pod._lerpPosL )
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:LocalToWorldAngles( Angle(0,180,0) )
|
||||
else
|
||||
local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) )
|
||||
|
||||
local Sub = TargetPos - pod._lerpPos
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100
|
||||
|
||||
pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12)
|
||||
pod._lerpPosL = self:WorldToLocal( pod._lerpPos )
|
||||
|
||||
local vel = self:GetVelocity()
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:GetAngles()
|
||||
end
|
||||
|
||||
view.origin = view.origin + ViewPosL
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 )
|
||||
|
||||
self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10
|
||||
|
||||
local Zoom = ply:lvsKeyDown( "ZOOM" )
|
||||
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local Dividor = math.abs( velL.x )
|
||||
local SideForce = math.Clamp( velL.y / Dividor, -1, 1)
|
||||
local UpForce = math.Clamp( velL.z / Dividor, -1, 1)
|
||||
|
||||
local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1))
|
||||
if Zoom then
|
||||
ViewPunch = Vector(0,0,0)
|
||||
end
|
||||
|
||||
pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0)
|
||||
pod._lerpPos = pos
|
||||
|
||||
local view = {}
|
||||
view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle()
|
||||
|
||||
if cvarFocus >= 1 then
|
||||
view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() )
|
||||
else
|
||||
view.angles.r = 0
|
||||
end
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight())
|
||||
local WallOffset = 4
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = view.origin,
|
||||
endpos = TargetOrigin,
|
||||
filter = function( e )
|
||||
local c = e:GetClass()
|
||||
local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS
|
||||
|
||||
return collide
|
||||
end,
|
||||
mins = Vector( -WallOffset, -WallOffset, -WallOffset ),
|
||||
maxs = Vector( WallOffset, WallOffset, WallOffset ),
|
||||
} )
|
||||
|
||||
view.origin = tr.HitPos
|
||||
|
||||
if tr.Hit and not tr.StartSolid then
|
||||
view.origin = view.origin + tr.HitNormal * WallOffset
|
||||
end
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
|
||||
return pos, angles, fov
|
||||
end
|
||||
|
||||
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
if ply:lvsMouseAim() then
|
||||
return self:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
|
||||
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
|
||||
|
||||
if self:GetDriverSeat() == pod then
|
||||
return self:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
function ENT:OnDestroyed()
|
||||
if not self.DeathSound then return end
|
||||
|
||||
if self:GetVelocity():Length() <= self.MaxVelocity * 0.5 then return end
|
||||
|
||||
self._sndDeath = CreateSound( self, self.DeathSound )
|
||||
self._sndDeath:SetSoundLevel( 125 )
|
||||
self._sndDeath:PlayEx( 1, 50 + 50 * self:CalcDoppler( LocalPlayer() ) )
|
||||
end
|
||||
|
||||
function ENT:StopDeathSound()
|
||||
if not self._sndDeath then return end
|
||||
|
||||
self._sndDeath:Stop()
|
||||
end
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
ENT.FlyByAdvance = 0
|
||||
|
||||
function ENT:FlyByThink()
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if ply:lvsGetVehicle() == self then EntTable.OldApproaching = false return end
|
||||
|
||||
local ViewEnt = ply:GetViewEntity()
|
||||
|
||||
if not IsValid( ViewEnt ) then return end
|
||||
|
||||
local Time = CurTime()
|
||||
|
||||
if (EntTable._nextflyby or 0) > Time then return end
|
||||
|
||||
EntTable._nextflyby = Time + 0.1
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
|
||||
if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end
|
||||
|
||||
local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance
|
||||
local ToPlayer = Sub:GetNormalized()
|
||||
local VelDir = Vel:GetNormalized()
|
||||
|
||||
local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) )
|
||||
|
||||
local Approaching = ApproachAngle < 80
|
||||
|
||||
if Approaching ~= EntTable.OldApproaching then
|
||||
EntTable.OldApproaching = Approaching
|
||||
|
||||
if Approaching then
|
||||
self:StopFlyBy()
|
||||
else
|
||||
self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnFlyBy( Pitch )
|
||||
if not self.FlyBySound then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound )
|
||||
EntTable.flybysnd:SetSoundLevel( 95 )
|
||||
EntTable.flybysnd:PlayEx( 1, Pitch )
|
||||
end
|
||||
|
||||
function ENT:StopFlyBy()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.flybysnd then return end
|
||||
|
||||
EntTable.flybysnd:Stop()
|
||||
EntTable.flybysnd = nil
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
ENT.IconEngine = Material( "lvs/engine.png" )
|
||||
|
||||
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0)
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
local Col = Throttle <= 1 and color_white or Color(0,0,0,255)
|
||||
local hX = X + W - H * 0.5
|
||||
local hY = Y + H * 0.25 + H * 0.25
|
||||
|
||||
surface.SetMaterial( self.IconEngine )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
|
||||
self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) )
|
||||
|
||||
if Throttle > 1 then
|
||||
draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSPreHudPaint( X, Y, ply )
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaint( X, Y, ply )
|
||||
if not self:LVSPreHudPaint( X, Y, ply ) then return end
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen()
|
||||
local HitPilot = self:GetEyeTrace().HitPos:ToScreen()
|
||||
|
||||
local MouseAim = ply:lvsMouseAim()
|
||||
|
||||
if self:IsDrawingReflectorSight() then
|
||||
self:DrawReflectorSight( HitPlane )
|
||||
|
||||
if MouseAim then
|
||||
local LineVisible = false
|
||||
|
||||
if not ply:lvsKeyDown( "FREELOOK" ) then
|
||||
LineVisible = self:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
end
|
||||
|
||||
if LineVisible then
|
||||
self:PaintCrosshairOuter( HitPilot )
|
||||
end
|
||||
end
|
||||
|
||||
self:LVSPaintHitMarker( HitPilot )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:PaintCrosshairCenter( HitPlane )
|
||||
self:PaintCrosshairOuter( HitPilot )
|
||||
|
||||
if MouseAim and not ply:lvsKeyDown( "FREELOOK" ) then
|
||||
self:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
end
|
||||
|
||||
self:LVSPaintHitMarker( HitPilot )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintDirectInput( Pos2D )
|
||||
self:PaintCrosshairCenter( Pos2D )
|
||||
self:PaintCrosshairOuter( Pos2D )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0)
|
||||
local Len = Sub:Length()
|
||||
|
||||
if Len <= 20 then return false end
|
||||
|
||||
local Dir = Sub:GetNormalized()
|
||||
|
||||
surface.SetDrawColor( 255, 255, 255, 100 )
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 )
|
||||
|
||||
-- shadow
|
||||
surface.SetDrawColor( 0, 0, 0, 50 )
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
include("shared.lua")
|
||||
include("cl_camera.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("cl_hud.lua")
|
||||
include("cl_flyby.lua")
|
||||
include("cl_deathsound.lua")
|
||||
include("cl_reflectorsight.lua")
|
||||
|
||||
local ExhaustSprite = Material( "effects/muzzleflash2" )
|
||||
|
||||
function ENT:DoExhaustFX()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
|
||||
local OffsetMagnitude = (8 + 4 * Throttle)
|
||||
|
||||
local T = CurTime()
|
||||
local FT = RealFrameTime()
|
||||
|
||||
local HP = self:GetHP()
|
||||
local MaxHP = self:GetMaxHP()
|
||||
|
||||
if HP <= 0 then return end
|
||||
|
||||
render.SetMaterial( ExhaustSprite )
|
||||
|
||||
local ShouldDoEffect = false
|
||||
|
||||
if (EntTable.NextFX or 0) < T then
|
||||
EntTable.NextFX = T + 0.05 + (1 - Throttle) / 10
|
||||
|
||||
ShouldDoEffect = true
|
||||
end
|
||||
|
||||
for id, data in pairs( EntTable.ExhaustPositions ) do
|
||||
if not EntTable.ExhaustPositions[ id ].PosOffset then
|
||||
EntTable.ExhaustPositions[ id ].PosOffset = 0
|
||||
end
|
||||
|
||||
if not EntTable.ExhaustPositions[ id ].NextFX then
|
||||
EntTable.ExhaustPositions[ id ].NextFX = 0
|
||||
end
|
||||
|
||||
local Pos = self:LocalToWorld( data.pos )
|
||||
local Dir = self:LocalToWorldAngles( data.ang ):Up()
|
||||
|
||||
self.ExhaustPositions[ id ].PosOffset = EntTable.ExhaustPositions[ id ].PosOffset + FT * (8 + 4 * Throttle)
|
||||
|
||||
if ShouldDoEffect then
|
||||
if math.random(0,1) == 1 then
|
||||
EntTable.ExhaustPositions[ id ].PosOffset = 0
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( Pos )
|
||||
effectdata:SetNormal( Dir )
|
||||
effectdata:SetMagnitude( Throttle )
|
||||
effectdata:SetEntity( self )
|
||||
if HP > MaxHP * 0.25 then
|
||||
util.Effect( "lvs_exhaust", effectdata )
|
||||
else
|
||||
util.Effect( "lvs_exhaust_fire", effectdata )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if EntTable.ExhaustPositions[ id ].PosOffset > 1 or Throttle < 0.5 then continue end
|
||||
|
||||
local Size = math.min( 10 * (1 - EntTable.ExhaustPositions[ id ].PosOffset ) ^ 2, 5 + 5 * Throttle )
|
||||
|
||||
render.SetMaterial( ExhaustSprite )
|
||||
render.DrawSprite( Pos + Dir * EntTable.ExhaustPositions[ id ].PosOffset * (5 + 5 * Throttle), Size, Size, color_white )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
ENT.ReflectorSight = false
|
||||
ENT.ReflectorSightPos = vector_origin
|
||||
ENT.ReflectorSightColor = color_white
|
||||
ENT.ReflectorSightColorBG = color_black
|
||||
ENT.ReflectorSightMaterial = Material("lvs/sights/german.png")
|
||||
ENT.ReflectorSightMaterialRes = 128
|
||||
ENT.ReflectorSightHeight = 3
|
||||
ENT.ReflectorSightWidth = 1.5
|
||||
ENT.ReflectorSightGlow = false
|
||||
ENT.ReflectorSightGlowMaterial = Material( "sprites/light_glow02_add" )
|
||||
ENT.ReflectorSightGlowMaterialRes = 600
|
||||
ENT.ReflectorSightGlowColor = color_white
|
||||
|
||||
function ENT:PaintReflectorSight( Pos2D, Ang, Origin2D )
|
||||
if self.ReflectorSightGlow then
|
||||
surface.SetDrawColor( self.ReflectorSightGlowColor )
|
||||
surface.SetMaterial( self.ReflectorSightGlowMaterial )
|
||||
surface.DrawTexturedRectRotated( Pos2D.x, Pos2D.y, self.ReflectorSightGlowMaterialRes, self.ReflectorSightGlowMaterialRes, -Ang )
|
||||
end
|
||||
|
||||
surface.SetDrawColor( self.ReflectorSightColor )
|
||||
surface.SetMaterial( self.ReflectorSightMaterial )
|
||||
surface.DrawTexturedRectRotated( Pos2D.x, Pos2D.y, self.ReflectorSightMaterialRes, self.ReflectorSightMaterialRes, -Ang )
|
||||
end
|
||||
|
||||
function ENT:IsDrawingReflectorSight()
|
||||
if not self.ReflectorSight then return false end
|
||||
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( Pod ) then return false end
|
||||
|
||||
return not Pod:GetThirdPersonMode()
|
||||
end
|
||||
|
||||
function ENT:DrawReflectorSight( Pos2D )
|
||||
local Pos = self:LocalToWorld( self.ReflectorSightPos )
|
||||
local Up = self:GetUp()
|
||||
local Right = self:GetRight()
|
||||
|
||||
local Width = self.ReflectorSightWidth
|
||||
local Height = self.ReflectorSightHeight
|
||||
|
||||
local TopLeft = (Pos + Up * Height - Right * Width):ToScreen()
|
||||
local TopRight = (Pos + Up * Height + Right * Width):ToScreen()
|
||||
local BottomLeft = (Pos - Right * Width):ToScreen()
|
||||
local BottomRight = (Pos + Right * Width):ToScreen()
|
||||
|
||||
Pos = Pos:ToScreen()
|
||||
|
||||
if not Pos.visible then return end
|
||||
|
||||
local poly = {
|
||||
{ x = TopLeft.x, y = TopLeft.y },
|
||||
{ x = TopRight.x, y = TopRight.y },
|
||||
{ x = BottomRight.x, y = BottomRight.y },
|
||||
{ x = BottomLeft.x, y = BottomLeft.y },
|
||||
}
|
||||
|
||||
local Ang = 0
|
||||
|
||||
if TopLeft.x < TopRight.x then
|
||||
Ang = (Vector( TopLeft.x, 0, TopLeft.y ) - Vector( TopRight.x, 0, TopRight.y )):Angle().p
|
||||
else
|
||||
Ang = (Vector( TopRight.x, 0, TopRight.y ) - Vector( TopLeft.x, 0, TopLeft.y )):Angle().p - 180
|
||||
end
|
||||
|
||||
draw.NoTexture()
|
||||
surface.SetDrawColor( self.ReflectorSightColorBG )
|
||||
surface.DrawPoly( poly )
|
||||
|
||||
render.SetStencilWriteMask( 0xFF )
|
||||
render.SetStencilTestMask( 0xFF )
|
||||
render.SetStencilReferenceValue( 0 )
|
||||
render.SetStencilPassOperation( STENCIL_KEEP )
|
||||
render.SetStencilZFailOperation( STENCIL_KEEP )
|
||||
render.ClearStencil()
|
||||
|
||||
render.SetStencilEnable( true )
|
||||
render.SetStencilReferenceValue( 1 )
|
||||
render.SetStencilCompareFunction( STENCIL_NEVER )
|
||||
render.SetStencilFailOperation( STENCIL_REPLACE )
|
||||
|
||||
draw.NoTexture()
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawPoly( poly )
|
||||
|
||||
render.SetStencilCompareFunction( STENCIL_EQUAL )
|
||||
render.SetStencilFailOperation( STENCIL_KEEP )
|
||||
|
||||
self:PaintReflectorSight( Pos2D, Ang, Pos )
|
||||
|
||||
render.SetStencilEnable( false )
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_camera.lua" )
|
||||
AddCSLuaFile( "sh_camera_eyetrace.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "cl_flyby.lua" )
|
||||
AddCSLuaFile( "cl_deathsound.lua" )
|
||||
AddCSLuaFile( "cl_reflectorsight.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_wheels.lua")
|
||||
include("sv_landinggear.lua")
|
||||
include("sv_components.lua")
|
||||
include("sv_ai.lua")
|
||||
include("sv_mouseaim.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
self.COL_GROUP_OLD = self:GetCollisionGroup()
|
||||
self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS )
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE )
|
||||
end
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement )
|
||||
local LocalAngles = self:WorldToLocalAngles( TargetAngle )
|
||||
|
||||
if self:GetAI() then self:SetAIAimVector( LocalAngles:Forward() ) end
|
||||
|
||||
local LocalAngPitch = LocalAngles.p
|
||||
local LocalAngYaw = LocalAngles.y
|
||||
local LocalAngRoll = LocalAngles.r
|
||||
|
||||
local TargetForward = TargetAngle:Forward()
|
||||
local Forward = self:GetForward()
|
||||
|
||||
local AngDiff = math.deg( math.acos( math.Clamp( Forward:Dot( TargetForward ) ,-1,1) ) )
|
||||
|
||||
local WingFinFadeOut = math.max( (90 - AngDiff ) / 90, 0 )
|
||||
local RudderFadeOut = math.min( math.max( (120 - AngDiff ) / 120, 0 ) * 3, 1 )
|
||||
|
||||
local AngVel = self:GetPhysicsObject():GetAngleVelocity()
|
||||
|
||||
local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 )
|
||||
local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 )
|
||||
|
||||
local Pitch = math.Clamp( -LocalAngPitch / 10 + SmoothPitch, -1, 1 )
|
||||
local Yaw = math.Clamp( -LocalAngYaw / 2 + SmoothYaw,-1,1) * RudderFadeOut
|
||||
local Roll = math.Clamp( (-math.Clamp(LocalAngYaw * 16,-90,90) + LocalAngRoll * RudderFadeOut * 0.75) * WingFinFadeOut / 180 , -1 , 1 )
|
||||
|
||||
if FreeMovement then
|
||||
Roll = math.Clamp( -LocalAngYaw * WingFinFadeOut / 180 , -1 , 1 )
|
||||
end
|
||||
|
||||
if OverridePitch and OverridePitch ~= 0 then
|
||||
Pitch = OverridePitch
|
||||
end
|
||||
|
||||
if OverrideYaw and OverrideYaw ~= 0 then
|
||||
Yaw = OverrideYaw
|
||||
end
|
||||
|
||||
if OverrideRoll and OverrideRoll ~= 0 then
|
||||
Roll = OverrideRoll
|
||||
end
|
||||
|
||||
self:SetSteer( Vector( math.Clamp(Roll * 1.25,-1,1), math.Clamp(-Pitch * 1.25,-1,1), -Yaw) )
|
||||
end
|
||||
|
||||
function ENT:CalcAero( phys, deltatime, EntTable )
|
||||
if not EntTable then
|
||||
EntTable = self:GetTable()
|
||||
end
|
||||
|
||||
-- mouse aim needs to run at high speed.
|
||||
if self:GetAI() then
|
||||
if EntTable._lvsAITargetAng then
|
||||
self:ApproachTargetAngle( EntTable._lvsAITargetAng )
|
||||
end
|
||||
else
|
||||
local ply = self:GetDriver()
|
||||
if IsValid( ply ) and ply:lvsMouseAim() then
|
||||
self:PlayerMouseAim( ply )
|
||||
end
|
||||
end
|
||||
|
||||
local WorldGravity = self:GetWorldGravity()
|
||||
local WorldUp = self:GetWorldUp()
|
||||
local Steer = self:GetSteer()
|
||||
|
||||
local Stability, InvStability, ForwardVelocity = self:GetStability()
|
||||
|
||||
local Forward = self:GetForward()
|
||||
local Left = -self:GetRight()
|
||||
local Up = self:GetUp()
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
local VelForward = Vel:GetNormalized()
|
||||
|
||||
local PitchPull = math.max( (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90, 0 )
|
||||
local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90
|
||||
|
||||
local GravMul = (WorldGravity / 600) * 0.25
|
||||
|
||||
-- crash behavior
|
||||
if self:IsDestroyed() then
|
||||
Steer = phys:GetAngleVelocity() / 200
|
||||
|
||||
PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90
|
||||
|
||||
GravMul = WorldGravity / 600
|
||||
end
|
||||
|
||||
local GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul * EntTable.GravityTurnRatePitch
|
||||
local GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul * EntTable.GravityTurnRateYaw
|
||||
|
||||
local StallMul = math.min( (-math.min(Vel.z + EntTable.StallVelocity,0) / 100) * EntTable.StallForceMultiplier, EntTable.StallForceMax )
|
||||
|
||||
local StallPitch = 0
|
||||
local StallYaw = 0
|
||||
|
||||
if StallMul > 0 then
|
||||
if InvStability < 1 then
|
||||
StallPitch = PitchPull* GravMul * StallMul
|
||||
StallYaw = YawPull * GravMul * StallMul
|
||||
else
|
||||
local StallPitchDir = self:Sign( math.deg( math.acos( math.Clamp( -VelForward:Dot( self:LocalToWorldAngles( Angle(-10,0,0) ):Up() ) ,-1,1) ) ) - 90 )
|
||||
local StallYawDir = self:Sign( math.deg( math.acos( math.Clamp( -VelForward:Dot( Left ) ,-1,1) ) ) - 90 )
|
||||
|
||||
local StallPitchPull = ((90 - math.abs( math.deg( math.acos( math.Clamp( -WorldUp:Dot( Up ) ,-1,1) ) ) - 90 )) / 90) * StallPitchDir
|
||||
local StallYawPull = ((90 - math.abs( math.deg( math.acos( math.Clamp( -WorldUp:Dot( Left ) ,-1,1) ) ) - 90 )) / 90) * StallYawDir * 0.5
|
||||
|
||||
StallPitch = StallPitchPull * GravMul * StallMul
|
||||
StallYaw = StallYawPull * GravMul * StallMul
|
||||
end
|
||||
end
|
||||
|
||||
local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * EntTable.TurnRatePitch * 3 * Stability - StallPitch * InvStability
|
||||
local Yaw = math.Clamp(Steer.z * 4 + GravityYaw,-1,1) * EntTable.TurnRateYaw * Stability + StallYaw * InvStability
|
||||
local Roll = math.Clamp(Steer.x * 1.5,-1,1) * EntTable.TurnRateRoll * 12 * Stability
|
||||
|
||||
self:HandleLandingGear( deltatime )
|
||||
self:SetWheelSteer( Steer.z * EntTable.WheelSteerAngle )
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + Vel )
|
||||
|
||||
local SlipMul = 1 - math.Clamp( math.max( math.abs( VelL.x ) - EntTable.MaxPerfVelocity, 0 ) / math.max(EntTable.MaxVelocity - EntTable.MaxPerfVelocity, 0 ),0,1)
|
||||
|
||||
local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - EntTable.MaxSlipAnglePitch * SlipMul * math.abs( Steer.y ), 0 ) / 90) * 0.3
|
||||
local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - EntTable.MaxSlipAngleYaw * SlipMul * math.abs( Steer.z ), 0 ) / 90) * 0.15
|
||||
|
||||
local Lift = -math.min( (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 180,0) * (WorldGravity / (1 / deltatime))
|
||||
|
||||
return Vector(0, -VelL.y * MulY, Lift - VelL.z * MulZ ) * Stability, Vector( Roll, Pitch, Yaw )
|
||||
end
|
||||
|
||||
function ENT:OnSkyCollide( data, PhysObj )
|
||||
|
||||
local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * math.Clamp(self:GetThrustStrenght() * self.MaxThrust,250,800)
|
||||
|
||||
PhysObj:SetVelocityInstantaneous( NewVelocity )
|
||||
PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity )
|
||||
|
||||
self:FreezeStability()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Aero, Torque = self:CalcAero( phys, deltatime, EntTable )
|
||||
|
||||
if self:GetEngineActive() then phys:Wake() end
|
||||
|
||||
local Thrust = math.max( self:GetThrustStrenght(), 0 ) * EntTable.MaxThrust * 100
|
||||
|
||||
local ForceLinear = (Aero * 10000 * EntTable.ForceLinearMultiplier + Vector(Thrust,0,0)) * deltatime
|
||||
local ForceAngle = (Torque * 25 * EntTable.ForceAngleMultiplier - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_LOCAL_ACCELERATION )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate )
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
function ENT:GetEyeTrace( trace_forward )
|
||||
local startpos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
startpos = pod:LocalToWorld( pod:OBBCenter() )
|
||||
end
|
||||
|
||||
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
|
||||
|
||||
local data = {
|
||||
start = startpos,
|
||||
endpos = (startpos + AimVector * 50000),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( data )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetAIAimVector()
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if not IsValid( Driver ) then return self:GetForward() end
|
||||
|
||||
if not Driver:lvsMouseAim() then
|
||||
if Driver:lvsKeyDown( "FREELOOK" ) then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
if pod:GetThirdPersonMode() then
|
||||
return -self:GetForward()
|
||||
else
|
||||
return Driver:GetAimVector()
|
||||
end
|
||||
else
|
||||
return self:GetForward()
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
else
|
||||
return Driver:EyeAngles():Forward()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
|
||||
ENT.Base = "lvs_base"
|
||||
|
||||
ENT.PrintName = "[LVS] Base Fighter Plane"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.MaxVelocity = 2500
|
||||
ENT.MaxPerfVelocity = 1500
|
||||
ENT.MaxThrust = 250
|
||||
|
||||
ENT.ThrottleRateUp = 0.6
|
||||
ENT.ThrottleRateDown = 0.3
|
||||
|
||||
ENT.TurnRatePitch = 1
|
||||
ENT.TurnRateYaw = 1
|
||||
ENT.TurnRateRoll = 1
|
||||
|
||||
ENT.GravityTurnRatePitch = 1
|
||||
ENT.GravityTurnRateYaw = 1
|
||||
|
||||
ENT.ForceLinearMultiplier = 1
|
||||
|
||||
ENT.ForceAngleMultiplier = 1
|
||||
ENT.ForceAngleDampingMultiplier = 1
|
||||
|
||||
ENT.MaxSlipAnglePitch = 20
|
||||
ENT.MaxSlipAngleYaw = 10
|
||||
|
||||
ENT.StallVelocity = 150
|
||||
ENT.StallForceMultiplier = 4
|
||||
ENT.StallForceMax = 40
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:CreateBaseDT()
|
||||
|
||||
self:AddDT( "Vector", "Steer" )
|
||||
self:AddDT( "Vector", "AIAimVector" )
|
||||
self:AddDT( "Float", "NWThrottle" )
|
||||
self:AddDT( "Float", "MaxThrottle" )
|
||||
self:AddDT( "Float", "LandingGear" )
|
||||
|
||||
if SERVER then
|
||||
self:SetLandingGear( 1 )
|
||||
self:SetMaxThrottle( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PlayerDirectInput( ply, cmd )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local KeyLeft = ply:lvsKeyDown( "-ROLL" )
|
||||
local KeyRight = ply:lvsKeyDown( "+ROLL" )
|
||||
local KeyPitchUp = ply:lvsKeyDown( "+PITCH" )
|
||||
local KeyPitchDown = ply:lvsKeyDown( "-PITCH" )
|
||||
local KeyRollRight = ply:lvsKeyDown( "+YAW" )
|
||||
local KeyRollLeft = ply:lvsKeyDown( "-YAW" )
|
||||
|
||||
local MouseX = cmd:GetMouseX()
|
||||
local MouseY = cmd:GetMouseY()
|
||||
|
||||
if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then
|
||||
MouseX = 0
|
||||
MouseY = 0
|
||||
else
|
||||
ply:SetEyeAngles( Angle(0,90,0) )
|
||||
end
|
||||
|
||||
local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity()
|
||||
|
||||
if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end
|
||||
if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end
|
||||
if KeyRollRight or KeyRollLeft then
|
||||
local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0)
|
||||
|
||||
MouseX = (NewX / SensX) * ReturnDelta
|
||||
end
|
||||
|
||||
local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 )
|
||||
|
||||
local Cur = self:GetSteer()
|
||||
|
||||
local Rate = Delta * 3 * ReturnDelta
|
||||
|
||||
local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0)
|
||||
|
||||
local Target = New + Input * Delta * 0.8
|
||||
|
||||
local Fx = math.Clamp( Target.x, -1, 1 )
|
||||
local Fy = math.Clamp( Target.y, -1, 1 )
|
||||
|
||||
local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0)
|
||||
local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3)
|
||||
|
||||
local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1)
|
||||
|
||||
self:SetSteer( F )
|
||||
end
|
||||
|
||||
function ENT:CalcThrottle( ply, cmd )
|
||||
if CLIENT then return end
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local ThrottleUp = ply:lvsKeyDown( "+THROTTLE" ) and self.ThrottleRateUp or 0
|
||||
local ThrottleDown = ply:lvsKeyDown( "-THROTTLE" ) and -self.ThrottleRateDown or 0
|
||||
|
||||
local Throttle = (ThrottleUp + ThrottleDown) * Delta
|
||||
|
||||
self:SetThrottle( self:GetThrottle() + Throttle )
|
||||
end
|
||||
|
||||
function ENT:SetThrottle( NewThrottle )
|
||||
if self:GetEngineActive() then
|
||||
self:SetNWThrottle( math.Clamp(NewThrottle,0,self:GetMaxThrottle()) )
|
||||
else
|
||||
self:SetNWThrottle( 0 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetThrottle()
|
||||
if self:GetEngineActive() then
|
||||
return self:GetNWThrottle()
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
if SERVER and not self.WheelAutoRetract then
|
||||
local KeyJump = ply:lvsKeyDown( "VSPEC" )
|
||||
|
||||
if self._lvsOldKeyJump ~= KeyJump then
|
||||
self._lvsOldKeyJump = KeyJump
|
||||
if KeyJump then
|
||||
self:ToggleLandingGear()
|
||||
self:PhysWake()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not ply:lvsMouseAim() then
|
||||
self:PlayerDirectInput( ply, cmd )
|
||||
end
|
||||
|
||||
self:CalcThrottle( ply, cmd )
|
||||
end
|
||||
|
||||
function ENT:FreezeStability()
|
||||
self._StabilityFrozen = CurTime() + 2
|
||||
end
|
||||
|
||||
function ENT:GetStability()
|
||||
if (self._StabilityFrozen or 0) > CurTime() then
|
||||
return 1, 0, self.MaxPerfVelocity
|
||||
end
|
||||
|
||||
local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x
|
||||
|
||||
local Stability = math.Clamp(ForwardVelocity / self.MaxPerfVelocity,0,1) ^ 2
|
||||
local InvStability = 1 - Stability
|
||||
|
||||
return Stability, InvStability, ForwardVelocity
|
||||
end
|
||||
|
||||
function ENT:GetThrustStrenght()
|
||||
local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x
|
||||
|
||||
return (self.MaxVelocity - ForwardVelocity) * self:GetThrottle() / self.MaxVelocity
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "plane"
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
local RangerLength = 15000
|
||||
local mySpeed = self:GetVelocity():Length()
|
||||
local MinDist = 600 + mySpeed
|
||||
|
||||
local StartPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local TraceFilter = self:GetCrosshairFilterEnts()
|
||||
|
||||
local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } )
|
||||
local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } )
|
||||
local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } )
|
||||
|
||||
local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } )
|
||||
local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } )
|
||||
local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } )
|
||||
|
||||
local cAvoid = Vector(0,0,0)
|
||||
|
||||
local myRadius = self:BoundingRadius()
|
||||
local myPos = self:GetPos()
|
||||
local myDir = self:GetForward()
|
||||
for _, v in pairs( LVS:GetVehicles() ) do
|
||||
if v == self then continue end
|
||||
|
||||
local theirRadius = v:BoundingRadius()
|
||||
local Sub = (myPos - v:GetPos())
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
if Dist < (theirRadius + myRadius + 200) then
|
||||
if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then
|
||||
cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8
|
||||
local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8
|
||||
|
||||
local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist
|
||||
local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist
|
||||
|
||||
local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist
|
||||
local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist
|
||||
|
||||
local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist
|
||||
local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist
|
||||
|
||||
local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist
|
||||
local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist
|
||||
|
||||
local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10
|
||||
|
||||
local alt = (StartPos - TraceDown.HitPos):Length()
|
||||
local ceiling = (StartPos - TraceUp.HitPos):Length()
|
||||
|
||||
local WallDist = (StartPos - TraceForward.HitPos):Length()
|
||||
|
||||
local Throttle = math.min( WallDist / mySpeed, 1 )
|
||||
|
||||
self._AIFireInput = false
|
||||
|
||||
if alt < 600 or ceiling < 600 or WallDist < (MinDist * 3 * (math.deg( math.acos( math.Clamp( Vector(0,0,1):Dot( myDir ) ,-1,1) ) ) / 180) ^ 2) then
|
||||
if ceiling < 600 then
|
||||
Throttle = 0
|
||||
else
|
||||
Throttle = 1
|
||||
|
||||
if self:HitGround() then
|
||||
TargetPos.z = StartPos.z + 750
|
||||
else
|
||||
if self:GetStability() < 0.5 then
|
||||
TargetPos.z = StartPos.z + 1500
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if self:GetStability() < 0.5 then
|
||||
TargetPos.z = StartPos.z + 600
|
||||
else
|
||||
if IsValid( self:GetHardLockTarget() ) then
|
||||
TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8
|
||||
else
|
||||
if alt > mySpeed then
|
||||
local Target = self._LastAITarget
|
||||
|
||||
if not IsValid( self._LastAITarget ) or not self:AITargetInFront( self._LastAITarget, 135 ) or not self:AICanSee( self._LastAITarget ) then
|
||||
Target = self:AIGetTarget()
|
||||
end
|
||||
|
||||
if IsValid( Target ) then
|
||||
if self:AITargetInFront( Target, 65 ) then
|
||||
local T = CurTime() + self:EntIndex() * 1337
|
||||
TargetPos = Target:GetPos() + cAvoid * 8 + Vector(0,0, math.sin( T * 5 ) * 500 ) + Target:GetVelocity() * math.abs( math.cos( T * 13.37 ) ) * 5
|
||||
|
||||
Throttle = math.min( (StartPos - TargetPos):Length() / mySpeed, 1 )
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = StartPos,
|
||||
endpos = (StartPos + self:GetForward() * 50000),
|
||||
mins = Vector( -50, -50, -50 ),
|
||||
maxs = Vector( 50, 50, 50 ),
|
||||
filter = TraceFilter
|
||||
} )
|
||||
|
||||
local CanShoot = (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true
|
||||
|
||||
if CanShoot and self:AITargetInFront( Target, 22 ) then
|
||||
local CurHeat = self:GetNWHeat()
|
||||
local CurWeapon = self:GetSelectedWeapon()
|
||||
|
||||
if CurWeapon > 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
else
|
||||
if CurHeat > 0.9 then
|
||||
if CurWeapon == 1 and self:AIHasWeapon( 2 ) then
|
||||
self:AISelectWeapon( 2 )
|
||||
|
||||
elseif CurWeapon == 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
else
|
||||
if CurHeat == 0 and math.cos( T ) > 0 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._AIFireInput = true
|
||||
end
|
||||
else
|
||||
self:AISelectWeapon( 1 )
|
||||
|
||||
if alt > 6000 and self:AITargetInFront( Target, 90 ) then
|
||||
TargetPos = Target:GetPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
TargetPos.z = StartPos.z + 2000
|
||||
end
|
||||
end
|
||||
end
|
||||
self:RaiseLandingGear()
|
||||
end
|
||||
|
||||
self:SetThrottle( Throttle )
|
||||
|
||||
self.smTargetPos = self.smTargetPos and self.smTargetPos + (TargetPos - self.smTargetPos) * FrameTime() or self:GetPos()
|
||||
|
||||
self._lvsAITargetAng = (self.smTargetPos - self:GetPos()):GetNormalized():Angle()
|
||||
end
|
||||
|
||||
function ENT:AISelectWeapon( ID )
|
||||
if ID == self:GetSelectedWeapon() then return end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (self._nextAISwitchWeapon or 0) > T then return end
|
||||
|
||||
self._nextAISwitchWeapon = T + math.random(3,6)
|
||||
|
||||
self:SelectWeapon( ID )
|
||||
end
|
||||
|
||||
function ENT:OnAITakeDamage( dmginfo )
|
||||
local attacker = dmginfo:GetAttacker()
|
||||
|
||||
if not IsValid( attacker ) then return end
|
||||
|
||||
if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then
|
||||
self:SetHardLockTarget( attacker )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetHardLockTarget( target )
|
||||
self._HardLockTarget = target
|
||||
self._HardLockTime = CurTime() + 4
|
||||
end
|
||||
|
||||
function ENT:GetHardLockTarget()
|
||||
if (self._HardLockTime or 0) < CurTime() then return NULL end
|
||||
|
||||
return self._HardLockTarget
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
function ENT:AddThruster( pos )
|
||||
local Thruster = ents.Create( "lvs_fighterplane_thruster" )
|
||||
|
||||
if not IsValid( Thruster ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create thruster entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Thruster:SetPos( self:LocalToWorld( pos ) )
|
||||
Thruster:SetAngles( self:GetAngles() )
|
||||
Thruster:Spawn()
|
||||
Thruster:Activate()
|
||||
Thruster:SetParent( self )
|
||||
Thruster:SetBase( self )
|
||||
|
||||
self:DeleteOnRemove( Thruster )
|
||||
|
||||
self:TransferCPPI( Thruster )
|
||||
|
||||
return Thruster
|
||||
end
|
||||
|
||||
function ENT:AddEngine( pos )
|
||||
local Engine = ents.Create( "lvs_fighterplane_engine" )
|
||||
|
||||
if not IsValid( Engine ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Engine:SetPos( self:LocalToWorld( pos ) )
|
||||
Engine:SetAngles( self:GetAngles() )
|
||||
Engine:Spawn()
|
||||
Engine:Activate()
|
||||
Engine:SetParent( self )
|
||||
Engine:SetBase( self )
|
||||
|
||||
self:DeleteOnRemove( Engine )
|
||||
|
||||
self:TransferCPPI( Engine )
|
||||
|
||||
self:AddDS( {
|
||||
pos = pos,
|
||||
ang = Angle(0,0,0),
|
||||
mins = Vector(-40,-20,-30),
|
||||
maxs = Vector(40,20,30),
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
dmginfo:ScaleDamage( 2.5 )
|
||||
end
|
||||
} )
|
||||
|
||||
return Engine
|
||||
end
|
||||
|
||||
function ENT:AddRotor( pos )
|
||||
local Rotor = ents.Create( "lvs_fighterplane_rotor" )
|
||||
|
||||
if not IsValid( Rotor ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create rotor entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Rotor:SetPos( self:LocalToWorld( pos ) )
|
||||
Rotor:SetAngles( self:GetAngles() )
|
||||
Rotor:Spawn()
|
||||
Rotor:Activate()
|
||||
Rotor:SetParent( self )
|
||||
Rotor:SetBase( self )
|
||||
|
||||
if self:BoundingRadius() >= 600 then
|
||||
Rotor:SetSound("lvs/vehicles/generic/bomber_propeller.wav")
|
||||
Rotor:SetSoundStrain("lvs/vehicles/generic/bomber_propeller_strain.wav")
|
||||
end
|
||||
|
||||
self:DeleteOnRemove( Rotor )
|
||||
|
||||
self:TransferCPPI( Rotor )
|
||||
|
||||
return Rotor
|
||||
end
|
||||
|
||||
function ENT:AddExhaust( pos, ang )
|
||||
if not istable( self.ExhaustPositions ) then self.ExhaustPositions = {} end
|
||||
|
||||
local Exhaust = {
|
||||
pos = pos,
|
||||
ang = ang,
|
||||
}
|
||||
|
||||
table.insert( self.ExhaustPositions, Exhaust )
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
function ENT:HandleLandingGear( Rate )
|
||||
local EnableBrakes = self:GetThrottle() <= 0
|
||||
|
||||
local Cur = self:GetLandingGear()
|
||||
|
||||
if self.WheelAutoRetract then
|
||||
if self:HitGround() then
|
||||
self.LandingGearUp = false
|
||||
else
|
||||
self.LandingGearUp = self:GetStability() > 0.4
|
||||
end
|
||||
end
|
||||
|
||||
local New = Cur + math.Clamp((self.LandingGearUp and 0 or 1) - Cur,-Rate,Rate)
|
||||
|
||||
local SetValue = Cur ~= New
|
||||
|
||||
if SetValue then
|
||||
self:SetLandingGear( New )
|
||||
end
|
||||
|
||||
for _, data in pairs( self:GetWheels() ) do
|
||||
local wheel = data.entity
|
||||
local mass = data.mass
|
||||
local physobj = data.physobj
|
||||
|
||||
if not IsValid( wheel ) or not IsValid( physobj ) then continue end
|
||||
|
||||
wheel:SetBrakes( EnableBrakes )
|
||||
|
||||
if not SetValue then continue end
|
||||
|
||||
physobj:SetMass( 1 + (mass - 1) * New ^ 4 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:ToggleLandingGear()
|
||||
if self.WheelAutoRetract then return end
|
||||
|
||||
self.LandingGearUp = not self.LandingGearUp
|
||||
|
||||
self:OnLandingGearToggled( self.LandingGearUp )
|
||||
end
|
||||
|
||||
function ENT:RaiseLandingGear()
|
||||
if self.WheelAutoRetract then return end
|
||||
|
||||
if not self.LandingGearUp then
|
||||
self.LandingGearUp = true
|
||||
|
||||
self:OnLandingGearToggled( self.LandingGearUp )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DeployLandingGear()
|
||||
if self.WheelAutoRetract then return end
|
||||
|
||||
if self.LandingGearUp then
|
||||
self.LandingGearUp = false
|
||||
|
||||
self:OnLandingGearToggled( self.LandingGearUp )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnLandingGearToggled( IsDeployed )
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
function ENT:PlayerMouseAim( ply )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local PitchUp = ply:lvsKeyDown( "+PITCH" )
|
||||
local PitchDown = ply:lvsKeyDown( "-PITCH" )
|
||||
local YawRight = ply:lvsKeyDown( "+YAW" )
|
||||
local YawLeft = ply:lvsKeyDown( "-YAW" )
|
||||
local RollRight = ply:lvsKeyDown( "+ROLL" )
|
||||
local RollLeft = ply:lvsKeyDown( "-ROLL" )
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
|
||||
local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() )
|
||||
|
||||
if FreeLook then
|
||||
if isangle( self.StoredEyeAngles ) then
|
||||
EyeAngles = self.StoredEyeAngles
|
||||
end
|
||||
else
|
||||
self.StoredEyeAngles = EyeAngles
|
||||
end
|
||||
|
||||
local OverridePitch = 0
|
||||
local OverrideYaw = 0
|
||||
local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0)
|
||||
|
||||
if PitchUp or PitchDown then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0)
|
||||
end
|
||||
|
||||
if YawRight or YawLeft then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0)
|
||||
end
|
||||
|
||||
self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook )
|
||||
end
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
ENT.WheelSteerAngle = 45
|
||||
|
||||
function ENT:AddWheelSteeringPlate( rear )
|
||||
if rear then
|
||||
if IsValid( self._lvsSteerPlateRear ) then
|
||||
return self._lvsSteerPlateRear
|
||||
end
|
||||
else
|
||||
if IsValid( self._lvsSteerPlate ) then
|
||||
return self._lvsSteerPlate
|
||||
end
|
||||
end
|
||||
|
||||
local SteerMaster = ents.Create( "prop_physics" )
|
||||
|
||||
if not IsValid( SteerMaster ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize steering plate. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
SteerMaster:SetModel( "models/hunter/plates/plate025x025.mdl" )
|
||||
SteerMaster:SetPos( self:GetPos() )
|
||||
SteerMaster:SetAngles( Angle(0,90,0) )
|
||||
SteerMaster:Spawn()
|
||||
SteerMaster:Activate()
|
||||
|
||||
local PhysObj = SteerMaster:GetPhysicsObject()
|
||||
if IsValid( PhysObj ) then
|
||||
PhysObj:EnableMotion( false )
|
||||
else
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize steering plate. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
SteerMaster:SetOwner( self )
|
||||
SteerMaster:DrawShadow( false )
|
||||
SteerMaster:SetNotSolid( true )
|
||||
SteerMaster:SetNoDraw( true )
|
||||
SteerMaster.DoNotDuplicate = true
|
||||
self:DeleteOnRemove( SteerMaster )
|
||||
self:TransferCPPI( SteerMaster )
|
||||
|
||||
if rear then
|
||||
self._lvsSteerPlateRear = SteerMaster
|
||||
else
|
||||
self._lvsSteerPlate = SteerMaster
|
||||
end
|
||||
|
||||
return SteerMaster
|
||||
end
|
||||
|
||||
function ENT:SetWheelSteer( SteerAngle )
|
||||
if IsValid( self._lvsSteerPlate ) then
|
||||
local PhysObj = self._lvsSteerPlate:GetPhysicsObject()
|
||||
|
||||
if IsValid( PhysObj ) then
|
||||
if PhysObj:IsMotionEnabled() then
|
||||
PhysObj:EnableMotion( false )
|
||||
end
|
||||
end
|
||||
|
||||
self._lvsSteerPlate:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(SteerAngle,-self.WheelSteerAngle,self.WheelSteerAngle),0) ) )
|
||||
end
|
||||
|
||||
if not IsValid( self._lvsSteerPlateRear ) then return end
|
||||
|
||||
local PhysObj = self._lvsSteerPlateRear:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) then return end
|
||||
|
||||
if PhysObj:IsMotionEnabled() then
|
||||
PhysObj:EnableMotion( false )
|
||||
end
|
||||
|
||||
self._lvsSteerPlateRear:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(-SteerAngle,-self.WheelSteerAngle,self.WheelSteerAngle),0) ) )
|
||||
end
|
||||
|
||||
function ENT:GetWheels()
|
||||
if not istable( self._lvsWheels ) then self._lvsWheels = {} end
|
||||
|
||||
return self._lvsWheels
|
||||
end
|
||||
|
||||
function ENT:AddWheel( pos, radius, mass, type )
|
||||
if not isvector( pos ) or not isnumber( radius ) or not isnumber( mass ) then return end
|
||||
|
||||
if not type then
|
||||
type = LVS.WHEEL_BRAKE
|
||||
end
|
||||
|
||||
local wheel = ents.Create( "lvs_fighterplane_wheel" )
|
||||
|
||||
if not IsValid( wheel ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize wheel. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local WheelPos = self:LocalToWorld( pos )
|
||||
local CenterPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
debugoverlay.Sphere( WheelPos, radius, 5, Color(150,150,150), true )
|
||||
debugoverlay.Line( CenterPos, WheelPos, 5, Color(150,150,150), true )
|
||||
|
||||
wheel:SetPos( WheelPos )
|
||||
wheel:SetAngles( self:LocalToWorldAngles( Angle(0,90,0) ) )
|
||||
wheel:Spawn()
|
||||
wheel:Activate()
|
||||
wheel:SetBase( self )
|
||||
wheel:Define(
|
||||
{
|
||||
physmat = "jeeptire",
|
||||
radius = radius,
|
||||
mass = mass,
|
||||
brake = type == LVS.WHEEL_BRAKE,
|
||||
}
|
||||
)
|
||||
|
||||
local PhysObj = wheel:GetPhysicsObject()
|
||||
if not IsValid( PhysObj ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to initialize wheel phys model. Vehicle terminated.")
|
||||
return
|
||||
end
|
||||
|
||||
PhysObj:EnableMotion( false )
|
||||
|
||||
self:DeleteOnRemove( wheel )
|
||||
self:TransferCPPI( wheel )
|
||||
|
||||
if type == LVS.WHEEL_STEER_NONE then
|
||||
local ballsocket = constraint.AdvBallsocket(wheel, self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 1)
|
||||
ballsocket.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket )
|
||||
end
|
||||
|
||||
if type == LVS.WHEEL_BRAKE then
|
||||
local axis = constraint.Axis( wheel, self, 0, 0, PhysObj:GetMassCenter(), wheel:GetPos(), 0, 0, 0, 0, Vector(1,0,0) , false )
|
||||
axis.DoNotDuplicate = true
|
||||
self:TransferCPPI( axis )
|
||||
|
||||
wheel:SetBrakes( true )
|
||||
end
|
||||
|
||||
if type == LVS.WHEEL_STEER_FRONT then
|
||||
wheel:SetAngles( Angle(0,0,0) )
|
||||
|
||||
local SteerMaster = self:AddWheelSteeringPlate( false )
|
||||
|
||||
local ballsocket1 = constraint.AdvBallsocket(wheel, SteerMaster,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -0.01, -0.01, 180, 0.01, 0.01, 0, 0, 0, 1, 0)
|
||||
ballsocket1.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket1 )
|
||||
|
||||
local ballsocket2 = constraint.AdvBallsocket(wheel,self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 0)
|
||||
ballsocket2.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket2 )
|
||||
end
|
||||
|
||||
if type == LVS.WHEEL_STEER_REAR then
|
||||
wheel:SetAngles( Angle(0,0,0) )
|
||||
|
||||
local SteerMaster = self:AddWheelSteeringPlate( true )
|
||||
|
||||
|
||||
local ballsocket1 = constraint.AdvBallsocket(wheel, SteerMaster,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -0.01, -0.01, 180, 0.01, 0.01, 0, 0, 0, 1, 0)
|
||||
ballsocket1.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket1 )
|
||||
|
||||
local ballsocket2 = constraint.AdvBallsocket(wheel,self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 0)
|
||||
ballsocket2.DoNotDuplicate = true
|
||||
self:TransferCPPI( ballsocket2 )
|
||||
end
|
||||
|
||||
local nocollide = constraint.NoCollide( wheel, self, 0, 0 )
|
||||
nocollide.DoNotDuplicate = true
|
||||
self:TransferCPPI( nocollide )
|
||||
|
||||
PhysObj:EnableMotion( true )
|
||||
PhysObj:EnableDrag( false )
|
||||
|
||||
local WheelData = {
|
||||
entity = wheel,
|
||||
physobj = PhysObj,
|
||||
mass = mass,
|
||||
}
|
||||
|
||||
if not istable( self._lvsWheels ) then self._lvsWheels = {} end
|
||||
|
||||
table.insert( self._lvsWheels, WheelData )
|
||||
|
||||
return wheel
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
include("shared.lua")
|
||||
|
||||
function ENT:Think()
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
end
|
||||
|
||||
function ENT:DrawTranslucent()
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetNWAimVector()
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
if self._AimVectorUnlocked then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
end
|
||||
end
|
||||
|
||||
return Driver:GetAimVector()
|
||||
else
|
||||
return self:GetForward()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSPaintHitMarker( scr )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
Base:LVSPaintHitMarker( scr )
|
||||
end
|
||||
|
||||
function ENT:LVSDrawCircle( X, Y, target_radius, value )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
Base:LVSDrawCircle( X, Y, target_radius, value )
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairCenter( Pos2D, Col )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
Base:PaintCrosshairCenter( Pos2D, Col )
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairOuter( Pos2D, Col )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
Base:PaintCrosshairOuter( Pos2D, Col )
|
||||
end
|
||||
|
||||
function ENT:PaintCrosshairSquare( Pos2D, Col )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
Base:PaintCrosshairSquare( Pos2D, Col )
|
||||
end
|
||||
327
garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/init.lua
Normal file
327
garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/init.lua
Normal file
@@ -0,0 +1,327 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_ai.lua")
|
||||
|
||||
function ENT:Initialize()
|
||||
self:SetMoveType( MOVETYPE_NONE )
|
||||
self:SetSolid( SOLID_NONE )
|
||||
self:DrawShadow( false )
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:HandleActive()
|
||||
self:WeaponsThink()
|
||||
|
||||
if self:GetAI() then
|
||||
self:RunAI()
|
||||
end
|
||||
|
||||
self:NextThink( CurTime() )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:HandleActive()
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( Pod ) then
|
||||
return
|
||||
end
|
||||
|
||||
local Driver = Pod:GetDriver()
|
||||
|
||||
if Driver ~= self:GetDriver() then
|
||||
local NewDriver = Driver
|
||||
local OldDriver = self:GetDriver()
|
||||
|
||||
self:SetDriver( Driver )
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if IsValid( Base ) then
|
||||
Base:OnPassengerChanged( OldDriver, NewDriver, Pod:GetNWInt( "pPodIndex", -1 ) )
|
||||
end
|
||||
|
||||
if IsValid( Driver ) then
|
||||
Driver:lvsBuildControls()
|
||||
else
|
||||
self:WeaponsFinish()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
end
|
||||
|
||||
function ENT:UpdateTransmitState()
|
||||
return TRANSMIT_ALWAYS
|
||||
end
|
||||
|
||||
function ENT:WeaponsFinish()
|
||||
if not self._activeWeapon then return end
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ self._activeWeapon ]
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
if CurWeapon.FinishAttack then
|
||||
CurWeapon.FinishAttack( self )
|
||||
end
|
||||
|
||||
self._activeWeapon = nil
|
||||
self.OldAttack = false
|
||||
end
|
||||
|
||||
function ENT:GetAmmo()
|
||||
if self:GetAI() then return self:GetMaxAmmo() end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return -1 end
|
||||
|
||||
return CurWeapon._CurAmmo or self:GetMaxAmmo()
|
||||
end
|
||||
|
||||
function ENT:TakeAmmo( num )
|
||||
if self:GetMaxAmmo() <= 0 then return end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
CurWeapon._CurAmmo = math.max( self:GetAmmo() - (num or 1), 0 )
|
||||
|
||||
self:SetNWAmmo( CurWeapon._CurAmmo )
|
||||
end
|
||||
|
||||
function ENT:GetHeat( weaponid )
|
||||
local CurWeapon
|
||||
|
||||
if isnumber( weaponid ) and weaponid > 0 then
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if IsValid( Base ) then
|
||||
CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ weaponid ]
|
||||
else
|
||||
CurWeapon = self:GetActiveWeapon()
|
||||
end
|
||||
else
|
||||
CurWeapon = self:GetActiveWeapon()
|
||||
end
|
||||
|
||||
if not CurWeapon then return 0 end
|
||||
|
||||
return (CurWeapon._CurHeat or 0)
|
||||
end
|
||||
|
||||
function ENT:GetOverheated()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return false end
|
||||
|
||||
return CurWeapon.Overheated == true
|
||||
end
|
||||
|
||||
function ENT:SetOverheated( overheat )
|
||||
if self:GetOverheated() == overheat then return end
|
||||
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
CurWeapon.Overheated = overheat
|
||||
|
||||
self:SetNWOverheated( overheat )
|
||||
|
||||
if self:GetHeat() == 0 then return end
|
||||
|
||||
if CurWeapon.OnOverheat then
|
||||
CurWeapon.OnOverheat( self )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetHeat( heat )
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
heat = math.Clamp( heat, 0, 1 )
|
||||
|
||||
CurWeapon._CurHeat = heat
|
||||
|
||||
if self:GetNWHeat() == heat then return end
|
||||
|
||||
self:SetNWHeat( heat )
|
||||
end
|
||||
|
||||
function ENT:CanAttack()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
return (CurWeapon._NextFire or 0) < CurTime()
|
||||
end
|
||||
|
||||
function ENT:SetNextAttack( time )
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
CurWeapon._NextFire = time
|
||||
end
|
||||
|
||||
function ENT:WeaponsShouldFire()
|
||||
if self:GetAI() then return self._AIFireInput end
|
||||
|
||||
local ply = self:GetDriver()
|
||||
|
||||
if not IsValid( ply ) then return false end
|
||||
|
||||
return ply:lvsKeyDown( "ATTACK" )
|
||||
end
|
||||
|
||||
function ENT:WeaponsThink()
|
||||
local T = CurTime()
|
||||
local FT = FrameTime()
|
||||
local CurWeapon, SelectedID = self:GetActiveWeapon()
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
for ID, Weapon in pairs( Base.WEAPONS[ self:GetPodIndex() ] ) do
|
||||
local IsActive = ID == SelectedID
|
||||
if Weapon.OnThink then Weapon.OnThink( self, IsActive ) end
|
||||
|
||||
if IsActive then continue end
|
||||
|
||||
if Weapon.HeatIsClip and not Weapon.Overheated and Weapon._CurHeat ~= 0 then
|
||||
Weapon.Overheated = true
|
||||
Weapon._CurHeat = 1
|
||||
|
||||
if Weapon.OnReload then Weapon.OnReload( self ) end
|
||||
end
|
||||
|
||||
-- cool all inactive weapons down
|
||||
Weapon._CurHeat = Weapon._CurHeat and Weapon._CurHeat - math.min( Weapon._CurHeat, (Weapon.HeatRateDown or 0.25) * FT ) or 0
|
||||
end
|
||||
|
||||
if not CurWeapon then return end
|
||||
|
||||
local ShouldFire = self:WeaponsShouldFire()
|
||||
local CurHeat = self:GetHeat()
|
||||
|
||||
if self:GetOverheated() then
|
||||
if CurHeat <= 0 then
|
||||
self:SetOverheated( false )
|
||||
else
|
||||
ShouldFire = false
|
||||
end
|
||||
else
|
||||
if CurHeat >= 1 then
|
||||
self:SetOverheated( true )
|
||||
ShouldFire = false
|
||||
if CurWeapon.OnReload then CurWeapon.OnReload( self ) end
|
||||
end
|
||||
end
|
||||
|
||||
if self:GetMaxAmmo() > 0 then
|
||||
if self:GetAmmo() <= 0 then
|
||||
ShouldFire = false
|
||||
end
|
||||
end
|
||||
|
||||
if ShouldFire ~= self.OldAttack then
|
||||
self.OldAttack = ShouldFire
|
||||
|
||||
if ShouldFire then
|
||||
if CurWeapon.StartAttack then
|
||||
CurWeapon.StartAttack( self )
|
||||
end
|
||||
self._activeWeapon = SelectedID
|
||||
else
|
||||
self:WeaponsFinish()
|
||||
end
|
||||
end
|
||||
|
||||
if ShouldFire then
|
||||
if not self:CanAttack() then return end
|
||||
|
||||
local ShootDelay = (CurWeapon.Delay or 0)
|
||||
|
||||
self:SetNextAttack( T + ShootDelay )
|
||||
self:SetHeat( CurHeat + (CurWeapon.HeatRateUp or 0.2) * math.max(ShootDelay, FT) )
|
||||
|
||||
if not CurWeapon.Attack then return end
|
||||
|
||||
if CurWeapon.Attack( self ) then
|
||||
self:SetHeat( CurHeat - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) )
|
||||
self:SetNextAttack( T )
|
||||
end
|
||||
|
||||
self._lvsNextActiveWeaponCoolDown = T + 0.25
|
||||
else
|
||||
if (self._lvsNextActiveWeaponCoolDown or 0) > T then return end
|
||||
|
||||
if CurWeapon.HeatIsClip and not CurWeapon.Overheated then
|
||||
|
||||
self:SetHeat( self:GetHeat() )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:SetHeat( self:GetHeat() - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SelectWeapon( ID )
|
||||
if not isnumber( ID ) then return end
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
if Base.WEAPONS[ self:GetPodIndex() ][ ID ] then
|
||||
self:SetSelectedWeapon( ID )
|
||||
end
|
||||
|
||||
local ply = self:GetDriver()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
net.Start( "lvs_select_weapon" )
|
||||
net.Send( ply )
|
||||
end
|
||||
|
||||
function ENT:OnWeaponChanged( name, old, new)
|
||||
if new == old then return end
|
||||
|
||||
self:WeaponsFinish()
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local PrevWeapon = Base.WEAPONS[ self:GetPodIndex() ][ old ]
|
||||
if PrevWeapon and PrevWeapon.OnDeselect then
|
||||
PrevWeapon.OnDeselect( self )
|
||||
end
|
||||
|
||||
local NextWeapon = Base.WEAPONS[ self:GetPodIndex() ][ new ]
|
||||
if NextWeapon and NextWeapon.OnSelect then
|
||||
NextWeapon.OnSelect( self )
|
||||
self:SetNWAmmo( NextWeapon._CurAmmo or NextWeapon.Ammo or -1 )
|
||||
self:SetNWOverheated( NextWeapon.Overheated == true )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSFireBullet( data )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
data.Entity = Base
|
||||
data.Velocity = data.Velocity + self:GetVelocity():Length()
|
||||
data.SrcEntity = Base:WorldToLocal( data.Src )
|
||||
|
||||
LVS:FireBullet( data )
|
||||
end
|
||||
@@ -0,0 +1,174 @@
|
||||
ENT.Type = "anim"
|
||||
|
||||
ENT.PrintName = "LBaseGunner"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
ENT.DoNotDuplicate = true
|
||||
|
||||
ENT.LVS_GUNNER = true
|
||||
ENT.VectorNull = Vector(0,0,0)
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar( "Entity",0, "Driver" )
|
||||
self:NetworkVar( "Entity",1, "DriverSeat" )
|
||||
|
||||
self:NetworkVar( "Int", 0, "PodIndex")
|
||||
self:NetworkVar( "Int", 1, "NWAmmo")
|
||||
self:NetworkVar( "Int", 2, "SelectedWeapon" )
|
||||
|
||||
self:NetworkVar( "Float", 0, "NWHeat" )
|
||||
|
||||
self:NetworkVar( "Bool", 0, "NWOverheated" )
|
||||
|
||||
self:NetworkVar( "Vector", 0, "NWAimVector" )
|
||||
|
||||
if SERVER then
|
||||
self:NetworkVarNotify( "SelectedWeapon", self.OnWeaponChanged )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:UnlockAimVector()
|
||||
self._AimVectorUnlocked = true
|
||||
end
|
||||
|
||||
function ENT:LockAimVector()
|
||||
self._AimVectorUnlocked = nil
|
||||
end
|
||||
|
||||
function ENT:GetEyeTrace()
|
||||
local startpos = self:GetPos()
|
||||
|
||||
local trace = util.TraceLine( {
|
||||
start = startpos,
|
||||
endpos = (startpos + self:GetAimVector() * 50000),
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAI()
|
||||
if IsValid( self:GetDriver() ) then return false end
|
||||
|
||||
local veh = self:GetVehicle()
|
||||
|
||||
if not IsValid( veh ) then return false end
|
||||
|
||||
return veh:GetAIGunners()
|
||||
end
|
||||
|
||||
function ENT:GetAITEAM()
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return 0 end
|
||||
|
||||
return Base:GetAITEAM()
|
||||
end
|
||||
|
||||
function ENT:GetVehicle()
|
||||
local Pod = self:GetParent()
|
||||
|
||||
if not IsValid( Pod ) then return NULL end
|
||||
|
||||
return Pod:GetParent()
|
||||
end
|
||||
|
||||
function ENT:HasWeapon( ID )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return false end
|
||||
|
||||
return istable( Base.WEAPONS[ self:GetPodIndex() ][ ID ] )
|
||||
end
|
||||
|
||||
function ENT:AIHasWeapon( ID )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return false end
|
||||
|
||||
local weapon = Base.WEAPONS[ self:GetPodIndex() ][ ID ]
|
||||
|
||||
if not istable( weapon ) then return false end
|
||||
|
||||
return weapon.UseableByAI
|
||||
end
|
||||
|
||||
function ENT:GetActiveWeapon()
|
||||
local SelectedID = self:GetSelectedWeapon()
|
||||
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return {}, SelectedID end
|
||||
|
||||
local CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ SelectedID ]
|
||||
|
||||
return CurWeapon, SelectedID
|
||||
end
|
||||
|
||||
function ENT:GetMaxAmmo()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return -1 end
|
||||
|
||||
return CurWeapon.Ammo or -1
|
||||
end
|
||||
|
||||
function ENT:GetClip()
|
||||
local CurWeapon = self:GetActiveWeapon()
|
||||
|
||||
if not CurWeapon then return 0 end
|
||||
|
||||
local HeatIncrement = (CurWeapon.HeatRateUp or 0.2) * math.max(CurWeapon.Delay or 0, FrameTime())
|
||||
|
||||
local Ammo = self:GetNWAmmo()
|
||||
|
||||
if self:GetMaxAmmo() <= 0 and CurWeapon.Clip then
|
||||
Ammo = CurWeapon.Clip
|
||||
end
|
||||
|
||||
return math.min( math.ceil( math.Round( (1 - self:GetNWHeat()) / HeatIncrement, 1 ) ), Ammo )
|
||||
end
|
||||
|
||||
function ENT:GetCrosshairFilterEnts()
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return {} end
|
||||
|
||||
return Base:GetCrosshairFilterEnts()
|
||||
end
|
||||
|
||||
function ENT:Sign( n )
|
||||
if n > 0 then return 1 end
|
||||
|
||||
if n < 0 then return -1 end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function ENT:VectorSubtractNormal( Normal, Velocity )
|
||||
local VelForward = Velocity:GetNormalized()
|
||||
|
||||
local Ax = math.acos( math.Clamp( Normal:Dot( VelForward ) ,-1,1) )
|
||||
|
||||
local Fx = math.cos( Ax ) * Velocity:Length()
|
||||
|
||||
local NewVelocity = Velocity - Normal * math.abs( Fx )
|
||||
|
||||
return NewVelocity
|
||||
end
|
||||
|
||||
function ENT:VectorSplitNormal( Normal, Velocity )
|
||||
return math.cos( math.acos( math.Clamp( Normal:Dot( Velocity:GetNormalized() ) ,-1,1) ) ) * Velocity:Length()
|
||||
end
|
||||
|
||||
function ENT:AngleBetweenNormal( Dir1, Dir2 )
|
||||
return math.deg( math.acos( math.Clamp( Dir1:Dot( Dir2 ) ,-1,1) ) )
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "LBaseGunner"
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
local Dir = self._ai_look_dir or self.VectorNull
|
||||
|
||||
self:SetNWAimVector( Dir )
|
||||
|
||||
return Dir
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
if self._AimVectorUnlocked then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
end
|
||||
end
|
||||
|
||||
return Driver:GetAimVector()
|
||||
else
|
||||
return self:GetForward()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Target = self:AIGetTarget( EntTable )
|
||||
|
||||
if not IsValid( Target ) then
|
||||
EntTable._ai_look_dir = self:GetForward()
|
||||
EntTable._AIFireInput = false
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local TargetPos = Target:GetPos()
|
||||
|
||||
if EntTable._AIFireInput then
|
||||
local T = CurTime() * 0.5 + self:EntIndex()
|
||||
local X = math.cos( T ) * 32
|
||||
local Y = math.sin( T ) * 32
|
||||
local Z = math.sin( math.cos( T / 0.5 ) * math.pi ) * 32
|
||||
TargetPos = Target:LocalToWorld( Target:OBBCenter() + Vector(X,Y,Z) )
|
||||
end
|
||||
|
||||
EntTable._ai_look_dir = (TargetPos - self:GetPos()):GetNormalized()
|
||||
|
||||
local StartPos = self:GetPos()
|
||||
|
||||
local trace = util.TraceHull( {
|
||||
start = StartPos,
|
||||
endpos = (StartPos + EntTable._ai_look_dir * 50000),
|
||||
mins = Vector( -50, -50, -50 ),
|
||||
maxs = Vector( 50, 50, 50 ),
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
if not self:AIHasWeapon( self:GetSelectedWeapon() ) then
|
||||
EntTable._AIFireInput = false
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if IsValid( trace.Entity ) and trace.Entity.GetAITEAM then
|
||||
EntTable._AIFireInput = (trace.Entity:GetAITEAM() ~= self:GetAITEAM() or trace.Entity:GetAITEAM() == 0)
|
||||
else
|
||||
EntTable._AIFireInput = true
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:AIGetTarget( EntTable )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return NULL end
|
||||
|
||||
if Base:GetAI() then
|
||||
return Base:AIGetTarget()
|
||||
end
|
||||
|
||||
if not isnumber( EntTable.ViewConeAdd ) then
|
||||
EntTable.ViewConeAdd = math.min( 100 + math.abs( Base:WorldToLocalAngles( self:GetAngles() ).y ), 360 )
|
||||
end
|
||||
|
||||
return Base:AIGetTarget( EntTable.ViewConeAdd )
|
||||
end
|
||||
|
||||
function ENT:AITargetInFront( ent, range )
|
||||
local Base = self:GetVehicle()
|
||||
|
||||
if not IsValid( Base ) then return NULL end
|
||||
|
||||
return Base:AITargetInFront( ent, range )
|
||||
end
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
|
||||
ENT._lvsSmoothFreeLook = 0
|
||||
|
||||
function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
local ViewPosL = pod:WorldToLocal( pos )
|
||||
|
||||
local view = {}
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = self:GetAngles()
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
local Zoom = ply:lvsKeyDown( "ZOOM" )
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
if FreeLook then
|
||||
view.angles = pod:LocalToWorldAngles( ply:EyeAngles() )
|
||||
end
|
||||
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local Dividor = math.abs( velL.x )
|
||||
local SideForce = math.Clamp( velL.y / Dividor, -1, 1)
|
||||
local UpForce = math.Clamp( velL.z / Dividor, -1, 1)
|
||||
|
||||
local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1))
|
||||
if Zoom then
|
||||
ViewPunch = Vector(0,0,0)
|
||||
end
|
||||
|
||||
pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0)
|
||||
pod._lerpPos = pos
|
||||
|
||||
view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5
|
||||
view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1
|
||||
view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
pod._lerpPos = pod._lerpPos or self:GetPos()
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
if FreeLook then
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local SideForce = math.Clamp(velL.y / 10,-250,250)
|
||||
local UpForce = math.Clamp(velL.z / 10,-250,250)
|
||||
|
||||
pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0)
|
||||
pod._lerpPos = self:LocalToWorld( pod._lerpPosL )
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:LocalToWorldAngles( Angle(0,180,0) )
|
||||
else
|
||||
local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) )
|
||||
|
||||
local Sub = TargetPos - pod._lerpPos
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100
|
||||
|
||||
pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12)
|
||||
pod._lerpPosL = self:WorldToLocal( pod._lerpPos )
|
||||
|
||||
local vel = self:GetVelocity()
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:GetAngles()
|
||||
end
|
||||
|
||||
view.origin = view.origin + ViewPosL
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 )
|
||||
|
||||
self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10
|
||||
|
||||
local view = {}
|
||||
view.origin = pos
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle()
|
||||
|
||||
if cvarFocus >= 1 then
|
||||
view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() )
|
||||
else
|
||||
view.angles.r = 0
|
||||
end
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight())
|
||||
local WallOffset = 4
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = view.origin,
|
||||
endpos = TargetOrigin,
|
||||
filter = function( e )
|
||||
local c = e:GetClass()
|
||||
local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS
|
||||
|
||||
return collide
|
||||
end,
|
||||
mins = Vector( -WallOffset, -WallOffset, -WallOffset ),
|
||||
maxs = Vector( WallOffset, WallOffset, WallOffset ),
|
||||
} )
|
||||
|
||||
view.origin = tr.HitPos
|
||||
|
||||
if tr.Hit and not tr.StartSolid then
|
||||
view.origin = view.origin + tr.HitNormal * WallOffset
|
||||
end
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
|
||||
return pos, angles, fov
|
||||
end
|
||||
|
||||
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
if ply:lvsMouseAim() then
|
||||
return self:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
|
||||
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
|
||||
|
||||
if self:GetDriverSeat() == pod then
|
||||
return self:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
ENT.FlyByAdvance = 0
|
||||
|
||||
function ENT:FlyByThink()
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if ply:lvsGetVehicle() == self then EntTable.OldApproaching = false return end
|
||||
|
||||
local ViewEnt = ply:GetViewEntity()
|
||||
|
||||
if not IsValid( ViewEnt ) then return end
|
||||
|
||||
local Time = CurTime()
|
||||
|
||||
if (EntTable._nextflyby or 0) > Time then return end
|
||||
|
||||
EntTable._nextflyby = Time + 0.1
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
|
||||
if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end
|
||||
|
||||
local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance
|
||||
local ToPlayer = Sub:GetNormalized()
|
||||
local VelDir = Vel:GetNormalized()
|
||||
|
||||
local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) )
|
||||
|
||||
local Approaching = ApproachAngle < 80
|
||||
|
||||
if Approaching ~= EntTable.OldApproaching then
|
||||
EntTable.OldApproaching = Approaching
|
||||
|
||||
if Approaching then
|
||||
self:StopFlyBy()
|
||||
else
|
||||
self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnFlyBy( Pitch )
|
||||
if not self.FlyBySound then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound )
|
||||
EntTable.flybysnd:SetSoundLevel( 95 )
|
||||
EntTable.flybysnd:PlayEx( 1, Pitch )
|
||||
end
|
||||
|
||||
function ENT:StopFlyBy()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.flybysnd then return end
|
||||
|
||||
EntTable.flybysnd:Stop()
|
||||
EntTable.flybysnd = nil
|
||||
end
|
||||
@@ -0,0 +1,168 @@
|
||||
ENT.IconEngine = Material( "lvs/engine.png" )
|
||||
|
||||
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0)
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local hX = X + W - H * 0.5
|
||||
local hY = Y + H * 0.25 + H * 0.25
|
||||
|
||||
surface.SetMaterial( self.IconEngine )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
|
||||
self:LVSDrawCircle( hX, hY, H * 0.35, self:GetThrustPercent() )
|
||||
end
|
||||
|
||||
function ENT:LVSPreHudPaint( X, Y, ply )
|
||||
return true
|
||||
end
|
||||
|
||||
ENT.Hud = true
|
||||
ENT.HudThirdPerson = false
|
||||
ENT.HudGradient = Material("gui/center_gradient")
|
||||
ENT.HudColor = Color(255,255,255)
|
||||
|
||||
function ENT:PaintHeliFlightInfo( X, Y, ply, Pos2D )
|
||||
local Roll = self:GetAngles().r
|
||||
|
||||
surface.SetDrawColor(0,0,0,40)
|
||||
surface.SetMaterial( self.HudGradient )
|
||||
surface.DrawTexturedRect( Pos2D.x - 270, Pos2D.y - 10, 140, 20 )
|
||||
surface.DrawTexturedRect( Pos2D.x + 130, Pos2D.y - 10, 140, 20 )
|
||||
|
||||
local X = math.cos( math.rad( Roll ) )
|
||||
local Y = math.sin( math.rad( Roll ) )
|
||||
|
||||
surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 )
|
||||
surface.DrawLine( Pos2D.x + X * 50, Pos2D.y + Y * 50, Pos2D.x + X * 125, Pos2D.y + Y * 125 )
|
||||
surface.DrawLine( Pos2D.x - X * 50, Pos2D.y - Y * 50, Pos2D.x - X * 125, Pos2D.y - Y * 125 )
|
||||
|
||||
surface.DrawLine( Pos2D.x + 125, Pos2D.y, Pos2D.x + 130, Pos2D.y + 5 )
|
||||
surface.DrawLine( Pos2D.x + 125, Pos2D.y, Pos2D.x + 130, Pos2D.y - 5 )
|
||||
surface.DrawLine( Pos2D.x - 125, Pos2D.y, Pos2D.x - 130, Pos2D.y + 5 )
|
||||
surface.DrawLine( Pos2D.x - 125, Pos2D.y, Pos2D.x - 130, Pos2D.y - 5 )
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 80 )
|
||||
surface.DrawLine( Pos2D.x + X * 50 + 1, Pos2D.y + Y * 50 + 1, Pos2D.x + X * 125 + 1, Pos2D.y + Y * 125 + 1 )
|
||||
surface.DrawLine( Pos2D.x - X * 50 + 1, Pos2D.y - Y * 50 + 1, Pos2D.x - X * 125 + 1, Pos2D.y - Y * 125 + 1 )
|
||||
|
||||
surface.DrawLine( Pos2D.x + 126, Pos2D.y + 1, Pos2D.x + 131, Pos2D.y + 6 )
|
||||
surface.DrawLine( Pos2D.x + 126, Pos2D.y + 1, Pos2D.x + 131, Pos2D.y - 4 )
|
||||
surface.DrawLine( Pos2D.x - 126, Pos2D.y + 1, Pos2D.x - 129, Pos2D.y + 6 )
|
||||
surface.DrawLine( Pos2D.x - 126, Pos2D.y + 1, Pos2D.x - 129, Pos2D.y - 4 )
|
||||
|
||||
local X = math.cos( math.rad( Roll + 45 ) )
|
||||
local Y = math.sin( math.rad( Roll + 45 ) )
|
||||
surface.DrawLine( Pos2D.x + X * 30 - 1, Pos2D.y + Y * 30 + 1, Pos2D.x + X * 60 - 1, Pos2D.y + Y * 60 + 1 )
|
||||
local X = math.cos( math.rad( Roll + 135 ) )
|
||||
local Y = math.sin( math.rad( Roll + 135 ) )
|
||||
surface.DrawLine( Pos2D.x + X * 30 + 1, Pos2D.y + Y * 30 + 1, Pos2D.x + X * 60 + 1, Pos2D.y + Y * 60 + 1 )
|
||||
|
||||
surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 )
|
||||
local X = math.cos( math.rad( Roll + 45 ) )
|
||||
local Y = math.sin( math.rad( Roll + 45 ) )
|
||||
surface.DrawLine( Pos2D.x + X * 30, Pos2D.y + Y * 30, Pos2D.x + X * 60, Pos2D.y + Y * 60 )
|
||||
local X = math.cos( math.rad( Roll + 135 ) )
|
||||
local Y = math.sin( math.rad( Roll + 135 ) )
|
||||
surface.DrawLine( Pos2D.x + X * 30, Pos2D.y + Y * 30, Pos2D.x + X * 60, Pos2D.y + Y * 60 )
|
||||
|
||||
local Pitch = -self:GetAngles().p
|
||||
|
||||
surface.DrawLine( Pos2D.x - 220, Pos2D.y, Pos2D.x - 180, Pos2D.y )
|
||||
surface.DrawLine( Pos2D.x + 220, Pos2D.y, Pos2D.x + 180, Pos2D.y )
|
||||
surface.SetDrawColor( 0, 0, 0, 80 )
|
||||
surface.DrawLine( Pos2D.x - 220, Pos2D.y + 1, Pos2D.x - 180, Pos2D.y + 1 )
|
||||
surface.DrawLine( Pos2D.x + 220, Pos2D.y + 1, Pos2D.x + 180, Pos2D.y + 1 )
|
||||
|
||||
draw.DrawText( math.Round( Pitch, 2 ), "LVS_FONT_PANEL", Pos2D.x - 175, Pos2D.y - 7, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ), TEXT_ALIGN_LEFT )
|
||||
draw.DrawText( math.Round( Pitch, 2 ), "LVS_FONT_PANEL", Pos2D.x + 175, Pos2D.y - 7, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ), TEXT_ALIGN_RIGHT )
|
||||
|
||||
for i = -180, 180 do
|
||||
local Y = -i * 10 + Pitch * 10
|
||||
|
||||
local absN = math.abs( i )
|
||||
|
||||
local IsTen = absN == math.Round( absN / 10, 0 ) * 10
|
||||
|
||||
local SizeX = IsTen and 20 or 10
|
||||
|
||||
local Alpha = 255 - (math.min( math.abs( Y ) / 200,1) ^ 2) * 255
|
||||
|
||||
if Alpha <= 0 then continue end
|
||||
|
||||
surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.75 )
|
||||
surface.DrawLine(Pos2D.x - 200 - SizeX, Pos2D.y + Y, Pos2D.x - 200, Pos2D.y + Y )
|
||||
surface.DrawLine(Pos2D.x + 200 + SizeX, Pos2D.y + Y, Pos2D.x + 200, Pos2D.y + Y )
|
||||
surface.SetDrawColor( 0, 0, 0, Alpha * 0.25 )
|
||||
surface.DrawLine(Pos2D.x - 200 - SizeX, Pos2D.y + Y + 1, Pos2D.x - 200, Pos2D.y + Y + 1 )
|
||||
surface.DrawLine(Pos2D.x + 200 + SizeX, Pos2D.y + Y + 1, Pos2D.x + 200, Pos2D.y + Y + 1)
|
||||
|
||||
if not IsTen then continue end
|
||||
|
||||
draw.DrawText( i, "LVS_FONT_HUD", Pos2D.x - 225, Pos2D.y + Y - 10, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.5 ), TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( i, "LVS_FONT_HUD", Pos2D.x + 225, Pos2D.y + Y - 10, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.5 ), TEXT_ALIGN_LEFT )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaint( X, Y, ply )
|
||||
if not self:LVSPreHudPaint( X, Y, ply ) then return end
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen()
|
||||
local HitPilot = self:GetEyeTrace().HitPos:ToScreen()
|
||||
|
||||
local pod = ply:GetVehicle()
|
||||
|
||||
if self.Hud then
|
||||
if not pod:GetThirdPersonMode() then
|
||||
self:PaintHeliFlightInfo( X, Y, ply, HitPilot )
|
||||
end
|
||||
end
|
||||
|
||||
if self.HudThirdPerson then
|
||||
if pod:GetThirdPersonMode() then
|
||||
self:PaintHeliFlightInfo( X, Y, ply, HitPilot )
|
||||
end
|
||||
end
|
||||
|
||||
self:PaintCrosshairCenter( HitPlane )
|
||||
self:PaintCrosshairOuter( HitPilot )
|
||||
|
||||
if ply:lvsMouseAim() and not ply:lvsKeyDown( "FREELOOK" ) then
|
||||
self:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
end
|
||||
|
||||
self:LVSPaintHitMarker( HitPilot )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintDirectInput( Pos2D )
|
||||
self:PaintCrosshairCenter( Pos2D )
|
||||
self:PaintCrosshairOuter( Pos2D )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0)
|
||||
local Len = Sub:Length()
|
||||
local Dir = Sub:GetNormalized()
|
||||
|
||||
surface.SetDrawColor( 255, 255, 255, 100 )
|
||||
if Len > 20 then
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 )
|
||||
|
||||
-- shadow
|
||||
surface.SetDrawColor( 0, 0, 0, 50 )
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
include("shared.lua")
|
||||
include("cl_camera.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("cl_hud.lua")
|
||||
include("cl_flyby.lua")
|
||||
@@ -0,0 +1,210 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_camera.lua" )
|
||||
AddCSLuaFile( "sh_camera_eyetrace.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "cl_flyby.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_ai.lua")
|
||||
include("sv_mouseaim.lua")
|
||||
include("sv_components.lua")
|
||||
include("sv_engine.lua")
|
||||
include("sv_vehiclespecific.lua")
|
||||
include("sv_damage_extension.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
self.COL_GROUP_OLD = self:GetCollisionGroup()
|
||||
self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS )
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE )
|
||||
end
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement, phys, deltatime )
|
||||
if not IsValid( phys ) then
|
||||
phys = self:GetPhysicsObject()
|
||||
end
|
||||
|
||||
if not deltatime then
|
||||
deltatime = FrameTime()
|
||||
end
|
||||
|
||||
local LocalAngles = self:WorldToLocalAngles( TargetAngle )
|
||||
|
||||
local LocalAngPitch = LocalAngles.p
|
||||
local LocalAngYaw = LocalAngles.y
|
||||
local LocalAngRoll = LocalAngles.r
|
||||
|
||||
local TargetForward = TargetAngle:Forward()
|
||||
local Forward = self:GetForward()
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
local AngVel = phys:GetAngleVelocity()
|
||||
|
||||
local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 50,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 )
|
||||
local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 50,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 )
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local Pitch = math.Clamp(-LocalAngPitch / 10 + SmoothPitch,-1,1)
|
||||
local Yaw = math.Clamp(-LocalAngYaw + SmoothYaw,-1,1)
|
||||
local Roll = math.Clamp(LocalAngRoll / 100 + Yaw * 0.25 + math.Clamp(VelL.y / math.abs( VelL.x ) / 10,-0.25,0.25),-1,1)
|
||||
|
||||
if OverrideRoll ~= 0 then
|
||||
Roll = math.Clamp( self:WorldToLocalAngles( Angle( Ang.p, Ang.y, OverrideRoll * 60 ) ).r / 40,-1,1)
|
||||
end
|
||||
|
||||
self.Roll = Roll
|
||||
|
||||
if OverridePitch and OverridePitch ~= 0 then
|
||||
Pitch = OverridePitch
|
||||
end
|
||||
|
||||
if OverrideYaw and OverrideYaw ~= 0 then
|
||||
Yaw = OverrideYaw
|
||||
end
|
||||
|
||||
self:SetSteer( Vector( Roll, -Pitch, -Yaw) )
|
||||
end
|
||||
|
||||
function ENT:OnSkyCollide( data, PhysObj )
|
||||
|
||||
local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * 50
|
||||
|
||||
PhysObj:SetVelocityInstantaneous( NewVelocity )
|
||||
PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
if self:GetEngineActive() then phys:Wake() end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local WorldGravity = self:GetWorldGravity()
|
||||
local WorldUp = self:GetWorldUp()
|
||||
|
||||
local Up = self:GetUp()
|
||||
local Left = -self:GetRight()
|
||||
|
||||
local Mul = self:GetThrottle()
|
||||
local InputThrust = math.min( self:GetThrust() , 0 ) * EntTable.ThrustDown + math.max( self:GetThrust(), 0 ) * EntTable.ThrustUp
|
||||
|
||||
if self:HitGround() and InputThrust <= 0 then
|
||||
Mul = 0
|
||||
end
|
||||
|
||||
-- mouse aim needs to run at high speed.
|
||||
if self:GetAI() then
|
||||
self:CalcAIMove( phys, deltatime )
|
||||
else
|
||||
local ply = self:GetDriver()
|
||||
if IsValid( ply ) and ply:lvsMouseAim() then
|
||||
self:PlayerMouseAim( ply, phys, deltatime )
|
||||
end
|
||||
end
|
||||
|
||||
local Steer = self:GetSteer()
|
||||
|
||||
local Vel = phys:GetVelocity()
|
||||
local VelL = phys:WorldToLocal( phys:GetPos() + Vel )
|
||||
|
||||
local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90
|
||||
|
||||
local GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * (WorldGravity / 100) * (math.min( Vector(VelL.x,VelL.y,0):Length() / EntTable.MaxVelocity,1) ^ 2)
|
||||
|
||||
local Pitch = math.Clamp(Steer.y,-1,1) * EntTable.TurnRatePitch
|
||||
local Yaw = math.Clamp(Steer.z + GravityYaw * 0.25 * EntTable.GravityTurnRateYaw,-1,1) * EntTable.TurnRateYaw * 60
|
||||
local Roll = math.Clamp(Steer.x,-1,1) * 1.5 * EntTable.TurnRateRoll
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
local FadeMul = (1 - math.max( (45 - self:AngleBetweenNormal( WorldUp, Up )) / 45,0)) ^ 2
|
||||
local ThrustMul = math.Clamp( 1 - (Vel:Length() / EntTable.MaxVelocity) * FadeMul, 0, 1 )
|
||||
|
||||
local Thrust = self:LocalToWorldAngles( Angle(Pitch,0,Roll) ):Up() * (WorldGravity + InputThrust * 500 * ThrustMul) * Mul
|
||||
|
||||
local Force, ForceAng = phys:CalculateForceOffset( Thrust, phys:LocalToWorld( phys:GetMassCenter() ) + self:GetUp() * 1000 )
|
||||
|
||||
local ForceLinear = (Force - Vel * 0.15 * EntTable.ForceLinearDampingMultiplier) * Mul
|
||||
local ForceAngle = (ForceAng + (Vector(0,0,Yaw) - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250) * Mul
|
||||
|
||||
if EntTable._SteerOverride then
|
||||
ForceAngle.z = (EntTable._SteerOverrideMove * math.max( self:GetThrust() * 2, 1 ) * 100 - phys:GetAngleVelocity().z) * Mul
|
||||
end
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_GLOBAL_ACCELERATION )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate )
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
|
||||
function ENT:ApproachThrust( New, Delta )
|
||||
if not Delta then
|
||||
Delta = FrameTime()
|
||||
end
|
||||
|
||||
local Cur = self:GetThrust()
|
||||
|
||||
self:SetThrust( Cur + (New - Cur) * Delta * self.ThrustRate * 2.5 )
|
||||
end
|
||||
|
||||
function ENT:CalcThrust( KeyUp, KeyDown, Delta )
|
||||
if self:HitGround() and not KeyUp then
|
||||
self:ApproachThrust( -1, Delta )
|
||||
self.Roll = self:GetAngles().r
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local Up = KeyUp and 1 or 0
|
||||
local Down = KeyDown and -1 or 0
|
||||
|
||||
self:ApproachThrust( Up + Down, Delta )
|
||||
end
|
||||
|
||||
function ENT:CalcHover( InputLeft, InputRight, InputUp, InputDown, ThrustUp, ThrustDown, PhysObj, deltatime )
|
||||
if not IsValid( PhysObj ) then
|
||||
PhysObj = self:GetPhysicsObject()
|
||||
end
|
||||
|
||||
local VelL = PhysObj:WorldToLocal( PhysObj:GetPos() + PhysObj:GetVelocity() )
|
||||
local AngVel = PhysObj:GetAngleVelocity()
|
||||
|
||||
local KeyLeft = InputLeft and 60 or 0
|
||||
local KeyRight = InputRight and 60 or 0
|
||||
local KeyPitchUp = InputUp and 60 or 0
|
||||
local KeyPitchDown = InputDown and 60 or 0
|
||||
|
||||
local Pitch = KeyPitchDown - KeyPitchUp
|
||||
local Roll = KeyRight - KeyLeft
|
||||
|
||||
if (Pitch + Roll) == 0 then
|
||||
Pitch = math.Clamp(-VelL.x / 200,-1,1) * 60
|
||||
Roll = math.Clamp(VelL.y / 250,-1,1) * 60
|
||||
end
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
local Steer = self:GetSteer()
|
||||
Steer.x = math.Clamp( Roll - Ang.r - AngVel.x,-1,1)
|
||||
Steer.y = math.Clamp( Pitch - Ang.p - AngVel.y,-1,1)
|
||||
|
||||
self:SetSteer( Steer )
|
||||
|
||||
self.Roll = Ang.r
|
||||
|
||||
if ThrustUp or ThrustDown then
|
||||
self:CalcThrust( ThrustUp, ThrustDown, deltatime )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:ApproachThrust( math.Clamp(-VelL.z / 100,-1,1), deltatime )
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
function ENT:GetEyeTrace( trace_forward )
|
||||
local startpos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
startpos = pod:LocalToWorld( pod:OBBCenter() )
|
||||
end
|
||||
|
||||
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
|
||||
|
||||
local data = {
|
||||
start = startpos,
|
||||
endpos = (startpos + AimVector * 50000),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( data )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetAIAimVector()
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if not IsValid( Driver ) then return self:GetForward() end
|
||||
|
||||
if not Driver:lvsMouseAim() then
|
||||
if Driver:lvsKeyDown( "FREELOOK" ) then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
if pod:GetThirdPersonMode() then
|
||||
return -self:GetForward()
|
||||
else
|
||||
return Driver:GetAimVector()
|
||||
end
|
||||
else
|
||||
return self:GetForward()
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
else
|
||||
return Driver:EyeAngles():Forward()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,153 @@
|
||||
|
||||
ENT.Base = "lvs_base"
|
||||
|
||||
ENT.PrintName = "[LVS] Base Helicopter"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.MaxVelocity = 2150
|
||||
|
||||
ENT.ThrustUp = 1
|
||||
ENT.ThrustDown = 0.8
|
||||
ENT.ThrustRate = 1
|
||||
|
||||
ENT.ThrottleRateUp = 0.2
|
||||
ENT.ThrottleRateDown = 0.2
|
||||
|
||||
ENT.TurnRatePitch = 1
|
||||
ENT.TurnRateYaw = 1
|
||||
ENT.TurnRateRoll = 1
|
||||
|
||||
ENT.GravityTurnRateYaw = 1
|
||||
|
||||
ENT.ForceLinearDampingMultiplier = 1.5
|
||||
|
||||
ENT.ForceAngleMultiplier = 1
|
||||
ENT.ForceAngleDampingMultiplier = 1
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:CreateBaseDT()
|
||||
|
||||
self:AddDT( "Vector", "Steer" )
|
||||
self:AddDT( "Vector", "AIAimVector" )
|
||||
self:AddDT( "Float", "Throttle" )
|
||||
self:AddDT( "Float", "NWThrust" )
|
||||
end
|
||||
|
||||
function ENT:PlayerDirectInput( ply, cmd )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local KeyLeft = ply:lvsKeyDown( "-ROLL_HELI" )
|
||||
local KeyRight = ply:lvsKeyDown( "+ROLL_HELI" )
|
||||
local KeyPitchUp = ply:lvsKeyDown( "+PITCH_HELI" )
|
||||
local KeyPitchDown = ply:lvsKeyDown( "-PITCH_HELI" )
|
||||
local KeyRollRight = ply:lvsKeyDown( "+YAW_HELI" )
|
||||
local KeyRollLeft = ply:lvsKeyDown( "-YAW_HELI" )
|
||||
|
||||
local MouseX = cmd:GetMouseX()
|
||||
local MouseY = cmd:GetMouseY()
|
||||
|
||||
if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then
|
||||
MouseX = 0
|
||||
MouseY = 0
|
||||
else
|
||||
ply:SetEyeAngles( Angle(0,90,0) )
|
||||
end
|
||||
|
||||
local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity()
|
||||
|
||||
if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end
|
||||
if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end
|
||||
if KeyRollRight or KeyRollLeft then
|
||||
local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0)
|
||||
|
||||
MouseX = (NewX / SensX) * ReturnDelta
|
||||
end
|
||||
|
||||
local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 )
|
||||
|
||||
local Cur = self:GetSteer()
|
||||
|
||||
local Rate = Delta * 3 * ReturnDelta
|
||||
|
||||
local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0)
|
||||
|
||||
local Target = New + Input * Delta * 0.8
|
||||
|
||||
local Fx = math.Clamp( Target.x, -1, 1 )
|
||||
local Fy = math.Clamp( Target.y, -1, 1 )
|
||||
|
||||
local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0)
|
||||
local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3)
|
||||
|
||||
local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1)
|
||||
|
||||
self:SetSteer( F )
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
if ply:lvsKeyDown( "HELI_HOVER" ) then
|
||||
self:CalcHover( ply:lvsKeyDown( "-YAW_HELI" ), ply:lvsKeyDown( "+YAW_HELI" ), KeyPitchUp, KeyPitchDown, ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ) )
|
||||
|
||||
self.ResetSteer = true
|
||||
|
||||
else
|
||||
if self.ResetSteer then
|
||||
self.ResetSteer = nil
|
||||
|
||||
self:SetSteer( Vector(0,0,0) )
|
||||
end
|
||||
|
||||
self:CalcThrust( ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ) )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
if SERVER then
|
||||
local KeyJump = ply:lvsKeyDown( "VSPEC" )
|
||||
|
||||
if self._lvsOldKeyJump ~= KeyJump then
|
||||
self._lvsOldKeyJump = KeyJump
|
||||
|
||||
if KeyJump then
|
||||
self:ToggleVehicleSpecific()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not ply:lvsMouseAim() then
|
||||
self:PlayerDirectInput( ply, cmd )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetThrust( New )
|
||||
if self:GetEngineActive() then
|
||||
self:SetNWThrust( math.Clamp(New,-1,1) )
|
||||
else
|
||||
self:SetNWThrust( 0 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetThrust()
|
||||
return self:GetNWThrust()
|
||||
end
|
||||
|
||||
function ENT:GetThrustPercent()
|
||||
return math.Clamp(0.5 * self:GetThrottle() + self:GetThrust() * 0.5,0,1)
|
||||
end
|
||||
|
||||
function ENT:GetThrustStrenght()
|
||||
return (1 - (self:GetVelocity():Length() / self.MaxVelocity)) * self:GetThrustPercent()
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "helicopter"
|
||||
end
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
local RangerLength = 15000
|
||||
|
||||
local mySpeed = self:GetVelocity():Length()
|
||||
local myRadius = self:BoundingRadius()
|
||||
local myPos = self:GetPos()
|
||||
local myDir = self:GetForward()
|
||||
|
||||
local MinDist = 600 + mySpeed
|
||||
local StartPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local TraceFilter = self:GetCrosshairFilterEnts()
|
||||
|
||||
local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } )
|
||||
local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } )
|
||||
local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } )
|
||||
|
||||
local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } )
|
||||
local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } )
|
||||
local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } )
|
||||
|
||||
local cAvoid = Vector(0,0,0)
|
||||
|
||||
for _, v in pairs( LVS:GetVehicles() ) do
|
||||
if v == self then continue end
|
||||
|
||||
local theirRadius = v:BoundingRadius()
|
||||
local Sub = (myPos - v:GetPos())
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
if Dist < (theirRadius + myRadius + 200) then
|
||||
if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then
|
||||
cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8
|
||||
local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8
|
||||
|
||||
local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist
|
||||
local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist
|
||||
|
||||
local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist
|
||||
local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist
|
||||
|
||||
local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist
|
||||
local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist
|
||||
|
||||
local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist
|
||||
local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist
|
||||
|
||||
local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10
|
||||
|
||||
local alt = (StartPos - TraceDown.HitPos):Length()
|
||||
local ceiling = (StartPos - TraceUp.HitPos):Length()
|
||||
|
||||
self._AIFireInput = false
|
||||
|
||||
local Target = self:AIGetTarget()
|
||||
|
||||
if alt < 600 or ceiling < 600 then
|
||||
if ceiling < 600 then
|
||||
TargetPos.z = StartPos.z - 2000
|
||||
else
|
||||
TargetPos.z = StartPos.z + 2000
|
||||
end
|
||||
else
|
||||
if IsValid( self:GetHardLockTarget() ) then
|
||||
TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8
|
||||
else
|
||||
if IsValid( Target ) then
|
||||
local HisRadius = Target:BoundingRadius()
|
||||
local HisPos = Target:GetPos() + Vector(0,0,600)
|
||||
|
||||
TargetPos = HisPos + (myPos - HisPos):GetNormalized() * (myRadius + HisRadius + 500) + cAvoid * 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._lvsTargetPos = TargetPos
|
||||
|
||||
if IsValid( Target ) then
|
||||
TargetPos = Target:LocalToWorld( Target:OBBCenter() )
|
||||
|
||||
if self:AITargetInFront( Target, 65 ) then
|
||||
local tr = self:GetEyeTrace()
|
||||
|
||||
if (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true then
|
||||
local CurHeat = self:GetNWHeat()
|
||||
local CurWeapon = self:GetSelectedWeapon()
|
||||
|
||||
if CurWeapon > 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
else
|
||||
if CurHeat > 0.9 then
|
||||
if CurWeapon == 1 and self:AIHasWeapon( 2 ) then
|
||||
self:AISelectWeapon( 2 )
|
||||
|
||||
elseif CurWeapon == 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
else
|
||||
if CurHeat == 0 and math.cos( CurTime() ) > 0 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._AIFireInput = true
|
||||
end
|
||||
else
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
self:SetAIAimVector( (TargetPos - myPos):GetNormalized() )
|
||||
end
|
||||
|
||||
function ENT:CalcAIMove( PhysObj, deltatime )
|
||||
if not self._lvsTargetPos then return end
|
||||
|
||||
local StartPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local Target = self:AIGetTarget()
|
||||
|
||||
local TargetPos = self._lvsTargetPos
|
||||
|
||||
local VelL = PhysObj:WorldToLocal( PhysObj:GetPos() + PhysObj:GetVelocity() )
|
||||
local AngVel = PhysObj:GetAngleVelocity()
|
||||
|
||||
local LPos = self:WorldToLocal( TargetPos )
|
||||
|
||||
local Pitch = math.Clamp(LPos.x * 0.01 - VelL.x * 0.01,-1,1) * 40
|
||||
local Yaw = ((IsValid( Target ) and Target:GetPos() or TargetPos) - StartPos):Angle().y
|
||||
local Roll = math.Clamp(VelL.y * 0.01,-1,1) * 40
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
local Steer = self:GetSteer()
|
||||
Steer.x = math.Clamp( Roll - Ang.r - AngVel.x,-1,1)
|
||||
Steer.y = math.Clamp( Pitch - Ang.p - AngVel.y,-1,1)
|
||||
Steer.z = math.Clamp( self:WorldToLocalAngles( Angle(0,Yaw,0) ).y / 10,-1,1)
|
||||
|
||||
self:SetSteer( Steer )
|
||||
self:SetThrust( math.Clamp( LPos.z - VelL.z,-1,1) )
|
||||
end
|
||||
|
||||
function ENT:AISelectWeapon( ID )
|
||||
if ID == self:GetSelectedWeapon() then return end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (self._nextAISwitchWeapon or 0) > T then return end
|
||||
|
||||
self._nextAISwitchWeapon = T + math.random(3,6)
|
||||
|
||||
self:SelectWeapon( ID )
|
||||
end
|
||||
|
||||
function ENT:OnAITakeDamage( dmginfo )
|
||||
local attacker = dmginfo:GetAttacker()
|
||||
|
||||
if not IsValid( attacker ) then return end
|
||||
|
||||
if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then
|
||||
self:SetHardLockTarget( attacker )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetHardLockTarget( target )
|
||||
if not self:IsEnemy( target ) then return end
|
||||
|
||||
self._HardLockTarget = target
|
||||
self._HardLockTime = CurTime() + 4
|
||||
end
|
||||
|
||||
function ENT:GetHardLockTarget()
|
||||
if (self._HardLockTime or 0) < CurTime() then return NULL end
|
||||
|
||||
return self._HardLockTarget
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
function ENT:AddEngineSound( pos )
|
||||
local Engine = ents.Create( "lvs_helicopter_engine" )
|
||||
|
||||
if not IsValid( Engine ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Engine:SetPos( self:LocalToWorld( pos ) )
|
||||
Engine:SetAngles( self:GetAngles() )
|
||||
Engine:Spawn()
|
||||
Engine:Activate()
|
||||
Engine:SetParent( self )
|
||||
Engine:SetBase( self )
|
||||
|
||||
self:DeleteOnRemove( Engine )
|
||||
|
||||
self:TransferCPPI( Engine )
|
||||
|
||||
return Engine
|
||||
end
|
||||
|
||||
function ENT:AddRotor( pos, ang, radius, speed )
|
||||
if not pos or not ang or not radius or not speed then return end
|
||||
|
||||
local Rotor = ents.Create( "lvs_helicopter_rotor" )
|
||||
|
||||
if not IsValid( Rotor ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create rotor entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Rotor:SetPos( self:LocalToWorld( pos ) )
|
||||
Rotor:SetAngles( self:LocalToWorldAngles( ang ) )
|
||||
Rotor:Spawn()
|
||||
Rotor:Activate()
|
||||
Rotor:SetParent( self )
|
||||
Rotor:SetBase( self )
|
||||
Rotor:SetRadius( radius )
|
||||
Rotor:SetSpeed( speed )
|
||||
|
||||
self:DeleteOnRemove( Rotor )
|
||||
|
||||
self:TransferCPPI( Rotor )
|
||||
|
||||
return Rotor
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
function ENT:StartDestroyTimer()
|
||||
if self._DestroyTimerStarted then return end
|
||||
|
||||
self._DestroyTimerStarted = true
|
||||
|
||||
timer.Simple( self:GetAI() and 5 or 60, function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
self.MarkForDestruction = true
|
||||
end )
|
||||
end
|
||||
|
||||
function ENT:DestroySteering( movevalue )
|
||||
if self._SteerOverride then return end
|
||||
|
||||
self._SteerOverride = true
|
||||
self._SteerOverrideMove = (movevalue or 1)
|
||||
self:StartDestroyTimer()
|
||||
end
|
||||
|
||||
function ENT:DestroyEngine()
|
||||
if self._EngineDestroyed then return end
|
||||
|
||||
self._EngineDestroyed = true
|
||||
|
||||
self:TurnOffEngine()
|
||||
|
||||
self:SetThrottle( 0 )
|
||||
self:SetThrust( 0 )
|
||||
|
||||
self:StartDestroyTimer()
|
||||
end
|
||||
|
||||
function ENT:IsSteeringDestroyed()
|
||||
return self._SteerOverride == true
|
||||
end
|
||||
|
||||
function ENT:IsEngineDestroyed()
|
||||
return self._EngineDestroyed == true
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
function ENT:CalcThrottle()
|
||||
if not self:GetEngineActive() then
|
||||
|
||||
if self:GetThrottle() ~= 0 then self:SetThrottle( 0 ) end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local Cur = self:GetThrottle()
|
||||
local New = self._StopEngine and 0 or 1
|
||||
|
||||
if self:IsDestroyed() then New = 0 end
|
||||
|
||||
if Cur == New and New == 0 then self:TurnOffEngine() return end
|
||||
|
||||
self:SetThrottle( Cur + math.Clamp( (New - Cur), -self.ThrottleRateDown * Delta, self.ThrottleRateUp * Delta ) )
|
||||
end
|
||||
|
||||
function ENT:HandleStart()
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if IsValid( Driver ) then
|
||||
local KeyReload = Driver:lvsKeyDown( "ENGINE" )
|
||||
|
||||
if self.OldKeyReload ~= KeyReload then
|
||||
self.OldKeyReload = KeyReload
|
||||
|
||||
if KeyReload then
|
||||
self:ToggleEngine()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:CalcThrottle()
|
||||
end
|
||||
|
||||
function ENT:ToggleEngine()
|
||||
if self:GetEngineActive() and not self._StopEngine then
|
||||
self:StopEngine()
|
||||
else
|
||||
self:StartEngine()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:StartEngine()
|
||||
if not self:IsEngineStartAllowed() then return end
|
||||
if self._EngineDestroyed then return end
|
||||
|
||||
if self:GetEngineActive() then
|
||||
self._StopEngine = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:PhysWake()
|
||||
|
||||
self:SetEngineActive( true )
|
||||
self:OnEngineActiveChanged( true )
|
||||
|
||||
self._StopEngine = nil
|
||||
end
|
||||
|
||||
function ENT:StopEngine()
|
||||
if self._StopEngine then return end
|
||||
|
||||
self._StopEngine = true
|
||||
self:OnEngineActiveChanged( false )
|
||||
|
||||
if self:WaterLevel() >= self.WaterLevelAutoStop then
|
||||
self:TurnOffEngine()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TurnOffEngine()
|
||||
if not self:GetEngineActive() then return end
|
||||
|
||||
self:SetEngineActive( false )
|
||||
|
||||
self._StopEngine = nil
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
function ENT:PlayerMouseAim( ply, phys, deltatime )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local PitchUp = ply:lvsKeyDown( "+PITCH_HELI" )
|
||||
local PitchDown = ply:lvsKeyDown( "-PITCH_HELI" )
|
||||
local YawRight = ply:lvsKeyDown( "+YAW_HELI" )
|
||||
local YawLeft = ply:lvsKeyDown( "-YAW_HELI" )
|
||||
local RollRight = ply:lvsKeyDown( "+ROLL_HELI" )
|
||||
local RollLeft = ply:lvsKeyDown( "-ROLL_HELI" )
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
|
||||
local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() )
|
||||
|
||||
if FreeLook then
|
||||
if isangle( self.StoredEyeAngles ) then
|
||||
EyeAngles = self.StoredEyeAngles
|
||||
end
|
||||
else
|
||||
self.StoredEyeAngles = EyeAngles
|
||||
end
|
||||
|
||||
local OverridePitch = 0
|
||||
local OverrideYaw = 0
|
||||
local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0)
|
||||
|
||||
if PitchUp or PitchDown then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0)
|
||||
end
|
||||
|
||||
if YawRight or YawLeft then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0)
|
||||
end
|
||||
|
||||
self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook, phys, deltatime )
|
||||
|
||||
if ply:lvsKeyDown( "HELI_HOVER" ) then
|
||||
self:CalcHover( RollLeft, RollRight, PitchUp, PitchDown, ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ), phys, deltatime )
|
||||
|
||||
self.ResetSteer = true
|
||||
|
||||
else
|
||||
if self.ResetSteer then
|
||||
self.ResetSteer = nil
|
||||
|
||||
self:SetSteer( Vector(0,0,0) )
|
||||
end
|
||||
|
||||
self:CalcThrust( ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ), deltatime )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
function ENT:ToggleVehicleSpecific()
|
||||
self._VSPEC = not self._VSPEC
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:EnableVehicleSpecific()
|
||||
if self._VSPEC then return end
|
||||
|
||||
self._VSPEC = true
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:DisableVehicleSpecific()
|
||||
if not self._VSPEC then return end
|
||||
|
||||
self._VSPEC = false
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:OnVehicleSpecificToggled( IsActive )
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
include("shared.lua")
|
||||
|
||||
local up = Vector(0,0,1)
|
||||
local down = Vector(0,0,-1)
|
||||
|
||||
function ENT:DoVehicleFX()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Vel = self:GetVelocity():Length()
|
||||
|
||||
if EntTable._WindSFX then
|
||||
EntTable._WindSFX:ChangeVolume( math.Clamp( (Vel * EntTable.GroundEffectsMultiplier - 1200) / 2800,0,1 ), 0.25 )
|
||||
end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (EntTable.nextFX or 0) < T then
|
||||
EntTable.nextFX = T + 0.01
|
||||
|
||||
self:DoAdvancedWaterEffects( EntTable, Vel )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoAdvancedWaterEffects( EntTable, Vel )
|
||||
local pos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local traceSky = util.TraceLine( {
|
||||
start = pos,
|
||||
endpos = pos + up * 50000,
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
local traceWater = util.TraceLine( {
|
||||
start = traceSky.HitPos,
|
||||
endpos = pos + down * 50000,
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
mask = MASK_WATER
|
||||
} )
|
||||
|
||||
local traceSoil = util.TraceLine( {
|
||||
start = traceSky.HitPos,
|
||||
endpos = pos + down * 50000,
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
mask = MASK_ALL
|
||||
} )
|
||||
|
||||
if traceSoil.HitPos.z > traceWater.HitPos.z then
|
||||
if EntTable._WaterSFX then
|
||||
EntTable._WaterSFX:ChangeVolume( 0, 0.25 )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if EntTable._WaterSFX then
|
||||
EntTable._WaterSFX:ChangeVolume( math.min(Vel / EntTable.MaxVelocity,1) ^ 2, 0.25 )
|
||||
EntTable._WaterSFX:ChangePitch( math.Clamp((Vel / EntTable.MaxVelocity) * 50,80,150), 0.5 )
|
||||
end
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( traceWater.HitPos )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_physics_water_advanced", effectdata )
|
||||
end
|
||||
@@ -0,0 +1,167 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_controls.lua")
|
||||
include("sv_components.lua")
|
||||
|
||||
function ENT:EnableManualTransmission()
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:DisableManualTransmission()
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:SpawnFunction( ply, tr, ClassName )
|
||||
|
||||
local startpos = ply:GetShootPos()
|
||||
local endpos = startpos + ply:GetAimVector() * 10000
|
||||
|
||||
local waterTrace = util.TraceLine( {
|
||||
start = startpos,
|
||||
endpos = endpos,
|
||||
mask = MASK_WATER,
|
||||
filter = ply
|
||||
} )
|
||||
|
||||
if waterTrace.Hit and ((waterTrace.HitPos - startpos):Length() < (tr.HitPos - startpos):Length()) then
|
||||
tr = waterTrace
|
||||
end
|
||||
|
||||
if not tr.Hit then return end
|
||||
|
||||
local ent = ents.Create( ClassName )
|
||||
ent:StoreCPPI( ply )
|
||||
ent:SetPos( tr.HitPos + tr.HitNormal * ent.SpawnNormalOffset )
|
||||
ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) )
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return ent
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
end
|
||||
|
||||
function ENT:GetEnginePos()
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if IsValid( Engine ) then return Engine:GetPos() end
|
||||
|
||||
return self:LocalToWorld( self:OBBCenter() )
|
||||
end
|
||||
|
||||
local up = Vector(0,0,1)
|
||||
local down = Vector(0,0,-1)
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
|
||||
if self:GetEngineActive() then phys:Wake() end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local pos = self:GetEnginePos()
|
||||
|
||||
local traceSky = util.TraceLine( {
|
||||
start = pos,
|
||||
endpos = pos + up * 50000,
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
} )
|
||||
|
||||
local traceData = {
|
||||
start = traceSky.HitPos,
|
||||
endpos = pos + down * 50000,
|
||||
filter = self:GetCrosshairFilterEnts()
|
||||
}
|
||||
|
||||
pos = phys:LocalToWorld( phys:GetMassCenter() )
|
||||
|
||||
local traceSoil = util.TraceLine( traceData )
|
||||
traceData.mask = MASK_WATER
|
||||
local traceWater = util.TraceLine( traceData )
|
||||
|
||||
local EngineInWater = traceWater.Hit
|
||||
local Engine = self:GetEngine()
|
||||
if IsValid( Engine ) then
|
||||
traceData.start = Engine:GetPos()
|
||||
traceData.endpos = Engine:LocalToWorld( Vector(0,0,-EntTable.EngineSplashDistance) )
|
||||
|
||||
EngineInWater = util.TraceLine( traceData ).Hit
|
||||
end
|
||||
|
||||
if not EngineInWater then return vector_origin, vector_origin, SIM_NOTHING end
|
||||
|
||||
local BuoyancyForce = math.min( math.max( traceWater.HitPos.z - pos.z + EntTable.FloatHeight, 0 ), 10 )
|
||||
|
||||
local Grav = physenv.GetGravity()
|
||||
local Vel = phys:GetVelocity()
|
||||
local AngVel = phys:GetAngleVelocity()
|
||||
|
||||
local mul = BuoyancyForce / 10
|
||||
local invmul = math.Clamp( 1 - mul, 0, 1 )
|
||||
|
||||
local Force = (-Grav + Vector(0,0,-Vel.z * invmul * EntTable.FloatForce)) * mul
|
||||
local ForcePos = pos + self:GetUp() * BuoyancyForce
|
||||
|
||||
local ForceLinear, ForceAngle = phys:CalculateForceOffset( Force, ForcePos )
|
||||
|
||||
ForceAngle = (ForceAngle - AngVel * invmul * 2) * 15 * EntTable.ForceAngleMultiplier
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + Vel )
|
||||
|
||||
local Thrust = self:GetThrust()
|
||||
local ThrustStrength = math.abs( self:GetThrustStrenght() )
|
||||
|
||||
local SteerInput = self:GetSteer()
|
||||
local Steer = SteerInput * math.Clamp( ThrustStrength + math.min(math.abs( VelL.x ) / math.min( EntTable.MaxVelocity, EntTable.MaxVelocityReverse ), 1 ), 0, 1 )
|
||||
|
||||
local Brake = self:GetBrake()
|
||||
|
||||
if Brake > 0 then
|
||||
Steer = Steer * -Brake
|
||||
end
|
||||
|
||||
local Pitch = -(math.max( math.cos( CurTime() * EntTable.FloatWaveFrequency + self:EntIndex() * 1337 ), 0 ) * VelL.x * 0.25 * EntTable.FloatWaveIntensity + Thrust * 0.25 * math.Clamp( VelL.x / EntTable.MaxVelocity,0,1) * EntTable.FloatThrottleIntensity)
|
||||
local Yaw = Steer * EntTable.TurnForceYaw
|
||||
local Roll = - AngVel.x * 5 - Steer * EntTable.TurnForceRoll
|
||||
|
||||
if math.abs( SteerInput ) < 1 and BuoyancyForce > 0 and not self:IsPlayerHolding() then
|
||||
Yaw = Yaw - AngVel.z * math.max( ((1 - math.abs( SteerInput )) ^ 2) * 100 - 100 * ThrustStrength, 1 )
|
||||
end
|
||||
|
||||
ForceLinear:Add( self:GetForward() * Thrust + self:GetRight() * VelL.y )
|
||||
ForceAngle:Add( Vector(Roll,Pitch,Yaw) )
|
||||
|
||||
local FloatExp = math.max( self:GetUp().z, 0 ) ^ EntTable.FloatExponent
|
||||
|
||||
ForceLinear:Mul( FloatExp )
|
||||
ForceAngle:Mul( FloatExp )
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_GLOBAL_ACCELERATION )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate )
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle )
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return end
|
||||
|
||||
local ang = pod:GetAngles()
|
||||
ang:RotateAroundAxis( self:GetUp(), 90 )
|
||||
|
||||
local Forward = ang:Right()
|
||||
local View = pod:WorldToLocalAngles( TargetAngle ):Forward()
|
||||
|
||||
local Reversed = false
|
||||
if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then
|
||||
Reversed = self:GetReverse()
|
||||
end
|
||||
|
||||
local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle
|
||||
|
||||
local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer ))
|
||||
|
||||
self:SetSteer( Steer )
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
ENT.Base = "lvs_base_wheeldrive"
|
||||
|
||||
ENT.PrintName = "[LVS] Base Boat"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.MaxHealth = 600
|
||||
ENT.MaxHealthEngine = 50
|
||||
ENT.MaxHealthFuelTank = 10
|
||||
|
||||
ENT.EngineIdleRPM = 1000
|
||||
ENT.EngineMaxRPM = 6000
|
||||
|
||||
ENT.EngineSplash = false
|
||||
ENT.EngineSplashStartSize = 50
|
||||
ENT.EngineSplashEndSize = 200
|
||||
ENT.EngineSplashVelocity = 500
|
||||
ENT.EngineSplashVelocityRandomAdd = 200
|
||||
ENT.EngineSplashThrowAngle = 0
|
||||
ENT.EngineSplashDistance = 100
|
||||
|
||||
ENT.DeleteOnExplode = true
|
||||
|
||||
ENT.lvsAllowEngineTool = false
|
||||
ENT.lvsShowInSpawner = false
|
||||
|
||||
ENT.AllowSuperCharger = false
|
||||
ENT.AllowTurbo = false
|
||||
|
||||
ENT.FloatHeight = 0
|
||||
ENT.FloatForce = 20
|
||||
ENT.FloatWaveFrequency = 5
|
||||
ENT.FloatExponent = 2
|
||||
ENT.FloatWaveIntensity = 1
|
||||
|
||||
ENT.FloatThrottleIntensity = 1
|
||||
|
||||
ENT.TurnRate = 5
|
||||
ENT.TurnForceYaw = 600
|
||||
ENT.TurnForceRoll = 400
|
||||
|
||||
ENT.MaxThrust = 1000
|
||||
|
||||
ENT.MaxVelocity = 1000
|
||||
ENT.MaxVelocityReverse = 350
|
||||
|
||||
ENT.MinVelocityAutoBrake = 200
|
||||
|
||||
ENT.ForceLinearMultiplier = 1
|
||||
ENT.ForceAngleMultiplier = 1
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "boat"
|
||||
end
|
||||
|
||||
function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed )
|
||||
ply:SetPlaybackRate( 1 )
|
||||
|
||||
if CLIENT then
|
||||
if ply == self:GetDriver() then
|
||||
ply:SetPoseParameter( "vehicle_steer", -self:GetSteer() )
|
||||
ply:InvalidateBoneCache()
|
||||
end
|
||||
|
||||
GAMEMODE:GrabEarAnimation( ply )
|
||||
GAMEMODE:MouthMoveAnimation( ply )
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:GetThrust()
|
||||
return self:GetThrustStrenght() * self.MaxThrust
|
||||
end
|
||||
|
||||
function ENT:GetThrustStrenght()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local DesiredVelocity = EntTable.MaxVelocity * self:GetThrottle() - EntTable.MaxVelocityReverse * self:GetBrake()
|
||||
|
||||
if DesiredVelocity == 0 and math.abs( VelL.x ) > EntTable.MinVelocityAutoBrake then
|
||||
return 0
|
||||
end
|
||||
|
||||
return math.Clamp((DesiredVelocity - VelL.x) / EntTable.MaxVelocity,-1,1)
|
||||
end
|
||||
|
||||
function ENT:GetGear()
|
||||
return -1
|
||||
end
|
||||
|
||||
function ENT:IsManualTransmission()
|
||||
return false
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
function ENT:AddEngine( pos, ang, mins, maxs )
|
||||
if IsValid( self:GetEngine() ) then return end
|
||||
|
||||
ang = ang or angle_zero
|
||||
mins = mins or Vector(-10,-10,-10)
|
||||
maxs = maxs or Vector(10,10,10)
|
||||
|
||||
local Engine = ents.Create( "lvs_powerboat_engine" )
|
||||
|
||||
if not IsValid( Engine ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Engine:SetPos( self:LocalToWorld( pos ) )
|
||||
Engine:SetAngles( self:LocalToWorldAngles( ang ) )
|
||||
Engine:Spawn()
|
||||
Engine:Activate()
|
||||
Engine:SetParent( self )
|
||||
Engine:SetBase( self )
|
||||
Engine:SetMaxHP( self.MaxHealthEngine )
|
||||
Engine:SetHP( self.MaxHealthEngine )
|
||||
|
||||
self:SetEngine( Engine )
|
||||
|
||||
self:DeleteOnRemove( Engine )
|
||||
|
||||
self:TransferCPPI( Engine )
|
||||
|
||||
debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 0, 255, 255, 255 ) )
|
||||
|
||||
self:AddDS( {
|
||||
pos = pos,
|
||||
ang = ang,
|
||||
mins = mins,
|
||||
maxs = maxs,
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if not IsValid( Engine ) then return end
|
||||
|
||||
Engine:TakeTransmittedDamage( dmginfo )
|
||||
|
||||
if not Engine:GetDestroyed() then
|
||||
dmginfo:ScaleDamage( 0.25 )
|
||||
end
|
||||
end
|
||||
} )
|
||||
|
||||
return Engine
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
function ENT:CalcMouseSteer( ply )
|
||||
self:ApproachTargetAngle( ply:EyeAngles() )
|
||||
end
|
||||
|
||||
function ENT:CalcSteer( ply, Steer )
|
||||
local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" )
|
||||
local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" )
|
||||
|
||||
local Steer = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0)
|
||||
|
||||
self:LerpSteer( Steer )
|
||||
end
|
||||
|
||||
function ENT:LerpSteer( Steer )
|
||||
local Rate = FrameTime() * self.TurnRate
|
||||
local Cur = self:GetSteer()
|
||||
local New = Cur + math.Clamp(Steer - Cur,-Rate,Rate)
|
||||
|
||||
self:SetSteer( New )
|
||||
end
|
||||
|
||||
function ENT:CalcThrottle( ply )
|
||||
local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" )
|
||||
local KeyBrake = ply:lvsKeyDown( "CAR_BRAKE" )
|
||||
|
||||
if KeyBrake then
|
||||
KeyThrottle = false
|
||||
end
|
||||
|
||||
local ThrottleValue = ply:lvsKeyDown( "CAR_THROTTLE_MOD" ) and self:GetMaxThrottle() or 0.5
|
||||
local Throttle = KeyThrottle and ThrottleValue or 0
|
||||
local Brake = KeyBrake and ThrottleValue or 0
|
||||
|
||||
self:LerpThrottle( Throttle )
|
||||
self:LerpBrake( Brake )
|
||||
end
|
||||
|
||||
function ENT:CalcHandbrake( ply )
|
||||
end
|
||||
|
||||
function ENT:CalcTransmission( ply, T )
|
||||
end
|
||||
|
||||
function ENT:OnHandbrakeActiveChanged( Active )
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
include("shared.lua")
|
||||
@@ -0,0 +1,106 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
include("shared.lua")
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement )
|
||||
|
||||
if self:GetAI() then self:SetAIAimVector( self:WorldToLocalAngles( TargetAngle ):Forward() ) end
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
local AngY = self:WorldToLocalAngles( Angle(TargetAngle.p,TargetAngle.y,Ang.r ) )
|
||||
local AngP = self:WorldToLocalAngles( Angle(TargetAngle.p,Ang.y,Ang.r ) )
|
||||
|
||||
local LocalAngPitch = AngP.p
|
||||
local LocalAngYaw = AngY.y
|
||||
|
||||
local TargetForward = TargetAngle:Forward()
|
||||
local Forward = self:GetForward()
|
||||
|
||||
local AngVel = self:GetPhysicsObject():GetAngleVelocity()
|
||||
|
||||
local Pitch = math.Clamp( -LocalAngPitch / 22 * math.Clamp( math.abs( AngY.y ) / 0.9,1,10), -1, 1 ) + math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch )
|
||||
local Yaw = math.Clamp( -LocalAngYaw / 22,-1,1) + math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw )
|
||||
|
||||
if OverridePitch and OverridePitch ~= 0 then
|
||||
Pitch = OverridePitch
|
||||
end
|
||||
|
||||
if OverrideYaw and OverrideYaw ~= 0 then
|
||||
Yaw = OverrideYaw
|
||||
end
|
||||
|
||||
self:SetSteer( Vector( math.Clamp(Yaw,-1,1), -math.Clamp(Pitch,-1,1), 0) )
|
||||
end
|
||||
|
||||
function ENT:CalcAero( phys, deltatime )
|
||||
if self:GetAI() then
|
||||
if self._lvsAITargetAng then
|
||||
self:ApproachTargetAngle( self._lvsAITargetAng )
|
||||
end
|
||||
end
|
||||
|
||||
local Steer = self:GetSteer()
|
||||
|
||||
local Forward = self:GetForward()
|
||||
local Left = -self:GetRight()
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
local VelForward = Vel:GetNormalized()
|
||||
|
||||
local GravityPitch = 0
|
||||
local GravityYaw = 0
|
||||
|
||||
-- crash bebehavior
|
||||
if self:IsDestroyed() then
|
||||
local WorldGravity = self:GetWorldGravity()
|
||||
local WorldUp = self:GetWorldUp()
|
||||
|
||||
local Up = self:GetUp()
|
||||
|
||||
Steer = phys:GetAngleVelocity() / 200
|
||||
|
||||
local PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90
|
||||
local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90
|
||||
|
||||
local GravMul = WorldGravity / 600
|
||||
|
||||
GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul
|
||||
GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul
|
||||
|
||||
if not phys:IsGravityEnabled() then
|
||||
phys:EnableGravity( true )
|
||||
end
|
||||
end
|
||||
|
||||
local Ang = self:GetAngles()
|
||||
|
||||
Steer.y = math.max(math.abs( Steer.y ) - math.min( math.abs( Ang.p / 90 ), 1 ) ^ 3,0) * self:Sign( Steer.y )
|
||||
|
||||
if Ang.p > self.MaxPitch and Steer.y > 0 then
|
||||
Steer.y = 0
|
||||
end
|
||||
|
||||
if Ang.p < -self.MaxPitch and Steer.y < 0 then
|
||||
Steer.y = 0
|
||||
end
|
||||
|
||||
local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * self.TurnRatePitch * 4
|
||||
local Yaw = math.Clamp(-Steer.x + GravityYaw,-1,1) * self.TurnRateYaw * 4
|
||||
|
||||
local VtolMove = self:GetVtolMove()
|
||||
|
||||
local RollMul = (math.abs(self:AngleBetweenNormal( Vector(0,0,1) , self:GetForward() ) - 90) / 90) ^ 2
|
||||
local TargetRoll = (Steer.x * math.min( self:GetThrottle() ^ 2, 1 ) - (VtolMove.y / self.ThrustVtol) * 0.25) * 45 * (1 - RollMul)
|
||||
|
||||
local Roll = math.Clamp( self:WorldToLocalAngles( Angle( 0, Ang.y, TargetRoll ) ).r / 90,-1,1) * self.TurnRateRoll * 16
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + Vel )
|
||||
|
||||
local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - math.abs( Steer.y ), 0 ) / 90) * 0.3
|
||||
local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - math.abs( Steer.z ), 0 ) / 90) * 0.15
|
||||
|
||||
local Move = Vector( (VtolMove.x < 0) and -math.min(VelL.x * 0.15,0) or 0, -VelL.y * MulY, -VelL.z * MulZ ) + VtolMove
|
||||
|
||||
return Move, Vector( Roll, Pitch, Yaw )
|
||||
end
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
ENT.Base = "lvs_base_starfighter"
|
||||
|
||||
ENT.PrintName = "[LVS] Base Gunship"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.ThrustVtol = 30
|
||||
ENT.ThrustRateVtol = 2
|
||||
|
||||
ENT.MaxPitch = 60
|
||||
|
||||
ENT.DisableBallistics = true
|
||||
|
||||
function ENT:CalcVtolThrottle( ply, cmd )
|
||||
local Delta = FrameTime()
|
||||
|
||||
local ThrottleZero = self:GetThrottle() <= 0
|
||||
|
||||
local VtolX = ThrottleZero and (ply:lvsKeyDown( "-VTOL_X_SF" ) and -1 or 0) or 0
|
||||
local VtolY = ((ply:lvsKeyDown( "+VTOL_Y_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Y_SF" ) and 1 or 0))
|
||||
|
||||
if ply:lvsMouseAim() then
|
||||
VtolY = math.Clamp( VtolY + ((ply:lvsKeyDown( "-ROLL_SF" ) and 1 or 0) - (ply:lvsKeyDown( "+ROLL_SF" ) and 1 or 0)), -1 , 1)
|
||||
end
|
||||
|
||||
VtolY = VtolY * math.max( 1 - self:GetThrottle() ^ 2, 0 )
|
||||
local VtolZ = ((ply:lvsKeyDown( "+VTOL_Z_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Z_SF" ) and 1 or 0))
|
||||
|
||||
local DesiredVtol = Vector(VtolX,VtolY,VtolZ)
|
||||
local NewVtolMove = self:GetNWVtolMove() + (DesiredVtol - self:GetNWVtolMove()) * self.ThrustRateVtol * Delta
|
||||
|
||||
if not ThrottleZero or self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x > 100 then
|
||||
NewVtolMove.x = 0
|
||||
end
|
||||
|
||||
self:SetVtolMove( NewVtolMove )
|
||||
end
|
||||
|
||||
function ENT:GetVtolMove()
|
||||
if self:GetEngineActive() and not self:GetAI() then
|
||||
return self:GetNWVtolMove() * self.ThrustVtol
|
||||
else
|
||||
return Vector(0,0,0)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PlayerDirectInput( ply, cmd )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local KeyLeft = ply:lvsKeyDown( "-ROLL_SF" )
|
||||
local KeyRight = ply:lvsKeyDown( "+ROLL_SF" )
|
||||
local KeyPitchUp = ply:lvsKeyDown( "+PITCH_SF" )
|
||||
local KeyPitchDown = ply:lvsKeyDown( "-PITCH_SF" )
|
||||
|
||||
local MouseX = cmd:GetMouseX()
|
||||
local MouseY = cmd:GetMouseY()
|
||||
|
||||
if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then
|
||||
MouseX = 0
|
||||
MouseY = 0
|
||||
else
|
||||
ply:SetEyeAngles( Angle(0,90,0) )
|
||||
end
|
||||
|
||||
local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity()
|
||||
|
||||
if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end
|
||||
if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end
|
||||
if KeyLeft then MouseX = -(10 / SensX) * ReturnDelta end
|
||||
if KeyRight then MouseX = (10 / SensX) * ReturnDelta end
|
||||
|
||||
local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 )
|
||||
|
||||
local Cur = self:GetSteer()
|
||||
|
||||
local Rate = Delta * 3 * ReturnDelta
|
||||
|
||||
local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0)
|
||||
|
||||
local Target = New + Input * Delta * 0.8
|
||||
|
||||
local Fx = math.Clamp( Target.x, -1, 1 )
|
||||
local Fy = math.Clamp( Target.y, -1, 1 )
|
||||
|
||||
local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0)
|
||||
local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3)
|
||||
|
||||
local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1)
|
||||
|
||||
self:SetSteer( F )
|
||||
end
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
if SERVER then
|
||||
local KeyJump = ply:lvsKeyDown( "VSPEC" )
|
||||
|
||||
if self._lvsOldKeyJump ~= KeyJump then
|
||||
self._lvsOldKeyJump = KeyJump
|
||||
|
||||
if KeyJump then
|
||||
self:ToggleVehicleSpecific()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ply:lvsMouseAim() then
|
||||
if SERVER then
|
||||
self:PlayerMouseAim( ply, cmd )
|
||||
end
|
||||
else
|
||||
self:PlayerDirectInput( ply, cmd )
|
||||
end
|
||||
|
||||
self:CalcThrottle( ply, cmd )
|
||||
self:CalcVtolThrottle( ply, cmd )
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "repulsorlift"
|
||||
end
|
||||
@@ -0,0 +1,158 @@
|
||||
|
||||
ENT._lvsSmoothFreeLook = 0
|
||||
|
||||
function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
local ViewPosL = pod:WorldToLocal( pos )
|
||||
|
||||
local view = {}
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = self:GetAngles()
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
local Zoom = ply:lvsKeyDown( "ZOOM" )
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
if FreeLook then
|
||||
view.angles = pod:LocalToWorldAngles( ply:EyeAngles() )
|
||||
end
|
||||
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local Dividor = math.abs( velL.x )
|
||||
local SideForce = math.Clamp( velL.y / Dividor, -1, 1)
|
||||
local UpForce = math.Clamp( velL.z / Dividor, -1, 1)
|
||||
|
||||
local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1))
|
||||
if Zoom then
|
||||
ViewPunch = Vector(0,0,0)
|
||||
end
|
||||
|
||||
pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0)
|
||||
pod._lerpPos = pos
|
||||
|
||||
view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5
|
||||
view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1
|
||||
view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
pod._lerpPos = pod._lerpPos or self:GetPos()
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
if FreeLook then
|
||||
local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() )
|
||||
|
||||
local SideForce = math.Clamp(velL.y / 10,-250,250)
|
||||
local UpForce = math.Clamp(velL.z / 10,-250,250)
|
||||
|
||||
pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0)
|
||||
pod._lerpPos = self:LocalToWorld( pod._lerpPosL )
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:LocalToWorldAngles( Angle(0,180,0) )
|
||||
else
|
||||
local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) )
|
||||
|
||||
local Sub = TargetPos - pod._lerpPos
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100
|
||||
|
||||
pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12)
|
||||
pod._lerpPosL = self:WorldToLocal( pod._lerpPos )
|
||||
|
||||
local vel = self:GetVelocity()
|
||||
|
||||
view.origin = pod._lerpPos
|
||||
view.angles = self:GetAngles()
|
||||
end
|
||||
|
||||
view.origin = view.origin + ViewPosL
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 )
|
||||
|
||||
self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10
|
||||
|
||||
local view = {}
|
||||
view.origin = pos
|
||||
view.fov = fov
|
||||
view.drawviewer = true
|
||||
view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle()
|
||||
|
||||
if cvarFocus >= 1 then
|
||||
view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() )
|
||||
else
|
||||
view.angles.r = 0
|
||||
end
|
||||
|
||||
if not pod:GetThirdPersonMode() then
|
||||
|
||||
view.drawviewer = false
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
local radius = 550
|
||||
radius = radius + radius * pod:GetCameraDistance()
|
||||
|
||||
local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight())
|
||||
local WallOffset = 4
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = view.origin,
|
||||
endpos = TargetOrigin,
|
||||
filter = function( e )
|
||||
local c = e:GetClass()
|
||||
local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS
|
||||
|
||||
return collide
|
||||
end,
|
||||
mins = Vector( -WallOffset, -WallOffset, -WallOffset ),
|
||||
maxs = Vector( WallOffset, WallOffset, WallOffset ),
|
||||
} )
|
||||
|
||||
view.origin = tr.HitPos
|
||||
|
||||
if tr.Hit and not tr.StartSolid then
|
||||
view.origin = view.origin + tr.HitNormal * WallOffset
|
||||
end
|
||||
|
||||
return view
|
||||
end
|
||||
|
||||
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
|
||||
return pos, angles, fov
|
||||
end
|
||||
|
||||
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
if ply:lvsMouseAim() then
|
||||
return self:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
|
||||
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
|
||||
|
||||
if self:GetDriverSeat() == pod then
|
||||
return self:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
function ENT:OnDestroyed()
|
||||
if not self.DeathSound then return end
|
||||
|
||||
if self:GetVelocity():Length() <= self.MaxVelocity * 0.5 then return end
|
||||
|
||||
self._sndDeath = CreateSound( self, self.DeathSound )
|
||||
self._sndDeath:SetSoundLevel( 125 )
|
||||
self._sndDeath:PlayEx( 1, 50 + 50 * self:CalcDoppler( LocalPlayer() ) )
|
||||
end
|
||||
|
||||
function ENT:StopDeathSound()
|
||||
if not self._sndDeath then return end
|
||||
|
||||
self._sndDeath:Stop()
|
||||
end
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
ENT.FlyByAdvance = 0
|
||||
|
||||
function ENT:FlyByThink()
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if ply:lvsGetVehicle() == self then self.OldApproaching = false return end
|
||||
|
||||
local ViewEnt = ply:GetViewEntity()
|
||||
|
||||
if not IsValid( ViewEnt ) then return end
|
||||
|
||||
local Time = CurTime()
|
||||
|
||||
if (EntTable._nextflyby or 0) > Time then return end
|
||||
|
||||
EntTable._nextflyby = Time + 0.1
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
|
||||
if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end
|
||||
|
||||
local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance
|
||||
local ToPlayer = Sub:GetNormalized()
|
||||
local VelDir = Vel:GetNormalized()
|
||||
|
||||
local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) )
|
||||
|
||||
local Approaching = ApproachAngle < 80
|
||||
|
||||
if Approaching ~= EntTable.OldApproaching then
|
||||
EntTable.OldApproaching = Approaching
|
||||
|
||||
if Approaching then
|
||||
self:StopFlyBy()
|
||||
else
|
||||
self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnFlyBy( Pitch )
|
||||
if not self.FlyBySound then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound )
|
||||
EntTable.flybysnd:SetSoundLevel( 95 )
|
||||
EntTable.flybysnd:PlayEx( 1, Pitch )
|
||||
end
|
||||
|
||||
function ENT:StopFlyBy()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.flybysnd then return end
|
||||
|
||||
EntTable.flybysnd:Stop()
|
||||
EntTable.flybysnd = nil
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
ENT.IconEngine = Material( "lvs/engine.png" )
|
||||
|
||||
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0)
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
local Col = Throttle <= 1 and color_white or Color(0,0,0,255)
|
||||
local hX = X + W - H * 0.5
|
||||
local hY = Y + H * 0.25 + H * 0.25
|
||||
|
||||
surface.SetMaterial( self.IconEngine )
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
|
||||
self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) )
|
||||
|
||||
if Throttle > 1 then
|
||||
draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:LVSPreHudPaint( X, Y, ply )
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaint( X, Y, ply )
|
||||
if not self:LVSPreHudPaint( X, Y, ply ) then return end
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen()
|
||||
local HitPilot = self:GetEyeTrace().HitPos:ToScreen()
|
||||
|
||||
self:PaintCrosshairCenter( HitPlane )
|
||||
self:PaintCrosshairOuter( HitPilot )
|
||||
|
||||
if ply:lvsMouseAim() and not ply:lvsKeyDown( "FREELOOK" ) then
|
||||
self:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
end
|
||||
|
||||
self:LVSPaintHitMarker( HitPilot )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintDirectInput( Pos2D )
|
||||
self:PaintCrosshairCenter( Pos2D )
|
||||
self:PaintCrosshairOuter( Pos2D )
|
||||
end
|
||||
|
||||
function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot )
|
||||
local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0)
|
||||
local Len = Sub:Length()
|
||||
local Dir = Sub:GetNormalized()
|
||||
|
||||
surface.SetDrawColor( 255, 255, 255, 100 )
|
||||
if Len > 20 then
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 )
|
||||
|
||||
-- shadow
|
||||
surface.SetDrawColor( 0, 0, 0, 50 )
|
||||
surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
include("shared.lua")
|
||||
include("cl_camera.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("cl_hud.lua")
|
||||
include("cl_flyby.lua")
|
||||
include("cl_deathsound.lua")
|
||||
|
||||
ENT.TrailAlpha = 25
|
||||
|
||||
DEFINE_BASECLASS( "lvs_base" )
|
||||
|
||||
function ENT:Think()
|
||||
BaseClass.Think( self )
|
||||
|
||||
self.EFxScale = self.EFxScale and (self.EFxScale - self.EFxScale * RealFrameTime()) or 0
|
||||
|
||||
self:CalcOnThrottle()
|
||||
end
|
||||
|
||||
function ENT:CalcOnThrottle()
|
||||
if not self:GetEngineActive() then
|
||||
self._oldOnTHR = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
|
||||
if self._oldOnTHR ~= Throttle then
|
||||
if self._oldOnTHR == 0 and Throttle > 0 then
|
||||
self._IsAccelerating = true
|
||||
end
|
||||
|
||||
if Throttle > (self._oldOnTHR or 0) then
|
||||
self._IsAccelerating = true
|
||||
else
|
||||
self._IsAccelerating = false
|
||||
end
|
||||
|
||||
if self._oldOnTHR == 1 then
|
||||
self:StopBoost()
|
||||
end
|
||||
|
||||
self._oldOnTHR = Throttle
|
||||
end
|
||||
|
||||
if self._oldAccelerating ~= self._IsAccelerating then
|
||||
self._oldAccelerating = self._IsAccelerating
|
||||
|
||||
if not self._IsAccelerating then return end
|
||||
|
||||
self:StartBoost()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:StartBoost()
|
||||
local T = CurTime()
|
||||
|
||||
if (self._NextSND or 0) > T then return end
|
||||
|
||||
self._NextSND = T + 1
|
||||
|
||||
self.EFxScale = 100
|
||||
|
||||
self:OnStartBoost()
|
||||
end
|
||||
|
||||
function ENT:StopBoost()
|
||||
local T = CurTime()
|
||||
|
||||
if (self._NextSND or 0) > T then return end
|
||||
|
||||
self._NextSND = T + 1
|
||||
|
||||
self:OnStopBoost()
|
||||
end
|
||||
|
||||
function ENT:GetBoost()
|
||||
return (self.EFxScale or 0)
|
||||
end
|
||||
|
||||
function ENT:OnStartBoost()
|
||||
end
|
||||
|
||||
function ENT:OnStopBoost()
|
||||
end
|
||||
@@ -0,0 +1,210 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_camera.lua" )
|
||||
AddCSLuaFile( "sh_camera_eyetrace.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "cl_flyby.lua" )
|
||||
AddCSLuaFile( "cl_deathsound.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_ai.lua")
|
||||
include("sv_mouseaim.lua")
|
||||
include("sv_components.lua")
|
||||
include("sv_vehiclespecific.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
|
||||
DEFINE_BASECLASS( "lvs_base" )
|
||||
|
||||
function ENT:OnDriverChanged( Old, New, VehicleIsActive )
|
||||
|
||||
if not VehicleIsActive and self:GetThrottle() == 0 then
|
||||
self:SetSteer( vector_origin )
|
||||
self:SetVtolMove( vector_origin )
|
||||
end
|
||||
|
||||
self:OnPassengerChanged( Old, New, 1 )
|
||||
end
|
||||
|
||||
function ENT:StartEngine()
|
||||
if self:GetEngineActive() or not self:IsEngineStartAllowed() then return end
|
||||
|
||||
self:GetPhysicsObject():EnableGravity( false )
|
||||
|
||||
BaseClass.StartEngine( self )
|
||||
end
|
||||
|
||||
function ENT:StopEngine()
|
||||
if not self:GetEngineActive() then return end
|
||||
|
||||
self:GetPhysicsObject():EnableGravity( true )
|
||||
|
||||
BaseClass.StopEngine( self )
|
||||
end
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
self.COL_GROUP_OLD = self:GetCollisionGroup()
|
||||
self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS )
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE )
|
||||
end
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement )
|
||||
local LocalAngles = self:WorldToLocalAngles( TargetAngle )
|
||||
|
||||
if self:GetAI() then self:SetAIAimVector( LocalAngles:Forward() ) end
|
||||
|
||||
local LocalAngPitch = LocalAngles.p
|
||||
local LocalAngYaw = LocalAngles.y
|
||||
local LocalAngRoll = LocalAngles.r
|
||||
|
||||
local TargetForward = TargetAngle:Forward()
|
||||
local Forward = self:GetForward()
|
||||
|
||||
local AngDiff = math.deg( math.acos( math.Clamp( Forward:Dot( TargetForward ) ,-1,1) ) )
|
||||
|
||||
local WingFinFadeOut = math.max( (90 - AngDiff ) / 90, 0 )
|
||||
local RudderFadeOut = math.min( math.max( (120 - AngDiff ) / 120, 0 ) * 3, 1 )
|
||||
|
||||
local AngVel = self:GetPhysicsObject():GetAngleVelocity()
|
||||
|
||||
local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 )
|
||||
local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 )
|
||||
|
||||
local Pitch = math.Clamp( -LocalAngPitch / 10 + SmoothPitch, -1, 1 )
|
||||
local Yaw = math.Clamp( -LocalAngYaw / 2 + SmoothYaw,-1,1) * RudderFadeOut
|
||||
local Roll = math.Clamp( (-math.Clamp(LocalAngYaw * 16 * self:GetThrottle(),-90,90) + LocalAngRoll * RudderFadeOut * 0.75) * WingFinFadeOut / 180 , -1 , 1 )
|
||||
|
||||
if FreeMovement then
|
||||
Roll = math.Clamp( -LocalAngYaw * WingFinFadeOut / 180 , -1 , 1 )
|
||||
end
|
||||
|
||||
if OverridePitch and OverridePitch ~= 0 then
|
||||
Pitch = OverridePitch
|
||||
end
|
||||
|
||||
if OverrideYaw and OverrideYaw ~= 0 then
|
||||
Yaw = OverrideYaw
|
||||
end
|
||||
|
||||
if OverrideRoll and OverrideRoll ~= 0 then
|
||||
Roll = OverrideRoll
|
||||
end
|
||||
|
||||
self:SetSteer( Vector( math.Clamp(Roll * 1.25,-1,1), math.Clamp(-Pitch * 1.25,-1,1), -Yaw) )
|
||||
end
|
||||
|
||||
function ENT:CalcAero( phys, deltatime, EntTable )
|
||||
if not EntTable then
|
||||
EntTable = self:GetTable()
|
||||
end
|
||||
|
||||
-- mouse aim needs to run at high speed.
|
||||
if self:GetAI() then
|
||||
if EntTable._lvsAITargetAng then
|
||||
self:ApproachTargetAngle( EntTable._lvsAITargetAng )
|
||||
end
|
||||
else
|
||||
local ply = self:GetDriver()
|
||||
if IsValid( ply ) and ply:lvsMouseAim() then
|
||||
self:PlayerMouseAim( ply )
|
||||
end
|
||||
end
|
||||
|
||||
local Steer = self:GetSteer()
|
||||
|
||||
local Forward = self:GetForward()
|
||||
local Left = -self:GetRight()
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
local VelForward = Vel:GetNormalized()
|
||||
|
||||
local GravityPitch = 0
|
||||
local GravityYaw = 0
|
||||
|
||||
-- crash bebehavior
|
||||
if self:IsDestroyed() then
|
||||
local WorldGravity = self:GetWorldGravity()
|
||||
local WorldUp = self:GetWorldUp()
|
||||
|
||||
local Up = self:GetUp()
|
||||
|
||||
Steer = phys:GetAngleVelocity() / 200
|
||||
|
||||
local PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90
|
||||
local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90
|
||||
|
||||
local GravMul = WorldGravity / 600
|
||||
|
||||
GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul
|
||||
GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul
|
||||
|
||||
if not phys:IsGravityEnabled() then
|
||||
phys:EnableGravity( true )
|
||||
end
|
||||
end
|
||||
|
||||
local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * EntTable.TurnRatePitch * 3
|
||||
local Yaw = math.Clamp(Steer.z * 4 + GravityYaw,-1,1) * EntTable.TurnRateYaw
|
||||
local Roll = math.Clamp(Steer.x * 1.5,-1,1) * EntTable.TurnRateRoll * 12
|
||||
|
||||
local VelL = self:WorldToLocal( self:GetPos() + Vel )
|
||||
|
||||
local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - math.abs( Steer.y ), 0 ) / 90) * 0.3
|
||||
local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - math.abs( Steer.z ), 0 ) / 90) * 0.15
|
||||
|
||||
local VtolMove = self:GetVtolMove()
|
||||
|
||||
local Move = Vector( (VtolMove.x < 0) and -math.min(VelL.x * 0.15,0) or 0, -VelL.y * MulY, -VelL.z * MulZ ) + VtolMove
|
||||
|
||||
return Move, Vector( Roll, Pitch, Yaw )
|
||||
end
|
||||
|
||||
function ENT:OnSkyCollide( data, PhysObj )
|
||||
local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * math.Clamp(self:GetThrustStrenght() * self.MaxThrust,250,800)
|
||||
|
||||
PhysObj:SetVelocityInstantaneous( NewVelocity )
|
||||
PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
if self:GetEngineActive() then
|
||||
phys:Wake()
|
||||
else
|
||||
return vector_origin, vector_origin, SIM_NOTHING
|
||||
end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local Aero, Torque = self:CalcAero( phys, deltatime, EntTable )
|
||||
|
||||
local Thrust = self:GetThrustStrenght() * EntTable.MaxThrust * 100
|
||||
|
||||
if self:IsDestroyed() then
|
||||
Thrust = math.max( Thrust, 0 ) -- dont allow braking, but allow accelerating while destroyed
|
||||
end
|
||||
|
||||
local ForceLinear = (Aero * 10000 * EntTable.ForceLinearMultiplier + Vector(Thrust,0,0)) * deltatime
|
||||
local ForceAngle = (Torque * 25 * EntTable.ForceAngleMultiplier - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_LOCAL_ACCELERATION )
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate )
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
|
||||
function ENT:OnMaintenance()
|
||||
for _, Engine in pairs( self:GetEngines() ) do
|
||||
if not IsValid( Engine ) then continue end
|
||||
|
||||
if not Engine.SetHP or not Engine.GetMaxHP or not Engine.SetDestroyed then continue end
|
||||
|
||||
Engine:SetHP( Engine:GetMaxHP() )
|
||||
Engine:SetDestroyed( false )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
function ENT:GetEyeTrace( trace_forward )
|
||||
local startpos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
startpos = pod:LocalToWorld( pod:OBBCenter() )
|
||||
end
|
||||
|
||||
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
|
||||
|
||||
local data = {
|
||||
start = startpos,
|
||||
endpos = (startpos + AimVector * 50000),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( data )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetAIAimVector()
|
||||
end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if not IsValid( Driver ) then return self:GetForward() end
|
||||
|
||||
if not Driver:lvsMouseAim() then
|
||||
if Driver:lvsKeyDown( "FREELOOK" ) then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
if pod:GetThirdPersonMode() then
|
||||
return -self:GetForward()
|
||||
else
|
||||
return Driver:GetAimVector()
|
||||
end
|
||||
else
|
||||
return self:GetForward()
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return Driver:EyeAngles():Forward() end
|
||||
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
else
|
||||
return Driver:EyeAngles():Forward()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,194 @@
|
||||
|
||||
ENT.Base = "lvs_base"
|
||||
|
||||
ENT.PrintName = "[LVS] Base Starfighter"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS]"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.MaxVelocity = 3000
|
||||
ENT.MaxThrust = 3000
|
||||
|
||||
ENT.ThrustVtol = 55
|
||||
ENT.ThrustRateVtol = 3
|
||||
|
||||
ENT.ThrottleRateUp = 0.6
|
||||
ENT.ThrottleRateDown = 0.6
|
||||
|
||||
ENT.TurnRatePitch = 1
|
||||
ENT.TurnRateYaw = 1
|
||||
ENT.TurnRateRoll = 1
|
||||
|
||||
ENT.ForceLinearMultiplier = 1
|
||||
|
||||
ENT.ForceAngleMultiplier = 1
|
||||
ENT.ForceAngleDampingMultiplier = 1
|
||||
|
||||
ENT.DisableBallistics = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:AddDT( "Vector", "Steer" )
|
||||
self:AddDT( "Vector", "AIAimVector" )
|
||||
self:AddDT( "Vector", "NWVtolMove" )
|
||||
self:AddDT( "Float", "NWThrottle" )
|
||||
self:AddDT( "Float", "MaxThrottle" )
|
||||
|
||||
if SERVER then
|
||||
self:SetMaxThrottle( 1 )
|
||||
end
|
||||
|
||||
self:CreateBaseDT()
|
||||
end
|
||||
|
||||
function ENT:PlayerDirectInput( ply, cmd )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local KeyLeft = ply:lvsKeyDown( "-ROLL_SF" )
|
||||
local KeyRight = ply:lvsKeyDown( "+ROLL_SF" )
|
||||
local KeyPitchUp = ply:lvsKeyDown( "+PITCH_SF" )
|
||||
local KeyPitchDown = ply:lvsKeyDown( "-PITCH_SF" )
|
||||
local KeyRollRight = ply:lvsKeyDown( "+YAW_SF" )
|
||||
local KeyRollLeft = ply:lvsKeyDown( "-YAW_SF" )
|
||||
|
||||
local MouseX = cmd:GetMouseX()
|
||||
local MouseY = cmd:GetMouseY()
|
||||
|
||||
if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then
|
||||
MouseX = 0
|
||||
MouseY = 0
|
||||
else
|
||||
ply:SetEyeAngles( Angle(0,90,0) )
|
||||
end
|
||||
|
||||
local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity()
|
||||
|
||||
if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end
|
||||
if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end
|
||||
if KeyRollRight or KeyRollLeft then
|
||||
local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0)
|
||||
|
||||
MouseX = (NewX / SensX) * ReturnDelta
|
||||
end
|
||||
|
||||
local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 )
|
||||
|
||||
local Cur = self:GetSteer()
|
||||
|
||||
local Rate = Delta * 3 * ReturnDelta
|
||||
|
||||
local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0)
|
||||
|
||||
local Target = New + Input * Delta * 0.8
|
||||
|
||||
local Fx = math.Clamp( Target.x, -1, 1 )
|
||||
local Fy = math.Clamp( Target.y, -1, 1 )
|
||||
|
||||
local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0)
|
||||
local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3)
|
||||
|
||||
local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1)
|
||||
|
||||
self:SetSteer( F )
|
||||
end
|
||||
|
||||
function ENT:CalcThrottle( ply, cmd )
|
||||
if CLIENT then return end
|
||||
|
||||
local Delta = FrameTime()
|
||||
|
||||
local ThrottleUp = ply:lvsKeyDown( "+THRUST_SF" ) and self.ThrottleRateUp or 0
|
||||
local ThrottleDown = ply:lvsKeyDown( "-THRUST_SF" ) and -self.ThrottleRateDown or 0
|
||||
|
||||
local Throttle = (ThrottleUp + ThrottleDown) * Delta
|
||||
|
||||
self:SetThrottle( self:GetThrottle() + Throttle )
|
||||
end
|
||||
|
||||
function ENT:CalcVtolThrottle( ply, cmd )
|
||||
local Delta = FrameTime()
|
||||
|
||||
local ThrottleZero = self:GetThrottle() <= 0
|
||||
|
||||
local VtolX = ThrottleZero and (ply:lvsKeyDown( "-VTOL_X_SF" ) and -1 or 0) or 0
|
||||
local VtolY = ((ply:lvsKeyDown( "+VTOL_Y_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Y_SF" ) and 1 or 0))
|
||||
local VtolZ = ((ply:lvsKeyDown( "+VTOL_Z_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Z_SF" ) and 1 or 0))
|
||||
|
||||
local DesiredVtol = Vector(VtolX,VtolY,VtolZ)
|
||||
local NewVtolMove = self:GetNWVtolMove() + (DesiredVtol - self:GetNWVtolMove()) * self.ThrustRateVtol * Delta
|
||||
|
||||
if not ThrottleZero or self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x > 100 then
|
||||
NewVtolMove.x = 0
|
||||
end
|
||||
|
||||
self:SetVtolMove( NewVtolMove )
|
||||
end
|
||||
|
||||
function ENT:SetVtolMove( NewMove )
|
||||
if self:GetEngineActive() then
|
||||
self:SetNWVtolMove( NewMove )
|
||||
else
|
||||
self:SetNWVtolMove( Vector(0,0,0) )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetThrottle( NewThrottle )
|
||||
if self:GetEngineActive() then
|
||||
self:SetNWThrottle( math.Clamp(NewThrottle,0,self:GetMaxThrottle()) )
|
||||
else
|
||||
self:SetNWThrottle( 0 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetThrottle()
|
||||
if self:GetEngineActive() then
|
||||
return self:GetNWThrottle()
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetVtolMove()
|
||||
if self:GetEngineActive() and not self:GetAI() then
|
||||
return self:GetNWVtolMove() * self.ThrustVtol * (1 - math.min( self:GetThrottle(), 1 ))
|
||||
else
|
||||
return Vector(0,0,0)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:StartCommand( ply, cmd )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
if SERVER then
|
||||
local KeyJump = ply:lvsKeyDown( "VSPEC" )
|
||||
|
||||
if self._lvsOldKeyJump ~= KeyJump then
|
||||
self._lvsOldKeyJump = KeyJump
|
||||
|
||||
if KeyJump then
|
||||
self:ToggleVehicleSpecific()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not ply:lvsMouseAim() then
|
||||
self:PlayerDirectInput( ply, cmd )
|
||||
end
|
||||
|
||||
self:CalcThrottle( ply, cmd )
|
||||
self:CalcVtolThrottle( ply, cmd )
|
||||
end
|
||||
|
||||
function ENT:GetThrustStrenght()
|
||||
local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x
|
||||
|
||||
return (self.MaxVelocity * self:GetThrottle() - ForwardVelocity) / self.MaxVelocity
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "starfighter"
|
||||
end
|
||||
@@ -0,0 +1,192 @@
|
||||
|
||||
function ENT:OnCreateAI()
|
||||
self:StartEngine()
|
||||
end
|
||||
|
||||
function ENT:OnRemoveAI()
|
||||
self:StopEngine()
|
||||
end
|
||||
|
||||
function ENT:RunAI()
|
||||
local RangerLength = 15000
|
||||
local mySpeed = self:GetVelocity():Length()
|
||||
local MinDist = 600 + mySpeed
|
||||
|
||||
local StartPos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local TraceFilter = self:GetCrosshairFilterEnts()
|
||||
|
||||
local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } )
|
||||
local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } )
|
||||
local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } )
|
||||
|
||||
local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } )
|
||||
local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } )
|
||||
|
||||
local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } )
|
||||
local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } )
|
||||
local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } )
|
||||
|
||||
local cAvoid = Vector(0,0,0)
|
||||
|
||||
local myRadius = self:BoundingRadius()
|
||||
local myPos = self:GetPos()
|
||||
local myDir = self:GetForward()
|
||||
for _, v in pairs( LVS:GetVehicles() ) do
|
||||
if v == self then continue end
|
||||
|
||||
local theirRadius = v:BoundingRadius()
|
||||
local Sub = (myPos - v:GetPos())
|
||||
local Dir = Sub:GetNormalized()
|
||||
local Dist = Sub:Length()
|
||||
|
||||
if Dist < (theirRadius + myRadius + 200) then
|
||||
if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then
|
||||
cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8
|
||||
local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8
|
||||
|
||||
local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist
|
||||
local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist
|
||||
|
||||
local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist
|
||||
local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist
|
||||
|
||||
local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist
|
||||
local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist
|
||||
|
||||
local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist
|
||||
local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist
|
||||
|
||||
local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10
|
||||
|
||||
local alt = (StartPos - TraceDown.HitPos):Length()
|
||||
local ceiling = (StartPos - TraceUp.HitPos):Length()
|
||||
|
||||
local WallDist = (StartPos - TraceForward.HitPos):Length()
|
||||
|
||||
local Throttle = math.min( WallDist / mySpeed, 1 )
|
||||
|
||||
self._AIFireInput = false
|
||||
|
||||
if alt < 600 or ceiling < 600 or WallDist < (MinDist * 3 * (math.deg( math.acos( math.Clamp( Vector(0,0,1):Dot( myDir ) ,-1,1) ) ) / 180) ^ 2) then
|
||||
Throttle = 1
|
||||
|
||||
if self:HitGround() then
|
||||
TargetPos.z = StartPos.z + 750
|
||||
end
|
||||
else
|
||||
if IsValid( self:GetHardLockTarget() ) then
|
||||
TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8
|
||||
else
|
||||
if alt > mySpeed then
|
||||
local Target = self._LastAITarget
|
||||
|
||||
if not IsValid( self._LastAITarget ) or not self:AITargetInFront( self._LastAITarget, 135 ) or not self:AICanSee( self._LastAITarget ) then
|
||||
Target = self:AIGetTarget()
|
||||
end
|
||||
|
||||
if IsValid( Target ) then
|
||||
if self:AITargetInFront( Target, 65 ) then
|
||||
local T = CurTime() + self:EntIndex() * 1337
|
||||
TargetPos = Target:GetPos() + cAvoid * 8 + Vector(0,0, math.sin( T * 5 ) * 500 ) + Target:GetVelocity() * math.abs( math.cos( T * 13.37 ) ) * 5
|
||||
|
||||
Throttle = math.min( (StartPos - TargetPos):Length() / mySpeed, 1 )
|
||||
|
||||
local tr = util.TraceHull( {
|
||||
start = StartPos,
|
||||
endpos = (StartPos + self:GetForward() * 50000),
|
||||
mins = Vector( -50, -50, -50 ),
|
||||
maxs = Vector( 50, 50, 50 ),
|
||||
filter = TraceFilter
|
||||
} )
|
||||
|
||||
local CanShoot = (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true
|
||||
|
||||
if CanShoot and self:AITargetInFront( Target, 22 ) then
|
||||
local CurHeat = self:GetNWHeat()
|
||||
local CurWeapon = self:GetSelectedWeapon()
|
||||
|
||||
if CurWeapon > 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
else
|
||||
if CurHeat > 0.9 then
|
||||
if CurWeapon == 1 and self:AIHasWeapon( 2 ) then
|
||||
self:AISelectWeapon( 2 )
|
||||
|
||||
elseif CurWeapon == 2 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
else
|
||||
if CurHeat == 0 and math.cos( T ) > 0 then
|
||||
self:AISelectWeapon( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self._AIFireInput = true
|
||||
end
|
||||
else
|
||||
self:AISelectWeapon( 1 )
|
||||
|
||||
if alt > 6000 and self:AITargetInFront( Target, 90 ) then
|
||||
TargetPos = Target:GetPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
TargetPos.z = StartPos.z + 2000
|
||||
|
||||
self:EnableVehicleSpecific()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:SetThrottle( Throttle )
|
||||
|
||||
self.smTargetPos = self.smTargetPos and self.smTargetPos + (TargetPos - self.smTargetPos) * FrameTime() or self:GetPos()
|
||||
|
||||
self._lvsAITargetAng = (self.smTargetPos - self:GetPos()):GetNormalized():Angle()
|
||||
end
|
||||
|
||||
function ENT:AISelectWeapon( ID )
|
||||
if ID == self:GetSelectedWeapon() then return end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (self._nextAISwitchWeapon or 0) > T then return end
|
||||
|
||||
self._nextAISwitchWeapon = T + math.random(3,6)
|
||||
|
||||
self:SelectWeapon( ID )
|
||||
end
|
||||
|
||||
function ENT:OnAITakeDamage( dmginfo )
|
||||
local attacker = dmginfo:GetAttacker()
|
||||
|
||||
if not IsValid( attacker ) then return end
|
||||
|
||||
if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then
|
||||
self:SetHardLockTarget( attacker )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SetHardLockTarget( target )
|
||||
self._HardLockTarget = target
|
||||
self._HardLockTime = CurTime() + 4
|
||||
end
|
||||
|
||||
function ENT:GetHardLockTarget()
|
||||
if (self._HardLockTime or 0) < CurTime() then return NULL end
|
||||
|
||||
return self._HardLockTarget
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
function ENT:AddEngine( pos, ang, mins, maxs, health )
|
||||
local Engine = ents.Create( "lvs_starfighter_engine" )
|
||||
|
||||
if not IsValid( Engine ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
Engine:SetPos( self:LocalToWorld( pos ) )
|
||||
Engine:SetAngles( self:GetAngles() )
|
||||
Engine:Spawn()
|
||||
Engine:Activate()
|
||||
Engine:SetParent( self )
|
||||
Engine:SetBase( self )
|
||||
|
||||
if not health then
|
||||
health = self.MaxHealth / 8
|
||||
end
|
||||
|
||||
Engine:SetMaxHP( health )
|
||||
Engine:SetHP( health )
|
||||
self:DeleteOnRemove( Engine )
|
||||
self:TransferCPPI( Engine )
|
||||
|
||||
self:AddDS( {
|
||||
pos = pos,
|
||||
ang = (ang or Angle(0,0,0)),
|
||||
mins = (mins or Vector(-40,-20,-30)),
|
||||
maxs = (maxs or Vector(40,20,30)),
|
||||
Callback = function( tbl, ent, dmginfo )
|
||||
if dmginfo:GetDamage() <= 0 then return end
|
||||
|
||||
Engine:TakeDamageInfo( dmginfo )
|
||||
end
|
||||
} )
|
||||
|
||||
|
||||
if not istable( self._lvsEngines ) then
|
||||
self._lvsEngines = {}
|
||||
end
|
||||
|
||||
table.insert( self._lvsEngines, Engine )
|
||||
|
||||
return Engine
|
||||
end
|
||||
|
||||
function ENT:GetEngines()
|
||||
return self._lvsEngines or {}
|
||||
end
|
||||
|
||||
function ENT:AddEngineSound( pos )
|
||||
local EngineSND = ents.Create( "lvs_starfighter_soundemitter" )
|
||||
|
||||
if not IsValid( EngineSND ) then
|
||||
self:Remove()
|
||||
|
||||
print("LVS: Failed to create engine sound entity. Vehicle terminated.")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
EngineSND:SetPos( self:LocalToWorld( pos ) )
|
||||
EngineSND:SetAngles( self:GetAngles() )
|
||||
EngineSND:Spawn()
|
||||
EngineSND:Activate()
|
||||
EngineSND:SetParent( self )
|
||||
EngineSND:SetBase( self )
|
||||
|
||||
self:DeleteOnRemove( EngineSND )
|
||||
|
||||
self:TransferCPPI( EngineSND )
|
||||
|
||||
return EngineSND
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
function ENT:PlayerMouseAim( ply )
|
||||
local Pod = self:GetDriverSeat()
|
||||
|
||||
local PitchUp = ply:lvsKeyDown( "+PITCH_SF" )
|
||||
local PitchDown = ply:lvsKeyDown( "-PITCH_SF" )
|
||||
local YawRight = ply:lvsKeyDown( "+YAW_SF" )
|
||||
local YawLeft = ply:lvsKeyDown( "-YAW_SF" )
|
||||
local RollRight = ply:lvsKeyDown( "+ROLL_SF" )
|
||||
local RollLeft = ply:lvsKeyDown( "-ROLL_SF" )
|
||||
|
||||
local FreeLook = ply:lvsKeyDown( "FREELOOK" )
|
||||
|
||||
local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() )
|
||||
|
||||
if FreeLook then
|
||||
if isangle( self.StoredEyeAngles ) then
|
||||
EyeAngles = self.StoredEyeAngles
|
||||
end
|
||||
else
|
||||
self.StoredEyeAngles = EyeAngles
|
||||
end
|
||||
|
||||
local OverridePitch = 0
|
||||
local OverrideYaw = 0
|
||||
local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0)
|
||||
|
||||
if PitchUp or PitchDown then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0)
|
||||
end
|
||||
|
||||
if YawRight or YawLeft then
|
||||
EyeAngles = self:GetAngles()
|
||||
|
||||
self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0)
|
||||
|
||||
OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0)
|
||||
end
|
||||
|
||||
self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook )
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
function ENT:ToggleVehicleSpecific()
|
||||
self._VSPEC = not self._VSPEC
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:EnableVehicleSpecific()
|
||||
if self._VSPEC then return end
|
||||
|
||||
self._VSPEC = true
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:DisableVehicleSpecific()
|
||||
if not self._VSPEC then return end
|
||||
|
||||
self._VSPEC = false
|
||||
|
||||
self:OnVehicleSpecificToggled( self._VSPEC )
|
||||
end
|
||||
|
||||
function ENT:OnVehicleSpecificToggled( IsActive )
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
|
||||
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
|
||||
return pos, angles, fov
|
||||
end
|
||||
|
||||
function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
|
||||
pos = pos + pod:GetUp() * 7 - pod:GetRight() * 11
|
||||
|
||||
if ply:lvsMouseAim() then
|
||||
angles = ply:EyeAngles()
|
||||
|
||||
return self:CalcViewMouseAim( ply, pos, angles, fov, pod )
|
||||
else
|
||||
return self:CalcViewDirectInput( ply, pos, angles, fov, pod )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
|
||||
return LVS:CalcView( self, ply, pos, angles, fov, pod )
|
||||
end
|
||||
|
||||
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
|
||||
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
|
||||
|
||||
local new_fov = math.min( fov + self:CalcViewPunch( ply, pos, angles, fov, pod ), 180 )
|
||||
|
||||
if self:GetDriverSeat() == pod then
|
||||
return self:CalcViewDriver( ply, pos, angles, new_fov, pod )
|
||||
else
|
||||
return self:CalcViewPassenger( ply, pos, angles, new_fov, pod )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:SuppressViewPunch( time )
|
||||
self._viewpunch_supressed_time = CurTime() + (time or 0.2)
|
||||
end
|
||||
|
||||
function ENT:IsViewPunchSuppressed()
|
||||
return (self._viewpunch_supressed_time or 0) > CurTime()
|
||||
end
|
||||
|
||||
function ENT:CalcViewPunch( ply, pos, angles, fov, pod )
|
||||
local Vel = self:GetVelocity()
|
||||
local VelLength = Vel:Length()
|
||||
local VelPercentMaxSpeed = math.min( VelLength / 1000, 1 )
|
||||
|
||||
if ply:lvsMouseAim() then
|
||||
angles = ply:EyeAngles()
|
||||
end
|
||||
|
||||
local direction = (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90
|
||||
|
||||
local FovValue = math.min( VelPercentMaxSpeed ^ 2 * 100, 15 )
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
local Brake = self:GetBrake()
|
||||
|
||||
if self:IsViewPunchSuppressed() then
|
||||
self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (-VelPercentMaxSpeed * FovValue - self._viewpunch_fov) * RealFrameTime() or 0
|
||||
else
|
||||
local newFov =(1 - VelPercentMaxSpeed) * Throttle * FovValue - VelPercentMaxSpeed * Brake * FovValue
|
||||
|
||||
self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (newFov - self._viewpunch_fov) * RealFrameTime() * 10 or 0
|
||||
end
|
||||
|
||||
return self._viewpunch_fov * (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
function ENT:IsBackFireEnabled()
|
||||
if not isfunction( self.GetBackfire ) then return false end
|
||||
|
||||
return self:GetBackfire()
|
||||
end
|
||||
|
||||
function ENT:DoExhaustFX( Magnitude )
|
||||
for _, data in ipairs( self.ExhaustPositions ) do
|
||||
if data.bodygroup then
|
||||
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
|
||||
end
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( self:LocalToWorld( data.pos ) )
|
||||
effectdata:SetNormal( self:LocalToWorldAngles( data.ang ):Forward() )
|
||||
effectdata:SetMagnitude( Magnitude )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( data.effect or "lvs_exhaust", effectdata )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:ExhaustEffectsThink()
|
||||
if not self:IsBackFireEnabled() then return end
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
|
||||
if self._backfireTHR ~= Throttle then
|
||||
self._backfireTHR = Throttle
|
||||
|
||||
if Throttle ~= 0 then return end
|
||||
|
||||
self:CalcExhaustPop()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:CalcExhaustPop()
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if not IsValid( Engine ) then return end
|
||||
|
||||
local RPM = Engine:GetRPM()
|
||||
|
||||
local num = (Engine:GetRPM() / 500)
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
|
||||
if Throttle > 0 and Throttle < 0.6 then return end
|
||||
|
||||
if Throttle ~= 0 or (not IsValid( self:GetTurbo() ) and not IsValid( self:GetCompressor() )) then num = 0 end
|
||||
|
||||
for i = 0, num do
|
||||
timer.Simple( self.TransShiftSpeed + i * 0.1 , function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
if i > 0 and self:GetThrottle() ~= 0 then return end
|
||||
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if not IsValid( Engine ) then return end
|
||||
|
||||
local RPM = Engine:GetRPM()
|
||||
|
||||
if RPM < self.EngineMaxRPM * 0.6 then return end
|
||||
|
||||
if i == 0 then
|
||||
self:DoExhaustPop( LVS.EngineVolume )
|
||||
else
|
||||
self:DoExhaustPop( 0.75 * LVS.EngineVolume )
|
||||
end
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoExhaustPop( volume )
|
||||
if not istable( self.ExhaustPositions ) then return end
|
||||
|
||||
for _, data in ipairs( self.ExhaustPositions ) do
|
||||
if data.bodygroup then
|
||||
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
|
||||
end
|
||||
|
||||
timer.Simple( math.Rand(0,0.2), function()
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data.pos )
|
||||
effectdata:SetAngles( data.ang )
|
||||
effectdata:SetEntity( self )
|
||||
effectdata:SetMagnitude( volume or 1 )
|
||||
util.Effect( "lvs_carexhaust_pop", effectdata )
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoExhaustBackFire()
|
||||
if not istable( self.ExhaustPositions ) then return end
|
||||
|
||||
for _, data in ipairs( self.ExhaustPositions ) do
|
||||
if data.bodygroup then
|
||||
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
|
||||
end
|
||||
|
||||
if math.random( 1, math.floor( #self.ExhaustPositions * 0.75 ) ) ~= 1 then continue end
|
||||
|
||||
timer.Simple( math.Rand(0.5,1), function()
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data.pos )
|
||||
effectdata:SetAngles( data.ang )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_carexhaust_backfire", effectdata )
|
||||
end )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
ENT.FlyByVelocity = 500
|
||||
ENT.FlyByMinThrottle = 0
|
||||
ENT.FlyByAdvance = 1
|
||||
ENT.FlyBySound = "lvs/vehicles/generic/car_flyby.wav"
|
||||
|
||||
function ENT:FlyByThink()
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local veh = ply:lvsGetVehicle()
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if veh == self then EntTable.OldApproaching = false return end
|
||||
|
||||
local ViewEnt = ply:GetViewEntity()
|
||||
|
||||
if not IsValid( ViewEnt ) then return end
|
||||
|
||||
if IsValid( veh ) and ViewEnt == ply then
|
||||
ViewEnt = veh
|
||||
end
|
||||
|
||||
local Time = CurTime()
|
||||
|
||||
if (EntTable._nextflyby or 0) > Time then return end
|
||||
|
||||
EntTable._nextflyby = Time + 0.1
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
|
||||
if self:GetThrottle() <= EntTable.FlyByMinThrottle or Vel:Length() <= EntTable.FlyByVelocity then return end
|
||||
|
||||
local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance
|
||||
local ToPlayer = Sub:GetNormalized()
|
||||
local VelDir = Vel:GetNormalized()
|
||||
|
||||
local ApproachAngle = self:AngleBetweenNormal( ToPlayer, VelDir )
|
||||
|
||||
local Approaching = ApproachAngle < 80
|
||||
|
||||
if Approaching ~= EntTable.OldApproaching then
|
||||
EntTable.OldApproaching = Approaching
|
||||
|
||||
if Approaching then
|
||||
self:StopFlyBy()
|
||||
else
|
||||
self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnFlyBy( Pitch )
|
||||
if not self.FlyBySound then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound )
|
||||
EntTable.flybysnd:SetSoundLevel( 95 )
|
||||
EntTable.flybysnd:PlayEx( 1, Pitch )
|
||||
end
|
||||
|
||||
function ENT:StopFlyBy()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if not EntTable.flybysnd then return end
|
||||
|
||||
EntTable.flybysnd:Stop()
|
||||
EntTable.flybysnd = nil
|
||||
end
|
||||
@@ -0,0 +1,237 @@
|
||||
|
||||
include("cl_hud_speedometer.lua")
|
||||
|
||||
ENT.IconEngine = Material( "lvs/engine.png" )
|
||||
ENT.IconFuel = Material( "lvs/fuel.png" )
|
||||
|
||||
local WaveScale = 0
|
||||
local WaveMaterial = Material( "effects/select_ring" )
|
||||
local oldThrottleActive = false
|
||||
local oldReverse = false
|
||||
local oldGear = -1
|
||||
|
||||
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
|
||||
if self:GetRacingHud() then return end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (EntTable._nextRefreshVel or 0) < T then
|
||||
EntTable._nextRefreshVel = T + 0.1
|
||||
EntTable._refreshVel = self:GetVelocity():Length()
|
||||
end
|
||||
|
||||
local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 )
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
|
||||
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
local Col = Throttle <= 1 and color_white or Color(0,0,0,255)
|
||||
local hX = X + W - H * 0.5
|
||||
local hY = Y + H * 0.25 + H * 0.25
|
||||
|
||||
local fueltank = self:GetFuelTank()
|
||||
|
||||
if IsValid( fueltank ) and fueltank:GetFuel() <= 0 then
|
||||
surface.SetMaterial( EntTable.IconFuel )
|
||||
else
|
||||
surface.SetMaterial( EntTable.IconEngine )
|
||||
end
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
|
||||
surface.SetDrawColor( color_white )
|
||||
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
|
||||
|
||||
if not self:GetEngineActive() then
|
||||
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
oldThrottleActive = false
|
||||
|
||||
local Reverse = self:GetReverse()
|
||||
|
||||
if oldReverse ~= Reverse then
|
||||
oldReverse = Reverse
|
||||
|
||||
WaveScale = 1
|
||||
end
|
||||
|
||||
local IsManual = self:IsManualTransmission()
|
||||
local Gear = self:GetGear()
|
||||
|
||||
if oldGear ~= Gear then
|
||||
oldGear = Gear
|
||||
|
||||
WaveScale = 1
|
||||
end
|
||||
|
||||
if WaveScale > 0 then
|
||||
WaveScale = math.max( WaveScale - RealFrameTime() * 2, 0 )
|
||||
|
||||
local WaveRadius = (1 - WaveScale) * H * 1.5
|
||||
|
||||
surface.SetDrawColor( 0, 127, 255, 255 * WaveScale ^ 2 )
|
||||
surface.SetMaterial( WaveMaterial )
|
||||
|
||||
surface.DrawTexturedRectRotated( hX, hY, WaveRadius, WaveRadius, 0 )
|
||||
|
||||
if not Reverse and not IsManual then
|
||||
draw.SimpleText( "D" , "LVS_FONT", hX, hY, Color(0,0,0,math.min(800 * WaveScale ^ 2,255)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
if IsManual then
|
||||
draw.SimpleText( (Reverse and -1 or 1) * Gear , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
if Reverse then
|
||||
draw.SimpleText( "R" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) )
|
||||
|
||||
if Throttle > 1 then
|
||||
draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
LVS:AddHudEditor( "CarMenu", ScrW() - 690, ScrH() - 85, 220, 75, 220, 75, "CAR MENU",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintCarMenu then return end
|
||||
vehicle:LVSHudPaintCarMenu( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
local function DrawTexturedRect( X, Y, size, selected )
|
||||
local oz = 2
|
||||
|
||||
surface.SetDrawColor( 0, 0, 0, 150 )
|
||||
surface.DrawTexturedRectRotated( X + oz, Y + oz, size, size, 0 )
|
||||
|
||||
if IsColor( selected ) then
|
||||
surface.SetDrawColor( selected.r, selected.g, selected.b, selected.a )
|
||||
else
|
||||
if selected then
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
else
|
||||
surface.SetDrawColor( 150, 150, 150, 150 )
|
||||
end
|
||||
end
|
||||
|
||||
surface.DrawTexturedRectRotated( X, Y, size, size, 0 )
|
||||
end
|
||||
|
||||
ENT.CarMenuSiren = Material( "lvs/carmenu_siren.png" )
|
||||
ENT.CarMenuHighbeam = Material( "lvs/carmenu_highbeam.png" )
|
||||
ENT.CarMenuLowbeam = Material( "lvs/carmenu_lowbeam.png" )
|
||||
ENT.CarMenuDisable = Material( "lvs/carmenu_cross.png" )
|
||||
ENT.CarMenuFog = Material( "lvs/carmenu_fog.png" )
|
||||
ENT.CarMenuHazard = Material( "lvs/carmenu_hazard.png" )
|
||||
ENT.CarMenuLeft = Material( "lvs/carmenu_turnleft.png" )
|
||||
ENT.CarMenuRight = Material( "lvs/carmenu_turnRight.png" )
|
||||
|
||||
function ENT:LVSHudPaintCarMenu( X, Y, w, h, ScrX, ScrY, ply )
|
||||
if self:GetDriver() ~= ply then return end
|
||||
|
||||
local MenuOpen = ply:lvsKeyDown( "CAR_MENU" ) and self:HasTurnSignals()
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if MenuOpen then
|
||||
if ply:lvsKeyDown( "CAR_BRAKE" ) then
|
||||
EntTable._SelectedMode = 3
|
||||
end
|
||||
if ply:lvsKeyDown( "CAR_THROTTLE" ) then
|
||||
EntTable._SelectedMode = 0
|
||||
end
|
||||
if ply:lvsKeyDown( "CAR_STEER_LEFT" ) then
|
||||
EntTable._SelectedMode = 1
|
||||
end
|
||||
if ply:lvsKeyDown( "CAR_STEER_RIGHT" ) then
|
||||
EntTable._SelectedMode = 2
|
||||
end
|
||||
|
||||
if EntTable._oldSelectedMode ~= EntTable._SelectedMode then
|
||||
EntTable._oldSelectedMode = EntTable._SelectedMode
|
||||
|
||||
self:EmitSound("buttons/lightswitch2.wav",75,120,0.25)
|
||||
end
|
||||
else
|
||||
if EntTable._oldSelectedMode and isnumber( EntTable._SelectedMode ) then
|
||||
self:EmitSound("buttons/lightswitch2.wav",75,100,0.25)
|
||||
|
||||
net.Start( "lvs_car_turnsignal" )
|
||||
net.WriteInt( EntTable._SelectedMode, 4 )
|
||||
net.SendToServer()
|
||||
|
||||
EntTable._SelectedMode = 0
|
||||
EntTable._oldSelectedMode = nil
|
||||
end
|
||||
|
||||
local size = 32
|
||||
local dist = 5
|
||||
|
||||
local cX = X + w * 0.5
|
||||
local cY = Y + h - size * 0.5 - dist
|
||||
|
||||
local LightsHandler = self:GetLightsHandler()
|
||||
|
||||
if IsValid( LightsHandler ) then
|
||||
if LightsHandler:GetActive() then
|
||||
if LightsHandler:GetHighActive() then
|
||||
surface.SetMaterial( self.CarMenuHighbeam )
|
||||
DrawTexturedRect( cX, cY, size, Color(0,255,255,255) )
|
||||
else
|
||||
surface.SetMaterial( self.CarMenuLowbeam )
|
||||
DrawTexturedRect( cX, cY, size, Color(0,255,0,255) )
|
||||
end
|
||||
end
|
||||
|
||||
if LightsHandler:GetFogActive() then
|
||||
surface.SetMaterial( self.CarMenuFog )
|
||||
DrawTexturedRect( cX, cY - (size + dist), size, Color(255,100,0,255) )
|
||||
end
|
||||
end
|
||||
|
||||
local TurnMode = self:GetTurnMode()
|
||||
|
||||
if TurnMode == 0 then return end
|
||||
|
||||
local Alpha = self:GetTurnFlasher() and 255 or 0
|
||||
|
||||
if TurnMode == 1 or TurnMode == 3 then
|
||||
surface.SetMaterial( EntTable.CarMenuLeft )
|
||||
DrawTexturedRect( cX - (size + dist), cY, size, Color(0,255,157,Alpha) )
|
||||
end
|
||||
|
||||
if TurnMode == 2 or TurnMode == 3 then
|
||||
surface.SetMaterial( EntTable.CarMenuRight )
|
||||
DrawTexturedRect( cX + (size + dist), cY, size, Color(0,255,157,Alpha) )
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local SelectedThing = EntTable._SelectedMode or 0
|
||||
|
||||
local size = 32
|
||||
local dist = 5
|
||||
|
||||
local cX = X + w * 0.5
|
||||
local cY = Y + h - size * 0.5 - dist
|
||||
|
||||
surface.SetMaterial( EntTable.CarMenuDisable )
|
||||
DrawTexturedRect( cX, cY - (size + dist), size, SelectedThing == 0 )
|
||||
|
||||
surface.SetMaterial( EntTable.CarMenuLeft )
|
||||
DrawTexturedRect( cX - (size + dist), cY, size, SelectedThing == 1 )
|
||||
|
||||
surface.SetMaterial( EntTable.CarMenuRight)
|
||||
DrawTexturedRect( cX + (size + dist), cY, size, SelectedThing == 2 )
|
||||
|
||||
surface.SetMaterial( EntTable.CarMenuHazard )
|
||||
DrawTexturedRect( cX, cY, size, SelectedThing == 3 )
|
||||
end
|
||||
@@ -0,0 +1,403 @@
|
||||
|
||||
LVS:AddHudEditor( "Tachometer", ScrW() - 530, ScrH() - 250, 300, 220, 300, 220, "TACH",
|
||||
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
|
||||
if not vehicle.LVSHudPaintTach or not vehicle.GetRacingHud then return end
|
||||
|
||||
vehicle:LVSHudPaintTach( X, Y, W, H, ScrX, ScrY, ply )
|
||||
end
|
||||
)
|
||||
|
||||
local THE_FONT = {
|
||||
font = "Verdana",
|
||||
extended = false,
|
||||
size = 100,
|
||||
weight = 2000,
|
||||
blursize = 0,
|
||||
scanlines = 0,
|
||||
antialias = false,
|
||||
underline = false,
|
||||
italic = false,
|
||||
strikeout = false,
|
||||
symbol = false,
|
||||
rotary = false,
|
||||
shadow = false,
|
||||
additive = false,
|
||||
outline = false,
|
||||
}
|
||||
surface.CreateFont( "LVS_TACHOMETER", THE_FONT )
|
||||
|
||||
local circles = include("includes/circles/circles.lua")
|
||||
|
||||
local Center = 650
|
||||
|
||||
local startAngleSpeedo = 180
|
||||
local endAngleSpeedo = 375
|
||||
|
||||
local startAngleTach = 165
|
||||
local endAngleTach = 360
|
||||
|
||||
local Ring = circles.New( CIRCLE_OUTLINED, 300, 0, 0, 60 )
|
||||
Ring:SetMaterial( true )
|
||||
|
||||
local Circle = circles.New( CIRCLE_OUTLINED, 625, 0, 0, 230 )
|
||||
Circle:SetX( Center )
|
||||
Circle:SetY( Center )
|
||||
Circle:SetMaterial( true )
|
||||
|
||||
local RingOuter = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 35 )
|
||||
RingOuter:SetX( Center )
|
||||
RingOuter:SetY( Center )
|
||||
RingOuter:SetMaterial( true )
|
||||
|
||||
local RingInner = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 25 )
|
||||
RingInner:SetX( Center )
|
||||
RingInner:SetY( Center )
|
||||
RingInner:SetMaterial( true )
|
||||
|
||||
local RingFrame = circles.New( CIRCLE_OUTLINED, 390, 0, 0, 10 )
|
||||
RingFrame:SetX( Center )
|
||||
RingFrame:SetY( Center )
|
||||
RingFrame:SetMaterial( true )
|
||||
|
||||
local RingFrameOuter = circles.New( CIRCLE_OUTLINED, 395, 0, 0, 20 )
|
||||
RingFrameOuter:SetX( Center )
|
||||
RingFrameOuter:SetY( Center )
|
||||
RingFrameOuter:SetMaterial( true )
|
||||
|
||||
local RingOuterRedline = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 20 )
|
||||
RingOuterRedline:SetX( Center )
|
||||
RingOuterRedline:SetY( Center )
|
||||
RingOuterRedline:SetMaterial( true )
|
||||
|
||||
local RingInnerRedline = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 10 )
|
||||
RingInnerRedline:SetX( Center )
|
||||
RingInnerRedline:SetY( Center )
|
||||
RingInnerRedline:SetMaterial( true )
|
||||
|
||||
local VehicleTach = {}
|
||||
|
||||
function ENT:GetBakedTachMaterial( MaxRPM )
|
||||
local Class = self:GetClass()
|
||||
|
||||
if VehicleTach[ Class ] then return VehicleTach[ Class ] end
|
||||
|
||||
local TachRange = endAngleTach - startAngleTach
|
||||
|
||||
local Steps = math.ceil(MaxRPM / 1000)
|
||||
local AngleStep = TachRange / Steps
|
||||
local AngleRedline = startAngleTach + (TachRange / MaxRPM) * self.EngineMaxRPM
|
||||
|
||||
local tachRT = GetRenderTarget( "lvs_tach_"..Class, Center * 2, Center * 2 )
|
||||
|
||||
local old = DisableClipping( true )
|
||||
|
||||
render.OverrideAlphaWriteEnable( true, true )
|
||||
|
||||
render.PushRenderTarget( tachRT )
|
||||
|
||||
cam.Start2D()
|
||||
render.ClearDepth()
|
||||
render.Clear( 0, 0, 0, 0 )
|
||||
|
||||
surface.SetDrawColor( Color( 0, 0, 0, 150 ) )
|
||||
|
||||
Circle:SetStartAngle( startAngleTach )
|
||||
Circle:SetEndAngle( endAngleTach )
|
||||
Circle()
|
||||
|
||||
surface.SetDrawColor( Color( 0, 0, 0, 200 ) )
|
||||
|
||||
RingOuter:SetStartAngle( startAngleTach )
|
||||
RingOuter:SetEndAngle( AngleRedline )
|
||||
RingOuter()
|
||||
|
||||
RingOuterRedline:SetStartAngle( AngleRedline )
|
||||
RingOuterRedline:SetEndAngle( endAngleTach )
|
||||
RingOuterRedline()
|
||||
|
||||
RingFrameOuter:SetStartAngle( startAngleTach )
|
||||
RingFrameOuter:SetEndAngle( endAngleTach )
|
||||
RingFrameOuter()
|
||||
|
||||
surface.SetDrawColor( color_white )
|
||||
|
||||
for i = 0, Steps do
|
||||
local Ang = AngleStep * i + startAngleTach
|
||||
|
||||
local AngX = math.cos( math.rad( Ang ) )
|
||||
local AngY = math.sin( math.rad( Ang ) )
|
||||
|
||||
local StartX = Center + AngX * 554
|
||||
local StartY = Center + AngY * 554
|
||||
|
||||
local EndX = Center + AngX * 635
|
||||
local EndY = Center + AngY * 635
|
||||
|
||||
if Ang > AngleRedline then
|
||||
surface.SetDrawColor( Color(255,0,0,255) )
|
||||
else
|
||||
surface.SetDrawColor( color_white )
|
||||
end
|
||||
|
||||
draw.NoTexture()
|
||||
surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 90, 15, -Ang )
|
||||
|
||||
local TextX = Center + AngX * 485
|
||||
local TextY = Center + AngY * 485
|
||||
|
||||
if Ang > AngleRedline then
|
||||
draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, Color(255,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, Steps do
|
||||
local Start = AngleStep * i + startAngleTach
|
||||
|
||||
for n = 1, 9 do
|
||||
local Ang = Start - (AngleStep / 10) * n
|
||||
|
||||
if Ang > AngleRedline then
|
||||
surface.SetDrawColor( Color(150,0,0,255) )
|
||||
else
|
||||
surface.SetDrawColor( Color(150,150,150,255) )
|
||||
end
|
||||
|
||||
local AngX = math.cos( math.rad( Ang ) )
|
||||
local AngY = math.sin( math.rad( Ang ) )
|
||||
|
||||
local StartX = Center + AngX * 575
|
||||
local StartY = Center + AngY * 575
|
||||
|
||||
local EndX = Center + AngX * 635
|
||||
local EndY = Center + AngY * 635
|
||||
|
||||
draw.NoTexture()
|
||||
surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 60, 5, -Ang )
|
||||
end
|
||||
end
|
||||
|
||||
surface.SetDrawColor( color_white )
|
||||
|
||||
RingInner:SetStartAngle( startAngleTach )
|
||||
RingInner:SetEndAngle( AngleRedline )
|
||||
RingInner()
|
||||
|
||||
RingFrame:SetStartAngle( startAngleTach )
|
||||
RingFrame:SetEndAngle( endAngleTach )
|
||||
RingFrame()
|
||||
|
||||
surface.SetDrawColor( Color(255,0,0,255) )
|
||||
|
||||
RingInnerRedline:SetStartAngle( AngleRedline )
|
||||
RingInnerRedline:SetEndAngle( endAngleTach )
|
||||
RingInnerRedline()
|
||||
|
||||
cam.End2D()
|
||||
|
||||
render.OverrideAlphaWriteEnable( false )
|
||||
|
||||
render.PopRenderTarget()
|
||||
|
||||
local Mat = CreateMaterial( "lvs_tach_"..Class.."_mat", "UnlitGeneric", { ["$basetexture"] = tachRT:GetName(), ["$translucent"] = 1, ["$vertexcolor"] = 1 } )
|
||||
|
||||
VehicleTach[ Class ] = Mat
|
||||
|
||||
DisableClipping( old )
|
||||
|
||||
return Mat
|
||||
end
|
||||
|
||||
local TachNeedleColor = Color(255,0,0,255)
|
||||
local TachNeedleRadiusInner = 90
|
||||
local TachNeedleRadiusOuter = 145
|
||||
local TachNeedleBlurTime = 0.1
|
||||
local TachNeedles = {}
|
||||
local CurRPM = 0
|
||||
local CurSpeed = 0
|
||||
|
||||
function ENT:LVSHudPaintTach( X, Y, w, h, ScrX, ScrY, ply )
|
||||
if ply ~= self:GetDriver() then return end
|
||||
|
||||
if not self:GetRacingHud() then return end
|
||||
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if not IsValid( Engine ) then return end
|
||||
|
||||
local Delta = (Engine:GetRPM() - CurRPM) * RealFrameTime() * 20
|
||||
|
||||
CurRPM = CurRPM + Delta
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local MaxRPM = EntTable.EngineMaxRPM + 3000
|
||||
|
||||
local Ang = startAngleTach + (endAngleTach - startAngleTach) * (CurRPM / MaxRPM)
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
local FuelTank = self:GetFuelTank()
|
||||
|
||||
local UsesFuel = IsValid( FuelTank )
|
||||
|
||||
if self:GetEngineActive() then
|
||||
local Gear = self:GetGear()
|
||||
|
||||
local printGear = Gear
|
||||
|
||||
if Gear == -1 then
|
||||
printGear = self:GetReverse() and "R" or "D"
|
||||
else
|
||||
if self:GetReverse() then
|
||||
printGear = "-"..Gear
|
||||
end
|
||||
end
|
||||
|
||||
draw.DrawText( printGear, "LVS_FONT_HUD_HUMONGOUS", X + w * 0.5, Y + w * 0.23, color_white, TEXT_ALIGN_CENTER )
|
||||
else
|
||||
surface.SetMaterial( EntTable.IconEngine )
|
||||
if UsesFuel and FuelTank:GetFuel() <= 0 then
|
||||
surface.SetMaterial( EntTable.IconFuel )
|
||||
end
|
||||
|
||||
surface.SetDrawColor( Color(255,0,0, math.abs( math.cos( T * 5 ) ) * 255 ) )
|
||||
surface.DrawTexturedRectRotated( X + w * 0.5 + 2, Y + w * 0.35 - 1, w * 0.15, w * 0.15, 0 )
|
||||
end
|
||||
|
||||
if (EntTable._nextRefreshVel or 0) < T then
|
||||
EntTable._nextRefreshVel = T + 0.1
|
||||
EntTable._refreshVel = self:GetVelocity():Length()
|
||||
end
|
||||
|
||||
local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 )
|
||||
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + w * 0.81, Y + w * 0.6, color_white, TEXT_ALIGN_LEFT )
|
||||
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + w * 0.81 - 5, Y + w * 0.6, color_white, TEXT_ALIGN_RIGHT )
|
||||
|
||||
-- fuel, oil, coolant
|
||||
local barlength = w * 0.2
|
||||
if UsesFuel then
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1, barlength + 2, 7 )
|
||||
|
||||
local col = LVS.FUELTYPES[ FuelTank:GetFuelType() ].color
|
||||
surface.SetDrawColor( Color(col.r,col.g,col.b,255) )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5, barlength * FuelTank:GetFuel(), 5 )
|
||||
|
||||
draw.DrawText( "fuel", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 - 5, Color(255,150,0,255), TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
if self:HasQuickVar( "oil" ) then
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 10, barlength + 2, 7 )
|
||||
surface.SetDrawColor( 80, 80, 80, 255 )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 10, barlength * math.min(self:GetQuickVar( "oil" ),1), 5 )
|
||||
draw.DrawText( "oil pressure", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 5, Color(0, 0, 0, 255), TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
if self:HasQuickVar( "temp" ) then
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 20, barlength + 2, 7 )
|
||||
surface.SetDrawColor( 0, 127, 255, 255 )
|
||||
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 20, barlength * math.min(self:GetQuickVar( "temp" ),1), 5 )
|
||||
draw.DrawText( "coolant temp", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 15, Color(0, 0, 255, 255), TEXT_ALIGN_LEFT )
|
||||
end
|
||||
|
||||
-- brake, clutch, throttle bar
|
||||
local throttle = self:GetThrottle()
|
||||
local clutch = self:GetQuickVar( "clutch" )
|
||||
local hasClutch = self:HasQuickVar( "clutch" )
|
||||
local brake = self:GetBrake()
|
||||
local engine = self:GetEngine()
|
||||
if IsValid( engine ) then
|
||||
local ClutchActive = engine:GetClutch()
|
||||
|
||||
if not clutch then
|
||||
clutch = ClutchActive and 1 or 0
|
||||
end
|
||||
|
||||
if ClutchActive then
|
||||
throttle = math.max( throttle - clutch, 0 )
|
||||
end
|
||||
end
|
||||
surface.SetDrawColor( 0, 0, 0, 200 )
|
||||
surface.DrawRect( X + w * 0.3 - 1, Y + w * 0.4 - 1, 7, barlength + 2 )
|
||||
surface.DrawRect( X + w * 0.3 + 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 )
|
||||
if hasClutch then surface.DrawRect( X + w * 0.3 - 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 ) end
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
if hasClutch then
|
||||
local cllength = barlength * clutch
|
||||
surface.DrawRect( X + w * 0.3 - 10, Y + w * 0.4 + barlength - cllength, 5, cllength )
|
||||
end
|
||||
local brlength = barlength * brake
|
||||
surface.DrawRect( X + w * 0.3, Y + w * 0.4 + barlength - brlength, 5, brlength )
|
||||
local thrlength = barlength * throttle
|
||||
surface.DrawRect( X + w * 0.3 + 10, Y + w * 0.4 + barlength - thrlength, 5, thrlength )
|
||||
if hasClutch then draw.DrawText( "c", "LVS_FONT_PANEL", X + w * 0.3 - 7, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER ) end
|
||||
draw.DrawText( "b", "LVS_FONT_PANEL", X + w * 0.3 + 3, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER )
|
||||
draw.DrawText( "t", "LVS_FONT_PANEL", X + w * 0.3 + 13, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER )
|
||||
|
||||
|
||||
local TachRange = endAngleTach - startAngleTach
|
||||
local AngleRedline = startAngleTach + (TachRange / MaxRPM) * EntTable.EngineMaxRPM
|
||||
Ring:SetX( X + w * 0.5 )
|
||||
Ring:SetY( Y + w * 0.5 )
|
||||
Ring:SetRadius( w * 0.49 )
|
||||
Ring:SetOutlineWidth( w * 0.04 )
|
||||
Ring:SetStartAngle( startAngleTach )
|
||||
Ring:SetEndAngle( math.min( Ang, AngleRedline ) )
|
||||
Ring()
|
||||
|
||||
if Ang > AngleRedline then
|
||||
surface.SetDrawColor( 255, 0, 0, 255 )
|
||||
Ring:SetStartAngle( AngleRedline )
|
||||
Ring:SetEndAngle( math.min( Ang, endAngleTach ) )
|
||||
Ring()
|
||||
end
|
||||
|
||||
surface.SetDrawColor( 255, 255, 255, 255 )
|
||||
surface.SetMaterial( self:GetBakedTachMaterial( MaxRPM ) )
|
||||
surface.DrawTexturedRect( X, Y, w, w )
|
||||
|
||||
local CenterX = X + w * 0.5
|
||||
local CenterY = Y + w * 0.5
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
local AngX = math.cos( math.rad( Ang ) )
|
||||
local AngY = math.sin( math.rad( Ang ) )
|
||||
|
||||
if math.abs( Delta ) > 1 then
|
||||
local data = {
|
||||
StartX = (CenterX + AngX * TachNeedleRadiusInner),
|
||||
StartY = (CenterY + AngY * TachNeedleRadiusInner),
|
||||
EndX = (CenterX + AngX * TachNeedleRadiusOuter),
|
||||
EndY = (CenterY + AngY * TachNeedleRadiusOuter),
|
||||
Time = T + TachNeedleBlurTime
|
||||
}
|
||||
|
||||
table.insert( TachNeedles, data )
|
||||
else
|
||||
local StartX = CenterX + AngX * TachNeedleRadiusInner
|
||||
local StartY = CenterY + AngY * TachNeedleRadiusInner
|
||||
local EndX = CenterX + AngX * TachNeedleRadiusOuter
|
||||
local EndY = CenterY + AngY * TachNeedleRadiusOuter
|
||||
|
||||
surface.SetDrawColor( TachNeedleColor )
|
||||
surface.DrawLine( StartX, StartY, EndX, EndY )
|
||||
end
|
||||
|
||||
for index, data in pairs( TachNeedles ) do
|
||||
if data.Time < T then
|
||||
TachNeedles[ index ] = nil
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
local Brightness = (data.Time - T) / TachNeedleBlurTime
|
||||
|
||||
surface.SetDrawColor( Color( TachNeedleColor.r * Brightness, TachNeedleColor.g * Brightness, TachNeedleColor.b * Brightness, TachNeedleColor.a * Brightness ^ 2 ) )
|
||||
surface.DrawLine( data.StartX, data.StartY, data.EndX, data.EndY )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,192 @@
|
||||
include("shared.lua")
|
||||
include("sh_animations.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
include("cl_flyby.lua")
|
||||
include("cl_tiresounds.lua")
|
||||
include("cl_camera.lua")
|
||||
include("cl_hud.lua")
|
||||
include("cl_scrolltexture.lua")
|
||||
include("cl_exhausteffects.lua")
|
||||
|
||||
DEFINE_BASECLASS( "lvs_base" )
|
||||
|
||||
function ENT:CreateSubMaterial( SubMaterialID, name )
|
||||
if not SubMaterialID then return end
|
||||
|
||||
local mat = self:GetMaterials()[ SubMaterialID + 1 ]
|
||||
|
||||
if not mat then return end
|
||||
|
||||
local string_data = file.Read( "materials/"..mat..".vmt", "GAME" )
|
||||
|
||||
if not string_data then return end
|
||||
|
||||
return CreateMaterial( name, "VertexLitGeneric", util.KeyValuesToTable( string_data ) )
|
||||
end
|
||||
|
||||
function ENT:CalcPoseParameters()
|
||||
local steer = self:GetSteer() / self:GetMaxSteerAngle()
|
||||
|
||||
local kmh = math.Round( self:GetVelocity():Length() * 0.09144, 0 )
|
||||
|
||||
local lights = self:GetLightsHandler()
|
||||
local ammeter = 0.5
|
||||
|
||||
local rpm = 0
|
||||
local oil = 0.25
|
||||
|
||||
local gear = 1
|
||||
local clutch = 0
|
||||
|
||||
local throttle = self:GetThrottle()
|
||||
|
||||
local engine = self:GetEngine()
|
||||
local engineActive = self:GetEngineActive()
|
||||
|
||||
local fuel = 1
|
||||
local fueltank = self:GetFuelTank()
|
||||
|
||||
local handbrake = self:QuickLerp( "handbrake", self:GetNWHandBrake() and 1 or 0 )
|
||||
|
||||
local temperature = 0
|
||||
|
||||
if IsValid( engine ) then
|
||||
local EngineHealthFraction = engine:GetHP() / engine:GetMaxHP()
|
||||
|
||||
rpm = self:QuickLerp( "rpm", engine:GetRPM() )
|
||||
gear = engine:GetGear()
|
||||
oil = self:QuickLerp( "oil", engineActive and math.min( (EngineHealthFraction ^ 2) * 0.2 + (rpm / self.EngineMaxRPM) * 1.25 - (math.max( rpm - self.EngineMaxRPM, 0 ) / 2000) * 1.6, 1 ) or 0, 2 ) ^ 2
|
||||
|
||||
local ClutchActive = engine:GetClutch()
|
||||
|
||||
clutch = self:QuickLerp( "clutch", ClutchActive and 1 or 0 )
|
||||
|
||||
if ClutchActive then
|
||||
throttle = math.max( throttle - clutch, 0 )
|
||||
end
|
||||
|
||||
temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - EngineHealthFraction) ^ 2 * 1.25, 0.5 )
|
||||
else
|
||||
temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - self:GetHP() / self:GetMaxHP()) ^ 2 * 1.25, 0.5 )
|
||||
end
|
||||
|
||||
if IsValid( lights ) then
|
||||
local Available = 0.5 + (rpm / self.EngineMaxRPM) * 0.25
|
||||
|
||||
local Use1 = lights:GetActive() and 0.1 or 0
|
||||
local Use2 = lights:GetHighActive() and 0.15 or 0
|
||||
local Use3 = lights:GetFogActive() and 0.05 or 0
|
||||
local Use4 = (self:GetTurnMode() ~= 0 and self:GetTurnFlasher()) and 0.03 or 0
|
||||
|
||||
ammeter = self:QuickLerp( "ammeter", math.max( Available - Use1 - Use2 - Use3 - Use4, 0 ), math.Rand(1,10) )
|
||||
end
|
||||
|
||||
if IsValid( fueltank ) then
|
||||
fuel = self:QuickLerp( "fuel", fueltank:GetFuel() )
|
||||
end
|
||||
|
||||
self:UpdatePoseParameters( steer, self:QuickLerp( "kmh", kmh ), rpm, throttle, self:GetBrake(), handbrake, clutch, (self:GetReverse() and -gear or gear), temperature, fuel, oil, ammeter )
|
||||
self:InvalidateBoneCache()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if not self:IsInitialized() then return end
|
||||
|
||||
BaseClass.Think( self )
|
||||
|
||||
self:TireSoundThink()
|
||||
self:ExhaustEffectsThink()
|
||||
|
||||
if isfunction( self.UpdatePoseParameters ) then
|
||||
self:CalcPoseParameters()
|
||||
else
|
||||
self:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() )
|
||||
self:InvalidateBoneCache()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
self:TireSoundRemove()
|
||||
|
||||
BaseClass.OnRemove( self )
|
||||
end
|
||||
|
||||
function ENT:PostDrawTranslucent()
|
||||
local Handler = self:GetLightsHandler()
|
||||
|
||||
if not IsValid( Handler ) or not istable( self.Lights ) then return end
|
||||
|
||||
Handler:RenderLights( self, self.Lights )
|
||||
end
|
||||
|
||||
function ENT:OnEngineStallBroken()
|
||||
for i = 0,math.random(3,6) do
|
||||
timer.Simple( math.Rand(0,1.5) , function()
|
||||
if not IsValid( self ) then return end
|
||||
|
||||
self:DoExhaustBackFire()
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnChangeGear( oldGear, newGear )
|
||||
local HP = self:GetHP()
|
||||
local MaxHP = self:GetMaxHP()
|
||||
|
||||
local Engine = self:GetEngine()
|
||||
local EngineHP = 0
|
||||
local EngineMaxHP = 0
|
||||
|
||||
if IsValid( Engine ) then
|
||||
EngineHP = Engine:GetHP()
|
||||
EngineMaxHP = Engine:GetMaxHP()
|
||||
end
|
||||
|
||||
local Damaged = HP < MaxHP * 0.5
|
||||
local EngineDamaged = EngineHP < EngineMaxHP * 0.5
|
||||
|
||||
if (Damaged or EngineDamaged) then
|
||||
if oldGear > newGear then
|
||||
if Damaged then
|
||||
self:EmitSound( "lvs/vehicles/generic/gear_grind"..math.random(1,6)..".ogg", 75, math.Rand(70,100), 0.25 )
|
||||
self:DoExhaustBackFire()
|
||||
end
|
||||
else
|
||||
if EngineDamaged then
|
||||
self:DoExhaustBackFire()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:EmitSound( self.TransShiftSound, 75 )
|
||||
|
||||
if self:IsBackFireEnabled() then
|
||||
self:CalcExhaustPop()
|
||||
end
|
||||
end
|
||||
|
||||
self:SuppressViewPunch( self.TransShiftSpeed )
|
||||
end
|
||||
|
||||
function ENT:GetTurnFlasher()
|
||||
return math.cos( CurTime() * 8 + self:EntIndex() * 1337 ) > 0
|
||||
end
|
||||
|
||||
function ENT:OnEngineActiveChanged( Active )
|
||||
if Active then
|
||||
self:EmitSound( "lvs/vehicles/generic/engine_start1.wav", 75, 100, LVS.EngineVolume )
|
||||
else
|
||||
self:EmitSound( "vehicles/jetski/jetski_off.wav", 75, 100, LVS.EngineVolume )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetWheels()
|
||||
local wheels = {}
|
||||
|
||||
for _, ent in pairs( self:GetCrosshairFilterEnts() ) do
|
||||
if not IsValid( ent ) or ent:GetClass() ~= "lvs_wheeldrive_wheel" then continue end
|
||||
|
||||
table.insert( wheels, ent )
|
||||
end
|
||||
|
||||
return wheels
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
ENT.ScrollTextureData = {
|
||||
["$alphatest"] = "1",
|
||||
["$translate"] = "[0.0 0.0 0.0]",
|
||||
["$colorfix"] = "{255 255 255}",
|
||||
["Proxies"] = {
|
||||
["TextureTransform"] = {
|
||||
["translateVar"] = "$translate",
|
||||
["centerVar"] = "$center",
|
||||
["resultVar"] = "$basetexturetransform",
|
||||
},
|
||||
["Equals"] = {
|
||||
["srcVar1"] = "$colorfix",
|
||||
["resultVar"] = "$color",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ENT:GetRotationDelta( name, rot )
|
||||
name = "_deltaAng"..name
|
||||
|
||||
if not self[ name ] then self[ name ] = Angle(0,0,0) end
|
||||
|
||||
local ang = Angle(0,0,rot)
|
||||
local cur = ang:Right()
|
||||
local old = self[ name ]:Up()
|
||||
|
||||
local delta = self:AngleBetweenNormal( cur, old ) - 90
|
||||
|
||||
self[ name ] = ang
|
||||
|
||||
return delta
|
||||
end
|
||||
|
||||
function ENT:CalcScroll( name, rot )
|
||||
local delta = self:GetRotationDelta( name, rot )
|
||||
|
||||
name = "_deltaScroll"..name
|
||||
|
||||
if not self[ name ] then self[ name ] = 0 end
|
||||
|
||||
self[ name ] = self[ name ] + delta
|
||||
|
||||
if self[ name ] > 32768 then
|
||||
self[ name ] = self[ name ] - 32768
|
||||
end
|
||||
|
||||
if self[ name ] < -32768 then
|
||||
self[ name ] = self[ name ] + 32768
|
||||
end
|
||||
|
||||
return self[ name ]
|
||||
end
|
||||
|
||||
function ENT:ScrollTexture( name, material, pos )
|
||||
if not isstring( name ) or not isstring( material ) or not isvector( pos ) then return "" end
|
||||
|
||||
local id = self:EntIndex()
|
||||
local class = self:GetClass()
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local texture_name = class.."_["..id.."]_"..name
|
||||
|
||||
if istable( EntTable._StoredScrollTextures ) then
|
||||
if EntTable._StoredScrollTextures[ texture_name ] then
|
||||
self._StoredScrollTextures[ texture_name ]:SetVector("$translate", pos )
|
||||
|
||||
return "!"..texture_name
|
||||
end
|
||||
else
|
||||
EntTable._StoredScrollTextures = {}
|
||||
end
|
||||
|
||||
local data = table.Copy( EntTable.ScrollTextureData )
|
||||
data["$basetexture"] = material
|
||||
|
||||
local mat = CreateMaterial(texture_name, "VertexLitGeneric", data )
|
||||
|
||||
EntTable._StoredScrollTextures[ texture_name ] = mat
|
||||
|
||||
return "!"..texture_name
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
ENT.TireSoundFade = 0.15
|
||||
ENT.TireSoundTypes = {
|
||||
["roll"] = "lvs/vehicles/generic/wheel_roll.wav",
|
||||
["roll_racing"] = "lvs/vehicles/generic/wheel_roll.wav",
|
||||
["roll_dirt"] = "lvs/vehicles/generic/wheel_roll_dirt.wav",
|
||||
["roll_wet"] = "lvs/vehicles/generic/wheel_roll_wet.wav",
|
||||
["roll_damaged"] = "lvs/wheel_damaged_loop.wav",
|
||||
["skid"] = "lvs/vehicles/generic/wheel_skid.wav",
|
||||
["skid_racing"] = "lvs/vehicles/generic/wheel_skid_racing.wav",
|
||||
["skid_dirt"] = "lvs/vehicles/generic/wheel_skid_dirt.wav",
|
||||
["skid_wet"] = "lvs/vehicles/generic/wheel_skid_wet.wav",
|
||||
["tire_damage_layer"] = "lvs/wheel_destroyed_loop.wav",
|
||||
}
|
||||
ENT.TireSoundLevelSkid = 85
|
||||
ENT.TireSoundLevelRoll = 75
|
||||
|
||||
function ENT:TireSoundRemove()
|
||||
for snd, _ in pairs( self.TireSoundTypes ) do
|
||||
self:StopTireSound( snd )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TireSoundThink()
|
||||
for snd, _ in pairs( self.TireSoundTypes ) do
|
||||
local T = self:GetTireSoundTime( snd )
|
||||
|
||||
if T > 0 then
|
||||
local speed = self:GetVelocity():Length()
|
||||
|
||||
local sound = self:StartTireSound( snd )
|
||||
|
||||
if string.StartsWith( snd, "skid" ) or snd == "tire_damage_layer" then
|
||||
local vel = speed
|
||||
speed = math.max( math.abs( self:GetWheelVelocity() ) - vel, 0 ) * 5 + vel
|
||||
end
|
||||
|
||||
local volume = math.min(speed / 1000,1) ^ 2 * T
|
||||
local pitch = 100 + math.Clamp((speed - 400) / 200,0,155)
|
||||
|
||||
sound:ChangeVolume( volume, 0 )
|
||||
sound:ChangePitch( pitch, 0.5 )
|
||||
else
|
||||
self:StopTireSound( snd )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoTireSound( snd )
|
||||
if not istable( self._TireSounds ) then
|
||||
self._TireSounds = {}
|
||||
end
|
||||
|
||||
self._TireSounds[ snd ] = CurTime() + self.TireSoundFade
|
||||
end
|
||||
|
||||
function ENT:GetTireSoundTime( snd )
|
||||
if not istable( self._TireSounds ) or not self._TireSounds[ snd ] then return 0 end
|
||||
|
||||
return math.max(self._TireSounds[ snd ] - CurTime(),0) / self.TireSoundFade
|
||||
end
|
||||
|
||||
function ENT:StartTireSound( snd )
|
||||
if not self.TireSoundTypes[ snd ] or not istable( self._ActiveTireSounds ) then
|
||||
self._ActiveTireSounds = {}
|
||||
end
|
||||
|
||||
if self._ActiveTireSounds[ snd ] then return self._ActiveTireSounds[ snd ] end
|
||||
|
||||
local sound = CreateSound( self, self.TireSoundTypes[ snd ] )
|
||||
sound:SetSoundLevel( string.StartsWith( snd, "skid" ) and self.TireSoundLevelSkid or self.TireSoundLevelRoll )
|
||||
sound:PlayEx(0,100)
|
||||
|
||||
self._ActiveTireSounds[ snd ] = sound
|
||||
|
||||
return sound
|
||||
end
|
||||
|
||||
function ENT:StopTireSound( snd )
|
||||
if not istable( self._ActiveTireSounds ) or not self._ActiveTireSounds[ snd ] then return end
|
||||
|
||||
self._ActiveTireSounds[ snd ]:Stop()
|
||||
self._ActiveTireSounds[ snd ] = nil
|
||||
end
|
||||
@@ -0,0 +1,534 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_flyby.lua" )
|
||||
AddCSLuaFile( "cl_camera.lua" )
|
||||
AddCSLuaFile( "cl_hud.lua" )
|
||||
AddCSLuaFile( "cl_hud_speedometer.lua" )
|
||||
AddCSLuaFile( "cl_tiresounds.lua" )
|
||||
AddCSLuaFile( "cl_scrolltexture.lua" )
|
||||
AddCSLuaFile( "cl_exhausteffects.lua" )
|
||||
AddCSLuaFile( "sh_animations.lua" )
|
||||
AddCSLuaFile( "sh_camera_eyetrace.lua" )
|
||||
include("shared.lua")
|
||||
include("sh_animations.lua")
|
||||
include("sv_controls.lua")
|
||||
include("sv_controls_handbrake.lua")
|
||||
include("sv_components.lua")
|
||||
include("sv_ai.lua")
|
||||
include("sv_riggedwheels.lua")
|
||||
include("sv_wheelsystem.lua")
|
||||
include("sv_damage.lua")
|
||||
include("sv_pivotsteer.lua")
|
||||
include("sv_manualtransmission.lua")
|
||||
include("sv_engine.lua")
|
||||
include("sv_hydraulics.lua")
|
||||
include("sh_camera_eyetrace.lua")
|
||||
|
||||
ENT.DriverActiveSound = "common/null.wav"
|
||||
ENT.DriverInActiveSound = "common/null.wav"
|
||||
|
||||
DEFINE_BASECLASS( "lvs_base" )
|
||||
|
||||
local function SetMinimumAngularVelocityTo( new )
|
||||
local tbl = physenv.GetPerformanceSettings()
|
||||
|
||||
if tbl.MaxAngularVelocity < new then
|
||||
local OldAngVel = tbl.MaxAngularVelocity
|
||||
|
||||
tbl.MaxAngularVelocity = new
|
||||
physenv.SetPerformanceSettings( tbl )
|
||||
|
||||
print("[LVS-Cars] Wheels require higher MaxAngularVelocity to perform correctly! Increasing! "..OldAngVel.." =>"..new)
|
||||
end
|
||||
end
|
||||
|
||||
local function IsServerOK( class )
|
||||
|
||||
if GetConVar( "gmod_physiterations" ):GetInt() ~= 4 then
|
||||
RunConsoleCommand("gmod_physiterations", "4")
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:TracksCreate( PObj )
|
||||
end
|
||||
|
||||
local function DontDuplicatePaintSheme( ply, ent, data )
|
||||
ent.RandomColor = nil
|
||||
|
||||
if not duplicator or not duplicator.StoreEntityModifier then return end
|
||||
|
||||
duplicator.StoreEntityModifier( ent, "lvsVehiclePaintSheme", data )
|
||||
end
|
||||
|
||||
if duplicator and duplicator.RegisterEntityModifier then
|
||||
duplicator.RegisterEntityModifier( "lvsVehiclePaintSheme", DontDuplicatePaintSheme )
|
||||
end
|
||||
|
||||
function ENT:PostInitialize( PObj )
|
||||
|
||||
self:TracksCreate( PObj )
|
||||
|
||||
if not IsServerOK( self:GetClass() ) then
|
||||
self:Remove()
|
||||
print("[LVS] ERROR COULDN'T INITIALIZE VEHICLE!")
|
||||
end
|
||||
|
||||
if istable( self.Lights ) then
|
||||
self:AddLights()
|
||||
end
|
||||
|
||||
if istable( self.RandomColor ) then
|
||||
local data = self.RandomColor[ math.random( #self.RandomColor ) ]
|
||||
|
||||
if IsColor( data ) then
|
||||
self:SetColor( data )
|
||||
else
|
||||
self:SetSkin( data.Skin or 0 )
|
||||
self:SetColor( data.Color or color_white )
|
||||
|
||||
if istable( data.Wheels ) then
|
||||
self._WheelSkin = data.Wheels.Skin or 0
|
||||
self._WheelColor = data.Wheels.Color or color_white
|
||||
end
|
||||
|
||||
if istable( data.BodyGroups ) then
|
||||
for id, subgroup in pairs( data.BodyGroups ) do
|
||||
self:SetBodygroup( id, subgroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DontDuplicatePaintSheme( NULL, self, {} )
|
||||
end
|
||||
|
||||
BaseClass.PostInitialize( self, PObj )
|
||||
|
||||
if isstring( self.HornSound ) and isvector( self.HornPos ) and #self.WEAPONS[1] == 0 then
|
||||
if IsValid( self.HornSND ) then self.HornSND:Remove() end
|
||||
|
||||
self.HornSND = self:AddSoundEmitter( self.HornPos or vector_origin, self.HornSound, self.HornSoundInterior )
|
||||
self.HornSND:SetSoundLevel( 75 )
|
||||
self.HornSND:SetDoppler( true )
|
||||
end
|
||||
|
||||
if istable( self.SirenSound ) then
|
||||
if IsValid( self.SirenSND ) then self.SirenSND:Remove() end
|
||||
|
||||
self.SirenSND = self:AddSoundEmitter( self.SirenPos or vector_origin, "common/null.wav" )
|
||||
self.SirenSND:SetSoundLevel( 75 )
|
||||
self.SirenSND:SetDoppler( true )
|
||||
end
|
||||
|
||||
PObj:SetMass( self.PhysicsMass * self.PhysicsWeightScale )
|
||||
PObj:EnableDrag( false )
|
||||
PObj:SetInertia( self.PhysicsInertia * self.PhysicsWeightScale )
|
||||
|
||||
SetMinimumAngularVelocityTo( 24000 )
|
||||
|
||||
if BRANCH == "dev" or BRANCH == "x86-64" then
|
||||
for _, wheel in pairs( self:GetWheels() ) do
|
||||
if not IsValid( wheel ) then continue end
|
||||
|
||||
wheel:SetLightingOriginEntity( self )
|
||||
end
|
||||
end
|
||||
|
||||
self:EnableHandbrake()
|
||||
end
|
||||
|
||||
function ENT:AlignView( ply )
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
timer.Simple( 0, function()
|
||||
if not IsValid( ply ) or not IsValid( self ) then return end
|
||||
|
||||
local Ang = Angle(0,90,0)
|
||||
|
||||
local pod = ply:GetVehicle()
|
||||
local MouseAim = ply:lvsMouseAim() and self:GetDriver() == ply
|
||||
|
||||
if MouseAim and IsValid( pod ) then
|
||||
Ang = pod:LocalToWorldAngles( Angle(0,90,0) )
|
||||
Ang.r = 0
|
||||
end
|
||||
|
||||
ply:SetEyeAngles( Ang )
|
||||
end)
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulateOverride( ForceAngle, phys, deltatime, simulate )
|
||||
|
||||
if not self:GetRacingTires() then
|
||||
return ForceAngle, vector_origin, simulate
|
||||
end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local WheelSideForce = EntTable.WheelSideForce * EntTable.ForceLinearMultiplierRacingTires
|
||||
local ForceLinear = Vector(0,0,0)
|
||||
|
||||
for id, wheel in pairs( self:GetWheels() ) do
|
||||
if wheel:IsHandbrakeActive() or not wheel:PhysicsOnGround() then continue end
|
||||
|
||||
local AxleAng = wheel:GetDirectionAngle()
|
||||
|
||||
local Forward = AxleAng:Forward()
|
||||
local Right = AxleAng:Right()
|
||||
local Up = AxleAng:Up()
|
||||
|
||||
local wheelPos = wheel:GetPos()
|
||||
local wheelVel = phys:GetVelocityAtPoint( wheelPos )
|
||||
local wheelRadius = wheel:GetRadius()
|
||||
|
||||
local Slip = math.Clamp(1 - self:AngleBetweenNormal( Forward, wheelVel:GetNormalized() ) / 90,0,1)
|
||||
|
||||
local ForwardVel = self:VectorSplitNormal( Forward, wheelVel )
|
||||
|
||||
Force = -Right * self:VectorSplitNormal( Right, wheelVel ) * WheelSideForce * Slip
|
||||
local wSideForce, wAngSideForce = phys:CalculateVelocityOffset( Force, wheelPos )
|
||||
|
||||
ForceAngle:Add( Vector(0,0,wAngSideForce.z) )
|
||||
ForceLinear:Add( wSideForce )
|
||||
end
|
||||
|
||||
return ForceAngle, ForceLinear, simulate
|
||||
end
|
||||
|
||||
function ENT:PhysicsSimulate( phys, deltatime )
|
||||
|
||||
if self:GetEngineActive() then phys:Wake() end
|
||||
|
||||
local ent = phys:GetEntity()
|
||||
|
||||
if ent == self then
|
||||
local Vel = 0
|
||||
|
||||
for _, wheel in pairs( self:GetWheels() ) do
|
||||
if wheel:GetTorqueFactor() <= 0 then continue end
|
||||
|
||||
local wheelVel = wheel:RPMToVel( math.abs( wheel:GetRPM() or 0 ) )
|
||||
|
||||
if wheelVel > Vel then
|
||||
Vel = wheelVel
|
||||
end
|
||||
end
|
||||
|
||||
self:SetWheelVelocity( Vel )
|
||||
|
||||
if not self:StabilityAssist() or not self:WheelsOnGround() then return self:PhysicsSimulateOverride( Vector(0,0,0), phys, deltatime, SIM_NOTHING ) end
|
||||
|
||||
local ForceAngle = Vector(0,0, math.deg( -phys:GetAngleVelocity().z ) * math.min( phys:GetVelocity():Length() / self.PhysicsDampingSpeed, 1 ) * self.ForceAngleMultiplier )
|
||||
|
||||
return self:PhysicsSimulateOverride( ForceAngle, phys, deltatime, SIM_GLOBAL_ACCELERATION )
|
||||
end
|
||||
|
||||
if not self:AlignWheel( ent ) or self:IsDestroyed() then self:EnableHandbrake() return vector_origin, vector_origin, SIM_NOTHING end
|
||||
|
||||
local WheelTable = ent:GetTable()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if ent:IsHandbrakeActive() then
|
||||
if WheelTable.SetRPM then
|
||||
ent:SetRPM( 0 )
|
||||
end
|
||||
|
||||
return vector_origin, vector_origin, SIM_NOTHING
|
||||
end
|
||||
|
||||
local T = CurTime()
|
||||
|
||||
if (WheelTable._NextSimulate or 0) < T or not WheelTable.Simulate then
|
||||
WheelTable._NextSimulate = T + ((self:PivotSteer() or self:GetBrake() > 0) and EntTable.WheelTickIntervalBraking or EntTable.WheelTickInterval)
|
||||
|
||||
WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate = self:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime )
|
||||
end
|
||||
|
||||
return WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate
|
||||
end
|
||||
|
||||
function ENT:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime )
|
||||
local RotationAxis = ent:GetRotationAxis()
|
||||
|
||||
local curRPM = self:VectorSplitNormal( RotationAxis, phys:GetAngleVelocity() ) / 6
|
||||
|
||||
local Throttle = self:GetThrottle()
|
||||
|
||||
ent:SetRPM( curRPM )
|
||||
|
||||
local ForceAngle = vector_origin
|
||||
local ForceLinear = Vector(0,0,0)
|
||||
|
||||
local TorqueFactor = ent:GetTorqueFactor()
|
||||
|
||||
local IsBraking = self:GetBrake() > 0
|
||||
local IsBrakingWheel = (TorqueFactor * Throttle) <= 0.99
|
||||
|
||||
if IsBraking and IsBrakingWheel then
|
||||
if ent:IsRotationLocked() then
|
||||
ForceAngle = vector_origin
|
||||
else
|
||||
local ForwardVel = self:VectorSplitNormal( ent:GetDirectionAngle():Forward(), phys:GetVelocity() )
|
||||
|
||||
local targetRPM = ent:VelToRPM( ForwardVel ) * 0.5
|
||||
|
||||
if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM then
|
||||
ent:LockRotation()
|
||||
else
|
||||
if (ForwardVel > 0 and targetRPM > 0) or (ForwardVel < 0 and targetRPM < 0) then
|
||||
ForceAngle = RotationAxis * math.Clamp( (targetRPM - curRPM) / 100,-1,1) * math.deg( EntTable.WheelBrakeForce ) * ent:GetBrakeFactor() * self:GetBrake()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM and Throttle == 0 then
|
||||
ent:LockRotation()
|
||||
else
|
||||
if ent:IsRotationLocked() then
|
||||
ent:ReleaseRotation()
|
||||
end
|
||||
end
|
||||
|
||||
if TorqueFactor > 0 and Throttle > 0 then
|
||||
local engineTorque = self:GetEngineTorque()
|
||||
|
||||
local targetVelocity = self:GetTargetVelocity()
|
||||
|
||||
local targetRPM = ent:VelToRPM( targetVelocity )
|
||||
|
||||
local targetRPMabs = math.abs( targetRPM )
|
||||
|
||||
local powerRPM = targetRPMabs * EntTable.EngineCurve
|
||||
|
||||
local powerCurve = (powerRPM + math.max( targetRPMabs - powerRPM,0) - math.max(math.abs(curRPM) - powerRPM,0)) / targetRPMabs * self:Sign( targetRPM - curRPM )
|
||||
|
||||
local Torque = powerCurve * engineTorque * TorqueFactor * Throttle
|
||||
|
||||
local BoostRPM = 0
|
||||
|
||||
if self:GetReverse() then
|
||||
Torque = math.min( Torque, 0 )
|
||||
|
||||
BoostRPM = ent:VelToRPM( EntTable.MaxVelocityReverse / EntTable.TransGearsReverse ) * 0.5
|
||||
else
|
||||
Torque = math.max( Torque, 0 )
|
||||
|
||||
BoostRPM = ent:VelToRPM( EntTable.MaxVelocity / EntTable.TransGears ) * 0.5
|
||||
end
|
||||
|
||||
local BoostMul = math.max( EntTable.EngineCurveBoostLow, 0 )
|
||||
local BoostStart = 1 + BoostMul
|
||||
|
||||
local TorqueBoost = BoostStart - (math.min( math.max( math.abs( curRPM ) - BoostRPM, 0 ), BoostRPM) / BoostRPM) * BoostMul
|
||||
|
||||
local Forward = ent:GetDirectionAngle():Forward()
|
||||
|
||||
local curVelocity = self:VectorSplitNormal( Forward, phys:GetVelocity() )
|
||||
|
||||
if targetVelocity >= 0 then
|
||||
if curVelocity < targetVelocity then
|
||||
ForceAngle = RotationAxis * Torque * TorqueBoost
|
||||
end
|
||||
else
|
||||
if curVelocity > targetVelocity then
|
||||
ForceAngle = RotationAxis * Torque * TorqueBoost
|
||||
end
|
||||
end
|
||||
|
||||
if self:PivotSteer() then
|
||||
local RotationDirection = ent:GetWheelType() * self:GetPivotSteer()
|
||||
|
||||
if EntTable.PivotSteerByBrake and RotationDirection < 0 then
|
||||
ent:LockRotation( true )
|
||||
|
||||
return vector_origin, vector_origin, SIM_NOTHING
|
||||
end
|
||||
|
||||
powerCurve = math.Clamp((EntTable.PivotSteerWheelRPM * RotationDirection - curRPM) / EntTable.PivotSteerWheelRPM,-1,1)
|
||||
|
||||
Torque = powerCurve * engineTorque * TorqueFactor * Throttle * 2 * EntTable.PivotSteerTorqueMul
|
||||
|
||||
ForceAngle = RotationAxis * Torque
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not self:StabilityAssist() or not self:WheelsOnGround() then return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION end
|
||||
|
||||
local Vel = phys:GetVelocity()
|
||||
|
||||
local ForwardAngle = ent:GetDirectionAngle()
|
||||
|
||||
local Forward = ForwardAngle:Forward()
|
||||
local Right = ForwardAngle:Right()
|
||||
|
||||
local Fy = self:VectorSplitNormal( Right, Vel )
|
||||
local Fx = self:VectorSplitNormal( Forward, Vel )
|
||||
|
||||
if TorqueFactor >= 1 then
|
||||
local VelX = math.abs( Fx )
|
||||
local VelY = math.abs( Fy )
|
||||
|
||||
if VelY > VelX * 0.1 then
|
||||
if VelX > EntTable.FastSteerActiveVelocity then
|
||||
if VelY < VelX * 0.6 then
|
||||
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
|
||||
end
|
||||
else
|
||||
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if IsBraking and not IsBrakingWheel then
|
||||
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
|
||||
end
|
||||
|
||||
ForceLinear:Add( -self:GetUp() * EntTable.WheelDownForce * TorqueFactor )
|
||||
|
||||
if not self:GetRacingTires() then
|
||||
ForceLinear:Add( -Right * math.Clamp(Fy * 5 * math.min( math.abs( Fx ) / 500, 1 ),-EntTable.WheelSideForce,EntTable.WheelSideForce) * EntTable.ForceLinearMultiplier )
|
||||
end
|
||||
|
||||
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
|
||||
end
|
||||
|
||||
function ENT:SteerTo( TargetValue, MaxSteer )
|
||||
local Cur = self:GetSteer() / MaxSteer
|
||||
|
||||
local Diff = TargetValue - Cur
|
||||
|
||||
local Returning = (Diff > 0 and Cur < 0) or (Diff < 0 and Cur > 0)
|
||||
|
||||
local Rate = FrameTime() * (Returning and self.SteerReturnSpeed or self.SteerSpeed)
|
||||
|
||||
local New = (Cur + math.Clamp(Diff,-Rate,Rate))
|
||||
|
||||
self:SetSteer( New * MaxSteer )
|
||||
|
||||
if New == 0 or self:GetEngineActive() then return end
|
||||
|
||||
for _, wheel in pairs( self:GetWheels() ) do
|
||||
if not IsValid( wheel ) then continue end
|
||||
|
||||
wheel:PhysWake()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnDriverEnterVehicle( ply )
|
||||
end
|
||||
|
||||
function ENT:OnDriverExitVehicle( ply )
|
||||
end
|
||||
|
||||
function ENT:OnDriverChanged( Old, New, VehicleIsActive )
|
||||
self:OnPassengerChanged( Old, New, 1 )
|
||||
|
||||
if VehicleIsActive then
|
||||
|
||||
self:OnDriverEnterVehicle( New )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:OnDriverExitVehicle( Old )
|
||||
self:SetThrottle( 0 )
|
||||
|
||||
if self:GetBrake() > 0 then
|
||||
self:SetBrake( 0 )
|
||||
self:EnableHandbrake()
|
||||
self:StopEngine()
|
||||
self:SetTurnMode( 0 )
|
||||
|
||||
local LightsHandler = self:GetLightsHandler()
|
||||
|
||||
if IsValid( LightsHandler ) then
|
||||
LightsHandler:SetActive( false )
|
||||
LightsHandler:SetHighActive( false )
|
||||
LightsHandler:SetFogActive( false )
|
||||
end
|
||||
else
|
||||
if not self:GetEngineActive() then
|
||||
self:SetBrake( 0 )
|
||||
self:EnableHandbrake()
|
||||
end
|
||||
end
|
||||
|
||||
self:SetReverse( false )
|
||||
|
||||
self:StopSiren()
|
||||
|
||||
if self:GetSirenMode() > 0 then
|
||||
self:SetSirenMode( 0 )
|
||||
end
|
||||
|
||||
if IsValid( self.HornSND ) then
|
||||
self.HornSND:Stop()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnRefueled()
|
||||
local FuelTank = self:GetFuelTank()
|
||||
|
||||
if not IsValid( FuelTank ) then return end
|
||||
|
||||
FuelTank:EmitSound( "vehicles/jetski/jetski_no_gas_start.wav" )
|
||||
end
|
||||
|
||||
function ENT:OnMaintenance()
|
||||
local FuelTank = self:GetFuelTank()
|
||||
local Engine = self:GetEngine()
|
||||
|
||||
if IsValid( Engine ) then
|
||||
Engine:SetHP( Engine:GetMaxHP() )
|
||||
Engine:SetDestroyed( false )
|
||||
end
|
||||
|
||||
if IsValid( FuelTank ) then
|
||||
FuelTank:ExtinguishAndRepair()
|
||||
|
||||
if FuelTank:GetFuel() ~= 1 then
|
||||
FuelTank:SetFuel( 1 )
|
||||
|
||||
self:OnRefueled()
|
||||
end
|
||||
end
|
||||
|
||||
for _, wheel in pairs( self:GetWheels() ) do
|
||||
if not IsValid( wheel ) then continue end
|
||||
|
||||
wheel:SetHP( wheel:GetMaxHP() )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnSuperCharged( enable )
|
||||
end
|
||||
|
||||
function ENT:OnTurboCharged( enable )
|
||||
end
|
||||
|
||||
function ENT:ApproachTargetAngle( TargetAngle )
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return end
|
||||
|
||||
local ang = pod:GetAngles()
|
||||
ang:RotateAroundAxis( self:GetUp(), 90 )
|
||||
|
||||
local Forward = ang:Right()
|
||||
local View = pod:WorldToLocalAngles( TargetAngle ):Forward()
|
||||
|
||||
local Reversed = false
|
||||
if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then
|
||||
Reversed = self:GetReverse()
|
||||
end
|
||||
|
||||
local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle
|
||||
|
||||
local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer ))
|
||||
|
||||
self:SteerTo( Reversed and Steer or -Steer, self:GetMaxSteerAngle() )
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
function ENT:CalcMainActivityPassenger( ply )
|
||||
end
|
||||
|
||||
function ENT:CalcMainActivity( ply )
|
||||
if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end
|
||||
|
||||
if ply.m_bWasNoclipping then
|
||||
ply.m_bWasNoclipping = nil
|
||||
ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM )
|
||||
|
||||
if CLIENT then
|
||||
ply:SetIK( true )
|
||||
end
|
||||
end
|
||||
|
||||
ply.CalcIdeal = ACT_STAND
|
||||
ply.CalcSeqOverride = ply:LookupSequence( "drive_jeep" )
|
||||
|
||||
return ply.CalcIdeal, ply.CalcSeqOverride
|
||||
end
|
||||
|
||||
function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed )
|
||||
ply:SetPlaybackRate( 1 )
|
||||
|
||||
if CLIENT then
|
||||
if ply == self:GetDriver() then
|
||||
ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() )
|
||||
ply:InvalidateBoneCache()
|
||||
end
|
||||
|
||||
GAMEMODE:GrabEarAnimation( ply )
|
||||
GAMEMODE:MouthMoveAnimation( ply )
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
function ENT:GetEyeTrace( trace_forward )
|
||||
local startpos = self:LocalToWorld( self:OBBCenter() )
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if IsValid( pod ) then
|
||||
startpos = pod:LocalToWorld( pod:OBBCenter() )
|
||||
end
|
||||
|
||||
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
|
||||
|
||||
local data = {
|
||||
start = startpos,
|
||||
endpos = (startpos + AimVector * 50000),
|
||||
filter = self:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( data )
|
||||
|
||||
return trace
|
||||
end
|
||||
|
||||
function ENT:GetAimVector()
|
||||
if self:GetAI() then
|
||||
return self:GetAIAimVector()
|
||||
end
|
||||
|
||||
local pod = self:GetDriverSeat()
|
||||
|
||||
if not IsValid( pod ) then return self:GetForward() end
|
||||
|
||||
local Driver = self:GetDriver()
|
||||
|
||||
if not IsValid( Driver ) then return pod:GetForward() end
|
||||
|
||||
if Driver:lvsMouseAim() then
|
||||
if SERVER then
|
||||
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
|
||||
else
|
||||
return Driver:EyeAngles():Forward()
|
||||
end
|
||||
else
|
||||
if SERVER then
|
||||
return Driver:EyeAngles():Forward()
|
||||
else
|
||||
return pod:LocalToWorldAngles( Driver:EyeAngles() ):Forward()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,345 @@
|
||||
|
||||
ENT.Base = "lvs_base"
|
||||
|
||||
ENT.PrintName = "[LVS] Wheeldrive Base"
|
||||
ENT.Author = "Luna"
|
||||
ENT.Information = "Luna's Vehicle Script"
|
||||
ENT.Category = "[LVS] - Cars"
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
|
||||
ENT.MaxHealth = 600
|
||||
ENT.MaxHealthEngine = 50
|
||||
ENT.MaxHealthFuelTank = 10
|
||||
|
||||
ENT.MaxVelocity = 1400
|
||||
ENT.MaxVelocityReverse = 700
|
||||
|
||||
ENT.EngineCurve = 0.65
|
||||
ENT.EngineCurveBoostLow = 1
|
||||
ENT.EngineTorque = 350
|
||||
ENT.EngineIdleRPM = 1000
|
||||
ENT.EngineMaxRPM = 6000
|
||||
|
||||
ENT.ThrottleRate = 3.5
|
||||
ENT.BrakeRate = 3.5
|
||||
|
||||
ENT.ForceLinearMultiplier = 1
|
||||
ENT.ForceLinearMultiplierRacingTires = 1.15
|
||||
ENT.ForceAngleMultiplier = 0.5
|
||||
|
||||
ENT.TransGears = 4
|
||||
ENT.TransGearsReverse = 1
|
||||
ENT.TransMinGearHoldTime = 1
|
||||
ENT.TransShiftSpeed = 0.3
|
||||
ENT.TransShiftTorqueFactor = 0
|
||||
ENT.TransWobble = 40
|
||||
ENT.TransWobbleTime = 1.5
|
||||
ENT.TransWobbleFrequencyMultiplier = 1
|
||||
ENT.TransShiftSound = "lvs/vehicles/generic/gear_shift.wav"
|
||||
|
||||
ENT.SteerSpeed = 3
|
||||
ENT.SteerReturnSpeed = 10
|
||||
|
||||
ENT.FastSteerActiveVelocity = 500
|
||||
ENT.FastSteerAngleClamp = 10
|
||||
ENT.FastSteerDeactivationDriftAngle = 7
|
||||
|
||||
ENT.SteerAssistDeadZoneAngle = 1
|
||||
ENT.SteerAssistMaxAngle = 15
|
||||
ENT.SteerAssistExponent = 1.5
|
||||
ENT.SteerAssistMultiplier = 3
|
||||
|
||||
ENT.MouseSteerAngle = 20
|
||||
ENT.MouseSteerExponent = 2
|
||||
|
||||
ENT.PhysicsWeightScale = 1
|
||||
ENT.PhysicsMass = 1000
|
||||
ENT.PhysicsInertia = Vector(1500,1500,750)
|
||||
ENT.PhysicsDampingSpeed = 4000
|
||||
ENT.PhysicsDampingForward = true
|
||||
ENT.PhysicsDampingReverse = false
|
||||
|
||||
ENT.WheelTickInterval = 0.2
|
||||
ENT.WheelTickIntervalBraking = 0.02
|
||||
|
||||
ENT.WheelPhysicsMass = 100
|
||||
ENT.WheelPhysicsInertia = Vector(10,8,10)
|
||||
ENT.WheelPhysicsTireHeight = 4
|
||||
ENT.WheelPhysicsMaterials = {
|
||||
[0] = "friction_00", -- 0
|
||||
[1] = "friction_10", -- 0.1
|
||||
[2] = "friction_25", -- 0.25
|
||||
[3] = "rubber", -- 0.8
|
||||
[4] = "rubber",
|
||||
[5] = "rubber",
|
||||
[6] = "rubber",
|
||||
[7] = "rubber",
|
||||
[8] = "rubber",
|
||||
[9] = "rubber",
|
||||
[10] = "jeeptire", -- 1.337 -- i don't believe friction in havok can go above 1, however other settings such as bouncyness and elasticity are affected by it as it seems. We use jeeptire as default even tho it technically isn't the "best" choice, but rather the most common one
|
||||
[11] = "jalopytire", -- 1.337
|
||||
[12] = "phx_tire_normal", -- 3
|
||||
}
|
||||
|
||||
ENT.AutoReverseVelocity = 50
|
||||
|
||||
ENT.WheelBrakeLockupRPM = 20
|
||||
|
||||
ENT.WheelBrakeForce = 400
|
||||
|
||||
ENT.WheelSideForce = 800
|
||||
ENT.WheelDownForce = 500
|
||||
|
||||
ENT.AllowSuperCharger = true
|
||||
ENT.SuperChargerVolume = 0.6
|
||||
ENT.SuperChargerSound = "lvs/vehicles/generic/supercharger_loop.wav"
|
||||
ENT.SuperChargerVisible = true
|
||||
|
||||
ENT.AllowTurbo = true
|
||||
ENT.TurboVolume = 0.6
|
||||
ENT.TurboSound = "lvs/vehicles/generic/turbo_loop.wav"
|
||||
ENT.TurboBlowOff = {"lvs/vehicles/generic/turbo_blowoff1.wav","lvs/vehicles/generic/turbo_blowoff2.wav"}
|
||||
|
||||
ENT.DeleteOnExplode = false
|
||||
|
||||
ENT.lvsAllowEngineTool = true
|
||||
ENT.lvsShowInSpawner = false
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:CreateBaseDT()
|
||||
|
||||
self:AddDT( "Float", "Steer" )
|
||||
self:AddDT( "Float", "Throttle" )
|
||||
self:AddDT( "Float", "MaxThrottle" )
|
||||
self:AddDT( "Float", "Brake" )
|
||||
|
||||
self:AddDT( "Float", "NWMaxSteer" )
|
||||
self:AddDT( "Float", "WheelVelocity" )
|
||||
|
||||
self:AddDT( "Int", "NWGear" )
|
||||
self:AddDT( "Int", "TurnMode" )
|
||||
self:AddDT( "Int", "SirenMode" )
|
||||
|
||||
self:AddDT( "Bool", "Reverse" )
|
||||
self:AddDT( "Bool", "NWHandBrake" )
|
||||
|
||||
self:AddDT( "Bool", "RacingHud" )
|
||||
self:AddDT( "Bool", "RacingTires" )
|
||||
self:AddDT( "Bool", "Backfire" )
|
||||
|
||||
self:AddDT( "Entity", "Engine" )
|
||||
self:AddDT( "Entity", "FuelTank" )
|
||||
self:AddDT( "Entity", "LightsHandler" )
|
||||
self:AddDT( "Entity", "Turbo" )
|
||||
self:AddDT( "Entity", "Compressor" )
|
||||
|
||||
self:AddDT( "Vector", "AIAimVector" )
|
||||
|
||||
self:TrackSystemDT()
|
||||
|
||||
if SERVER then
|
||||
self:SetMaxThrottle( 1 )
|
||||
self:SetSirenMode( -1 )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TrackSystemDT()
|
||||
end
|
||||
|
||||
function ENT:StabilityAssist()
|
||||
if self:GetReverse() then
|
||||
return self.PhysicsDampingReverse
|
||||
end
|
||||
|
||||
return self.PhysicsDampingForward
|
||||
end
|
||||
|
||||
function ENT:GetMaxSteerAngle()
|
||||
if CLIENT then return self:GetNWMaxSteer() end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if EntTable._WheelMaxSteerAngle then return EntTable._WheelMaxSteerAngle end
|
||||
|
||||
local Cur = 0
|
||||
|
||||
for _, Axle in pairs( EntTable._WheelAxleData ) do
|
||||
if not Axle.SteerAngle then continue end
|
||||
|
||||
if Axle.SteerAngle > Cur then
|
||||
Cur = Axle.SteerAngle
|
||||
end
|
||||
end
|
||||
|
||||
EntTable._WheelMaxSteerAngle = Cur
|
||||
|
||||
self:SetNWMaxSteer( Cur )
|
||||
|
||||
return Cur
|
||||
end
|
||||
|
||||
function ENT:GetTargetVelocity()
|
||||
local Reverse = self:GetReverse()
|
||||
|
||||
if self:IsManualTransmission() then
|
||||
local Gear = self:GetGear()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local NumGears = Reverse and EntTable.TransGearsReverse or EntTable.TransGears
|
||||
local MaxVelocity = Reverse and EntTable.MaxVelocityReverse or EntTable.MaxVelocity
|
||||
|
||||
local GearedVelocity = math.min( (MaxVelocity / NumGears) * (Gear + 1), MaxVelocity )
|
||||
|
||||
return GearedVelocity * (Reverse and -1 or 1)
|
||||
end
|
||||
|
||||
if Reverse then
|
||||
return -self.MaxVelocityReverse
|
||||
end
|
||||
|
||||
return self.MaxVelocity
|
||||
end
|
||||
|
||||
function ENT:HasHighBeams()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if isbool( EntTable._HasHighBeams ) then return EntTable._HasHighBeams end
|
||||
|
||||
if not istable( EntTable.Lights ) then return false end
|
||||
|
||||
local HasHigh = false
|
||||
|
||||
for _, data in pairs( EntTable.Lights ) do
|
||||
if not istable( data ) then continue end
|
||||
|
||||
for id, typedata in pairs( data ) do
|
||||
if id == "Trigger" and typedata == "high" then
|
||||
HasHigh = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EntTable._HasHighBeams = HasHigh
|
||||
|
||||
return HasHigh
|
||||
end
|
||||
|
||||
function ENT:HasFogLights()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if isbool( EntTable._HasFogLights ) then return EntTable._HasFogLights end
|
||||
|
||||
if not istable( EntTable.Lights ) then return false end
|
||||
|
||||
local HasFog = false
|
||||
|
||||
for _, data in pairs( EntTable.Lights ) do
|
||||
if not istable( data ) then continue end
|
||||
|
||||
for id, typedata in pairs( data ) do
|
||||
if id == "Trigger" and typedata == "fog" then
|
||||
HasFog = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EntTable._HasFogLights = HasFog
|
||||
|
||||
return HasFog
|
||||
end
|
||||
|
||||
function ENT:HasTurnSignals()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if isbool( EntTable._HasTurnSignals ) then return EntTable._HasTurnSignals end
|
||||
|
||||
if not istable( EntTable.Lights ) then return false end
|
||||
|
||||
local HasTurnSignals = false
|
||||
|
||||
for _, data in pairs( EntTable.Lights ) do
|
||||
if not istable( data ) then continue end
|
||||
|
||||
for id, typedata in pairs( data ) do
|
||||
if id == "Trigger" and (typedata == "turnleft" or typedata == "turnright" or typedata == "main+brake+turnleft" or typedata == "main+brake+turnright") then
|
||||
HasTurnSignals = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EntTable._HasTurnSignals = HasTurnSignals
|
||||
|
||||
return HasTurnSignals
|
||||
end
|
||||
|
||||
function ENT:GetGear()
|
||||
local Gear = self:GetNWGear()
|
||||
|
||||
if Gear <= 0 then
|
||||
return -1
|
||||
end
|
||||
|
||||
if self:GetReverse() then
|
||||
return math.Clamp( Gear, 1, self.TransGearsReverse )
|
||||
end
|
||||
|
||||
return math.Clamp( Gear, 1, self.TransGears )
|
||||
end
|
||||
|
||||
function ENT:IsManualTransmission()
|
||||
return self:GetNWGear() > 0
|
||||
end
|
||||
|
||||
function ENT:BodygroupIsValid( name, groups )
|
||||
if not name or not istable( groups ) then return false end
|
||||
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
local id = -1
|
||||
|
||||
if EntTable._StoredBodyGroups then
|
||||
if EntTable._StoredBodyGroups[ name ] then
|
||||
id = EntTable._StoredBodyGroups[ name ]
|
||||
end
|
||||
else
|
||||
EntTable._StoredBodyGroups = {}
|
||||
end
|
||||
|
||||
if id == -1 then
|
||||
for _, data in pairs( self:GetBodyGroups() ) do
|
||||
if data.name == name then
|
||||
id = data.id
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if id == -1 then return false end
|
||||
|
||||
EntTable._StoredBodyGroups[ name ] = id
|
||||
|
||||
local cur = self:GetBodygroup( id )
|
||||
|
||||
for _, active in pairs( groups ) do
|
||||
if cur == active then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:GetWheelUp()
|
||||
return self:GetUp()
|
||||
end
|
||||
|
||||
function ENT:GetVehicleType()
|
||||
return "car"
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user