Skip to content

Instantly share code, notes, and snippets.

@Divran
Last active June 22, 2020 18:33
Show Gist options
  • Save Divran/8c23f82857196559b9fbb0e37c374e36 to your computer and use it in GitHub Desktop.
Save Divran/8c23f82857196559b9fbb0e37c374e36 to your computer and use it in GitHub Desktop.
train re-railer v1

Divran's re-railer E2

hologram train

real train teleported

Crowbar commands

  • Left click: Scan train (left click the front main body of the train, the locomotive)
  • Right click: Hold this and drag from one side of the rails to the other to start scanning the track
  • Reload: After scanning the track, press reload to teleport the train

Chat commands

  • .trackwidth (number)

    or

    .trackwidth (name)

    • Sets the width of the track (default: 75 source units)
    • Available names:
      • "russian" (79.9375 units - will use 75)
      • "2ft" (31.9365 units - will use 28)
      • "standard" (unknown, for now)
  • .save

    • Scans and saves the train.
    • If you are sitting in a vehicle, will start the scan from the prop the E2 is welded to
    • If you are not sitting in a vehicle look at your front or rear loco/car
  • .scan

    • Scans the track for a suitable place to rerail.
    • The chat command scanner isn't as versatile as using the crowbar method. It's only able to find the start of the track if the track has nothing blocking it in between the rails. If you want to start the scan in an area with something blocking the center of the rails, you should use the crowbar method instead.
    • The chat command scanner also can't start in an area where the two rails are "hovering" above the ground (a common issue on sunsetgulch)
  • .teleport

    or

    .tp

    • Teleports the train to the scanned position
  • .crowbar

    • Toggles the use of the crowbar

Notes

  • This rerailer has a few requirements:
    1. The train has at least 2 units, (Loco + cab, Loco + loco, cab+cab etc.) to be scanned. (This limitation is due to model makers not having a standard forward direction for their props.) (It can still work for single-unit trains, if the model happens to have a proper forward direction specified by its creator.)
    2. You must select the first (or last) wagon/loco in your train. Selecing any other wagon will not work, and selecting a bogey will break it in strange ways, so don't do that.
    3. Each unit should have bogeys constrained to one base prop.
    4. The train must be positioned in a straight line while scanning and saving the train.
  • If the number of constrained entities changes by more than a few since your scan the rerailer will not allow the teleport to occur (for your safety).
  • The saved train data will be stored in a gTable, so if you accidentally remove your E2, you can simply respawn it and it'll reload the data.
