From 628c3b31b5b5ccf5cedca0bd525867c37b69039d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 5 Jul 2025 23:05:20 -0500 Subject: [PATCH] Improvement: Updated Faction Starting Location Logic for Mercenary and Pirate Campaigns --- MekHQ/src/mekhq/campaign/Campaign.java | 176 +++++++++++++++++-------- 1 file changed, 124 insertions(+), 52 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index a8b068934a..81b2e6e1ed 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -251,8 +251,8 @@ import mekhq.campaign.universe.factionStanding.FactionAccoladeLevel; import mekhq.campaign.universe.factionStanding.FactionCensureEvent; import mekhq.campaign.universe.factionStanding.FactionCensureLevel; -import mekhq.campaign.universe.factionStanding.FactionStandingUltimatum; import mekhq.campaign.universe.factionStanding.FactionStandingJudgmentType; +import mekhq.campaign.universe.factionStanding.FactionStandingUltimatum; import mekhq.campaign.universe.factionStanding.FactionStandingUtilities; import mekhq.campaign.universe.factionStanding.FactionStandings; import mekhq.campaign.universe.factionStanding.PerformBatchall; @@ -6607,6 +6607,21 @@ public boolean isPirateCampaign() { return faction.getShortName().equals(PIRATE_FACTION_CODE); } + /** + * Determines whether the current campaign is a mercenary campaign. + * + *

This method checks if the faction associated with the campaign is Mercenary, returning {@code true} if it is, + * and {@code false} otherwise.

+ * + * @return {@code true} if the campaign is Mercenary, {@code false} otherwise. + * + * @author Illiani + * @since 0.50.07 + */ + public boolean isMercenaryCampaign() { + return faction.getShortName().equals(MERCENARY_FACTION_CODE); + } + public void setFaction(final Faction faction) { setFactionDirect(faction); updateTechFactionCode(); @@ -10275,79 +10290,136 @@ public List getAllCombatEntities() { return units; } - /** - * Determines the appropriate starting planet for a new campaign. + * Determines the appropriate starting planet for a new campaign based on campaign type, faction, and various + * fallback scenarios. * - *

This method first attempts to obtain the starting planet from the campaign's primary method. If no valid - * system is found, or if the result is "Terra" (which is the default value used when no system is set), it selects - * a fallback faction's starting planet using the following logic:

+ *

This method first checks if the campaign is classified as a mercenary or pirate campaign. If so, it + * delegates responsibility to {@link #getMercenaryOrPirateStartingPlanet(Factions, String)}, which implements + * special logic to handle those campaign types.

* - * + *

For all other campaign types, it uses the current campaign's faction to attempt to retrieve that faction’s + * canonical starting system for the current game date. If no valid system can be found (due to, for example, the + * faction not having a valid capital), the logic falls back to a default faction’s starting planet, and, if + * necessary, ultimately falls back to the planet Terra as a default universal location.

* - *

The returned result is always the system's primary planet.

+ *

The method also includes special handling for Clan campaigns: if the fallback logic would result in the + * campaign starting on Terra but the campaign is clan-based, it attempts to relocate the starting planet to + * Strana Mechty.

* - * @return the {@link Planet} object representing the new campaign's starting planet + * @return the {@link Planet} instance where the campaign should start * - * @author Illiani * @since 0.50.07 + * @author Illiani */ public Planet getNewCampaignStartingPlanet() { Factions factions = Factions.getInstance(); - PlanetarySystem startingSystem = faction.getStartingPlanet(this, currentDay); + final String TERRA_ID = "Terra"; + final String CLAN_CODE = "CLAN"; + + Faction startingFaction; + PlanetarySystem startingSystem; + + if (isMercenaryCampaign() || isPirateCampaign()) { + return getMercenaryOrPirateStartingPlanet(factions, TERRA_ID); + } + + // Default for non-merc/pirate campaigns + startingFaction = faction; + startingSystem = startingFaction.getStartingPlanet(this, currentDay); + // Fallback if the system is unavailable if (startingSystem == null) { - Faction fallbackFaction = factions.getDefaultFaction(); - startingSystem = fallbackFaction.getStartingPlanet(this, currentDay); - } else if (startingSystem.getId().equalsIgnoreCase("Terra")) { - Faction fallbackFaction = factions.getDefaultFaction(); - - if (faction.getShortName().equalsIgnoreCase("PIR")) { - List pirateFactions = new ArrayList<>(); - for (Faction activeFaction : factions.getActiveFactions(currentDay)) { - if (activeFaction.isPirate() && - !activeFaction.getShortName().equalsIgnoreCase("PIR")) { - pirateFactions.add(activeFaction); - } - } + startingFaction = factions.getDefaultFaction(); + startingSystem = startingFaction.getStartingPlanet(this, currentDay); + if (startingSystem == null) { + startingSystem = Systems.getInstance().getSystemById(TERRA_ID); + } + } - if (!pirateFactions.isEmpty()) { - fallbackFaction = ObjectUtility.getRandomItem(pirateFactions); + // Special case: Clan campaign starting on Terra, swap to Clan homeworld + if (TERRA_ID.equals(startingSystem.getId()) && isClanCampaign()) { + Faction clanFaction = factions.getFaction(CLAN_CODE); + if (clanFaction != null) { + PlanetarySystem clanSystem = clanFaction.getStartingPlanet(this, currentDay); + if (clanSystem != null) { + startingSystem = clanSystem; } - } else if (faction.isClan()) { - fallbackFaction = factions.getFaction("CLAN"); - } else if (faction.getShortName().equalsIgnoreCase("MERC")) { - // Most of the time, mercenary campaigns will begin on their faction capital (Galatea, etc.). - // However, there is a 25% chance they begin in another faction's territory - int roll = randomInt(4); + } + } - if (roll == 0) { - fallbackFaction = factions.getFaction("MERC"); - } else { - List recruitingFaction = new ArrayList<>(); - for (Faction activeFaction : factions.getActiveFactions(currentDay)) { - if (activeFaction.isPlayable() && !activeFaction.isClan() && !activeFaction.isDeepPeriphery()) { - recruitingFaction.add(activeFaction); - } - } + return startingSystem.getPrimaryPlanet(); + } + + /** + * Selects a starting planet for mercenary or pirate campaigns by considering eligible factions, campaign date, and + * appropriate weighting for periphery factions (if pirate). + * + *

For mercenary campaigns, the designated mercenary faction is used as the initial fallback. For pirate + * campaigns, the Tortuga Dominions are preferred, but only if they are active at the campaign's start date; + * otherwise, the game's configured default faction is used (usually Mercenary, but I opted not to hardcode + * mercenary here incase the default changes).

+ * + *

There is a two-thirds probability that the starting faction will be selected from all factions, subject to + * several filters (playability, not a Clan, not deep periphery). For pirate campaigns, eligible periphery factions + * are intentionally added multiple times to the selection pool to increase their likelihood of being chosen + * (weighted randomness).

+ * + *

After the faction is chosen, this method attempts to get that faction’s canonical starting world. If no + * valid system is found, the logic falls back to Terra, ensuring that the campaign always has a valid starting + * world even in case of missing data.

+ * + * @param factions The {@link Factions} manager supplying access to all faction data. + * @param TERRA_ID The globally unique identifier for the planet Terra, used for the ultimate fallback. + * + * @return the {@link Planet} used as the campaign start location. + * + * @author Illiani + * @since 0.50.07 + */ + private Planet getMercenaryOrPirateStartingPlanet(Factions factions, String TERRA_ID) { + final String TORTUGA_CODE = "TD"; - if (!recruitingFaction.isEmpty()) { - fallbackFaction = ObjectUtility.getRandomItem(recruitingFaction); + PlanetarySystem startingSystem; + Faction startingFaction; + // Determine fallback faction for merc/pirate + startingFaction = isMercenaryCampaign() + ? factions.getFaction(MERCENARY_FACTION_CODE) + : factions.getFaction(TORTUGA_CODE); + + // If pirate fallback is unavailable at the campaign's start date, use the default faction + if (isPirateCampaign() && !startingFaction.validIn(currentDay)) { + startingFaction = factions.getDefaultFaction(); + } + + // 33% chance to start in fallback faction's capital + if (randomInt(3) != 0) { + // Pick a random, eligible recruiting faction + List recruitingFactions = new ArrayList<>(); + for (Faction possibleFaction : factions.getActiveFactions(currentDay)) { + if (possibleFaction.isPlayable() && !possibleFaction.isClan() && !possibleFaction.isDeepPeriphery()) { + recruitingFactions.add(possibleFaction); + + // If we're playing a pirate campaign, we want to triple the chance that we start in the periphery + if (possibleFaction.isPeriphery() && isPirateCampaign()) { + recruitingFactions.add(possibleFaction); + recruitingFactions.add(possibleFaction); } } } + if (!recruitingFactions.isEmpty()) { + startingFaction = ObjectUtility.getRandomItem(recruitingFactions); + } + } - startingSystem = fallbackFaction.getStartingPlanet(this, currentDay); + startingSystem = startingFaction.getStartingPlanet(this, currentDay); + if (startingSystem != null) { + return startingSystem.getPrimaryPlanet(); } - return startingSystem.getPrimaryPlanet(); + // Fallback if no startingSystem + startingSystem = Systems.getInstance().getSystemById(TERRA_ID); + return startingSystem != null ? startingSystem.getPrimaryPlanet() : null; } }