001package jmri.jmrit.operations.trains.trainbuilder; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.apache.commons.lang3.StringUtils; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import jmri.jmrit.operations.locations.Location; 011import jmri.jmrit.operations.locations.Track; 012import jmri.jmrit.operations.rollingstock.RollingStock; 013import jmri.jmrit.operations.rollingstock.engines.Engine; 014import jmri.jmrit.operations.routes.Route; 015import jmri.jmrit.operations.routes.RouteLocation; 016import jmri.jmrit.operations.setup.Control; 017import jmri.jmrit.operations.setup.Setup; 018import jmri.jmrit.operations.trains.BuildFailedException; 019import jmri.jmrit.operations.trains.Train; 020 021/** 022 * Contains methods for engines when building a train. 023 * 024 * @author Daniel Boudreau Copyright (C) 2022 025 */ 026public class TrainBuilderEngines extends TrainBuilderBase { 027 028 /** 029 * Builds a list of possible engines for this train. 030 */ 031 protected void getAndRemoveEnginesFromList() { 032 setEngineList(engineManager.getAvailableTrainList(getTrain())); 033 034 // remove any locos that the train can't use 035 for (int indexEng = 0; indexEng < getEngineList().size(); indexEng++) { 036 Engine engine = getEngineList().get(indexEng); 037 // remove engines types that train does not service 038 if (!getTrain().isTypeNameAccepted(engine.getTypeName())) { 039 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineType", engine.toString(), 040 engine.getLocationName(), engine.getTrackName(), engine.getTypeName())); 041 getEngineList().remove(indexEng--); 042 continue; 043 } 044 // remove engines with roads that train does not service 045 if (!getTrain().isLocoRoadNameAccepted(engine.getRoadName())) { 046 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(), 047 engine.getLocationName(), engine.getTrackName(), engine.getRoadName())); 048 getEngineList().remove(indexEng--); 049 continue; 050 } 051 // remove engines with owners that train does not service 052 if (!getTrain().isOwnerNameAccepted(engine.getOwnerName())) { 053 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineOwner", engine.toString(), 054 engine.getLocationName(), engine.getTrackName(), engine.getOwnerName())); 055 getEngineList().remove(indexEng--); 056 continue; 057 } 058 // remove engines with built dates that train does not service 059 if (!getTrain().isBuiltDateAccepted(engine.getBuilt())) { 060 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineBuilt", engine.toString(), 061 engine.getLocationName(), engine.getTrackName(), engine.getBuilt())); 062 getEngineList().remove(indexEng--); 063 continue; 064 } 065 // remove engines that are out of service 066 if (engine.isOutOfService()) { 067 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineOutOfService", engine.toString(), 068 engine.getLocationName(), engine.getTrackName())); 069 getEngineList().remove(indexEng--); 070 continue; 071 } 072 // remove engines that aren't on the train's route 073 if (getTrain().getRoute().getLastLocationByName(engine.getLocationName()) == null) { 074 log.debug("removing engine ({}) location ({}) not serviced by train", engine.toString(), 075 engine.getLocationName()); 076 getEngineList().remove(indexEng--); 077 continue; 078 } 079 // is engine at interchange? 080 if (engine.getTrack().isInterchange()) { 081 // don't service a engine at interchange and has been dropped off 082 // by this train 083 if (engine.getTrack().getPickupOption().equals(Track.ANY) && 084 engine.getLastRouteId().equals(getTrain().getRoute().getId())) { 085 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineDropByTrain", engine.toString(), 086 engine.getTypeName(), getTrain().getRoute().getName(), engine.getLocationName(), 087 engine.getTrackName())); 088 getEngineList().remove(indexEng--); 089 continue; 090 } 091 } 092 // is engine at interchange or spur and is this train allowed to pull? 093 if (engine.getTrack().isInterchange() || engine.getTrack().isSpur()) { 094 if (engine.getTrack().getPickupOption().equals(Track.TRAINS) || 095 engine.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) { 096 if (engine.getTrack().isPickupTrainAccepted(getTrain())) { 097 log.debug("Engine ({}) can be picked up by this train", engine.toString()); 098 } else { 099 addLine(SEVEN, 100 Bundle.getMessage("buildExcludeEngineByTrain", engine.toString(), engine.getTypeName(), 101 engine.getTrack().getTrackTypeName(), engine.getLocationName(), 102 engine.getTrackName())); 103 getEngineList().remove(indexEng--); 104 continue; 105 } 106 } else if (engine.getTrack().getPickupOption().equals(Track.ROUTES) || 107 engine.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) { 108 if (engine.getTrack().isPickupRouteAccepted(getTrain().getRoute())) { 109 log.debug("Engine ({}) can be picked up by this route", engine.toString()); 110 } else { 111 addLine(SEVEN, 112 Bundle.getMessage("buildExcludeEngineByRoute", engine.toString(), engine.getTypeName(), 113 engine.getTrack().getTrackTypeName(), engine.getLocationName(), 114 engine.getTrackName())); 115 getEngineList().remove(indexEng--); 116 continue; 117 } 118 } 119 } 120 } 121 } 122 123 protected boolean getEngines(String requestedEngines, String model, String road, RouteLocation rl, 124 RouteLocation rld) throws BuildFailedException { 125 return getEngines(requestedEngines, model, road, rl, rld, !USE_BUNIT); 126 } 127 128 /** 129 * Get the engines for this train at a route location. If departing from 130 * staging engines must come from that track. Finds the required number of 131 * engines in a consist, or if the option to build from single locos, builds 132 * a consist for the user. When true, engines successfully added to train 133 * for the leg requested. 134 * 135 * @param requestedEngines Requested number of Engines, can be number, AUTO 136 * or AUTO HPT 137 * @param model Optional model name for the engines 138 * @param road Optional road name for the engines 139 * @param rl Departure route location for the engines 140 * @param rld Destination route location for the engines 141 * @param useBunit true if B unit engine is allowed 142 * @return true if correct number of engines found. 143 * @throws BuildFailedException if coding issue 144 */ 145 protected boolean getEngines(String requestedEngines, String model, String road, RouteLocation rl, 146 RouteLocation rld, boolean useBunit) throws BuildFailedException { 147 // load departure track if staging 148 Track departStagingTrack = null; 149 if (rl == getTrain().getTrainDepartsRouteLocation()) { 150 // get departure track from staging, could be null 151 departStagingTrack = getDepartureStagingTrack(); 152 } 153 154 int reqNumberEngines = getNumberEngines(requestedEngines); 155 156 // if not departing staging track and engines aren't required done! 157 if (departStagingTrack == null && reqNumberEngines == 0) { 158 return true; 159 } 160 // if departing staging and no engines required and none available, 161 // we're done 162 if (departStagingTrack != null && reqNumberEngines == 0 && departStagingTrack.getNumberEngines() == 0) { 163 return true; 164 } 165 166 // code check, staging track selection checks number of engines needed 167 if (departStagingTrack != null && 168 reqNumberEngines != 0 && 169 departStagingTrack.getNumberEngines() != reqNumberEngines) { 170 throw new BuildFailedException(Bundle.getMessage("buildStagingNotEngines", departStagingTrack.getName(), 171 departStagingTrack.getNumberEngines(), reqNumberEngines)); 172 } 173 174 // code check 175 if (rl == null || rld == null) { 176 throw new BuildFailedException( 177 Bundle.getMessage("buildErrorEngLocUnknown")); 178 } 179 180 addLine(FIVE, Bundle.getMessage("buildBegineSearchEngines", reqNumberEngines, model, road, 181 rl.getName(), rld.getName())); 182 183 int assignedLocos = 0; // the number of locos assigned to this train 184 List<Engine> singleLocos = new ArrayList<>(); 185 for (int indexEng = 0; indexEng < getEngineList().size(); indexEng++) { 186 Engine engine = getEngineList().get(indexEng); 187 log.debug("Engine ({}) at location ({}, {})", engine.toString(), engine.getLocationName(), 188 engine.getTrackName()); 189 190 // use engines that are departing from the selected staging track 191 // (departTrack 192 // != null if staging) 193 if (departStagingTrack != null && !departStagingTrack.equals(engine.getTrack())) { 194 continue; 195 } 196 // use engines that are departing from the correct location 197 if (!engine.getLocationName().equals(rl.getName())) { 198 log.debug("Skipping engine ({}) at location ({})", engine.toString(), engine.getLocationName()); 199 continue; 200 } 201 // skip engines models that train does not service 202 if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) { 203 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineModel", engine.toString(), 204 engine.getModel(), engine.getLocationName())); 205 continue; 206 } 207 // Does the train have a very specific engine road name requirement? 208 if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road)) { 209 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(), 210 engine.getLocationName(), engine.getTrackName(), engine.getRoadName())); 211 continue; 212 } 213 // skip engines on tracks that don't service the train's departure 214 // direction 215 if (!checkPickUpTrainDirection(engine, rl)) { 216 continue; 217 } 218 // skip engines that have been assigned destinations that don't 219 // match the requested destination 220 if (engine.getDestination() != null && !engine.getDestinationName().equals(rld.getName())) { 221 addLine(SEVEN, Bundle.getMessage("buildExcludeEngineDestination", engine.toString(), 222 engine.getDestinationName())); 223 continue; 224 } 225 // don't use non lead locos in a consist 226 if (engine.getConsist() != null) { 227 if (engine.isLead()) { 228 addLine(SEVEN, 229 Bundle.getMessage("buildEngineLeadConsist", engine.toString(), 230 engine.getConsist().getName(), engine.getConsist().getEngines().size())); 231 } else { 232 continue; 233 } 234 } 235 if (!checkQuickServiceDeparting(engine, rl)) { 236 continue; 237 } 238 // departing staging, then all locos must go! 239 if (departStagingTrack != null) { 240 if (!setEngineDestination(engine, rl, rld)) { 241 return false; 242 } 243 getEngineList().remove(indexEng--); 244 if (engine.getConsist() != null) { 245 assignedLocos = assignedLocos + engine.getConsist().getSize(); 246 } else { 247 assignedLocos++; 248 } 249 continue; 250 } 251 // can't use B units if requesting one loco 252 if (!useBunit && reqNumberEngines == 1 && engine.isBunit()) { 253 addLine(SEVEN, 254 Bundle.getMessage("buildExcludeEngineBunit", engine.toString(), engine.getModel())); 255 continue; 256 } 257 // is this engine part of a consist? 258 if (engine.getConsist() == null) { 259 // single engine, but does the train require a consist? 260 if (reqNumberEngines > 1) { 261 addLine(SEVEN, 262 Bundle.getMessage("buildExcludeEngineSingle", engine.toString(), reqNumberEngines)); 263 singleLocos.add(engine); 264 continue; 265 } 266 // engine is part of a consist 267 } else if (engine.getConsist().getSize() == reqNumberEngines) { 268 log.debug("Consist ({}) has the required number of engines", engine.getConsist().getName()); // NOI18N 269 } else if (reqNumberEngines != 0) { 270 addLine(SEVEN, 271 Bundle.getMessage("buildExcludeEngConsistNumber", engine.toString(), 272 engine.getConsist().getName(), engine.getConsist().getSize())); 273 continue; 274 } 275 // found a loco or consist! 276 assignedLocos++; 277 278 // now find terminal track for engine(s) 279 addLine(FIVE, 280 Bundle.getMessage("buildEngineRoadModelType", engine.toString(), engine.getRoadName(), 281 engine.getModel(), engine.getTypeName(), engine.getLocationName(), engine.getTrackName(), 282 rld.getName())); 283 if (setEngineDestination(engine, rl, rld)) { 284 getEngineList().remove(indexEng--); 285 return true; // normal exit when not staging 286 } 287 } 288 // build a consist out of non-consisted locos 289 if (assignedLocos == 0 && reqNumberEngines > 1 && getTrain().isBuildConsistEnabled()) { 290 if (buildConsistFromSingleLocos(reqNumberEngines, singleLocos, rl, rld)) { 291 return true; // normal exit when building with single locos 292 } 293 } 294 if (assignedLocos == 0) { 295 String locationName = rl.getName(); 296 if (departStagingTrack != null) { 297 locationName = locationName + ", " + departStagingTrack.getName(); 298 } 299 addLine(FIVE, Bundle.getMessage("buildNoLocosFoundAtLocation", locationName)); 300 } else if (departStagingTrack != null && (reqNumberEngines == 0 || reqNumberEngines == assignedLocos)) { 301 return true; // normal exit assigning from staging 302 } 303 // not able to assign engines to train 304 return false; 305 } 306 307 private boolean buildConsistFromSingleLocos(int reqNumberEngines, List<Engine> singleLocos, RouteLocation rl, 308 RouteLocation rld) { 309 addLine(FIVE, Bundle.getMessage("buildOptionBuildConsist", reqNumberEngines, rl.getName())); 310 addLine(FIVE, Bundle.getMessage("buildOptionSingleLocos", singleLocos.size(), rl.getName())); 311 if (singleLocos.size() >= reqNumberEngines) { 312 int locos = 0; 313 // first find an "A" unit 314 for (Engine engine : singleLocos) { 315 if (engine.isBunit()) { 316 continue; 317 } 318 if (setEngineDestination(engine, rl, rld)) { 319 getEngineList().remove(engine); 320 singleLocos.remove(engine); 321 locos++; 322 break; // found "A" unit 323 } 324 } 325 // did we find an "A" unit? 326 if (locos > 0) { 327 // now add the rest "A" or "B" units 328 for (Engine engine : singleLocos) { 329 if (setEngineDestination(engine, rl, rld)) { 330 getEngineList().remove(engine); 331 locos++; 332 } 333 if (locos == reqNumberEngines) { 334 return true; // done! 335 } 336 } 337 } else { 338 // list the "B" units found 339 for (Engine engine : singleLocos) { 340 if (engine.isBunit()) { 341 addLine(FIVE, 342 Bundle.getMessage("BuildEngineBunit", engine.toString(), engine.getLocationName(), 343 engine.getTrackName())); 344 } 345 } 346 } 347 } 348 return false; 349 } 350 351 /** 352 * Adds engines to the train starting at the first location in the train's 353 * route. Note that engines from staging are already part of the train. 354 * There can be up to two engine swaps in a train's route. 355 * 356 * @throws BuildFailedException if required engines can't be added to train. 357 */ 358 protected void addEnginesToTrain() throws BuildFailedException { 359 // allow up to two engine and caboose swaps in the train's route 360 RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation(); 361 RouteLocation engineTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation(); 362 363 // Adjust where the locos will terminate 364 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 365 getTrain().getSecondLegStartRouteLocation() != null) { 366 engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation(); 367 } 368 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 369 getTrain().getThirdLegStartRouteLocation() != null) { 370 engineTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation(); 371 // No engine or caboose change at first leg? 372 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) { 373 engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation(); 374 } 375 } 376 377 if (getTrain().getLeadEngine() == null) { 378 // option to remove locos from the train 379 if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 380 getTrain().getSecondLegStartRouteLocation() != null) { 381 addLine(THREE, BLANK_LINE); 382 addLine(THREE, 383 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(), 384 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 385 getTrain().getSecondLegEngineRoad())); 386 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 387 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 388 getTrain().getSecondLegStartRouteLocation())) { 389 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 390 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 391 getTrain().getSecondLegStartRouteLocation())) { 392 } else { 393 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 394 getTrain().getSecondLegNumberEngines(), getTrain().getTrainDepartsName(), 395 getTrain().getSecondLegStartRouteLocation().getLocation().getName())); 396 } 397 } 398 if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 399 getTrain().getThirdLegStartRouteLocation() != null) { 400 addLine(THREE, BLANK_LINE); 401 addLine(THREE, 402 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(), 403 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 404 getTrain().getThirdLegEngineRoad())); 405 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 406 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 407 getTrain().getThirdLegStartRouteLocation())) { 408 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 409 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 410 getTrain().getThirdLegStartRouteLocation())) { 411 } else { 412 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 413 getTrain().getThirdLegNumberEngines(), getTrain().getTrainDepartsName(), 414 getTrain().getThirdLegStartRouteLocation().getLocation().getName())); 415 } 416 } 417 // load engines at the start of the route for this train 418 addLine(THREE, BLANK_LINE); 419 if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(), 420 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 421 // when adding a caboose later in the route, no engine change 422 _secondLeadEngine = _lastEngine; 423 _thirdLeadEngine = _lastEngine; 424 } else if (getConsist(getTrain().getNumberEngines(), getTrain().getEngineModel(), 425 getTrain().getEngineRoad(), 426 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 427 // when adding a caboose later in the route, no engine change 428 _secondLeadEngine = _lastEngine; 429 _thirdLeadEngine = _lastEngine; 430 } else { 431 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", getTrain().getNumberEngines(), 432 getTrain().getTrainDepartsName(), engineTerminatesFirstLeg.getName())); 433 } 434 } 435 436 // First engine change in route? 437 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 438 (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 439 addLine(THREE, BLANK_LINE); 440 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 441 addLine(THREE, 442 Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(), 443 getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 444 getTrain().getSecondLegEngineRoad())); 445 } else { 446 addLine(THREE, 447 Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(), 448 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 449 getTrain().getSecondLegEngineRoad())); 450 } 451 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 452 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 453 engineTerminatesSecondLeg)) { 454 _secondLeadEngine = _lastEngine; 455 _thirdLeadEngine = _lastEngine; 456 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 457 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 458 engineTerminatesSecondLeg)) { 459 _secondLeadEngine = _lastEngine; 460 _thirdLeadEngine = _lastEngine; 461 } else { 462 throw new BuildFailedException( 463 Bundle.getMessage("buildErrorEngines", getTrain().getSecondLegNumberEngines(), 464 getTrain().getSecondLegStartRouteLocation(), engineTerminatesSecondLeg)); 465 } 466 } 467 // Second engine change in route? 468 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 469 (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 470 addLine(THREE, BLANK_LINE); 471 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 472 addLine(THREE, 473 Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(), 474 getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 475 getTrain().getThirdLegEngineRoad())); 476 } else { 477 addLine(THREE, 478 Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(), 479 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 480 getTrain().getThirdLegEngineRoad())); 481 } 482 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 483 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 484 getTrain().getTrainTerminatesRouteLocation())) { 485 _thirdLeadEngine = _lastEngine; 486 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 487 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 488 getTrain().getTrainTerminatesRouteLocation())) { 489 _thirdLeadEngine = _lastEngine; 490 } else { 491 throw new BuildFailedException( 492 Bundle.getMessage("buildErrorEngines", Integer.parseInt(getTrain().getThirdLegNumberEngines()), 493 getTrain().getThirdLegStartRouteLocation(), 494 getTrain().getTrainTerminatesRouteLocation())); 495 } 496 } 497 if (!getTrain().getNumberEngines().equals("0") && 498 (!getTrain().isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) { 499 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 500 } 501 } 502 503 protected boolean getConsist(String reqNumEngines, String model, String road, RouteLocation rl, RouteLocation rld) 504 throws BuildFailedException { 505 if (reqNumEngines.equals(Train.AUTO_HPT)) { 506 for (int i = 2; i < Setup.getMaxNumberEngines(); i++) { 507 if (getEngines(Integer.toString(i), model, road, rl, rld)) { 508 return true; 509 } 510 } 511 } 512 return false; 513 } 514 515 protected void showEnginesByLocation() { 516 // show how many engines were found 517 addLine(SEVEN, BLANK_LINE); 518 addLine(ONE, 519 Bundle.getMessage("buildFoundLocos", Integer.toString(getEngineList().size()), getTrain().getName())); 520 521 // only show engines once using the train's route 522 List<String> locationNames = new ArrayList<>(); 523 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 524 if (locationNames.contains(rl.getName())) { 525 continue; 526 } 527 locationNames.add(rl.getName()); 528 int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getEngineList())); 529 if (rl.getLocation().isStaging()) { 530 addLine(FIVE, 531 Bundle.getMessage("buildLocosInStaging", count, rl.getName())); 532 } else { 533 addLine(FIVE, 534 Bundle.getMessage("buildLocosAtLocation", count, rl.getName())); 535 } 536 for (Engine engine : getEngineList()) { 537 if (engine.getLocationName().equals(rl.getName())) { 538 addLine(SEVEN, 539 Bundle.getMessage("buildLocoAtLocWithMoves", engine.toString(), engine.getTypeName(), 540 engine.getModel(), engine.getLocationName(), engine.getTrackName(), 541 engine.getMoves())); 542 } 543 } 544 addLine(SEVEN, BLANK_LINE); 545 } 546 } 547 548 /** 549 * Adds engines to the train if needed based on HPT. Note that the engine 550 * additional weight isn't considered in this method so HP requirements can 551 * be lower compared to the original calculation which did include the 552 * weight of the engines. 553 * 554 * @param hpAvailable the engine hp already assigned to the train for this 555 * leg 556 * @param extraHpNeeded the additional hp needed 557 * @param rlNeedHp where in the route the additional hp is needed 558 * @param rl the start of the leg 559 * @param rld the end of the leg 560 * @throws BuildFailedException if unable to add engines to train 561 */ 562 protected void addEnginesBasedHPT(int hpAvailable, int extraHpNeeded, RouteLocation rlNeedHp, RouteLocation rl, 563 RouteLocation rld) throws BuildFailedException { 564 if (rlNeedHp == null) { 565 return; 566 } 567 int numberLocos = 0; 568 // determine how many locos have already been assigned to the train 569 List<Engine> engines = engineManager.getList(getTrain()); 570 for (Engine rs : engines) { 571 if (rs.getRouteLocation() == rl) { 572 numberLocos++; 573 } 574 } 575 576 addLine(ONE, BLANK_LINE); 577 addLine(ONE, Bundle.getMessage("buildTrainReqExtraHp", extraHpNeeded, rlNeedHp.getName(), 578 rld.getName(), numberLocos)); 579 580 // determine engine model and road 581 String model = getTrain().getEngineModel(); 582 String road = getTrain().getEngineRoad(); 583 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 584 rl == getTrain().getSecondLegStartRouteLocation()) { 585 model = getTrain().getSecondLegEngineModel(); 586 road = getTrain().getSecondLegEngineRoad(); 587 } else if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 588 rl == getTrain().getThirdLegStartRouteLocation()) { 589 model = getTrain().getThirdLegEngineModel(); 590 road = getTrain().getThirdLegEngineRoad(); 591 } 592 593 while (numberLocos < Setup.getMaxNumberEngines()) { 594 // if no engines assigned, can't use B unit as first engine 595 if (!getEngines("1", model, road, rl, rld, numberLocos > 0)) { 596 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", Bundle.getMessage("additional"), 597 rl.getName(), rld.getName())); 598 } 599 numberLocos++; 600 int currentHp = getTrain().getTrainHorsePower(rlNeedHp); 601 if (currentHp > hpAvailable + extraHpNeeded) { 602 break; // done 603 } 604 if (numberLocos < Setup.getMaxNumberEngines()) { 605 addLine(FIVE, BLANK_LINE); 606 addLine(THREE, 607 Bundle.getMessage("buildContinueAddLocos", (hpAvailable + extraHpNeeded - currentHp), 608 rlNeedHp.getName(), rld.getName(), numberLocos, currentHp)); 609 } else { 610 addLine(FIVE, 611 Bundle.getMessage("buildMaxNumberLocoAssigned", Setup.getMaxNumberEngines())); 612 } 613 } 614 } 615 616 /** 617 * Checks to see if the engine or consist assigned to the train has the 618 * appropriate HP. If the train's HP requirements are significantly higher 619 * or lower than the engine that was assigned, the program will search for a 620 * more appropriate engine or consist, and assign that engine or consist to 621 * the train. The HP calculation is based on a minimum train speed of 36 622 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 623 * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade 624 * requires a minimum of 3 HPT. Disabled for trains departing staging. 625 * 626 * @throws BuildFailedException if coding error. 627 */ 628 protected void checkEngineHP() throws BuildFailedException { 629 if (Setup.getHorsePowerPerTon() != 0) { 630 if (getTrain().getNumberEngines().equals(Train.AUTO_HPT)) { 631 checkEngineHP(getTrain().getLeadEngine(), getTrain().getEngineModel(), getTrain().getEngineRoad()); // 1st 632 // leg 633 } 634 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 635 getTrain().getSecondLegNumberEngines().equals(Train.AUTO_HPT)) { 636 checkEngineHP(_secondLeadEngine, getTrain().getSecondLegEngineModel(), 637 getTrain().getSecondLegEngineRoad()); 638 } 639 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 640 getTrain().getThirdLegNumberEngines().equals(Train.AUTO_HPT)) { 641 checkEngineHP(_thirdLeadEngine, getTrain().getThirdLegEngineModel(), 642 getTrain().getThirdLegEngineRoad()); 643 } 644 } 645 } 646 647 private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException { 648 // code check 649 if (leadEngine == null) { 650 throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()"); 651 } 652 // departing staging? 653 if (leadEngine.getRouteLocation() == getTrain().getTrainDepartsRouteLocation() && 654 getTrain().isDepartingStaging()) { 655 return; 656 } 657 addLine(ONE, BLANK_LINE); 658 addLine(ONE, 659 Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(), 660 leadEngine.getDestinationName(), getTrain().getTrainHorsePower(leadEngine.getRouteLocation()), 661 Setup.getHorsePowerPerTon())); 662 // now determine the HP needed for this train 663 double hpNeeded = 0; 664 int hpAvailable = 0; 665 Route route = getTrain().getRoute(); 666 if (route != null) { 667 boolean helper = false; 668 boolean foundStart = false; 669 for (RouteLocation rl : route.getLocationsBySequenceList()) { 670 if (!foundStart && rl != leadEngine.getRouteLocation()) { 671 continue; 672 } 673 foundStart = true; 674 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 675 rl == getTrain().getSecondLegStartRouteLocation()) || 676 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 677 rl == getTrain().getThirdLegStartRouteLocation())) { 678 addLine(FIVE, 679 Bundle.getMessage("AddHelpersAt", rl.getName())); 680 helper = true; 681 } 682 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 683 rl == getTrain().getSecondLegEndRouteLocation()) || 684 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 685 rl == getTrain().getThirdLegEndRouteLocation())) { 686 addLine(FIVE, 687 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 688 helper = false; 689 } 690 if (helper) { 691 continue; // ignore HP needed when helpers are assigned to 692 // the train 693 } 694 // check for a change of engines in the train's route 695 if (rl == leadEngine.getRouteDestination()) { 696 log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName()); 697 break; // done 698 } 699 if (getTrain().getTrainHorsePower(rl) > hpAvailable) 700 hpAvailable = getTrain().getTrainHorsePower(rl); 701 int weight = rl.getTrainWeight(); 702 double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight; 703 if (hpRequired < Setup.getHorsePowerPerTon() * weight) 704 hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT 705 if (hpRequired > hpNeeded) { 706 addLine(SEVEN, 707 Bundle.getMessage("buildReportTrainHpNeeds", weight, getTrain().getNumberCarsInTrain(rl), 708 rl.getGrade(), rl.getName(), rl.getId(), hpRequired)); 709 hpNeeded = hpRequired; 710 } 711 } 712 } 713 if (hpNeeded > hpAvailable) { 714 addLine(ONE, 715 Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded)); 716 getNewEngine((int) hpNeeded, leadEngine, model, road); 717 } else if (hpAvailable > 2 * hpNeeded) { 718 addLine(ONE, 719 Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded)); 720 getNewEngine((int) hpNeeded, leadEngine, model, road); 721 } else { 722 log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString()); 723 } 724 } 725 726 /** 727 * Removes engine from train and attempts to replace it with engine or 728 * consist that meets the HP requirements of the train. 729 * 730 * @param hpNeeded How much hp is needed 731 * @param leadEngine The lead engine for this leg 732 * @param model The engine's model 733 * @param road The engine's road 734 * @throws BuildFailedException if new engine not found 735 */ 736 protected void getNewEngine(int hpNeeded, Engine leadEngine, String model, String road) 737 throws BuildFailedException { 738 // save lead engine's rl, and rld 739 RouteLocation rl = leadEngine.getRouteLocation(); 740 RouteLocation rld = leadEngine.getRouteDestination(); 741 removeEngineFromTrain(leadEngine); 742 getEngineList().add(0, leadEngine); // put engine back into the pool 743 if (hpNeeded < 50) { 744 hpNeeded = 50; // the minimum HP 745 } 746 int hpMax = hpNeeded; 747 // largest single engine HP known today is less than 15,000. 748 // high end modern diesel locos approximately 5000 HP. 749 // 100 car train at 100 tons per car and 2 HPT requires 20,000 HP. 750 // will assign consisted engines to train. 751 boolean foundLoco = false; 752 List<Engine> rejectedLocos = new ArrayList<>(); 753 hpLoop: while (hpMax < 20000) { 754 hpMax += hpNeeded / 2; // start off looking for an engine with no 755 // more than 50% extra HP 756 log.debug("Max hp {}", hpMax); 757 for (Engine engine : getEngineList()) { 758 if (rejectedLocos.contains(engine)) { 759 continue; 760 } 761 // don't use non lead locos in a consist 762 if (engine.getConsist() != null && !engine.isLead()) { 763 continue; 764 } 765 if (engine.getLocation() != rl.getLocation()) { 766 continue; 767 } 768 if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) { 769 continue; 770 } 771 if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road) || 772 road.equals(Train.NONE) && !getTrain().isLocoRoadNameAccepted(engine.getRoadName())) { 773 continue; 774 } 775 int engineHp = engine.getHpInteger(); 776 if (engine.getConsist() != null) { 777 for (Engine e : engine.getConsist().getEngines()) { 778 if (e != engine) { 779 engineHp = engineHp + e.getHpInteger(); 780 } 781 } 782 } 783 if (engineHp > hpNeeded && engineHp <= hpMax) { 784 addLine(FIVE, 785 Bundle.getMessage("buildLocoHasRequiredHp", engine.toString(), engineHp, hpNeeded)); 786 if (setEngineDestination(engine, rl, rld)) { 787 foundLoco = true; 788 break hpLoop; 789 } else { 790 rejectedLocos.add(engine); 791 } 792 } 793 } 794 } 795 if (!foundLoco && !getTrain().isBuildConsistEnabled()) { 796 throw new BuildFailedException(Bundle.getMessage("buildErrorEngHp", rl.getLocation().getName())); 797 } 798 } 799 800 /** 801 * Sets the destination track for an engine and assigns it to the train. 802 * 803 * @param engine The engine to be added to train 804 * @param rl Departure route location 805 * @param rld Destination route location 806 * @return true if destination track found and set 807 */ 808 protected boolean setEngineDestination(Engine engine, RouteLocation rl, RouteLocation rld) { 809 // engine to staging? 810 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 811 String status = 812 engine.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 813 if (status.equals(Track.OKAY)) { 814 addEngineToTrain(engine, rl, rld, getTerminateStagingTrack()); 815 return true; // done 816 } else { 817 addLine(SEVEN, 818 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 819 getTerminateStagingTrack().getTrackTypeName(), 820 getTerminateStagingTrack().getLocation().getName(), 821 getTerminateStagingTrack().getName(), status)); 822 } 823 } else { 824 // find a destination track for this engine 825 Location destination = rld.getLocation(); 826 List<Track> destTracks = destination.getTracksByMoves(null); 827 if (destTracks.size() == 0) { 828 addLine(THREE, Bundle.getMessage("buildNoTracksAtDestination", rld.getName())); 829 } 830 for (Track track : destTracks) { 831 if (!checkDropTrainDirection(engine, rld, track)) { 832 continue; 833 } 834 if (!checkTrainCanDrop(engine, track)) { 835 continue; 836 } 837 String status = engine.checkDestination(destination, track); 838 if (status.equals(Track.OKAY)) { 839 addLine(FIVE, 840 Bundle.getMessage("buildEngineCanDrop", engine.toString(), 841 track.getTrackTypeName(), 842 track.getLocation().getName(), track.getName())); 843 addEngineToTrain(engine, rl, rld, track); 844 return true; 845 } else { 846 addLine(SEVEN, 847 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 848 track.getTrackTypeName(), 849 track.getLocation().getName(), track.getName(), status)); 850 } 851 } 852 addLine(FIVE, 853 Bundle.getMessage("buildCanNotDropEngToDest", engine.toString(), rld.getName())); 854 } 855 return false; // not able to set loco's destination 856 } 857 858 /** 859 * Adds an engine to the train. 860 * 861 * @param engine the engine being added to the train 862 * @param rl where in the train's route to pick up the engine 863 * @param rld where in the train's route to set out the engine 864 * @param track the destination track for this engine 865 */ 866 private void addEngineToTrain(Engine engine, RouteLocation rl, RouteLocation rld, Track track) { 867 _lastEngine = engine; // needed in case there's a engine change in the 868 // train's route 869 engine = checkQuickServiceArrival(engine, rld, track); 870 if (getTrain().getLeadEngine() == null) { 871 getTrain().setLeadEngine(engine); // load lead engine 872 } 873 addLine(ONE, Bundle.getMessage("buildEngineAssigned", engine.toString(), rl.getName(), 874 rld.getName(), track.getName())); 875 engine.setDestination(track.getLocation(), track, Engine.FORCE); 876 int length = engine.getTotalLength(); 877 int weightTons = engine.getAdjustedWeightTons(); 878 // engine in consist? 879 if (engine.getConsist() != null) { 880 length = engine.getConsist().getTotalLength(); 881 weightTons = engine.getConsist().getAdjustedWeightTons(); 882 for (Engine cEngine : engine.getConsist().getEngines()) { 883 if (cEngine != engine) { 884 addLine(ONE, Bundle.getMessage("buildEngineAssigned", cEngine.toString(), 885 rl.getName(), rld.getName(), track.getName())); 886 cEngine.setTrain(getTrain()); 887 cEngine.setRouteLocation(rl); 888 cEngine.setRouteDestination(rld); 889 cEngine.setDestination(track.getLocation(), track, RollingStock.FORCE); // force 890 } 891 } 892 } 893 // now adjust train length and weight for each location that engines are 894 // in the train 895 finishAddRsToTrain(engine, rl, rld, length, weightTons); 896 } 897 898 /** 899 * Checks to see if track is requesting a quick service. Since it isn't 900 * possible for a engine to be pulled and set out twice, this code creates a 901 * "clone" engine to create the requested Manifest. A engine could have 902 * multiple clones, therefore each clone has a creation order number. The 903 * first clone is used to restore a engine's location in the case of reset. 904 * 905 * @param engine the engine possibly needing quick service 906 * @param track the destination track 907 * @return the engine if not quick service, or a clone if quick service 908 */ 909 private Engine checkQuickServiceArrival(Engine engine, RouteLocation rld, Track track) { 910 if (!track.isQuickServiceEnabled()) { 911 return engine; 912 } 913 addLine(FIVE, 914 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 915 track.getLocation().getName(), track.getName())); 916 // quick service enabled, create clones 917 Engine cloneEng = engineManager.createClone(engine, track, getTrain(), getStartTime()); 918 // for timing, use arrival times for the train that is building 919 // other trains will use their departure time, loaded when creating the Manifest 920 String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true); 921 cloneEng.setSetoutTime(expectedArrivalTime); 922 // remember where in the route the car was delivered 923 engine.setRouteDestination(rld); 924 return cloneEng; // return clone 925 } 926 927 /** 928 * Checks to see if additional engines are needed for the train based on the 929 * train's calculated tonnage. Minimum speed for the train is fixed at 36 930 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 931 * horsepower needed. For example a 1% grade requires a minimum of 3 HPT. 932 * Ignored when departing staging 933 * 934 * @throws BuildFailedException if build failure 935 */ 936 protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException { 937 if (!getTrain().isBuildConsistEnabled() || 938 Setup.getHorsePowerPerTon() == 0) { 939 return; 940 } 941 addLine(ONE, BLANK_LINE); 942 addLine(ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon())); 943 Route route = getTrain().getRoute(); 944 int hpAvailable = 0; 945 int extraHpNeeded = 0; 946 RouteLocation rlNeedHp = null; 947 RouteLocation rlStart = getTrain().getTrainDepartsRouteLocation(); 948 RouteLocation rlEnd = getTrain().getTrainTerminatesRouteLocation(); 949 boolean departingStaging = getTrain().isDepartingStaging(); 950 if (route != null) { 951 boolean helper = false; 952 for (RouteLocation rl : route.getLocationsBySequenceList()) { 953 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 954 rl == getTrain().getSecondLegStartRouteLocation()) || 955 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 956 rl == getTrain().getThirdLegStartRouteLocation())) { 957 addLine(FIVE, Bundle.getMessage("AddHelpersAt", rl.getName())); 958 helper = true; 959 } 960 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 961 rl == getTrain().getSecondLegEndRouteLocation()) || 962 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 963 rl == getTrain().getThirdLegEndRouteLocation())) { 964 addLine(FIVE, 965 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 966 helper = false; 967 } 968 if (helper) { 969 continue; 970 } 971 // check for a change of engines in the train's route 972 if (((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 973 rl == getTrain().getSecondLegStartRouteLocation()) || 974 ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 975 rl == getTrain().getThirdLegStartRouteLocation())) { 976 log.debug("Loco change at ({})", rl.getName()); 977 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rl); 978 addLine(THREE, BLANK_LINE); 979 // reset for next leg of train's route 980 rlStart = rl; 981 rlNeedHp = null; 982 extraHpNeeded = 0; 983 departingStaging = false; 984 } 985 if (departingStaging) { 986 continue; 987 } 988 double weight = rl.getTrainWeight(); 989 if (weight > 0) { 990 double hptMinimum = Setup.getHorsePowerPerTon(); 991 double hptGrade = (Control.speedHpt * rl.getGrade() / 12); 992 double hp = getTrain().getTrainHorsePower(rl); 993 double hpt = hp / weight; 994 if (hptGrade > hptMinimum) { 995 hptMinimum = hptGrade; 996 } 997 if (hptMinimum > hpt) { 998 int addHp = (int) (hptMinimum * weight - hp); 999 if (addHp > extraHpNeeded) { 1000 hpAvailable = (int) hp; 1001 extraHpNeeded = addHp; 1002 rlNeedHp = rl; 1003 } 1004 addLine(SEVEN, 1005 Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(), 1006 hpt, hptMinimum, rl.getName(), rl.getId())); 1007 addLine(FIVE, 1008 Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum)); 1009 } 1010 } 1011 } 1012 } 1013 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rlEnd); 1014 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 1015 addLine(THREE, BLANK_LINE); 1016 } 1017 1018 protected void removeEngineFromTrain(Engine engine) { 1019 // replace lead engine? 1020 if (getTrain().getLeadEngine() == engine) { 1021 getTrain().setLeadEngine(null); 1022 } 1023 if (engine.getConsist() != null) { 1024 for (Engine e : engine.getConsist().getEngines()) { 1025 removeRollingStockFromTrain(e); 1026 } 1027 } else { 1028 removeRollingStockFromTrain(engine); 1029 } 1030 } 1031 1032 private final static Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class); 1033}