001package jmri.jmrit.operations.trains; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006import jmri.jmrit.operations.rollingstock.engines.Engine; 007import jmri.jmrit.operations.routes.Route; 008import jmri.jmrit.operations.routes.RouteLocation; 009import jmri.jmrit.operations.setup.Setup; 010 011/** 012 * Contains methods for engines when building a train. 013 * 014 * @author Daniel Boudreau Copyright (C) 2022 015 */ 016public class TrainBuilderEngines extends TrainBuilderBase { 017 018 /** 019 * Builds a list of possible engines for this train. 020 */ 021 protected void getAndRemoveEnginesFromList() { 022 _engineList = engineManager.getAvailableTrainList(_train); 023 024 // remove any locos that the train can't use 025 for (int indexEng = 0; indexEng < _engineList.size(); indexEng++) { 026 Engine engine = _engineList.get(indexEng); 027 // remove engines types that train does not service 028 if (!_train.isTypeNameAccepted(engine.getTypeName())) { 029 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineType", engine.toString(), 030 engine.getLocationName(), engine.getTrackName(), engine.getTypeName())); 031 _engineList.remove(indexEng--); 032 continue; 033 } 034 // remove engines with roads that train does not service 035 if (!_train.isLocoRoadNameAccepted(engine.getRoadName())) { 036 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineRoad", engine.toString(), 037 engine.getLocationName(), engine.getTrackName(), engine.getRoadName())); 038 _engineList.remove(indexEng--); 039 continue; 040 } 041 // remove engines with owners that train does not service 042 if (!_train.isOwnerNameAccepted(engine.getOwnerName())) { 043 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineOwner", engine.toString(), 044 engine.getLocationName(), engine.getTrackName(), engine.getOwnerName())); 045 _engineList.remove(indexEng--); 046 continue; 047 } 048 // remove engines with built dates that train does not service 049 if (!_train.isBuiltDateAccepted(engine.getBuilt())) { 050 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineBuilt", engine.toString(), 051 engine.getLocationName(), engine.getTrackName(), engine.getBuilt())); 052 _engineList.remove(indexEng--); 053 continue; 054 } 055 // remove engines that are out of service 056 if (engine.isOutOfService()) { 057 addLine(_buildReport, SEVEN, Bundle.getMessage("buildExcludeEngineOutOfService", engine.toString(), 058 engine.getLocationName(), engine.getTrackName())); 059 _engineList.remove(indexEng--); 060 continue; 061 } 062 // remove engines that aren't on the train's route 063 if (_train.getRoute().getLastLocationByName(engine.getLocationName()) == null) { 064 log.debug("removing engine ({}) location ({}) not serviced by train", engine.toString(), 065 engine.getLocationName()); 066 _engineList.remove(indexEng--); 067 continue; 068 } 069 } 070 } 071 072 /** 073 * Adds engines to the train starting at the first location in the train's 074 * route. Note that engines from staging are already part of the train. 075 * There can be up to two engine swaps in a train's route. 076 * 077 * @throws BuildFailedException if required engines can't be added to train. 078 */ 079 protected void addEnginesToTrain() throws BuildFailedException { 080 // allow up to two engine and caboose swaps in the train's route 081 RouteLocation engineTerminatesFirstLeg = _train.getTrainTerminatesRouteLocation(); 082 RouteLocation engineTerminatesSecondLeg = _train.getTrainTerminatesRouteLocation(); 083 084 // Adjust where the locos will terminate 085 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 086 _train.getSecondLegStartRouteLocation() != null) { 087 engineTerminatesFirstLeg = _train.getSecondLegStartRouteLocation(); 088 } 089 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 090 _train.getThirdLegStartRouteLocation() != null) { 091 engineTerminatesSecondLeg = _train.getThirdLegStartRouteLocation(); 092 // No engine or caboose change at first leg? 093 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) != Train.CHANGE_ENGINES) { 094 engineTerminatesFirstLeg = _train.getThirdLegStartRouteLocation(); 095 } 096 } 097 098 // load engines at the start of the route for this train 099 if (_train.getLeadEngine() == null) { 100 addLine(_buildReport, THREE, BLANK_LINE); 101 if (getEngines(_train.getNumberEngines(), _train.getEngineModel(), _train.getEngineRoad(), 102 _train.getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 103 _secondLeadEngine = _lastEngine; // when adding a caboose later 104 // in the route, no engine 105 // change 106 _thirdLeadEngine = _lastEngine; 107 } else if (getConsist(_train.getNumberEngines(), _train.getEngineModel(), _train.getEngineRoad(), 108 _train.getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 109 _secondLeadEngine = _lastEngine; // when adding a caboose later 110 // in the route, no engine 111 // change 112 _thirdLeadEngine = _lastEngine; 113 } else { 114 throw new BuildFailedException(Bundle.getMessage("buildErrorEngines", _train.getNumberEngines(), 115 _train.getTrainDepartsName(), engineTerminatesFirstLeg.getName())); 116 } 117 } 118 119 // First engine change in route? 120 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 121 addLine(_buildReport, THREE, BLANK_LINE); 122 addLine(_buildReport, THREE, 123 Bundle.getMessage("buildTrainEngineChange", _train.getSecondLegStartLocationName(), 124 _train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 125 _train.getSecondLegEngineRoad())); 126 if (getEngines(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 127 _train.getSecondLegEngineRoad(), _train.getSecondLegStartRouteLocation(), 128 engineTerminatesSecondLeg)) { 129 _secondLeadEngine = _lastEngine; 130 _thirdLeadEngine = _lastEngine; 131 } else if (getConsist(_train.getSecondLegNumberEngines(), _train.getSecondLegEngineModel(), 132 _train.getSecondLegEngineRoad(), _train.getSecondLegStartRouteLocation(), 133 engineTerminatesSecondLeg)) { 134 _secondLeadEngine = _lastEngine; 135 _thirdLeadEngine = _lastEngine; 136 } else { 137 throw new BuildFailedException( 138 Bundle.getMessage("buildErrorEngines", _train.getSecondLegNumberEngines(), 139 _train.getSecondLegStartRouteLocation(), engineTerminatesSecondLeg)); 140 } 141 } 142 // Second engine change in route? 143 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 144 addLine(_buildReport, THREE, BLANK_LINE); 145 addLine(_buildReport, THREE, 146 Bundle.getMessage("buildTrainEngineChange", _train.getThirdLegStartLocationName(), 147 _train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 148 _train.getThirdLegEngineRoad())); 149 if (getEngines(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 150 _train.getThirdLegEngineRoad(), _train.getThirdLegStartRouteLocation(), 151 _train.getTrainTerminatesRouteLocation())) { 152 _thirdLeadEngine = _lastEngine; 153 } else if (getConsist(_train.getThirdLegNumberEngines(), _train.getThirdLegEngineModel(), 154 _train.getThirdLegEngineRoad(), _train.getThirdLegStartRouteLocation(), 155 _train.getTrainTerminatesRouteLocation())) { 156 _thirdLeadEngine = _lastEngine; 157 } else { 158 throw new BuildFailedException( 159 Bundle.getMessage("buildErrorEngines", Integer.parseInt(_train.getThirdLegNumberEngines()), 160 _train.getThirdLegStartRouteLocation(), _train.getTrainTerminatesRouteLocation())); 161 } 162 } 163 if (!_train.getNumberEngines().equals("0") && 164 (!_train.isBuildConsistEnabled() || Setup.getHorsePowerPerTon() == 0)) { 165 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", _train.getName())); 166 } 167 } 168 169 /** 170 * Checks to see if the engine or consist assigned to the train has the 171 * appropriate HP. If the train's HP requirements are significantly higher 172 * or lower than the engine that was assigned, the program will search for a 173 * more appropriate engine or consist, and assign that engine or consist to 174 * the train. The HP calculation is based on a minimum train speed of 36 175 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 176 * horsepower required. Speed is fixed at 36 MPH. For example a 1% grade 177 * requires a minimum of 3 HPT. Disabled for trains departing staging. 178 * 179 * @throws BuildFailedException if coding error. 180 */ 181 protected void checkEngineHP() throws BuildFailedException { 182 if (Setup.getHorsePowerPerTon() != 0) { 183 if (_train.getNumberEngines().equals(Train.AUTO_HPT)) { 184 checkEngineHP(_train.getLeadEngine(), _train.getEngineModel(), _train.getEngineRoad()); // 1st 185 // leg 186 } 187 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 188 _train.getSecondLegNumberEngines().equals(Train.AUTO_HPT)) { 189 checkEngineHP(_secondLeadEngine, _train.getSecondLegEngineModel(), _train.getSecondLegEngineRoad()); 190 } 191 if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 192 _train.getThirdLegNumberEngines().equals(Train.AUTO_HPT)) { 193 checkEngineHP(_thirdLeadEngine, _train.getThirdLegEngineModel(), _train.getThirdLegEngineRoad()); 194 } 195 } 196 } 197 198 private void checkEngineHP(Engine leadEngine, String model, String road) throws BuildFailedException { 199 // code check 200 if (leadEngine == null) { 201 throw new BuildFailedException("ERROR coding issue, engine missing from checkEngineHP()"); 202 } 203 // departing staging? 204 if (leadEngine.getRouteLocation() == _train.getTrainDepartsRouteLocation() && _departStageTrack != null) { 205 return; 206 } 207 addLine(_buildReport, ONE, BLANK_LINE); 208 addLine(_buildReport, ONE, 209 Bundle.getMessage("buildDetermineHpNeeded", leadEngine.toString(), leadEngine.getLocationName(), 210 leadEngine.getDestinationName(), _train.getTrainHorsePower(leadEngine.getRouteLocation()), 211 Setup.getHorsePowerPerTon())); 212 // now determine the HP needed for this train 213 int hpNeeded = 0; 214 int hpAvailable = 0; 215 Route route = _train.getRoute(); 216 if (route != null) { 217 boolean helper = false; 218 boolean foundStart = false; 219 for (RouteLocation rl : route.getLocationsBySequenceList()) { 220 if (!foundStart && rl != leadEngine.getRouteLocation()) { 221 continue; 222 } 223 foundStart = true; 224 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 225 rl == _train.getSecondLegStartRouteLocation()) || 226 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 227 rl == _train.getThirdLegStartRouteLocation())) { 228 addLine(_buildReport, FIVE, 229 Bundle.getMessage("AddHelpersAt", rl.getName())); 230 helper = true; 231 } 232 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 233 rl == _train.getSecondLegEndRouteLocation()) || 234 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 235 rl == _train.getThirdLegEndRouteLocation())) { 236 addLine(_buildReport, FIVE, 237 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 238 helper = false; 239 } 240 if (helper) { 241 continue; // ignore HP needed when helpers are assigned to 242 // the train 243 } 244 // check for a change of engines in the train's route 245 if (rl == leadEngine.getRouteDestination()) { 246 log.debug("Remove loco ({}) at ({})", leadEngine.toString(), rl.getName()); 247 break; // done 248 } 249 if (_train.getTrainHorsePower(rl) > hpAvailable) 250 hpAvailable = _train.getTrainHorsePower(rl); 251 int weight = rl.getTrainWeight(); 252 int hpRequired = (int) ((36 * rl.getGrade() / 12) * weight); 253 if (hpRequired < Setup.getHorsePowerPerTon() * weight) 254 hpRequired = Setup.getHorsePowerPerTon() * weight; // minimum 255 // HPT 256 if (hpRequired > hpNeeded) { 257 addLine(_buildReport, SEVEN, 258 Bundle.getMessage("buildReportTrainHpNeeds", weight, _train.getNumberCarsInTrain(rl), 259 rl.getGrade(), rl.getName(), rl.getId(), hpRequired)); 260 hpNeeded = hpRequired; 261 } 262 } 263 } 264 if (hpNeeded > hpAvailable) { 265 addLine(_buildReport, ONE, 266 Bundle.getMessage("buildAssignedHpNotEnough", leadEngine.toString(), hpAvailable, hpNeeded)); 267 getNewEngine(hpNeeded, leadEngine, model, road); 268 } else if (hpAvailable > 2 * hpNeeded) { 269 addLine(_buildReport, ONE, 270 Bundle.getMessage("buildAssignedHpTooMuch", leadEngine.toString(), hpAvailable, hpNeeded)); 271 getNewEngine(hpNeeded, leadEngine, model, road); 272 } else { 273 log.debug("Keeping engine ({}) it meets the train's HP requirement", leadEngine.toString()); 274 } 275 } 276 277 /** 278 * Checks to see if additional engines are needed for the train based on the 279 * train's calculated tonnage. Minimum speed for the train is fixed at 36 280 * MPH. The formula HPT x 12 / % Grade = Speed, is used to determine the 281 * horsepower needed. For example a 1% grade requires a minimum of 3 HPT. 282 * 283 * @throws BuildFailedException if build failure 284 */ 285 protected void checkNumnberOfEnginesNeededHPT() throws BuildFailedException { 286 if (_train.getNumberEngines().equals("0") || 287 !_train.isBuildConsistEnabled() || 288 Setup.getHorsePowerPerTon() == 0) { 289 return; 290 } 291 addLine(_buildReport, ONE, BLANK_LINE); 292 addLine(_buildReport, ONE, Bundle.getMessage("buildDetermineNeeds", Setup.getHorsePowerPerTon())); 293 Route route = _train.getRoute(); 294 int hpAvailable = 0; 295 int extraHpNeeded = 0; 296 RouteLocation rlNeedHp = null; 297 RouteLocation rlStart = _train.getTrainDepartsRouteLocation(); 298 RouteLocation rlEnd = _train.getTrainTerminatesRouteLocation(); 299 if (route != null) { 300 boolean helper = false; 301 for (RouteLocation rl : route.getLocationsBySequenceList()) { 302 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 303 rl == _train.getSecondLegStartRouteLocation()) || 304 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 305 rl == _train.getThirdLegStartRouteLocation())) { 306 addLine(_buildReport, FIVE, Bundle.getMessage("AddHelpersAt", rl.getName())); 307 helper = true; 308 } 309 if ((_train.getSecondLegOptions() == Train.HELPER_ENGINES && 310 rl == _train.getSecondLegEndRouteLocation()) || 311 (_train.getThirdLegOptions() == Train.HELPER_ENGINES && 312 rl == _train.getThirdLegEndRouteLocation())) { 313 addLine(_buildReport, FIVE, 314 Bundle.getMessage("RemoveHelpersAt", rl.getName())); 315 helper = false; 316 } 317 if (helper) { 318 continue; 319 } 320 // check for a change of engines in the train's route 321 if (((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 322 rl == _train.getSecondLegStartRouteLocation()) || 323 ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 324 rl == _train.getThirdLegStartRouteLocation())) { 325 log.debug("Loco change at ({})", rl.getName()); 326 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rl); 327 addLine(_buildReport, THREE, BLANK_LINE); 328 // reset for next leg of train's route 329 rlStart = rl; 330 rlNeedHp = null; 331 extraHpNeeded = 0; 332 } 333 int weight = rl.getTrainWeight(); 334 if (weight > 0) { 335 double hptMinimum = Setup.getHorsePowerPerTon(); 336 double hptGrade = (36 * rl.getGrade() / 12); 337 int hp = _train.getTrainHorsePower(rl); 338 int hpt = hp / weight; 339 if (hptGrade > hptMinimum) { 340 hptMinimum = hptGrade; 341 } 342 if (hptMinimum > hpt) { 343 int addHp = (int) (hptMinimum * weight - hp); 344 if (addHp > extraHpNeeded) { 345 hpAvailable = hp; 346 extraHpNeeded = addHp; 347 rlNeedHp = rl; 348 } 349 addLine(_buildReport, SEVEN, Bundle.getMessage("buildAddLocosStatus", weight, hp, rl.getGrade(), 350 hpt, hptMinimum, rl.getName(), rl.getId())); 351 addLine(_buildReport, FIVE, 352 Bundle.getMessage("buildTrainRequiresAddHp", addHp, rl.getName(), hptMinimum)); 353 } 354 } 355 } 356 } 357 addEnginesBasedHPT(hpAvailable, extraHpNeeded, rlNeedHp, rlStart, rlEnd); 358 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDoneAssingEnginesTrain", _train.getName())); 359 addLine(_buildReport, THREE, BLANK_LINE); 360 } 361 362 private final static Logger log = LoggerFactory.getLogger(TrainBuilderEngines.class); 363}