Skip to content

Commit 1355e02

Browse files
committed
Merge branch 'master' into factionStandingCommandCircuit
# Conflicts: # MekHQ/src/mekhq/campaign/universe/factionStanding/FactionStandingUtilities.java
2 parents 635ea06 + 63dbf5e commit 1355e02

File tree

7 files changed

+332
-65
lines changed

7 files changed

+332
-65
lines changed

MekHQ/resources/mekhq/resources/Campaign.properties

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,14 @@ gainedExperience.text=%s<b>%s</b>%s personnel <a href='PERSONNEL_ADVANCEMENT|'>g
119119
becomeBondsman.text=%s has finally %s<b>passed</b>%s all necessary security vetting and loyalty\
120120
\ tests. They are now a full <b>Bondsman</b> and can be transitioned to their assigned role.
121121
unitArrived.text=%s has been %s<b>Delivered</b>%s.
122+
unableToEnterSystem.abandoned.ic=%s, I've spoken with the JumpShip Captain, and we've reached an impasse. They're \
123+
reluctant to enter some systems on our route. I can probably turn them around, but it may require us to accept \
124+
additional risk. Let know what you want to do.
125+
unableToEnterSystem.abandoned.ooc=You are attempting to draw a route to an empty system while having the 'avoid empty\
126+
\ systems' option enabled. Disable that option before proceeding.\
127+
<p>Currently, there are no penalties for entering empty systems, however this will not always be the case.</p>
128+
unableToEnterSystem.outlawed.ic=%s, I regret to inform you that we're unable to navigate to that system. It's \
129+
somewhat awkward, but the system owners have taken exception to our activities and outlawed us. Maybe we should \
130+
travel somewhere else?
131+
unableToEnterSystem.outlawed.ooc=Outlawing can occur as the result of supremely low Faction Standing, or accepting a \
132+
contract from an enemy of the system owners.

