mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
1354 lines
39 KiB
Lua
1354 lines
39 KiB
Lua
--[[
|
||
| This file was obtained through the combined efforts
|
||
| of Madbluntz & Plymouth Antiquarian Society.
|
||
|
|
||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||
| Maloy, DrPepper10 @ RIP, Atle!
|
||
|
|
||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||
--]]
|
||
|
||
-- ZAPC
|
||
-- Copyright (c) 2012 Zaubermuffin
|
||
--
|
||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||
--
|
||
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||
--
|
||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
-- Resources first.
|
||
AddCSLuaFile('cl_init.lua')
|
||
AddCSLuaFile('shared.lua')
|
||
|
||
include('shared.lua')
|
||
|
||
-- Spawn Icon
|
||
resource.AddFile('materials/vgui/entities/prop_vehicle_zapc.vmt')
|
||
|
||
-- HUD
|
||
resource.AddFile('materials/zapc_hud/seat.png')
|
||
resource.AddFile('materials/zapc_hud/seat_driver.png')
|
||
resource.AddFile('materials/zapc_hud/seat_gunner.png')
|
||
resource.AddFile('materials/zapc_hud/crosshair.png')
|
||
resource.AddFile('materials/zapc_hud/rocket.png')
|
||
|
||
-- Feel free to change those.
|
||
local function CreateCVar(name, default, text)
|
||
local cvar = CreateConVar(name, default, FCVAR_DEMO, text)
|
||
return function() return cvar:GetFloat() end
|
||
end
|
||
|
||
local function CreateCVarInt(name, default, text)
|
||
local cvar = CreateConVar(name, default, FCVAR_DEMO, text)
|
||
return function() return cvar:GetInt() end
|
||
end
|
||
|
||
local ZAPC_PRIMARY_DAMAGE = CreateCVar('zapc_primary_damage', 40, 'The damage inflicted by the turret')
|
||
local ZAPC_PRIMARY_DELAY = CreateCVar('zapc_primary_delay', 0.1, 'The delay between two turret shots')
|
||
local ZAPC_PRIMARY_FORCE = CreateCVar('zapc_primary_force', 2, 'The force applies to objects hit by the turret')
|
||
local ZAPC_PRIMARY_SPREAD = CreateCVar('zapc_primary_spread', 0.01, 'The spread of the turret.')
|
||
local ZAPC_EXPLOSION_MAGNITUDE = CreateCVarInt('zapc_explosion_magnitude', 600, 'Damage done by the explosion upon destroying an APC. Must be an integer.')
|
||
local ZAPC_EXPLOSION_RADIUS = CreateCVarInt('zapc_explosion_radius', 230, 'Explosion radius upon destroying an APC. Must be an integer.')
|
||
local ZAPC_REPAIR_FORCE = CreateCVarInt('zapc_repair_force', 15, 'How much health can be repaired each tick using the easteregg. Must be an integer.')
|
||
local ZAPC_REMOTE_LOCK_DISTANCE = CreateCVarInt('zapc_remote_lock_distance', 300, 'The maximum distance a player can be away to lock the hatch.')
|
||
local ZAPC_REPAIR_SOUND = 'suitrecharge.chargingloop' -- why this sound? WHY NOT?
|
||
local ZAPC_ALARM_INTERVAL = SoundDuration('ambient/alarms/apc_alarm_loop1.wav') * 0.5 -- time between two alarm waves - tightly tied to the sound, don't change it!
|
||
|
||
-- Do not feel free to modify anything below.
|
||
|
||
-- Make sure it doesn't conflict.
|
||
local ZAPC_VIEW_UNRELATED, ZAPC_VIEW_DRIVER, ZAPC_VIEW_GUNNER, ZAPC_VIEW_PASSENGER = ZAPC_VIEW_UNRELATED, ZAPC_VIEW_DRIVER, ZAPC_VIEW_GUNNER, ZAPC_VIEW_PASSENGER
|
||
local ZAPC_MAX_HEALTH = ZAPC_MAX_HEALTH
|
||
local ZAPC_PRIMARY_RELOAD_TIME, ZAPC_SECONDARY_RELOAD_TIME = ZAPC_PRIMARY_RELOAD_TIME, ZAPC_SECONDARY_RELOAD_TIME
|
||
local ZAPC_MAX_PASSENGERS = ZAPC_MAX_PASSENGERS
|
||
|
||
-- Get rid of them.
|
||
for k, v in pairs(_G) do
|
||
if type(k) == 'string' and k:find('^ZAPC_') then
|
||
_G[k] = nil
|
||
end
|
||
end
|
||
|
||
-- Resources.
|
||
local MODEL = Model('models/combine_apc.mdl')
|
||
local INVISIBLE_MODEL = Model('models/Items/combine_rifle_ammo01.mdl')
|
||
|
||
-- Checks if a player has access to a certain thing.
|
||
-- Possible actions:
|
||
-- "personal": Is able to enter and use gunner/driver functions. This will be checked upon any action and if it isn't fulfilled anymore, the player will be thrown out from the APC.
|
||
-- "destruct": Is able to use zapc_sd self-destruct any APC by looking at it from any distance (while not being inside any APC). Passed as parameter is the APC in question.
|
||
-- "alarm": Is able to use zapc_sa alarming "easter egg" any APC by looking at it. Passed as parameter is the APC in question.
|
||
-- "repair": Is able to use +zapc_repair repairing "easter egg" (that can transform APCs into so called 'golden APCs'. Passed as parameter is the APC in question. This might be called although the repair itself won't start (due to other limitations).
|
||
-- "tool": Is able to use any tool (except duplicator, which will stay explicitly forbidden) on an APC. Needs to have proper called SENT:CanTool on the SENT in order to work (i.e. by the sandbox gamemode). Passed as parameters are the trace and the toolmode.
|
||
local function CheckAccess(ply, action, apc, ...)
|
||
return hook.Call('ZAPC_CheckAccess', nil, ply, action, apc, ...) ~= false
|
||
end
|
||
|
||
-- Replies to a player.
|
||
local function ReplyTo(ply, text, ...)
|
||
if IsValid(ply) then
|
||
ply:PrintMessage(HUD_PRINTCONSOLE, '[APC] ' .. string.format(text, ...))
|
||
else
|
||
print('[APC] ' .. string.format(text, ...))
|
||
end
|
||
end
|
||
|
||
-- Beep.
|
||
local function Beep(apc, snd)
|
||
if not IsValid(apc) then
|
||
return
|
||
end
|
||
|
||
apc:EmitSound(snd)
|
||
end
|
||
|
||
-- Pouff
|
||
local function BlowUp(apc)
|
||
if IsValid(apc) then
|
||
apc:Explode()
|
||
end
|
||
end
|
||
|
||
local function Unsatisfiable()
|
||
return false
|
||
end
|
||
|
||
function ENT:Initialize()
|
||
-- Move us upwards.
|
||
self:SetPos(self:GetPos() + Vector(0, 0, 60))
|
||
|
||
-- ghastly ghost! boooooo!
|
||
self:SetModel(INVISIBLE_MODEL)
|
||
self:SetRenderMode(RENDERMODE_NONE)
|
||
self:SetNoDraw(true)
|
||
|
||
-- Cheat
|
||
self:SetMoveType(MOVETYPE_NONE)
|
||
self:SetSolid(SOLID_NONE)
|
||
self:SetNotSolid(true)
|
||
|
||
-- Create the driver seat.
|
||
self.DriverSeat = ents.Create('prop_vehicle_jeep')
|
||
self.DriverSeat:SetSolid(SOLID_VPHYSICS)
|
||
self.DriverSeat:SetPos(self:GetPos() - self:GetUp()*50)
|
||
self.DriverSeat:SetAngles(self:GetAngles())
|
||
self.DriverSeat:SetModel(MODEL)
|
||
self.DriverSeat:SetKeyValue('vehiclescript', 'scripts/vehicles/zapc_script.txt', 0)
|
||
self.DriverSeat:SetKeyValue('limitview', 0, 0)
|
||
self.DriverSeat.CanTool = function(_, ...) return self:CanTool(...) end
|
||
self.DriverSeat.PhysgunPickup = function(_, ...) return self:PhysgunPickup(...) end
|
||
self.DriverSeat.PhysgunDisabled = true -- redundant, but eh.
|
||
self.DriverSeat:Spawn()
|
||
|
||
-- The gunner "seat".
|
||
self.GunnerSeat = ents.Create('prop_vehicle_prisoner_pod')
|
||
self.GunnerSeat:SetModel(INVISIBLE_MODEL)
|
||
self.GunnerSeat:SetSolid(SOLID_NONE)
|
||
self.GunnerSeat:SetNotSolid(true)
|
||
self.GunnerSeat:SetPos(self:GetPos() - self:GetUp()*10 + self:GetRight()*10)
|
||
local ang = self:GetAngles()
|
||
ang:RotateAroundAxis(self:GetUp(), 180)
|
||
self.GunnerSeat:SetAngles(ang)
|
||
self.GunnerSeat:SetKeyValue('limitview', 0, 0)
|
||
self.GunnerSeat:SetRenderMode(RENDERMODE_NONE)
|
||
self.GunnerSeat.CanTool = Unsatisfiable
|
||
self.GunnerSeat.PhysgunPickup = Unsatisfiable
|
||
self.GunnerSeat.PhysgunDisabled = true -- redundant, but eh.
|
||
self.GunnerSeat:Spawn()
|
||
self.GunnerSeat:GetPhysicsObject():EnableCollisions(false)
|
||
self.GunnerSeat:SetMoveType(MOVETYPE_NONE)
|
||
|
||
-- Turret.
|
||
-- Could be a custom SENT, but we're just abusing the gmod turret here.
|
||
self.Turret = ents.Create('base_gmodentity')
|
||
self.Turret:SetModel(INVISIBLE_MODEL)
|
||
self.Turret:SetPos(self.DriverSeat:GetAttachment(self.DriverSeat:LookupAttachment('muzzle')).Pos)
|
||
self.Turret:Spawn()
|
||
self.Turret:SetParent(self.DriverSeat)
|
||
self.Turret.NextShot = 0
|
||
self.Turret:SetNotSolid(true)
|
||
self.Turret:SetMoveType(MOVETYPE_NONE)
|
||
self.Turret:SetRenderMode(RENDERMODE_NONE)
|
||
self.Turret.CanTool = Unsatisfiable
|
||
self.Turret.PhysgunPickup = Unsatisfiable
|
||
self.Turret.PhysgunDisabled = true -- redundant, but eh.
|
||
self.Turret.APC = self
|
||
|
||
-- The driver and the gunner are nil per default.
|
||
-- Not necessary, but to declare the variables.
|
||
self.Driver, self.Gunner = nil, nil
|
||
|
||
-- Weld everything to the real driving thing.
|
||
-- But set us slightly differently first.
|
||
self:SetParent(self.DriverSeat)
|
||
self.GunnerSeat:SetParent(self.DriverSeat)
|
||
|
||
-- pew pew stuff.
|
||
self.Bullets = 50
|
||
|
||
self.NextPrimary, self.NextSecondary = 0, 0
|
||
|
||
-- Health!
|
||
self:SetMaxHealth(ZAPC_MAX_HEALTH())
|
||
self:SetHealth(ZAPC_MAX_HEALTH())
|
||
|
||
-- informational wiring. Necessary for certain things.
|
||
self.DriverSeat.APC = self
|
||
self.GunnerSeat.APC = self
|
||
|
||
-- We are APC. A callback would be unhandier.
|
||
self.IsAPC = true
|
||
|
||
-- Passengers!
|
||
self.Passengers = {}
|
||
self.HatchOpened = false
|
||
|
||
-- Lock us!
|
||
self.DriverSeat:Fire('TurnOff', '')
|
||
self.TurnedOn = false
|
||
self.SlowDrive = false
|
||
end
|
||
|
||
-- Introduced in the March '15 update, I don't think we need to know
|
||
-- what class we are.
|
||
function ENT:SetVehicleClass(name)
|
||
assert(name == 'prop_vehicle_zapc', 'invalid class name')
|
||
end
|
||
|
||
-- Informs the user about his gunner view status.
|
||
local function SendViewMode(ply, status)
|
||
if not IsValid(ply) then
|
||
return
|
||
end
|
||
|
||
umsg.Start('ZAPC_VU', ply)
|
||
umsg.Char(status)
|
||
umsg.End()
|
||
end
|
||
|
||
|
||
local function LimitPitch(ang)
|
||
return math.Clamp(ang, -45, 45)
|
||
end
|
||
|
||
local function ProcessAngle(ang)
|
||
ang.pitch = LimitPitch(math.NormalizeAngle(ang.pitch))
|
||
ang.yaw = math.NormalizeAngle(ang.yaw)
|
||
ang.roll = math.NormalizeAngle(ang.roll)
|
||
return ang
|
||
end
|
||
|
||
function ENT:Think()
|
||
self:UpdateGun()
|
||
self:NextThink(CurTime() + 1/200)
|
||
if self.NextAlarm and self.NextAlarm <= CurTime() then
|
||
self.DriverSeat:GetPhysicsObject():AddVelocity(Vector(0, 0, 150))
|
||
self.NextAlarm = self.NextAlarm + ZAPC_ALARM_INTERVAL * (self.Special and 0.5 or 1)
|
||
end
|
||
|
||
if IsValid(self.Gunner) then
|
||
if self.NextPrimary < CurTime() and self.Gunner:KeyDown(IN_ATTACK) then
|
||
self:FireTurret()
|
||
end
|
||
|
||
if self.NextSecondary < CurTime() and self.Gunner:KeyDown(IN_ATTACK2) then
|
||
self:FireRocket()
|
||
end
|
||
end
|
||
|
||
return true
|
||
end
|
||
|
||
-- Updates the gun according to the gunner's view.
|
||
function ENT:UpdateGun()
|
||
if not IsValid(self.Gunner) then
|
||
return
|
||
end
|
||
|
||
local gunnerAngles, apcAngles = ProcessAngle(self.Gunner:EyeAngles()), self.DriverSeat:GetAngles()
|
||
|
||
self.DriverSeat:SetPoseParameter('vehicle_weapon_yaw', math.NormalizeAngle(gunnerAngles.yaw - apcAngles.yaw - 90))
|
||
self.DriverSeat:SetPoseParameter('vehicle_weapon_pitch', LimitPitch(math.NormalizeAngle(gunnerAngles.pitch - apcAngles.pitch)))
|
||
|
||
if self.Special and self.SirenSound then
|
||
self.SirenSound:ChangePitch(self.DriverSeat:GetPoseParameter('vehicle_weapon_pitch') + 95, 0)
|
||
end
|
||
end
|
||
|
||
-- Returns everybody that should know about updates.
|
||
-- Because, if we put nil or [NULL] into a umsg, it's sent to everybody.
|
||
function ENT:GetReceipees()
|
||
local t = RecipientFilter()
|
||
|
||
if IsValid(self.Driver) then
|
||
t:AddPlayer(self.Driver)
|
||
end
|
||
|
||
if IsValid(self.Gunner) then
|
||
t:AddPlayer(self.Gunner)
|
||
end
|
||
|
||
return t
|
||
end
|
||
|
||
-- Send the new passengers to whoever cares
|
||
function ENT:PassengersUpdate(ply)
|
||
if IsValid(self.Driver) or IsValid(self.Gunner) then
|
||
umsg.Start('ZAPC_PU', ply or self:GetReceipees())
|
||
umsg.Entity(self.Driver or NULL)
|
||
umsg.Entity(self.Gunner or NULL)
|
||
for i = 1, ZAPC_MAX_PASSENGERS() do
|
||
local pass = self.Passengers[i]
|
||
umsg.Entity(IsValid(pass) and pass:GetPassenger(0) or NULL)
|
||
end
|
||
umsg.End()
|
||
end
|
||
end
|
||
|
||
-- Sends a bullet update.
|
||
function ENT:BulletUpdate(ply)
|
||
umsg.Start('ZAPC_BU', ply or self:GetReceipees())
|
||
umsg.Char(math.max(self.Bullets, 0))
|
||
umsg.End()
|
||
end
|
||
|
||
-- Health update
|
||
function ENT:HealthUpdate(ply)
|
||
umsg.Start('ZAPC_HU', ply or self:GetReceipees())
|
||
umsg.Short(self:Health())
|
||
umsg.End()
|
||
end
|
||
|
||
function ENT:HatchUpdate(ply)
|
||
local recv = ply
|
||
if not recv then
|
||
recv = self:GetReceipees()
|
||
for k, v in pairs(self.Passengers) do
|
||
recv:AddPlayer(v:GetPassenger(0))
|
||
end
|
||
end
|
||
|
||
SendUserMessage('ZAPC_HOU', recv, self.HatchOpened)
|
||
end
|
||
|
||
function ENT:ToggleHatch()
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
self.HatchOpened = not self.HatchOpened
|
||
self:HatchUpdate()
|
||
end
|
||
|
||
local function KeyPress(ply, key)
|
||
local vehicle = ply:GetVehicle()
|
||
local apc = vehicle.APC
|
||
if IsValid(apc) then
|
||
if (not CheckAccess(ply, 'personal', apc)) and (vehicle == apc.DriverSeat or vehicle == apc.GunnerSeat) then
|
||
ply:ExitVehicle()
|
||
ReplyTo(ply, '[ZAPC] You are no longer authorized to use this specialized vehicle. If you think this is a mistake, please contact the nearest police station.')
|
||
return
|
||
end
|
||
|
||
-- Driver?
|
||
if ply == apc.Driver then
|
||
if key == IN_ATTACK then
|
||
apc:ToggleEngine()
|
||
elseif key == IN_ATTACK2 then
|
||
apc:ToggleSpeed()
|
||
elseif key == IN_SPEED then
|
||
if not IsValid(apc.Gunner) then
|
||
apc:DriverToGunner(ply)
|
||
end
|
||
elseif key == IN_RELOAD then
|
||
apc:ToggleSiren()
|
||
elseif key == IN_WALK then
|
||
apc:ToggleHatch()
|
||
end
|
||
-- Gunner!
|
||
elseif ply == apc.Gunner then
|
||
-- Reloading?
|
||
if key == IN_RELOAD then
|
||
apc:ReloadGun()
|
||
return true
|
||
elseif key == IN_SPEED then
|
||
if not IsValid(apc.Driver) then
|
||
apc:GunnerToDriver(ply)
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
hook.Add('KeyPress', '_ZAPC.KeyPress', KeyPress)
|
||
|
||
local function ColdBoot(apc)
|
||
if IsValid(apc) and apc.TurnedOn and not apc.Destructing then
|
||
apc.DriverSeat:Fire('TurnOn', '')
|
||
apc.DriverSeat:Fire('HandbrakeOff', '')
|
||
end
|
||
end
|
||
|
||
function ENT:StartEngine()
|
||
timer.Simple(1, function() ColdBoot(self) end)
|
||
self:EmitSound('apc_engine_start')
|
||
end
|
||
|
||
function ENT:StopEngine()
|
||
self.DriverSeat:Fire('TurnOff', '')
|
||
self:StopSound('apc_engine_start')
|
||
self:EmitSound('apc_engine_stop')
|
||
|
||
if self.MovingSound then
|
||
self:StopSound(self.MovingSound)
|
||
self.MovingSound = nil
|
||
end
|
||
|
||
self.Direction = nil
|
||
end
|
||
|
||
function ENT:AddDriver(driver)
|
||
self.Driver = driver
|
||
-- Health before APC, so the APC overwrites the thing.
|
||
self:HealthUpdate(driver)
|
||
self:EnterPlayer(driver)
|
||
-- And the bullets.
|
||
self:BulletUpdate(driver)
|
||
SendViewMode(driver, ZAPC_VIEW_DRIVER)
|
||
self:PassengersUpdate()
|
||
end
|
||
|
||
function ENT:AddGunner(gunner)
|
||
self.Gunner = gunner
|
||
-- Health before APC, so the APC overwrites the thing.
|
||
self:HealthUpdate(gunner)
|
||
self:EnterPlayer(gunner)
|
||
-- And the bullets.
|
||
self:BulletUpdate(gunner)
|
||
SendViewMode(gunner, ZAPC_VIEW_GUNNER)
|
||
self:PassengersUpdate()
|
||
end
|
||
|
||
-- Adds a new passenger in the back-door-hatch
|
||
-- unlucky name is unlucky
|
||
function ENT:AddPassenger(ply)
|
||
local seat = ents.Create('prop_vehicle_prisoner_pod')
|
||
seat:SetModel(INVISIBLE_MODEL)
|
||
seat:SetSolid(SOLID_NONE)
|
||
seat:SetNotSolid(true)
|
||
seat:SetNoDraw(true)
|
||
seat:SetColor(Color(255, 255, 255, 0))
|
||
seat:SetPos(self:GetPos())
|
||
seat:SetAngles(self.DriverSeat:GetAngles())
|
||
seat:SetParent(self.DriverSeat)
|
||
seat:SetKeyValue('limitview', 0, 0)
|
||
seat.CanTool = Unsatisfiable
|
||
seat.PhysgunPickup = Unsatisfiable
|
||
seat.PhysgunDisabled = true -- redundant, but eh.
|
||
seat.APC = self
|
||
seat:Spawn()
|
||
seat:SetMoveType(MOVETYPE_NONE)
|
||
local phys = seat:GetPhysicsObject()
|
||
phys:EnableCollisions(false)
|
||
table.insert(self.Passengers, seat)
|
||
self:EnterPlayer(ply)
|
||
ply:EnterVehicle(seat)
|
||
SendViewMode(ply, ZAPC_VIEW_PASSENGER)
|
||
self:PassengersUpdate()
|
||
end
|
||
|
||
-- Hook callback when a new passenger (gunner, driver, passenger) is detected.
|
||
function ENT:NewPassenger(ply, seat)
|
||
if self.DriverSeat == seat then
|
||
self:AddDriver(ply)
|
||
elseif self.GunnerSeat == seat then
|
||
self:AddGunner(ply)
|
||
else
|
||
-- I suppose it's a passenger. Those were added manually anyway - but let's check
|
||
for k, v in pairs(self.Passengers) do
|
||
if v == seat then
|
||
return
|
||
end
|
||
end
|
||
|
||
error('Attempt to add ' .. tostring(ply) .. ' to ' .. tostring(self) .. ' - but player is not inside the APC.')
|
||
end
|
||
end
|
||
|
||
local function ResetLastPassenger(apc, ply)
|
||
if not IsValid(apc) or not IsValid(ply) or apc.LastPassenger ~= ply then
|
||
return
|
||
end
|
||
|
||
apc.LastPassenger = nil
|
||
end
|
||
|
||
-- This function may be called multiple times, depending on how GMod feels right now.
|
||
-- Therefore, the if-checks SHOULD NOT BE COMBINED (no if seat == and ply ==). No.
|
||
-- Note: Due to historical reasons, this is here to remove ANY KIND OF PASSENGER - not just "passengers".
|
||
function ENT:RemovePassenger(ply, seat)
|
||
if seat == self.DriverSeat then
|
||
if ply == self.Driver then
|
||
self.Driver = nil
|
||
self:ExitPlayer(ply, true)
|
||
ply:SetPos(self:GetPos() + self:GetForward()*60)
|
||
end
|
||
elseif seat == self.GunnerSeat then
|
||
if ply == self.Gunner then
|
||
self.Gunner = nil
|
||
self.LastPassenger = ply
|
||
timer.Simple(0.5, function() ResetLastPassenger(self, ply) end)
|
||
self:ExitPlayer(ply, true)
|
||
self.DriverSeat:SetPoseParameter('vehicle_weapon_yaw', 0)
|
||
self.DriverSeat:SetPoseParameter('vehicle_weapon_pitch', 0)
|
||
ply:SetPos(self:GetPos() - self:GetForward()*60)
|
||
end
|
||
else
|
||
-- Passenger?
|
||
for k, v in pairs(self.Passengers) do
|
||
if v == seat then
|
||
-- Make sure he doesn't get in IMMEDIATELY AGAIN.
|
||
self.LastPassenger = ply
|
||
timer.Simple(0.5, function() ResetLastPassenger(self, ply) end)
|
||
-- ok bye
|
||
self:ExitPlayer(ply, true)
|
||
ply:SetPos(self:GetPos() + self:GetRight() * 150)
|
||
seat:Remove()
|
||
table.remove(self.Passengers, k)
|
||
self:PassengersUpdate()
|
||
return
|
||
end
|
||
end
|
||
|
||
error('Unable to find ' .. tostring(ply) .. ' as passenger in ' .. tostring(seat))
|
||
end
|
||
|
||
self:PassengersUpdate()
|
||
end
|
||
|
||
function ENT:DriverToGunner(driver)
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
|
||
driver:ExitVehicle()
|
||
driver:EnterVehicle(self.GunnerSeat)
|
||
end
|
||
|
||
function ENT:GunnerToDriver(gunner)
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
|
||
gunner:ExitVehicle()
|
||
gunner:EnterVehicle(self.DriverSeat)
|
||
end
|
||
|
||
local function ReloadComplete(apc)
|
||
if not IsValid(apc) or apc.Bullets ~= -1 or apc.Destructing then
|
||
return
|
||
end
|
||
|
||
apc:EmitSound('Weapon_AR2.Reload_Push')
|
||
apc.Bullets = 50
|
||
apc:BulletUpdate()
|
||
end
|
||
|
||
function ENT:ReloadGun()
|
||
-- eeh.
|
||
if self.Bullets == -1 or self.Bullets == 50 or self.Destructing then
|
||
return
|
||
end
|
||
|
||
self.Bullets = 0
|
||
self:BulletUpdate()
|
||
self.NextPrimary = CurTime() + ZAPC_PRIMARY_RELOAD_TIME()
|
||
|
||
self:PrimaryUpdate()
|
||
self.Bullets = -1
|
||
timer.Simple(ZAPC_PRIMARY_RELOAD_TIME(), function() ReloadComplete(self) end)
|
||
end
|
||
|
||
local function CanPlayerEnterVehicle(ply, vehicle, role)
|
||
local apc = vehicle.APC
|
||
|
||
if IsValid(apc) then
|
||
local access = CheckAccess(ply, 'personal', apc)
|
||
|
||
-- +walk moves us into the passenger bay.
|
||
if access and vehicle == apc.DriverSeat and ply:KeyDown(IN_WALK) then
|
||
return false
|
||
end
|
||
|
||
-- If we are exploding or trying to access and invalid seat, nope
|
||
if apc.Destructing or (not access and (vehicle == apc.DriverSeat or vehicle == apc.GunnerSeat)) then
|
||
apc:EmitSound('buttons.snd47')
|
||
return false
|
||
else
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
hook.Add('CanPlayerEnterVehicle', '_ZAPC.CanPlayerEnterVehicle', CanPlayerEnterVehicle)
|
||
|
||
local function PlayerEnteredVehicle(ply, veh, role)
|
||
if IsValid(veh.APC) then
|
||
veh.APC:NewPassenger(ply, veh)
|
||
end
|
||
end
|
||
hook.Add('PlayerEnteredVehicle', '_ZAPC.PlayerEnteredVehicle', PlayerEnteredVehicle)
|
||
|
||
local function PlayerUse(ply, ent)
|
||
if IsValid(ent.APC) and not IsValid(ply:GetVehicle()) then
|
||
local apc = ent.APC
|
||
|
||
-- The APC is NOT blowing up and this isn't the last passenger?
|
||
if not apc.Destructing and apc.LastPassenger ~= ply then
|
||
-- If we don't have a driver OR he isn't authorized
|
||
local driverE = IsValid(apc.Driver)
|
||
|
||
if (driverE or not CheckAccess(ply, 'personal', apc) or ply:KeyDown(IN_WALK)) and apc.HatchOpened and #apc.Passengers < ZAPC_MAX_PASSENGERS() then
|
||
apc:AddPassenger(ply)
|
||
return true
|
||
-- Driver?
|
||
elseif not driverE and CheckAccess(ply, 'personal', apc) then
|
||
-- It's okay. Just return.
|
||
return
|
||
-- Authorized to be gunner?
|
||
elseif CheckAccess(ply, 'personal', apc) and not IsValid(apc.Gunner) then
|
||
ply:EnterVehicle(apc.GunnerSeat)
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
hook.Add('PlayerUse', '_ZAPC.PlayerUse', PlayerUse)
|
||
|
||
local function CanExitVehicle(vehicle, ply)
|
||
local apc = vehicle.APC
|
||
if IsValid(apc) then
|
||
if apc.Destructing or (ply ~= apc.Driver and ply ~= apc.Gunner and not apc.HatchOpened) then
|
||
return false
|
||
end
|
||
end
|
||
end
|
||
hook.Add('CanExitVehicle', '_ZAPC.CanExitVehicle', CanExitVehicle)
|
||
|
||
function ENT:FireTurret()
|
||
-- more gun? No gun.
|
||
if not IsValid(self.Gunner) or self.Destructing then
|
||
return
|
||
end
|
||
|
||
self.NextPrimary = CurTime() + ZAPC_PRIMARY_DELAY()
|
||
|
||
-- click click click
|
||
if self.Bullets <= 0 then
|
||
self:EmitSound('Weapon_Shotgun.Empty', 200, 100)
|
||
return
|
||
end
|
||
|
||
local turret = self.Turret
|
||
|
||
-- Prepare.
|
||
self.Bullets = self.Bullets - 1
|
||
self:BulletUpdate()
|
||
|
||
-- If we call this RIGHT NOW (i.e. inside KeyPress), weird stuff happens.
|
||
-- Don't do that.
|
||
-- In Think, it's okay. So, just delay it.
|
||
local muzzleTach = self.DriverSeat:LookupAttachment('muzzle')
|
||
local attach = self.DriverSeat:GetAttachment(muzzleTach)
|
||
attach.Pos = attach.Pos - 5 * attach.Ang:Forward() - attach.Ang:Up()*6
|
||
turret:SetPos(attach.Pos)
|
||
turret:SetAngles(attach.Ang)
|
||
|
||
-- And now, pewpew.
|
||
turret:EmitSound('Weapon_AR2.Single')
|
||
|
||
local bullet = {}
|
||
bullet.Num = 1
|
||
bullet.Src = attach.Pos
|
||
bullet.Dir = attach.Ang:Forward()
|
||
bullet.Spread = Vector(ZAPC_PRIMARY_SPREAD(), ZAPC_PRIMARY_SPREAD(), 0)
|
||
bullet.Tracer = 1
|
||
bullet.TracerName = 'AR2Tracer'
|
||
bullet.Force = ZAPC_PRIMARY_FORCE()
|
||
bullet.Damage = ZAPC_PRIMARY_DAMAGE() * (self.Special and 100 or 1)
|
||
bullet.Attacker = self.Gunner
|
||
turret:FireBullets(bullet)
|
||
|
||
local effd = EffectData()
|
||
effd:SetEntity(self.DriverSeat)
|
||
effd:SetAngles(attach.Ang)
|
||
effd:SetOrigin(attach.Pos)
|
||
effd:SetScale(1.8)
|
||
effd:SetAttachment(muzzleTach)
|
||
util.Effect('AirboatMuzzleFlash', effd)
|
||
|
||
if self.Bullets == 0 then
|
||
self:ReloadGun()
|
||
end
|
||
end
|
||
|
||
-- Sends updates for primary
|
||
function ENT:PrimaryUpdate(ply)
|
||
SendUserMessage('ZAPC_LPU', ply or self:GetReceipees())
|
||
end
|
||
|
||
function ENT:SecondaryUpdate(ply)
|
||
SendUserMessage('ZAPC_LSU', ply or self:GetReceipees())
|
||
end
|
||
|
||
-- cleans the dishes, duh.
|
||
function ENT:FireRocket()
|
||
if self.NextSecondary > CurTime() or self.Destructing then
|
||
return
|
||
end
|
||
|
||
self.NextSecondary = CurTime() + ZAPC_SECONDARY_RELOAD_TIME()
|
||
|
||
self:SecondaryUpdate()
|
||
|
||
self:EmitSound('PropAPC.FireRocket')
|
||
local attachLookup = self.DriverSeat:LookupAttachment('cannon_muzzle')
|
||
local attach = self.DriverSeat:GetAttachment(attachLookup)
|
||
local ent = ents.Create('prop_vehicle_zapc_rocket')
|
||
ent:SetOwner(self.Gunner)
|
||
ent:SetPos(attach.Pos + self:GetForward()*2)
|
||
ent:SetAngles(attach.Ang)
|
||
ent.APC = self
|
||
ent:Spawn()
|
||
|
||
if self.Special then
|
||
ent:SetModel('models/props_junk/watermelon01.mdl')
|
||
ent:SetMaterial('models/shiny')
|
||
ent:SetColor(Color(0, 242, 255, 255))
|
||
end
|
||
|
||
-- MUZZLEDUZZLE
|
||
local ed = EffectData()
|
||
ed:SetEntity(self.DriverSeat)
|
||
ed:SetScale(2)
|
||
ed:SetAttachment(attachLookup)
|
||
util.Effect('AirboatMuzzleFlash', ed)
|
||
|
||
timer.Simple(ZAPC_SECONDARY_RELOAD_TIME(), function() Beep(self, 'buttons.snd6') end)
|
||
end
|
||
|
||
-- Helper function called to un-solid a player. Calls itself recursively.
|
||
local ZAPC_GHOST_LIST = {}
|
||
local function UnsolidifyPlayer(ply)
|
||
if not IsValid(ply) or not ply:Alive() or IsValid(ply:GetVehicle()) then
|
||
ZAPC_GHOST_LIST[ply] = nil
|
||
return
|
||
end
|
||
|
||
-- Check for collisions
|
||
local tr = util.TraceEntity({ start = ply:GetPos(), endpos = ply:GetPos(), filter = ply}, ply)
|
||
if tr.Hit then
|
||
timer.Simple(0.5, function() UnsolidifyPlayer(ply) end)
|
||
return
|
||
end
|
||
|
||
ply:SetCustomCollisionCheck(ZAPC_GHOST_LIST[ply])
|
||
ZAPC_GHOST_LIST[ply] = nil
|
||
end
|
||
|
||
-- Assure that ghosted players don't collide.
|
||
local function ShouldCollide(ent1, ent2)
|
||
if not IsValid(ent1) or not IsValid(ent2) then
|
||
return
|
||
end
|
||
|
||
if (ent2:IsPlayer() and ZAPC_GHOST_LIST[ent1] ~= nil) or (ent1:IsPlayer() and ZAPC_GHOST_LIST[ent2] ~= nil) then
|
||
return false
|
||
end
|
||
end
|
||
hook.Add('ShouldCollide', '_ZAPC.ShouldCollide', ShouldCollide)
|
||
|
||
-- Gets rid of a player... in a nice way.
|
||
function ENT:ExitPlayer(ply, nopos)
|
||
SendViewMode(ply, ZAPC_VIEW_UNRELATED)
|
||
ply:GodDisable()
|
||
ply:SetColor(Color(255, 255, 255, 255))
|
||
ply:SetNoDraw(false)
|
||
ZAPC_GHOST_LIST[ply] = ply:GetCustomCollisionCheck()
|
||
timer.Simple(0.5, function() UnsolidifyPlayer(ply) end)
|
||
if not nopos then
|
||
ply:SetPos(self:GetPos() + self:GetForward()*60)
|
||
end
|
||
end
|
||
|
||
-- Hello Fr<46>ulein!
|
||
function ENT:EnterPlayer(ply)
|
||
ply:GodEnable()
|
||
ply:SetNoDraw(true)
|
||
ply:SetColor(Color(255, 255, 255, 0))
|
||
-- Send the DriverSeat as APC.
|
||
SendUserMessage('ZAPC_AU', ply, self.DriverSeat)
|
||
-- And hatches!
|
||
self:HatchUpdate(ply)
|
||
end
|
||
|
||
-- Ouch.
|
||
local function EntityTakeDamage(ent, dmg)
|
||
if IsValid(ent) and ent:GetClass() == 'prop_vehicle_jeep' and IsValid(ent.APC) and dmg:GetDamage() > 1 then
|
||
ent.APC:Pewpew(dmg:GetDamage())
|
||
end
|
||
end
|
||
hook.Add('EntityTakeDamage', '_ZAPC.EntityTakeDamage', EntityTakeDamage)
|
||
|
||
local function PlayerSpawn(ply)
|
||
-- Under NO circumstances can you re-spawn inside the APC. This prevents KillSilent/Spawn combos to switch characters while inside.
|
||
if IsValid(ply) and IsValid(ply:GetVehicle()) and IsValid(ply:GetVehicle().APC) then
|
||
ply:ExitVehicle()
|
||
end
|
||
end
|
||
hook.Add('PlayerSpawn', '_ZAPC.PlayerSpawn', PlayerSpawn)
|
||
|
||
function ENT:Pewpew(amount)
|
||
if self.Special then
|
||
amount = amount * 0.001
|
||
end
|
||
|
||
self:SetHealth(math.max(self:Health() - amount, 0))
|
||
if self:Health() <= 0 then
|
||
-- We are not allowed to do this in this frame.
|
||
timer.Simple(0, function() BlowUp(self) end)
|
||
return
|
||
end
|
||
self:HealthUpdate()
|
||
end
|
||
|
||
|
||
local function CleanupGibs(ent, gibs)
|
||
for k, v in pairs(gibs) do
|
||
if IsValid(v) then
|
||
v:Remove()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- POUFF.
|
||
function ENT:Explode()
|
||
-- Do not explode multiple times.
|
||
if self.Died then
|
||
return
|
||
end
|
||
|
||
local apc = self.DriverSeat
|
||
local driver, gunner = self.Driver, self.Gunner
|
||
|
||
if IsValid(driver) then
|
||
self:ExitPlayer(driver, true)
|
||
driver:ExitVehicle()
|
||
driver:SetPos(apc:GetPos() + apc:GetForward()*150 - apc:GetRight()*50 + apc:GetUp()*150)
|
||
driver:SetVelocity(apc:GetForward()*500 + apc:GetUp()*600 - apc:GetRight() * math.random(50, 200))
|
||
self.Driver = nil
|
||
driver:Kill()
|
||
end
|
||
|
||
if IsValid(gunner) then
|
||
self:ExitPlayer(gunner, true)
|
||
gunner:ExitVehicle()
|
||
gunner:SetPos(apc:GetPos() + apc:GetForward()*150 + apc:GetRight()*50 + apc:GetUp()*150)
|
||
gunner:SetVelocity(apc:GetForward()*500 + apc:GetUp()*600 + apc:GetRight() * math.random(50, 200))
|
||
self.Gunner = nil
|
||
gunner:Kill()
|
||
end
|
||
|
||
for k, v in pairs(self.Passengers) do
|
||
local pass = v:GetPassenger(0)
|
||
self:ExitPlayer(pass)
|
||
pass:ExitVehicle()
|
||
pass:Kill()
|
||
end
|
||
|
||
-- Now, when we explode, we explode. Of course.
|
||
local expl = ents.Create('env_explosion')
|
||
expl:SetPos(apc:GetPos() + apc:GetForward()*80 + apc:GetUp()*10)
|
||
expl:SetKeyValue('imagnitude', tostring(ZAPC_EXPLOSION_MAGNITUDE()))
|
||
expl:SetKeyValue('iradiusoverride', tostring(ZAPC_EXPLOSION_RADIUS()))
|
||
expl:Spawn()
|
||
expl:Fire('explode', '', 0)
|
||
|
||
|
||
-- Create some gibs.
|
||
local gibs = {}
|
||
|
||
for i = 1,5 do
|
||
local ent = ents.Create('prop_physics')
|
||
ent:SetModel('models/combine_apc_destroyed_gib0' .. i .. '.mdl')
|
||
ent:SetPos(apc:GetPos())
|
||
ent:SetAngles(apc:GetAngles())
|
||
ent:Spawn()
|
||
table.insert(gibs, ent)
|
||
end
|
||
|
||
-- Fix the undo/cleanup history.
|
||
undo.ReplaceEntity(self, gibs[1])
|
||
cleanup.ReplaceEntity(self, gibs[1])
|
||
|
||
gibs[1]:CallOnRemove('ZAPCCleanUp', CleanupGibs, gibs)
|
||
|
||
-- Special for the wheel!
|
||
local ent = ents.Create('prop_physics')
|
||
ent:SetModel('models/combine_apc_destroyed_gib06.mdl')
|
||
ent:SetPos(self:GetPos() - self:GetRight()*110 - self:GetUp()*30)
|
||
ent:SetAngles(self:GetForward():Angle())
|
||
ent:Spawn()
|
||
table.insert(gibs, ent)
|
||
|
||
-- The softy
|
||
expl = ents.Create('env_physexplosion')
|
||
expl:SetPos(apc:GetPos() + apc:GetForward()*80)
|
||
expl:SetKeyValue('magnitude', tostring(ZAPC_EXPLOSION_MAGNITUDE()))
|
||
expl:SetKeyValue('spawnflags', '1') -- 19 would be push players + disorient players
|
||
expl:Spawn()
|
||
expl:Fire('explode', '', 0)
|
||
expl:Fire('kill', '', 1)
|
||
|
||
-- And dead.
|
||
self:Remove()
|
||
self.Died = true
|
||
end
|
||
|
||
-- dam dam daaaam
|
||
function ENT:OnRemove()
|
||
-- Eject people
|
||
local driver = self.Driver
|
||
if IsValid(driver) then
|
||
self:ExitPlayer(driver, true)
|
||
end
|
||
|
||
local gunner = self.Gunner
|
||
if IsValid(gunner) then
|
||
self:ExitPlayer(gunner)
|
||
end
|
||
|
||
-- Passengers are a bit special; because the table is modified (i.e. the passenger is removed)
|
||
for k, v in pairs(self.Passengers) do
|
||
local pass = v:GetPassenger(0)
|
||
self:ExitPlayer(pass)
|
||
end
|
||
|
||
-- Get rid of the APC
|
||
if IsValid(self.DriverSeat) then
|
||
self.DriverSeat:Remove()
|
||
end
|
||
|
||
if IsValid(self.GunnerSeat) then
|
||
self.GunnerSeat:Remove()
|
||
end
|
||
|
||
for k, v in pairs(self.Passengers) do
|
||
v:Remove()
|
||
end
|
||
|
||
if self.SirenSound then
|
||
self.SirenSound:Stop()
|
||
end
|
||
|
||
self:StopSound(ZAPC_REPAIR_SOUND)
|
||
self:StopSound('apc_engine_start')
|
||
end
|
||
|
||
local function PlayerLeaveVehicle(ply, vehicle)
|
||
if IsValid(vehicle.APC) and not vehicle.APC.Destructing then
|
||
-- Is it the player?
|
||
if vehicle.APC.Driver == ply then
|
||
timer.Simple(0, function() ColdBoot(vehicle.APC) end)
|
||
end
|
||
|
||
vehicle.APC:RemovePassenger(ply, vehicle)
|
||
end
|
||
end
|
||
hook.Add('PlayerLeaveVehicle', '_ZAPC.PlayerLeaveVehicle', PlayerLeaveVehicle)
|
||
|
||
-- If passengers disconnect while being inside, everything fucks up majorly.
|
||
local function PlayerDisconnected(ply)
|
||
local vehicle = ply:GetVehicle()
|
||
if IsValid(vehicle) and IsValid(vehicle.APC) then
|
||
vehicle.APC:RemovePassenger(ply, vehicle)
|
||
end
|
||
end
|
||
hook.Add('PlayerDisconnected', '_ZAPC.PlayerDisconnected', PlayerDisconnected)
|
||
|
||
concommand.Add('zapc_sd', function(ply, cmd, args)
|
||
if not IsValid(ply) then
|
||
return
|
||
end
|
||
|
||
local ent = ply:GetEyeTrace().Entity
|
||
if not IsValid(ent.APC) or ent.APC.Destructing or not CheckAccess(ply, 'destruct', ent.APC) or ply:GetVehicle() == ent.APC.DriverSeat or ply:GetVehicle() == ent.APC.GunnerSeat then
|
||
return
|
||
end
|
||
|
||
ent.APC:SelfDestruct()
|
||
end, nil, "Blows up the APC you are looking at - if you have the required authorization.")
|
||
|
||
concommand.Add('zapc_sa', function(ply, cmd, args)
|
||
if not IsValid(ply) then
|
||
return
|
||
end
|
||
|
||
local ent = ply:GetEyeTrace().Entity
|
||
if not IsValid(ent.APC) or ent.APC.Destructing or not CheckAccess(ply, 'alarm', ent.APC) or ply:GetVehicle() == ent.APC.DriverSeat or ply:GetVehicle() == ent.APC.GunnerSeat then
|
||
return
|
||
end
|
||
|
||
ent.APC:StartAlarm()
|
||
end, nil, "Starts an alarm on the APC you are looking at - if you have the required authorization.")
|
||
|
||
function ENT:SelfDestruct()
|
||
self.Destructing = true
|
||
|
||
-- Make the beeps!
|
||
-- Normal beeps
|
||
-- [1, 3] @ 1
|
||
for i = 0, 3 do
|
||
timer.Simple(i, function() Beep(self, 'buttons.snd16') end)
|
||
end
|
||
|
||
-- Faster beeps
|
||
-- [3, 5] @ 5
|
||
for i = 1, 4 do
|
||
timer.Simple(3 + i/2, function() Beep(self, 'buttons.snd16') end)
|
||
end
|
||
|
||
-- BEEEEP
|
||
-- [5.25, 6.5] @ 0.25
|
||
for i = 1, 9 do
|
||
timer.Simple(5 + i/8, function() Beep(self, 'buttons.snd16') end)
|
||
end
|
||
|
||
timer.Simple(6.25, function() BlowUp(self) end)
|
||
|
||
self.DriverSeat:Fire('TurnOff', '')
|
||
self.DriverSeat:Fire('Lock', '')
|
||
|
||
if IsValid(self.Driver) or IsValid(self.Gunner) then
|
||
SendUserMessage('ZAPC_SD', self:GetReceipees())
|
||
end
|
||
end
|
||
|
||
function ENT:StartRepairing(ply)
|
||
self.RepairingPlayer = ply
|
||
local effectdata = EffectData()
|
||
effectdata:SetScale(500)
|
||
effectdata:SetRadius(100000)
|
||
|
||
self.RepairEffectData = effectdata
|
||
|
||
-- And start the effect! Yaaay!
|
||
timer.Create('ZAPCRepair' .. self:EntIndex(), 0.1, 0, function() if IsValid(self) then self:RepairEffect() end end)
|
||
self:EmitSound(ZAPC_REPAIR_SOUND)
|
||
end
|
||
|
||
function ENT:RepairEffect()
|
||
-- Is the player still valid?
|
||
local tr = self.RepairingPlayer:GetEyeTrace()
|
||
-- Too close?
|
||
if not IsValid(self.RepairingPlayer) or tr.Entity ~= self.DriverSeat or tr.HitPos:Distance(self.RepairingPlayer:GetPos()) < 270 then
|
||
self:EndRepairing()
|
||
return
|
||
end
|
||
|
||
-- Do the effect!
|
||
local ed = self.RepairEffectData
|
||
ed:SetStart(self.RepairingPlayer:EyePos())
|
||
ed:SetOrigin(tr.HitPos)
|
||
ed:SetNormal(tr.HitNormal)
|
||
util.Effect("GaussTracer", ed)
|
||
|
||
-- Now repair us!
|
||
self:SetHealth(self:Health() + ZAPC_REPAIR_FORCE())
|
||
self:HealthUpdate()
|
||
-- Are we full again?
|
||
if self:Health() > ZAPC_MAX_HEALTH()*1.5 and not self.Special then
|
||
self:MakeSpecial()
|
||
-- End the healing. :(
|
||
self:EndRepairing()
|
||
end
|
||
end
|
||
|
||
function ENT:MakeSpecial()
|
||
-- BUT MAKE US SHINY
|
||
self.Special = true
|
||
self:EmitSound('bounce.concrete')
|
||
|
||
local effectdata = EffectData()
|
||
effectdata:SetOrigin(self.DriverSeat:GetPos())
|
||
effectdata:SetScale(1000)
|
||
effectdata:SetRadius(100)
|
||
effectdata:SetColor(255, 0, 255, 255)
|
||
|
||
for i = 1, 10 do
|
||
timer.Simple(i/20, function()
|
||
util.Effect('ThumperDust', effectdata)
|
||
end)
|
||
end
|
||
|
||
timer.Simple(0.6, function()
|
||
self.DriverSeat:SetMaterial('models/shiny')
|
||
self.DriverSeat:SetColor(Color(255, 215, 0, 255))
|
||
end)
|
||
|
||
if self.SirenSound then
|
||
self.SirenSound:Stop()
|
||
self.SirenSound = nil
|
||
self.Siren = false
|
||
end
|
||
end
|
||
|
||
function ENT:EndRepairing()
|
||
timer.Destroy('ZAPCRepair' .. self:EntIndex())
|
||
self.RepairingPlayer = nil
|
||
self:StopSound(ZAPC_REPAIR_SOUND)
|
||
end
|
||
|
||
concommand.Add('+zapc_repair', function(ply, cmd, args)
|
||
-- Check the trace
|
||
local ent = ply:GetEyeTrace().Entity
|
||
if not IsValid(ent) or not IsValid(ent.APC) then
|
||
return
|
||
end
|
||
|
||
-- Is it even allowed?
|
||
if ZAPC_REPAIR_FORCE() == 0 then
|
||
return
|
||
end
|
||
|
||
-- It's an APC! Amazing!
|
||
local zapc = ent.APC
|
||
|
||
-- If we aren't allowed to do anything, the APC is already being healed OR it is at full health, do nothing
|
||
if not CheckAccess(ply, 'repair', zapc) or IsValid(zapc.HealingPlayer) or zapc:Health() >= ZAPC_MAX_HEALTH()*1.5 or zapc.Destructing then
|
||
return
|
||
end
|
||
zapc:StartRepairing(ply)
|
||
end, nil, "What could this function possibly do? Better don't touch it.")
|
||
|
||
concommand.Add('-zapc_repair', function(ply, cmd, args)
|
||
local ent = ply:GetEyeTrace().Entity
|
||
if not IsValid(ent) or not IsValid(ent.APC) then
|
||
return
|
||
end
|
||
|
||
if ent.APC.RepairingPlayer == ply then
|
||
ent.APC:EndRepairing()
|
||
end
|
||
end, nil, "What could this function possibly do? Better don't touch it.")
|
||
|
||
function ENT:ToggleEngine()
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
|
||
self.TurnedOn = not self.TurnedOn
|
||
self[(self.TurnedOn and 'Start' or 'Stop') .. 'Engine'](self)
|
||
end
|
||
|
||
local function StopSirenSound(apc)
|
||
if not IsValid(apc) then
|
||
return
|
||
end
|
||
|
||
apc.SirenSound = nil
|
||
end
|
||
|
||
function ENT:ToggleSiren()
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
|
||
-- Don't enable before we died.
|
||
if not self.Siren and self.SirenSound then
|
||
return
|
||
end
|
||
|
||
self.Siren = not self.Siren
|
||
|
||
if self.SirenSound then
|
||
self.SirenSound:FadeOut(1.5)
|
||
timer.Simple(1.6, function() StopSirenSound(self) end)
|
||
self.NextAlarm = nil
|
||
else
|
||
self.SirenSound = CreateSound(self.DriverSeat, self.Special and 'd1_trainstation.ChaseMusic' or 'd1_canals_08.siren01')
|
||
self.SirenSound:SetSoundLevel(self.Special and 110 or 150)
|
||
self.SirenSound:PlayEx(1.0, 100)
|
||
end
|
||
end
|
||
|
||
function ENT:StartAlarm()
|
||
if self.Destructing then
|
||
return
|
||
end
|
||
|
||
-- If the siren isn't running, enable it
|
||
if not self.Siren then
|
||
self:ToggleSiren()
|
||
end
|
||
|
||
self.SirenSound:ChangePitch(200, 0)
|
||
self.NextAlarm = CurTime() + ZAPC_ALARM_INTERVAL
|
||
end
|
||
|
||
function ENT:ToggleSpeed()
|
||
--~ self.SlowDrive = not self.SlowDrive
|
||
--~ self.DriverSeat:SetKeyValue('vehiclescript', 'scripts/vehicles/zapc_script' .. (self.SlowDrive and '_slow' or '') .. '.txt')
|
||
--~ RunConsoleCommand('vehicle_flushscript')
|
||
end
|
||
|
||
-- Fixes the view
|
||
local function SetupPlayerVisibility(ply, ent)
|
||
if IsValid(ply:GetVehicle().APC) then
|
||
-- Add above him too
|
||
AddOriginToPVS(ply:GetVehicle():GetPos() + Vector(0, 0, 10))
|
||
end
|
||
end
|
||
hook.Add('SetupPlayerVisibility', '_ZAPC.SetupPlayerVisibility', SetupPlayerVisibility)
|
||
|
||
-- Non-admins and non-authorized personal cannot do ANYTHING to us.
|
||
-- ANYTHING!
|
||
function ENT:CanTool(ply, tr, mode)
|
||
return CheckAccess(ply, 'tool', self, tr, mode) and mode ~= 'duplicator'
|
||
end
|
||
|
||
function ENT:PhysgunPickup(ply)
|
||
return false
|
||
end
|
||
|
||
concommand.Add('zapc_togglehatch', function(ply, cmd, args)
|
||
local apc = ply:GetEyeTrace().Entity.APC
|
||
|
||
-- Check if it's an APC
|
||
if not IsValid(apc) then
|
||
ReplyTo(ply, 'You are not looking at an APC.')
|
||
return
|
||
end
|
||
|
||
-- Check what way we want it locked
|
||
local opened = not apc.HatchOpened
|
||
|
||
-- Do we have a parameter-sided override?
|
||
if #args > 0 and #args[1] > 0 then
|
||
opened = tobool(args[1])
|
||
end
|
||
|
||
-- For HUI reasons.
|
||
local openedString
|
||
-- `locked' describes the state we want to put it in.
|
||
if opened then
|
||
openedString = 'unlock'
|
||
else
|
||
openedString = 'lock'
|
||
end
|
||
|
||
-- openedString describes what the player wishes to do
|
||
|
||
-- Check distance
|
||
if apc.DriverSeat:GetPos():Distance(ply:GetPos()) > ZAPC_REMOTE_LOCK_DISTANCE() then
|
||
ReplyTo(ply, "You are too far away to %s this APC's hatch.", openedString)
|
||
return
|
||
end
|
||
|
||
-- Check authorization
|
||
if (not CheckAccess(ply, 'remotelock', apc, locked)) then
|
||
ReplyTo(ply, "You are not authorized to %s this APC's hatch.", openedString)
|
||
return
|
||
end
|
||
|
||
-- Check if we are even changing something
|
||
if opened == apc.HatchOpened then
|
||
ReplyTo(ply, "The APC's hatch is already %sed", openedString) -- I love English
|
||
return
|
||
end
|
||
|
||
-- Do it then, I guess
|
||
apc:ToggleHatch()
|
||
|
||
ReplyTo(ply, 'Success. Hatch is now %sed.', openedString)
|
||
end)
|
||
|
||
concommand.Add('zapc_enterhatch', function(ply, cmd, args)
|
||
local apc = ply:GetEyeTrace().Entity.APC
|
||
|
||
-- Check if it's an APC
|
||
if not IsValid(apc) then
|
||
ReplyTo(ply, 'You are not looking at an APC.')
|
||
return
|
||
end
|
||
|
||
-- Check distance. In tests, 100 seemed OK. No need to have a configuration for this.
|
||
if ply:GetEyeTrace().HitPos:Distance(ply:EyePos()) > 100 then
|
||
ReplyTo(ply, "You are too far away to enter this APC.")
|
||
return
|
||
end
|
||
|
||
if not apc.HatchOpened then
|
||
ReplyTo(ply, "This APC's hatch is closed.")
|
||
return
|
||
end
|
||
|
||
if #apc.Passengers >= ZAPC_MAX_PASSENGERS() then
|
||
ReplyTo(ply, "This APC is full.")
|
||
return
|
||
end
|
||
|
||
-- Add him.
|
||
apc:AddPassenger(ply)
|
||
end)
|
||
|
||
--[[
|
||
Because some people fail to realize that hooks should only return a value if they wish this action to be final,
|
||
there are some addons that break the APC. Currently, there are none that I know of. However, there have
|
||
been in the past and this is the reason this function exists.
|
||
|
||
What this function does is basically "wrapping" the old functions. If such a "rogue" function has been found,
|
||
it will be wrapped and re-placed. Currently, the wrapper does nothing, and I believe simply re-inserting it into
|
||
the hook table is enough to execute said function *after* ours.
|
||
|
||
There would be an extremely easy way to fix the issue - but we needed lua 5.2 for the __pairs metamethod.
|
||
]]
|
||
|
||
local function WrapFunction(event, name)
|
||
local func = hook.GetTable()[event][name]
|
||
if not func then
|
||
return
|
||
end
|
||
|
||
hook.Remove(event, name)
|
||
hook.Add(event, name, function(...) func(...) end)
|
||
end
|
||
|
||
local function Initialize()
|
||
--[[
|
||
The official HALL OF SHAME.
|
||
(NOW HIRING)
|
||
]]--
|
||
end
|
||
hook.Add('Initialize', '_ZAPC.Initialize', Initialize)
|
||
|
||
-- In case this happens again, here's a few debuggers.
|
||
local function DumpHooks(ply, cmd, args)
|
||
if IsValid(ply) and (game.IsDedicated() or not ply:IsListenServerHost()) then
|
||
ply:PrintMessage(HUD_PRINTCONSOLE, "[ZAPC] This is a server command. Execute it on the server, not the client.")
|
||
return
|
||
end
|
||
|
||
local hooks = { 'PlayerEnteredVehicle', 'PlayerUse', 'CanPlayerEnterVehicle', 'KeyPress', 'CanExitVehicle', 'ShouldCollide', 'EntityTakeDamage', 'PlayerLeaveVehicle' }
|
||
|
||
print("\n[ZAPC] Dumping possible hook collisions...")
|
||
for _, h in pairs(hooks) do
|
||
print("[ZAPC] " .. h)
|
||
for k, v in pairs(hook.GetTable()[h]) do
|
||
print('[ZAPC] "' .. tostring(k) .. '"')
|
||
end
|
||
print()
|
||
end
|
||
|
||
print("[ZAPC] Hooks dumped.")
|
||
end
|
||
concommand.Add('zapc_dumphooks', DumpHooks, nil, "Dumps all hooks that could possibly cause havok.") |