1
0
mirror of https://github.com/metrostroi-repo/MetrostroiAddon.git synced 2026-05-02 00:42:29 +00:00
Files
MetrostroiAddon/lua/weapons/gmod_tool/stools/train_spawner.lua
2022-04-10 03:02:40 +03:00

540 lines
24 KiB
Lua

--local TOOL = player.GetBySteamID("STEAM_0:1:31566374"):GetTool("train_spawner")
TOOL.AddToMenu = false
local C_MaxWagons = GetConVar("metrostroi_maxwagons")
if CLIENT then
language.Add("Tool.train_spawner.name", "Train Spawner")
language.Add("Tool.train_spawner.desc", "Spawn a train")
language.Add("Tool.train_spawner.0", "Primary: Spawns a full train. Secondary: Reverse facing (yellow ed when facing the opposite side). Reload: Copy train settings.")
language.Add("Undone_81-7036", "Undone 81-7036 (does not work)")
language.Add("Undone_81-7037", "Undone 81-7037 (does not work)")
language.Add("Undone_81-717", "Undone 81-717")
language.Add("Undone_81-714", "Undone 81-714")
language.Add("Undone_Ezh3", "Undone Ezh3")
language.Add("Undone_Ema508T", "Undone Em508T")
language.Add("SBoxLimit_spawner_wrong_pos","Wrong train position! Can't spawn")
language.Add("SBoxLimit_spawner_restrict","This train is restricted for you")
end
local function Trace(ply,tr)
local verticaloffset = 5 -- Offset for the train model
local distancecap = 2000 -- When to ignore hitpos and spawn at set distanace
local pos, ang = nil
local inhibitrerail = false
--TODO: Make this work better for raw base ent
if tr.Hit then
-- Setup trace to find out of this is a track
local tracesetup = {}
tracesetup.start=tr.HitPos
tracesetup.endpos=tr.HitPos+tr.HitNormal*80
tracesetup.filter=ply
local tracedata = util.TraceLine(tracesetup)
if tracedata.Hit then
-- Trackspawn
pos = (tr.HitPos + tracedata.HitPos)/2 + Vector(0,0,verticaloffset)
ang = tracedata.HitNormal
ang:Rotate(Angle(0,90,0))
ang = ang:Angle()
-- Bit ugly because Rotate() messes with the orthogonal vector | Orthogonal? I wrote "origional?!" :V
else
-- Regular spawn
if tr.HitPos:Distance(tr.StartPos) > distancecap then
-- Spawnpos is far away, put it at distancecap instead
pos = tr.StartPos + tr.Normal * distancecap
inhibitrerail = true
else
-- Spawn is near
pos = tr.HitPos + tr.HitNormal * verticaloffset
end
ang = Angle(0,tr.Normal:Angle().y,0)
end
else
-- Trace didn't hit anything, spawn at distancecap
pos = tr.StartPos + tr.Normal * distancecap
ang = Angle(0,tr.Normal:Angle().y,0)
end
return {pos,ang,inhibitrerail}
end
function UpdateGhostPos(pl)
local trace = util.TraceLine(util.GetPlayerTrace(pl))
local tbl = Metrostroi.RerailGetTrackData(trace.HitPos,pl:GetAimVector())
if not tbl then tbl = Trace(pl, trace) end
local class = IsValid(trace.Entity) and trace.Entity:GetClass()
local pos,ang = Vector(0,0,0),Angle(0,0,0)
if tbl[3] ~= nil then
pos = tbl[1]+Vector(0,0,55)
ang = tbl[2]
return pos,ang,false,not class or (class == "func_door" or class == "prop_door_rotating")
else
pos = tbl.centerpos + Vector(0,0,112)
ang = tbl.right:Angle()+Angle(0,90,0)
return pos,ang,true,not class or (class == "func_door" or class == "prop_door_rotating")
end
end
function UpdateWagPos(pl)
local trace = util.TraceLine(util.GetPlayerTrace(pl))
local tbl = Metrostroi.RerailGetTrackData(trace.HitPos,pl:GetAimVector())
if not tbl then tbl = Trace(pl, trace) end
local pos,ang = Vector(0,0,0),Angle(0,0,0)
if tbl[3] ~= nil then
pos = tbl[1]
ang = tbl[2]
return pos,ang,false
else
pos = tbl.centerpos + Vector(0,0,112-55)
ang = tbl.right:Angle()+Angle(0,90,0)
return pos,ang,true
end
end
function TOOL:UpdateGhost()
local good,canDraw
for i,e in ipairs(self.GhostEntities) do
local t = self.Model[i]
local pos,ang
if i==1 then
pos,ang,good,canDraw = UpdateGhostPos(self:GetOwner())
if self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev") then
ang = ang+Angle(0,180,0)
end
elseif type(t) ~= "string" then
pos,ang = self.GhostEntities[1]:LocalToWorld(t.pos or Vector(0,0,0)),self.GhostEntities[1]:LocalToWorldAngles(self.Model[i].ang or Angle(0,0,0))
else
pos,ang = self.GhostEntities[1]:GetPos(),self.GhostEntities[1]:GetAngles()
end
e:SetNoDraw(not canDraw)
--if not pos then bad = true else pos,ang = rpos,rang end
if not good then
e:SetColor(Color(255,150,150,255))
elseif self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev") then
e:SetColor(Color(255,255,150,255))
else
e:SetColor(Color(255,255,255,255))
end
e:SetPos(pos)
e:SetAngles(ang)
end
end
function TOOL:Holster()
if not IsFirstTimePredicted() or SERVER then return end
end
--local owner
function TOOL:Think()
if not self.Train then return end
--owner = self:GetOwner()
--self.tbl = self:GetConvar()
--self.int = self.tbl.Prom > 0 or !Trains[self.tbl.Train][1]:find("Ezh3")
if CLIENT and self.Train.Spawner.model then
if not self.GhostEntities then self.GhostEntities = {} end
if not IsValid(self.GhostEntities[1]) or self.Model ~= self.Train.Spawner.model then
self.Model = self.Train.Spawner.model
for _,e in pairs(self.GhostEntities) do SafeRemoveEntity(e) end
self.GhostEntities = {}
if type(self.Model) == "string" then
self.GhostEntities[1] = ClientsideModel(self.Model,RENDERGROUP_OPAQUE)
self.GhostEntities[1]:SetModel(self.Model)
else
for i,t in pairs(self.Model) do
if type(t) == "string" then
self.GhostEntities[i] = ClientsideModel(t,RENDERGROUP_OPAQUE)
self.GhostEntities[i]:SetModel(t)
else
self.GhostEntities[i] = ClientsideModel(t[1],RENDERGROUP_OPAQUE)
self.GhostEntities[i]:SetModel(t[1])
end
end
end
for i,e in pairs(self.GhostEntities) do
e:SetRenderMode(RENDERMODE_TRANSALPHA)
e.GetBodyColor = function() return Vector(1,1,1) end
e.GetDirtLevel = function() return 0.25 end
end
hook.Add("Think",self.GhostEntities[1],function()
if not IsValid(self.Owner:GetActiveWeapon()) or self.Owner:GetActiveWeapon():GetClass()~="gmod_tool" or GetConVar("gmod_toolmode"):GetString() ~= "train_spawner" then
self:OnRemove()
end
end)
local oldOR = self.GhostEntities[1].OnRemove
self.GhostEntities[1].OnRemove = function(ent)
hook.Remove("Think",ent)
oldOR(ent)
end
else
self:UpdateGhost()
end
end
---if SERVER then self.Rev = self.Rev end
end
function TOOL:SetSettings(ent, ply, i,inth)
local rot = false
if i > 1 then
rot = i == self.tbl.WagNum and true or math.random() > 0.5
end
end
local function SetValue(ent,id,val)
if type(val) == "number" then
ent:SetNW2Int(id,val)
elseif type(val) == "string" then
ent:SetNW2String(id,val)
elseif type(val) == "boolean" then
ent:SetNW2Bool(id,val)
end
end
function TOOL:SpawnWagon(trace)
if CLIENT then return end
local ply = self:GetOwner()
local FIXFIXFIX = {}
for i=1,math.random(12) do
FIXFIXFIX[i] = ents.Create("env_sprite")
FIXFIXFIX[i]:Spawn()
end
local LastRot,LastEnt = false
local trains = {}
for i=1,self.Settings.WagNum do
local spawnfunc = self.Train.Spawner.spawnfunc
local ent
if i == 1 then
if spawnfunc then
ent = self.Train:SpawnFunction(ply,trace,spawnfunc(i,self.Settings,self.Train),self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev"),UpdateWagPos)
else
ent = self.Train:SpawnFunction(ply,trace,self.Train.Spawner.head or self.Train.ClassName,self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev"),UpdateWagPos)
end
--nil,self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev") and Angle(0,180,0) or Angle(0,0,0)) --Create a first entity in queue
if ent then
undo.Create(self.Train.Spawner.head or self.Train.ClassName)
else
self:GetOwner():LimitHit("spawner_wrong_pos")
return false
end
--if self:GetOwner():GetNW2Bool("metrostroi_train_spawner_rev") then
--ent:SetAngles(ent:LocalToWorldAngles(Angle(0,180,0)))
--end
--if self.Rot then
end
if i > 1 then
local rot = (i==self.Settings.WagNum or math.random() > 0.5) -- Rotate last wagon or rotate it randomly
if spawnfunc then
ent = ents.Create(spawnfunc(i,self.Settings,self.Train))
else
ent = ents.Create(i~=self.Settings.WagNum and self.Train.Spawner.interim or self.Train.Spawner.head or self.Train.ClassName)
end
ent.Owner = ply
ent:Spawn()
-- Invert bogeys by rotation
local bogeyL1,bogeyE1,bogeyE2
local couplL1,couplE1,couplE2
if LastRot then
bogeyL1 = LastEnt.FrontBogey
couplL1 = LastEnt.FrontCouple
else
bogeyL1 = LastEnt.RearBogey
couplL1 = LastEnt.RearCouple
end
if rot then
bogeyE1,bogeyE2 = ent.RearBogey,ent.FrontBogey
couplE1,couplE2 = ent.FrontCouple,ent.RearCouple
else
bogeyE1,bogeyE2 = ent.FrontBogey,ent.RearBogey
couplE1,couplE2 = ent.RearCouple,ent.FrontCouple
end
local haveCoupler = couplL1 ~= nil
if haveCoupler then
bogeyE1:SetAngles(ent:LocalToWorldAngles(bogeyE1.SpawnAng))
bogeyE2:SetAngles(ent:LocalToWorldAngles(bogeyE1.SpawnAng))
-- Set bogey position by our bogey couple offset and lastent bogey couple offset
couplE1:SetPos(
couplL1:LocalToWorld(
Vector(
couplL1.CouplingPointOffset.x*1.1+couplE1.CouplingPointOffset.x*1.1,
couplL1.CouplingPointOffset.y-couplE1.CouplingPointOffset.y,
couplL1.CouplingPointOffset.z-couplE1.CouplingPointOffset.z
)
)
)
-- Set bogey angles
couplE1:SetAngles(couplL1:LocalToWorldAngles(Angle(0,180,0)))
-- Set entity position by bogey pos and bogey offset
couplE2:SetAngles(couplE1:LocalToWorldAngles(Angle(0,180,0)))
ent:SetPos(couplE1:LocalToWorld(couplE1.SpawnPos*Vector(rot and -1 or 1,-1,-1)))
-- Set entity angles by last ent and rotation
ent:SetAngles(LastEnt:LocalToWorldAngles(Angle(0,rot ~= LastRot and 180 or 0,0)))
-- Set bogey pos
bogeyE1:SetPos(ent:LocalToWorld(bogeyE1.SpawnPos))
bogeyE2:SetPos(ent:LocalToWorld(bogeyE2.SpawnPos))
-- Set bogey angles
bogeyE1:SetAngles(ent:LocalToWorldAngles(bogeyE1.SpawnAng))
bogeyE2:SetAngles(ent:LocalToWorldAngles(bogeyE1.SpawnAng))
else
-- Set bogey position by our bogey couple offset and lastent bogey couple offset
bogeyE1:SetPos(
bogeyL1:LocalToWorld(
Vector(bogeyL1.CouplingPointOffset.x*1.1+bogeyE1.CouplingPointOffset.x*1.05,bogeyL1.CouplingPointOffset.y-bogeyE1.CouplingPointOffset.y,bogeyL1.CouplingPointOffset.z-bogeyE1.CouplingPointOffset.z)
)
)
-- Set bogey angles
bogeyE1:SetAngles(bogeyL1:LocalToWorldAngles(Angle(0,180,0)))
-- Set entity position by bogey pos and bogey offset
bogeyE2:SetAngles(bogeyE1:LocalToWorldAngles(Angle(0,180,0)))
ent:SetPos(bogeyE1:LocalToWorld(bogeyE1.SpawnPos*Vector(rot and -1 or 1,-1,-1)))
-- Set entity angles by last ent and rotation
ent:SetAngles(LastEnt:LocalToWorldAngles(Angle(0,rot ~= LastRot and 180 or 0,0)))
-- Set second bogey pos
bogeyE2:SetPos(ent:LocalToWorld(bogeyE2.SpawnPos))
end
Metrostroi.RerailTrain(ent) --Rerail train
--LastEnt:LocalToWorld(bogeyL1:WorldToLocal(Vector))))
LastRot = rot
end
ent._Settings = self.Settings
table.insert(trains,ent)
undo.AddEntity(ent)
--[[
ent:SetMoveType(MOVETYPE_NONE)
ent.FrontBogey:SetMoveType(MOVETYPE_NONE)
ent.RearBogey:SetMoveType(MOVETYPE_NONE)
if IsValid(ent.FrontCouple) then
ent.FrontCouple:SetMoveType(MOVETYPE_NONE)
ent.RearCouple:SetMoveType(MOVETYPE_NONE)
end]]
for _, set in ipairs(self.Train.Spawner) do
local val = self.Settings[set[1]]
if set[3] == "List" then
if set[6] and type(set[6]) == "function" then set[6](ent,val,LastRot,i,self.Settings.WagNum) else SetValue(ent,set[1],val) end
elseif set[3] == "Boolean" then
if set[5] and type(set[5]) == "function" then set[5](ent,val,LastRot,i,self.Settings.WagNum) else ent:SetNW2Bool(set[1],val) end
elseif set[3] == "Slider" then
if set[8] and type(set[8]) == "function" then set[8](ent,val,LastRot,i,self.Settings.WagNum) else ent:SetNW2Int(set[1],val) end
end
end
if self.Train.Spawner.func then self.Train.Spawner.func(ent,i,self.Settings.WagNum,LastRot) end
if self.Train.Spawner.wagfunc then ent:GenerateWagonNumber(function(_,number) return self.Train.Spawner.wagfunc(ent,i,number) end) end
if ent.TrainSpawnerUpdate then ent:TrainSpawnerUpdate() end
for k,v in pairs(ent.CustomSpawnerUpdates) do if k ~= "BaseClass" then v(ent) end end
hook.Run("MetrostroiSpawnerUpdate",ent,self.Settings)
ent:UpdateTextures()
ent.FrontAutoCouple = i > 1 and i < self.Settings.WagNum
ent.RearAutoCouple = self.Settings.WagNum > 1
LastEnt = ent
end
undo.SetPlayer(ply)
undo.SetCustomUndoText("Undone a train")
undo.Finish()
if self.Train.Spawner.postfunc then self.Train.Spawner.postfunc(trains,self.Settings.WagNum) end
--if self.Settings.AutoCouple and #trains > 1 then
local CoupledTrains,WagNum = 0,self.Settings.WagNum
local function StopCoupling()
if not IsValid(trains[1]) or not trains[1].IgnoreEngine then return end
for _,train in ipairs(trains) do
train.FrontBogey.BrakeCylinderPressure = 3
train.RearBogey.BrakeCylinderPressure = 3
train.FrontBogey.MotorPower = 0
train.RearBogey.MotorPower = 0
train.OnCoupled = nil
end
timer.Simple(1,function() for i,train in ipairs(trains) do train.IgnoreEngine = false end end)
end
for i,train in ipairs(trains) do
train.IgnoreEngine = true
train.RearBogey.MotorForce = 40000
train.FrontBogey.MotorForce = 40000
train.RearBogey.PneumaticBrakeForce = 50000
train.FrontBogey.PneumaticBrakeForce = 50000
if i==#trains then
train.RearBogey.MotorPower = 1
train.FrontBogey.MotorPower = 0
else
train.RearBogey.MotorPower = 0
train.FrontBogey.MotorPower = 0
end
if i==1 then
train.FrontBogey.BrakeCylinderPressure = 3
train.RearBogey.BrakeCylinderPressure = 3
else
train.FrontBogey.BrakeCylinderPressure = 0
train.RearBogey.BrakeCylinderPressure = 0
end
train.OnCoupled = function(ent)
CoupledTrains = CoupledTrains + 0.5
if CoupledTrains==WagNum-1 then StopCoupling() end
end
end
timer.Simple(3+1*#trains,StopCoupling)
--end
--self.rot = false
for k,v in pairs(FIXFIXFIX) do SafeRemoveEntity(v) end
end
function TOOL:OnRemove()
self:Finish()
end
function TOOL:Finish()
for _,e in pairs(self.GhostEntities) do SafeRemoveEntity(e) end
self.GhostEntities = {}
end
function TOOL:Reload(trace)
if CLIENT then return end
local ply = self:GetOwner()
if IsValid(trace.Entity) and trace.Entity._Settings then
ply:ConCommand("gmod_tool train_spawner")
ply:SelectWeapon("gmod_tool")
local tool = ply:GetTool("train_spawner")
tool.AllowSpawn = true
tool.Settings = trace.Entity._Settings
local ENT = scripted_ents.Get(tool.Settings.Train)
if not ENT then tool.AllowSpawn = false else tool.Train = ENT end
net.Start("train_spawner_open")
net.WriteTable(tool.Settings)
net.Send(ply)
end
local spawner = ents.Create("gmod_train_spawner")
spawner:SpawnFunction(ply)
end
function TOOL:LeftClick(trace)
if not self.Train then return end
local class = IsValid(trace.Entity) and trace.Entity:GetClass()
if class and (trace.Entity.Spawner or class ~= "func_door" and class ~= "prop_door_rotating") then
if SERVER then
if trace.Entity.ClassName == (self.Train.Spawner.head or self.Train.ClassName) or trace.Entity.ClassName == self.Train.Spawner.interim then
local LastEnt
local trains = {}
for k,ent in ipairs(trace.Entity.WagonList) do
--[[
local rot = ent.RearTrain and ent.RearTrain.FrontTrain == ent or ent.FrontTrain and ent.FrontTrain.RearTrain == ent
if not LastRot then
rot = ent.RearTrain and ent.RearTrain.RearTrain == ent or ent.FrontTrain and ent.FrontTrain.FrontTrain == ent
end]]
local rot = ent.RearTrain == LastEnt
LastEnt = ent
for i, set in ipairs(self.Train.Spawner) do
local val = self.Settings[set[1]]
if set[3] == "List" then
if set[6] and type(set[6]) == "function" then set[6](ent,val,rot,k,self.Settings.WagNum) else SetValue(ent,set[1],val) end
elseif set[3] == "Boolean" then
if set[5] and type(set[5]) == "function" then set[5](ent,val,rot,k,self.Settings.WagNum) else ent:SetNW2Bool(set[1],val) end
elseif set[3] == "Slider" then
if set[8] and type(set[8]) == "function" then set[8](ent,val,rot,k,self.Settings.WagNum) else ent:SetNW2Int(set[1],val) end
end
end
if self.Train.Spawner.func then self.Train.Spawner.func(ent,k,self.Settings.WagNum,rot) end
ent:GenerateWagonNumber(self.Train.Spawner.wagfunc)
if ent.TrainSpawnerUpdate then ent:TrainSpawnerUpdate() end
for k,v in pairs(ent.CustomSpawnerUpdates) do if k ~= "BaseClass" then v(ent) end end
hook.Run("MetrostroiSpawnerUpdate",ent,self.Settings)
ent:UpdateTextures()
ent._Settings = self.Settings
table.insert(trains,ent)
end
if self.Train.Spawner.postfunc then self.Train.Spawner.postfunc(trains,self.Settings.WagNum) end
end
end
return
end
if not self.AllowSpawn or not self.Train then return end
if SERVER then
if self.Settings.WagNum > C_MaxWagons:GetInt() then
self.Settings.WagNum = C_MaxWagons:GetInt()
end
if Metrostroi.TrainCountOnPlayer(self:GetOwner()) + self.Settings.WagNum > GetConVar("metrostroi_maxtrains_onplayer"):GetInt()*C_MaxWagons:GetInt()
or Metrostroi.TrainCount() + self.Settings.WagNum > GetConVar("metrostroi_maxtrains"):GetInt()*C_MaxWagons:GetInt() then
self:GetOwner():LimitHit("train_limit")
return true
end
if hook.Run("MetrostroiSpawnerRestrict",self:GetOwner(),self.Settings) then
self:GetOwner():LimitHit("spawner_restrict")
return true
end
end
self:SpawnWagon(trace)
return
end
function TOOL:RightClick(trace)
if not self.Train then return end
if IsValid(trace.Entity) then
if SERVER then
if trace.Entity.ClassName == (self.Train.Spawner.head or self.Train.ClassName) or trace.Entity.ClassName == self.Train.Spawner.interim then
local LastEnt
local trains = {}
for k,ent in pairs(trace.Entity.WagonList) do
local rot = ent.RearTrain == LastEnt
LastEnt = ent
if ent ~= trace.Entity then continue end
for i, set in ipairs(self.Train.Spawner) do
local val = self.Settings[set[1]]
if set[3] == "List" then
if set[6] and type(set[6]) == "function" then set[6](ent,val,rot,k,self.Settings.WagNum,true) else SetValue(ent,set[1],val) end
elseif set[3] == "Boolean" then
if set[5] and type(set[5]) == "function" then set[5](ent,val,rot,k,self.Settings.WagNum,true) else ent:SetNW2Bool(set[1],val) end
elseif set[3] == "Slider" then
if set[8] and type(set[8]) == "function" then set[8](ent,val,rot,k,self.Settings.WagNum,true) else ent:SetNW2Int(set[1],val) end
end
end
if self.Train.Spawner.func then self.Train.Spawner.func(ent,k,self.Settings.WagNum,rot) end
ent:GenerateWagonNumber(self.Train.Spawner.wagfunc)
if ent.TrainSpawnerUpdate then ent:TrainSpawnerUpdate() end
for k,v in pairs(ent.CustomSpawnerUpdates) do if k ~= "BaseClass" then v(ent) end end
hook.Run("MetrostroiSpawnerUpdate",ent,self.Settings)
ent:UpdateTextures()
ent._Settings = self.Settings
table.insert(trains,ent)
if self.Train.Spawner.postfunc then self.Train.Spawner.postfunc(trains,self.Settings.WagNum) end
end
end
end
return
end
if not self.AllowSpawn or not self.Train then return end
if CLIENT then return end
self.Rev = not self.Rev
self:GetOwner():SetNW2Bool("metrostroi_train_spawner_rev",self.Rev)
end
function TOOL.BuildCPanel(panel)
panel:SetName("#Tool.train_spawner.name")
panel:Help("#Tool.train_spawner.desc")
end
if SERVER then
util.AddNetworkString "train_spawner_open"
net.Receive("train_spawner_open",function(len,ply)
ply:ConCommand("gmod_tool train_spawner")
ply:SelectWeapon("gmod_tool")
local tool = ply:GetTool("train_spawner")
tool.AllowSpawn = true
tool.Settings = net.ReadTable()
local ENT = scripted_ents.Get(tool.Settings.Train)
if not ENT then tool.AllowSpawn = false else tool.Train = ENT end
end)
return
end