MekHQ/src/mekhq/campaign/Campaign.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7012,17 +7012,41 @@ public Accountant getAccountant() {
70127012
* exists between the systems. If start and end are the same system, returns a path containing only that
70137013
* system.
70147014
*/
7015-
public @Nullable JumpPath calculateJumpPath(PlanetarySystem start, PlanetarySystem end) {
7015+
public JumpPath calculateJumpPath(PlanetarySystem start, PlanetarySystem end) {
70167016
// Handle edge cases
70177017
if (null == start) {
7018-
return null;
7018+
return new JumpPath();
70197019
}
7020+
70207021
if ((null == end) || start.getId().equals(end.getId())) {
70217022
JumpPath jumpPath = new JumpPath();
70227023
jumpPath.addSystem(start);
70237024
return jumpPath;
70247025
}
70257026

7027+
// Shortcuts to ensure we're not processing a lot of data when we're unable to reach the target system
7028+
if (isAvoidingEmptySystems && end.getPopulation(currentDay) == 0) {
7029+
new ImmersiveDialogSimple(this, getSeniorAdminPerson(AdministratorSpecialization.TRANSPORT), null,
7030+
String.format(resources.getString("unableToEnterSystem.abandoned.ic"), getCommanderAddress(false)),
7031+
null, resources.getString("unableToEnterSystem.abandoned.ooc"), null, false);
7032+
7033+
return new JumpPath();
7034+
}
7035+
7036+
List<AtBContract> activeAtBContracts = getActiveAtBContracts();
7037+
7038+
if (campaignOptions.isUseFactionStandingOutlawed()) {
7039+
boolean canAccessSystem = FactionStandingUtilities.canEnterTargetSystem(faction, factionStandings,
7040+
getCurrentSystem(), end, currentDay, activeAtBContracts);
7041+
if (!canAccessSystem) {
7042+
new ImmersiveDialogSimple(this, getSeniorAdminPerson(AdministratorSpecialization.TRANSPORT), null,
7043+
String.format(resources.getString("unableToEnterSystem.outlawed.ic"), getCommanderAddress(false)),
7044+
null, resources.getString("unableToEnterSystem.outlawed.ooc"), null, false);
7045+
7046+
return new JumpPath();
7047+
}
7048+
}
7049+
70267050
// Initialize A* algorithm variables
70277051
String startKey = start.getId();
70287052
String endKey = end.getId();
@@ -7065,10 +7089,19 @@ public Accountant getAccountant() {
70657089
String neighborId = neighborSystem.getId();
70667090

70677091
// Skip systems without population if avoiding empty systems
7068-
if (isAvoidingEmptySystems && neighborSystem.getPopulation(currentDay) <= 0) {
7092+
if (isAvoidingEmptySystems && neighborSystem.getPopulation(currentDay) == 0) {
70697093
return;
70707094
}
70717095

7096+
// Skip systems where the campaign is outlawed
7097+
if (campaignOptions.isUseFactionStandingOutlawed()) {
7098+
boolean canAccessSystem = FactionStandingUtilities.canEnterTargetSystem(faction, factionStandings,
7099+
getCurrentSystem(), neighborSystem, currentDay, activeAtBContracts);
7100+
if (!canAccessSystem) {
7101+
return;
7102+
}
7103+
}
7104+
70727105
if (closed.contains(neighborId)) {
70737106
return; // Already evaluated
70747107
}

MekHQ/src/mekhq/campaign/CampaignOptions.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6996,8 +6996,8 @@ public boolean isUseFactionStandingCommandCircuit() {
69966996
* Checks whether tracking faction standing is enabled and if the use of faction standing command circuits are
69976997
* active.
69986998
*
6999-
* @return {@code true} if both faction standing tracking and faction standing command circuit
7000-
* usage are enabled; {@code false} otherwise.
6999+
* @return {@code true} if both faction standing tracking and faction standing command circuit usage are enabled;
7000+
* {@code false} otherwise.
70017001
*/
70027002
public boolean isUseFactionStandingCommandCircuitSafe() {
70037003
return trackFactionStanding && useFactionStandingCommandCircuit;
@@ -7020,11 +7020,10 @@ public boolean isUseFactionStandingOutlawed() {
70207020
}
70217021

70227022
/**
7023-
* Checks whether tracking faction standing is enabled and if the use of faction standing outlawing is
7024-
* active.
7023+
* Checks whether tracking faction standing is enabled and if the use of faction standing outlawing is active.
70257024
*
7026-
* @return {@code true} if both faction standing tracking and faction standing outlaw
7027-
* usage are enabled; {@code false} otherwise.
7025+
* @return {@code true} if both faction standing tracking and faction standing outlaw usage are enabled;
7026+
* {@code false} otherwise.
70287027
*/
70297028
public boolean isUseFactionStandingOutlawedSafe() {
70307029
return trackFactionStanding && useFactionStandingOutlawed;

MekHQ/src/mekhq/campaign/universe/Faction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ public boolean isInactive() {
403403
return is(FactionTag.INACTIVE);
404404
}
405405

406+
public boolean isActive() {
407+
return !isInactive();
408+
}
409+
406410
public boolean isChaos() {
407411
return is(FactionTag.CHAOS);
408412
}

MekHQ/src/mekhq/campaign/universe/Factions.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,38 @@ public Collection<Faction> getFactions() {
106106
}
107107

108108
/**
109-
* Returns a list of all factions active in a specific year.
110-
* @param date
111-
* @return
109+
* Returns a collection of all active factions on the specified date, excluding Command factions.
110+
*
111+
* <p>A faction is considered active if it is valid for the given date and not marked as inactive. Command
112+
* factions (those whose short name contains a dot, {@code .}) are not included in the results.</p>
113+
*
114+
* @param date the date for which to check faction activity
115+
* @return a collection of active factions (excluding Commands) for the specified date
112116
*/
113117
public Collection<Faction> getActiveFactions(LocalDate date) {
114-
return getFactions().stream().filter(f ->
115-
f.validIn(date) && !f.isInactive())
116-
.collect(Collectors.toList());
118+
return getActiveFactions(date, false);
119+
}
120+
121+
/**
122+
* Returns a collection of all active factions on the specified date.
123+
*
124+
* <p>A faction is considered active if it is valid for the given date and not marked as inactive.</p>
125+
*
126+
* <p>If {@code includeCommands} is {@code false}, factions whose short name contains a dot ({@code .}) are
127+
* excluded, as these denote Commands rather than standard factions. If {@code includeCommands} is {@code true},
128+
* these Command factions are included in the results.</p>
129+
*
130+
* @param date the date for which to check faction activity
131+
* @param includeCommands whether to include Command factions (those whose short name contains a dot)
132+
*
133+
* @return a collection of active factions for the specified date, optionally including Commands
134+
*/
135+
public Collection<Faction> getActiveFactions(LocalDate date, boolean includeCommands) {
136+
return getFactions().stream()
137+
.filter(f -> f.validIn(date))
138+
.filter(Faction::isActive)
139+
.filter(f -> (includeCommands || !f.getShortName().contains(".")))
140+
.toList();
117141
}
118142

119143
public Collection<String> getFactionList() {

MekHQ/src/mekhq/campaign/universe/factionStanding/FactionStandingUtilities.java

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,16 @@
3434

3535
import java.util.List;
3636

37+
import java.time.LocalDate;
38+
import java.util.HashSet;
39+
import java.util.Set;
40+
41+
import megamek.common.annotations.Nullable;
3742
import megamek.logging.MMLogger;
3843
import mekhq.campaign.mission.AtBContract;
44+
import mekhq.campaign.universe.Faction;
45+
import mekhq.campaign.universe.FactionHints;
46+
import mekhq.campaign.universe.PlanetarySystem;
3947

4048

4149
public class FactionStandingUtilities {
@@ -341,4 +349,131 @@ public static boolean isUseCommandCircuit(boolean overridingCommandCircuitRequir
341349
useCommandCircuit = hasCommandCircuitAccess(highestRegard);
342350
return useCommandCircuit;
343351
}
352+
353+
/**
354+
* Determines whether a campaign force is allowed to enter the specified target planetary system, based on
355+
* population, ownership, outlaw status, contract relationships, and state of war.
356+
*
357+
* <p>The rules for entry are as follows:</p>
358+
* <ol>
359+
* <li>If the target system is empty (population zero), entry is always permitted.</li>
360+
* <li>If the target system is owned by any faction that is either an employer or a contract target, entry is
361+
* allowed.</li>
362+
* <li>If the player is outlawed in their current system, they may always exit to another system (unless {@code
363+
* currentSystem} is {@code null}).</li>
364+
* <li>If the player is outlawed in the target system, entry is denied.</li>
365+
* <li>If the campaign faction is at war with all system factions, entry is denied.</li>
366+
* <li>If all system factions are at war with any employer faction, entry is denied.</li>
367+
* <li>If none of the above conditions block entry, it is permitted.</li>
368+
* </ol>
369+
*
370+
* @param campaignFaction the campaign's primary faction
371+
* @param factionStandings the standings of the campaign with all factions
372+
* @param currentSystem the planetary system currently occupied
373+
* @param targetSystem the planetary system to test entry for
374+
* @param when the date of attempted entry (population/ownership may change over time)
375+
* @param activeAtBContracts list of currently active contracts
376+
*
377+
* @return {@code true} if entry to the target system is allowed; {@code false} otherwise
378+
*
379+
* @author Illiani
380+
* @since 0.50.07
381+
*/
382+
public static boolean canEnterTargetSystem(Faction campaignFaction, FactionStandings factionStandings,
383+
@Nullable PlanetarySystem currentSystem, PlanetarySystem targetSystem, LocalDate when,
384+
List<AtBContract> activeAtBContracts) {
385+
// Always allowed in empty systems
386+
if (targetSystem.getPopulation(when) == 0) {
387+
LOGGER.debug("Target system is empty, access granted");
388+
return true;
389+
}
390+
391+
Set<Faction> systemFactions = targetSystem.getFactionSet(when);
392+
393+
Set<Faction> contractEmployers = new HashSet<>();
394+
Set<Faction> contractTargets = new HashSet<>();
395+
for (AtBContract contract : activeAtBContracts) {
396+
contractEmployers.add(contract.getEmployerFaction());
397+
contractTargets.add(contract.getEnemy());
398+
}
399+
400+
// Entry always allowed if the system is owned by any contract employer or target
401+
if (systemFactions.stream()
402+
.anyMatch(systemFaction -> contractEmployers.contains(systemFaction)
403+
|| contractTargets.contains(systemFaction))) {
404+
LOGGER.debug("System is owned by a contract employer or target, access granted");
405+
return true;
406+
}
407+
408+
// Always allowed to leave if outlawed in the current system
409+
if (currentSystem != null) {
410+
if (isOutlawedInSystem(factionStandings, currentSystem, when)) {
411+
LOGGER.debug("Player is outlawed in current system, but always allowed to escape, access granted");
412+
return true;
413+
}
414+
}
415+
416+
// Banned if outlawed in the target system
417+
if (isOutlawedInSystem(factionStandings, targetSystem, when)) {
418+
LOGGER.debug("Player is outlawed in target system, access denied");
419+
return false;
420+
}
421+
422+
// Disallow if the campaign faction is at war with all system factions
423+
FactionHints factionHints = FactionHints.defaultFactionHints();
424+
boolean allAtWarWithCampaign = systemFactions
425+
.stream()
426+
.allMatch(systemFaction -> factionHints.isAtWarWith(campaignFaction,
427+
systemFaction,
428+
when));
429+
if (allAtWarWithCampaign) {
430+
LOGGER.debug("Campaign faction is at war with all system factions, access denied");
431+
return false;
432+
}
433+
434+
// Disallow if all system factions are at war with any one employer
435+
for (Faction employer : contractEmployers) {
436+
boolean allAtWarWithEmployer = systemFactions
437+
.stream()
438+
.allMatch(systemFaction -> factionHints.getCurrentWar(employer,
439+
systemFaction,
440+
when) != null);
441+
if (allAtWarWithEmployer) {
442+
LOGGER.debug("All system factions are at war with employer {}, access denied", employer.getShortName());
443+
return false;
444+
}
445+
}
446+
447+
LOGGER.debug("Access granted");
448+
return true;
449+
}
450+
451+
/**
452+
* Determines whether a faction is outlawed in the specified target system at a given date.
453+
*
454+
* <p>This method evaluates the highest standing ("regard") among all factions present in the target planetary
455+
* system on the specified date. It then checks whether the faction corresponding to the highest regard is
456+
* considered outlawed.</p>
457+
*
458+
* @param factionStandings the faction standings data to use for regard calculations
459+
* @param targetSystem the planetary system in which to perform the check
460+
* @param when the date for which to determine outlaw status
461+
*
462+
* @return {@code true} if the faction is outlawed in the target system at the given date; {@code false} otherwise
463+
*
464+
* @author Illiani
465+
* @since 0.50.07
466+
*/
467+
private static boolean isOutlawedInSystem(FactionStandings factionStandings, PlanetarySystem targetSystem,
468+
LocalDate when) {
469+
double highestRegard = FactionStandingLevel.STANDING_LEVEL_0.getMinimumRegard();
470+
for (Faction faction : targetSystem.getFactionSet(when)) {
471+
double currentRegard = factionStandings.getRegardForFaction(faction.getShortName(), true);
472+
if (currentRegard > highestRegard) {
473+
highestRegard = currentRegard;
474+
}
475+
}
476+
477+
return isOutlawed(highestRegard);
478+
}
344479
}

0 commit comments

Comments
 (0)