Best way is to script it. Use object ID's as waypoints (road section at each bus stop). You won't have to place anything in the editor except the buses. Here's a sample from one of my bus scripts-
;Bus line 1 starts in Loukov moving South
;---------------------------------------------
_unit = _this select 0
#start
? !(canmove _unit): exit
_unit move getpos (object 136176)
@ Unitready _unit
_unit stop true
~1
_unit setdir 90
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Bitov"
_unit stop false
_unit move getpos (object 136171)
@ Unitready _unit
_unit move getpos (object 134629)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Lany"
_unit stop false
_unit move getpos (object 274)
@ Unitready _unit
_unit stop true
~1
_unit setdir 90
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Lipany"
_unit stop false
_unit move getpos (object 6378)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Okrouhlo"
_unit stop false
_unit move getpos (object 17796)
@ Unitready _unit
_unit move getpos (object 26089)
@ Unitready _unit
_unit move getpos (object 57847)
@ Unitready _unit
_unit move getpos (object 57852)
@ Unitready _unit
_unit move getpos (object 173142)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop St. Sedlo"
_unit stop false
_unit move getpos (object 172484)
@ Unitready _unit
_unit move getpos (object 171664)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Lipany"
_unit stop false
_unit move getpos (object 171568)
@ Unitready _unit
_unit move getpos (object 31626)
@ Unitready _unit
_unit move getpos (object 6378)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Lany"
_unit stop false
_unit move getpos (object 6322)
@ Unitready _unit
_unit move getpos (object 274)
@ Unitready _unit
_unit stop true
~1
_unit setdir 90
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Bitov"
_unit stop false
_unit move getpos (object 134570)
@ Unitready _unit
_unit move getpos (object 134629)
@ Unitready _unit
_unit stop true
? !(canmove _unit): exit
?(getdammage _unit > 0.1 and canmove _unit): _unit setdammage 0
_unit setfuel 1
~random(30) + 30
?(player in _unit): _unit globalchat "Next stop Loukov"
_unit stop false
_unit move getpos (object 136170)
@ Unitready _unit
goto "start"
The only problem that ever arose was the drivers inability to turn the big bus around to repeat the route, so I used setdir at a few locations to help him. The check for dammage is necessary since they tend to bump into things and after a while the dammage would build up to the point where the bus would not run.