diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 9d5f360ac1..73f811f61e 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -1196,7 +1196,7 @@ btnNewCampaign.text=Start a New Campaign btnLoadCampaign.text=Load a Campaign btnLoadLastCampaign.text=Load Last Save btnLoadStoryArc.text=Load a Story Arc - +btnStoryArcEditor.text=Story Arc Editor ##### Panes #### RankSystemsPane Class diff --git a/MekHQ/resources/mekhq/resources/StoryArcEditorGUI.properties b/MekHQ/resources/mekhq/resources/StoryArcEditorGUI.properties new file mode 100644 index 0000000000..bfd469492b --- /dev/null +++ b/MekHQ/resources/mekhq/resources/StoryArcEditorGUI.properties @@ -0,0 +1,4 @@ +fileMenu.text=File +miNewArc.text=New Story Arc... +miLoadArc.text=Load Story Arc... +miSaveArc.text=Save Story Arc \ No newline at end of file diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index bfd07f52ad..5dbc334f95 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -42,9 +42,11 @@ import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.Scenario; +import mekhq.campaign.storyarc.StoryArc; import mekhq.campaign.stratcon.StratconRulesManager; import mekhq.campaign.unit.Unit; import mekhq.gui.CampaignGUI; +import mekhq.gui.StoryArcEditorGUI; import mekhq.gui.dialog.ResolveScenarioWizardDialog; import mekhq.gui.dialog.RetirementDefectionDialog; import mekhq.gui.panels.StartupScreenPanel; @@ -95,6 +97,7 @@ public class MekHQ implements GameListener { // the actual campaign - this is where the good stuff is private CampaignController campaignController; private CampaignGUI campaignGUI; + private StoryArcEditorGUI storyArcEditorGUI; private IconPackage iconPackage = new IconPackage(); @@ -246,6 +249,10 @@ public void showNewView() { campaignGUI = new CampaignGUI(this); } + public void showNewStoryArcEditor(StoryArc arc) { + storyArcEditorGUI = new StoryArcEditorGUI(this, arc); + } + /** * Main method launching the application. */ diff --git a/MekHQ/src/mekhq/campaign/storyarc/StoryArc.java b/MekHQ/src/mekhq/campaign/storyarc/StoryArc.java index 191e7036e9..3b3a891528 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/StoryArc.java +++ b/MekHQ/src/mekhq/campaign/storyarc/StoryArc.java @@ -155,6 +155,10 @@ private UUID getStartingPointId() { return startingPointId; } + public List getStoryPoints() { + return new ArrayList(storyPoints.values()); + } + public void setInitCampaignPath(String s) { this.initCampaignPath =s; } @@ -246,6 +250,15 @@ public String toString() { return getTitle(); } + public boolean isDuplicateName(String name) { + for(StoryPoint sp : getStoryPoints()) { + if(sp.getName().equals(name)) { + return true; + } + } + return false; + } + //region EventHandlers @Subscribe public void handleScenarioResolved(ScenarioResolvedEvent ev) { diff --git a/MekHQ/src/mekhq/campaign/storyarc/StoryOutcome.java b/MekHQ/src/mekhq/campaign/storyarc/StoryOutcome.java index 9326b0fca2..683c60c93c 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/StoryOutcome.java +++ b/MekHQ/src/mekhq/campaign/storyarc/StoryOutcome.java @@ -48,7 +48,7 @@ public class StoryOutcome { /** A list of StoryTriggers to replace the defaults on this outcome */ List storyTriggers; - StoryOutcome() { + public StoryOutcome() { storyTriggers = new ArrayList<>(); } @@ -56,14 +56,26 @@ public String getResult() { return result; } + public void setResult(String r) { + this.result = r; + } + public UUID getNextStoryPointId() { return nextStoryPointId; } + public void setNextStoryPointId(UUID id) { + this.nextStoryPointId = id; + } + public List getStoryTriggers() { return storyTriggers; } + public void setStoryTriggers(List triggers) { + this.storyTriggers = triggers; + } + /** * Set the StoryArc on all StoryTriggers here * @param a a {@link StoryArc StoryArc} diff --git a/MekHQ/src/mekhq/campaign/storyarc/StoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/StoryPoint.java index 4bcf44e558..8d36f4a3f9 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/StoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/StoryPoint.java @@ -21,6 +21,7 @@ package mekhq.campaign.storyarc; import megamek.Version; +import mekhq.gui.StoryPointHyperLinkListener; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import org.apache.logging.log4j.LogManager; @@ -61,6 +62,8 @@ */ public abstract class StoryPoint { + public final static String DEFAULT_OUTCOME = "DEFAULT"; + /** The story arc that this story point is a part of **/ private StoryArc storyArc; @@ -103,7 +106,7 @@ public void setStoryArc(StoryArc a) { } } - protected StoryArc getStoryArc() { + public StoryArc getStoryArc() { return storyArc; } @@ -111,7 +114,7 @@ public void setId(UUID id) { this.id = id; } - protected UUID getId() { + public UUID getId() { return id; } @@ -125,6 +128,30 @@ public String getName() { return name; } + public void setName(String s) { + name = s; + } + + public UUID getNextStoryPointId() { + return nextStoryPointId; + } + + public void setNextStoryPointId(UUID nextStoryPointId) { + this.nextStoryPointId = nextStoryPointId; + } + + public List getStoryOutcomes() { + return new ArrayList(storyOutcomes.values()); + } + + public List getStoryTriggers() { + return storyTriggers; + } + + public void setStoryTriggers(List storyTriggers) { + this.storyTriggers = storyTriggers; + } + /** * Do whatever needs to be done to start this story point. Specific story point types may need to override this */ @@ -206,6 +233,53 @@ public Campaign getCampaign() { return getStoryArc().getCampaign(); } + public List getAllPossibleResults() { + ArrayList results = new ArrayList<>(); + results.add(DEFAULT_OUTCOME); + return results; + } + + public List getLinkingStoryPoints() { + List previous = new ArrayList(); + UUID nextId; + for (StoryPoint otherStoryPoint : storyArc.getStoryPoints()) { + nextId = otherStoryPoint.getNextStoryPointId(); + if ((nextId != null) && (nextId.equals(getId()))) { + previous.add(otherStoryPoint); + continue; + } + for( StoryOutcome outcome : otherStoryPoint.getStoryOutcomes()) { + nextId = outcome.getNextStoryPointId(); + if ((nextId != null) && (nextId.equals(getId()))) { + previous.add(otherStoryPoint); + continue; + } + } + } + return previous; + } + + public String getHyperlinkedName() { + return String.format("%s", StoryPointHyperLinkListener.STORYPOINT, getId(), getName()); + } + + public StoryOutcome getStoryOutcome(String result) { + return storyOutcomes.get(result); + } + + public void addStoryOutcome(String result, StoryOutcome outcome) { + storyOutcomes.put(result, outcome); + } + + public void removeStoryOutcome(String result) { + storyOutcomes.remove(result); + } + + public void removeDefaultOutcome() { + nextStoryPointId = null; + storyTriggers = new ArrayList<>(); + } + //region I/O public abstract void writeToXml(PrintWriter pw1, int indent); @@ -294,5 +368,4 @@ public static StoryPoint generateInstanceFromXML(Node wn, Campaign c, Version ve return retVal; } - } diff --git a/MekHQ/src/mekhq/campaign/storyarc/StoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/StoryTrigger.java index 7a0c1df36d..9cf1393565 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/StoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/StoryTrigger.java @@ -21,12 +21,14 @@ package mekhq.campaign.storyarc; import megamek.Version; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import org.apache.logging.log4j.LogManager; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -48,7 +50,7 @@ public void setStoryArc(StoryArc a) { this.arc = a; } - protected StoryArc getStoryArc() { + public StoryArc getStoryArc() { return arc; } @@ -61,6 +63,10 @@ protected Campaign getCampaign() { */ protected abstract void execute(); + public abstract String getDescription(); + + public abstract StoryTriggerPanel getPanel(JFrame frame); + //region I/O public abstract void writeToXml(PrintWriter pw1, int indent); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckMoreScenariosStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckMoreScenariosStoryPoint.java index 6e9f595770..4fd254a293 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckMoreScenariosStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckMoreScenariosStoryPoint.java @@ -19,6 +19,7 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.mission.Mission; @@ -29,6 +30,8 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** @@ -61,6 +64,15 @@ protected String getResult() { return "true"; } + @Override + public List getAllPossibleResults() { + ArrayList results = new ArrayList<>(); + results.add("true"); + results.add("false"); + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public void start() { super.start(); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckPersonStatusStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckPersonStatusStoryPoint.java index 500386bbbb..894cffcc34 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckPersonStatusStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckPersonStatusStoryPoint.java @@ -21,6 +21,8 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; @@ -31,6 +33,8 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** @@ -62,6 +66,16 @@ protected String getResult() { } } + @Override + public List getAllPossibleResults() { + ArrayList results = new ArrayList<>(); + for(PersonnelStatus status : PersonnelStatus.getImplementedStatuses()) { + results.add(status.name()); + } + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public void start() { super.start(); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckStringVariableStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckStringVariableStoryPoint.java index e959af54cf..2f93a7b60b 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckStringVariableStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/CheckStringVariableStoryPoint.java @@ -21,6 +21,9 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.storyarc.StoryArc; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.ChangeStringVariableStoryTrigger; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryPoint; @@ -30,6 +33,8 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; /** * This StoryPoint checks the value of a stored string variable from the @@ -57,6 +62,33 @@ protected String getResult() { return getStoryArc().getCustomStringVariable(key); } + @Override + public List getAllPossibleResults() { + // this one is complicated, but we can find all the triggers where this has been changed and collect + // all possibilities + List results = new ArrayList<>(); + for(StoryPoint sp : getStoryArc().getStoryPoints()) { + for(StoryTrigger trigger : sp.getStoryTriggers()) { + if(trigger instanceof ChangeStringVariableStoryTrigger) { + if(!((ChangeStringVariableStoryTrigger)trigger).getKey().equals(key)) { + continue; + } + String value = ((ChangeStringVariableStoryTrigger)trigger).getValue(); + if(!results.contains(value)) { + results.add(value); + } + } + } + } + // finally, check the current value of this variable and add it if not already there + String value = getStoryArc().getCustomStringVariable(key); + if(!results.contains(value)) { + results.add(value); + } + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public void start() { super.start(); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/ChoiceStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/ChoiceStoryPoint.java index a02a09be37..95c82a80b9 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/ChoiceStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/ChoiceStoryPoint.java @@ -21,6 +21,7 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryPoint; @@ -31,7 +32,9 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -66,6 +69,13 @@ public Map getChoices() { return choices; } + @Override + public List getAllPossibleResults() { + List results = new ArrayList(choices.keySet()); + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public void start() { super.start(); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/MissionStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/MissionStoryPoint.java index 7c854708a8..6dea227139 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/MissionStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/MissionStoryPoint.java @@ -21,6 +21,7 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.mission.Mission; @@ -128,6 +129,15 @@ public String getObjective() { return "Complete " + mission.getName() + " mission"; } + @Override + public List getAllPossibleResults() { + ArrayList results = new ArrayList<>(); + results.add(MissionStatus.FAILED.name()); + results.add(MissionStatus.SUCCESS.name()); + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/RollDiceStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/RollDiceStoryPoint.java index 50f710aeec..4729570184 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/RollDiceStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/RollDiceStoryPoint.java @@ -20,6 +20,7 @@ import megamek.Version; import megamek.common.Compute; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryPoint; @@ -29,6 +30,8 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; /** * This StoryPoint rolls some dice and returns the result. It can be used when some randomization is required. @@ -51,6 +54,7 @@ public String getTitle() { @Override public void start() { super.start(); + //index zero should contain the summed result value = Compute.individualDice(ndice, sides).get(0); complete(); } @@ -60,6 +64,16 @@ protected String getResult() { return Integer.toString(value); } + public List getAllPossibleResults() { + int min = ndice; + int max = ndice * sides; + List results = new ArrayList<>(); + for(int i = min; i <= max; i++) { + results.add(Integer.toString(i)); + } + results.add(DEFAULT_OUTCOME); + return results; + } @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storypoint/ScenarioStoryPoint.java b/MekHQ/src/mekhq/campaign/storyarc/storypoint/ScenarioStoryPoint.java index 34b0c9b586..e901de1e26 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storypoint/ScenarioStoryPoint.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storypoint/ScenarioStoryPoint.java @@ -21,6 +21,7 @@ package mekhq.campaign.storyarc.storypoint; import megamek.Version; +import mekhq.campaign.storyarc.StoryArc; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.force.Force; @@ -35,6 +36,8 @@ import java.io.PrintWriter; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** @@ -141,6 +144,19 @@ protected String getResult() { return ""; } + @Override + public List getAllPossibleResults() { + ArrayList results = new ArrayList<>(); + for(ScenarioStatus nextStatus : ScenarioStatus.values()) { + if(nextStatus.isCurrent()) { + continue; + } + results.add(nextStatus.name()); + } + results.add(DEFAULT_OUTCOME); + return results; + } + @Override public String getObjective() { return "Complete " + scenario.getName() + " scenario"; diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddPersonStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddPersonStoryTrigger.java index e7472f439e..6e7285179f 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddPersonStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddPersonStoryTrigger.java @@ -24,10 +24,13 @@ import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -47,6 +50,16 @@ protected void execute() { } + @Override + public String getDescription() { + return "Add person (" + person.getFullName() + ")"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new FakeStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddUnitStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddUnitStoryTrigger.java index 3d952538d0..e8a895a613 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddUnitStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AddUnitStoryTrigger.java @@ -27,11 +27,14 @@ import megamek.common.MechSummaryCache; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -61,6 +64,16 @@ protected void execute() { getCampaign().addNewUnit(en, false, 0); } + @Override + public String getDescription() { + return "Add unit(s)"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new FakeStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AdvanceTimeStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AdvanceTimeStoryTrigger.java index bdbb44b795..9cf4595e1c 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AdvanceTimeStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/AdvanceTimeStoryTrigger.java @@ -23,11 +23,15 @@ import megamek.Version; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.panels.storytriggerpanels.AdvanceTimeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -36,7 +40,7 @@ */ public class AdvanceTimeStoryTrigger extends StoryTrigger { - int days; + int days = 1; @Override protected void execute() { @@ -47,6 +51,24 @@ protected void execute() { } } + public int getDays() { + return days; + } + + public void setDays(int days) { + this.days = days; + } + + @Override + public String getDescription() { + return "Advance time (" + days + " days)"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new AdvanceTimeStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangePersonStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangePersonStoryTrigger.java index 1ceebf38da..31956c84d9 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangePersonStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangePersonStoryTrigger.java @@ -21,6 +21,8 @@ package mekhq.campaign.storyarc.storytrigger; import megamek.Version; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; @@ -31,6 +33,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; import java.util.UUID; @@ -128,6 +131,16 @@ protected void execute() { } } + @Override + public String getDescription() { + return "Change person"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new FakeStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangeStringVariableStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangeStringVariableStoryTrigger.java index e820f3d982..a0e56d6e66 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangeStringVariableStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/ChangeStringVariableStoryTrigger.java @@ -21,6 +21,9 @@ package mekhq.campaign.storyarc.storytrigger; import megamek.Version; +import mekhq.gui.panels.storytriggerpanels.ChangeStringVariableStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; @@ -28,6 +31,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -44,6 +48,32 @@ protected void execute() { getStoryArc().addCustomStringVariable(key, value); } + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String getDescription() { + return "Change value of " + key + " to " + value; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new ChangeStringVariableStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/CompleteMissionStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/CompleteMissionStoryTrigger.java index 28b032486c..2fd342552f 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/CompleteMissionStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/CompleteMissionStoryTrigger.java @@ -21,6 +21,10 @@ package mekhq.campaign.storyarc.storytrigger; import megamek.Version; +import mekhq.gui.StoryPointHyperLinkListener; +import mekhq.gui.panels.storytriggerpanels.CompleteMissionStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.mission.enums.MissionStatus; @@ -31,6 +35,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; import java.util.UUID; @@ -44,6 +49,22 @@ public class CompleteMissionStoryTrigger extends StoryTrigger { UUID missionStoryPointId; MissionStatus missionStatus; + public UUID getMissionStoryPointId() { + return missionStoryPointId; + } + + public void setMissionStoryPointId(UUID missionStoryPointId) { + this.missionStoryPointId = missionStoryPointId; + } + + public MissionStatus getMissionStatus() { + return missionStatus; + } + + public void setMissionStatus(MissionStatus missionStatus) { + this.missionStatus = missionStatus; + } + @Override protected void execute() { StoryPoint storyPoint = getStoryArc().getStoryPoint(missionStoryPointId); @@ -55,6 +76,29 @@ protected void execute() { } } + @Override + public String getDescription() { + StoryPoint storyPoint = getStoryArc().getStoryPoint(missionStoryPointId); + StringBuilder sb = new StringBuilder(); + sb.append("Complete "); + if(storyPoint == null) { + sb.append("mission (MISSING)"); + } else { + sb.append(storyPoint.getHyperlinkedName()); + } + if(missionStatus != null) { + sb.append(" ("); + sb.append(missionStatus.name()); + sb.append(")"); + } + return sb.toString(); + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new CompleteMissionStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/EndArcStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/EndArcStoryTrigger.java index 2e363a0051..2cf193b677 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/EndArcStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/EndArcStoryTrigger.java @@ -23,8 +23,11 @@ import megamek.Version; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.panels.storytriggerpanels.EndArcStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import org.w3c.dom.Node; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -39,6 +42,16 @@ protected void execute() { getCampaign().unloadStoryArc(); } + @Override + public String getDescription() { + return "Complete story arc"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new EndArcStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/GameOverStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/GameOverStoryTrigger.java index aad92cef39..aab2c65ad2 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/GameOverStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/GameOverStoryTrigger.java @@ -24,8 +24,12 @@ import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.GameOverStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import org.w3c.dom.Node; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -41,6 +45,16 @@ protected void execute() { getCampaign().getApp().restart(); } + @Override + public String getDescription() { + return "Game Over"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new GameOverStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/RemoveUnitStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/RemoveUnitStoryTrigger.java index 5c5ed97d07..76e16754da 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/RemoveUnitStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/RemoveUnitStoryTrigger.java @@ -24,11 +24,14 @@ import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; import mekhq.campaign.unit.Unit; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; import java.util.ArrayList; @@ -59,6 +62,16 @@ protected void execute() { } } + @Override + public String getDescription() { + return "Remove unit(s)"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new FakeStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SetDateStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SetDateStoryTrigger.java index 38c82a7109..0605bb6fee 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SetDateStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SetDateStoryTrigger.java @@ -19,6 +19,11 @@ package mekhq.campaign.storyarc.storytrigger; import megamek.Version; +import mekhq.MekHQ; +import mekhq.gui.StoryPointHyperLinkListener; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.SetDateStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryPoint; @@ -28,6 +33,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; import java.time.LocalDate; @@ -60,6 +66,31 @@ public SetDateStoryTrigger() { } //endregion Constructors + + public UUID getStoryPointId() { + return storyPointId; + } + + public void setStoryPointId(UUID storyPointId) { + this.storyPointId = storyPointId; + } + + public int getFutureDays() { + return futureDays; + } + + public void setFutureDays(int futureDays) { + this.futureDays = futureDays; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + @Override protected void execute() { StoryPoint storyPoint = getStoryArc().getStoryPoint(storyPointId); @@ -73,6 +104,31 @@ protected void execute() { } } + @Override + public String getDescription() { + StoryPoint storyPoint = getStoryArc().getStoryPoint(storyPointId); + StringBuilder sb = new StringBuilder(); + sb.append("Set date"); + if(storyPoint != null) { + sb.append(" in "); + sb.append(storyPoint.getHyperlinkedName()); + if(date == null) { + sb.append(" ahead by "); + sb.append(futureDays); + sb.append(" days"); + } else { + sb.append(" to "); + sb.append(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); + } + } + return sb.toString(); + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new SetDateStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SwitchTabStoryTrigger.java b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SwitchTabStoryTrigger.java index ed2867e3a1..a0eb2f89a3 100644 --- a/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SwitchTabStoryTrigger.java +++ b/MekHQ/src/mekhq/campaign/storyarc/storytrigger/SwitchTabStoryTrigger.java @@ -21,6 +21,9 @@ package mekhq.campaign.storyarc.storytrigger; import megamek.Version; +import mekhq.gui.panels.storytriggerpanels.FakeStoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; +import mekhq.gui.panels.storytriggerpanels.SwitchTabStoryTriggerPanel; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.storyarc.StoryTrigger; @@ -29,6 +32,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.io.PrintWriter; import java.text.ParseException; @@ -40,11 +44,29 @@ public class SwitchTabStoryTrigger extends StoryTrigger { MHQTabType tab; + public MHQTabType getTab() { + return tab; + } + + public void setTab(MHQTabType tab) { + this.tab = tab; + } + @Override protected void execute() { getCampaign().getApp().getCampaigngui().setSelectedTab(tab); } + @Override + public String getDescription() { + return "Switch to " + tab.toString() + " tab"; + } + + @Override + public StoryTriggerPanel getPanel(JFrame frame) { + return new SwitchTabStoryTriggerPanel(frame, "StoryTriggerPanel", this); + } + @Override public void writeToXml(PrintWriter pw1, int indent) { writeToXmlBegin(pw1, indent++); diff --git a/MekHQ/src/mekhq/gui/StoryArcEditorGUI.java b/MekHQ/src/mekhq/gui/StoryArcEditorGUI.java new file mode 100644 index 0000000000..f69bfa31dd --- /dev/null +++ b/MekHQ/src/mekhq/gui/StoryArcEditorGUI.java @@ -0,0 +1,229 @@ +package mekhq.gui; + +import mekhq.MekHQ; +import mekhq.campaign.Campaign; +import mekhq.campaign.storyarc.StoryArc; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.*; +import mekhq.gui.model.StoryPointTableModel; +import mekhq.gui.panels.StoryPointEditorPanel; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.ResourceBundle; +import java.util.UUID; + +public class StoryArcEditorGUI extends JPanel { + + //region Variable Declarations + public static final int MAX_START_WIDTH = 1400; + public static final int MAX_START_HEIGHT = 900; + + public static HashMap availableTriggers; + private JFrame frame; + private MekHQ app; + private StoryPointHyperLinkListener storyPointHLL; + + /* Menu Bar */ + private JMenuBar menuBar; + + /* Story Point Table */ + private JTable storyPointTable; + private StoryPointTableModel storyPointTableModel; + private JScrollPane scrollStoryPointEditor; + + + private StoryArc storyArc; + private static final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.StoryArcEditorGUI", + MekHQ.getMHQOptions().getLocale()); + //endregion Variable Declarations + + //region Constructors + public StoryArcEditorGUI(MekHQ app, StoryArc arc) { + this.app = app; + this.storyArc = arc; + storyPointHLL = new StoryPointHyperLinkListener(storyArc, this); + initializeAvailableTriggers(); + initComponents(); + } + //endregion Constructors + + //region Getters/Setters + public JFrame getFrame() { + return frame; + } + + protected MekHQ getApplication() { + return app; + } + + public StoryPointHyperLinkListener getStoryPointHLL() { + return storyPointHLL; + } + + protected Campaign getCampaign() { + return getApplication().getCampaign(); + } + + protected ResourceBundle getResourceMap() { + return null;//resourceMap; + } + //endregion Getters/Setters + + //region Initialization + private void initComponents() { + frame = new JFrame("Story Arc Editor"); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + setLayout(new BorderLayout()); + + storyPointTableModel = new StoryPointTableModel(); + storyPointTable = new JTable(storyPointTableModel); + storyPointTable.setRowHeight(60); + storyPointTable.getColumnModel().getColumn(0).setCellRenderer(storyPointTableModel.getRenderer()); + storyPointTable.setOpaque(false); + JScrollPane scrollStoryPoints = new JScrollPane(storyPointTable); + storyPointTable.getSelectionModel().addListSelectionListener(ev -> refreshStoryPointEditor()); + refreshStoryPoints(); + + add(scrollStoryPoints, BorderLayout.WEST); + + scrollStoryPointEditor = new JScrollPane(); + scrollStoryPointEditor.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollStoryPointEditor.setViewportView(null); + + add(scrollStoryPointEditor, BorderLayout.CENTER); + + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + + frame.setSize(Math.min(MAX_START_WIDTH, dim.width), + Math.min(MAX_START_HEIGHT, dim.height)); + + // Determine the new location of the window + int w = frame.getSize().width; + int h = frame.getSize().height; + int x = (dim.width - w) / 2; + int y = (dim.height - h) / 2; + + // Move the window + frame.setLocation(x, y); + + initMenu(); + frame.setJMenuBar(menuBar); + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(this, BorderLayout.CENTER); + frame.validate(); + + if (isMacOSX()) { + enableFullScreenMode(frame); + } + + frame.setVisible(true); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + getApplication().exit(); + } + }); + + } + + private void initMenu() { + menuBar = new JMenuBar(); + + JMenu menuFile = new JMenu(resourceMap.getString("fileMenu.text")); + menuFile.setMnemonic(KeyEvent.VK_F); + JMenuItem miNewArc = new JMenuItem(resourceMap.getString("miNewArc.text")); + miNewArc.setMnemonic(KeyEvent.VK_N); + menuFile.add(miNewArc); + JMenuItem miLoadArc = new JMenuItem(resourceMap.getString("miLoadArc.text")); + + miLoadArc.setMnemonic(KeyEvent.VK_L); + menuFile.add(miLoadArc); + JMenuItem miSaveArc = new JMenuItem(resourceMap.getString("miSaveArc.text")); + miSaveArc.setMnemonic(KeyEvent.VK_S); + menuFile.add(miSaveArc); + + menuBar.add(menuFile); + } + //endregion Initialization + + private static boolean isMacOSX() { + return System.getProperty("os.name").contains("Mac OS X"); + } + + private static void enableFullScreenMode(Window window) { + String className = "com.apple.eawt.FullScreenUtilities"; + String methodName = "setWindowCanFullScreen"; + + try { + Class clazz = Class.forName(className); + Method method = clazz.getMethod(methodName, Window.class, boolean.class); + method.invoke(null, window, true); + } catch (Throwable t) { + LogManager.getLogger().error("Full screen mode is not supported", t); + } + } + + public void refreshStoryPoints() { + final int selected = storyPointTable.getSelectedRow(); + final List storyPoints = storyArc.getStoryPoints(); + //storyPoints.sort(new PersonTitleSorter().reversed()); + storyPointTableModel.setData(storyPoints); + if ((selected > -1) && (selected < storyPoints.size())) { + storyPointTable.setRowSelectionInterval(selected, selected); + } + } + + public void focusOnStoryPoint(UUID id) { + int row = -1; + for (int i = 0; i < storyPointTable.getRowCount(); i++) { + if (storyPointTableModel.getStoryPointAt(i).getId().equals(id)) { + row = i; + break; + } + } + if (row != -1) { + storyPointTable.setRowSelectionInterval(row, row); + storyPointTable.scrollRectToVisible(storyPointTable.getCellRect(row, 0, true)); + } + } + + public void refreshStoryPointEditor() { + int row = storyPointTable.getSelectedRow(); + if (row < 0) { + scrollStoryPointEditor.setViewportView(null); + return; + } + StoryPoint selectedStoryPoint = storyPointTableModel.getStoryPointAt(storyPointTable.convertRowIndexToModel(row)); + scrollStoryPointEditor.setViewportView(new StoryPointEditorPanel(frame, "story point editor", selectedStoryPoint, this)); + SwingUtilities.invokeLater(() -> scrollStoryPointEditor.getVerticalScrollBar().setValue(0)); + } + + private void initializeAvailableTriggers() { + availableTriggers = new HashMap<>(); + StoryTrigger trigger; + trigger = new AdvanceTimeStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new ChangeStringVariableStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new CompleteMissionStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new EndArcStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new GameOverStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new SetDateStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + trigger = new SwitchTabStoryTrigger(); + availableTriggers.put(trigger.getClass().getSimpleName(), trigger.getClass().getName()); + } +} diff --git a/MekHQ/src/mekhq/gui/StoryPointHyperLinkListener.java b/MekHQ/src/mekhq/gui/StoryPointHyperLinkListener.java new file mode 100644 index 0000000000..795582d4c3 --- /dev/null +++ b/MekHQ/src/mekhq/gui/StoryPointHyperLinkListener.java @@ -0,0 +1,36 @@ +package mekhq.gui; + +import mekhq.campaign.storyarc.StoryArc; +import mekhq.gui.StoryArcEditorGUI; +import org.apache.logging.log4j.LogManager; + +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import java.util.UUID; + +public class StoryPointHyperLinkListener implements HyperlinkListener { + + private StoryArc storyArc; + private StoryArcEditorGUI editorGUI; + + public static final String STORYPOINT = "STORYPOINT"; + + public StoryPointHyperLinkListener(final StoryArc arc, final StoryArcEditorGUI gui) { + this.storyArc = arc; + this.editorGUI = gui; + } + + @Override + public void hyperlinkUpdate(HyperlinkEvent evt) { + if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (evt.getDescription().startsWith(STORYPOINT)) { + try { + final UUID id = UUID.fromString(evt.getDescription().split(":")[1]); + editorGUI.focusOnStoryPoint(id); + } catch (Exception e) { + LogManager.getLogger().error("", e); + } + } + } + } +} diff --git a/MekHQ/src/mekhq/gui/dialog/CustomizeStoryOutcomeDialog.java b/MekHQ/src/mekhq/gui/dialog/CustomizeStoryOutcomeDialog.java new file mode 100644 index 0000000000..414798f8ac --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/CustomizeStoryOutcomeDialog.java @@ -0,0 +1,220 @@ +package mekhq.gui.dialog; + +import mekhq.campaign.storyarc.StoryOutcome; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.GameOverStoryTrigger; +import mekhq.gui.StoryArcEditorGUI; +import mekhq.gui.panels.storytriggerpanels.StoryTriggerPanel; +import mekhq.gui.utilities.JSuggestField; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Vector; + +public class CustomizeStoryOutcomeDialog extends JDialog { + + JFrame frame; + private StoryOutcome outcome; + private StoryPoint storyPoint; + private String result; + boolean isNewOutcome; + private JSuggestField suggestNext; + + JComboBox choiceAddTrigger; + + private ArrayList triggerPanels; + private JPanel panTriggers; + private JScrollPane scrTriggers; + + public CustomizeStoryOutcomeDialog(JFrame parent, boolean modal, String result, StoryPoint sp, boolean isNew) { + super(parent, modal); + this.frame = parent; + this.storyPoint = sp; + this.isNewOutcome = isNew; + this.result = result; + if (this.result.equals(StoryPoint.DEFAULT_OUTCOME)) { + outcome = new StoryOutcome(); + outcome.setNextStoryPointId(storyPoint.getNextStoryPointId()); + outcome.setStoryTriggers(storyPoint.getStoryTriggers()); + } else if (isNewOutcome) { + outcome = new StoryOutcome(); + } else { + outcome = storyPoint.getStoryOutcome(this.result); + } + initialize(); + setLocationRelativeTo(parent); + pack(); + } + + private void initialize() { + setTitle("Customize Story Outcome"); + getContentPane().setLayout(new BorderLayout()); + JPanel panMain = new JPanel(new GridBagLayout()); + JPanel panButtons = new JPanel(new GridLayout(0, 2)); + getContentPane().add(panMain, BorderLayout.CENTER); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + + JButton btnOk = new JButton("OK"); + btnOk.addActionListener(this::done); + panButtons.add(btnOk); + JButton btnCancel = new JButton("Cancel"); + btnCancel.addActionListener(this::cancel); + panButtons.add(btnCancel); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(5, 5, 5, 5); + panMain.add(new JLabel("Result:"), gbc); + gbc.gridy++; + panMain.add(new JLabel("Next Story Point:"), gbc); + + gbc.gridx = 1; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + panMain.add(new JLabel(result), gbc); + + Vector otherPoints = new Vector(); + for (StoryPoint sp : storyPoint.getStoryArc().getStoryPoints()) { + if (sp.getId().equals(storyPoint.getId())) { + continue; + } + otherPoints.add(sp.getName()); + } + suggestNext = new JSuggestField(this, otherPoints); + + if (outcome.getNextStoryPointId() != null) { + suggestNext.setText(storyPoint.getStoryArc().getStoryPoint(outcome.getNextStoryPointId()).getName()); + } + gbc.gridy++; + panMain.add(suggestNext, gbc); + + choiceAddTrigger = new JComboBox<>(); + for(String s : StoryArcEditorGUI.availableTriggers.keySet()) { + choiceAddTrigger.addItem(s); + } + JButton btnAddTrigger = new JButton("Add Story Trigger"); + btnAddTrigger.addActionListener(evt -> addTrigger()); + JPanel panAddTrigger = new JPanel(new BorderLayout()); + panAddTrigger.add(choiceAddTrigger, BorderLayout.CENTER); + panAddTrigger.add(btnAddTrigger, BorderLayout.LINE_END); + gbc.gridy++; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + panMain.add(panAddTrigger, gbc); + + panTriggers = new JPanel(); + panTriggers.setLayout(new BoxLayout(panTriggers, BoxLayout.Y_AXIS)); + + triggerPanels = new ArrayList<>(); + for(StoryTrigger trigger : outcome.getStoryTriggers()) { + StoryTriggerPanel panTrigger = trigger.getPanel(frame); + panTrigger.getDeleteButton().addActionListener(evt -> removePanel(panTrigger)); + panTriggers.add(panTrigger); + triggerPanels.add(panTrigger); + } + + scrTriggers = new JScrollPane(panTriggers); + scrTriggers.setMinimumSize(new Dimension(400, 200)); + //scrTriggers.setPreferredSize(new Dimension(400, 200)); + gbc.gridy++; + gbc.gridx = 0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.BOTH; + panMain.add(scrTriggers, gbc); + } + + private void removePanel(StoryTriggerPanel panel) { + triggerPanels.remove(panel); + refreshTriggerPanels(); + } + + private void refreshTriggerPanels() { + panTriggers = new JPanel(); + panTriggers.setLayout(new BoxLayout(panTriggers, BoxLayout.Y_AXIS)); + for(StoryTriggerPanel panel : triggerPanels) { + panTriggers.add(panel); + } + scrTriggers.setViewportView(panTriggers); + } + + private void done(ActionEvent evt) { + //set no next story point as default and then look for it + outcome.setNextStoryPointId(null); + // need to find by name which might not be unique + for (StoryPoint sp : storyPoint.getStoryArc().getStoryPoints()) { + if (suggestNext.getText().equals(sp.getName())) { + outcome.setNextStoryPointId(sp.getId()); + break; + } + } + ArrayList triggers = new ArrayList<>(); + for(StoryTriggerPanel panel : triggerPanels) { + panel.updateStoryStrigger(); + triggers.add(panel.getStoryTrigger()); + } + outcome.setStoryTriggers(triggers); + // if this is the default or new, we need to do some additional stuff + if (result.equals(StoryPoint.DEFAULT_OUTCOME)) { + // if the default, we apply directly to the story point + storyPoint.setNextStoryPointId(outcome.getNextStoryPointId()); + storyPoint.setStoryTriggers(outcome.getStoryTriggers()); + } else if(isNewOutcome) { + // add a new outcome + outcome.setResult(result); + storyPoint.addStoryOutcome(result, outcome); + } + setVisible(false); + } + + private void cancel(ActionEvent evt) { + setVisible(false); + } + + private JFrame getFrame() { + return frame; + } + + private void addTrigger() { + String triggerName = (String) choiceAddTrigger.getSelectedItem(); + String className = StoryArcEditorGUI.availableTriggers.get(triggerName); + StoryTrigger trigger = null; + if(className != null) { + try { + trigger = (StoryTrigger) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + if(trigger != null) { + trigger.setStoryArc(storyPoint.getStoryArc()); + StoryTriggerPanel panTrigger = trigger.getPanel(getFrame()); + panTrigger.getDeleteButton().addActionListener(evt -> removePanel(panTrigger)); + triggerPanels.add(panTrigger); + refreshTriggerPanels(); + } + } +} diff --git a/MekHQ/src/mekhq/gui/dialog/DataLoadingDialog.java b/MekHQ/src/mekhq/gui/dialog/DataLoadingDialog.java index 502866e2ee..eaf8b6e013 100644 --- a/MekHQ/src/mekhq/gui/dialog/DataLoadingDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/DataLoadingDialog.java @@ -65,6 +65,7 @@ public class DataLoadingDialog extends AbstractMHQDialog implements PropertyChan private JLabel splash; private JProgressBar progressBar; private StoryArcStub storyArcStub; + private boolean storyArcEditor; //endregion Variable Declarations @@ -76,10 +77,17 @@ public DataLoadingDialog(final JFrame frame, final MekHQ application, public DataLoadingDialog(final JFrame frame, final MekHQ application, final @Nullable File campaignFile, StoryArcStub stub) { + this(frame, application, campaignFile, stub, false); + } + + public DataLoadingDialog(final JFrame frame, final MekHQ application, + final @Nullable File campaignFile, StoryArcStub stub, + boolean storyArcEditor) { super(frame, "DataLoadingDialog", "DataLoadingDialog.title"); this.application = application; this.campaignFile = campaignFile; this.storyArcStub = stub; + this.storyArcEditor = storyArcEditor; this.task = new Task(this); getTask().addPropertyChangeListener(this); initialize(); @@ -396,9 +404,13 @@ public void done() { if (campaign != null) { getApplication().setCampaign(campaign); getApplication().getCampaignController().setHost(campaign.getId()); - getApplication().showNewView(); + if(storyArcEditor) { + getApplication().showNewStoryArcEditor(storyArcStub.loadStoryArc(campaign)); + } else { + getApplication().showNewView(); + } getFrame().dispose(); - if (null != storyArcStub) { + if (null != storyArcStub && !storyArcEditor) { StoryArc storyArc = storyArcStub.loadStoryArc(campaign); if (null != storyArc) { campaign.useStoryArc(storyArc, true); diff --git a/MekHQ/src/mekhq/gui/model/StoryPointTableModel.java b/MekHQ/src/mekhq/gui/model/StoryPointTableModel.java new file mode 100644 index 0000000000..5a45199786 --- /dev/null +++ b/MekHQ/src/mekhq/gui/model/StoryPointTableModel.java @@ -0,0 +1,60 @@ +package mekhq.gui.model; + +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.gui.BasicInfo; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.util.ArrayList; + +public class StoryPointTableModel extends DataTableModel { + + public StoryPointTableModel() { + columnNames = new String[] { "Story Points" }; + data = new ArrayList(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return getStoryPointDesc((StoryPoint) data.get(rowIndex)); + } + + private String getStoryPointDesc(StoryPoint storyPoint) { + StringBuilder toReturn = new StringBuilder(128); + toReturn.append("").append(storyPoint.getName()).append("
"); + toReturn.append(storyPoint.getClass().getSimpleName()); + toReturn.append(""); + return toReturn.toString(); + } + + public StoryPoint getStoryPointAt(int row) { + return (StoryPoint) data.get(row); + } + + public StoryPointTableModel.Renderer getRenderer() { + return new StoryPointTableModel.Renderer(); + } + + public class Renderer extends BasicInfo implements TableCellRenderer { + public Renderer() { + super(); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + setHtmlText(getValueAt(row, column).toString()); + if (isSelected) { + highlightBorder(); + } else { + unhighlightBorder(); + } + setBackground(table.getBackground()); + setForeground(table.getForeground()); + return this; + } + } +} diff --git a/MekHQ/src/mekhq/gui/panels/StartupScreenPanel.java b/MekHQ/src/mekhq/gui/panels/StartupScreenPanel.java index 2003ccbbf3..27db1e5f83 100644 --- a/MekHQ/src/mekhq/gui/panels/StartupScreenPanel.java +++ b/MekHQ/src/mekhq/gui/panels/StartupScreenPanel.java @@ -138,6 +138,17 @@ protected void initialize() { startCampaign(storyArcStub.getInitCampaignFile(), storyArcStub); } }); + + MegamekButton btnStoryArcEditor = new MegamekButton(resources.getString("btnStoryArcEditor.text"), + UIComponents.MainMenuButton.getComp(), true); + btnStoryArcEditor.addActionListener(evt -> { + // FIXME: for starters we will only load existing arcs, but later need an option to start a new arc + StoryArcStub storyArcStub = selectStoryArc(); + if ((null != storyArcStub) && (null != storyArcStub.getInitCampaignFile())) { + startStoryArcEditor(storyArcStub); + } + }); + MegamekButton btnQuit = new MegamekButton(resources.getString("Quit.text"), UIComponents.MainMenuButton.getComp(), true); btnQuit.addActionListener(evt -> System.exit(0)); @@ -164,6 +175,8 @@ protected void initialize() { btnLoadLastCampaign.setPreferredSize(minButtonDim); btnLoadStoryArc.setMinimumSize(minButtonDim); btnLoadStoryArc.setPreferredSize(minButtonDim); + btnStoryArcEditor.setMinimumSize(minButtonDim); + btnStoryArcEditor.setPreferredSize(minButtonDim); btnQuit.setMinimumSize(minButtonDim); btnQuit.setPreferredSize(minButtonDim); @@ -197,6 +210,8 @@ protected void initialize() { c.gridy++; add(btnLoadStoryArc, c); c.gridy++; + add(btnStoryArcEditor, c); + c.gridy++; add(btnQuit, c); getFrame().setResizable(false); @@ -225,6 +240,10 @@ private void startCampaign(final @Nullable File file, @Nullable StoryArcStub sto new DataLoadingDialog(getFrame(), app, file, storyArcStub).setVisible(true); } + private void startStoryArcEditor(@Nullable StoryArcStub storyArcStub) { + new DataLoadingDialog(getFrame(), app, storyArcStub.getInitCampaignFile(), storyArcStub, true).setVisible(true); + } + private @Nullable File selectCampaignFile() { return FileDialogs.openCampaign(getFrame()).orElse(null); } diff --git a/MekHQ/src/mekhq/gui/panels/StoryPointEditorPanel.java b/MekHQ/src/mekhq/gui/panels/StoryPointEditorPanel.java new file mode 100644 index 0000000000..eaff042eed --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/StoryPointEditorPanel.java @@ -0,0 +1,302 @@ +package mekhq.gui.panels; + +import mekhq.campaign.storyarc.StoryOutcome; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.StoryArcEditorGUI; +import mekhq.gui.baseComponents.AbstractMHQScrollablePanel; +import mekhq.gui.dialog.CustomizeStoryOutcomeDialog; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; + +public class StoryPointEditorPanel extends AbstractMHQScrollablePanel { + + private StoryArcEditorGUI editorGUI; + private StoryPoint storyPoint; + + private JTextField txtName; + private JButton btnSaveName; + private JButton btnCancelName; + private JPanel pnlOutcomes; + + private JTable storyOutcomeTable; + + public StoryPointEditorPanel(JFrame frame, String name, StoryPoint sp, StoryArcEditorGUI gui) { + super(frame, name); + storyPoint = sp; + this.editorGUI = gui; + initialize(); + } + + @Override + protected void initialize() { + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.gridwidth = 4; + gbc.insets = new Insets(5, 5, 5, 5); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + add(new JLabel("

