--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 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: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