1
0
mirror of https://github.com/metrostroi-repo/MetrostroiAddon.git synced 2026-05-02 00:42:29 +00:00
Files
MetrostroiAddon/lua/metrostroi/sh_rerail.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