Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warnings on Campaign Load #5757

Merged
merged 8 commits into from
Jan 16, 2025
7 changes: 6 additions & 1 deletion MekHQ/resources/mekhq/resources/GUI.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1556,4 +1556,9 @@ AutoResolveMethod.PRINCESS.toolTipText=Princess plays the game for you.
AutoResolveMethod.ABSTRACT_COMBAT.text=Abstract Combat Auto Resolution
AutoResolveMethod.ABSTRACT_COMBAT.toolTipText=ACAR, a fast simulation using a subset of the abstract combat system rules
AutoResolveMethod.promptForAutoResolveMethod.text=Select the method to use for auto resolving the scenario.
AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method
AutoResolveMethod.promptForAutoResolveMethod.title=Auto Resolve Method

ActiveContractWarning.title=Active Contract Warning
ActiveContractWarning.message="This campaign has an active contract.\
\n\
\nPlease complete the contract before updating to version %s."
47 changes: 47 additions & 0 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,53 @@ public List<AtBContract> getAtBContracts() {
.collect(Collectors.toList());
}

/**
* Determines whether there is an active AtB (Against the Bot) contract.
* This method checks if there are contracts currently active. Optionally,
* it can also consider future contracts that have been accepted but have
* not yet started.
*
* @param includeFutureContracts a boolean indicating whether contracts that
* have been accepted but have not yet started
* should also be considered as active.
* @return {@code true} if there is any currently active AtB contract, or if
* {@code includeFutureContracts} is {@code true} and there are future
* contracts starting after the current date. Otherwise, {@code false}.
* @see #hasFutureAtBContract()
*/
public boolean hasActiveAtBContract(boolean includeFutureContracts) {
if (!getActiveAtBContracts().isEmpty()) {
return true;
}

if (includeFutureContracts) {
return hasFutureAtBContract();
}

return false;
}

/**
* Determines whether there are any future AtB (Against the Bot) contracts.
* A future contract is defined as a contract that has been accepted but
* has a start date later than the current day.
*
* @return true if there is at least one future AtB contract (accepted but
* starting after the current date). Otherwise, false.
*/
public boolean hasFutureAtBContract() {
List<AtBContract> contracts = getAtBContracts();

for (AtBContract contract : contracts) {
// This catches any contracts that have been accepted, but haven't yet started
if (contract.getStartDate().isAfter(currentDay)) {
return true;
}
}

return false;
}

public List<AtBContract> getActiveAtBContracts() {
return getActiveAtBContracts(false);
}
Expand Down
10 changes: 9 additions & 1 deletion MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2024 - The MegaMek Team. All Rights Reserved.
* Copyright (c) 2018-2025 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
Expand Down Expand Up @@ -29,6 +29,7 @@
import megamek.common.icons.Camouflage;
import megamek.common.weapons.bayweapons.BayWeapon;
import megamek.logging.MMLogger;
import mekhq.MHQConstants;
import mekhq.MekHQ;
import mekhq.NullEntityException;
import mekhq.Utilities;
Expand Down Expand Up @@ -67,6 +68,7 @@
import mekhq.campaign.universe.PlanetarySystem.PlanetarySystemEvent;
import mekhq.campaign.universe.Systems;
import mekhq.campaign.universe.fameAndInfamy.FameAndInfamyController;
import mekhq.gui.dialog.ActiveContractWarning;
import mekhq.io.idReferenceClasses.PersonIdReference;
import mekhq.module.atb.AtBEventProcessor;
import mekhq.utilities.MHQXMLUtility;
Expand Down Expand Up @@ -581,6 +583,12 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException {
}
});

if (MHQConstants.VERSION.isHigherThan(version)) {
if (retVal.hasActiveAtBContract(true)) {
new ActiveContractWarning();
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion would be something like the following, but inside the CampaignFactory file, with the CampaignXMLParser returning a container with the campaign and the relevant diagnostics:

Somewhere add this:

    // Somewhere
    public enum CampaignProblemType {
        NONE,
        ACTIVE_CONTRACT,
        ACTIVE_FUTURE_CONTRACT,
        CANT_LOAD_FROM_VERSION,
        CANT_PARSE_FILE,
    }

    public record ParsedCampaign(Campaign campaign, String currentMekHQVersion, String fileMekHQVersion, CampaignProblemType problem, String diagnostics) { 
    public boolean hasProblems() {
            return problem != null;
        }
    }

In the CampaignFactory


    public ParsedCampaign createCampaign(InputStream is)
        throws CampaignXmlParseException, IOException, NullEntityException {
        if (!is.markSupported()) {
            is = new BufferedInputStream(is);
        }

        byte[] header = readHeader(is);

        // Check if the first two bytes are the GZIP magic bytes...
        if ((header.length >= 2) && (header[0] == (byte) 0x1f) && (header[1] == (byte) 0x8b)) {
            is = new GZIPInputStream(is);
        }
        // ...otherwise, assume we're an XML file.

        CampaignXmlParser parser = new CampaignXmlParser(is, this.app);
        ParsedCampaign parsedCampaign = parser.parse();
        return parsedCampaign;
    }

This way, the place that receives the file will be able to handle it accordingly.

DataLoadingDialog.java

                // And then load the campaign object from it.
                try (FileInputStream fis = new FileInputStream(getCampaignFile())) {
                    ParsedCampaign parsedCampaign = CampaignFactory.newInstance(getApplication()).createCampaign(fis);
                    if (parsedCampaign.hasProblems()) {
                        // TODO: CALL ERROR HANDLING FUNCTION FOR EACH POSSIBLE PROBLEM
                    }
                    campaign.restore();
                    campaign.cleanUp();
                }
                // endregion Progress 6

Otherwise we keep with the problem where we have being imported stuff inserted inside a parser class.

Sorry for not being clear the first time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "parse()" and the diagnostics can run in two different steps, this way not disrupting any tests or old files and just adding new functionality.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I had to wait until my brain had the cycles to process this, but now that it does what you're saying makes perfect sense and will be super maintainable. I would have never thought to use Enums like this. Thanks for the feedback! :)

logger.info("Load of campaign file complete!");

return retVal;
Expand Down
55 changes: 55 additions & 0 deletions MekHQ/src/mekhq/gui/dialog/ActiveContractWarning.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2025 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
* MegaMek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MegaMek is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MegaMek. If not, see <http://www.gnu.org/licenses/>.
*/
package mekhq.gui.dialog;

import mekhq.MHQConstants;
import mekhq.MekHQ;

import javax.swing.*;
import java.util.ResourceBundle;

public class ActiveContractWarning {
private final String RESOURCE_KEY = "mekhq.resources.GUI";
private final transient ResourceBundle resources =
ResourceBundle.getBundle(RESOURCE_KEY, MekHQ.getMHQOptions().getLocale());

/**
* Displays a warning dialog to the user indicating that the campaign has
* an active contract. The warning informs the user to complete the active
* contract before updating to the specified version of the application.
* <p>
* The displayed message includes the current application version retrieved
* from {@code MHQConstants.VERSION}.
* <p>
* The dialog uses an error message icon and is displayed in a modal popup.
*
* @see JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)
*/
public ActiveContractWarning() {
String message = String.format(resources.getString("ActiveContractWarning.message"),
MHQConstants.VERSION);

JOptionPane.showMessageDialog(
null,
message,
resources.getString("ActiveContractWarning.title"),
JOptionPane.ERROR_MESSAGE
);
}
}
Loading