mirror of
https://github.com/metrostroi-repo/MetrostroiAddon.git
synced 2026-05-02 00:42:29 +00:00
315 lines
10 KiB
Lua
315 lines
10 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Rerailing, testing whether train is on rails
|
|
--------------------------------------------------------------------------------
|
|
-- Z Offset for rerailing bogeys
|
|
local bogeyOffset = 31
|
|
local TRACK_GAUGE = 80 --Distance between rails
|
|
local TRACK_WIDTH = 5.8 --Width of a single rail
|
|
local TRACK_HEIGHT = 10 --Height of a single rail
|
|
local TRACK_CLEARANCE = 150 --Vertical space above the rails that will always be clear of world, also used as rough estimation of train height
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local TRACK_SINGLERAIL = (TRACK_GAUGE + TRACK_WIDTH) / 2
|
|
|
|
local function dirdebug(v1,v2)
|
|
debugoverlay.Line(v1,v1+v2*30,10,Color(255,0,0),true)
|
|
end
|
|
|
|
-- Takes datatable from getTrackData
|
|
local function debugtrackdata(data)
|
|
dirdebug(data.centerpos,data.forward)
|
|
dirdebug(data.centerpos,data.right)
|
|
dirdebug(data.centerpos,data.up)
|
|
end
|
|
|
|
-- Helper for commonly used trace
|
|
local function traceWorldOnly(pos,dir,col)
|
|
local tr = util.TraceLine({
|
|
start = pos,
|
|
endpos = pos+dir,
|
|
mask = MASK_NPCWORLDSTATIC
|
|
})
|
|
if false then -- Shows all traces done by rerailer
|
|
debugoverlay.Line(tr.StartPos,tr.HitPos,10,col or Color(0,0,255),true)
|
|
debugoverlay.Sphere(tr.StartPos,2,10,Color(0,255,255),true)
|
|
end
|
|
return tr
|
|
end
|
|
|
|
-- Go over the enttable, bogeys and train and reset them
|
|
local function resetSolids(enttable,train)
|
|
for k,v in pairs(enttable) do
|
|
if IsValid(k) then
|
|
k:SetSolid(v)
|
|
k:GetPhysicsObject():EnableMotion(true)
|
|
end
|
|
end
|
|
if train ~= nil and IsValid(train) then
|
|
train.FrontBogey:GetPhysicsObject():EnableMotion(true)
|
|
train.RearBogey:GetPhysicsObject():EnableMotion(true)
|
|
if IsValid(train.FrontCouple) then
|
|
train.FrontCouple:GetPhysicsObject():EnableMotion(true)
|
|
train.RearCouple:GetPhysicsObject():EnableMotion(true)
|
|
end
|
|
|
|
train:GetPhysicsObject():EnableMotion(true)
|
|
end
|
|
end
|
|
|
|
-- Elevates a position to track level
|
|
-- Requires a position in the center of the track
|
|
local function ElevateToTrackLevel(pos,right,up)
|
|
local tr1 = traceWorldOnly(pos+up*TRACK_CLEARANCE+right*TRACK_SINGLERAIL,-up*TRACK_CLEARANCE*2)
|
|
local tr2 = traceWorldOnly(pos+up*TRACK_CLEARANCE-right*TRACK_SINGLERAIL,-up*TRACK_CLEARANCE*2)
|
|
--Trace from above the track down to the rails
|
|
if not tr1.Hit or not tr2.Hit then return false end
|
|
return (tr1.HitPos + tr2.HitPos)/2
|
|
end
|
|
|
|
-- Takes position and initial rough forward vector, return table of track data
|
|
-- Position needs to be between/below the tracks already, don't use a props origin
|
|
-- Only needs a rough forward vector, ent:GetAngles():Forward() suffices
|
|
local function getTrackData(pos,forward)
|
|
--Trace down
|
|
--debugoverlay.Cross(pos,5,10,Color(255,0,255),true)
|
|
local tr = traceWorldOnly(pos,Vector(0,0,-500))
|
|
if !tr or !tr.Hit then return false end
|
|
|
|
|
|
--debugoverlay.Line(tr.StartPos,tr.HitPos,10,Color(0,255,0),true)
|
|
local updir = tr.HitNormal
|
|
local floor = tr.HitPos + updir * (TRACK_HEIGHT * 0.9)
|
|
local right = forward:Cross(updir)
|
|
|
|
--Trace right
|
|
local tr = traceWorldOnly(floor,right*500)
|
|
if not tr or not tr.Hit then return false end
|
|
|
|
|
|
--debugoverlay.Line(tr.StartPos,tr.HitPos,10,Color(0,255,0),true)
|
|
|
|
local trackforward = tr.HitNormal:Cross(updir)
|
|
local trackright = trackforward:Cross(updir)
|
|
|
|
debugoverlay.Axis(floor,trackforward:Angle(),10,5,true)
|
|
|
|
--debugoverlay.Line(pos,pos+trackforward*30,10,Color(255,0,0),true)
|
|
|
|
--Trace right with proper right
|
|
local tr1 = traceWorldOnly(floor,trackright*TRACK_GAUGE)
|
|
local tr2 = traceWorldOnly(floor,-trackright*TRACK_GAUGE)
|
|
if not tr1 or not tr2 then return false end
|
|
|
|
|
|
local floor = (tr1.HitPos+tr2.HitPos)/2
|
|
|
|
debugoverlay.Cross(floor,5,10,Color(0,255,0),true)
|
|
|
|
local centerpos = ElevateToTrackLevel(floor,trackright,updir)
|
|
|
|
if not centerpos then return false end
|
|
|
|
debugoverlay.Cross(centerpos,5,10,Color(255,0,0),true)
|
|
|
|
local data = {
|
|
forward = trackforward,
|
|
right = trackright,
|
|
up = updir,
|
|
centerpos = centerpos
|
|
}
|
|
|
|
return data
|
|
end
|
|
Metrostroi.RerailGetTrackData = getTrackData
|
|
|
|
|
|
-- Helper function that tries to find trackdata at -z or -ent:Up()
|
|
local function getTrackDataBelowEnt(ent)
|
|
local forward = ent:GetAngles():Forward()
|
|
|
|
local tr = traceWorldOnly(ent:GetPos(),Vector(0,0,-500))
|
|
if tr.Hit then
|
|
local td = getTrackData(tr.HitPos,forward)
|
|
if td then return td end
|
|
end
|
|
|
|
local tr = traceWorldOnly(ent:GetPos(),ent:GetAngles():Up()*-500)
|
|
if tr.Hit then
|
|
local td = getTrackData(tr.HitPos,forward)
|
|
if td then return td end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function PlayerCanRerail(ply,ent)
|
|
if CPPI and ent.CPPICanTool then
|
|
return ent:CPPICanTool(ply,"metrostroi_rerailer")
|
|
else
|
|
return ply:IsAdmin() or (ent.Owner and ent.Owner == ply)
|
|
end
|
|
end
|
|
|
|
-- ConCMD for rerailer
|
|
local function RerailConCMDHandler(ply,cmd,args,fullstring)
|
|
local train = ply:GetEyeTrace().Entity
|
|
if not IsValid(train) then return end
|
|
|
|
|
|
--If we're aiming at bogeys or wheels
|
|
local nwent = train:GetNW2Entity("TrainEntity")
|
|
if nwent and nwent.SubwayTrain ~= nil then
|
|
train = nwent
|
|
end
|
|
|
|
if not PlayerCanRerail(ply,train) then return end
|
|
|
|
if train:GetClass() == "gmod_train_bogey" then
|
|
Metrostroi.RerailBogey(train)
|
|
else
|
|
Metrostroi.RerailTrain(train)
|
|
end
|
|
end
|
|
if SERVER then
|
|
concommand.Add("metrostroi_rerail",RerailConCMDHandler)
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Rerails a single bogey
|
|
--------------------------------------------------------------------------------
|
|
function Metrostroi.RerailBogey(bogey)
|
|
if timer.Exists("metrostroi_rerailer_solid_reset_"..bogey:EntIndex()) then return false end
|
|
|
|
local trackData = getTrackDataBelowEnt(bogey)
|
|
if not trackData then return false end
|
|
bogey:SetPos(trackData.centerpos+trackData.up*(bogey.BogeyOffset or bogeyOffset))
|
|
bogey:SetAngles(trackData.forward:Angle())
|
|
|
|
bogey:GetPhysicsObject():EnableMotion(false)
|
|
|
|
local solids = {}
|
|
local wheels = bogey.Wheels
|
|
|
|
solids[bogey]=bogey:GetSolid()
|
|
bogey:SetSolid(SOLID_NONE)
|
|
|
|
if wheels ~= nil then
|
|
solids[wheels]=wheels:GetSolid()
|
|
wheels:SetSolid(SOLID_NONE)
|
|
end
|
|
|
|
timer.Create("metrostroi_rerailer_solid_reset_"..bogey:EntIndex(),1,1,function() resetSolids(solids) end )
|
|
return true
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Rerails given train entity
|
|
--------------------------------------------------------------------------------
|
|
function Metrostroi.RerailTrain(train)
|
|
|
|
--Safety checks
|
|
if not IsValid(train) or train.SubwayTrain == nil then return false end
|
|
if train.NoPhysics or not IsValid(train:GetPhysicsObject()) then return false end
|
|
if not IsValid(train.FrontBogey) or not IsValid(train.RearBogey) then return false end
|
|
if timer.Exists("metrostroi_rerailer_solid_reset_"..train:EntIndex()) then return false end
|
|
--[[
|
|
--Trace down to get the track
|
|
local tr = traceWorldOnly(train:GetPos(),Vector(0,0,-500))
|
|
if !tr or !tr.Hit then
|
|
tr = traceWorldOnly(train:GetPos(),train:GetAngles():Up()*-500)
|
|
if !tr or !tr.Hit then return false end
|
|
end
|
|
|
|
--Get track data below the train
|
|
local trackdata = getTrackData(tr.HitPos+tr.HitNormal*3,train:GetAngles():Forward())
|
|
if !trackdata then return false end
|
|
--]]
|
|
|
|
local trackdata = getTrackDataBelowEnt(train)
|
|
if not trackdata then return false end
|
|
local ang = trackdata.forward:Angle()
|
|
|
|
|
|
--Get the positions of the bogeys if we'd rerail the train now
|
|
local frontoffset=train:WorldToLocal(train.FrontBogey:GetPos())
|
|
frontoffset:Rotate(ang)
|
|
local frontpos = frontoffset+train:GetPos()
|
|
|
|
local rearoffset = train:WorldToLocal(train.RearBogey:GetPos())
|
|
rearoffset:Rotate(ang)
|
|
local rearpos=rearoffset+train:GetPos()
|
|
|
|
--Get thet track data at these locations
|
|
local tr = traceWorldOnly(frontpos,-trackdata.up*500)
|
|
if !tr or !tr.Hit then return false end
|
|
local frontdata = getTrackData(tr.HitPos+tr.HitNormal*3,trackdata.forward)
|
|
if !frontdata then return false end
|
|
|
|
local tr = traceWorldOnly(rearpos,-trackdata.up*500)
|
|
if !tr or !tr.Hit then return false end
|
|
local reardata = getTrackData(tr.HitPos+tr.HitNormal*3,trackdata.forward)
|
|
if !reardata then return false end
|
|
|
|
--Find the current difference between the bogeys and the train's model center
|
|
local TrainOriginToBogeyOffset = (train:WorldToLocal(train.FrontBogey:GetPos())+train:WorldToLocal(train.RearBogey:GetPos()))/2
|
|
|
|
--Final trains pos is the average of the 2 bogey locations
|
|
local trainpos = (frontdata.centerpos+reardata.centerpos)/2
|
|
|
|
--Apply bogey-origin and bogey-track offsets
|
|
trainpos = LocalToWorld(TrainOriginToBogeyOffset*-1,ang,trainpos,ang) + Vector(0,0,(train.FrontBogey.BogeyOffset or bogeyOffset))
|
|
--Not sure if this is neccesary anymore, but I'm not touching this anytime soon
|
|
|
|
--Store and set solids
|
|
local entlist = {
|
|
train,
|
|
train.FrontBogey,
|
|
train.RearBogey,
|
|
train.FrontBogey.Wheels,
|
|
train.RearBogey.Wheels,
|
|
train.FrontCouple,
|
|
train.RearCouple
|
|
}
|
|
|
|
local solids = {}
|
|
for k,v in pairs(entlist) do
|
|
solids[v]=v:GetSolid()
|
|
v:SetSolid(SOLID_NONE)
|
|
end
|
|
|
|
train:SetPos(trainpos)
|
|
train:SetAngles(ang)
|
|
|
|
train.FrontBogey:SetPos(train:LocalToWorld(train.FrontBogey.SpawnPos))--frontdata.centerpos+frontdata.up*(train.FrontBogey.BogeyOffset or bogeyOffset))
|
|
train.RearBogey:SetPos(train:LocalToWorld(train.RearBogey.SpawnPos))--reardata.centerpos+reardata.up*(train.FrontBogey.BogeyOffset or bogeyOffset))
|
|
|
|
train.FrontBogey:SetAngles(train:LocalToWorldAngles(train.FrontBogey.SpawnAng))--(frontdata.forward*-1):Angle())
|
|
train.RearBogey:SetAngles(train:LocalToWorldAngles(train.RearBogey.SpawnAng))--reardata.forward:Angle())
|
|
|
|
|
|
if IsValid(train.FrontCouple) then
|
|
train.FrontCouple:SetPos(train:LocalToWorld(train.FrontCouple.SpawnPos))
|
|
train.RearCouple:SetPos(train:LocalToWorld(train.RearCouple.SpawnPos))
|
|
train.FrontCouple:SetAngles(train:LocalToWorldAngles(train.FrontCouple.SpawnAng))
|
|
train.RearCouple:SetAngles(train:LocalToWorldAngles(train.RearCouple.SpawnAng))
|
|
|
|
train.FrontCouple:GetPhysicsObject():EnableMotion(false)
|
|
train.RearCouple:GetPhysicsObject():EnableMotion(false)
|
|
end
|
|
|
|
train:GetPhysicsObject():EnableMotion(false)
|
|
|
|
train.FrontBogey:GetPhysicsObject():EnableMotion(false)
|
|
train.RearBogey:GetPhysicsObject():EnableMotion(false)
|
|
|
|
timer.Create("metrostroi_rerailer_solid_reset_"..train:EntIndex(),1,1,function() resetSolids(solids,train) end )
|
|
return true
|
|
end
|