" + storyPoint.getClass().getSimpleName() + "

"), gbc); + + gbc.gridy++; + gbc.gridwidth = 1; + add(new JLabel("Story Point Name:"), gbc); + + txtName = new JTextField(storyPoint.getName()); + txtName.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + checkName(); + } + @Override + public void removeUpdate(DocumentEvent e) { + checkName(); + } + @Override + public void insertUpdate(DocumentEvent e) { + checkName(); + } + }); + gbc.gridx++; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + add(txtName, gbc); + + gbc.gridx++; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + btnSaveName = new JButton("Save"); + btnSaveName.addActionListener(this::saveName); + btnSaveName.setEnabled(false); + add(btnSaveName, gbc); + gbc.gridx++; + btnCancelName = new JButton("Cancel"); + btnCancelName.addActionListener(this::cancelName); + btnCancelName.setEnabled(false); + add(btnCancelName, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + add(new JLabel("Linking Story Points:"), gbc); + + StringBuilder sb = new StringBuilder(); + List linkedStoryPoints = storyPoint.getLinkingStoryPoints(); + String SEPARATOR = ""; + for (int i = 0; i < linkedStoryPoints.size(); i++) { + sb.append(SEPARATOR); + sb.append(linkedStoryPoints.get(i).getHyperlinkedName()); + SEPARATOR = ", "; + } + gbc.gridx++; + gbc.weightx = 1.0; + gbc.gridwidth = 3; + gbc.fill = GridBagConstraints.HORIZONTAL; + JTextPane txtLinking = new JTextPane(); + txtLinking.setContentType("text/html"); + txtLinking.setEditable(false); + txtLinking.setText(sb.toString()); + txtLinking.addHyperlinkListener(editorGUI.getStoryPointHLL()); + add(txtLinking, gbc); + + pnlOutcomes = new JPanel(new GridBagLayout()); + pnlOutcomes.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("Story Outcomes and Triggers"), + BorderFactory.createEmptyBorder(5,5,5,5))); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.gridwidth = 4; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.BOTH; + add(pnlOutcomes, gbc); + refreshOutcomesPanel(); + } + + private void refreshOutcomesPanel() { + + pnlOutcomes.removeAll(); + // I would prefer to do this with a JTable, but I can't properly use a HyperLinkListener in a JTable + // so we need to create something else + GridBagConstraints gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridx = 0; + gbc.gridy = 0; + pnlOutcomes.add(new JLabel("Result"), gbc); + gbc.gridx++; + pnlOutcomes.add(new JLabel("Next Story Point"), gbc); + gbc.gridx++; + pnlOutcomes.add(new JLabel("Story Triggers"), gbc); + + List currentOutcomes = new ArrayList<>(); + for(StoryOutcome outcome : storyPoint.getStoryOutcomes()) { + gbc.gridx = 0; + gbc.weightx = 1.0; + gbc.gridy++; + JTextPane txtResult = new JTextPane(); + txtResult.setContentType("text/html"); + txtResult.setEditable(false); + txtResult.setText(outcome.getResult()); + pnlOutcomes.add(txtResult, gbc); + gbc.gridx++; + String next = outcome.getNextStoryPointId() == null ? "-" : + storyPoint.getStoryArc().getStoryPoint(outcome.getNextStoryPointId()).getHyperlinkedName(); + JTextPane txtNext = new JTextPane(); + txtNext.setContentType("text/html"); + txtNext.setEditable(false); + txtNext.setText(next); + txtNext.addHyperlinkListener(editorGUI.getStoryPointHLL()); + pnlOutcomes.add(txtNext, gbc); + gbc.gridx++; + JTextPane txtTriggers = new JTextPane(); + txtTriggers.setContentType("text/html"); + txtTriggers.setEditable(false); + txtTriggers.setText(getStoryTriggerDescription(outcome.getStoryTriggers())); + txtTriggers.addHyperlinkListener(editorGUI.getStoryPointHLL()); + pnlOutcomes.add(txtTriggers, gbc); + gbc.gridx++; + gbc.weightx = 0.0; + JButton btnEdit = new JButton("Edit Outcome"); + btnEdit.addActionListener(evt -> editOutcome(outcome.getResult())); + pnlOutcomes.add(btnEdit, gbc); + gbc.gridx++; + JButton btnRemove = new JButton("Delete Outcome"); + btnRemove.addActionListener(evt -> removeOutcome(outcome.getResult())); + pnlOutcomes.add(btnRemove, gbc); + currentOutcomes.add(outcome.getResult()); + } + + // check for a default outcome or trigger + if (storyPoint.getNextStoryPointId() != null || storyPoint.getStoryTriggers().size() > 0) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 1.0; + JTextPane txtResult = new JTextPane(); + txtResult.setContentType("text/html"); + txtResult.setEditable(false); + txtResult.setText("" + StoryPoint.DEFAULT_OUTCOME + ""); + pnlOutcomes.add(txtResult, gbc); + gbc.gridx++; + String next = storyPoint.getNextStoryPointId() == null ? "-" : + storyPoint.getStoryArc().getStoryPoint(storyPoint.getNextStoryPointId()).getHyperlinkedName(); + JTextPane txtNext = new JTextPane(); + txtNext.setContentType("text/html"); + txtNext.setEditable(false); + txtNext.setText(next); + txtNext.addHyperlinkListener(editorGUI.getStoryPointHLL()); + pnlOutcomes.add(txtNext, gbc); + gbc.gridx++; + JTextPane txtTriggers = new JTextPane(); + txtTriggers.setContentType("text/html"); + txtTriggers.setEditable(false); + txtTriggers.setText(getStoryTriggerDescription(storyPoint.getStoryTriggers())); + txtTriggers.addHyperlinkListener(editorGUI.getStoryPointHLL()); + pnlOutcomes.add(txtTriggers, gbc); + gbc.gridx++; + gbc.weightx = 0.0; + JButton btnEdit = new JButton("Edit Outcome"); + btnEdit.addActionListener(evt -> editOutcome(StoryPoint.DEFAULT_OUTCOME)); + pnlOutcomes.add(btnEdit, gbc); + gbc.gridx++; + JButton btnRemove = new JButton("Delete Outcome"); + btnRemove.addActionListener(evt -> removeOutcome(StoryPoint.DEFAULT_OUTCOME)); + pnlOutcomes.add(btnRemove, gbc); + currentOutcomes.add(StoryPoint.DEFAULT_OUTCOME); + } + + //check for other possible outcomes + List possibleResults = storyPoint.getAllPossibleResults(); + possibleResults.removeAll(currentOutcomes); + if(!possibleResults.isEmpty()) { + JPanel pnlAddOutcomes = new JPanel(new FlowLayout()); + JComboBox comboOutcomes = new JComboBox(); + for (String result : possibleResults) { + comboOutcomes.addItem(result); + } + JButton btnAdd = new JButton("Add Outcome"); + btnAdd.addActionListener(evt -> addOutcome((String) comboOutcomes.getSelectedItem())); + pnlAddOutcomes.add(btnAdd); + pnlAddOutcomes.add(comboOutcomes); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 5; + gbc.fill = GridBagConstraints.NONE; + pnlOutcomes.add(pnlAddOutcomes, gbc); + } + } + + private String getStoryTriggerDescription(List triggers) { + if(triggers.isEmpty()) { + return "-"; + } + StringBuilder sb = new StringBuilder(); + for(StoryTrigger trigger : triggers) { + sb.append(trigger.getDescription()); + sb.append("
"); + } + return sb.toString(); + } + + public void removeOutcome(String result) { + if(result.equals(StoryPoint.DEFAULT_OUTCOME)) { + storyPoint.removeDefaultOutcome(); + } else { + storyPoint.removeStoryOutcome(result); + } + refreshOutcomesPanel(); + pnlOutcomes.revalidate(); + } + + public void checkName() { + btnSaveName.setEnabled(!txtName.getText().equals(storyPoint.getName())); + btnCancelName.setEnabled(!txtName.getText().equals(storyPoint.getName())); + } + + public void saveName(ActionEvent evt) { + if(storyPoint.getStoryArc().isDuplicateName(txtName.getText())) { + JOptionPane.showMessageDialog(getFrame(), "This name is already being used", "Dialog", + JOptionPane.ERROR_MESSAGE); + return; + } + storyPoint.setName(txtName.getText()); + btnSaveName.setEnabled(false); + btnCancelName.setEnabled(false); + editorGUI.refreshStoryPoints(); + } + + public void cancelName(ActionEvent evt) { + txtName.setText(storyPoint.getName()); + btnSaveName.setEnabled(false); + btnCancelName.setEnabled(false); + } + + public void editOutcome(String result) { + CustomizeStoryOutcomeDialog csod = new CustomizeStoryOutcomeDialog(getFrame(), true, result, storyPoint, false); + csod.setVisible(true); + refreshOutcomesPanel(); + pnlOutcomes.revalidate(); + } + + public void addOutcome(String result) { + CustomizeStoryOutcomeDialog csod = new CustomizeStoryOutcomeDialog(getFrame(), true, result, storyPoint, true); + csod.setVisible(true); + refreshOutcomesPanel(); + pnlOutcomes.revalidate(); + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/AdvanceTimeStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/AdvanceTimeStoryTriggerPanel.java new file mode 100644 index 0000000000..84f0f58eb6 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/AdvanceTimeStoryTriggerPanel.java @@ -0,0 +1,41 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.AdvanceTimeStoryTrigger; + +import javax.swing.*; +import java.awt.*; + +public class AdvanceTimeStoryTriggerPanel extends StoryTriggerPanel { + + private JSpinner spinDays; + + public AdvanceTimeStoryTriggerPanel(JFrame frame, String name, AdvanceTimeStoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + getMainPanel().setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.insets = new Insets(5,5,5,5); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + getMainPanel().add(new JLabel("Number of days:"), gbc); + + gbc.gridx = 1; + gbc.weightx = 1.0; + spinDays = new JSpinner(new SpinnerNumberModel(((AdvanceTimeStoryTrigger) getStoryTrigger()).getDays(), + 1, null, 1)); + getMainPanel().add(spinDays, gbc); + } + + @Override + public void updateStoryStrigger() { + ((AdvanceTimeStoryTrigger) getStoryTrigger()).setDays((int) spinDays.getModel().getValue()); + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/ChangeStringVariableStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/ChangeStringVariableStoryTriggerPanel.java new file mode 100644 index 0000000000..8a5c66239f --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/ChangeStringVariableStoryTriggerPanel.java @@ -0,0 +1,51 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryArc; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.ChangeStringVariableStoryTrigger; + +import javax.swing.*; +import java.awt.*; + +public class ChangeStringVariableStoryTriggerPanel extends StoryTriggerPanel { + + private JComboBox comboKey; + private JTextField txtValue; + + public ChangeStringVariableStoryTriggerPanel(JFrame frame, String name, ChangeStringVariableStoryTrigger trigger) { + super(frame, name, trigger); + } + + + @Override + protected void createMainPanel() { + getMainPanel().setLayout(new GridBagLayout()); + + comboKey = new JComboBox<>(); + for(String key : getStoryTrigger().getStoryArc().getCustomStringVariables().keySet()) { + comboKey.addItem(key); + } + comboKey.setSelectedItem(((ChangeStringVariableStoryTrigger) getStoryTrigger()).getKey()); + + txtValue = new JTextField(((ChangeStringVariableStoryTrigger) getStoryTrigger()).getValue()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.insets = new Insets(5,5,5,5); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + getMainPanel().add(comboKey, gbc); + gbc.gridx++; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + getMainPanel().add(txtValue, gbc); + } + + @Override + public void updateStoryStrigger() { + ((ChangeStringVariableStoryTrigger) getStoryTrigger()).setKey((String) comboKey.getSelectedItem()); + ((ChangeStringVariableStoryTrigger) getStoryTrigger()).setValue(txtValue.getText()); + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/CompleteMissionStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/CompleteMissionStoryTriggerPanel.java new file mode 100644 index 0000000000..12b1f1ba97 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/CompleteMissionStoryTriggerPanel.java @@ -0,0 +1,69 @@ +package mekhq.gui.panels.storytriggerpanels; + +import megamek.client.ui.baseComponents.MMComboBox; +import mekhq.campaign.mission.enums.MissionStatus; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storypoint.MissionStoryPoint; +import mekhq.campaign.storyarc.storytrigger.CompleteMissionStoryTrigger; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class CompleteMissionStoryTriggerPanel extends StoryTriggerPanel { + + private List missions; + private JComboBox comboMission; + private MMComboBox comboStatus; + public CompleteMissionStoryTriggerPanel(JFrame frame, String name, CompleteMissionStoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + comboMission = new JComboBox<>(); + // need to track a list of UUIDs so we can get the right one + missions = new ArrayList<>(); + + for(StoryPoint storyPoint : getStoryTrigger().getStoryArc().getStoryPoints()) { + if(storyPoint instanceof MissionStoryPoint) { + comboMission.addItem(storyPoint.getName()); + missions.add(storyPoint.getId()); + } + } + UUID currentStoryPointId = ((CompleteMissionStoryTrigger) getStoryTrigger()).getMissionStoryPointId(); + StoryPoint currentMission = null; + if(currentStoryPointId != null) { + currentMission = getStoryTrigger().getStoryArc().getStoryPoint(currentStoryPointId); + } + if(currentMission != null) { + comboMission.setSelectedItem(currentMission.getName()); + } + + comboStatus = new MMComboBox<>("comboStatus", MissionStatus.values()); + comboStatus.setSelectedItem(((CompleteMissionStoryTrigger) getStoryTrigger()).getMissionStatus()); + + getMainPanel().setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0.5; + gbc.weighty = 1.0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.insets = new Insets(5, 5, 5, 5); + getMainPanel().add(comboMission, gbc); + gbc.gridx = 1; + getMainPanel().add(comboStatus, gbc); + + } + + @Override + public void updateStoryStrigger() { + ((CompleteMissionStoryTrigger) getStoryTrigger()).setMissionStoryPointId(missions.get(comboMission.getSelectedIndex())); + ((CompleteMissionStoryTrigger) getStoryTrigger()).setMissionStatus(comboStatus.getSelectedItem()); + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/EndArcStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/EndArcStoryTriggerPanel.java new file mode 100644 index 0000000000..f8d99fb049 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/EndArcStoryTriggerPanel.java @@ -0,0 +1,21 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryTrigger; + +import javax.swing.*; + +public class EndArcStoryTriggerPanel extends StoryTriggerPanel { + public EndArcStoryTriggerPanel(JFrame frame, String name, StoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + getMainPanel().add(new JLabel("This Story Trigger will finish the story arc.")); + } + + @Override + public void updateStoryStrigger() { + // nothing to update + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/FakeStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/FakeStoryTriggerPanel.java new file mode 100644 index 0000000000..2e52098316 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/FakeStoryTriggerPanel.java @@ -0,0 +1,23 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryTrigger; + +import javax.swing.*; + +public class FakeStoryTriggerPanel extends StoryTriggerPanel { + + public FakeStoryTriggerPanel(JFrame frame, String name, StoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + getMainPanel().add(new JLabel("Test!")); + } + + @Override + public void updateStoryStrigger() { + // nothing to update + } + +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/GameOverStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/GameOverStoryTriggerPanel.java new file mode 100644 index 0000000000..a5771d5eaa --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/GameOverStoryTriggerPanel.java @@ -0,0 +1,22 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryTrigger; + +import javax.swing.*; + +public class GameOverStoryTriggerPanel extends StoryTriggerPanel { + + public GameOverStoryTriggerPanel(JFrame frame, String name, StoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + getMainPanel().add(new JLabel("This Story Trigger will quit the game.")); + } + + @Override + public void updateStoryStrigger() { + // nothing to update + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SetDateStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SetDateStoryTriggerPanel.java new file mode 100644 index 0000000000..08a9d1cfd5 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SetDateStoryTriggerPanel.java @@ -0,0 +1,132 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.MekHQ; +import mekhq.campaign.storyarc.StoryPoint; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storypoint.CheckDateReachedStoryPoint; +import mekhq.campaign.storyarc.storytrigger.SetDateStoryTrigger; +import mekhq.gui.dialog.DateChooser; + +import javax.swing.*; +import java.awt.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class SetDateStoryTriggerPanel extends StoryTriggerPanel { + + private LocalDate date; + private List checkDateStoryPoints; + private JComboBox comboStoryPoints; + private JSpinner spinDays; + private JButton btnDate; + private JCheckBox checkUseDate; + + public SetDateStoryTriggerPanel(JFrame frame, String name, SetDateStoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + comboStoryPoints = new JComboBox<>(); + checkDateStoryPoints = new ArrayList<>(); + for(StoryPoint storyPoint : getStoryTrigger().getStoryArc().getStoryPoints()) { + if(storyPoint instanceof CheckDateReachedStoryPoint) { + comboStoryPoints.addItem(storyPoint.getName()); + checkDateStoryPoints.add(storyPoint.getId()); + } + } + UUID currentStoryPointId = ((SetDateStoryTrigger) getStoryTrigger()).getStoryPointId(); + StoryPoint currentStoryPoint = null; + if(currentStoryPointId != null) { + currentStoryPoint = getStoryTrigger().getStoryArc().getStoryPoint(currentStoryPointId); + } + if(currentStoryPoint != null) { + comboStoryPoints.setSelectedItem(currentStoryPoint.getName()); + } + + date = ((SetDateStoryTrigger) getStoryTrigger()).getDate(); + btnDate = new JButton("None"); + btnDate.setEnabled(false); + if(date != null) { + btnDate.setText(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); + btnDate.setEnabled(true); + } + btnDate.addActionListener(evt -> changeDate()); + + checkUseDate = new JCheckBox("Use Actual Date"); + checkUseDate.setSelected(date != null); + checkUseDate.addActionListener(evt -> checkUseDate()); + + spinDays = new JSpinner(new SpinnerNumberModel(((SetDateStoryTrigger) getStoryTrigger()).getFutureDays(), + 1, null, 1)); + spinDays.setEnabled(date == null); + + getMainPanel().setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.insets = new Insets(5,5,5,5); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + getMainPanel().add(new JLabel("CheckDateReachedStoryPoint:"), gbc); + gbc.gridy++; + gbc.gridwidth = 2; + getMainPanel().add(checkUseDate, gbc); + gbc.gridy++; + gbc.gridwidth = 1; + JLabel lblActualDate = new JLabel("Actual Date:"); + lblActualDate.setToolTipText("Set a specific date."); + getMainPanel().add(lblActualDate, gbc); + gbc.gridy++; + JLabel lblDays = new JLabel("Days in Future:"); + lblDays.setToolTipText("Set a date this many days in the future from when this trigger is executed."); + getMainPanel().add(lblDays, gbc); + + gbc.gridy = 0; + gbc.gridx++; + gbc.weightx = 1.0; + getMainPanel().add(comboStoryPoints, gbc); + gbc.gridy++; + gbc.gridy++; + getMainPanel().add(btnDate, gbc); + gbc.gridy++; + getMainPanel().add(spinDays, gbc); + } + + @Override + public void updateStoryStrigger() { + ((SetDateStoryTrigger) getStoryTrigger()).setStoryPointId(checkDateStoryPoints.get(comboStoryPoints.getSelectedIndex())); + if(checkUseDate.isSelected()) { + ((SetDateStoryTrigger) getStoryTrigger()).setDate(date); + } else { + ((SetDateStoryTrigger) getStoryTrigger()).setFutureDays((int) spinDays.getModel().getValue()); + } + } + + private void changeDate() { + // show the date chooser + DateChooser dc = new DateChooser(getFrame(), date); + // user can either choose a date or cancel by closing + if (dc.showDateChooser() == DateChooser.OK_OPTION) { + date = dc.getDate(); + btnDate.setText(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); + } + } + + private void checkUseDate() { + if(checkUseDate.isSelected()) { + date = LocalDate.of(3025, 1, 1); + btnDate.setText(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); + btnDate.setEnabled(true); + spinDays.setEnabled(false); + } else { + date = null; + btnDate.setText("None"); + btnDate.setEnabled(false); + spinDays.setEnabled(true); + } + } +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/StoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/StoryTriggerPanel.java new file mode 100644 index 0000000000..88ce8733ba --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/StoryTriggerPanel.java @@ -0,0 +1,52 @@ +package mekhq.gui.panels.storytriggerpanels; + +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.gui.baseComponents.AbstractMHQScrollablePanel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; + +public abstract class StoryTriggerPanel extends AbstractMHQScrollablePanel { + + private StoryTrigger storyTrigger; + protected JPanel panMain; + private JButton btnDelete; + + + public StoryTriggerPanel(JFrame frame, String name, StoryTrigger trigger) { + super(frame, name); + this.storyTrigger = trigger; + initialize(); + } + + public JButton getDeleteButton() { + return btnDelete; + } + public StoryTrigger getStoryTrigger() { + return storyTrigger; + } + + protected JPanel getMainPanel() { + return panMain; + } + + @Override + protected void initialize() { + setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(storyTrigger.getClass().getSimpleName()), + BorderFactory.createEmptyBorder(5,5,5,5))); + setLayout(new BorderLayout()); + btnDelete = new JButton("Delete Trigger"); + JPanel panTop = new JPanel(new FlowLayout(FlowLayout.LEFT)); + panTop.add(btnDelete); + add(panTop, BorderLayout.PAGE_START); + panMain = new JPanel(); + createMainPanel(); + add(panMain, BorderLayout.CENTER); + } + + protected abstract void createMainPanel(); + + public abstract void updateStoryStrigger(); +} diff --git a/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SwitchTabStoryTriggerPanel.java b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SwitchTabStoryTriggerPanel.java new file mode 100644 index 0000000000..c258cd1d29 --- /dev/null +++ b/MekHQ/src/mekhq/gui/panels/storytriggerpanels/SwitchTabStoryTriggerPanel.java @@ -0,0 +1,43 @@ +package mekhq.gui.panels.storytriggerpanels; + +import megamek.client.ui.baseComponents.MMComboBox; +import mekhq.campaign.storyarc.StoryTrigger; +import mekhq.campaign.storyarc.storytrigger.SwitchTabStoryTrigger; +import mekhq.gui.enums.MHQTabType; + +import javax.swing.*; +import java.awt.*; + +public class SwitchTabStoryTriggerPanel extends StoryTriggerPanel { + + private MMComboBox comboTab; + + public SwitchTabStoryTriggerPanel(JFrame frame, String name, SwitchTabStoryTrigger trigger) { + super(frame, name, trigger); + } + + @Override + protected void createMainPanel() { + getMainPanel().setLayout(new GridBagLayout()); + comboTab = new MMComboBox<>("comboTab", MHQTabType.values()); + comboTab.setSelectedItem(((SwitchTabStoryTrigger) getStoryTrigger()).getTab()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.insets = new Insets(5,5,5,5); + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + getMainPanel().add(new JLabel("Selected Tab:"), gbc); + gbc.gridx++; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + getMainPanel().add(comboTab, gbc); + } + + @Override + public void updateStoryStrigger() { + ((SwitchTabStoryTrigger) getStoryTrigger()).setTab(comboTab.getSelectedItem()); + } +}