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 RouteLocation engineTerminatesThirdLeg = getTrain().getTrainTerminatesRouteLocation(); 363 364 // Adjust where the locos will terminate 365 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 366 getTrain().getSecondLegStartRouteLocation() != null) { 367 engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation(); 368 } 369 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 370 getTrain().getThirdLegStartRouteLocation() != null) { 371 engineTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation(); 372 // No engine or caboose change at first leg? 373 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) { 374 engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation(); 375 } 376 } 377 // optionally set out added engines 378 if (getTrain().getSecondLegEndRouteLocation() != null && 379 ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 380 (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) { 381 engineTerminatesSecondLeg = getTrain().getSecondLegEndRouteLocation(); 382 } 383 if (getTrain().getThirdLegEndRouteLocation() != null && 384 ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 385 (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES)) { 386 engineTerminatesThirdLeg = getTrain().getThirdLegEndRouteLocation(); 387 } 388 389 if (getTrain().getLeadEngine() == null) { 390 // option to remove locos from the train 391 if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 392 getTrain().getSecondLegStartRouteLocation() != null) { 393 addLine(THREE, BLANK_LINE); 394 addLine(THREE, 395 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(), 396 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 397 getTrain().getSecondLegEngineRoad())); 398 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 399 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 400 getTrain().getSecondLegStartRouteLocation())) { 401 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 402 getTrain().getSecondLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 403 getTrain().getSecondLegStartRouteLocation())) { 404 } else { 405 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 406 getTrain().getSecondLegNumberEngines(), getTrain().getTrainDepartsName(), 407 getTrain().getSecondLegStartRouteLocation().getLocation().getName())); 408 } 409 } 410 if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES && 411 getTrain().getThirdLegStartRouteLocation() != null) { 412 addLine(THREE, BLANK_LINE); 413 addLine(THREE, 414 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(), 415 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 416 getTrain().getThirdLegEngineRoad())); 417 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 418 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 419 getTrain().getThirdLegStartRouteLocation())) { 420 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 421 getTrain().getThirdLegEngineRoad(), getTrain().getTrainDepartsRouteLocation(), 422 getTrain().getThirdLegStartRouteLocation())) { 423 } else { 424 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", 425 getTrain().getThirdLegNumberEngines(), getTrain().getTrainDepartsName(), 426 getTrain().getThirdLegStartRouteLocation().getLocation().getName())); 427 } 428 } 429 // load engines at the start of the route for this train 430 if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(), 431 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 432 // when adding a caboose later in the route, no engine change 433 _secondLeadEngine = _lastEngine; 434 _thirdLeadEngine = _lastEngine; 435 } else if (getConsist(getTrain().getNumberEngines(), getTrain().getEngineModel(), 436 getTrain().getEngineRoad(), 437 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 438 // when adding a caboose later in the route, no engine change 439 _secondLeadEngine = _lastEngine; 440 _thirdLeadEngine = _lastEngine; 441 } else { 442 addLine(THREE, BLANK_LINE); 443 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", getTrain().getNumberEngines(), 444 getTrain().getTrainDepartsName(), engineTerminatesFirstLeg.getName())); 445 } 446 } 447 448 // First engine change in route? 449 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 450 (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 451 (getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 452 addLine(THREE, BLANK_LINE); 453 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 454 addLine(THREE, 455 Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(), 456 getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 457 getTrain().getSecondLegEngineRoad())); 458 } else { 459 addLine(THREE, 460 Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(), 461 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 462 getTrain().getSecondLegEngineRoad())); 463 } 464 if (getEngines(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 465 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 466 engineTerminatesSecondLeg)) { 467 _secondLeadEngine = _lastEngine; 468 _thirdLeadEngine = _lastEngine; 469 } else if (getConsist(getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 470 getTrain().getSecondLegEngineRoad(), getTrain().getSecondLegStartRouteLocation(), 471 engineTerminatesSecondLeg)) { 472 _secondLeadEngine = _lastEngine; 473 _thirdLeadEngine = _lastEngine; 474 } else { 475 throw new BuildFailedException( 476 Bundle.getMessage("buildErrorEngines", getTrain().getSecondLegNumberEngines(), 477 getTrain().getSecondLegStartRouteLocation(), engineTerminatesSecondLeg)); 478 } 479 } 480 // Second engine change in route? 481 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 482 (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES || 483 (getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 484 addLine(THREE, BLANK_LINE); 485 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 486 addLine(THREE, 487 Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(), 488 getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 489 getTrain().getThirdLegEngineRoad())); 490 } else { 491 addLine(THREE, 492 Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(), 493 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 494 getTrain().getThirdLegEngineRoad())); 495 } 496 if (getEngines(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 497 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 498 engineTerminatesThirdLeg)) { 499 _thirdLeadEngine = _lastEngine; 500 } else if (getConsist(getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 501 getTrain().getThirdLegEngineRoad(), getTrain().getThirdLegStartRouteLocation(), 502 engineTerminatesThirdLeg)) { 503 _thirdLeadEngine = _lastEngine; 504 } else { 505 throw new BuildFailedException( 506 Bundle.getMessage("buildErrorEngines", Integer.parseInt(getTrain().getThirdLegNumberEngines()), 507 getTrain().getThirdLegStartRouteLocation(), 508 getTrain().getTrainTerminatesRouteLocation())); 509 } 510 } 511 if (!getTrain().getNumberEngines().equals("0") && 512 (!getTrain().isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) { 513 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 514 } 515 } 516 517 protected boolean getConsist(String reqNumEngines, String model, String road, RouteLocation rl, RouteLocation rld) 518 throws BuildFailedException { 519 if (reqNumEngines.equals(Train.AUTO_HPT)) { 520 for (int i = 2; i < Setup.getMaxNumberEngines(); i++) { 521 if (getEngines(Integer.toString(i), model, road, rl, rld)) { 522 return true; 523 } 524 } 525 } 526 return false; 527 } 528 529 protected void showEnginesByLocation() { 530 // show how many engines were found 531 addLine(SEVEN, BLANK_LINE); 532 addLine(ONE, 533 Bundle.getMessage("buildFoundLocos", Integer.toString(getEngineList().size()), getTrain().getName())); 534 535 // only show engines once using the train's route 536 List<String> locationNames = new ArrayList<>(); 537 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 538 if (locationNames.contains(rl.getName())) { 539 continue; 540 } 541 locationNames.add(rl.getName()); 542 int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getEngineList())); 543 if (rl.getLocation().isStaging()) { 544 addLine(FIVE, 545 Bundle.getMessage("buildLocosInStaging", count, rl.getName())); 546 } else { 547 addLine(FIVE, 548 Bundle.getMessage("buildLocosAtLocation", count, rl.getName())); 549 } 550 for (Engine engine : getEngineList()) { 551 if (engine.getLocationName().equals(rl.getName())) { 552 addLine(SEVEN, 553 Bundle.getMessage("buildLocoAtLocWithMoves", engine.toString(), engine.getTypeName(), 554 engine.getModel(), engine.getLocationName(), engine.getTrackName(), 555 engine.getMoves())); 556 } 557 } 558 addLine(SEVEN, BLANK_LINE); 559 } 560 } 561 562 /** 563 * Adds engines to the train if needed based on HPT. Note that the engine 564 * additional weight isn't considered in this method so HP requirements can 565 * be lower compared to the original calculation which did include the 566 * weight of the engines. 567 * 568 * @param hpAvailable the engine hp already assigned to the train for this 569 * leg 570 * @param extraHpNeeded the additional hp needed 571 * @param rlNeedHp where in the route the additional hp is needed 572 * @param rl the start of the leg 573 * @param rld the end of the leg 574 * @throws BuildFailedException if unable to add engines to train 575 */ 576 protected void addEnginesBasedHPT(int hpAvailable, int extraHpNeeded, RouteLocation rlNeedHp, RouteLocation rl, 577 RouteLocation rld) throws BuildFailedException { 578 if (rlNeedHp == null) { 579 return; 580 } 581 582 // determine how many locos have already been assigned to the train 583 int numberLocos = getTrain().getNumberEngines(rl); 584 585 addLine(ONE, BLANK_LINE); 586 addLine(ONE, Bundle.getMessage("buildTrainReqExtraHp", extraHpNeeded, rlNeedHp.getName(), 587 rld.getName(), numberLocos)); 588 589 // determine engine model and road 590 String model = getTrain().getEngineModel(); 591 String road = getTrain().getEngineRoad(); 592 if (rl == getTrain().getSecondLegStartRouteLocation() && 593 ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 594 (getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) { 595 model = getTrain().getSecondLegEngineModel(); 596 road = getTrain().getSecondLegEngineRoad(); 597 } else if (rl == getTrain().getThirdLegStartRouteLocation() && 598 ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES || 599 (getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES)) { 600 model = getTrain().getThirdLegEngineModel(); 601 road = getTrain().getThirdLegEngineRoad(); 602 } 603 604 while (numberLocos < Setup.getMaxNumberEngines()) { 605 // if no engines assigned, can't use B unit as first engine 606 if (!getEngines("1", model, road, rl, rld, numberLocos > 0)) { 607 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", Bundle.getMessage("additional"), 608 rl.getName(), rld.getName())); 609 } 610 numberLocos++; 611 int currentHp = getTrain().getTrainHorsePower(rlNeedHp); 612 if (currentHp >= hpAvailable + extraHpNeeded) { 613 break; // done 614 } 615 if (numberLocos < Setup.getMaxNumberEngines()) { 616 addLine(FIVE, BLANK_LINE); 617 addLine(THREE, 618 Bundle.getMessage("buildContinueAddLocos", (hpAvailable + extraHpNeeded - currentHp), 619 rlNeedHp.getName(), rld.getName(), numberLocos, currentHp)); 620 } else { 621 addLine(FIVE, 622 Bundle.getMessage("buildMaxNumberLocoAssigned", Setup.getMaxNumberEngines())); 623 } 624 } 625 } 626 627 /** 628 * Checks to see if the engine or consist assigned to the train has the 629 * appropriate HP. If the train's HP requirements are significantly higher 630 * or lower than the engine that was assigned, the program will search for a 631 * more appropriate engine or consist, and assign that engine or consist to 632 * the train. The HP calculation is based on a minimum train speed of 36 633 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 634 * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade 635 * requires a minimum of 3 HPT. Disabled for trains departing staging. 636 * 637 * @throws BuildFailedException if coding error. 638 */ 639 protected void checkEngineHP() throws BuildFailedException { 640 if (Setup.getHorsePowerPerTon() != 0) { 641 if (getTrain().getNumberEngines().equals(Train.AUTO_HPT)) { 642 checkEngineHP(getTrain().getLeadEngine(), getTrain().getEngineModel(), getTrain().getEngineRoad()); 643 } 644 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 645 getTrain().getSecondLegNumberEngines().equals(Train.AUTO_HPT)) { 646 checkEngineHP(_secondLeadEngine, getTrain().getSecondLegEngineModel(), 647 getTrain().getSecondLegEngineRoad()); 648 } 649 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 650 getTrain().getThirdLegNumberEngines().equals(Train.AUTO_HPT)) { 651 checkEngineHP(_thirdLeadEngine, getTrain().getThirdLegEngineModel(), 652 getTrain().getThirdLegEngineRoad()); 653 } 654 } 655 } 656 657 private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException { 658 // code check 659 if (leadEngine == null) { 660 throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()"); 661 } 662 // departing staging? 663 if (leadEngine.getRouteLocation() == getTrain().getTrainDepartsRouteLocation() && 664 getTrain().isDepartingStaging()) { 665 return; 666 } 667 addLine(ONE, BLANK_LINE); 668 addLine(ONE, 669 Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(), 670 leadEngine.getDestinationName(), getTrain().getTrainHorsePower(leadEngine.getRouteLocation()), 671 Setup.getHorsePowerPerTon())); 672 // now determine the HP needed for this train 673 double hpNeeded = 0; 674 int hpAvailable = 0; 675 Route route = getTrain().getRoute(); 676 if (route != null) { 677 boolean helper = false; 678 boolean foundStart = false; 679 for (RouteLocation rl : route.getLocationsBySequenceList()) { 680 if (!foundStart && rl != leadEngine.getRouteLocation()) { 681 continue; 682 } 683 foundStart = true; 684 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 685 rl == getTrain().getSecondLegStartRouteLocation()) || 686 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 687 rl == getTrain().getThirdLegStartRouteLocation())) { 688 addLine(FIVE, 689 Bundle.getMessage("AddHelpersAt", rl.getName())); 690 helper = true; 691 } 692 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 693 rl == getTrain().getSecondLegEndRouteLocation()) || 694 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 695 rl == getTrain().getThirdLegEndRouteLocation())) { 696 addLine(FIVE, 697 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 698 helper = false; 699 } 700 if (helper) { 701 continue; // ignore HP needed when helpers are assigned to 702 // the train 703 } 704 // check for a change of engines in the train's route 705 if (rl == leadEngine.getRouteDestination()) { 706 log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName()); 707 break; // done 708 } 709 if (getTrain().getTrainHorsePower(rl) > hpAvailable) 710 hpAvailable = getTrain().getTrainHorsePower(rl); 711 int weight = rl.getTrainWeight(); 712 double hpRequired = (Control.speedHpt * rl.getGrade() / 12) * weight; 713 if (hpRequired < Setup.getHorsePowerPerTon() * weight) 714 hpRequired = Setup.getHorsePowerPerTon() * weight; // min HPT 715 if (hpRequired > hpNeeded) { 716 addLine(SEVEN, 717 Bundle.getMessage("buildReportTrainHpNeeds", weight, getTrain().getNumberCarsInTrain(rl), 718 rl.getGrade(), rl.getName(), rl.getId(), hpRequired)); 719 hpNeeded = hpRequired; 720 } 721 } 722 } 723 if (hpNeeded > hpAvailable) { 724 addLine(ONE, 725 Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded)); 726 getNewEngine((int) hpNeeded, leadEngine, model, road); 727 } else if (hpAvailable > 2 * hpNeeded) { 728 addLine(ONE, 729 Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded)); 730 getNewEngine((int) hpNeeded, leadEngine, model, road); 731 } else { 732 log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString()); 733 } 734 } 735 736 /** 737 * Removes engine from train and attempts to replace it with engine or 738 * consist that meets the HP requirements of the train. 739 * 740 * @param hpNeeded How much hp is needed 741 * @param leadEngine The lead engine for this leg 742 * @param model The engine's model 743 * @param road The engine's road 744 * @throws BuildFailedException if new engine not found 745 */ 746 protected void getNewEngine(int hpNeeded, Engine leadEngine, String model, String road) 747 throws BuildFailedException { 748 // save lead engine's rl, and rld 749 RouteLocation rl = leadEngine.getRouteLocation(); 750 RouteLocation rld = leadEngine.getRouteDestination(); 751 removeEngineFromTrain(leadEngine); 752 getEngineList().add(0, leadEngine); // put engine back into the pool 753 if (hpNeeded < 50) { 754 hpNeeded = 50; // the minimum HP 755 } 756 int hpMax = hpNeeded; 757 // largest single engine HP known today is less than 15,000. 758 // high end modern diesel locos approximately 5000 HP. 759 // 100 car train at 100 tons per car and 2 HPT requires 20,000 HP. 760 // will assign consisted engines to train. 761 boolean foundLoco = false; 762 List<Engine> rejectedLocos = new ArrayList<>(); 763 hpLoop: while (hpMax < 20000) { 764 hpMax += hpNeeded / 2; // start off looking for an engine with no 765 // more than 50% extra HP 766 log.debug("Max hp {}", hpMax); 767 for (Engine engine : getEngineList()) { 768 if (rejectedLocos.contains(engine)) { 769 continue; 770 } 771 // don't use non lead locos in a consist 772 if (engine.getConsist() != null && !engine.isLead()) { 773 continue; 774 } 775 if (engine.getLocation() != rl.getLocation()) { 776 continue; 777 } 778 if (!model.equals(Train.NONE) && !engine.getModel().equals(model)) { 779 continue; 780 } 781 if (!road.equals(Train.NONE) && !engine.getRoadName().equals(road) || 782 road.equals(Train.NONE) && !getTrain().isLocoRoadNameAccepted(engine.getRoadName())) { 783 continue; 784 } 785 int engineHp = engine.getHpInteger(); 786 if (engine.getConsist() != null) { 787 for (Engine e : engine.getConsist().getEngines()) { 788 if (e != engine) { 789 engineHp = engineHp + e.getHpInteger(); 790 } 791 } 792 } 793 if (engineHp > hpNeeded && engineHp <= hpMax) { 794 addLine(FIVE, 795 Bundle.getMessage("buildLocoHasRequiredHp", engine.toString(), engineHp, hpNeeded)); 796 if (setEngineDestination(engine, rl, rld)) { 797 foundLoco = true; 798 break hpLoop; 799 } else { 800 rejectedLocos.add(engine); 801 } 802 } 803 } 804 } 805 if (!foundLoco && !getTrain().isBuildConsistEnabled()) { 806 throw new BuildFailedException(Bundle.getMessage("buildErrorEngHp", rl.getLocation().getName())); 807 } 808 } 809 810 /** 811 * Sets the destination track for an engine and assigns it to the train. 812 * 813 * @param engine The engine to be added to train 814 * @param rl Departure route location 815 * @param rld Destination route location 816 * @return true if destination track found and set 817 */ 818 protected boolean setEngineDestination(Engine engine, RouteLocation rl, RouteLocation rld) { 819 // engine to staging? 820 if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) { 821 String status = 822 engine.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 823 if (status.equals(Track.OKAY)) { 824 addEngineToTrain(engine, rl, rld, getTerminateStagingTrack()); 825 return true; // done 826 } else { 827 addLine(SEVEN, 828 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 829 getTerminateStagingTrack().getTrackTypeName(), 830 getTerminateStagingTrack().getLocation().getName(), 831 getTerminateStagingTrack().getName(), status)); 832 } 833 } else { 834 // find a destination track for this engine 835 Location destination = rld.getLocation(); 836 List<Track> destTracks = destination.getTracksByMoves(null); 837 if (destTracks.size() == 0) { 838 addLine(THREE, Bundle.getMessage("buildNoTracksAtDestination", rld.getName())); 839 } 840 for (Track track : destTracks) { 841 if (!checkDropTrainDirection(engine, rld, track)) { 842 continue; 843 } 844 if (!checkTrainCanDrop(engine, track)) { 845 continue; 846 } 847 String status = engine.checkDestination(destination, track); 848 if (status.equals(Track.OKAY)) { 849 addLine(FIVE, 850 Bundle.getMessage("buildEngineCanDrop", engine.toString(), 851 track.getTrackTypeName(), 852 track.getLocation().getName(), track.getName())); 853 addEngineToTrain(engine, rl, rld, track); 854 return true; 855 } else { 856 addLine(SEVEN, 857 Bundle.getMessage("buildCanNotDropEngineToTrack", engine.toString(), 858 track.getTrackTypeName(), 859 track.getLocation().getName(), track.getName(), status)); 860 } 861 } 862 addLine(FIVE, 863 Bundle.getMessage("buildCanNotDropEngToDest", engine.toString(), rld.getName())); 864 } 865 return false; // not able to set loco's destination 866 } 867 868 /** 869 * Adds an engine to the train. 870 * 871 * @param engine the engine being added to the train 872 * @param rl where in the train's route to pick up the engine 873 * @param rld where in the train's route to set out the engine 874 * @param track the destination track for this engine 875 */ 876 private void addEngineToTrain(Engine engine, RouteLocation rl, RouteLocation rld, Track track) { 877 _lastEngine = engine; // needed in case there's a engine change in the 878 // train's route 879 engine = checkQuickServiceArrival(engine, rld, track); 880 if (getTrain().getLeadEngine() == null) { 881 getTrain().setLeadEngine(engine); // load lead engine 882 } 883 addLine(ONE, Bundle.getMessage("buildEngineAssigned", engine.toString(), rl.getName(), 884 rld.getName(), track.getName())); 885 engine.setDestination(track.getLocation(), track, Engine.FORCE); 886 int length = engine.getTotalLength(); 887 int weightTons = engine.getAdjustedWeightTons(); 888 // engine in consist? 889 if (engine.getConsist() != null) { 890 length = engine.getConsist().getTotalLength(); 891 weightTons = engine.getConsist().getAdjustedWeightTons(); 892 for (Engine cEngine : engine.getConsist().getEngines()) { 893 if (cEngine != engine) { 894 addLine(ONE, Bundle.getMessage("buildEngineAssigned", cEngine.toString(), 895 rl.getName(), rld.getName(), track.getName())); 896 cEngine.setTrain(getTrain()); 897 cEngine.setRouteLocation(rl); 898 cEngine.setRouteDestination(rld); 899 cEngine.setDestination(track.getLocation(), track, RollingStock.FORCE); // force 900 } 901 } 902 } 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 // for timing, use arrival times for the train that is building 944 // other trains will use their departure time, loaded when creating the Manifest 945 String expectedArrivalTime = getTrain().getExpectedArrivalTime(rld, true); 946 cloneEng.setSetoutTime(expectedArrivalTime); 947 // remember where in the route the car was delivered 948 engine.setRouteDestination(rld); 949 return cloneEng; // return clone 950 } 951 952 int _hpAvailable = 0; 953 int _extraHpNeeded = 0; 954 RouteLocation _rlNeedHp; 955 RouteLocation _rlStart; 956 957 /** 958 * Checks to see if additional engines are needed for the train based on the 959 * train's calculated tonnage. Minimum speed for the train is fixed at 36 960 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 961 * horsepower needed. For example a 1% grade requires a minimum of 3 HPT. 962 * Ignored when departing staging. When using helpers, no additional 963 * engines. 964 * 965 * @throws BuildFailedException if build failure 966 */ 967 protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException { 968 if (!getTrain().isBuildConsistEnabled() || 969 Setup.getHorsePowerPerTon() == 0) { 970 return; 971 } 972 addLine(ONE, BLANK_LINE); 973 addLine(ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon())); 974 Route route = getTrain().getRoute(); 975 _rlStart = getTrain().getTrainDepartsRouteLocation(); 976 boolean departingStaging = getTrain().isDepartingStaging(); 977 if (route != null) { 978 boolean helper = false; 979 for (RouteLocation rl : route.getLocationsBySequenceList()) { 980 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 981 rl == getTrain().getSecondLegStartRouteLocation()) || 982 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 983 rl == getTrain().getThirdLegStartRouteLocation())) { 984 addLine(FIVE, Bundle.getMessage("AddHelpersAt", rl.getName())); 985 helper = true; 986 } 987 if ((getTrain().getSecondLegOptions() == Train.HELPER_ENGINES && 988 rl == getTrain().getSecondLegEndRouteLocation()) || 989 (getTrain().getThirdLegOptions() == Train.HELPER_ENGINES && 990 rl == getTrain().getThirdLegEndRouteLocation())) { 991 addLine(FIVE, 992 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 993 helper = false; 994 } 995 if (helper) { 996 continue; 997 } 998 // check for a change of engines in the train's route 999 if (((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 1000 rl == getTrain().getSecondLegStartRouteLocation()) || 1001 ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 1002 rl == getTrain().getThirdLegStartRouteLocation())) { 1003 log.debug("Loco change at ({})", rl.getName()); 1004 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, rl); 1005 // reset for next leg of train's route 1006 _rlStart = rl; 1007 _rlNeedHp = null; 1008 _extraHpNeeded = 0; 1009 departingStaging = false; 1010 } 1011 // check for add engines in the train's route 1012 if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES && 1013 rl == getTrain().getSecondLegStartRouteLocation()) { 1014 RouteLocation rlEnd = getTrain().getSecondLegEndRouteLocation(); 1015 addEnginesIfNeed(route, rl, rlEnd); 1016 departingStaging = false; 1017 } 1018 if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES && 1019 rl == getTrain().getThirdLegStartRouteLocation()) { 1020 RouteLocation rlEnd = getTrain().getThirdLegEndRouteLocation(); 1021 addEnginesIfNeed(route, rl, rlEnd); 1022 departingStaging = false; 1023 } 1024 if (departingStaging) { 1025 continue; 1026 } 1027 determineMaxHpNeeded(rl); 1028 } 1029 } 1030 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, _rlStart, 1031 getTrain().getTrainTerminatesRouteLocation()); 1032 addLine(SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", getTrain().getName())); 1033 addLine(THREE, BLANK_LINE); 1034 } 1035 1036 private void addEnginesIfNeed(Route route, RouteLocation rl, RouteLocation rlEnd) throws BuildFailedException { 1037 if (rlEnd == null) { 1038 rlEnd = getTrain().getTrainTerminatesRouteLocation(); 1039 } 1040 determineMaxHpNeeded(route, rl, rlEnd); 1041 addEnginesBasedHPT(_hpAvailable, _extraHpNeeded, _rlNeedHp, rl, rlEnd); 1042 // reset for next leg of train's route 1043 _rlStart = rl; 1044 _rlNeedHp = null; 1045 _extraHpNeeded = 0; 1046 } 1047 1048 private void determineMaxHpNeeded(Route route, RouteLocation rl, RouteLocation rlEnd) { 1049 addLine(FIVE, BLANK_LINE); 1050 addLine(FIVE, Bundle.getMessage("buildAddEnginesHPT", rl.getName(), rlEnd.getName())); 1051 boolean foundStart = false; 1052 for (RouteLocation rlx : route.getLocationsBySequenceList()) { 1053 if (rlx == rl) { 1054 foundStart = true; 1055 } 1056 if (foundStart) { 1057 determineMaxHpNeeded(rlx); 1058 if (rlx == rlEnd) { 1059 break; 1060 } 1061 } 1062 } 1063 } 1064 1065 private void determineMaxHpNeeded(RouteLocation rl) { 1066 double weight = rl.getTrainWeight(); 1067 if (weight > 0) { 1068 double hptMinimum = Setup.getHorsePowerPerTon(); 1069 double hptGrade = (Control.speedHpt * rl.getGrade() / 12); 1070 double hp = getTrain().getTrainHorsePower(rl); 1071 double hpt = hp / weight; 1072 if (hptGrade > hptMinimum) { 1073 hptMinimum = hptGrade; 1074 } 1075 if (hptMinimum > hpt) { 1076 int addHp = (int) (hptMinimum * weight - hp); 1077 if (addHp > _extraHpNeeded) { 1078 _hpAvailable = (int) hp; 1079 _extraHpNeeded = addHp; 1080 _rlNeedHp = rl; 1081 } 1082 addLine(SEVEN, 1083 Bundle.getMessage("buildAddLocosStatus", weight, hp, Control.speedHpt, rl.getGrade(), 1084 hpt, hptMinimum, rl.getName(), rl.getId())); 1085 addLine(FIVE, 1086 Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum)); 1087 } 1088 } 1089 } 1090 1091 protected void removeEngineFromTrain(Engine engine) { 1092 // replace lead engine? 1093 if (getTrain().getLeadEngine() == engine) { 1094 getTrain().setLeadEngine(null); 1095 } 1096 if (engine.getConsist() != null) { 1097 for (Engine e : engine.getConsist().getEngines()) { 1098 removeRollingStockFromTrain(e); 1099 } 1100 } else { 1101 removeRollingStockFromTrain(engine); 1102 } 1103 } 1104 1105 private final static Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class); 1106}