001package jmri.jmrit.operations.trains.trainbuilder; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import jmri.jmrit.operations.locations.Location; 007import jmri.jmrit.operations.locations.Track; 008import jmri.jmrit.operations.rollingstock.RollingStock; 009import jmri.jmrit.operations.rollingstock.engines.Engine; 010import jmri.jmrit.operations.routes.Route; 011import jmri.jmrit.operations.routes.RouteLocation; 012import jmri.jmrit.operations.setup.Control; 013import jmri.jmrit.operations.setup.Setup; 014import jmri.jmrit.operations.trains.BuildFailedException; 015import jmri.jmrit.operations.trains.Train; 016 017import org.apache.commons.lang3.StringUtils; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * Contains methods for engines when building a train. 023 * 024 * @author Daniel Boudreau Copyright (C) 2022, 2026 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 (_engineIndex = 0; _engineIndex < getEngineList().size(); _engineIndex++) { 186 Engine engine = getEngineList().get(_engineIndex); 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 if (engine.getConsist() != null) { 244 assignedLocos = assignedLocos + engine.getConsist().getSize(); 245 } else { 246 assignedLocos++; 247 } 248 continue; 249 } 250 // can't use B units if requesting one loco 251 if (!useBunit && reqNumberEngines == 1 && engine.isBunit()) { 252 addLine(SEVEN, 253 Bundle.getMessage("buildExcludeEngineBunit", engine.toString(), engine.getModel())); 254 continue; 255 } 256 // is this engine part of a consist? 257 if (engine.getConsist() == null) { 258 // single engine, but does the train require a consist? 259 if (reqNumberEngines > 1) { 260 addLine(SEVEN, 261 Bundle.getMessage("buildExcludeEngineSingle", engine.toString(), reqNumberEngines)); 262 singleLocos.add(engine); 263 continue; 264 } 265 // engine is part of a consist 266 } else if (engine.getConsist().getSize() == reqNumberEngines) { 267 log.debug("Consist ({}) has the required number of engines", engine.getConsist().getName()); // NOI18N 268 } else if (reqNumberEngines != 0) { 269 addLine(SEVEN, 270 Bundle.getMessage("buildExcludeEngConsistNumber", engine.toString(), 271 engine.getConsist().getName(), engine.getConsist().getSize())); 272 continue; 273 } 274 // found a loco or consist! 275 assignedLocos++; 276 277 // now find terminal track for engine(s) 278 addLine(FIVE, 279 Bundle.getMessage("buildEngineRoadModelType", engine.toString(), engine.getRoadName(), 280 engine.getModel(), engine.getTypeName(), engine.getLocationName(), engine.getTrackName(), 281 rld.getName())); 282 if (setEngineDestination(engine, rl, rld)) { 283 return true; // normal exit when not staging 284 } 285 } 286 // build a consist out of non-consisted locos 287 if (assignedLocos == 0 && reqNumberEngines > 1 && getTrain().isBuildConsistEnabled()) { 288 if (buildConsistFromSingleLocos(reqNumberEngines, singleLocos, rl, rld)) { 289 return true; // normal exit when building with single locos 290 } 291 } 292 if (assignedLocos == 0) { 293 String locationName = rl.getName(); 294 if (departStagingTrack != null) { 295 locationName = locationName + ", " + departStagingTrack.getName(); 296 } 297 addLine(FIVE, Bundle.getMessage("buildNoLocosFoundAtLocation", locationName)); 298 } else if (departStagingTrack != null && (reqNumberEngines == 0 || reqNumberEngines == assignedLocos)) { 299 return true; // normal exit assigning from staging 300 } 301 // not able to assign engines to train 302 return false; 303 } 304 305 private boolean buildConsistFromSingleLocos(int reqNumberEngines, List<Engine> singleLocos, RouteLocation rl, 306 RouteLocation rld) { 307 addLine(FIVE, Bundle.getMessage("buildOptionBuildConsist", reqNumberEngines, rl.getName())); 308 addLine(FIVE, Bundle.getMessage("buildOptionSingleLocos", singleLocos.size(), rl.getName())); 309 if (singleLocos.size() >= reqNumberEngines) { 310 int locos = 0; 311 // first find an "A" unit 312 for (Engine engine : singleLocos) { 313 if (engine.isBunit()) { 314 continue; 315 } 316 if (setEngineDestination(engine, rl, rld)) { 317 singleLocos.remove(engine); 318 locos++; 319 break; // found "A" unit 320 } 321 } 322 // did we find an "A" unit? 323 if (locos > 0) { 324 // now add the rest "A" or "B" units 325 for (Engine engine : singleLocos) { 326 if (setEngineDestination(engine, rl, rld)) { 327 locos++; 328 } 329 if (locos == reqNumberEngines) { 330 return true; // done! 331 } 332 } 333 } else { 334 // list the "B" units found 335 for (Engine engine : singleLocos) { 336 if (engine.isBunit()) { 337 addLine(FIVE, 338 Bundle.getMessage("BuildEngineBunit", engine.toString(), engine.getLocationName(), 339 engine.getTrackName())); 340 } 341 } 342 } 343 } 344 return false; 345 } 346 347 /** 348 * Adds engines to the train starting at the first location in the train's 349 * route. Note that engines from staging are already part of the train. 350 * There can be up to two engine swaps in a train's route. 351 * 352 * @throws BuildFailedException if required engines can't be added to train. 353 */ 354 protected void addEnginesToTrain() throws BuildFailedException { 355 // allow up to two engine and caboose swaps in the train's route 356 RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation(); 357 RouteLocation engineTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation(); 358 RouteLocation engineTerminatesThirdLeg = getTrain().getTrainTerminatesRouteLocation(); 359 360 // Adjust where the locos will terminate 361 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 362 getTrain().getSecondLegStartRouteLocation() != null) { 363 engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation(); 364 } 365 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 366 getTrain().getThirdLegStartRouteLocation() != null) { 367 engineTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation(); 368 // No engine or caboose change at first leg? 369 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) { 370 engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation(); 371 } 372 } 373 // optionally set out added engines 374 if (getTrain().getSecondLegEndRouteLocation() != null && 375 ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 376 (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) { 377 engineTerminatesSecondLeg = getTrain().getSecondLegEndRouteLocation(); 378 } 379 if (getTrain().getThirdLegEndRouteLocation() != null && 380 ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 381 (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) { 382 engineTerminatesThirdLeg = getTrain().getThirdLegEndRouteLocation(); 383 } 384 385 if (getTrain().getLeadEngine() == null) { 386 // option to remove locos from the train 387 if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 388 getTrain().getSecondLegStartRouteLocation() != null) { 389 addLine(THREE, BLANK_LINE); 390 addLine(THREE, 391 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(), 392 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 393 getTrain().getSecondLegEngineRoad())); 394 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 395 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 396 getTrain().getSecondLegStartRouteLocation())) { 397 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 398 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 399 getTrain().getSecondLegStartRouteLocation())) { 400 } else { 401 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 402 getTrain().getSecondLegNumberEngines(), getTrain().getTrainDepartsName(), 403 getTrain().getSecondLegStartRouteLocation().getLocation().getName())); 404 } 405 } 406 if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 407 getTrain().getThirdLegStartRouteLocation() != null) { 408 addLine(THREE, BLANK_LINE); 409 addLine(THREE, 410 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(), 411 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 412 getTrain().getThirdLegEngineRoad())); 413 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 414 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 415 getTrain().getThirdLegStartRouteLocation())) { 416 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 417 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 418 getTrain().getThirdLegStartRouteLocation())) { 419 } else { 420 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 421 getTrain().getThirdLegNumberEngines(), getTrain().getTrainDepartsName(), 422 getTrain().getThirdLegStartRouteLocation().getLocation().getName())); 423 } 424 } 425 // load engines at the start of the route for this train 426 if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(), 427 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 428 // when adding a caboose later in the route, no engine change 429 _secondLeadEngine = _lastEngine; 430 _thirdLeadEngine = _lastEngine; 431 } else if (getConsist(getTrain().getNumberEngines(), getTrain().getEngineModel(), 432 getTrain().getEngineRoad(), 433 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 434 // when adding a caboose later in the route, no engine change 435 _secondLeadEngine = _lastEngine; 436 _thirdLeadEngine = _lastEngine; 437 } else { 438 addLine(THREE, BLANK_LINE); 439 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", getTrain().getNumberEngines(), 440 getTrain().getTrainDepartsName(), engineTerminatesFirstLeg.getName())); 441 } 442 } 443 444 // First engine change in route? 445 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 446 (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 447 (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 448 addLine(THREE, BLANK_LINE); 449 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 450 addLine(THREE, 451 Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(), 452 getTrain().getSecondLegStartRouteLocation().getId(), 453 getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 454 getTrain().getSecondLegEngineRoad())); 455 } else { 456 addLine(THREE, 457 Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(), 458 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 459 getTrain().getSecondLegEngineRoad())); 460 } 461 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 462 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 463 engineTerminatesSecondLeg)) { 464 _secondLeadEngine = _lastEngine; 465 _thirdLeadEngine = _lastEngine; 466 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 467 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 468 engineTerminatesSecondLeg)) { 469 _secondLeadEngine = _lastEngine; 470 _thirdLeadEngine = _lastEngine; 471 } else { 472 throw new BuildFailedException( 473 Bundle.getMessage("buildErrorEngines", getTrain().getSecondLegNumberEngines(), 474 getTrain().getSecondLegStartRouteLocation(), engineTerminatesSecondLeg)); 475 } 476 } 477 // Second engine change in route? 478 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 479 (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 480 (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 481 addLine(THREE, BLANK_LINE); 482 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 483 addLine(THREE, 484 Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(), 485 getTrain().getThirdLegStartRouteLocation().getId(), 486 getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 487 getTrain().getThirdLegEngineRoad())); 488 } else { 489 addLine(THREE, 490 Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(), 491 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 492 getTrain().getThirdLegEngineRoad())); 493 } 494 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 495 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 496 engineTerminatesThirdLeg)) { 497 _thirdLeadEngine = _lastEngine; 498 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 499 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 500 engineTerminatesThirdLeg)) { 501 _thirdLeadEngine = _lastEngine; 502 } else { 503 throw new BuildFailedException( 504 Bundle.getMessage("buildErrorEngines", Integer.parseInt(getTrain().getThirdLegNumberEngines()), 505 getTrain().getThirdLegStartRouteLocation(), 506 getTrain().getTrainTerminatesRouteLocation())); 507 } 508 } 509 if (!getTrain().getNumberEngines().equals("0") && 510 (!getTrain().isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) { 511 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 512 } 513 } 514 515 protected boolean getConsist(String reqNumEngines, String model, String road, RouteLocation rl, RouteLocation rld) 516 throws BuildFailedException { 517 if (reqNumEngines.equals(Train.AUTO_HPT)) { 518 for (int i = 2; i < Setup.getMaxNumberEngines(); i++) { 519 if (getEngines(Integer.toString(i), model, road, rl, rld)) { 520 return true; 521 } 522 } 523 } 524 return false; 525 } 526 527 protected void showEnginesByLocation() { 528 // show how many engines were found 529 addLine(SEVEN, BLANK_LINE); 530 addLine(ONE, 531 Bundle.getMessage("buildFoundLocos", Integer.toString(getEngineList().size()), getTrain().getName())); 532 533 // only show engines once using the train's route 534 List<String> locationNames = new ArrayList<>(); 535 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 536 if (locationNames.contains(rl.getName())) { 537 continue; 538 } 539 locationNames.add(rl.getName()); 540 int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getEngineList())); 541 if (rl.getLocation().isStaging()) { 542 addLine(FIVE, 543 Bundle.getMessage("buildLocosInStaging", count, rl.getName())); 544 } else { 545 addLine(FIVE, 546 Bundle.getMessage("buildLocosAtLocation", count, rl.getName())); 547 } 548 for (Engine engine : getEngineList()) { 549 if (engine.getLocationName().equals(rl.getName())) { 550 addLine(SEVEN, 551 Bundle.getMessage("buildLocoAtLocWithMoves", engine.toString(), engine.getTypeName(), 552 engine.getModel(), engine.getLocationName(), engine.getTrackName(), 553 engine.getMoves())); 554 } 555 } 556 addLine(SEVEN, BLANK_LINE); 557 } 558 } 559 560 /** 561 * Adds engines to the train if needed based on HPT. Note that the engine 562 * additional weight isn't considered in this method so HP requirements can 563 * be lower compared to the original calculation which did include the 564 * weight of the engines. 565 * 566 * @param hpAvailable the engine hp already assigned to the train for this 567 * leg 568 * @param extraHpNeeded the additional hp needed 569 * @param rlNeedHp where in the route the additional hp is needed 570 * @param rl the start of the leg 571 * @param rld the end of the leg 572 * @throws BuildFailedException if unable to add engines to train 573 */ 574 protected void addEnginesBasedHPT(int hpAvailable, int extraHpNeeded, RouteLocation rlNeedHp, RouteLocation rl, 575 RouteLocation rld) throws BuildFailedException { 576 if (rlNeedHp == null) { 577 return; 578 } 579 580 // determine how many locos have already been assigned to the train 581 int numberLocos = getTrain().getNumberEngines(rl); 582 583 addLine(ONE, BLANK_LINE); 584 addLine(ONE, Bundle.getMessage("buildTrainReqExtraHp", extraHpNeeded, rlNeedHp.getName(), 585 rld.getName(), numberLocos)); 586 587 // determine engine model and road 588 String model = getTrain().getEngineModel(); 589 String road = getTrain().getEngineRoad(); 590 if (rl == getTrain().getSecondLegStartRouteLocation() && 591 ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 592 (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) { 593 model = getTrain().getSecondLegEngineModel(); 594 road = getTrain().getSecondLegEngineRoad(); 595 } else if (rl == getTrain().getThirdLegStartRouteLocation() && 596 ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 597 (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) { 598 model = getTrain().getThirdLegEngineModel(); 599 road = getTrain().getThirdLegEngineRoad(); 600 } 601 602 while (numberLocos < Setup.getMaxNumberEngines()) { 603 // if no engines assigned, can't use B unit as first engine 604 if (!getEngines("1", model, road, rl, rld, numberLocos > 0)) { 605 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", Bundle.getMessage("additional"), 606 rl.getName(), rld.getName())); 607 } 608 numberLocos++; 609 int currentHp = getTrain().getTrainHorsePower(rlNeedHp); 610 if (currentHp >= hpAvailable + extraHpNeeded) { 611 break; // done 612 } 613 if (numberLocos < Setup.getMaxNumberEngines()) { 614 addLine(FIVE, BLANK_LINE); 615 addLine(THREE, 616 Bundle.getMessage("buildContinueAddLocos", (hpAvailable + extraHpNeeded - currentHp), 617 rlNeedHp.getName(), rld.getName(), numberLocos, currentHp)); 618 } else { 619 addLine(FIVE, 620 Bundle.getMessage("buildMaxNumberLocoAssigned", Setup.getMaxNumberEngines())); 621 } 622 } 623 } 624 625 /** 626 * Checks to see if the engine or consist assigned to the train has the 627 * appropriate HP. If the train's HP requirements are significantly higher 628 * or lower than the engine that was assigned, the program will search for a 629 * more appropriate engine or consist, and assign that engine or consist to 630 * the train. The HP calculation is based on a minimum train speed of 36 631 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 632 * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade 633 * requires a minimum of 3 HPT. Disabled for trains departing staging. 634 * 635 * @throws BuildFailedException if coding error. 636 */ 637 protected void checkEngineHP() throws BuildFailedException { 638 if (Setup.getHorsePowerPerTon() != 0) { 639 if (getTrain().getNumberEngines().equals(Train.AUTO_HPT)) { 640 checkEngineHP(getTrain().getLeadEngine(), getTrain().getEngineModel(), getTrain().getEngineRoad()); 641 } 642 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 643 getTrain().getSecondLegNumberEngines().equals(Train.AUTO_HPT)) { 644 checkEngineHP(_secondLeadEngine, getTrain().getSecondLegEngineModel(), 645 getTrain().getSecondLegEngineRoad()); 646 } 647 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 648 getTrain().getThirdLegNumberEngines().equals(Train.AUTO_HPT)) { 649 checkEngineHP(_thirdLeadEngine, getTrain().getThirdLegEngineModel(), 650 getTrain().getThirdLegEngineRoad()); 651 } 652 } 653 } 654 655 private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException { 656 // code check 657 if (leadEngine == null) { 658 throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()"); 659 } 660 // departing staging? 661 if (leadEngine.getRouteLocation() == getTrain().getTrainDepartsRouteLocation() && 662 getTrain().isDepartingStaging()) { 663 return; 664 } 665 addLine(ONE, BLANK_LINE); 666 addLine(ONE, 667 Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(), 668 leadEngine.getDestinationName(), getTrain().getTrainHorsePower(leadEngine.getRouteLocation()), 669 Setup.getHorsePowerPerTon())); 670 // now determine the HP needed for this train 671 double hpNeeded = 0; 672 int hpAvailable = 0; 673 Route route = getTrain().getRoute(); 674 if (route != null) { 675 boolean helper = false; 676 boolean foundStart = false; 677 for (RouteLocation rl : route.getLocationsBySequenceList()) { 678 if (!foundStart && rl != leadEngine.getRouteLocation()) { 679 continue; 680 } 681 foundStart = true; 682 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 683 rl == getTrain().getSecondLegStartRouteLocation()) || 684 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 685 rl == getTrain().getThirdLegStartRouteLocation())) { 686 addLine(FIVE, 687 Bundle.getMessage("AddHelpersAt", rl.getName())); 688 helper = true; 689 } 690 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 691 rl == getTrain().getSecondLegEndRouteLocation()) || 692 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 693 rl == getTrain().getThirdLegEndRouteLocation())) { 694 addLine(FIVE, 695 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 696 helper = false; 697 } 698 if (helper) { 699 continue; // ignore HP needed when helpers are assigned to 700 // the train 701 } 702 // check for a change of engines in the train's route 703 if (rl == leadEngine.getRouteDestination()) { 704 log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName()); 705 break; // done 706 } 707 if (getTrain().getTrainHorsePower(rl) > hpAvailable) 708 hpAvailable = getTrain().getTrainHorsePower(rl); 709 int weight = rl.getTrainWeight(); 710 double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight; 711 if (hpRequired < Setup.getHorsePowerPerTon() * weight) 712 hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT 713 if (hpRequired > hpNeeded) { 714 addLine(SEVEN, 715 Bundle.getMessage("buildReportTrainHpNeeds", weight, getTrain().getNumberCarsInTrain(rl), 716 rl.getGrade(), rl.getName(), rl.getId(), hpRequired)); 717 hpNeeded = hpRequired; 718 } 719 } 720 } 721 if (hpNeeded > hpAvailable) { 722 addLine(ONE, 723 Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded)); 724 getNewEngine((int) hpNeeded, leadEngine, model, road); 725 } else if (hpAvailable > 2 * hpNeeded) { 726 addLine(ONE, 727 Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded)); 728 getNewEngine((int) hpNeeded, leadEngine, model, road); 729 } else { 730 log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString()); 731 } 732 } 733 734 /** 735 * Removes engine from train and attempts to replace it with engine or 736 * consist that meets the HP requirements of the train. 737 * 738 * @param hpNeeded How much hp is needed 739 * @param leadEngine The lead engine for this leg 740 * @param model The engine's model 741 * @param road The engine's road 742 * @throws BuildFailedException if new engine not found 743 */ 744 protected void getNewEngine(int hpNeeded, Engine leadEngine, String model, String road) 745 throws BuildFailedException { 746 // save lead engine's rl, and rld 747 RouteLocation rl = leadEngine.getRouteLocation(); 748 RouteLocation rld = leadEngine.getRouteDestination(); 749 removeEngineFromTrain(leadEngine); 750 getEngineList().add(0, leadEngine); // put engine back into the pool 751 if (hpNeeded < 50) { 752 hpNeeded = 50; // the minimum HP 753 } 754 int hpMax = hpNeeded; 755 // largest single engine HP known today is less than 15,000. 756 // high end modern diesel locos approximately 5000 HP. 757 // 100 car train at 100 tons per car and 2 HPT requires 20,000 HP. 758 // will assign consisted engines to train. 759 boolean foundLoco = false; 760 List<Engine> rejectedLocos = new ArrayList<>(); 761 hpLoop: while (hpMax < 20000) { 762 hpMax += hpNeeded / 2; // start off looking for an engine with no 763 // more than 50% extra HP 764 log.debug("Max hp {}", hpMax); 765 for (Engine engine : getEngineList()) { 766 if (rejectedLocos.contains(engine)) { 767 continue; 768 } 769 // don't use non lead locos in a consist 770 if (engine.getConsist() != null && !engine.isLead()) { 771 continue; 772 } 773 if (engine.getLocation() != rl.getLocation()) { 774 continue; 775 } 776 if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) { 777 continue; 778 } 779 if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road) || 780 road.equals(Train.NONE) && !getTrain().isLocoRoadNameAccepted(engine.getRoadName())) { 781 continue; 782 } 783 int engineHp = engine.getHpInteger(); 784 if (engine.getConsist() != null) { 785 for (Engine e : engine.getConsist().getEngines()) { 786 if (e != engine) { 787 engineHp = engineHp + e.getHpInteger(); 788 } 789 } 790 } 791 if (engineHp > hpNeeded && engineHp <= hpMax) { 792 addLine(FIVE, 793 Bundle.getMessage("buildLocoHasRequiredHp", engine.toString(), engineHp, hpNeeded)); 794 if (setEngineDestination(engine, rl, rld)) { 795 foundLoco = true; 796 break hpLoop; 797 } else { 798 rejectedLocos.add(engine); 799 } 800 } 801 } 802 } 803 if (!foundLoco && !getTrain().isBuildConsistEnabled()) { 804 throw new BuildFailedException(Bundle.getMessage("buildErrorEngHp", rl.getLocation().getName())); 805 } 806 } 807 808 /** 809 * Sets the destination track for an engine and assigns it to the train. 810 * 811 * @param engine The engine to be added to train 812 * @param rl Departure route location 813 * @param rld Destination route location 814 * @return true if destination track found and set 815 */ 816 protected boolean setEngineDestination(Engine engine, RouteLocation rl, RouteLocation rld) { 817 engine.setRouteDestinationTiming(rld); // for timing 818 // engine to staging? 819 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 820 String status = 821 engine.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 822 if (status.equals(Track.OKAY)) { 823 addEngineToTrain(engine, rl, rld, getTerminateStagingTrack()); 824 return true; // done 825 } else { 826 addLine(SEVEN, 827 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 828 getTerminateStagingTrack().getTrackTypeName(), 829 getTerminateStagingTrack().getLocation().getName(), 830 getTerminateStagingTrack().getName(), status)); 831 } 832 } else { 833 // find a destination track for this engine 834 Location destination = rld.getLocation(); 835 List<Track> destTracks = destination.getTracksByMoves(null); 836 if (destTracks.size() == 0) { 837 addLine(THREE, Bundle.getMessage("buildNoTracksAtDestination", rld.getName())); 838 } 839 for (Track track : destTracks) { 840 if (!checkDropTrainDirection(engine, rld, track)) { 841 continue; 842 } 843 if (!checkTrainCanDrop(engine, track)) { 844 continue; 845 } 846 String status = engine.checkDestination(destination, track); 847 if (status.equals(Track.OKAY)) { 848 addLine(FIVE, 849 Bundle.getMessage("buildEngineCanDrop", engine.toString(), 850 track.getTrackTypeName(), 851 track.getLocation().getName(), track.getName())); 852 addEngineToTrain(engine, rl, rld, track); 853 return true; 854 } else { 855 addLine(SEVEN, 856 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 857 track.getTrackTypeName(), 858 track.getLocation().getName(), track.getName(), status)); 859 } 860 } 861 addLine(FIVE, 862 Bundle.getMessage("buildCanNotDropEngToDest", engine.toString(), rld.getName())); 863 } 864 return false; // not able to set loco's destination 865 } 866 867 /** 868 * Adds an engine to the train. 869 * 870 * @param engine the engine being added to the train 871 * @param rl where in the train's route to pick up the engine 872 * @param rld where in the train's route to set out the engine 873 * @param track the destination track for this engine 874 */ 875 private void addEngineToTrain(Engine engine, RouteLocation rl, RouteLocation rld, Track track) { 876 _lastEngine = engine; // needed in case there's a engine change in the 877 // train's route 878 if (getTrain().getLeadEngine() == null) { 879 getTrain().setLeadEngine(engine); // load lead engine 880 } 881 engine = checkQuickServiceArrival(engine, rld, track); 882 addLine(ONE, Bundle.getMessage("buildEngineAssigned", engine.toString(), rl.getName(), 883 rld.getName(), track.getName())); 884 engine.setDestination(track.getLocation(), track, Engine.FORCE); 885 int length = engine.getTotalLength(); 886 int weightTons = engine.getAdjustedWeightTons(); 887 // engine in consist? 888 if (engine.getConsist() != null) { 889 length = engine.getConsist().getTotalLength(); 890 weightTons = engine.getConsist().getAdjustedWeightTons(); 891 for (Engine cEngine : engine.getConsist().getEngines()) { 892 if (cEngine != engine) { 893 addLine(ONE, Bundle.getMessage("buildEngineAssigned", cEngine.toString(), 894 rl.getName(), rld.getName(), track.getName())); 895 cEngine.setTrain(getTrain()); 896 cEngine.setRouteLocation(rl); 897 cEngine.setRouteDestination(rld); 898 cEngine.setDestination(track.getLocation(), track, RollingStock.FORCE); // force 899 } 900 } 901 } 902 remove (engine); 903 // now adjust train length and weight for each location that engines are 904 // in the train 905 finishAddRsToTrain(engine, rl, rld, length, weightTons); 906 } 907 908 /** 909 * Checks to see if track is requesting a quick service. Since it isn't 910 * possible for a engine to be pulled and set out twice, this code creates a 911 * "clone" engine to create the requested Manifest. A engine could have 912 * multiple clones, therefore each clone has a creation order number. The 913 * first clone is used to restore a engine's location in the case of reset. 914 * 915 * @param engine the engine possibly needing quick service 916 * @param track the destination track 917 * @return the engine if not quick service, or a clone if quick service 918 */ 919 private Engine checkQuickServiceArrival(Engine engine, RouteLocation rld, Track track) { 920 if (!track.isQuickServiceEnabled()) { 921 if (Setup.isBuildOnTime()) { 922 addLine(THREE, 923 Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()), 924 track.getLocation().getName(), track.getName(), engine.toString())); 925 // warn if departing staging that is quick serviced enabled 926 if (engine.getTrack().isStaging() && engine.getTrack().isQuickServiceEnabled()) { 927 _warnings++; 928 addLine(THREE, 929 Bundle.getMessage("buildWarningQuickService", engine.toString(), 930 engine.getTrack().getTrackTypeName(), 931 engine.getTrack().getLocation().getName(), engine.getTrack().getName(), 932 getTrain().getName(), 933 StringUtils.capitalize(engine.getTrack().getTrackTypeName()))); 934 } 935 } 936 return engine; 937 } 938 // quick service enabled, create clones 939 Engine cloneEng = engineManager.createClone(engine, track, getTrain(), getStartTime()); 940 addLine(FIVE, 941 Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()), 942 track.getLocation().getName(), track.getName(), cloneEng.toString(), engine.toString())); 943 // remember where in the route the car was delivered 944 engine.setRouteDestination(rld); 945 return cloneEng; // return clone 946 } 947 948 int _hpAvailable = 0; 949 int _extraHpNeeded = 0; 950 RouteLocation _rlNeedHp; 951 RouteLocation _rlStart; 952 953 /** 954 * Checks to see if additional engines are needed for the train based on the 955 * train's calculated tonnage. Minimum speed for the train is fixed at 36 956 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 957 * horsepower needed. For example a 1% grade requires a minimum of 3 HPT. 958 * Ignored when departing staging. When using helpers, no additional 959 * engines. 960 * 961 * @throws BuildFailedException if build failure 962 */ 963 protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException { 964 if (!getTrain().isBuildConsistEnabled() || 965 Setup.getHorsePowerPerTon() == 0) { 966 return; 967 } 968 addLine(ONE, BLANK_LINE); 969 addLine(ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon())); 970 Route route = getTrain().getRoute(); 971 _rlStart = getTrain().getTrainDepartsRouteLocation(); 972 boolean departingStaging = getTrain().isDepartingStaging(); 973 if (route != null) { 974 boolean helper = false; 975 for (RouteLocation rl : route.getLocationsBySequenceList()) { 976 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 977 rl == getTrain().getSecondLegStartRouteLocation()) || 978 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 979 rl == getTrain().getThirdLegStartRouteLocation())) { 980 addLine(FIVE, Bundle.getMessage("AddHelpersAt", rl.getName())); 981 helper = true; 982 } 983 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 984 rl == getTrain().getSecondLegEndRouteLocation()) || 985 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 986 rl == getTrain().getThirdLegEndRouteLocation())) { 987 addLine(FIVE, 988 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 989 helper = false; 990 } 991 if (helper) { 992 continue; 993 } 994 // check for a change of engines in the train's route 995 if (((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 996 rl == getTrain().getSecondLegStartRouteLocation()) || 997 ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 998 rl == getTrain().getThirdLegStartRouteLocation())) { 999 log.debug("Loco change at ({})", rl.getName()); 1000 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, rl); 1001 // reset for next leg of train's route 1002 _rlStart = rl; 1003 _rlNeedHp = null; 1004 _extraHpNeeded = 0; 1005 departingStaging = false; 1006 } 1007 // check for add engines in the train's route 1008 if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES && 1009 rl == getTrain().getSecondLegStartRouteLocation()) { 1010 RouteLocation rlEnd = getTrain().getSecondLegEndRouteLocation(); 1011 addEnginesIfNeed(route, rl, rlEnd); 1012 departingStaging = false; 1013 } 1014 if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES && 1015 rl == getTrain().getThirdLegStartRouteLocation()) { 1016 RouteLocation rlEnd = getTrain().getThirdLegEndRouteLocation(); 1017 addEnginesIfNeed(route, rl, rlEnd); 1018 departingStaging = false; 1019 } 1020 if (departingStaging) { 1021 continue; 1022 } 1023 determineMaxHpNeeded(rl); 1024 } 1025 } 1026 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, 1027 getTrain().getTrainTerminatesRouteLocation()); 1028 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 1029 addLine(THREE, BLANK_LINE); 1030 } 1031 1032 private void addEnginesIfNeed(Route route, RouteLocation rl, RouteLocation rlEnd) throws BuildFailedException { 1033 if (rlEnd == null) { 1034 rlEnd = getTrain().getTrainTerminatesRouteLocation(); 1035 } 1036 determineMaxHpNeeded(route, rl, rlEnd); 1037 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, rl, rlEnd); 1038 // reset for next leg of train's route 1039 _rlStart = rl; 1040 _rlNeedHp = null; 1041 _extraHpNeeded = 0; 1042 } 1043 1044 private void determineMaxHpNeeded(Route route, RouteLocation rl, RouteLocation rlEnd) { 1045 addLine(FIVE, BLANK_LINE); 1046 addLine(FIVE, Bundle.getMessage("buildAddEnginesHPT", rl.getName(), rlEnd.getName())); 1047 boolean foundStart = false; 1048 for (RouteLocation rlx : route.getLocationsBySequenceList()) { 1049 if (rlx == rl) { 1050 foundStart = true; 1051 } 1052 if (foundStart) { 1053 determineMaxHpNeeded(rlx); 1054 if (rlx == rlEnd) { 1055 break; 1056 } 1057 } 1058 } 1059 } 1060 1061 private void determineMaxHpNeeded(RouteLocation rl) { 1062 double weight = rl.getTrainWeight(); 1063 if (weight > 0) { 1064 double hptMinimum = Setup.getHorsePowerPerTon(); 1065 double hptGrade = (Control.speedHpt * rl.getGrade() / 12); 1066 double hp = getTrain().getTrainHorsePower(rl); 1067 double hpt = hp / weight; 1068 if (hptGrade > hptMinimum) { 1069 hptMinimum = hptGrade; 1070 } 1071 if (hptMinimum > hpt) { 1072 int addHp = (int) (hptMinimum * weight - hp); 1073 if (addHp > _extraHpNeeded) { 1074 _hpAvailable = (int) hp; 1075 _extraHpNeeded = addHp; 1076 _rlNeedHp = rl; 1077 } 1078 addLine(SEVEN, 1079 Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(), 1080 hpt, hptMinimum, rl.getName(), rl.getId())); 1081 addLine(FIVE, 1082 Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum)); 1083 } 1084 } 1085 } 1086 1087 protected void removeEngineFromTrain(Engine engine) { 1088 // replace lead engine? 1089 if (getTrain().getLeadEngine() == engine) { 1090 getTrain().setLeadEngine(null); 1091 } 1092 if (engine.getConsist() != null) { 1093 for (Engine e : engine.getConsist().getEngines()) { 1094 removeRollingStockFromTrain(e); 1095 } 1096 } else { 1097 removeRollingStockFromTrain(engine); 1098 } 1099 } 1100 1101 private static final Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class); 1102}