@name Divran's train re-railer v1
#contraption scanner stuff
@persist Scanned:table
@persist ScanQueueWagons:array ScanQueueBogeys:array
@persist AlreadyScannedWagons:array AlreadyScannedBogeys:array
@persist ScanIdx ShiftAmount:vector
#track scanner stuff
@persist Active FunctionsDefined DefaultTrackWidth
@persist SmallSize:vector TrackWidth RailWidth TrackHeight
@persist MaxScanHeight ScanStep PreviousBogey:table
#bogey aligner stuff
@persist ScanIdxSub [CurrentData StartData PreviousData]:table ReadyForTeleport
#right click tool stuff
@persist [StartPos StartForward StartUp EndPos EndForward EndUp]:vector
#other
@persist Target:entity NumEntitiesInContraption AreYouSure EnableCrowbar HideChat FilterPropPhysics
#[
Divran's re-railer E2
Source: https://gist.github.com/Divran/8c23f82857196559b9fbb0e37c374e36
Crowbar commands:
Left click: Scan train (left click the front main body of the train, the locomotive)
Right click: Hold this and drag from one side of the rails to the other to start scanning the track
Reload: After scanning the track, press reload to teleport the train
Chat commands:
.trackwidth <number>
or
.trackwidth <name>
Set the width of the track (default: 75 source units)
Available names:
"russian" (79.9375 units - will use 75)
"2ft" (31.9365 units - will use 28)
"standard" (unknown, for now)
.save
Scans and saves the train.
If you are sitting in a vehicle, will start the scan from the prop the E2 is welded to
If you are not sitting in a vehicle look at your front or rear loco/car
.scan
Scans the track for a suitable place to rerail.
* The chat command scanner isn't as versatile as using the crowbar method. It's only able to find the start of the track
if the track has nothing blocking it in between the rails.
If you want to start the scan in an area with something blocking the center of the rails, you should
use the crowbar method instead.
* The chat command scanner also can't start in an area where the two rails are "hovering" above the ground (a common issue on sunsetgulch)
.teleport
or
.tp
Teleports the train to the scanned position
.crowbar
Toggles the use of the crowbar
Notes:
* This rerailer has a few requirements:
1. The train has at least 2 units, (Loco + cab, Loco + loco, cab+cab etc.) to be scanned.
(This limitation is due to model makers not having a standard forward direction for their props.)
(It can still work for single-unit trains, if the model happens to have a proper forward direction specified by its creator.)
2. You must select the first (or last) wagon/loco in your train. Selecing any other wagon will not work, and selecting a bogey will break it in strange ways, so don't do that.
3. Each unit should have bogeys constrained to one base prop.
4. The train must be positioned in a straight line while scanning and saving the train.
* If the number of constrained entities changes by more than a few since your scan the rerailer will not allow the teleport to occur (for your safety).
* The saved train data will be stored in a gTable, so if you accidentally remove your E2, you can simply respawn it and it'll reload the data.
]#
if (first()|dupefinished()) {
#--------------------------------------------------------
#OPTIONS BEGIN HERE
#Set this if you want to disable crowbar controls (can be toggled later with chat commands)
EnableCrowbar = 0
#Set this if you want to hide your chat commands
HideChat = 1
#Set this if you want the track scanner to ignore all prop physics on the map
#Enabling this will mean you can't scan tracks spawned by players,
#but it means you can scan through other people's trains (teleporting there is not recommended, though)
FilterPropPhysics = 0
#comment this out if you want to disable chat commands
runOnChat(1)
local G = gTable("rerailer",0)
if (G["Scanned",table]:ncount() > 0) {
print("Existing saved train discovered, scan loaded! Ready to scan track.")
Scanned = G["Scanned",table]
AlreadyScannedWagons = G["AlreadyScannedWagons",array]
AlreadyScannedBogeys = G["AlreadyScannedBogeys",array]
ScanQueueWagons = array()
ScanQueueBogeys = array()
}
#[
#Uncomment this if you would like it to auto-save the entity it's welded to immediately
Target = entity():isWeldedTo()
if (Target:isValid() & !Target:isWorld()) {
timer("init",0)
}
]#
#--------------------------------------------------------
#CODE BEGINS HERE
if (EnableCrowbar) {runOnKeys(owner(),1,array("attack","attack2","reload"))}
#track scanner stuff
SmallSize = vec(16,16,2)
TrackWidth = 75
RailWidth = 8
TrackHeight = 8
MaxScanHeight = 128
ScanStep = 16
FunctionsDefined = 0
rangerFilter(players())
if (FilterPropPhysics) {
findByClass("prop_physics")
rangerFilter(findToArray())
}
rangerPersist(1)
function checkNumEntities() {
if (AreYouSure == 0) {
local CurrentNumEntitiesInContraption = Scanned[1,table]["wagon",entity]:getConnectedEntities("constraint"):count()
if (abs(CurrentNumEntitiesInContraption-NumEntitiesInContraption) >= min(20,NumEntitiesInContraption*0.5)) {
print(">WARNING< A large change in the number of constrained entities has been detected. Are you sure you want to teleport the train? Try again to confirm.")
AreYouSure = -1
exit()
}
} elseif (AreYouSure == 1) {
AreYouSure = 0
}
}
} elseif (clk("init")) {
if (!Target:isValid() | Target:isWorld()) {
print("Invalid entity selected")
exit()
}
print("Train scanning started...")
ReadyForTeleport = 0
timer("scan wagon",0)
#Debug holograms
#[
holoCreate(1)
holoCreate(2)
holoCreate(3)
holoColor(1,vec4(255,0,0,100))
holoColor(2,vec4(0,255,0,100))
holoColor(3,vec4(0,0,255,100))
holoScale(1,vec(1))
holoScale(2,vec(0.8))
holoScale(3,vec(0.6))
#]#
#contraption scanner stuff
Scanned = table()
ScanQueueWagons = array()
ScanQueueBogeys = array()
AlreadyScannedWagons = array()
AlreadyScannedBogeys = array()
local Start = Target
ScanQueueWagons:pushEntity(Start)
AlreadyScannedWagons[Start:id(),number] = 1
NumEntitiesInContraption = Start:getConnectedEntities("constraint"):count()
AreYouSure = 0
} elseif (chatClk(owner())) {
local S = lastSaid():explode(" ")
if (S[1,string] == ".trackwidth") {
hideChat(1)
local Names = table(
"russian" = 75,
"2ft" = 28,
"twoft" = 28
#"standard" = 0
)
local N = S[2,string]
if (Names:exists(N)) {
TrackWidth = Names[N,number]
} else {
if (N:toNumber() == 0) {
print("Invalid width ('"+S[2,string]+"') entered")
exit()
}
TrackWidth = N:toNumber()
}
print("TrackWidth set to "+TrackWidth+" units")
} elseif (S[1,string] == ".save") {
hideChat(1)
if (owner():inVehicle()) {
Target = entity():isWeldedTo()
timer("init",0)
} else {
Target = owner():aimEntity()
timer("init",0)
}
} elseif (S[1,string] == ".scan") {
hideChat(1)
#do stuff
StartPos = owner():aimPos()+owner():aimNormal()*TrackHeight/2
StartForward = owner():eyeAngles():setPitch(0):forward()
StartUp = owner():aimNormal()
EndPos = vec() EndForward = vec() EndUp = vec()
Active = 1
ScanIdx = 1
holoDeleteAll()
print("Creating holograms...")
timer("create hologram",0)
} elseif (S[1,string] == ".teleport" | S[1,string] == ".tp") {
hideChat(1)
if (ReadyForTeleport) {
if (AreYouSure == -1) {AreYouSure = 1}
checkNumEntities()
print("Teleporting...")
timer("freeze",100)
} else {
print("Not yet ready for teleport")
}
} elseif (S[1,string] == ".crowbar") {
hideChat(1)
EnableCrowbar = !EnableCrowbar
runOnKeys(owner(),EnableCrowbar,array("attack","attack2","reload"))
print(EnableCrowbar ? "Crowbar enabled" : "Crowbar disabled")
}
} elseif (keyClk()) {
local Player = keyClk()
local Pressed = keyClk(Player)
local Key = keyClkPressedBind()
if (Player == owner() & owner():weapon():type() == "weapon_crowbar") {
if (Key == "attack" & Pressed == 1) {
Target = owner():aimEntity()
timer("init",0)
} elseif (Key == "attack2") {
ReadyForTeleport = 0
if (Pressed == 1) {
StartPos = owner():aimPos() + owner():aimNormal() * TrackHeight/2
StartForward = owner():eyeAngles():setPitch(0):forward()
StartUp = owner():aimNormal()
} elseif (Pressed == -1) {
EndPos = owner():aimPos() + owner():aimNormal() * TrackHeight/2
EndForward = owner():eyeAngles():setPitch(0):forward()
EndUp = owner():aimNormal()
Active = 1
ScanIdx = 1
holoDeleteAll()
timer("create hologram",0)
print("Creating holograms...")
}
} elseif (Key == "reload" & Pressed == 1) {
if (ReadyForTeleport) {
if (AreYouSure == -1) {AreYouSure = 1}
checkNumEntities()
print("Teleporting...")
timer("freeze",100)
} else {
print("Not yet ready for teleport")
}
}
}
} elseif (clk("create hologram")) {
if (holoRemainingSpawns() > Scanned[ScanIdx,table]["bogeys",table]:ncount()+1) {
local Scan = Scanned[ScanIdx,table]
local FirstEntity = Scanned[1,table]["wagon",entity]
local FirstHoloID = FirstEntity:id()
local FirstHolo = holoEntity(FirstHoloID)
local Pos = (
(ScanIdx == 1) ?
entity():pos() : #owner():aimPos() :
FirstHolo:toWorld(Scan["pos",vector])
)
local Ang = (
(ScanIdx == 1) ?
-owner():eyeAngles():setPitch(0) :
FirstHolo:toWorld(Scan["ang",angle])
)
holoCreate(
Scan["wagon",entity]:id(),
Pos,
vec(1),
Ang,
vec4(255,255,255,100),
Scan["wagon",entity]:model()
)
if (ScanIdx > 1) {
#holoParent(Scan["wagon",entity]:id(),FirstHoloID)
}
for(I=1,Scan["bogeys",table]:ncount()) {
local Bogey = Scan["bogeys",table][I,table]
holoCreate(
Bogey["bogey",entity]:id(),
FirstHolo:toWorld(Bogey["pos",vector]),
vec(1),
FirstHolo:toWorld(Bogey["ang",angle]),
vec4(255,255,255,100),
Bogey["bogey",entity]:model()
)
#holoParent(Bogey["bogey",entity]:id(),FirstHoloID)
}
ScanIdx++
if (ScanIdx > Scanned:ncount()) {
timer("scan start",0)
print("Holograms created! Scanning for train track...")
} else {
timer("create hologram",50)
}
} else {
timer("create hologram",500)
}
} elseif (clk("scan start")) {
if (!FunctionsDefined) {
FunctionsDefined = 1
function table standardTrace(Pos:vector, Forward:vector, Left:vector, Up:vector, CurrentTrackWidth) {
# upper left
local SurfaceLeft = rangerOffsetHull(
Pos + vec(0,0,MaxScanHeight),
Pos - vec(0,0,MaxScanHeight),
-SmallSize/2,SmallSize/2
)
if (!SurfaceLeft:hit()) {return table()}
#move position to this height
Pos = Pos:setZ(SurfaceLeft:pos():z() - TrackHeight/2)
local InnerLeft = rangerOffset(
Pos - Left * RailWidth/2,
Pos + Left * RailWidth
)
if (!InnerLeft:hit()) {return table()}
#if (InnerLeft:startSolid()) {print("InnerLeft started solid") return table()}
#shift position to right side
Pos = Pos - Left * CurrentTrackWidth
#upper right
local SurfaceRight = rangerOffsetHull(
Pos + vec(0,0,MaxScanHeight),
Pos - vec(0,0,MaxScanHeight),
-SmallSize/2,SmallSize/2
)
if (!SurfaceRight:hit()) {return table()}
return table(
"SurfaceLeft" = SurfaceLeft,
"InnerLeft" = InnerLeft,
"SurfaceRight" = SurfaceRight
)
}
function table standardInfo(SurfaceLeft:ranger, InnerLeft:ranger, SurfaceRight:ranger) {
local Center = (SurfaceLeft:pos()+SurfaceRight:pos())/2
local New_up = ((SurfaceLeft:hitNormal()+SurfaceRight:hitNormal())/2):normalized()
local New_forward = (-InnerLeft:hitNormal()):cross(New_up)
local Temp_up = (SurfaceRight:pos()-SurfaceLeft:pos()):normalized():cross(New_forward)
if (Temp_up == Temp_up) {New_up = Temp_up}
#if temp_up == temp_up then new_up = temp_up end
return table(
"forward" = New_forward,
"up" = New_up
)
}
# finds the start of the track and returns info about it
# expects starting position inside left hand side rail
function table findTrackStart(Pos:vector,Forward:vector,Up:vector,CurrentTrackWidth) {
local Left = -Forward:cross(Up)
local Data = standardTrace(Pos,Forward,Left,Up,CurrentTrackWidth)
if (Data:count() == 0) {return table()}
local SurfaceLeft = Data["SurfaceLeft",ranger]
local InnerLeft = Data["InnerLeft",ranger]
local SurfaceRight = Data["SurfaceRight",ranger]
# move position to right side
Pos = (Pos - Left * CurrentTrackWidth):setZ(SurfaceRight:pos():z() - TrackHeight/2)
# inner right
local InnerRight = rangerOffset(
Pos + Left * RailWidth/2,
Pos - Left * RailWidth
)
if (!InnerRight:hit()) {return table()}
#if (InnerRight:startSolid()) {print("InnerRight started solid") return table()}
local Ret = standardInfo(SurfaceLeft,InnerLeft,SurfaceRight)
Ret["trackWidth",number] = (InnerLeft:pos()-InnerRight:pos()):length()
Ret["pos",vector] = (SurfaceLeft:pos()+SurfaceRight:pos())/2-vec(0,0,1)
return Ret
}
#finds and returns track or empty table on failure
#expects starting position at center of track
function table findTrack(Pos:vector,Forward:vector,Up:vector) {
local Left = -Forward:cross(Up)
Pos = Pos + Left * (TrackWidth/2) #convert position to left hand rail
local Data = standardTrace(Pos, Forward, Left, Up, TrackWidth)
if (Data:count() == 0) {return table()}
local SurfaceLeft = Data["SurfaceLeft",ranger]
local InnerLeft = Data["InnerLeft",ranger]
local SurfaceRight = Data["SurfaceRight",ranger]
local Ret = standardInfo(SurfaceLeft,InnerLeft,SurfaceRight)
Ret["pos",vector] = (InnerLeft:pos() - Left * TrackWidth/2):setZ(
(SurfaceLeft:pos():z()+SurfaceRight:pos():z())/2-1
)
return Ret
}
function table findTrackStart_Ex() {
#we have both start and endpos, this makes it easy
local Center = (StartPos+EndPos)/2
local Diff = (StartPos-EndPos)
local Len = Diff:length()
local Forward = ((StartForward+EndForward)/2):normalized()
local Up = ((StartUp+EndUp)/2):normalized()
local Left = -Forward:cross(Up)
local IsInFront = Diff:dot(Left) / Diff:length()
local LeftPos = vec()
if (IsInFront > 0) {
LeftPos = StartPos
} else {
LeftPos = EndPos
}
return findTrackStart(LeftPos,Forward,Up,Len)
}
}
if (FilterPropPhysics) {
findByClass("prop_physics")
rangerFilter(findToArray())
}
local Data = table()
if (EndPos != vec() & EndForward != vec() & EndUp != vec()) {
Data = findTrackStart_Ex()
} else {
# we only have startpos, this is a little bit harder
local RightDir = (round(StartForward:cross(StartUp):toAngle()/45)*45):forward()
local RLeft = rangerOffset(TrackWidth*2,StartPos,-RightDir)
if (RLeft:hit() & RLeft:distance() < TrackWidth * 1.2) {
local RRight = rangerOffset(TrackWidth*2,StartPos,RightDir)
if (RRight:hit() & RRight:distance() < TrackWidth*1.2) {
StartPos = RLeft:pos() + RLeft:hitNormal() * 2
EndPos = RRight:pos() + RRight:hitNormal() * 2
EndForward = StartForward
EndUp = StartUp
Data = findTrackStart_Ex()
} else {
print("Unable to find start of track - Try starting in an area with nothing in between the two rails. Or alternatively use the crowbar method instead.")
}
} else {
print("Unable to find start of track - Try starting in an area with nothing in between the two rails. Or alternatively use the crowbar method instead.")
}
}
if (Data:count() == 0) {
print("Unable to find start of track")
holoDeleteAll()
} else {
ScanIdx = 1
ScanIdxSub = 1
StartData = Data
PreviousData = Data
CurrentData = Data
TrackWidth = Data["trackWidth",number]
timer("scan track",500)
}
} elseif (clk("scan track")) {
local First = Scanned[1,table]
local FirstWagon = First["wagon",entity]
local FirstHoloID = FirstWagon:id()
if (ScanIdx==1 & ScanIdxSub==1) {
PreviousBogey = table(
"pos" = vec(),
"ang" = ang()
)
}
while(perf()) {
local NextBogey = Scanned[ScanIdx,table]["bogeys",table][ScanIdxSub,table]
local BogeyEntity = NextBogey["bogey",entity]
local DistanceBetweenBogeys = PreviousBogey["pos",vector]:distance(NextBogey["pos",vector])
local DistanceBetweenData = PreviousData["pos",vector]:distance(CurrentData["pos",vector])
local DistanceToNextBogey = DistanceBetweenBogeys - DistanceBetweenData
#local DistanceToNextBogey = abs((CurrentData["pos",vector]-StartData["pos",vector]):length() - NextBogey["pos",vector]:length())
#print("ScanIdx",ScanIdx,"ScanIdxSub",ScanIdxSub,"DistanceToNextBogey",DistanceToNextBogey)
if (DistanceToNextBogey < 32) {
if (DistanceToNextBogey <= 1) {
#print("Distance below 4! found for entity",BogeyEntity)
# found!
holoPos(BogeyEntity:id(),CurrentData["pos",vector]+CurrentData["up",vector]*(ShiftAmount:z()-BogeyEntity:aabbMin():z()))
local Ang = quat(CurrentData["forward",vector],CurrentData["up",vector]):toAngle()
Ang = Ang:rotateAroundAxis(CurrentData["up",vector],NextBogey["ang",angle]:yaw())
holoAng(BogeyEntity:id(),Ang)
#holoAng(BogeyEntity:id(),NextBogey["ang",angle]+quat(CurrentData["forward",vector],CurrentData["up",vector]):toAngle())
ScanIdxSub++
local NrBogeys = Scanned[ScanIdx,table]["bogeys",table]:ncount()
if (ScanIdxSub > NrBogeys) {
#print("done scanning for a wagon, getting next")
local Scan = Scanned[ScanIdx,table]
local Wagon = Scan["wagon",entity]
local Pos = vec()
local Ang = ang()
if (NrBogeys == 1) {
local Bogey1 = Scanned[ScanIdx,table]["bogeys",table][1,table]
local Axis1PosBogey = Bogey1["axis_pos_bogey",vector]
local Axis1PosWagon = Bogey1["axis_pos_wagon",vector]
local Axis1AngOffset = Bogey1["axis_ang",angle]
local BogeyE = holoEntity(BogeyEntity:id())
Pos = BogeyE:toWorld(Axis1PosBogey-Axis1PosWagon)
Ang = BogeyE:toWorld(Axis1AngOffset)
} else {
local Bogey1 = Scanned[ScanIdx,table]["bogeys",table][1,table]
local Bogey2 = Scanned[ScanIdx,table]["bogeys",table][NrBogeys,table]
local Axis1PosBogey = Bogey1["axis_pos_bogey",vector]
local Axis2PosBogey = Bogey2["axis_pos_bogey",vector]
local Axis1PosWagon = Bogey1["axis_pos_wagon",vector]
local Axis2PosWagon = Bogey2["axis_pos_wagon",vector]
#local Axis1AngOffset = Bogey1["axis_ang",angle]
#local Axis2AngOffset = Bogey2["axis_ang",angle]
local BogeyE1 = holoEntity(BogeyEntity:id())
local BogeyE2 = holoEntity(PreviousBogey["bogey",entity]:id())
Ang = quat((BogeyE1:toWorld(Axis1PosBogey)-BogeyE2:toWorld(Axis2PosBogey)):toAngle():forward(),BogeyE1:up()):toAngle()
Ang = Ang:rotateAroundAxis(Ang:up(),Scan["ang",angle]:yaw())
#Ang += (Axis2AngOffset+Axis1AngOffset)
#print("ScanIdx",ScanIdx,"offsets added",(Axis2AngOffset+Axis1AngOffset))
#print(Bogey1:toLocal(Bogey2:toWorld(Axis2AngOffset)))
#Ang = Ang:rotateAroundAxis(Ang:up(),-90)
Pos = (BogeyE1:toWorld(Axis1PosBogey)+BogeyE2:toWorld(Axis2PosBogey))/2+BogeyE1:up()*-Axis1PosWagon:z()
}
#Ang += Scan["ang",angle]
holoPos(Wagon:id(),Pos)
holoAng(Wagon:id(),Ang)
#holoPos(Wagon:id(),(PreviousData["pos",vector]+CurrentData["pos",vector])/2+CurrentData["up",vector]*ShiftAmount:z())
#holoAng(Wagon:id(),Scan["ang",angle]+((CurrentData["pos",vector]-PreviousData["pos",vector]):toAngle()))
ScanIdxSub = 1
ScanIdx++
if (ScanIdx > Scanned:ncount()) {
ScanIdx=0
ReadyForTeleport = 1
print("Track scanning finished, press R to teleport")
exit()
}
}
PreviousBogey = NextBogey
PreviousData = CurrentData
} else {
ScanStep = max(1,floor(DistanceToNextBogey / 2))
}
} else {
ScanStep = 16
}
#holoPos(1,CurrentData["pos",vector])
#holoPos(2,StartData["pos",vector]-FirstWagon:toWorldAxis(NextBogey["pos",vector]))
#holoPos(3,CurrentData["pos",vector]+CurrentData["up",vector]*50)
Pos = CurrentData["pos",vector] + CurrentData["forward",vector] * ScanStep
CurrentData = findTrack(Pos,CurrentData["forward",vector],CurrentData["up",vector])
if (CurrentData:count() == 0) {
print("Unable to find track, scan failed")
holoDeleteAll()
exit()
}
}
timer("scan track",0)
} elseif (clk("freeze")) {
ReadyForTeleport = 0
ScanIdx++
local Scan = Scanned[ScanIdx,table]
local Wagon = Scan["wagon",entity]
local Contraption = Scan["wagon",entity]:getConnectedEntities("constraint","parent","-axis","-ballsocket","-advballsocket","-rope")
Contraption:remove(1)
Wagon:propFreeze(1)
local Bogeys = Scan["bogeys",table]
for(I=1,Bogeys:ncount()) {
local Bogey = Bogeys[I,table]["bogey",entity]
Bogey:propFreeze(1)
local BogeyContr = Bogey:getConnectedEntities("constraint","parent","-axis","-ballsocket","-advballsocket","-rope")
BogeyContr:remove(1)
Contraption:add(BogeyContr)
}
Scan["contraption",array] = Contraption
Scan["positions",array] = array()
Scan["angles",array] = array()
if (ScanIdx == Scanned:ncount()) {
ScanIdx = 1
ScanIdxSub = 0
timer("freeze contraption",100)
} else {
timer("freeze",100)
}
} elseif (clk("freeze contraption")) {
while(perf()) {
local Scan = Scanned[ScanIdx,table]
local Wagon = Scan["wagon",entity]
ScanIdxSub++
local Contraption = Scan["contraption",array]
if (Contraption:count() > 0) {
local Positions = Scan["positions",array]
local Angles = Scan["angles",array]
local Ent = Contraption[ScanIdxSub,entity]
if (AlreadyScannedWagons[Ent:id(),number] == 0 & AlreadyScannedBogeys[Ent:id(),number] == 0) {
Positions[Ent:id(),vector] = Wagon:toLocal(Ent:pos())
Angles[Ent:id(),angle] = Wagon:toLocal(Ent:angles())
Ent:propFreeze(1)
}
}
if (ScanIdxSub >= Contraption:count()) {
ScanIdx++
ScanIdxSub = 0
if (ScanIdx > Scanned:ncount()) {
ScanIdx = 0
timer("teleport",100)
exit()
}
}
}
timer("freeze contraption",0)
} elseif (clk("teleport")) {
ScanIdx++
local Wagon = Scanned[ScanIdx,table]["wagon",entity]
local E = holoEntity(Wagon:id())
Wagon:setPos(E:pos())
Wagon:setAng(E:angles())
local Bogeys = Scanned[ScanIdx,table]["bogeys",table]
for(I=1,Bogeys:ncount()) {
local Bogey = Bogeys[I,table]["bogey",entity]
local E = holoEntity(Bogey:id())
Bogey:setPos(E:pos())
Bogey:setAng(E:angles())
}
ScanIdxSub = 0
timer("teleport contraption",0)
} elseif (clk("teleport contraption")) {
while(perf()) {
local Scan = Scanned[ScanIdx,table]
local Wagon = Scan["wagon",entity]
ScanIdxSub++
local Contraption = Scan["contraption",array]
if (Contraption:count() > 0) {
local Positions = Scan["positions",array]
local Angles = Scan["angles",array]
local Ent = Contraption[ScanIdxSub,entity]
if (AlreadyScannedWagons[Ent:id(),number] == 0 & AlreadyScannedBogeys[Ent:id(),number] == 0) {
Ent:setPos(Wagon:toWorld(Positions[Ent:id(),vector]))
Ent:setAng(Wagon:toWorld(Angles[Ent:id(),angle]))
}
}
if (ScanIdxSub >= Contraption:count()) {
if (ScanIdx >= Scanned:ncount()) {
print("Done!")
holoDeleteAll()
Active = 0
} else {
timer("teleport",0)
}
exit()
}
}
timer("teleport contraption",0)
} elseif (clk("scan wagon")) {
local First = Scanned[1,table]
local FirstEntity = First["wagon",entity]
local Wagon = ScanQueueWagons:popEntity()
if (Scanned:ncount() == 0) {FirstEntity = Wagon}
local Bogeys = Wagon:getConnectedEntities("axis","ballsocket","advballsocket")
Bogeys:remove(1) #remove self entity
#[
if (Bogeys:count() == 0) {
Bogeys = Wagon:getConnectedEntities("ballsocket")
Bogeys:remove(1) #remove self entity
Bogeys = Wagon:getConnectedEntities("advballsocket")
Bogeys:remove(1) #remove self entity
}
]#
local BogeyTable = table()
for(I=1,Bogeys:count()) {
local Bogey = Bogeys[I,entity]
if (AlreadyScannedBogeys[Bogey:id(),number] == 0) {
AlreadyScannedBogeys[Bogey:id(),number] = 1
ScanQueueBogeys:pushEntity(Bogey)
Bogey["wagon",entity] = Wagon
}
local AxisPosBogey = Bogey:boxCenter():setZ(Bogey:boxMax():z())
BogeyTable:pushTable(table(
"bogey" = Bogey,
"pos" = FirstEntity:toLocal(Bogey:pos()),
"ang" = FirstEntity:toLocal(Bogey:angles()),
"axis_pos_bogey" = AxisPosBogey,
"axis_pos_wagon" = Wagon:toLocal(Bogey:toWorld(AxisPosBogey))
#"axis_ang" = Bogey:toLocal(Wagon:angles())
))
}
First["backward",vector] = First["backward",vector] + (Wagon:boxCenterW()-FirstEntity:boxCenterW()):normalized()
Scanned:pushTable(table(
"wagon" = Wagon,
"pos" = FirstEntity:toLocal(Wagon:pos()),
"ang" = FirstEntity:toLocal(Wagon:angles()),
"bogeys" = BogeyTable
))
if (ScanQueueWagons:count() > 0) {timer("scan wagon",0)}
elseif (ScanQueueBogeys:count() > 0) {timer("scan bogey",0)}
else {timer("done",0)}
} elseif (clk("scan bogey")) {
local Bogey = ScanQueueBogeys:popEntity()
local Wagon = noentity()
if (Bogey["wagon",entity]:isValid()) {
Wagon = Bogey["wagon",entity]
Bogey["wagon",entity] = noentity() #delete reference now, we no longer need it
} else {
#fallback checks in case the above fails
Wagon = Bogey:isConstrainedTo("axis")
if (!Wagon:isValid()) {
Wagon = Bogey:isConstrainedTo("ballsocket")
if (!Wagon:isValid()) {
Wagon = Bogey:isConstrainedTo("advballsocket")
}
}
}
if (!Wagon:isValid()) {
print("Error: Unable to find wagon of bogey. Scan aborted")
exit()
}
if (AlreadyScannedWagons[Wagon:id(),number] == 0) {
AlreadyScannedWagons[Wagon:id(),number] = 1
ScanQueueWagons:pushEntity(Wagon)
}
local OtherBogeys = Bogey:getConnectedEntities("rope")
OtherBogeys:remove(1) #remove self entity
for(I=1,OtherBogeys:count()) {
local OtherBogey = OtherBogeys[I,entity]
if (AlreadyScannedBogeys[OtherBogey:id(),number] == 0) {
AlreadyScannedBogeys[OtherBogey:id(),number] = 1
ScanQueueBogeys:pushEntity(OtherBogey)
}
}
if (ScanQueueWagons:count() > 0) {timer("scan wagon",0)}
elseif (ScanQueueBogeys:count() > 0) {timer("scan bogey",0)}
else {timer("done",0)}
} elseif (clk("sort")) {
#sort by distance to front of train
ScanIdx++
local FirstWagon = Scanned[1,table]["wagon",entity]
local Front = FirstWagon:toLocal(FirstWagon:boxCenterW()+Scanned[1,table]["forward",vector]*1000)
local Scan = Scanned[ScanIdx,table]
#Sort wagons
local MinDist = Scan["pos",vector]:distance(Front)
local MinJ = ScanIdx
for(J=ScanIdx,Scanned:ncount()) {
local Dist = Scanned[J,table]["pos",vector]:distance(Front)
if (Dist < MinDist) {
MinDist = Dist
MinJ = J
}
}
if (ScanIdx != MinJ) {
local Val = Scanned:removeTable(MinJ)
Scanned:insertTable(MinJ,Scanned[ScanIdx,table])
Scanned:remove(ScanIdx)
Scanned:insertTable(ScanIdx,Val)
}
#Sort bogeys
local Bogeys = Scan["bogeys",table]
local Count = Bogeys:ncount()
for(I=1,Count) {
local MinDist = Bogeys[I,table]["pos",vector]:distance2(Front)
local MinJ = I
for(J=I+1,Count) {
local Dist = Bogeys[J,table]["pos",vector]:distance2(Front)
if (Dist < MinDist) {
MinDist = Dist
MinJ = J
}
}
if (I!=MinJ) {
local Val = Bogeys:removeTable(MinJ)
Bogeys:insertTable(MinJ,Bogeys[I,table])
Bogeys:remove(I)
Bogeys:insertTable(I,Val)
}
}
#Scan[ScanIdx,table]["bogeys",table] = NewBogeys
if (ScanIdx == Scanned:ncount()) {
ScanIdx=0
local FirstBogeys = Scanned[1,table]["bogeys",table]
#ShiftAmount = Scanned[1,table]["forward",vector] * FirstBogeys[FirstBogeys:count(),table]["pos",vector]:length()/2 + vec(0,0,Scanned[1,table]["wagon",entity]:boxSize():z()/2+20)
local FirstBogey = FirstBogeys[1,table]["bogey",entity]
local FirstWagon = Scanned[1,table]["wagon",entity]
local ShiftUp = 0 #-min(FirstWagon:boxMin():z(),FirstWagon:toLocal(FirstBogey:toWorld(FirstBogey:boxMin())):z())
ShiftAmount = vec(FirstBogeys[FirstBogeys:count(),table]["pos",vector]:length()*1.2,0,ShiftUp)
#print("done sorting, shifting all entities back:",ShiftAmount)
if (Target != FirstWagon) {
print(">WARNING< You didn't select the wagon/loco at the front (or back) of the train. This E2 will not work properly if you do this. Scan aborted.")
local G = gTable("rerailer",0)
if (G["Scanned",table]:ncount()>0) {
Scanned = G["Scanned",table]
AlreadyScannedWagons = G["AlreadyScannedWagons",array]
AlreadyScannedBogeys = G["AlreadyScannedBogeys",array]
}
exit()
}
if (Scanned:ncount() == 1) {
print(">WARNING< Only a single wagon/locomotive found. There is a large chance it will not be angled correctly after teleporting!")
}
timer("shift",0)
#printTable(Scanned)
} else {
timer("sort",0)
}
} elseif (clk("shift")) {
local Forward = Scanned[1,table]["forward",vector]
local OffsetAng = Scanned[1,table]["offsetang",angle]
ScanIdx++
local Scan = Scanned[ScanIdx,table]
Scan["pos",vector] = Scan["pos",vector] + ShiftAmount
Scan["ang",angle] = Scan["ang",angle] + OffsetAng
for(I=1,Scan["bogeys",table]:ncount()) {
local Bogey = Scan["bogeys",table][I,table]
Bogey["pos",vector] = Bogey["pos",vector] + ShiftAmount
Bogey["ang",angle] = Bogey["ang",angle] + OffsetAng
}
if (ScanIdx == Scanned:ncount()) {
print("Done sorting. Ready to scan track.")
local G = gTable("rerailer",0)
G["Scanned",table] = Scanned
G["AlreadyScannedWagons",array] = AlreadyScannedWagons
G["AlreadyScannedBogeys",array] = AlreadyScannedBogeys
#[
for(I=1,Scanned:ncount()) {
print("Scanned["+I+",table] = ")
printTable(Scanned[I,table])
print("-----")
}
]#
} else {
timer("shift",0)
}
} elseif (clk("done")) {
local First = Scanned[1,table]
local FirstWagon = First["wagon",entity]
local Forward = -(First["backward",vector]/Scanned:ncount()):normalized()
First["forward",vector] = Forward
First["offsetang",angle] = FirstWagon:toLocal(quat(Forward,FirstWagon:up()):toAngle())
if (abs(First["offsetang",angle]:yaw()) > 120) {First["offsetang",angle] = ang()}
ScanIdx = 0
print("Done scanning contraption ("+Scanned:ncount()+" wagons found), sorting scanned entries and doing some math...")
#printTable(Scanned)
timer("sort",0)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment