34
34
import static megamek .common .Compute .d6 ;
35
35
import static megamek .common .Compute .randomInt ;
36
36
import static megamek .common .UnitType .*;
37
+ import static megamek .common .WeaponType .CLASS_ARTILLERY ;
37
38
import static megamek .common .planetaryconditions .Atmosphere .THIN ;
38
39
import static megamek .common .planetaryconditions .Wind .TORNADO_F4 ;
39
40
import static mekhq .campaign .force .CombatTeam .getStandardForceSize ;
68
69
import megamek .common .containers .MunitionTree ;
69
70
import megamek .common .enums .Gender ;
70
71
import megamek .common .enums .SkillLevel ;
72
+ import megamek .common .equipment .WeaponMounted ;
71
73
import megamek .common .icons .Camouflage ;
72
74
import megamek .common .planetaryconditions .Atmosphere ;
73
75
import megamek .common .planetaryconditions .Wind ;
@@ -1062,6 +1064,12 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
1062
1064
if (unidentifiedThirdPartyPresent ) {
1063
1065
generatedForce .setCamouflage (AtBContract .pickRandomCamouflage (currentDate .getYear (), factionCode ));
1064
1066
}
1067
+
1068
+ boolean isDeployOffBoard = forceTemplate .getDeployOffboard ();
1069
+ if (isDeployOffBoard ) {
1070
+ validateOffBoardCapabilities (generatedEntities );
1071
+ }
1072
+
1065
1073
scenario .addBotForce (generatedForce , forceTemplate , campaign );
1066
1074
1067
1075
if (!contract .isBatchallAccepted ()) {
@@ -1232,6 +1240,46 @@ public static int generateForce(AtBDynamicScenario scenario, AtBContract contrac
1232
1240
return generatedLanceCount ;
1233
1241
}
1234
1242
1243
+ /**
1244
+ * Validates whether a force contains the required artillery weapons to deploy off-board, adjusting deployment
1245
+ * settings if necessary.
1246
+ *
1247
+ * <p>This method iterates through the generated entities in a force, checking if they have at least one weapon
1248
+ * classified as artillery. If any entity in the force doesn't have an artillery weapon, the entire force is moved
1249
+ * on-board, as forces cannot be split between on-board and off-board deployments.</p>
1250
+ *
1251
+ * @param generatedEntities A list of {@link Entity} objects representing the units generated for the force. Each
1252
+ * entity and its weapon list will be checked for artillery capability.
1253
+ *
1254
+ * @author Illiani
1255
+ * @since 0.50.05
1256
+ */
1257
+ public static void validateOffBoardCapabilities (List <Entity > generatedEntities ) {
1258
+ for (Entity entity : generatedEntities ) {
1259
+ boolean hasArtillery = false ;
1260
+ for (WeaponMounted weapon : entity .getTotalWeaponList ()) {
1261
+ WeaponType type = weapon .getType ();
1262
+
1263
+ if (type == null ) {
1264
+ continue ;
1265
+ }
1266
+
1267
+ int attackClass = type .getAtClass ();
1268
+
1269
+ if (attackClass == CLASS_ARTILLERY ) {
1270
+ hasArtillery = true ;
1271
+ break ;
1272
+ }
1273
+ }
1274
+
1275
+ if (!hasArtillery ) {
1276
+ logger .info ("{} was meant to deploy off board, but they don't have an artillery weapon." +
1277
+ " Moving them on board." , entity .getDisplayName ());
1278
+ entity .setOffBoard (0 , OffBoardDirection .NONE );
1279
+ }
1280
+ }
1281
+ }
1282
+
1235
1283
/**
1236
1284
* Retrieves the {@link StratconTrackState} associated with the given {@link AtBDynamicScenario}.
1237
1285
* <p>
@@ -2041,13 +2089,15 @@ public static Entity getInfantryEntity(UnitGeneratorParameters params, SkillLeve
2041
2089
MekSummary unitData = campaign .getUnitGenerator ().generate (params );
2042
2090
2043
2091
if (unitData == null ) {
2044
-
2045
2092
// If XCT troops were requested but none were found, generate without the role
2046
2093
if (useTempXCT && params .getMissionRoles ().contains (XCT )) {
2047
2094
noXCTParams = params .clone ();
2048
- noXCTParams .getMissionRoles ().remove (XCT );
2049
- unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2050
- temporaryXCT = true ;
2095
+
2096
+ if (noXCTParams != null ) {
2097
+ noXCTParams .getMissionRoles ().remove (XCT );
2098
+ unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2099
+ temporaryXCT = true ;
2100
+ }
2051
2101
}
2052
2102
if (unitData == null ) {
2053
2103
if (!params .getMissionRoles ().isEmpty ()) {
@@ -2212,16 +2262,21 @@ private static List<Entity> fillTransport(AtBScenario scenario, Entity transport
2212
2262
while (remainingCount > 0 ) {
2213
2263
2214
2264
// Set base random generation parameters
2265
+ logger .info ("Generating infantry bay for params {}" , params );
2215
2266
UnitGeneratorParameters newParams = params .clone ();
2216
- newParams .clearMovementModes ();
2217
- newParams .setWeightClass (AtBDynamicScenarioFactory .UNIT_WEIGHT_UNSPECIFIED );
2267
+ if (newParams == null ) {
2268
+ logger .info ("newParams is null" );
2269
+ } else {
2270
+ newParams .clearMovementModes ();
2271
+ newParams .setWeightClass (AtBDynamicScenarioFactory .UNIT_WEIGHT_UNSPECIFIED );
2272
+ }
2218
2273
2219
2274
Entity transportedUnit = null ;
2220
2275
Entity mechanizedBAUnit = null ;
2221
2276
2222
2277
// If a roll against the battle armor target number succeeds, try to generate a
2223
2278
// battle armor unit first
2224
- if (d6 (2 ) >= infantryToBAUpgradeTNs [params .getQuality ()]) {
2279
+ if (newParams != null && d6 (2 ) >= infantryToBAUpgradeTNs [params .getQuality ()]) {
2225
2280
newParams .setMissionRoles (requiredRoles .getOrDefault (BATTLE_ARMOR , new HashSet <>()));
2226
2281
transportedUnit = generateTransportedBAUnit (newParams , bayCapacity , skill , false , campaign );
2227
2282
@@ -2238,7 +2293,7 @@ private static List<Entity> fillTransport(AtBScenario scenario, Entity transport
2238
2293
2239
2294
// If a battle armor unit wasn't generated and conditions permit, try generating
2240
2295
// conventional infantry. Generate air assault infantry for VTOL transports.
2241
- if (transportedUnit == null && allowInfantry ) {
2296
+ if (newParams != null && transportedUnit == null && allowInfantry ) {
2242
2297
newParams .setMissionRoles (requiredRoles .getOrDefault (INFANTRY , new HashSet <>()));
2243
2298
if (transport .getUnitType () == VTOL && !newParams .getMissionRoles ().contains (XCT )) {
2244
2299
UnitGeneratorParameters paratrooperParams = newParams .clone ();
@@ -2303,10 +2358,15 @@ private static List<Entity> fillTransport(AtBScenario scenario, Entity transport
2303
2358
*
2304
2359
* @return Generated infantry unit, or null if one cannot be generated
2305
2360
*/
2306
- private static Entity generateTransportedInfantryUnit (UnitGeneratorParameters params , double bayCapacity ,
2361
+ private static @ Nullable Entity generateTransportedInfantryUnit (UnitGeneratorParameters params , double bayCapacity ,
2307
2362
SkillLevel skill , boolean useTempXCT , Campaign campaign ) {
2308
2363
2309
2364
UnitGeneratorParameters newParams = params .clone ();
2365
+ if (newParams == null ) {
2366
+ logger .warn ("newParams is null" );
2367
+ return null ;
2368
+ }
2369
+
2310
2370
newParams .setUnitType (INFANTRY );
2311
2371
MekSummary unitData ;
2312
2372
boolean temporaryXCT = false ;
@@ -2317,7 +2377,6 @@ private static Entity generateTransportedInfantryUnit(UnitGeneratorParameters pa
2317
2377
// which may
2318
2378
// include other types
2319
2379
if (bayCapacity <= IUnitGenerator .FOOT_PLATOON_INFANTRY_WEIGHT ) {
2320
-
2321
2380
if (newParams .getMissionRoles ().contains (PARATROOPER )) {
2322
2381
newParams .setMovementModes (IUnitGenerator .ALL_INFANTRY_MODES );
2323
2382
} else {
@@ -2331,9 +2390,12 @@ private static Entity generateTransportedInfantryUnit(UnitGeneratorParameters pa
2331
2390
// If XCT troops were requested but none were found, generate without the role
2332
2391
if (useTempXCT && newParams .getMissionRoles ().contains (XCT )) {
2333
2392
noXCTParams = newParams .clone ();
2334
- noXCTParams .getMissionRoles ().remove (XCT );
2335
- unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2336
- temporaryXCT = true ;
2393
+
2394
+ if (noXCTParams != null ) {
2395
+ noXCTParams .getMissionRoles ().remove (XCT );
2396
+ unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2397
+ temporaryXCT = true ;
2398
+ }
2337
2399
}
2338
2400
if (unitData == null ) {
2339
2401
return null ;
@@ -2362,9 +2424,12 @@ private static Entity generateTransportedInfantryUnit(UnitGeneratorParameters pa
2362
2424
// If XCT troops were requested but none were found, generate without the role
2363
2425
if (useTempXCT && newParams .getMissionRoles ().contains (XCT )) {
2364
2426
noXCTParams = newParams .clone ();
2365
- noXCTParams .getMissionRoles ().remove (XCT );
2366
- unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2367
- temporaryXCT = true ;
2427
+
2428
+ if (noXCTParams != null ) {
2429
+ noXCTParams .getMissionRoles ().remove (XCT );
2430
+ unitData = campaign .getUnitGenerator ().generate (noXCTParams );
2431
+ temporaryXCT = true ;
2432
+ }
2368
2433
}
2369
2434
if (unitData == null ) {
2370
2435
return null ;
@@ -2394,7 +2459,7 @@ private static Entity generateTransportedInfantryUnit(UnitGeneratorParameters pa
2394
2459
*
2395
2460
* @return Generated battle armor entity with crew, null if one cannot be generated
2396
2461
*/
2397
- private static Entity generateTransportedBAUnit (UnitGeneratorParameters params , double bayCapacity ,
2462
+ private static @ Nullable Entity generateTransportedBAUnit (UnitGeneratorParameters params , double bayCapacity ,
2398
2463
SkillLevel skill , boolean retryAsMechanized , Campaign campaign ) {
2399
2464
2400
2465
// Ensure a proposed non-mechanized carrier has enough bay space
@@ -2403,23 +2468,24 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params,
2403
2468
}
2404
2469
2405
2470
UnitGeneratorParameters newParams = params .clone ();
2406
- newParams .setUnitType (BATTLE_ARMOR );
2407
-
2408
- newParams .getMovementModes ().addAll (IUnitGenerator .ALL_BATTLE_ARMOR_MODES );
2409
-
2410
- // Set the parameters to filter out types that are too heavy for the provided
2411
- // bay space, or those that cannot use mechanized BA travel
2412
- if (bayCapacity != IUnitGenerator .NO_WEIGHT_LIMIT ) {
2413
- newParams .setFilter (inf -> inf .getTons () <= bayCapacity );
2414
- } else {
2415
- newParams .addMissionRole (MECHANIZED_BA );
2471
+ if (newParams != null ) {
2472
+ newParams .setUnitType (BATTLE_ARMOR );
2473
+ newParams .getMovementModes ().addAll (IUnitGenerator .ALL_BATTLE_ARMOR_MODES );
2474
+
2475
+ // Set the parameters to filter out types that are too heavy for the provided
2476
+ // bay space, or those that cannot use mechanized BA travel
2477
+ if (bayCapacity != IUnitGenerator .NO_WEIGHT_LIMIT ) {
2478
+ newParams .setFilter (inf -> inf .getTons () <= bayCapacity );
2479
+ } else {
2480
+ newParams .addMissionRole (MECHANIZED_BA );
2481
+ }
2416
2482
}
2417
2483
2418
2484
MekSummary unitData = campaign .getUnitGenerator ().generate (newParams );
2419
2485
2420
2486
// If generating for an internal bay fails, try again as mechanized if the flag is set
2421
2487
if (unitData == null ) {
2422
- if (bayCapacity != IUnitGenerator .NO_WEIGHT_LIMIT && retryAsMechanized ) {
2488
+ if (newParams != null && bayCapacity != IUnitGenerator .NO_WEIGHT_LIMIT && retryAsMechanized ) {
2423
2489
newParams .setFilter (null );
2424
2490
newParams .addMissionRole ((MECHANIZED_BA ));
2425
2491
unitData = campaign .getUnitGenerator ().generate (newParams );
@@ -2430,7 +2496,11 @@ private static Entity generateTransportedBAUnit(UnitGeneratorParameters params,
2430
2496
}
2431
2497
2432
2498
// Add an appropriate crew
2433
- return createEntityWithCrew (newParams .getFaction (), skill , campaign , unitData );
2499
+ if (newParams != null && unitData != null ) {
2500
+ return createEntityWithCrew (newParams .getFaction (), skill , campaign , unitData );
2501
+ } else {
2502
+ return null ;
2503
+ }
2434
2504
}
2435
2505
2436
2506
/**
0 commit comments