|
28 | 28 | package mekhq.campaign.stratcon; |
29 | 29 |
|
30 | 30 | import megamek.codeUtilities.ObjectUtility; |
| 31 | +import megamek.common.Entity; |
31 | 32 | import megamek.common.Minefield; |
32 | 33 | import megamek.common.TargetRoll; |
33 | 34 | import megamek.common.annotations.Nullable; |
@@ -827,6 +828,7 @@ private static void swapInPlayerUnits(StratconScenario scenario, Campaign campai |
827 | 828 | } |
828 | 829 | } |
829 | 830 | } |
| 831 | + |
830 | 832 | for (Unit unit : potentialUnits) { |
831 | 833 | if ((sft.getAllowedUnitType() == 11) && (!campaign.getCampaignOptions().isUseDropShips())) { |
832 | 834 | continue; |
@@ -2305,119 +2307,245 @@ private static boolean subElementsOrSelfDeployed(Force force, Campaign campaign) |
2305 | 2307 | } |
2306 | 2308 |
|
2307 | 2309 | /** |
2308 | | - * Returns a list of individual units eligible for deployment in scenarios run |
2309 | | - * by "Defend" lances |
| 2310 | + * Retrieves a list of units that are eligible for deployment in support of a Frontline force. |
| 2311 | + * |
| 2312 | + * <p>A unit is considered eligible if:</p> |
| 2313 | + * <ul> |
| 2314 | + * <li>It is valid (available, properly deployed, and of a suitable type i.e., conventional |
| 2315 | + * infantry or battle armor).</li> |
| 2316 | + * <li>The force to which it belongs is valid (not deployed, part of a combat team, and not in |
| 2317 | + * reserve).</li> |
| 2318 | + * </ul> |
2310 | 2319 | * |
2311 | | - * @return List of unit IDs. |
| 2320 | + * @param campaign The campaign instance holding the units and forces involved. |
| 2321 | + * @param currentScenario |
| 2322 | + * @return A list of {@code Unit} objects that meet the requirements for deployment in support |
| 2323 | + * of a Frontline force. |
2312 | 2324 | */ |
2313 | | - public static List<Unit> getEligibleDefensiveUnits(Campaign campaign) { |
2314 | | - List<Unit> retVal = new ArrayList<>(); |
2315 | | - |
2316 | | - for (Unit u : campaign.getUnits()) { |
2317 | | - // "defensive" units are infantry, battle armor and (Weisman help you) gun |
2318 | | - // emplacements |
2319 | | - // and also said unit should be intact/alive/etc |
2320 | | - boolean isEligibleInfantry = ((u.getEntity().getUnitType() == INFANTRY) |
2321 | | - || (u.getEntity().getUnitType() == BATTLE_ARMOR)) && !u.isUnmanned(); |
2322 | | - |
2323 | | - boolean isEligibleGunEmplacement = u.getEntity().getUnitType() == GUN_EMPLACEMENT; |
2324 | | - |
2325 | | - if ((isEligibleInfantry || isEligibleGunEmplacement) |
2326 | | - && !u.isDeployed() |
2327 | | - && !u.isMothballed() |
2328 | | - && (u.checkDeployment() == null) |
2329 | | - && !isUnitDeployedToStratCon(u)) { |
2330 | | - |
2331 | | - // this is a little inefficient, but probably there aren't too many active AtB |
2332 | | - // contracts at a time |
2333 | | - for (AtBContract contract : campaign.getActiveAtBContracts()) { |
2334 | | - if (contract.getStratconCampaignState().isForceDeployedHere(u.getForceId())) { |
2335 | | - continue; |
2336 | | - } |
2337 | | - } |
| 2325 | + public static List<Unit> getEligibleFrontlineUnits(Campaign campaign, StratconScenario currentScenario) { |
| 2326 | + List<Unit> defensiveUnits = new ArrayList<>(); |
| 2327 | + |
| 2328 | + // Retrieve the list of units from force 0 |
| 2329 | + Vector<UUID> unitIDs = campaign.getForce(0).getAllUnits(true); |
| 2330 | + |
| 2331 | + for (UUID unitId : unitIDs) { |
| 2332 | + Unit unit = campaign.getUnit(unitId); |
| 2333 | + |
| 2334 | + // Validate the unit |
| 2335 | + if (!isUnitValidForFrontlineDeployment(unit)) { |
| 2336 | + continue; |
| 2337 | + } |
2338 | 2338 |
|
2339 | | - retVal.add(u); |
| 2339 | + // Validate the force associated with the unit |
| 2340 | + if (!isForceEligible(unit, campaign, currentScenario)) { |
| 2341 | + continue; |
2340 | 2342 | } |
| 2343 | + |
| 2344 | + defensiveUnits.add(unit); |
2341 | 2345 | } |
2342 | 2346 |
|
2343 | | - return retVal; |
| 2347 | + return defensiveUnits; |
| 2348 | + } |
| 2349 | + |
| 2350 | + /** |
| 2351 | + * Checks if a unit is valid for deployment in support of a Frontline force. |
| 2352 | + * |
| 2353 | + * <p>A unit is considered valid if:</p> |
| 2354 | + * <ul> |
| 2355 | + * <li>The unit is not null.</li> |
| 2356 | + * <li>The unit is available for deployment.</li> |
| 2357 | + * <li>The unit's deployment checks return no errors.</li> |
| 2358 | + * <li>The unit's entity is of type conventional infantry or battle armor.</li> |
| 2359 | + * </ul> |
| 2360 | + * |
| 2361 | + * @param unit The {@code Unit} object to validate. |
| 2362 | + * @return {@code true} if the unit is valid; {@code false} otherwise. |
| 2363 | + */ |
| 2364 | + private static boolean isUnitValidForFrontlineDeployment(@Nullable Unit unit) { |
| 2365 | + if (unit == null) { |
| 2366 | + return false; |
| 2367 | + } |
| 2368 | + |
| 2369 | + if (!unit.isAvailable()) { |
| 2370 | + return false; |
| 2371 | + } |
| 2372 | + |
| 2373 | + if (unit.checkDeployment() != null) { |
| 2374 | + return false; |
| 2375 | + } |
| 2376 | + |
| 2377 | + Entity entity = unit.getEntity(); |
| 2378 | + return entity != null && (unit.isConventionalInfantry() || unit.isBattleArmor()); |
2344 | 2379 | } |
2345 | 2380 |
|
2346 | 2381 | /** |
2347 | | - * Returns a list of individual units eligible for deployment in scenarios that |
2348 | | - * result from the |
2349 | | - * lance leader having a leadership score |
| 2382 | + * Checks if the force associated with a unit is eligible for deployment in support of a Frontline |
| 2383 | + * force. |
2350 | 2384 | * |
2351 | | - * @return List of unit IDs. |
| 2385 | + * <p>A force is considered eligible if:</p> |
| 2386 | + * <ul> |
| 2387 | + * <li>The force is not null.</li> |
| 2388 | + * <li>The force has not already been deployed.</li> |
| 2389 | + * <li>The force is part of a combat team.</li> |
| 2390 | + * <li>The combat team is not assigned a reserve role.</li> |
| 2391 | + * </ul> |
| 2392 | + * |
| 2393 | + * @param unit The {@code Unit} whose associated force is being validated. |
| 2394 | + * @param campaign The {@code Campaign} object used to retrieve information about the force. |
| 2395 | + * @param currentScenario |
| 2396 | + * @return {@code true} if the associated force is eligible; {@code false} otherwise. |
2352 | 2397 | */ |
2353 | | - public static List<Unit> getEligibleLeadershipUnits(Campaign campaign, ArrayList<Integer> forceIDs, |
| 2398 | + private static boolean isForceEligible(Unit unit, Campaign campaign, StratconScenario currentScenario) { |
| 2399 | + int forceId = unit.getForceId(); |
| 2400 | + Force force = campaign.getForce(forceId); |
| 2401 | + |
| 2402 | + // If the force is deployed, skip; added check for insurance |
| 2403 | + if (force == null || force.isDeployed()) { |
| 2404 | + return false; |
| 2405 | + } |
| 2406 | + |
| 2407 | + // Check the associated combat team and its role |
| 2408 | + CombatTeam combatTeam = force.isCombatTeam() ? campaign.getCombatTeamsTable().get(forceId) : null; |
| 2409 | + |
| 2410 | + if (combatTeam == null) { |
| 2411 | + return false; |
| 2412 | + } |
| 2413 | + |
| 2414 | + if (combatTeam.getRole().isReserve()) { |
| 2415 | + return false; |
| 2416 | + } |
| 2417 | + |
| 2418 | + AtBContract forceContract = combatTeam.getContract(campaign); |
| 2419 | + AtBContract scenarioContract = currentScenario.getBackingContract(campaign); |
| 2420 | + |
| 2421 | + return forceContract.equals(scenarioContract); |
| 2422 | + } |
| 2423 | + |
| 2424 | + /** |
| 2425 | + * Retrieves a list of units that are eligible for leadership deployment. |
| 2426 | + * |
| 2427 | + * <p>A unit is considered eligible for leadership deployment if:</p> |
| 2428 | + * <ul> |
| 2429 | + * <li>There is sufficient leadership skill to justify leadership deployment.</li> |
| 2430 | + * <li>The total leadership budget (based on leadership skill) is greater than 0.</li> |
| 2431 | + * <li>The unit matches the general unit type of the primary force in the scenario.</li> |
| 2432 | + * <li>The unit's battle value is within the computed budget.</li> |
| 2433 | + * <li>The unit and its associated force are valid for deployment.</li> |
| 2434 | + * </ul> |
| 2435 | + * |
| 2436 | + * @param campaign The campaign instance holding the units and forces involved. |
| 2437 | + * @param currentScenario The current StratCon scenario being processed. |
| 2438 | + * @param leadershipSkill The leadership skill value used to calculate budget. |
| 2439 | + * @return A list of {@code Unit} objects eligible for deployment as leadership units. |
| 2440 | + */ |
| 2441 | + public static List<Unit> getEligibleLeadershipUnits(Campaign campaign, StratconScenario currentScenario, |
2354 | 2442 | int leadershipSkill) { |
2355 | | - List<Unit> eligibleUnits = new ArrayList<>(); |
| 2443 | + List<Integer> forceIds = currentScenario.getPrimaryForceIDs(); |
| 2444 | + List<Unit> leadershipUnits = new ArrayList<>(); |
2356 | 2445 |
|
2357 | 2446 | // If there is no leadership skill, we shouldn't continue |
2358 | 2447 | if (leadershipSkill <= 0) { |
2359 | | - return eligibleUnits; |
| 2448 | + return leadershipUnits; |
2360 | 2449 | } |
2361 | 2450 |
|
2362 | | - // The criteria are as follows: |
2363 | | - // - unit is eligible to be spawned on the scenario type |
2364 | | - // - unit has a lower BV than the BV budget granted from Leadership |
2365 | | - // Leadership budget is capped at 5 levels |
2366 | 2451 | int totalBudget = min(BASE_LEADERSHIP_BUDGET * leadershipSkill, BASE_LEADERSHIP_BUDGET * 5); |
2367 | 2452 |
|
2368 | | - int primaryUnitType = getPrimaryUnitType(campaign, forceIDs); |
| 2453 | + int primaryUnitType = getPrimaryUnitType(campaign, forceIds); |
2369 | 2454 |
|
2370 | 2455 | // If there are no units (somehow), we've no reason to continue |
2371 | 2456 | if (primaryUnitType == -1) { |
2372 | | - return eligibleUnits; |
| 2457 | + return leadershipUnits; |
2373 | 2458 | } |
2374 | 2459 |
|
2375 | 2460 | int generalUnitType = convertSpecificUnitTypeToGeneral(primaryUnitType); |
2376 | 2461 |
|
2377 | | - for (UUID unitId : campaign.getForce(0).getAllUnits(true)) { |
| 2462 | + |
| 2463 | + // Retrieve the list of units from force 0 |
| 2464 | + Vector<UUID> unitIDs = campaign.getForce(0).getAllUnits(true); |
| 2465 | + |
| 2466 | + for (UUID unitId : unitIDs) { |
2378 | 2467 | Unit unit = campaign.getUnit(unitId); |
2379 | | - if (unit == null) { |
| 2468 | + |
| 2469 | + // Validate the unit |
| 2470 | + if (!isUnitValidForLeadershipDeployment(unit, generalUnitType, totalBudget)) { |
2380 | 2471 | continue; |
2381 | 2472 | } |
2382 | 2473 |
|
2383 | | - // the general idea is that we want something that can be deployed to the scenario - |
2384 | | - // e.g., no infantry on air scenarios etc. |
2385 | | - boolean validUnitType = (forceCompositionMatchesDeclaredUnitType(unit.getEntity().getUnitType(), |
2386 | | - generalUnitType)); |
2387 | | - |
2388 | | - if (validUnitType |
2389 | | - && !unit.isDeployed() |
2390 | | - && !unit.isMothballed() |
2391 | | - && (unit.getEntity().calculateBattleValue(true, true) <= totalBudget) |
2392 | | - && (unit.checkDeployment() == null) |
2393 | | - && !isUnitDeployedToStratCon(unit)) { |
2394 | | - eligibleUnits.add(unit); |
| 2474 | + // Validate the force associated with the unit |
| 2475 | + if (!isForceEligible(unit, campaign, currentScenario)) { |
| 2476 | + continue; |
2395 | 2477 | } |
| 2478 | + |
| 2479 | + leadershipUnits.add(unit); |
| 2480 | + } |
| 2481 | + |
| 2482 | + return leadershipUnits; |
| 2483 | + } |
| 2484 | + |
| 2485 | + /** |
| 2486 | + * Checks if a unit is valid for leadership deployment. |
| 2487 | + * |
| 2488 | + * <p>A unit is considered valid for leadership deployment if:</p> |
| 2489 | + * <ul> |
| 2490 | + * <li>The unit is not null.</li> |
| 2491 | + * <li>The unit is available for deployment.</li> |
| 2492 | + * <li>The unit has no existing deployment records (i.e., not already deployed).</li> |
| 2493 | + * <li>The unit's entity is valid and has a battle value within the provided leadership budget.</li> |
| 2494 | + * <li>The unit matches the general unit type required for the deployment.</li> |
| 2495 | + * </ul> |
| 2496 | + * |
| 2497 | + * @param unit The {@code Unit} object to validate for leadership deployment. |
| 2498 | + * @param generalUnitType The general unit type required for this deployment. |
| 2499 | + * @param totalBudget The total battle value budget available for leadership deployment. |
| 2500 | + * @return {@code true} if the unit is valid for leadership deployment; {@code false} otherwise. |
| 2501 | + */ |
| 2502 | + private static boolean isUnitValidForLeadershipDeployment(@Nullable Unit unit, int generalUnitType, |
| 2503 | + int totalBudget) { |
| 2504 | + if (unit == null) { |
| 2505 | + return false; |
| 2506 | + } |
| 2507 | + |
| 2508 | + if (!unit.isAvailable()) { |
| 2509 | + return false; |
| 2510 | + } |
| 2511 | + |
| 2512 | + if (unit.checkDeployment() != null) { |
| 2513 | + return false; |
| 2514 | + } |
| 2515 | + |
| 2516 | + Entity entity = unit.getEntity(); |
| 2517 | + |
| 2518 | + if (entity == null) { |
| 2519 | + return false; |
| 2520 | + } |
| 2521 | + |
| 2522 | + if (entity.calculateBattleValue(true, true) > totalBudget) { |
| 2523 | + return false; |
2396 | 2524 | } |
2397 | 2525 |
|
2398 | | - return eligibleUnits; |
| 2526 | + return forceCompositionMatchesDeclaredUnitType(entity.getUnitType(), generalUnitType); |
2399 | 2527 | } |
2400 | 2528 |
|
2401 | 2529 | /** |
2402 | 2530 | * Check if the unit's force (if one exists) has been deployed to a StratCon |
2403 | 2531 | * track |
2404 | 2532 | */ |
2405 | | - public static boolean isUnitDeployedToStratCon(Unit u) { |
2406 | | - if (!u.getCampaign().getCampaignOptions().isUseStratCon()) { |
| 2533 | + public static boolean isUnitDeployedToStratCon(Unit unit) { |
| 2534 | + if (!unit.getCampaign().getCampaignOptions().isUseStratCon()) { |
2407 | 2535 | return false; |
2408 | 2536 | } |
2409 | 2537 |
|
2410 | 2538 | // this is a little inefficient, but probably there aren't too many active AtB |
2411 | 2539 | // contracts at a time |
2412 | | - return u.getCampaign().getActiveAtBContracts().stream() |
| 2540 | + return unit.getCampaign().getActiveAtBContracts().stream() |
2413 | 2541 | .anyMatch(contract -> (contract.getStratconCampaignState() != null) && |
2414 | | - contract.getStratconCampaignState().isForceDeployedHere(u.getForceId())); |
| 2542 | + contract.getStratconCampaignState().isForceDeployedHere(unit.getForceId())); |
2415 | 2543 | } |
2416 | 2544 |
|
2417 | 2545 | /** |
2418 | 2546 | * Calculates the majority unit type for the forces given the IDs. |
2419 | 2547 | */ |
2420 | | - private static int getPrimaryUnitType(Campaign campaign, ArrayList<Integer> forceIDs) { |
| 2548 | + private static int getPrimaryUnitType(Campaign campaign, List<Integer> forceIDs) { |
2421 | 2549 | Map<Integer, Integer> unitTypeBuckets = new TreeMap<>(); |
2422 | 2550 | int biggestBucketID = -1; |
2423 | 2551 | int biggestBucketCount = 0; |
|
0 commit comments