Skip to content

Commit 63dbf5e

Browse files
authored
Merge pull request #7252 from IllianiBird/factionStandingOutlawing
Improvement: Implemented Faction Standings and Contract Outlawing
2 parents 7d02ce3 + 8e33dc9 commit 63dbf5e

File tree

5 files changed

+300
-59
lines changed

5 files changed

+300
-59
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
@@ -6998,17 +6998,41 @@ public Accountant getAccountant() {
69986998
* exists between the systems. If start and end are the same system, returns a path containing only that
69996999
* system.
70007000
*/
7001-
public @Nullable JumpPath calculateJumpPath(PlanetarySystem start, PlanetarySystem end) {
7001+
public JumpPath calculateJumpPath(PlanetarySystem start, PlanetarySystem end) {
70027002
// Handle edge cases
70037003
if (null == start) {
7004-
return null;
7004+
return new JumpPath();
70057005
}
7006+
70067007
if ((null == end) || start.getId().equals(end.getId())) {
70077008
JumpPath jumpPath = new JumpPath();
70087009
jumpPath.addSystem(start);
70097010
return jumpPath;
70107011
}
70117012

7013+
// Shortcuts to ensure we're not processing a lot of data when we're unable to reach the target system
7014+
if (isAvoidingEmptySystems && end.getPopulation(currentDay) == 0) {
7015+
new ImmersiveDialogSimple(this, getSeniorAdminPerson(AdministratorSpecialization.TRANSPORT), null,
7016+
String.format(resources.getString("unableToEnterSystem.abandoned.ic"), getCommanderAddress(false)),
7017+
null, resources.getString("unableToEnterSystem.abandoned.ooc"), null, false);
7018+
7019+
return new JumpPath();
7020+
}
7021+
7022+
List<AtBContract> activeAtBContracts = getActiveAtBContracts();
7023+
7024+
if (campaignOptions.isUseFactionStandingOutlawed()) {
7025+
boolean canAccessSystem = FactionStandingUtilities.canEnterTargetSystem(faction, factionStandings,
7026+
getCurrentSystem(), end, currentDay, activeAtBContracts);
7027+
if (!canAccessSystem) {
7028+
new ImmersiveDialogSimple(this, getSeniorAdminPerson(AdministratorSpecialization.TRANSPORT), null,
7029+
String.format(resources.getString("unableToEnterSystem.outlawed.ic"), getCommanderAddress(false)),
7030+
null, resources.getString("unableToEnterSystem.outlawed.ooc"), null, false);
7031+
7032+
return new JumpPath();
7033+
}
7034+
}
7035+
70127036
// Initialize A* algorithm variables
70137037
String startKey = start.getId();
70147038
String endKey = end.getId();
@@ -7046,10 +7070,19 @@ public Accountant getAccountant() {
70467070
String neighborId = neighborSystem.getId();
70477071

70487072
// Skip systems without population if avoiding empty systems
7049-
if (isAvoidingEmptySystems && neighborSystem.getPopulation(currentDay) <= 0) {
7073+
if (isAvoidingEmptySystems && neighborSystem.getPopulation(currentDay) == 0) {
70507074
return;
70517075
}
70527076

7077+
// Skip systems where the campaign is outlawed
7078+
if (campaignOptions.isUseFactionStandingOutlawed()) {
7079+
boolean canAccessSystem = FactionStandingUtilities.canEnterTargetSystem(faction, factionStandings,
7080+
getCurrentSystem(), neighborSystem, currentDay, activeAtBContracts);
7081+
if (!canAccessSystem) {
7082+
return;
7083+
}
7084+
}
7085+
70537086
if (closed.contains(neighborId)) {
70547087
return; // Already evaluated
70557088
}

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/factionStanding/FactionStandingUtilities.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,17 @@
3232
*/
3333
package mekhq.campaign.universe.factionStanding;
3434

35+
import java.time.LocalDate;
36+
import java.util.HashSet;
37+
import java.util.List;
38+
import java.util.Set;
39+
40+
import megamek.common.annotations.Nullable;
3541
import megamek.logging.MMLogger;
42+
import mekhq.campaign.mission.AtBContract;
43+
import mekhq.campaign.universe.Faction;
44+
import mekhq.campaign.universe.FactionHints;
45+
import mekhq.campaign.universe.PlanetarySystem;
3646

3747

3848
public class FactionStandingUtilities {
@@ -290,4 +300,131 @@ public static int getSupportPointModifierPeriodic(final double regard) {
290300

291301
return standing.getSupportPointModifierPeriodic();
292302
}
303+
304+
/**
305+
* Determines whether a campaign force is allowed to enter the specified target planetary system, based on
306+
* population, ownership, outlaw status, contract relationships, and state of war.
307+
*
308+
* <p>The rules for entry are as follows:</p>
309+
* <ol>
310+
* <li>If the target system is empty (population zero), entry is always permitted.</li>
311+
* <li>If the target system is owned by any faction that is either an employer or a contract target, entry is
312+
* allowed.</li>
313+
* <li>If the player is outlawed in their current system, they may always exit to another system (unless {@code
314+
* currentSystem} is {@code null}).</li>
315+
* <li>If the player is outlawed in the target system, entry is denied.</li>
316+
* <li>If the campaign faction is at war with all system factions, entry is denied.</li>
317+
* <li>If all system factions are at war with any employer faction, entry is denied.</li>
318+
* <li>If none of the above conditions block entry, it is permitted.</li>
319+
* </ol>
320+
*
321+
* @param campaignFaction the campaign's primary faction
322+
* @param factionStandings the standings of the campaign with all factions
323+
* @param currentSystem the planetary system currently occupied
324+
* @param targetSystem the planetary system to test entry for
325+
* @param when the date of attempted entry (population/ownership may change over time)
326+
* @param activeAtBContracts list of currently active contracts
327+
*
328+
* @return {@code true} if entry to the target system is allowed; {@code false} otherwise
329+
*
330+
* @author Illiani
331+
* @since 0.50.07
332+
*/
333+
public static boolean canEnterTargetSystem(Faction campaignFaction, FactionStandings factionStandings,
334+
@Nullable PlanetarySystem currentSystem, PlanetarySystem targetSystem, LocalDate when,
335+
List<AtBContract> activeAtBContracts) {
336+
// Always allowed in empty systems
337+
if (targetSystem.getPopulation(when) == 0) {
338+
LOGGER.debug("Target system is empty, access granted");
339+
return true;
340+
}
341+
342+
Set<Faction> systemFactions = targetSystem.getFactionSet(when);
343+
344+
Set<Faction> contractEmployers = new HashSet<>();
345+
Set<Faction> contractTargets = new HashSet<>();
346+
for (AtBContract contract : activeAtBContracts) {
347+
contractEmployers.add(contract.getEmployerFaction());
348+
contractTargets.add(contract.getEnemy());
349+
}
350+
351+
// Entry always allowed if the system is owned by any contract employer or target
352+
if (systemFactions.stream()
353+
.anyMatch(systemFaction -> contractEmployers.contains(systemFaction)
354+
|| contractTargets.contains(systemFaction))) {
355+
LOGGER.debug("System is owned by a contract employer or target, access granted");
356+
return true;
357+
}
358+
359+
// Always allowed to leave if outlawed in the current system
360+
if (currentSystem != null) {
361+
if (isOutlawedInSystem(factionStandings, currentSystem, when)) {
362+
LOGGER.debug("Player is outlawed in current system, but always allowed to escape, access granted");
363+
return true;
364+
}
365+
}
366+
367+
// Banned if outlawed in the target system
368+
if (isOutlawedInSystem(factionStandings, targetSystem, when)) {
369+
LOGGER.debug("Player is outlawed in target system, access denied");
370+
return false;
371+
}
372+
373+
// Disallow if the campaign faction is at war with all system factions
374+
FactionHints factionHints = FactionHints.defaultFactionHints();
375+
boolean allAtWarWithCampaign = systemFactions
376+
.stream()
377+
.allMatch(systemFaction -> factionHints.isAtWarWith(campaignFaction,
378+
systemFaction,
379+
when));
380+
if (allAtWarWithCampaign) {
381+
LOGGER.debug("Campaign faction is at war with all system factions, access denied");
382+
return false;
383+
}
384+
385+
// Disallow if all system factions are at war with any one employer
386+
for (Faction employer : contractEmployers) {
387+
boolean allAtWarWithEmployer = systemFactions
388+
.stream()
389+
.allMatch(systemFaction -> factionHints.getCurrentWar(employer,
390+
systemFaction,
391+
when) != null);
392+
if (allAtWarWithEmployer) {
393+
LOGGER.debug("All system factions are at war with employer {}, access denied", employer.getShortName());
394+
return false;
395+
}
396+
}
397+
398+
LOGGER.debug("Access granted");
399+
return true;
400+
}
401+
402+
/**
403+
* Determines whether a faction is outlawed in the specified target system at a given date.
404+
*
405+
* <p>This method evaluates the highest standing ("regard") among all factions present in the target planetary
406+
* system on the specified date. It then checks whether the faction corresponding to the highest regard is
407+
* considered outlawed.</p>
408+
*
409+
* @param factionStandings the faction standings data to use for regard calculations
410+
* @param targetSystem the planetary system in which to perform the check
411+
* @param when the date for which to determine outlaw status
412+
*
413+
* @return {@code true} if the faction is outlawed in the target system at the given date; {@code false} otherwise
414+
*
415+
* @author Illiani
416+
* @since 0.50.07
417+
*/
418+
private static boolean isOutlawedInSystem(FactionStandings factionStandings, PlanetarySystem targetSystem,
419+
LocalDate when) {
420+
double highestRegard = FactionStandingLevel.STANDING_LEVEL_0.getMinimumRegard();
421+
for (Faction faction : targetSystem.getFactionSet(when)) {
422+
double currentRegard = factionStandings.getRegardForFaction(faction.getShortName(), true);
423+
if (currentRegard > highestRegard) {
424+
highestRegard = currentRegard;
425+
}
426+
}
427+
428+
return isOutlawed(highestRegard);
429+
}
293430
}

0 commit comments

Comments
 (0)