diff --git a/MekHQ/resources/mekhq/resources/CustomizeBotForceDialog.properties b/MekHQ/resources/mekhq/resources/CustomizeBotForceDialog.properties new file mode 100644 index 0000000000..dbb2a594ee --- /dev/null +++ b/MekHQ/resources/mekhq/resources/CustomizeBotForceDialog.properties @@ -0,0 +1,45 @@ +title=Customize BotForce +lblName.text=Name: +lblTeam.text=Team: +panBehavior.title=Behavior +panRandomUnits.title=Random Units +scrollFixedUnits.title=Fixed Units +choiceTeam.text=Team +choiceAllied.text=Allied +lblCowardice.text=Cowardice: +lblSelfPreservation.text=Self-Preservation: +lblAggression.text=Aggression: +lblHerdMentality.text=Herd Mentality: +lblPilotingRisk.text=Piloting Risk: +lblForcedWithdrawal.text=Forced Withdrawal: +lblAutoFlee.text=Auto Flee: +lblBalancingMethod.text=Balancing Method: +lblBalancingMethod.tooltip=What characteristic to balance force multiplier on
(e.g. BV, weight) +lblFaction.text=Faction: +lblFaction.tooltip=Faction for selecting units +lblUnitType.text=Unit Type: +lblUnitType.tooltip=Which unit type to generate. For mixed unit types
use different forces on the same team or use percent
conventional and/or integrated BA chance. +lblSkillLevel.text=Skill Level: +lblSkillLevel.tooltip=Base skill level for random skill generation of the units. +lblQuality.text=Rating: +lblQuality.tooltip=Determines the tech quality of generated units. +lblFocalWeightClass.text=Focal Weight Class: +lblFocalWeightClass.tooltip=The targeted weight class of the generated units. If not specified,
the targeted weight class will be chosen to match the player's force. +lblForceMultiplier.text=Force Multiplier: +lblForceMultiplier.tooltip=How big/tough is this force relative to the player's force?
The final calculation will include fixed units as well. +lblPercentConventional.text=Percent Conventional: +lblPercentConventional.tooltip=Replace a certain percent of a Mek or Aero force
with vehicles or conventional fighters, respectively. +lblBaChance.text=Integrated BA Chance: +lblBaChance.tooltip=Probability that this force includes
integrated Battle Armor units. +lblLanceSize.text=Formation Size: +lblLanceSize.tooltip=The minimum group size that units should be added in.
Larger values can lead to more approximate
results with force multiplier matching. +btnOK.text=Done +btnClose.text=Cancel +btnBehavior.text=Edit Behavior Settings +btnLoadUnits.text=Load Fixed Units +btnLoadUnits.tooltip=Load fixed units from a .mul file,
overwriting existing units +btnSaveUnits.text=Save Fixed Units +btnSaveUnits.tooltip=Save fixed units to a .mul file +btnDeleteUnits.text=Delete Fixed Units +btnDeleteUnits.tooltip=Delete all fixed units +chkUseRandomUnits.text=Use randomly generated units \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/CustomizeScenarioDialog.properties b/MekHQ/resources/mekhq/resources/CustomizeScenarioDialog.properties index 2eacdbca6e..2a3cdffc5f 100644 --- a/MekHQ/resources/mekhq/resources/CustomizeScenarioDialog.properties +++ b/MekHQ/resources/mekhq/resources/CustomizeScenarioDialog.properties @@ -1,7 +1,49 @@ btnCancel.text=Cancel -btnOkay.text=OK -lblName.text=Scenario Name: -lblStatus.text=Status: +btnOkay.text=Done +lblName.text=Scenario Name: +lblDate.text=Scenario Date: +lblDeployment.text=Deployment: +lblStatus.text=Status: title=Customize Scenario title.new=New Scenario -lblDate.text=Date: \ No newline at end of file +btnAddLoot.text=Add Loot +btnEditLoot.text=Edit Loot +btnDeleteLoot.text=Delete Loot +btnAddObjective.text=Add Objective +btnEditObjective.text=Edit Objective +btnDeleteObjective.text=Delete Objective +btnPlanetaryConditions.text=Edit Planetary Conditions... +btnMapSettings.text=Edit Map Settings... +panPlanetaryConditions.title=Planetary Conditions +panDeploymentLimits.title=Deployment Limits +panMap.title=Map Settings +lblAllowedUnits.text=Allowed Unit Types: +lblQuantityLimit.text=Quantity Limit: +lblRequiredPersonnel.text=Required Personnel: +lblRequiredUnits.text=Required Units: +lblLight.text=Light: +lblWeather.text=Weather: +lblWind.text=Wind: +lblFog.text=Fog: +lblBlowingSand.text=Blowing Sand: +lblEMI.text=EMI: +lblAtmosphere.text=Atmosphere: +lblGravity.text=Gravity: +lblTemperature.text=Temperature: +lblOtherConditions.text=Other: +lblBoardType.text=Board Type: +lblMap.text=Map: +lblMapSize.text=Map Size: +emi.text=Electromagnetic interference +sand.text=Blowing sand +panOtherForces.title=Other Forces +btnAddForce.text=Add Force +btnEditForce.text=Edit Force +btnDeleteForce.text=Delete Force +panObjectives.title=Scenario Objectives +panLoot.title=Scenario Costs & Payouts +addEventButton.text=Apply Modifier +txtDesc.title=Description +txtReport.title=After-Action Report +btnEditLimits.text=Edit Limits +btnRemoveLimits.text=Remove Limits diff --git a/MekHQ/resources/mekhq/resources/CustomizeScenarioObjectiveDialog.properties b/MekHQ/resources/mekhq/resources/CustomizeScenarioObjectiveDialog.properties new file mode 100644 index 0000000000..3befcb662f --- /dev/null +++ b/MekHQ/resources/mekhq/resources/CustomizeScenarioObjectiveDialog.properties @@ -0,0 +1,17 @@ +dialog.title=Customize Scenario Objective +panObjectiveEffect.title=Objective Effects +lblDescription.text=Description: +lblDetails.text=Details: +lblObjectiveType.text=Objective Type: +lblForceNames.text=Force Names: +lblTimeLimit.text=Time Limit: +lblMagnitude.text=Amount: +lblEffectType.text=Effect Type: +lblEffectScaling.text=Effect Scaling: +lblEffectCondition.text=Effect Condition: +lblSuccessEffects.text=Effects on Success +lblFailureEffects.text=Effects on Failure +btnAdd.text=Add +btnRemove.text=Remove +btnCancel.text=Cancel +btnOK.text=Done \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/EditDeploymentDialog.properties b/MekHQ/resources/mekhq/resources/EditDeploymentDialog.properties new file mode 100644 index 0000000000..a243e62e3c --- /dev/null +++ b/MekHQ/resources/mekhq/resources/EditDeploymentDialog.properties @@ -0,0 +1,8 @@ +labDeploymentOffset.text=Deployment Zone Offset +labDeploymentOffset.tip=Deployment Zone Offset, in hexes from corresponding map edge +labDeploymentWidth.text=Deployment Zone Width +labDeploymentWidth.tip=Deployment Zone width, in hexes +labDeploymentAnyNW.text=Deployment Zone NW corner +labDeploymentAnySE.text=Deployment Zone SE corner +btnOK.text=Done +btnCancel.text=Cancel \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/EditMapSettingsDialog.properties b/MekHQ/resources/mekhq/resources/EditMapSettingsDialog.properties new file mode 100644 index 0000000000..ffa16929f9 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/EditMapSettingsDialog.properties @@ -0,0 +1,7 @@ +dialog.title=Select Map +checkFixed.text=Use Fixed Map +listMapGenerators.none=None +lblBoardType.text=Board Type: +lblMapSize.text=Map Size: +btnOK.text=Done +btnCancel.text=Cancel \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/EditScenarioDeploymentLimitsDialog.properties b/MekHQ/resources/mekhq/resources/EditScenarioDeploymentLimitsDialog.properties new file mode 100644 index 0000000000..a60fc98277 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/EditScenarioDeploymentLimitsDialog.properties @@ -0,0 +1,8 @@ +dialog.title=Edit Scenario Deployment Limits +panAllowedUnits.title=Allowed Units +checkAllUnits.text=Allow all units +lblQuantityType.text=Quantity Type: +lblCountType.text=Maximum Type: +lblQuantity.text=Maximum Quantity: +btnOK.text=Done +btnCancel.text=Cancel diff --git a/MekHQ/src/mekhq/GameThread.java b/MekHQ/src/mekhq/GameThread.java index 6e1f5c8eb8..6d61d46ebd 100644 --- a/MekHQ/src/mekhq/GameThread.java +++ b/MekHQ/src/mekhq/GameThread.java @@ -174,22 +174,7 @@ public void run() { client.sendMapSettings(mapSettings); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); - PlanetaryConditions planetaryConditions = new PlanetaryConditions(); - planetaryConditions.setLight(scenario.getLight()); - planetaryConditions.setWeather(scenario.getWeather()); - planetaryConditions.setWind(scenario.getWind()); - planetaryConditions.setFog(scenario.getFog()); - planetaryConditions.setAtmosphere(scenario.getAtmosphere()); - planetaryConditions.setTemperature(scenario.getTemperature()); - planetaryConditions.setGravity(scenario.getGravity()); - planetaryConditions.setEMI(scenario.getEMI()); - planetaryConditions.setBlowingSand(scenario.getBlowingSand()); - planetaryConditions.setShiftingWindDirection(scenario.canWindShiftDirection()); - planetaryConditions.setShiftingWindStrength(scenario.canWindShiftStrength()); - planetaryConditions.setWindMax(scenario.getMaxWindStrength()); - planetaryConditions.setWindMin(scenario.getMinWindStrength()); - - client.sendPlanetaryConditions(planetaryConditions); + client.sendPlanetaryConditions(scenario.createPlanetaryConditions()); Thread.sleep(MekHQ.getMHQOptions().getStartGameDelay()); // set player deployment diff --git a/MekHQ/src/mekhq/Utilities.java b/MekHQ/src/mekhq/Utilities.java index fd0eb58ffe..866a797719 100644 --- a/MekHQ/src/mekhq/Utilities.java +++ b/MekHQ/src/mekhq/Utilities.java @@ -32,6 +32,7 @@ import mekhq.campaign.Campaign; import mekhq.campaign.CampaignOptions; import mekhq.campaign.finances.Money; +import mekhq.campaign.mission.IPlayerSettings; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.PersonnelRole; @@ -1285,4 +1286,90 @@ public static MechSummary retrieveOriginalUnit(Entity newE) throws EntityLoading return summary; } + + public static List generateEntityStub(List entities) { + List stub = new ArrayList<>(); + for (Entity en : entities) { + if (null == en) { + stub.add("No random assignment table found for faction"); + } else { + stub.add("" + en.getCrew().getName() + " (" + + en.getCrew().getGunnery() + "/" + + en.getCrew().getPiloting() + "), " + + "" + en.getShortName() + "" + + ""); + } + } + return stub; + } + + /** + * Display a descriptive character string for the deployment parameters in an object that implements IPlayerSettings + * @param player object that implements IPlayerSettings + * @return A character string + */ + public static String getDeploymentString(Player player) { + StringBuilder result = new StringBuilder(""); + + if(player.getStartingPos() >=0 + && player.getStartingPos() <= IStartingPositions.START_LOCATION_NAMES.length) { + result.append(IStartingPositions.START_LOCATION_NAMES[player.getStartingPos()]); + } + + if (player.getStartingPos() == 0) { + int NWx = player.getStartingAnyNWx() + 1; + int NWy = player.getStartingAnyNWy() + 1; + int SEx = player.getStartingAnySEx() + 1; + int SEy = player.getStartingAnySEy() + 1; + if ((NWx + NWy + SEx + SEy) > 0) { + result.append(" (" + NWx + ", " + NWy + ")-(" + SEx + ", " + SEy + ")"); + } + } + int so = player.getStartOffset(); + int sw = player.getStartWidth(); + if ((so != 0) || (sw != 3)) { + result.append(", " + so); + result.append(", " + sw); + } + + return result.toString(); + } + + public static String getDeploymentString(IPlayerSettings settings) { + return getDeploymentString(createPlayer(settings)); + } + + /** + * Create a Player object from IPlayerSettings parameters. Useful for tracking these variables in dialogs. + * @param settings an object that implements IPlayerSettings + * @return A Player object + */ + public static Player createPlayer(IPlayerSettings settings) { + Player p = new Player(1, "fake"); + p.setStartingPos(settings.getStartingPos()); + p.setStartWidth(settings.getStartWidth()); + p.setStartOffset(settings.getStartOffset()); + p.setStartingAnyNWx(settings.getStartingAnyNWx()); + p.setStartingAnyNWy(settings.getStartingAnyNWy()); + p.setStartingAnySEx(settings.getStartingAnySEx()); + p.setStartingAnySEy(settings.getStartingAnySEy()); + + return p; + } + + /** + * Update values of an object that implements IPlayerSettings from a player object + * @param settings An object that implements IPlayerSettings + * @param player A Player object from which to read values + */ + public static void updatePlayerSettings(IPlayerSettings settings, Player player) { + settings.setStartingPos(player.getStartingPos()); + settings.setStartWidth(player.getStartWidth()); + settings.setStartOffset(player.getStartOffset()); + settings.setStartingAnyNWx(player.getStartingAnyNWx()); + settings.setStartingAnyNWy(player.getStartingAnyNWy()); + settings.setStartingAnySEx(player.getStartingAnySEx()); + settings.setStartingAnySEy(player.getStartingAnySEy()); + + } } diff --git a/MekHQ/src/mekhq/campaign/mission/AtBScenario.java b/MekHQ/src/mekhq/campaign/mission/AtBScenario.java index 126cfa250c..7406c3eb1e 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBScenario.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBScenario.java @@ -33,6 +33,7 @@ import megamek.common.planetaryconditions.Atmosphere; import mekhq.MHQConstants; import mekhq.MekHQ; +import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.againstTheBot.AtBConfiguration; import mekhq.campaign.againstTheBot.AtBStaticWeightGenerator; @@ -1501,7 +1502,7 @@ protected BotForce getEnemyBotForce(AtBContract c, int start, int home, List fixedEntityList; @@ -109,6 +107,43 @@ public BotForce(String name, int team, int start, int home, List entityL behaviorSettings.setDestinationEdge(CardinalEdge.NONE); } + @Override + public BotForce clone() { + final BotForce copy = new BotForce(); + copy.setName(this.getName()); + copy.setTeam(this.getTeam()); + copy.setStartingPos(this.getStartingPos()); + copy.setStartOffset(this.getStartOffset()); + copy.setStartWidth(this.getStartWidth()); + copy.setStartingAnyNWx(this.getStartingAnyNWx()); + copy.setStartingAnyNWy(this.getStartingAnyNWy()); + copy.setStartingAnySEx(this.getStartingAnySEx()); + copy.setStartingAnySEy(this.getStartingAnySEy()); + copy.setDeployRound(this.getDeployRound()); + copy.setCamouflage(this.getCamouflage().clone()); + copy.setColour(this.getColour()); + copy.setTemplateName(this.getTemplateName()); + if (this.getBotForceRandomizer() != null) { + copy.setBotForceRandomizer(this.getBotForceRandomizer().clone()); + } + // UUID is immutable so this should work + copy.traitors = traitors.stream().collect(Collectors.toList()); + copy.setBehaviorSettings(new BehaviorSettings()); + copy.getBehaviorSettings().setAutoFlee(this.getBehaviorSettings().shouldAutoFlee()); + copy.getBehaviorSettings().setForcedWithdrawal(this.getBehaviorSettings().isForcedWithdrawal()); + copy.getBehaviorSettings().setDestinationEdge(this.getBehaviorSettings().getDestinationEdge()); + copy.getBehaviorSettings().setRetreatEdge(this.getBehaviorSettings().getRetreatEdge()); + copy.getBehaviorSettings().setBraveryIndex(this.getBehaviorSettings().getBraveryIndex()); + copy.getBehaviorSettings().setFallShameIndex(this.getBehaviorSettings().getFallShameIndex()); + copy.getBehaviorSettings().setHyperAggressionIndex(this.getBehaviorSettings().getHyperAggressionIndex()); + copy.getBehaviorSettings().setSelfPreservationIndex(this.getBehaviorSettings().getSelfPreservationIndex()); + copy.getBehaviorSettings().setHerdMentalityIndex(this.getBehaviorSettings().getHerdMentalityIndex()); + // this bit of trickery seems to work to make a proper copy of the entity list + copy.fixedEntityList = this.getFixedEntityListDirect().stream().collect(Collectors.toList()); + + return copy; + } + /* Convert from MM's Board to Princess's HomeEdge */ public CardinalEdge findCardinalEdge(int start) { switch (start) { @@ -190,36 +225,50 @@ public void setTeam(int team) { this.team = team; } + @Override public int getStartingPos() { return startingPos; } + @Override public void setStartingPos(int start) { this.startingPos = start; } + @Override public int getStartOffset() { return startOffset; } + @Override public void setStartOffset(int startOffset) { this.startOffset = startOffset; } + @Override public int getStartWidth() { return startWidth; } + @Override public void setStartWidth(int startWidth) { this.startWidth = startWidth; } + @Override public int getStartingAnyNWx() { return startingAnyNWx; } + @Override public void setStartingAnyNWx(int startingAnyNWx) { this.startingAnyNWx = startingAnyNWx; } + @Override public int getStartingAnyNWy() { return startingAnyNWy; } + @Override public void setStartingAnyNWy(int startingAnyNWy) { this.startingAnyNWy = startingAnyNWy; } + @Override public int getStartingAnySEx() { return startingAnySEx; } + @Override public void setStartingAnySEx(int startingAnySEx) { this.startingAnySEx = startingAnySEx; } + @Override public int getStartingAnySEy() { return startingAnySEy; } + @Override public void setStartingAnySEy(int startingAnySEy) { this.startingAnySEy = startingAnySEy; } public int getDeployRound() { return deployRound; } @@ -270,6 +319,19 @@ public int getTotalBV(Campaign c) { return bv; } + public int getFixedBV() { + int bv = 0; + + for (Entity entity : getFixedEntityList()) { + if (entity == null) { + LogManager.getLogger().error("Null entity when calculating the BV a bot force, we should never find a null here. Please investigate"); + } else { + bv += entity.calculateBattleValue(true, false); + } + } + return bv; + } + public BehaviorSettings getBehaviorSettings() { return behaviorSettings; } @@ -300,7 +362,7 @@ public void generateRandomForces(List playerUnits, Campaign c) { List existingEntityList = new ArrayList<>(); existingEntityList.addAll(fixedEntityList); existingEntityList.addAll(getTraitorEntities(c)); - generatedEntityList = bfRandomizer.generateForce(playerUnits, existingEntityList); + generatedEntityList = bfRandomizer.generateForce(playerUnits, existingEntityList, c); } public List getTraitorPersons() { @@ -352,6 +414,17 @@ public boolean isTraitor(Unit unit) { return false; } + public BotForceStub generateStub(Campaign c) { + List stubs = Utilities.generateEntityStub(getFullEntityList(c)); + return new BotForceStub("" + + getName() + " " + + ((getTeam() == 1) ? "Allied" : "Enemy") + "" + + " Start: " + IStartingPositions.START_LOCATION_NAMES[getStartingPos()] + + " Fixed BV: " + getTotalBV(c) + + ((null == getBotForceRandomizer()) ? "" : "
Random: " + getBotForceRandomizer().getDescription(c)) + + "", stubs); + } + public void writeToXML(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLOpenTag(pw, indent++, "botForce"); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "name", name); diff --git a/MekHQ/src/mekhq/campaign/mission/BotForceRandomizer.java b/MekHQ/src/mekhq/campaign/mission/BotForceRandomizer.java index 6633f354ac..e7b6769d7c 100644 --- a/MekHQ/src/mekhq/campaign/mission/BotForceRandomizer.java +++ b/MekHQ/src/mekhq/campaign/mission/BotForceRandomizer.java @@ -29,6 +29,7 @@ import megamek.common.annotations.Nullable; import megamek.common.enums.Gender; import megamek.common.enums.SkillLevel; +import mekhq.campaign.rating.IUnitRating; import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Bloodname; @@ -59,7 +60,7 @@ public class BotForceRandomizer { //region Variable declarations public static final int UNIT_WEIGHT_UNSPECIFIED = -1; - private enum BalancingMethod { + public enum BalancingMethod { BV, WEIGHT_ADJ, GENERIC_BV; @@ -101,9 +102,6 @@ public String toString() { /** balancing method **/ private BalancingMethod balancingMethod; - /** convenience campaign pointer **/ - private Campaign campaign; - /** * what percent of mek and aero forces should actually be conventional? * (tanks and conventional aircraft respectively) @@ -126,6 +124,7 @@ public BotForceRandomizer() { factionCode = "MERC"; skill = SkillLevel.REGULAR; unitType = UnitType.MEK; + quality = IUnitRating.DRAGOON_C; forceMultiplier = 1.0; percentConventional = 0; baChance = 0; @@ -135,6 +134,23 @@ public BotForceRandomizer() { } //endregion Constructors + @Override + public BotForceRandomizer clone() { + BotForceRandomizer copy = new BotForceRandomizer(); + copy.setFactionCode(this.getFactionCode()); + copy.skill = this.skill; + copy.unitType = this.unitType; + copy.forceMultiplier = this.forceMultiplier; + copy.percentConventional = this.percentConventional; + copy.baChance = this.baChance; + copy.balancingMethod = this.balancingMethod; + copy.lanceSize = this.lanceSize; + copy.focalWeightClass = this.focalWeightClass; + copy.quality = this.quality; + + return copy; + } + //region Getters/Setters public String getFactionCode() { return factionCode; @@ -144,6 +160,78 @@ public void setFactionCode(final String factionCode) { this.factionCode = factionCode; } + public SkillLevel getSkill() { + return skill; + } + + public void setSkill(SkillLevel s) { + skill = s; + } + + public int getUnitType() { + return unitType; + } + + public void setUnitType(int u) { + unitType = u; + } + + public int getQuality() { + return quality; + } + + public void setQuality(int q) { + quality = q; + } + + public BalancingMethod getBalancingMethod() { + return balancingMethod; + } + + public void setBalancingMethod(BalancingMethod bm) { + balancingMethod = bm; + } + + public double getForceMultiplier() { + return forceMultiplier; + } + + public void setForceMultiplier(double fm) { + forceMultiplier = fm; + } + + public int getPercentConventional() { + return percentConventional; + } + + public void setPercentConventional(int p) { + percentConventional = p; + } + + public int getBaChance() { + return baChance; + } + + public void setBaChance(int b) { + baChance = b; + } + + public int getLanceSize() { + return lanceSize; + } + + public void setLanceSize(int l) { + lanceSize = l; + } + + public double getFocalWeightClass() { + return focalWeightClass; + } + + public void setFocalWeightClass(double f) { + focalWeightClass = f; + } + public double getError() { return error; } @@ -160,9 +248,10 @@ public void setError(double d) { * is used to determine the total points allowed for this force. * @param botFixedEntities A List of The fixed Entities that might have also been declared in BotForce already. * This is used to calculate the starting points already used when generating the force. + * @param campaign A Campaign object which is necessary for various information * @return A List of Entities that will be added to the game by GameThread. */ - public List generateForce(List playerUnits, List botFixedEntities) { + public List generateForce(List playerUnits, List botFixedEntities, Campaign campaign) { ArrayList entityList = new ArrayList<>(); double targetPoints = calculateMaxPoints(playerUnits); @@ -214,8 +303,7 @@ public List generateForce(List playerUnits, List botFixedE && (Compute.randomInt(100) <= percentConventional)) { uType = UnitType.CONV_FIGHTER; } - - lanceList = generateLance(lanceSize, uType, weightClass); + lanceList = generateLance(lanceSize, uType, weightClass, campaign); for (Entity e : lanceList) { entityList.add(e); currentPoints += calculatePoints(e); @@ -252,13 +340,14 @@ public List generateForce(List playerUnits, List botFixedE * @param uType The UnitType of generated units * @param weightClass an int giving the weight class of generated units. The function applies some randomness * to this, so some entities within the lance may be heavier or lighter. + * @param campaign a Campaign object for campaign related information * @return A List of generated entities. */ - public List generateLance(int size, int uType, int weightClass) { + public List generateLance(int size, int uType, int weightClass, Campaign campaign) { ArrayList lanceList = new ArrayList<>(); for (int i = 0; i < size; i++) { - Entity e = getEntity(uType, weightClass); + Entity e = getEntity(uType, weightClass, campaign); if (null != e) { lanceList.add(e); } @@ -268,7 +357,7 @@ public List generateLance(int size, int uType, int weightClass) { if ((unitType == UnitType.MEK) && (baChance > 0) && (Compute.randomInt(100) <= baChance)) { for (int i = 0; i < size; i++) { - Entity e = getEntity(UnitType.BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED); + Entity e = getEntity(UnitType.BATTLE_ARMOR, UNIT_WEIGHT_UNSPECIFIED, campaign); if (null != e) { lanceList.add(e); } @@ -284,9 +373,10 @@ public List generateLance(int size, int uType, int weightClass) { * * @param uType The UnitTableData constant for the type of unit to generate. * @param weightClass The weight class of the unit to generate + * @param campaign A campaign object * @return A new Entity with crew. */ - public Entity getEntity(int uType, int weightClass) { + public Entity getEntity(int uType, int weightClass, Campaign campaign) { MechSummary ms; // allow some variation in actual weight class @@ -311,16 +401,17 @@ public Entity getEntity(int uType, int weightClass) { ms = campaign.getUnitGenerator().generate(params); - return createEntityWithCrew(ms); + return createEntityWithCrew(ms, campaign); } /** * This creates the entity with a crew. Borrows heavily from AtBDynamicScenarioFactory#createEntityWithCrew * * @param ms Which entity to generate + * @param campaign A campaign file * @return A crewed entity */ - public @Nullable Entity createEntityWithCrew(MechSummary ms) { + public @Nullable Entity createEntityWithCrew(MechSummary ms, Campaign campaign) { Entity en; try { en = new MechFileParser(ms.getSourceFile(), ms.getEntryName()).getEntity(); @@ -533,12 +624,21 @@ private double calculateMeanWeightClass(List playerUnits) { return sumWeightClass / ((double) nUnits); } + public String getShortDescription() { + StringBuilder sb = new StringBuilder(); + sb.append(forceMultiplier); + sb.append(" ("); + sb.append(balancingMethod.toString()); + sb.append(")"); + return sb.toString(); + } + /** * This method returns a description of the random parameters of this object that will be shown in the * ScenarioViewPanel * @return a String giving the description. */ - public String getDescription() { + public String getDescription(Campaign campaign) { StringBuilder sb = new StringBuilder(); sb.append(Factions.getInstance().getFaction(factionCode).getFullName(campaign.getGameYear())); sb.append(" "); @@ -576,7 +676,6 @@ public void writeToXML(final PrintWriter pw, int indent) { public static BotForceRandomizer generateInstanceFromXML(Node wn, Campaign c, Version version) { BotForceRandomizer retVal = new BotForceRandomizer(); - retVal.campaign = c; try { // Okay, now load Part-specific fields! NodeList nl = wn.getChildNodes(); diff --git a/MekHQ/src/mekhq/campaign/mission/IPlayerSettings.java b/MekHQ/src/mekhq/campaign/mission/IPlayerSettings.java new file mode 100644 index 0000000000..729c742817 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/mission/IPlayerSettings.java @@ -0,0 +1,46 @@ +/* + * IPlayerSettings.java + * + * Copyright (c) 2024 - MegaMek Team. All rights reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.campaign.mission; + +/** + * This interface allows to identify classes that track player information like deployment, initiative bonuses, + * minefields, etc., so we can identify getter and setter methods across these types. Currently, that applies to + * Scenario and BotForce. + */ +public interface IPlayerSettings { + + // deployment information + int getStartingPos(); + void setStartingPos(int i); + int getStartWidth(); + void setStartWidth(int i); + int getStartOffset(); + void setStartOffset(int i); + int getStartingAnyNWx(); + void setStartingAnyNWx(int i); + int getStartingAnyNWy(); + void setStartingAnyNWy(int i); + int getStartingAnySEx(); + void setStartingAnySEx(int i); + int getStartingAnySEy(); + void setStartingAnySEy(int i); + +} diff --git a/MekHQ/src/mekhq/campaign/mission/Scenario.java b/MekHQ/src/mekhq/campaign/mission/Scenario.java index 104716ec3b..1b2c39ad86 100644 --- a/MekHQ/src/mekhq/campaign/mission/Scenario.java +++ b/MekHQ/src/mekhq/campaign/mission/Scenario.java @@ -23,10 +23,7 @@ import megamek.Version; import megamek.client.ui.swing.lobby.LobbyUtility; -import megamek.common.Board; -import megamek.common.Entity; -import megamek.common.IStartingPositions; -import megamek.common.MapSettings; +import megamek.common.*; import megamek.common.annotations.Nullable; import megamek.common.planetaryconditions.*; import mekhq.MekHQ; @@ -52,7 +49,7 @@ /** * @author Jay Lawson (jaylawson39 at yahoo.com) */ -public class Scenario { +public class Scenario implements IPlayerSettings { //region Variable Declarations public static final int S_DEFAULT_ID = -1; @@ -238,54 +235,67 @@ public int getStartingPos() { return startingPos; } + @Override public void setStartingPos(int start) { this.startingPos = start; } + @Override public int getStartOffset() { return startOffset; } + @Override public void setStartOffset(int startOffset) { this.startOffset = startOffset; } + @Override public int getStartWidth() { return startWidth; } + @Override public void setStartWidth(int startWidth) { this.startWidth = startWidth; } + @Override public int getStartingAnyNWx() { return startingAnyNWx; } + @Override public void setStartingAnyNWx(int startingAnyNWx) { this.startingAnyNWx = startingAnyNWx; } + @Override public int getStartingAnyNWy() { return startingAnyNWy; } + @Override public void setStartingAnyNWy(int startingAnyNWy) { this.startingAnyNWy = startingAnyNWy; } + @Override public int getStartingAnySEx() { return startingAnySEx; } + @Override public void setStartingAnySEx(int startingAnySEx) { this.startingAnySEx = startingAnySEx; } + @Override public int getStartingAnySEy() { return startingAnySEy; } + @Override public void setStartingAnySEy(int startingAnySEy) { this.startingAnySEy = startingAnySEy; } @@ -427,10 +437,58 @@ public BlowingSand getBlowingSand() { public void setMinWindStrength(Wind strength) { this.minWindStrength = strength; } + /** + * Create a PlanetaryConditions object from variables + * @return PlanetaryConditions object + */ + public PlanetaryConditions createPlanetaryConditions() { + PlanetaryConditions planetaryConditions = new PlanetaryConditions(); + planetaryConditions.setLight(getLight()); + planetaryConditions.setWeather(getWeather()); + planetaryConditions.setWind(getWind()); + planetaryConditions.setFog(getFog()); + planetaryConditions.setAtmosphere(getAtmosphere()); + planetaryConditions.setTemperature(getTemperature()); + planetaryConditions.setGravity(getGravity()); + planetaryConditions.setEMI(getEMI()); + planetaryConditions.setBlowingSand(getBlowingSand()); + planetaryConditions.setShiftingWindDirection(canWindShiftDirection()); + planetaryConditions.setShiftingWindStrength(canWindShiftStrength()); + planetaryConditions.setWindMax(getMaxWindStrength()); + planetaryConditions.setWindMin(getMinWindStrength()); + + return planetaryConditions; + } + + /** + * Read the values from a PlanetaryConditions object into the Scenario variables for planetary conditions. + * This is necessary because MekHQ has XML and MegaMek doesn't. + * @param planetaryConditions A PlanetaryConditions object + */ + public void readPlanetaryConditions(PlanetaryConditions planetaryConditions) { + this.setLight(planetaryConditions.getLight()); + this.setWeather(planetaryConditions.getWeather()); + this.setWind(planetaryConditions.getWind()); + this.setFog(planetaryConditions.getFog()); + this.setAtmosphere(planetaryConditions.getAtmosphere()); + this.setTemperature(planetaryConditions.getTemperature()); + this.setGravity(planetaryConditions.getGravity()); + this.setEMI(planetaryConditions.getEMI()); + this.setBlowingSand(planetaryConditions.getBlowingSand()); + this.setShiftWindDirection(planetaryConditions.shiftingWindDirection()); + this.setShiftWindStrength(planetaryConditions.shiftingWindStrength()); + this.setMaxWindStrength(planetaryConditions.getWindMax()); + this.setMinWindStrength(planetaryConditions.getWindMin()); + } + public ScenarioDeploymentLimit getDeploymentLimit() { return deploymentLimit; } + public void setDeploymentLimit(ScenarioDeploymentLimit limit) { + this.deploymentLimit = limit; + } + public Map> getPlayerTransportLinkages() { return playerTransportLinkages; } @@ -551,7 +609,7 @@ public void convertToStub(final Campaign campaign, final ScenarioStatus status) public void generateStub(Campaign c) { stub = new ForceStub(getForces(c), c); for (BotForce bf : botForces) { - botForcesStubs.add(generateBotStub(bf, c)); + botForcesStubs.add(bf.generateStub(c)); } botForces.clear(); } @@ -560,33 +618,6 @@ public ForceStub getForceStub() { return stub; } - public BotForceStub generateBotStub(BotForce bf, Campaign c) { - List stubs = generateEntityStub(bf.getFullEntityList(c)); - return new BotForceStub("" + - bf.getName() + " " + - ((bf.getTeam() == 1) ? "Allied" : "Enemy") + "" + - " Start: " + IStartingPositions.START_LOCATION_NAMES[bf.getStartingPos()] + - " Fixed BV: " + bf.getTotalBV(c) + - ((null == bf.getBotForceRandomizer()) ? "" : "
Random: " + bf.getBotForceRandomizer().getDescription()) + - "", stubs); - } - - public List generateEntityStub(List entities) { - List stub = new ArrayList<>(); - for (Entity en : entities) { - if (null == en) { - stub.add("No random assignment table found for faction"); - } else { - stub.add("" + en.getCrew().getName() + " (" + - en.getCrew().getGunnery() + "/" + - en.getCrew().getPiloting() + "), " + - "" + en.getShortName() + "" + - ""); - } - } - return stub; - } - public boolean isAssigned(Unit unit, Campaign campaign) { for (UUID uid : getForces(campaign).getAllUnits(true)) { if (uid.equals(unit.getId())) { @@ -600,6 +631,10 @@ public List getBotForces() { return botForces; } + public void setBotForces(List bf) { + botForces = bf; + } + public void addBotForce(BotForce botForce, Campaign c) { botForces.add(botForce); @@ -1093,4 +1128,9 @@ public boolean getHasTrack() { public void setHasTrack(boolean b) { hasTrack = b; } + + public static String getBoardTypeName(int i) { + return typeNames[i]; + } + } diff --git a/MekHQ/src/mekhq/campaign/mission/ScenarioDeploymentLimit.java b/MekHQ/src/mekhq/campaign/mission/ScenarioDeploymentLimit.java index deabbc15ba..26cc3b3467 100644 --- a/MekHQ/src/mekhq/campaign/mission/ScenarioDeploymentLimit.java +++ b/MekHQ/src/mekhq/campaign/mission/ScenarioDeploymentLimit.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.UUID; import java.util.Vector; +import java.util.stream.Collectors; /** * This class is optionally used by Scenario to determine any limits on the type and quantity of units that the player @@ -50,7 +51,7 @@ public class ScenarioDeploymentLimit { /** * The QuantityType enum tells this class how to interpret the meaning quantityLimit variable */ - private enum QuantityType { + public enum QuantityType { /** * the PERCENT QuantityType will treat the quantityLimit variable as a percent of the player's valid forces */ @@ -64,7 +65,7 @@ private enum QuantityType { /** * The CountType enum tells this class what the units of the quantity limits should be */ - private enum CountType { + public enum CountType { /** * The BV CountType will limit deployed forces by BV */ @@ -118,6 +119,50 @@ public ScenarioDeploymentLimit() { } //endregion Constructors + public ScenarioDeploymentLimit getCopy() { + ScenarioDeploymentLimit copy = new ScenarioDeploymentLimit(); + copy.quantityLimit = this.quantityLimit; + copy.quantityType = this.quantityType; + copy.countType = this.countType; + copy.requiredPersonnel = this.requiredPersonnel.stream().collect(Collectors.toList()); + copy.requiredPersonnel = this.requiredUnits.stream().collect(Collectors.toList()); + copy.allowedUnitTypes = this.allowedUnitTypes.stream().collect(Collectors.toList()); + + return copy; + } + + public int getQuantityLimit() { + return quantityLimit; + } + + public void setQuantityLimit(int quantityLimit) { + this.quantityLimit = quantityLimit; + } + + public CountType getCountType() { + return countType; + } + + public void setCountType(CountType countType) { + this.countType = countType; + } + + public QuantityType getQuantityType() { + return quantityType; + } + + public void setQuantityType(QuantityType quantityType) { + this.quantityType = quantityType; + } + + public List getAllowedUnitTypes() { + return allowedUnitTypes; + } + + public void setAllowedUnitTypes(List allowedUnitTypes) { + this.allowedUnitTypes = allowedUnitTypes; + } + //region Unit type methods /** * Add a UnitType integer to the allowed unit types list @@ -310,6 +355,9 @@ public String getRequiredPersonnelDesc(Campaign c) { personNames.add(p.getFullName()); } } + if (personNames.isEmpty()) { + return "None"; + } return String.join(", ", personNames); } //endregion Required personnel methods @@ -371,6 +419,9 @@ public String getRequiredUnitDesc(Campaign c) { unitNames.add(u.getName()); } } + if (unitNames.isEmpty()) { + return "None"; + } return String.join(", ", unitNames); } //endregion Required unit methods diff --git a/MekHQ/src/mekhq/campaign/mission/ScenarioObjective.java b/MekHQ/src/mekhq/campaign/mission/ScenarioObjective.java index 9e2d2b4267..113771a808 100644 --- a/MekHQ/src/mekhq/campaign/mission/ScenarioObjective.java +++ b/MekHQ/src/mekhq/campaign/mission/ScenarioObjective.java @@ -140,7 +140,10 @@ public enum ObjectiveAmountType { } public ScenarioObjective() { - + description = ""; + objectiveCriterion = ObjectiveCriterion.Preserve; + destinationEdge = OffBoardDirection.NONE; + percentage = 100; } /** @@ -191,6 +194,14 @@ public void clearForces() { associatedForceNames.clear(); } + public void clearDetails() { + additionalDetails.clear(); + } + + public List getAdditionalDetails() { + return additionalDetails; + } + public void addDetail(String detail) { additionalDetails.add(detail); } @@ -219,6 +230,10 @@ public void clearAssociatedUnits() { associatedUnitIDs.clear(); } + public void clearSuccessEffects() { + successEffects.clear(); + } + public void addSuccessEffect(ObjectiveEffect successEffect) { successEffects.add(successEffect); } @@ -227,6 +242,10 @@ public List getSuccessEffects() { return successEffects; } + public void clearFailureEffects() { + failureEffects.clear(); + } + public void addFailureEffect(ObjectiveEffect failureEffect) { failureEffects.add(failureEffect); } diff --git a/MekHQ/src/mekhq/gui/FileDialogs.java b/MekHQ/src/mekhq/gui/FileDialogs.java index 32c8ba4538..873f9f325e 100644 --- a/MekHQ/src/mekhq/gui/FileDialogs.java +++ b/MekHQ/src/mekhq/gui/FileDialogs.java @@ -225,6 +225,23 @@ public static Optional saveDeployUnits(JFrame frame, Scenario scenario, St return value; } + /** + * Displays a dialog window from which the user can select a .mul file to save to. + * + * @return the file selected, if any + */ + public static Optional saveUnits(JFrame frame, String name) { + Optional value = GUI.fileDialogSave( + frame, + "Save Units", + FileType.MUL, + MekHQ.getUnitsDirectory().getValue(), + name + ".mul"); + + value.ifPresent(x -> MekHQ.getUnitsDirectory().setValue(x.getParent())); + return value; + } + /** * Displays a dialog window from which the user can select a campaign file to open. * diff --git a/MekHQ/src/mekhq/gui/dialog/CustomizeBotForceDialog.java b/MekHQ/src/mekhq/gui/dialog/CustomizeBotForceDialog.java new file mode 100644 index 0000000000..163f91bee9 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/CustomizeBotForceDialog.java @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.gui.dialog; + +import megamek.client.bot.princess.BehaviorSettings; +import megamek.client.bot.princess.PrincessException; +import megamek.client.ui.baseComponents.MMComboBox; +import megamek.client.ui.dialogs.BotConfigDialog; +import megamek.client.ui.dialogs.CamoChooserDialog; +import megamek.common.*; +import megamek.common.enums.SkillLevel; +import megamek.common.icons.Camouflage; +import mekhq.MekHQ; +import mekhq.Utilities; +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.BotForce; +import mekhq.campaign.mission.BotForceRandomizer; +import mekhq.campaign.mission.BotForceRandomizer.BalancingMethod; +import mekhq.campaign.universe.Factions; +import mekhq.gui.FileDialogs; +import mekhq.gui.baseComponents.DefaultMHQScrollablePanel; +import mekhq.gui.displayWrappers.FactionDisplay; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; + +public class CustomizeBotForceDialog extends JDialog { + + private JFrame frame; + private BotForce botForce; + private Campaign campaign; + private Camouflage camo; + private Player player; + private BehaviorSettings behavior; + private BotForceRandomizer randomizer; + private boolean useRandomUnits; + private List fixedEntities; + + //gui components + private JTextField txtName; + private JComboBox choiceTeam; + private JButton btnDeployment; + private JButton btnCamo; + private JPanel panBehavior; + private JPanel panRandomUnits; + private DefaultMHQScrollablePanel panFixedUnits; + private JLabel lblCowardice; + private JLabel lblSelfPreservation; + private JLabel lblAggression; + private JLabel lblHerdMentality; + private JLabel lblPilotingRisk; + private JLabel lblForcedWithdrawal; + private JLabel lblAutoFlee; + private JCheckBox chkUseRandomUnits; + private JSpinner spnForceMultiplier; + private JSpinner spnPercentConventional; + private JSpinner spnBaChance; + private JSpinner spnLanceSize; + private MMComboBox choiceBalancingMethod; + private MMComboBox choiceUnitType; + private MMComboBox choiceSkillLevel; + private MMComboBox choiceFocalWeightClass; + private MMComboBox choiceFaction; + private MMComboBox choiceQuality; + + public CustomizeBotForceDialog(JFrame parent, boolean modal, BotForce bf, Campaign c) { + super(parent, modal); + this.frame = parent; + if (null == bf) { + botForce = new BotForce(); + botForce.setName("New Bot Force"); + // assume enemy by default + botForce.setTeam(2); + } else { + botForce = bf; + } + campaign = c; + player = Utilities.createPlayer(botForce); + camo = botForce.getCamouflage(); + behavior = new BehaviorSettings(); + try { + behavior = botForce.getBehaviorSettings().getCopy(); + } catch (PrincessException ex) { + LogManager.getLogger().error("Error copying princess behaviors", ex); + } + useRandomUnits = botForce.getBotForceRandomizer() != null; + if (useRandomUnits) { + randomizer = botForce.getBotForceRandomizer().clone(); + } else { + randomizer = new BotForceRandomizer(); + } + fixedEntities = botForce.getFixedEntityListDirect().stream().collect(Collectors.toList()); + initComponents(); + setLocationRelativeTo(parent); + pack(); + } + + private void initComponents() { + + final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.CustomizeBotForceDialog", + MekHQ.getMHQOptions().getLocale()); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle(resourceMap.getString("title")); + + getContentPane().setLayout(new BorderLayout()); + JPanel panName = new JPanel(new GridBagLayout()); + JPanel panLeft = new JPanel(new GridBagLayout()); + JPanel panCenter = new JPanel(new GridBagLayout()); + + getContentPane().add(panName, BorderLayout.NORTH); + getContentPane().add(panLeft, BorderLayout.WEST); + getContentPane().add(panCenter, BorderLayout.CENTER); + + JPanel panButtons = new JPanel(new FlowLayout()); + JButton btnOK = new JButton(resourceMap.getString("btnOK.text")); + btnOK.addActionListener(this::done); + JButton btnClose = new JButton(resourceMap.getString("btnClose.text")); + btnClose.addActionListener(this::cancel); + panButtons.add(btnOK); + panButtons.add(btnClose); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.gridwidth = 1; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(5, 5, 5, 5); + panName.add(new JLabel(resourceMap.getString("lblName.text")), gbc); + + txtName = new JTextField(botForce.getName()); + gbc.gridx = 1; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + panName.add(txtName, gbc); + + gbc.gridx = 0; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + panLeft.add(new JLabel(resourceMap.getString("lblTeam.text")), gbc); + + choiceTeam = new JComboBox<>(); + for (int i = 1; i < 6; i++) { + String choice = resourceMap.getString("choiceTeam.text") + " " + i; + if (i ==1) { + choice = choice + " (" + resourceMap.getString("choiceAllied.text") + ")"; + } + choiceTeam.addItem(choice); + } + choiceTeam.setSelectedIndex(botForce.getTeam() - 1); + gbc.gridx = 1; + gbc.weightx = 1.0; + panLeft.add(choiceTeam, gbc); + + gbc.gridx = 0; + gbc.gridy = 1; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + panLeft.add(new JLabel("Deployment:"), gbc); + + btnDeployment = new JButton(Utilities.getDeploymentString(player)); + btnDeployment.addActionListener(evt -> changeDeployment()); + gbc.gridx = 1; + gbc.weightx = 1.0; + panLeft.add(btnDeployment, gbc); + + btnCamo = new JButton(); + btnCamo.setIcon(camo.getImageIcon()); + btnCamo.setMinimumSize(new Dimension(84, 72)); + btnCamo.setPreferredSize(new Dimension(84, 72)); + btnCamo.setMaximumSize(new Dimension(84, 72)); + btnCamo.addActionListener(this::editCamo); + gbc.gridx = 0; + gbc.gridy = 2; + gbc.gridwidth = 2; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + panLeft.add(btnCamo, gbc); + + + intBehaviorPanel(resourceMap); + gbc.gridx = 0; + gbc.gridy = 3; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + panLeft.add(panBehavior, gbc); + + + initRandomForcesPanel(resourceMap); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.gridwidth = 3; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.BOTH; + panCenter.add(panRandomUnits, gbc); + gbc.gridy++; + gbc.weightx = 0.0; + gbc.gridwidth = 1; + gbc.fill = GridBagConstraints.NONE; + JButton btnLoadUnits = new JButton(resourceMap.getString("btnLoadUnits.text")); + btnLoadUnits.setToolTipText(resourceMap.getString("btnLoadUnits.tooltip")); + btnLoadUnits.addActionListener(this::loadUnits); + panCenter.add(btnLoadUnits, gbc); + gbc.gridx++; + JButton btnSaveUnits = new JButton(resourceMap.getString("btnSaveUnits.text")); + btnSaveUnits.setToolTipText(resourceMap.getString("btnSaveUnits.tooltip")); + btnSaveUnits.addActionListener(this::saveUnits); + panCenter.add(btnSaveUnits, gbc); + gbc.gridx++; + gbc.weightx = 1.0; + JButton btnDeleteUnits = new JButton(resourceMap.getString("btnDeleteUnits.text")); + btnDeleteUnits.setToolTipText(resourceMap.getString("btnDeleteUnits.tooltip")); + btnDeleteUnits.addActionListener(this::deleteUnits); + panCenter.add(btnDeleteUnits, gbc); + + panFixedUnits = new DefaultMHQScrollablePanel(frame, "panFixedEntity", new GridBagLayout()); + refreshFixedEntityPanel(); + JScrollPane scrollFixedUnits = new JScrollPane(panFixedUnits); + scrollFixedUnits.setMinimumSize(new Dimension(400, 200)); + scrollFixedUnits.setPreferredSize(new Dimension(400, 200)); + scrollFixedUnits.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("scrollFixedUnits.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.gridwidth = 3; + gbc.fill = GridBagConstraints.BOTH; + panCenter.add(scrollFixedUnits, gbc); + + } + + private void intBehaviorPanel(ResourceBundle resourceMap) { + panBehavior = new JPanel(new GridBagLayout()); + panBehavior.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("panBehavior.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + + GridBagConstraints gbcLeft = new GridBagConstraints(); + gbcLeft.gridx = 0; + gbcLeft.gridy = 0; + gbcLeft.weightx = 0.0; + gbcLeft.weighty = 0.0; + gbcLeft.fill = GridBagConstraints.NONE; + gbcLeft.anchor = GridBagConstraints.WEST; + gbcLeft.insets = new Insets(0, 0, 0, 5); + + GridBagConstraints gbcRight = new GridBagConstraints(); + gbcRight.gridx = 1; + gbcRight.gridy = 0; + gbcRight.weightx = 1.0; + gbcRight.weighty = 0.0; + gbcRight.fill = GridBagConstraints.NONE; + gbcRight.anchor = GridBagConstraints.EAST; + gbcRight.insets = new Insets(0, 5, 0, 0); + + lblCowardice = new JLabel(Integer.toString(behavior.getBraveryIndex())); + lblSelfPreservation = new JLabel(Integer.toString(behavior.getSelfPreservationIndex())); + lblAggression = new JLabel(Integer.toString(behavior.getHyperAggressionIndex())); + lblHerdMentality = new JLabel(Integer.toString(behavior.getHerdMentalityIndex())); + lblPilotingRisk = new JLabel(Integer.toString(behavior.getFallShameIndex())); + lblForcedWithdrawal = new JLabel(getForcedWithdrawalDescription(behavior)); + lblAutoFlee = new JLabel(getAutoFleeDescription(behavior)); + + + panBehavior.add(new JLabel(resourceMap.getString("lblCowardice.text")), gbcLeft); + panBehavior.add(lblCowardice, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblSelfPreservation.text")), gbcLeft); + panBehavior.add(lblSelfPreservation, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblAggression.text")), gbcLeft); + panBehavior.add(lblAggression, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblHerdMentality.text")), gbcLeft); + panBehavior.add(lblHerdMentality, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblPilotingRisk.text")), gbcLeft); + panBehavior.add(lblPilotingRisk, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblForcedWithdrawal.text")), gbcLeft); + panBehavior.add(lblForcedWithdrawal, gbcRight); + gbcLeft.gridy++; + gbcRight.gridy++; + panBehavior.add(new JLabel(resourceMap.getString("lblAutoFlee.text")), gbcLeft); + panBehavior.add(lblAutoFlee, gbcRight); + + JButton btnBehavior = new JButton(resourceMap.getString("btnBehavior.text")); + btnBehavior.addActionListener(this::editBehavior); + gbcLeft.gridy++; + gbcLeft.gridwidth = 2; + panBehavior.add(btnBehavior, gbcLeft); + } + + private void initRandomForcesPanel(ResourceBundle resourceMap) { + panRandomUnits = new JPanel(new GridBagLayout()); + panRandomUnits.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("panRandomUnits.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = 4; + chkUseRandomUnits = new JCheckBox(resourceMap.getString("chkUseRandomUnits.text")); + chkUseRandomUnits.setSelected(useRandomUnits); + chkUseRandomUnits.addActionListener(evt -> { + spnForceMultiplier.setEnabled(chkUseRandomUnits.isSelected()); + spnPercentConventional.setEnabled(chkUseRandomUnits.isSelected()); + spnBaChance.setEnabled(chkUseRandomUnits.isSelected()); + spnLanceSize.setEnabled(chkUseRandomUnits.isSelected()); + choiceFaction.setEnabled(chkUseRandomUnits.isSelected()); + choiceBalancingMethod.setEnabled(chkUseRandomUnits.isSelected()); + choiceUnitType.setEnabled(chkUseRandomUnits.isSelected()); + choiceFocalWeightClass.setEnabled(chkUseRandomUnits.isSelected()); + choiceSkillLevel.setEnabled(chkUseRandomUnits.isSelected()); + choiceQuality.setEnabled(chkUseRandomUnits.isSelected()); + }); + panRandomUnits.add(chkUseRandomUnits, gbc); + + choiceBalancingMethod = new MMComboBox<>("choiceBalancingMethod", BalancingMethod.values()); + choiceBalancingMethod.setSelectedItem(randomizer.getBalancingMethod()); + choiceBalancingMethod.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = 1; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(1, 0, 0, 5); + JLabel lblBalancingMethod = new JLabel(resourceMap.getString("lblBalancingMethod.text")); + lblBalancingMethod.setToolTipText(resourceMap.getString("lblBalancingMethod.tooltip")); + panRandomUnits.add(lblBalancingMethod, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceBalancingMethod, gbc); + + DefaultComboBoxModel factionModel = new DefaultComboBoxModel<>(); + factionModel.addAll(FactionDisplay.getSortedValidFactionDisplays(Factions.getInstance().getFactions(), + campaign.getLocalDate())); + choiceFaction = new MMComboBox<>("choiceFaction", factionModel); + choiceFaction.setSelectedItem(new FactionDisplay(Factions.getInstance().getFaction(randomizer.getFactionCode()), + campaign.getLocalDate())); + choiceFaction.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + JLabel lblFaction = new JLabel(resourceMap.getString("lblFaction.text")); + lblFaction.setToolTipText(resourceMap.getString("lblFaction.tooltip")); + panRandomUnits.add(lblFaction, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceFaction, gbc); + + DefaultComboBoxModel unitTypeModel = new DefaultComboBoxModel<>(); + for (int i = 0; i < UnitType.SIZE; i++) { + unitTypeModel.addElement(UnitType.getTypeName(i)); + } + choiceUnitType = new MMComboBox<>("choiceUnitType", unitTypeModel); + choiceUnitType.setSelectedItem(UnitType.getTypeName(randomizer.getUnitType())); + choiceUnitType.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + JLabel lblUnitType = new JLabel(resourceMap.getString("lblUnitType.text")); + lblUnitType.setToolTipText(resourceMap.getString("lblUnitType.tooltip")); + panRandomUnits.add(lblUnitType, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceUnitType, gbc); + + //leave out none as a skill option + ArrayList skills = Arrays.stream(SkillLevel.values()). + filter(skill -> !skill.isNone()).collect(Collectors.toCollection(() -> new ArrayList<>())); + choiceSkillLevel = new MMComboBox("choiceSkillLevel", skills.toArray()); + choiceSkillLevel.setSelectedItem(randomizer.getSkill()); + choiceSkillLevel.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + JLabel lblSkillLevel = new JLabel(resourceMap.getString("lblSkillLevel.text")); + lblSkillLevel.setToolTipText(resourceMap.getString("lblSkillLevel.tooltip")); + panRandomUnits.add(lblSkillLevel, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceSkillLevel, gbc); + + DefaultComboBoxModel qualityModel = new DefaultComboBoxModel<>(); + qualityModel.addElement("F"); + qualityModel.addElement("D"); + qualityModel.addElement("C"); + qualityModel.addElement("B"); + qualityModel.addElement("A"); + choiceQuality = new MMComboBox<>("choiceQuality", qualityModel); + choiceQuality.setSelectedIndex(randomizer.getQuality()); + choiceQuality.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + JLabel lblQuality = new JLabel(resourceMap.getString("lblQuality.text")); + lblQuality.setToolTipText(resourceMap.getString("lblQuality.tooltip")); + panRandomUnits.add(lblQuality, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceQuality, gbc); + + DefaultComboBoxModel weightClassModel = new DefaultComboBoxModel<>(); + weightClassModel.addElement("Not Specified"); + for (int i = EntityWeightClass.WEIGHT_LIGHT; i <= EntityWeightClass.WEIGHT_ASSAULT; i++) { + weightClassModel.addElement(EntityWeightClass.getClassName(i)); + } + choiceFocalWeightClass = new MMComboBox("choiceFocalWeightClass", weightClassModel); + if (randomizer.getFocalWeightClass() < EntityWeightClass.WEIGHT_LIGHT + || randomizer.getFocalWeightClass() > EntityWeightClass.WEIGHT_ASSAULT) { + choiceFocalWeightClass.setSelectedIndex(0); + } else { + choiceFocalWeightClass.setSelectedItem(EntityWeightClass + .getClassName((int) Math.round(randomizer.getFocalWeightClass()))); + } + choiceFocalWeightClass.setEnabled(useRandomUnits); + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + JLabel lblFocalWeightClass = new JLabel(resourceMap.getString("lblFocalWeightClass.text")); + lblFocalWeightClass.setToolTipText(resourceMap.getString("lblFocalWeightClass.tooltip")); + panRandomUnits.add(lblFocalWeightClass, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + panRandomUnits.add(choiceFocalWeightClass, gbc); + + spnForceMultiplier = new JSpinner(new SpinnerNumberModel(randomizer.getForceMultiplier(), + 0.05, 5, 0.05)); + spnForceMultiplier.setEnabled(useRandomUnits); + gbc.gridx = 2; + gbc.gridy = 1; + gbc.gridwidth = 1; + gbc.weightx = 0.0; + JLabel lblForceMultiplier = new JLabel(resourceMap.getString("lblForceMultiplier.text")); + lblForceMultiplier.setToolTipText(resourceMap.getString("lblForceMultiplier.tooltip")); + panRandomUnits.add(lblForceMultiplier, gbc); + gbc.gridx = 3; + panRandomUnits.add(spnForceMultiplier, gbc); + + spnPercentConventional = new JSpinner(new SpinnerNumberModel(randomizer.getPercentConventional(), + 0, 75, 5)); + spnPercentConventional.setEnabled(useRandomUnits); + gbc.gridx = 2; + gbc.gridy++; + JLabel lblPercentConventional = new JLabel(resourceMap.getString("lblPercentConventional.text")); + lblPercentConventional.setToolTipText(resourceMap.getString("lblPercentConventional.tooltip")); + panRandomUnits.add(lblPercentConventional, gbc); + gbc.gridx = 3; + panRandomUnits.add(spnPercentConventional, gbc); + + spnBaChance = new JSpinner(new SpinnerNumberModel(randomizer.getBaChance(), + 0, 100, 5)); + spnBaChance.setEnabled(useRandomUnits); + gbc.gridx = 2; + gbc.gridy++; + JLabel lblBaChance = new JLabel(resourceMap.getString("lblBaChance.text")); + lblBaChance.setToolTipText(resourceMap.getString("lblBaChance.tooltip")); + panRandomUnits.add(lblBaChance, gbc); + gbc.gridx = 3; + panRandomUnits.add(spnBaChance, gbc); + + spnLanceSize = new JSpinner(new SpinnerNumberModel(randomizer.getLanceSize(), + 0, 6, 1)); + spnLanceSize.setEnabled(useRandomUnits); + gbc.gridx = 2; + gbc.gridy++; + JLabel lblLanceSize = new JLabel(resourceMap.getString("lblLanceSize.text")); + lblLanceSize.setToolTipText(resourceMap.getString("lblLanceSize.tooltip")); + panRandomUnits.add(lblLanceSize, gbc); + gbc.gridx = 3; + panRandomUnits.add(spnLanceSize, gbc); + } + + private void refreshFixedEntityPanel() { + + panFixedUnits.removeAll(); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(2, 5, 0, 0); + for (String en : Utilities.generateEntityStub(fixedEntities)) { + panFixedUnits.add(new JLabel(en), gbc); + gbc.gridy++; + } + panFixedUnits.revalidate(); + panFixedUnits.repaint(); + } + + public BotForce getBotForce() { + return botForce; + } + + private void editBehavior(ActionEvent evt) { + BotConfigDialog bcd = new BotConfigDialog(frame, botForce.getName(), botForce.getBehaviorSettings(), null); + bcd.setVisible(true); + if (!bcd.getResult().isCancelled()) { + behavior = bcd.getBehaviorSettings(); + lblCowardice.setText(Integer.toString(behavior.getBraveryIndex())); + lblSelfPreservation.setText(Integer.toString(behavior.getSelfPreservationIndex())); + lblAggression.setText(Integer.toString(behavior.getHyperAggressionIndex())); + lblHerdMentality.setText(Integer.toString(behavior.getHerdMentalityIndex())); + lblPilotingRisk.setText(Integer.toString(behavior.getFallShameIndex())); + lblForcedWithdrawal.setText(getForcedWithdrawalDescription(behavior)); + lblAutoFlee.setText(getAutoFleeDescription(behavior)); + } + } + + private void editCamo(ActionEvent evt) { + CamoChooserDialog ccd = new CamoChooserDialog(frame, botForce.getCamouflage()); + if (ccd.showDialog().isConfirmed()) { + camo = ccd.getSelectedItem(); + btnCamo.setIcon(camo.getImageIcon()); + } + } + + private void changeDeployment() { + EditDeploymentDialog edd = new EditDeploymentDialog(frame, true, player); + edd.setVisible(true); + btnDeployment.setText(Utilities.getDeploymentString(player)); + } + + private void loadUnits(ActionEvent evt) { + Optional units = FileDialogs.openUnits(frame); + if (units.isPresent()) { + final MULParser parser; + try { + parser = new MULParser(units.get(), campaign.getGameOptions()); + } catch (Exception ex) { + LogManager.getLogger().error("Could not parse BotForce entities", ex); + return; + } + fixedEntities = Collections.list(parser.getEntities().elements()); + refreshFixedEntityPanel(); + } + } + + private void saveUnits(ActionEvent evt) { + Optional saveUnits = FileDialogs.saveUnits(frame, + (!botForce.getName().isEmpty()) ? botForce.getName() : "BotForce"); + + if (saveUnits.isPresent()) { + try { + EntityListFile.saveTo(saveUnits.get(), (ArrayList) fixedEntities); + } catch (Exception ex) { + LogManager.getLogger().error("Could not save BotForce to file", ex); + } + } + } + + private void deleteUnits(ActionEvent evt) { + fixedEntities = new ArrayList(); + refreshFixedEntityPanel(); + } + + private String getForcedWithdrawalDescription(BehaviorSettings behavior) { + if (!behavior.isForcedWithdrawal()) { + return "NONE"; + } else { + return behavior.getRetreatEdge().toString(); + } + } + private String getAutoFleeDescription(BehaviorSettings behavior) { + if (!behavior.shouldAutoFlee()) { + return "NO"; + } else { + return behavior.getDestinationEdge().toString(); + } + } + + private void done(ActionEvent evt) { + botForce.setName(txtName.getText()); + botForce.setTeam(choiceTeam.getSelectedIndex()+1); + Utilities.updatePlayerSettings(botForce, player); + botForce.setCamouflage(camo); + botForce.setBehaviorSettings(behavior); + botForce.setBotForceRandomizer(randomizer); + botForce.setFixedEntityList(fixedEntities); + useRandomUnits = chkUseRandomUnits.isSelected(); + if (useRandomUnits) { + randomizer.setFactionCode(choiceFaction.getSelectedItem().getFaction().getShortName()); + randomizer.setForceMultiplier((double) spnForceMultiplier.getValue()); + randomizer.setPercentConventional((int) spnPercentConventional.getValue()); + randomizer.setBaChance((int) spnBaChance.getValue()); + randomizer.setLanceSize((int) spnLanceSize.getValue()); + randomizer.setFocalWeightClass(choiceFocalWeightClass.getSelectedIndex()); + randomizer.setSkill(choiceSkillLevel.getSelectedItem()); + randomizer.setQuality(choiceQuality.getSelectedIndex()); + randomizer.setUnitType(choiceUnitType.getSelectedIndex()); + randomizer.setBalancingMethod(choiceBalancingMethod.getSelectedItem()); + botForce.setBotForceRandomizer(randomizer); + } else { + botForce.setBotForceRandomizer(null); + } + + this.setVisible(false); + } + private void cancel(ActionEvent evt) { + botForce = null; + this.setVisible(false); + } + +} diff --git a/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioDialog.java b/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioDialog.java index bf51b4a320..8ba9906957 100644 --- a/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioDialog.java @@ -22,14 +22,20 @@ import megamek.client.ui.preferences.JWindowPreference; import megamek.client.ui.preferences.PreferencesNode; +import megamek.client.ui.swing.PlanetaryConditionsDialog; +import megamek.common.Player; +import megamek.common.planetaryconditions.PlanetaryConditions; import mekhq.MekHQ; +import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.mission.*; import mekhq.campaign.mission.atb.AtBScenarioModifier; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.mission.enums.ScenarioStatus; import mekhq.gui.FileDialogs; +import mekhq.gui.model.BotForceTableModel; import mekhq.gui.model.LootTableModel; +import mekhq.gui.model.ObjectiveTableModel; import mekhq.gui.utilities.MarkdownEditorPanel; import org.apache.logging.log4j.LogManager; @@ -39,44 +45,99 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.io.File; +import java.text.DecimalFormat; import java.time.LocalDate; import java.util.ArrayList; +import java.util.List; import java.util.ResourceBundle; +import java.util.stream.Collectors; /** * @author Taharqa */ public class CustomizeScenarioDialog extends JDialog { + + // region Variable declarations private JFrame frame; private Scenario scenario; private Mission mission; private Campaign campaign; private boolean newScenario; private LocalDate date; + private ScenarioDeploymentLimit deploymentLimits; + private PlanetaryConditions planetaryConditions; + private Player player; + private List botForces; - private LootTableModel lootModel; + // map parameters + private int mapSizeX; + private int mapSizeY; + private String map; + private boolean usingFixedMap; + private int boardType; - private JButton btnAdd; - private JButton btnEdit; - private JButton btnDelete; + // objectives + private List objectives; + private JTable objectiveTable; + private ObjectiveTableModel objectiveModel; + + // loot private ArrayList loots; private JTable lootTable; + private LootTableModel lootModel; + + // other forces + private JTable forcesTable; + private BotForceTableModel forcesModel; + + // panels + private JPanel panDeploymentLimits; private JPanel panLoot; + private JPanel panObjectives; + private JPanel panOtherForces; + private JPanel panPlanetaryConditions; + private JPanel panMap; - private JComboBox modifierBox; + // labels + private JLabel lblAllowedUnitsDesc; + private JLabel lblQuantityLimitDesc; + private JLabel lblRequiredPersonnelDesc; + private JLabel lblRequiredUnitsDesc; + private JLabel lblLightDesc; + private JLabel lblWindDesc; + private JLabel lblAtmosphereDesc; + private JLabel lblWeatherDesc; + private JLabel lblFogDesc; + private JLabel lblTemperatureDesc; + private JLabel lblGravityDesc; + private JLabel lblOtherConditionsDesc; + private JLabel lblMap; + private JLabel lblBoardType; + private JLabel lblMapSize; + // end: labels - private JPanel panMain; - private JPanel panBtn; - private JButton btnClose; - private JButton btnOK; - private JLabel lblName; + // textfields private JTextField txtName; - private MarkdownEditorPanel txtDesc; - private MarkdownEditorPanel txtReport; + + // comboboxes + private JComboBox modifierBox; private JComboBox choiceStatus; - private JLabel lblStatus; + + // buttons private JButton btnDate; - private JLabel lblDate; + private JButton btnDeployment; + private JButton btnEditLoot; + private JButton btnDeleteLoot; + + private JButton btnEditObjective; + private JButton btnDeleteObjective; + private JButton btnEditForce; + private JButton btnDeleteForce; + + // markdown editors + private MarkdownEditorPanel txtDesc; + private MarkdownEditorPanel txtReport; + //endregion Variable declarations public CustomizeScenarioDialog(JFrame parent, boolean modal, Scenario s, Mission m, Campaign c) { super(parent, modal); @@ -95,11 +156,38 @@ public CustomizeScenarioDialog(JFrame parent, boolean modal, Scenario s, Mission } date = scenario.getDate(); + if(scenario.getDeploymentLimit() != null) { + deploymentLimits = scenario.getDeploymentLimit().getCopy(); + } + + player = Utilities.createPlayer(scenario); + + planetaryConditions = scenario.createPlanetaryConditions(); + + botForces = new ArrayList<>(); + for(BotForce bf : scenario.getBotForces()) { + botForces.add(bf.clone()); + } + forcesModel = new BotForceTableModel(botForces, campaign); + loots = new ArrayList<>(); for (Loot loot : scenario.getLoot()) { loots.add((Loot) loot.clone()); } lootModel = new LootTableModel(loots); + + objectives = new ArrayList<>(); + for(ScenarioObjective objective : scenario.getScenarioObjectives()) { + objectives.add(new ScenarioObjective(objective)); + } + objectiveModel = new ObjectiveTableModel(objectives); + + map = scenario.getMap(); + mapSizeX = scenario.getMapSizeX(); + mapSizeY = scenario.getMapSizeY(); + usingFixedMap = scenario.isUsingFixedMap(); + boardType = scenario.getBoardType(); + initComponents(); setLocationRelativeTo(parent); setUserPreferences(); @@ -107,58 +195,46 @@ public CustomizeScenarioDialog(JFrame parent, boolean modal, Scenario s, Mission } private void initComponents() { - txtName = new JTextField(); - lblName = new JLabel(); - btnOK = new JButton(); - btnClose = new JButton(); - lblStatus = new JLabel(); - panMain = new JPanel(); - panBtn = new JPanel(); - choiceStatus = new JComboBox<>(); - + getContentPane().setLayout(new BorderLayout()); final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.CustomizeScenarioDialog", MekHQ.getMHQOptions().getLocale()); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setName("Form"); - setTitle(resourceMap.getString("title.new")); + if(newScenario) { + setTitle(resourceMap.getString("title.new")); + } else { + setTitle(resourceMap.getString("title")); + } - getContentPane().setLayout(new BorderLayout()); - panMain.setLayout(new GridBagLayout()); - panBtn.setLayout(new GridLayout(0,2)); - - lblName.setText(resourceMap.getString("lblName.text")); - lblName.setName("lblName"); - GridBagConstraints gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridwidth = 1; - gridBagConstraints.anchor = GridBagConstraints.WEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panMain.add(lblName, gridBagConstraints); + JPanel panMain = new JPanel(new GridBagLayout()); + JPanel panInfo = new JPanel(new GridBagLayout()); + JPanel panWrite = new JPanel(new GridBagLayout()); + JPanel panBtn = new JPanel(new FlowLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 1; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(5, 5, 5, 5); + panInfo.add(new JLabel(resourceMap.getString("lblName.text")), gbc); + txtName = new JTextField(); txtName.setText(scenario.getName()); - txtName.setName("txtName"); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridwidth = 1; - gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = GridBagConstraints.WEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panMain.add(txtName, gridBagConstraints); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(5, 5, 5, 5); + panInfo.add(txtName, gbc); - if (!scenario.getStatus().isCurrent()) { - lblStatus.setText(resourceMap.getString("lblStatus.text")); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(5, 5, 0, 0); - panMain.add(lblStatus, gridBagConstraints); - - choiceStatus.setModel(new DefaultComboBoxModel<>(ScenarioStatus.values())); - choiceStatus.setName("choiceStatus"); - choiceStatus.setSelectedItem(scenario.getStatus()); - choiceStatus.setRenderer(new DefaultListCellRenderer() { + gbc.gridx = 0; + gbc.gridy++; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 5, 0, 0); + panInfo.add(new JLabel(resourceMap.getString("lblStatus.text")), gbc); + + choiceStatus = new JComboBox<>(new DefaultComboBoxModel<>(ScenarioStatus.values())); + choiceStatus.setSelectedItem(scenario.getStatus()); + choiceStatus.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, @@ -169,64 +245,46 @@ public Component getListCellRendererComponent(final JList list, final Object } return this; } - }); - gridBagConstraints.gridx = 1; - gridBagConstraints.insets = new Insets(5, 5, 0, 0); - panMain.add(choiceStatus, gridBagConstraints); - } - - lblDate = new JLabel(resourceMap.getString("lblDate.text")); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.gridwidth = 1; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.anchor = GridBagConstraints.WEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panMain.add(lblDate, gridBagConstraints); - - btnDate = new JButton(); - btnDate.setText(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); + }); + gbc.gridx = 1; + gbc.insets = new Insets(5, 5, 0, 0); + choiceStatus.setEnabled(!scenario.getStatus().isCurrent()); + panInfo.add(choiceStatus, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.insets = new Insets(5, 5, 5, 5); + panInfo.add(new JLabel(resourceMap.getString("lblDate.text")), gbc); + + btnDate = new JButton(MekHQ.getMHQOptions().getDisplayFormattedDate(date)); btnDate.addActionListener(evt -> changeDate()); - gridBagConstraints.gridx = 1; - gridBagConstraints.insets = new Insets(5, 5, 0, 0); - panMain.add(btnDate, gridBagConstraints); - - if (scenario.getStatus().isCurrent()) { - initLootPanel(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.gridwidth = 2; - gridBagConstraints.anchor = GridBagConstraints.WEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panLoot.setPreferredSize(new Dimension(400,150)); - panLoot.setMinimumSize(new Dimension(400,150)); - panLoot.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createTitledBorder("Scenario Costs & Payouts"), - BorderFactory.createEmptyBorder(5,5,5,5))); - panMain.add(panLoot, gridBagConstraints); - } - - txtDesc = new MarkdownEditorPanel("Description"); - txtDesc.setText(scenario.getDescription()); - txtDesc.setMinimumSize(new Dimension(400, 100)); - txtDesc.setPreferredSize(new Dimension(400, 250)); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.gridwidth = 2; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.fill = GridBagConstraints.BOTH; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panMain.add(txtDesc, gridBagConstraints); + gbc.gridx = 1; + gbc.gridwidth = 1; + gbc.insets = new Insets(5, 5, 0, 0); + panInfo.add(btnDate, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.insets = new Insets(5, 5, 5, 5); + panInfo.add(new JLabel(resourceMap.getString("lblDeployment.text")), gbc); + + btnDeployment = new JButton(Utilities.getDeploymentString(player)); + btnDeployment.setEnabled(scenario.getStatus().isCurrent()); + btnDeployment.addActionListener(evt -> changeDeployment()); + gbc.gridx = 1; + gbc.gridwidth = 1; + gbc.insets = new Insets(5, 5, 0, 0); + panInfo.add(btnDeployment, gbc); if (scenario.getStatus().isCurrent() && (scenario instanceof AtBDynamicScenario)) { - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; modifierBox = new JComboBox<>(); - EventTiming scenarioState = ((AtBDynamicScenario) scenario).getNumBots() > 0 ? + EventTiming scenarioState = scenario.getNumBots() > 0 ? EventTiming.PostForceGeneration : EventTiming.PreForceGeneration; for (String modifierKey : AtBScenarioModifier.getOrderedModifierKeys()) { @@ -234,28 +292,81 @@ public Component getListCellRendererComponent(final JList list, final Object modifierBox.addItem(modifierKey); } } - panMain.add(modifierBox, gridBagConstraints); + panInfo.add(modifierBox, gbc); - JButton addEventButton = new JButton("Apply Modifier"); + JButton addEventButton = new JButton(resourceMap.getString("addEventButton.text")); addEventButton.addActionListener(this::btnAddModifierActionPerformed); - gridBagConstraints.gridx = 1; - panMain.add(addEventButton, gridBagConstraints); + gbc.gridx = 1; + panInfo.add(addEventButton, gbc); } + initDeployLimitPanel(resourceMap); + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 2; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + panInfo.add(panDeploymentLimits, gbc); + + initPlanetaryConditionsPanel(resourceMap); + gbc.gridy++; + panInfo.add(panPlanetaryConditions, gbc); + + initMapPanel(resourceMap); + gbc.gridy++; + panInfo.add(panMap, gbc); + + initObjectivesPanel(resourceMap); + panObjectives.setPreferredSize(new Dimension(400,150)); + panObjectives.setMinimumSize(new Dimension(400,150)); + panObjectives.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("panObjectives.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + + initLootPanel(resourceMap); + panLoot.setPreferredSize(new Dimension(400,150)); + panLoot.setMinimumSize(new Dimension(400,150)); + panLoot.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("panLoot.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + + initOtherForcesPanel(resourceMap); + panOtherForces.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 10, 0), + BorderFactory.createTitledBorder(resourceMap.getString("panOtherForces.title")))); + panOtherForces.setPreferredSize(new Dimension(600,250)); + panOtherForces.setMinimumSize(new Dimension(600,250)); + + txtDesc = new MarkdownEditorPanel(resourceMap.getString("txtDesc.title")); + txtDesc.setText(scenario.getDescription()); + txtDesc.setMinimumSize(new Dimension(400, 100)); + txtDesc.setPreferredSize(new Dimension(400, 250)); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.insets = new Insets(5, 5, 5, 5); + panWrite.add(txtDesc, gbc); + if (!scenario.getStatus().isCurrent()) { - txtReport = new MarkdownEditorPanel("After-Action Report"); + txtReport = new MarkdownEditorPanel(resourceMap.getString("txtReport.title")); txtReport.setText(scenario.getReport()); txtReport.setMinimumSize(new Dimension(400, 100)); txtReport.setPreferredSize(new Dimension(400, 250)); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy++; - gridBagConstraints.gridwidth = 2; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.fill = GridBagConstraints.BOTH; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - panMain.add(txtReport, gridBagConstraints); + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.insets = new Insets(5, 5, 5, 5); + panWrite.add(txtReport, gbc); + txtReport.setEnabled(!scenario.getStatus().isCurrent()); } if (newScenario && (mission instanceof AtBContract)) { @@ -267,7 +378,7 @@ public Component getListCellRendererComponent(final JList list, final Object (scenario.getStatus().isCurrent())) { JButton btnFinalize = new JButton(); - if (((AtBDynamicScenario) scenario).getNumBots() > 0) { + if (scenario.getNumBots() > 0) { btnFinalize.setText("Regenerate Bot Forces"); } else { btnFinalize.setText("Generate Bot Forces"); @@ -277,23 +388,52 @@ public Component getListCellRendererComponent(final JList list, final Object panBtn.add(btnFinalize); } - btnOK.setText(resourceMap.getString("btnOkay.text")); - btnOK.setName("btnOK"); + JButton btnOK = new JButton(resourceMap.getString("btnOkay.text")); btnOK.addActionListener(this::btnOKActionPerformed); panBtn.add(btnOK); - btnClose.setText(resourceMap.getString("btnCancel.text")); - btnClose.setName("btnClose"); + JButton btnClose = new JButton(resourceMap.getString("btnCancel.text")); btnClose.addActionListener(this::btnCloseActionPerformed); - gridBagConstraints.gridx = GridBagConstraints.RELATIVE; - gridBagConstraints.gridwidth = 1; - gridBagConstraints.anchor = GridBagConstraints.CENTER; - gridBagConstraints.insets = new Insets(5, 5, 0, 0); panBtn.add(btnClose); getContentPane().add(panMain, BorderLayout.CENTER); getContentPane().add(panBtn, BorderLayout.PAGE_END); + JPanel panNW = new JPanel(new GridBagLayout()); + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridheight = 2; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + panNW.add(panInfo, gbc); + gbc.gridx = 1; + gbc.gridheight = 1; + gbc.fill = GridBagConstraints.BOTH; + panNW.add(panObjectives, gbc); + gbc.gridy = 1; + panNW.add(panLoot, gbc); + + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.BOTH; + panMain.add(panNW, gbc); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.gridheight = 2; + gbc.weighty = 1.0; + panMain.add(panWrite, gbc); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridheight = 1; + panMain.add(panOtherForces, gbc); + pack(); } @@ -321,7 +461,12 @@ private void btnOKActionPerformed(ActionEvent evt) { scenario.setStatus((ScenarioStatus) choiceStatus.getSelectedItem()); } } + scenario.setDeploymentLimit(deploymentLimits); + Utilities.updatePlayerSettings(scenario, player); + scenario.readPlanetaryConditions(planetaryConditions); scenario.setDate(date); + scenario.setBotForces(botForces); + scenario.setScenarioObjectives(objectives); scenario.resetLoot(); for (Loot loot : lootModel.getAllLoot()) { scenario.addLoot(loot); @@ -329,6 +474,11 @@ private void btnOKActionPerformed(ActionEvent evt) { if (newScenario) { campaign.addScenario(scenario, mission); } + scenario.setMap(map); + scenario.setMapSizeX(mapSizeX); + scenario.setMapSizeY(mapSizeY); + scenario.setBoardType(boardType); + scenario.setUsingFixedMap(usingFixedMap); this.setVisible(false); } @@ -386,23 +536,451 @@ private void changeDate() { } } - private void initLootPanel() { + private void changeDeployment() { + EditDeploymentDialog edd = new EditDeploymentDialog(frame, true, player); + edd.setVisible(true); + btnDeployment.setText(Utilities.getDeploymentString(player)); + } + + private void initDeployLimitPanel(ResourceBundle resourceMap) { + + panDeploymentLimits = new JPanel(new GridBagLayout()); + panDeploymentLimits.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 10, 0), + BorderFactory.createTitledBorder(resourceMap.getString("panDeploymentLimits.title")))); + + JPanel panButtons = new JPanel(new GridLayout(0, 2)); + JButton btnEditLimits = new JButton(resourceMap.getString("btnEditLimits.text")); + btnEditLimits.setEnabled(scenario.getStatus().isCurrent()); + btnEditLimits.addActionListener(this::editLimits); + panButtons.add(btnEditLimits); + JButton btnRemoveLimits = new JButton(resourceMap.getString("btnRemoveLimits.text")); + btnRemoveLimits.setEnabled(scenario.getStatus().isCurrent()); + btnRemoveLimits.addActionListener(this::removeLimits); + panButtons.add(btnRemoveLimits); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.NORTHWEST; + panDeploymentLimits.add(panButtons, gbc); + + GridBagConstraints leftGbc = new GridBagConstraints(); + leftGbc.gridx = 0; + leftGbc.gridy = 1; + leftGbc.gridwidth = 1; + leftGbc.weightx = 0.0; + leftGbc.weighty = 0.0; + leftGbc.insets = new Insets(0, 0, 5, 10); + leftGbc.fill = GridBagConstraints.NONE; + leftGbc.anchor = GridBagConstraints.NORTHWEST; + + GridBagConstraints rightGbc = new GridBagConstraints(); + rightGbc.gridx = 1; + rightGbc.gridy = 1; + rightGbc.gridwidth = 1; + rightGbc.weightx = 1.0; + rightGbc.weighty = 0.0; + rightGbc.insets = new Insets(0, 10, 5, 0); + rightGbc.fill = GridBagConstraints.HORIZONTAL; + rightGbc.anchor = GridBagConstraints.NORTHWEST; + + leftGbc.gridy++; + panDeploymentLimits.add(new JLabel(resourceMap.getString("lblAllowedUnits.text")), leftGbc); + + lblAllowedUnitsDesc = new JLabel(); + rightGbc.gridy++; + panDeploymentLimits.add(lblAllowedUnitsDesc, rightGbc); + + leftGbc.gridy++; + panDeploymentLimits.add(new JLabel(resourceMap.getString("lblQuantityLimit.text")), leftGbc); + + lblQuantityLimitDesc = new JLabel(); + rightGbc.gridy++; + panDeploymentLimits.add(lblQuantityLimitDesc, rightGbc); + + leftGbc.gridy++; + panDeploymentLimits.add(new JLabel(resourceMap.getString("lblRequiredPersonnel.text")), leftGbc); + + lblRequiredPersonnelDesc = new JLabel(); + rightGbc.gridy++; + panDeploymentLimits.add(lblRequiredPersonnelDesc, rightGbc); + + leftGbc.gridy++; + panDeploymentLimits.add(new JLabel(resourceMap.getString("lblRequiredUnits.text")), leftGbc); + + lblRequiredUnitsDesc = new JLabel(); + rightGbc.gridy++; + panDeploymentLimits.add(lblRequiredUnitsDesc, rightGbc); + + refreshDeploymentLimits(); + } + + private void refreshDeploymentLimits() { + if (deploymentLimits != null) { + lblAllowedUnitsDesc.setText("" + deploymentLimits.getAllowedUnitTypeDesc() + ""); + lblQuantityLimitDesc.setText("" +deploymentLimits.getQuantityLimitDesc(scenario, campaign) + ""); + lblRequiredPersonnelDesc.setText("" + deploymentLimits.getRequiredPersonnelDesc(campaign) + ""); + lblRequiredUnitsDesc.setText("" + deploymentLimits.getRequiredUnitDesc(campaign) + ""); + } else { + lblAllowedUnitsDesc.setText("All"); + lblQuantityLimitDesc.setText("No Limits"); + lblRequiredPersonnelDesc.setText("None"); + lblRequiredUnitsDesc.setText("None"); + } + } + + private void editLimits(ActionEvent evt) { + EditScenarioDeploymentLimitDialog esdld = new EditScenarioDeploymentLimitDialog(frame, true, deploymentLimits); + esdld.setVisible(true); + deploymentLimits = esdld.getDeploymentLimit(); + refreshDeploymentLimits(); + } + + private void removeLimits(ActionEvent evt) { + deploymentLimits = null; + refreshDeploymentLimits(); + } + + private void initPlanetaryConditionsPanel(ResourceBundle resourceMap) { + panPlanetaryConditions = new JPanel(new GridBagLayout()); + panPlanetaryConditions.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 10, 0), + BorderFactory.createTitledBorder(resourceMap.getString("panPlanetaryConditions.title")))); + + JButton btnPlanetaryConditions = new JButton(resourceMap.getString("btnPlanetaryConditions.text")); + btnPlanetaryConditions.addActionListener(evt -> changePlanetaryConditions()); + btnPlanetaryConditions.setEnabled(scenario.getStatus().isCurrent()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 4; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 0, 0, 0); + panPlanetaryConditions.add(btnPlanetaryConditions, gbc); + + GridBagConstraints leftGbc = new GridBagConstraints(); + leftGbc.gridx = 0; + leftGbc.gridy = 0; + leftGbc.gridwidth = 1; + leftGbc.weightx = 0.0; + leftGbc.weighty = 0.0; + leftGbc.insets = new Insets(0, 5, 5, 5); + leftGbc.fill = GridBagConstraints.NONE; + leftGbc.anchor = GridBagConstraints.NORTHWEST; + + GridBagConstraints rightGbc = new GridBagConstraints(); + rightGbc.gridx = 1; + rightGbc.gridy = 0; + rightGbc.gridwidth = 1; + rightGbc.weightx = 0.0; + rightGbc.weighty = 0.0; + rightGbc.insets = new Insets(0, 5, 5, 0); + rightGbc.fill = GridBagConstraints.NONE; + rightGbc.anchor = GridBagConstraints.NORTHWEST; + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblLight.text")), leftGbc); + + lblLightDesc = new JLabel(scenario.getLight().toString()); + rightGbc.gridy++; + panPlanetaryConditions.add(lblLightDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblWeather.text")), leftGbc); + + lblWeatherDesc = new JLabel(scenario.getWeather().toString()); + rightGbc.gridy++; + panPlanetaryConditions.add(lblWeatherDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblWind.text")), leftGbc); + + lblWindDesc = new JLabel(scenario.getWind().toString()); + rightGbc.gridy++; + panPlanetaryConditions.add(lblWindDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblFog.text")), leftGbc); + + lblFogDesc = new JLabel(scenario.getFog().toString()); + rightGbc.gridy++; + rightGbc.weightx = 1.0; + panPlanetaryConditions.add(lblFogDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblOtherConditions.text")), leftGbc); + + ArrayList otherConditions = new ArrayList<>(); + if (scenario.getEMI().isEMI()) { + otherConditions.add(resourceMap.getString("emi.text")); + } + if (scenario.getBlowingSand().isBlowingSand()) { + otherConditions.add(resourceMap.getString("sand.text")); + } + + lblOtherConditionsDesc = new JLabel(String.join(", ", otherConditions)); + if (otherConditions.isEmpty()) { + lblOtherConditionsDesc.setText("None"); + } + rightGbc.gridy++; + rightGbc.gridwidth = 3; + panPlanetaryConditions.add(lblOtherConditionsDesc, rightGbc); + + leftGbc.gridx = 2; + leftGbc.gridy = 1; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblTemperature.text")), leftGbc); + + lblTemperatureDesc = new JLabel(PlanetaryConditions.getTemperatureDisplayableName(scenario.getTemperature())); + rightGbc.gridx = 3; + rightGbc.gridy = 1; + rightGbc.gridwidth = 1; + panPlanetaryConditions.add(lblTemperatureDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblGravity.text")), leftGbc); + + lblGravityDesc = new JLabel(DecimalFormat.getInstance().format(scenario.getGravity())); + rightGbc.gridy++; + panPlanetaryConditions.add(lblGravityDesc, rightGbc); + + leftGbc.gridy++; + panPlanetaryConditions.add(new JLabel(resourceMap.getString("lblAtmosphere.text")), leftGbc); + + lblAtmosphereDesc = new JLabel(scenario.getAtmosphere().toString()); + rightGbc.gridy++; + panPlanetaryConditions.add(lblAtmosphereDesc, rightGbc); + } + + private void refreshPlanetaryConditions() { + lblLightDesc.setText(planetaryConditions.getLight().toString()); + lblAtmosphereDesc.setText(planetaryConditions.getAtmosphere().toString()); + lblWeatherDesc.setText(planetaryConditions.getWeather().toString()); + lblFogDesc.setText(planetaryConditions.getFog().toString()); + lblWindDesc.setText(planetaryConditions.getWind().toString()); + lblGravityDesc.setText(DecimalFormat.getInstance().format(planetaryConditions.getGravity())); + lblTemperatureDesc.setText(PlanetaryConditions.getTemperatureDisplayableName(planetaryConditions.getTemperature())); + ArrayList otherConditions = new ArrayList<>(); + if (planetaryConditions.getEMI().isEMI()) { + otherConditions.add("Electromagnetic interference"); + } + if (planetaryConditions.getBlowingSand().isBlowingSand()) { + otherConditions.add("Blowing sand"); + } + if (otherConditions.isEmpty()) { + lblOtherConditionsDesc.setText("None"); + } else { + lblOtherConditionsDesc.setText(String.join(", ", otherConditions)); + } + } + + private void changePlanetaryConditions() { + PlanetaryConditionsDialog pc = new PlanetaryConditionsDialog(frame, planetaryConditions); + if(pc.showDialog()) { + planetaryConditions = pc.getConditions(); + } + refreshPlanetaryConditions(); + } + + private void initMapPanel(ResourceBundle resourceMap) { + panMap = new JPanel(new GridBagLayout()); + panMap.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 10, 0), + BorderFactory.createTitledBorder(resourceMap.getString("panMap.title")))); + + JButton btnMapSettings = new JButton(resourceMap.getString("btnMapSettings.text")); + btnMapSettings.addActionListener(evt -> changeMapSettings()); + btnMapSettings.setEnabled(scenario.getStatus().isCurrent()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.gridwidth = 2; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 0, 0, 0); + panMap.add(btnMapSettings, gbc); + + GridBagConstraints leftGbc = new GridBagConstraints(); + leftGbc.gridx = 0; + leftGbc.gridy = 1; + leftGbc.gridwidth = 1; + leftGbc.weightx = 0.0; + leftGbc.weighty = 0.0; + leftGbc.insets = new Insets(0, 5, 5, 5); + leftGbc.fill = GridBagConstraints.NONE; + leftGbc.anchor = GridBagConstraints.NORTHWEST; + + GridBagConstraints rightGbc = new GridBagConstraints(); + rightGbc.gridx = 1; + rightGbc.gridy = 1; + rightGbc.gridwidth = 1; + rightGbc.weightx = 1.0; + rightGbc.weighty = 0.0; + rightGbc.insets = new Insets(0, 5, 5, 0); + rightGbc.fill = GridBagConstraints.NONE; + rightGbc.anchor = GridBagConstraints.NORTHWEST; + + panMap.add(new JLabel(resourceMap.getString("lblBoardType.text")), leftGbc); + lblBoardType = new JLabel(Scenario.getBoardTypeName(boardType)); + panMap.add(lblBoardType, rightGbc); + + leftGbc.gridy++; + rightGbc.gridy++; + panMap.add(new JLabel(resourceMap.getString("lblMap.text")), leftGbc); + StringBuilder sb = new StringBuilder(); + if(map == null) { + sb.append("None"); + } else { + sb.append(map).append(usingFixedMap ? " (Fixed)" : " (Random)"); + } + lblMap = new JLabel(sb.toString()); + panMap.add(lblMap, rightGbc); + + leftGbc.gridy++; + rightGbc.gridy++; + panMap.add(new JLabel(resourceMap.getString("lblMapSize.text")), leftGbc); + sb = new StringBuilder(); + sb.append(mapSizeX).append(" x ").append(mapSizeY); + lblMapSize = new JLabel(sb.toString()); + panMap.add(lblMapSize, rightGbc); + } + + private void refreshMapSettings() { + lblBoardType.setText(Scenario.getBoardTypeName(boardType)); + StringBuilder sb = new StringBuilder(); + if(map == null) { + sb.append("None"); + } else { + sb.append(map).append(usingFixedMap ? " (Fixed)" : " (Random)"); + } + lblMap.setText(sb.toString()); + sb = new StringBuilder(); + sb.append(mapSizeX).append(" x ").append(mapSizeY); + lblMapSize.setText(sb.toString()); + } + + private void changeMapSettings() { + EditMapSettingsDialog emsd = new EditMapSettingsDialog(frame, true, boardType, usingFixedMap, + map, mapSizeX, mapSizeY); + emsd.setVisible(true); + boardType = emsd.getBoardType(); + usingFixedMap = emsd.getUsingFixedMap(); + map = emsd.getMap(); + mapSizeX = emsd.getMapSizeX(); + mapSizeY = emsd.getMapSizeY(); + refreshMapSettings(); + } + + private void initObjectivesPanel(ResourceBundle resourceMap) { + panObjectives = new JPanel(new BorderLayout()); + + JPanel panBtns = new JPanel(new GridLayout(1,0)); + JButton btnAddObjective = new JButton(resourceMap.getString("btnAddObjective.text")); + btnAddObjective.addActionListener(evt -> addObjective()); + btnAddObjective.setEnabled(scenario.getStatus().isCurrent()); + panBtns.add(btnAddObjective); + + btnEditObjective = new JButton(resourceMap.getString("btnEditObjective.text")); + btnEditObjective.setEnabled(false); + btnEditObjective.addActionListener(evt -> editObjective()); + panBtns.add(btnEditObjective); + + btnDeleteObjective = new JButton(resourceMap.getString("btnDeleteObjective.text")); + btnDeleteObjective.setEnabled(false); + btnDeleteObjective.addActionListener(evt -> deleteObjective()); + panBtns.add(btnDeleteObjective); + panObjectives.add(panBtns, BorderLayout.PAGE_START); + + objectiveTable = new JTable(objectiveModel); + TableColumn column; + for (int i = 0; i < ObjectiveTableModel.N_COL; i++) { + column = objectiveTable.getColumnModel().getColumn(i); + column.setPreferredWidth(objectiveModel.getColumnWidth(i)); + column.setCellRenderer(objectiveModel.getRenderer()); + } + objectiveTable.setIntercellSpacing(new Dimension(0, 0)); + objectiveTable.setShowGrid(false); + objectiveTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + objectiveTable.getSelectionModel().addListSelectionListener(this::objectiveTableValueChanged); + + panObjectives.add(new JScrollPane(objectiveTable), BorderLayout.CENTER); + + } + + private void objectiveTableValueChanged(ListSelectionEvent evt) { + int row = objectiveTable.getSelectedRow(); + btnDeleteObjective.setEnabled(row != -1); + btnEditObjective.setEnabled(row != -1); + } + + private List getBotForceNames() { + return botForces.stream().map(BotForce::getName).collect(Collectors.toCollection(ArrayList::new)); + } + + private void addObjective() { + CustomizeScenarioObjectiveDialog csod = new CustomizeScenarioObjectiveDialog(frame, true, + new ScenarioObjective(), getBotForceNames()); + csod.setVisible(true); + if (null != csod.getObjective()) { + objectives.add(csod.getObjective()); + } + refreshObjectiveTable(); + } + + private void editObjective() { + ScenarioObjective objective = objectiveModel.getObjectiveAt(objectiveTable.getSelectedRow()); + if (null != objective) { + CustomizeScenarioObjectiveDialog csod = new CustomizeScenarioObjectiveDialog(frame, true, objective, + getBotForceNames()); + csod.setVisible(true); + refreshObjectiveTable(); + } + } + + private void deleteObjective() { + int row = objectiveTable.getSelectedRow(); + if (-1 != row) { + objectives.remove(row); + } + refreshObjectiveTable(); + } + + private void refreshObjectiveTable() { + int selectedRow = objectiveTable.getSelectedRow(); + objectiveModel.setData(objectives); + if (selectedRow != -1) { + if (objectiveTable.getRowCount() > 0) { + if (objectiveTable.getRowCount() == selectedRow) { + objectiveTable.setRowSelectionInterval(selectedRow-1, selectedRow-1); + } else { + objectiveTable.setRowSelectionInterval(selectedRow, selectedRow); + } + } + } + } + + private void initLootPanel(ResourceBundle resourceMap) { panLoot = new JPanel(new BorderLayout()); JPanel panBtns = new JPanel(new GridLayout(1,0)); - btnAdd = new JButton("Add Loot"); - btnAdd.addActionListener(evt -> addLoot()); - panBtns.add(btnAdd); - - btnEdit = new JButton("Edit Loot"); - btnEdit.setEnabled(false); - btnEdit.addActionListener(evt -> editLoot()); - panBtns.add(btnEdit); - - btnDelete = new JButton("Delete Loot"); - btnDelete.setEnabled(false); - btnDelete.addActionListener(evt -> deleteLoot()); - panBtns.add(btnDelete); + JButton btnAddLoot = new JButton(resourceMap.getString("btnAddLoot.text")); + btnAddLoot.addActionListener(evt -> addLoot()); + btnAddLoot.setEnabled(scenario.getStatus().isCurrent()); + panBtns.add(btnAddLoot); + + btnEditLoot = new JButton(resourceMap.getString("btnEditLoot.text")); + btnEditLoot.setEnabled(false); + btnEditLoot.addActionListener(evt -> editLoot()); + panBtns.add(btnEditLoot); + + btnDeleteLoot = new JButton(resourceMap.getString("btnDeleteLoot.text")); + btnDeleteLoot.setEnabled(false); + btnDeleteLoot.addActionListener(evt -> deleteLoot()); + panBtns.add(btnDeleteLoot); panLoot.add(panBtns, BorderLayout.PAGE_START); lootTable = new JTable(lootModel); @@ -422,8 +1000,8 @@ private void initLootPanel() { private void lootTableValueChanged(ListSelectionEvent evt) { int row = lootTable.getSelectedRow(); - btnDelete.setEnabled(row != -1); - btnEdit.setEnabled(row != -1); + btnDeleteLoot.setEnabled(row != -1); + btnEditLoot.setEnabled(row != -1); } private void addLoot() { @@ -432,7 +1010,7 @@ private void addLoot() { if (null != ekld.getLoot()) { lootModel.addLoot(ekld.getLoot()); } - refreshTable(); + refreshLootTable(); } private void editLoot() { @@ -440,7 +1018,7 @@ private void editLoot() { if (null != loot) { LootDialog ekld = new LootDialog(frame, true, loot, campaign); ekld.setVisible(true); - refreshTable(); + refreshLootTable(); } } @@ -449,10 +1027,10 @@ private void deleteLoot() { if (-1 != row) { loots.remove(row); } - refreshTable(); + refreshLootTable(); } - private void refreshTable() { + private void refreshLootTable() { int selectedRow = lootTable.getSelectedRow(); lootModel.setData(loots); if (selectedRow != -1) { @@ -466,6 +1044,119 @@ private void refreshTable() { } } + private void initOtherForcesPanel(ResourceBundle resourceMap) { + panOtherForces = new JPanel(new BorderLayout()); + + JPanel panBtns = new JPanel(new GridLayout(1,0)); + JButton btnAddForce = new JButton(resourceMap.getString("btnAddForce.text")); + btnAddForce.addActionListener(evt -> addForce()); + btnAddForce.setEnabled(scenario.getStatus().isCurrent()); + panBtns.add(btnAddForce); + + btnEditForce = new JButton(resourceMap.getString("btnEditForce.text")); + btnEditForce.setEnabled(false); + btnEditForce.addActionListener(evt -> editForce()); + btnEditForce.setEnabled(false); + panBtns.add(btnEditForce); + + btnDeleteForce = new JButton(resourceMap.getString("btnDeleteForce.text")); + btnDeleteForce.setEnabled(false); + btnDeleteForce.addActionListener(evt -> deleteForce()); + btnDeleteForce.setEnabled(false); + panBtns.add(btnDeleteForce); + panOtherForces.add(panBtns, BorderLayout.PAGE_START); + + forcesTable = new JTable(forcesModel); + TableColumn column; + for (int i = 0; i < BotForceTableModel.N_COL; i++) { + column = forcesTable.getColumnModel().getColumn(i); + column.setPreferredWidth(forcesModel.getColumnWidth(i)); + column.setCellRenderer(forcesModel.getRenderer()); + } + forcesTable.setIntercellSpacing(new Dimension(0, 0)); + forcesTable.setShowGrid(false); + forcesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + forcesTable.getSelectionModel().addListSelectionListener(this::forcesTableValueChanged); + + panOtherForces.add(new JScrollPane(forcesTable), BorderLayout.CENTER); + } + + private void forcesTableValueChanged(ListSelectionEvent evt) { + int row = forcesTable.getSelectedRow(); + btnDeleteForce.setEnabled(row != -1); + btnEditForce.setEnabled(row != -1); + } + + private void addForce() { + CustomizeBotForceDialog cbfd = new CustomizeBotForceDialog(frame, true, null, campaign); + cbfd.setVisible(true); + if (null != cbfd.getBotForce()) { + forcesModel.addForce(cbfd.getBotForce()); + } + refreshForcesTable(); + } + + private void editForce() { + BotForce bf = forcesModel.getBotForceAt(forcesTable.getSelectedRow()); + String nameOld = bf.getName(); + CustomizeBotForceDialog cbfd = new CustomizeBotForceDialog(frame, true, bf, campaign); + cbfd.setVisible(true); + refreshForcesTable(); + if (!bf.getName().equals(nameOld)) { + checkForceRename(nameOld, bf.getName()); + refreshObjectiveTable(); + } + } + + private void deleteForce() { + BotForce bf = forcesModel.getBotForceAt(forcesTable.getSelectedRow()); + String nameRemove = bf.getName(); + int row = forcesTable.getSelectedRow(); + if (-1 != row) { + botForces.remove(row); + checkForceDelete(nameRemove); + refreshObjectiveTable(); + } + refreshForcesTable(); + } + + private void refreshForcesTable() { + int selectedRow = forcesTable.getSelectedRow(); + forcesModel.setData(botForces); + if (selectedRow != -1) { + if (forcesTable.getRowCount() > 0) { + if (forcesTable.getRowCount() == selectedRow) { + forcesTable.setRowSelectionInterval(selectedRow-1, selectedRow-1); + } else { + forcesTable.setRowSelectionInterval(selectedRow, selectedRow); + } + } + } + } + + /** + * If a force was renamed, we need to change its name in any corresponding scenario objectives + */ + private void checkForceRename(String nameOld, String nameNew) { + for (ScenarioObjective objective : objectives) { + if (objective.getAssociatedForceNames().contains(nameOld)) { + objective.removeForce(nameOld); + objective.addForce(nameNew); + } + } + } + + /** + * If a force is deleted, check scenario objectives and remove it there as well + */ + private void checkForceDelete(String nameRemove) { + for (ScenarioObjective objective : objectives) { + if (objective.getAssociatedForceNames().contains(nameRemove)) { + objective.removeForce(nameRemove); + } + } + } + /** * Event handler for the 'add modifier' button. * @param event diff --git a/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioObjectiveDialog.java b/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioObjectiveDialog.java new file mode 100644 index 0000000000..9b4fc6acdc --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/CustomizeScenarioObjectiveDialog.java @@ -0,0 +1,635 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * 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 . + */ +package mekhq.gui.dialog; + +import megamek.client.ui.baseComponents.MMComboBox; +import megamek.common.OffBoardDirection; +import mekhq.MHQConstants; +import mekhq.MekHQ; +import mekhq.campaign.mission.*; +import mekhq.campaign.mission.ObjectiveEffect.EffectScalingType; +import mekhq.campaign.mission.ObjectiveEffect.ObjectiveEffectConditionType; +import mekhq.campaign.mission.ObjectiveEffect.ObjectiveEffectType; +import mekhq.campaign.mission.ScenarioObjective.ObjectiveAmountType; +import mekhq.campaign.mission.ScenarioObjective.ObjectiveCriterion; +import mekhq.campaign.mission.ScenarioObjective.TimeLimitType; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.ResourceBundle; + +public class CustomizeScenarioObjectiveDialog extends JDialog { + + private ScenarioObjective objective; + private List botForceNames; + private JPanel panObjectiveType; + private JPanel panForce; + private JPanel panTimeLimits; + private JPanel panObjectiveEffect; + private JTextField txtShortDescription; + private JComboBox cboObjectiveType; + private JComboBox cboDirection; + private JSpinner spnAmount; + private SpinnerNumberModel modelPercent; + private SpinnerNumberModel modelFixed; + private MMComboBox cboCountType; + private JComboBox cboForceName; + + private JSpinner spnScore; + private JComboBox cboScalingType; + private JComboBox cboEffectType; + private JComboBox cboEffectCondition; + + private JList successEffects; + private JList failureEffects; + private JButton btnRemoveSuccess; + private JButton btnRemoveFailure; + + private JComboBox cboTimeLimitDirection; + private JComboBox cboTimeScaling; + private JSpinner spnTimeLimit; + + private JList forceNames; + JButton btnRemove; + + private JList lstDetails; + + DefaultListModel forceModel = new DefaultListModel<>(); + DefaultListModel successEffectsModel = new DefaultListModel<>(); + DefaultListModel failureEffectsModel = new DefaultListModel<>(); + + DefaultListModel detailModel = new DefaultListModel<>(); + + + public CustomizeScenarioObjectiveDialog(JFrame parent, boolean modal, ScenarioObjective objective, List botForceNames) { + super(parent, modal); + this.objective = objective; + this.botForceNames = botForceNames; + + initialize(); + + for (String forceName : objective.getAssociatedForceNames()) { + forceModel.addElement(forceName); + } + + txtShortDescription.setText(objective.getDescription()); + cboObjectiveType.setSelectedItem(objective.getObjectiveCriterion()); + cboCountType.setSelectedItem(objective.getAmountType()); + spnAmount.setValue(objective.getAmount()); + setDirectionDropdownVisibility(); + + cboDirection.setSelectedIndex(objective.getDestinationEdge().ordinal()); + + cboTimeScaling.setSelectedItem(objective.getTimeLimitType()); + updateTimeLimitUI(); + cboTimeLimitDirection.setSelectedIndex(objective.isTimeLimitAtMost() ? 0 : 1); + if (objective.getTimeLimitType() == TimeLimitType.ScaledToPrimaryUnitCount) { + spnTimeLimit.setValue(objective.getTimeLimitScaleFactor()); + } else { + if (objective.getTimeLimit() != null) { + spnTimeLimit.setValue(objective.getTimeLimit()); + } + } + + for (ObjectiveEffect currentEffect : objective.getSuccessEffects()) { + successEffectsModel.addElement(currentEffect); + } + for (ObjectiveEffect currentEffect : objective.getFailureEffects()) { + failureEffectsModel.addElement(currentEffect); + } + + for (String detail : objective.getDetails()) { + detailModel.addElement(detail); + } + + validate(); + setLocationRelativeTo(parent); + pack(); + } + + private void initialize() { + + final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.CustomizeScenarioObjectiveDialog", + MekHQ.getMHQOptions().getLocale()); + + setTitle(resourceMap.getString("dialog.title")); + getContentPane().setLayout(new BorderLayout()); + JPanel panMain = new JPanel(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 5, 5, 5); + panMain.add(new JLabel(resourceMap.getString("lblDescription.text")), gbc); + + txtShortDescription = new JTextField(); + gbc.gridx++; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + panMain.add(txtShortDescription, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + panMain.add(new JLabel(resourceMap.getString("lblDetails.text")), gbc); + + JTextField txtDetail = new JTextField(); + txtDetail.setColumns(40); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + panMain.add(txtDetail, gbc); + + JButton btnAddDetail = new JButton(resourceMap.getString("btnAdd.text")); + btnAddDetail.addActionListener(e -> this.addDetail(txtDetail)); + gbc.gridx++; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + panMain.add(btnAddDetail, gbc); + + + lstDetails = new JList<>(detailModel); + JButton btnRemoveDetail = new JButton(resourceMap.getString("btnRemove.text")); + btnRemoveDetail.addActionListener(e -> this.removeDetails()); + lstDetails.addListSelectionListener(e -> btnRemoveDetail.setEnabled(!lstDetails.getSelectedValuesList().isEmpty())); + lstDetails.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JScrollPane scrDetails = new JScrollPane(lstDetails); + scrDetails.setMinimumSize(new Dimension(200, 100)); + scrDetails.setPreferredSize(new Dimension(200, 100)); + gbc.gridx = 1; + gbc.gridy++; + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + panMain.add(scrDetails, gbc); + + gbc.gridx++; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.NORTHWEST; + panMain.add(btnRemoveDetail, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.anchor = GridBagConstraints.WEST; + panMain.add(new JLabel(resourceMap.getString("lblObjectiveType.text")), gbc); + initObjectiveTypePanel(); + gbc.gridx++; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + panMain.add(panObjectiveType, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.NORTHWEST; + panMain.add(new JLabel(resourceMap.getString("lblForceNames.text")), gbc); + + initForcePanel(resourceMap); + gbc.gridx++; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + panMain.add(panForce, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.anchor = GridBagConstraints.WEST; + panMain.add(new JLabel(resourceMap.getString("lblTimeLimit.text")), gbc); + + initTimeLimitPanel(); + gbc.gridx++; + gbc.gridwidth = 2; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + panMain.add(panTimeLimits, gbc); + + initObjectiveEffectPanel(resourceMap); + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 3; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + panMain.add(panObjectiveEffect, gbc); + + getContentPane().add(panMain, BorderLayout.CENTER); + + JPanel panButtons = new JPanel(new FlowLayout()); + JButton btnCancel = new JButton(resourceMap.getString("btnCancel.text")); + btnCancel.addActionListener(e -> this.setVisible(false)); + JButton btnOK = new JButton(resourceMap.getString("btnOK.text")); + btnOK.addActionListener(e -> this.saveObjectiveAndClose()); + panButtons.add(btnOK); + panButtons.add(btnCancel); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + + } + + /** + * Handles the "objective type" row + */ + private void initObjectiveTypePanel() { + panObjectiveType = new JPanel(new GridBagLayout()); + cboObjectiveType = new JComboBox<>(); + for (ObjectiveCriterion objectiveType : ObjectiveCriterion.values()) { + cboObjectiveType.addItem(objectiveType); + } + cboObjectiveType.addActionListener(e -> this.setDirectionDropdownVisibility()); + + modelPercent = new SpinnerNumberModel(0, 0, 100, 5); + modelFixed = new SpinnerNumberModel(0, 0, null, 1); + spnAmount = new JSpinner(modelPercent); + + cboCountType = new MMComboBox<>("cboCountType", ObjectiveAmountType.values()); + cboCountType.addActionListener(etv -> switchNumberModel()); + + + cboDirection = new JComboBox<>(); + cboDirection.addItem("Force Destination Edge"); + for (int x = 1; x < OffBoardDirection.values().length; x++) { + cboDirection.addItem(OffBoardDirection.values()[x].toString()); + } + cboDirection.setVisible(false); + + GridBagConstraints localGbc = new GridBagConstraints(); + localGbc.gridx = 0; + localGbc.gridy = 0; + localGbc.anchor = GridBagConstraints.WEST; + localGbc.insets = new Insets(0, 0, 0, 5); + + panObjectiveType.add(cboObjectiveType, localGbc); + localGbc.gridx++; + panObjectiveType.add(cboDirection, localGbc); + localGbc.gridx++; + panObjectiveType.add(spnAmount, localGbc); + localGbc.gridx++; + localGbc.weightx = 1.0; + panObjectiveType.add(cboCountType, localGbc); + + } + + /** + * Handles the UI for adding/removing forces relevant to this objective + */ + private void initForcePanel(ResourceBundle resourceMap) { + panForce = new JPanel(new GridBagLayout()); + + cboForceName = new JComboBox<>(); + cboForceName.addItem(MHQConstants.EGO_OBJECTIVE_NAME); + for (String name : botForceNames) { + cboForceName.addItem(name); + } + + forceNames = new JList<>(forceModel); + forceNames.setVisibleRowCount(5); + forceNames.addListSelectionListener(e -> btnRemove.setEnabled(!forceNames.getSelectedValuesList().isEmpty())); + + JButton btnAdd = new JButton(resourceMap.getString("btnAdd.text")); + btnAdd.addActionListener(e -> this.addForce()); + + btnRemove = new JButton(resourceMap.getString("btnRemove.text")); + btnRemove.addActionListener(e -> this.removeForce()); + btnRemove.setEnabled(false); + + GridBagConstraints localGbc = new GridBagConstraints(); + localGbc.gridx = 0; + localGbc.gridy = 0; + localGbc.anchor = GridBagConstraints.NORTHWEST; + localGbc.insets = new Insets(0, 0, 0, 5); + + panForce.add(cboForceName, localGbc); + localGbc.gridx++; + panForce.add(btnAdd, localGbc); + localGbc.gridx++; + JScrollPane scrForceNames = new JScrollPane(forceNames); + scrForceNames.setMinimumSize(new Dimension(250, 100)); + scrForceNames.setPreferredSize(new Dimension(250, 100)); + panForce.add(scrForceNames, localGbc); + localGbc.gridx++; + localGbc.weightx = 1.0; + panForce.add(btnRemove, localGbc); + } + + /** + * Handles the UI for adding objective effects + */ + private void initObjectiveEffectPanel(ResourceBundle resourceMap) { + panObjectiveEffect = new JPanel(new GridBagLayout()); + panObjectiveEffect.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(resourceMap.getString("panObjectiveEffect.title")), + BorderFactory.createEmptyBorder(5,5,5,5))); + + GridBagConstraints gbcLeft = new GridBagConstraints(); + gbcLeft.gridx = 0; + gbcLeft.gridy = 0; + gbcLeft.anchor = GridBagConstraints.WEST; + gbcLeft.fill = GridBagConstraints.NONE; + gbcLeft.weightx = 0.0; + + GridBagConstraints gbcRight = new GridBagConstraints(); + gbcRight.gridx = 1; + gbcRight.gridy = 0; + gbcRight.anchor = GridBagConstraints.WEST; + gbcRight.fill = GridBagConstraints.NONE; + gbcRight.weightx = 1.0; + + JLabel lblMagnitude = new JLabel(resourceMap.getString("lblMagnitude.text")); + panObjectiveEffect.add(lblMagnitude, gbcLeft); + spnScore = new JSpinner(new SpinnerNumberModel(1, 1, null, 1)); + panObjectiveEffect.add(spnScore, gbcRight); + + gbcLeft.gridy++; + gbcRight.gridy++; + panObjectiveEffect.add(new JLabel(resourceMap.getString("lblEffectScaling.text")), gbcLeft); + cboScalingType = new JComboBox<>(); + for (EffectScalingType scalingType : EffectScalingType.values()) { + cboScalingType.addItem(scalingType); + } + panObjectiveEffect.add(cboScalingType, gbcRight); + + gbcLeft.gridy++; + gbcRight.gridy++; + panObjectiveEffect.add(new JLabel(resourceMap.getString("lblEffectType.text")), gbcLeft); + cboEffectType = new JComboBox<>(); + for (ObjectiveEffectType scalingType : ObjectiveEffectType.values()) { + cboEffectType.addItem(scalingType); + } + panObjectiveEffect.add(cboEffectType, gbcRight); + + gbcLeft.gridy++; + gbcRight.gridy++; + panObjectiveEffect.add(new JLabel(resourceMap.getString("lblEffectCondition.text")), gbcLeft); + cboEffectCondition = new JComboBox<>(); + cboEffectCondition.addItem(ObjectiveEffectConditionType.ObjectiveSuccess); + cboEffectCondition.addItem(ObjectiveEffectConditionType.ObjectiveFailure); + panObjectiveEffect.add(cboEffectCondition, gbcRight); + + JButton btnAdd = new JButton(resourceMap.getString("btnAdd.text")); + btnAdd.addActionListener(e -> this.addEffect()); + gbcLeft.gridy++; + panObjectiveEffect.add(btnAdd, gbcLeft); + + JLabel lblSuccessEffects = new JLabel(resourceMap.getString("lblSuccessEffects.text")); + JLabel lblFailureEffects = new JLabel(resourceMap.getString("lblSuccessEffects.text")); + + successEffects = new JList<>(successEffectsModel); + successEffects.addListSelectionListener(e -> btnRemoveSuccess.setEnabled(!successEffects.getSelectedValuesList().isEmpty())); + failureEffects = new JList<>(failureEffectsModel); + failureEffects.addListSelectionListener(e -> btnRemoveFailure.setEnabled(!failureEffects.getSelectedValuesList().isEmpty())); + + btnRemoveSuccess = new JButton(resourceMap.getString("btnRemove.text")); + btnRemoveSuccess.addActionListener(e -> this.removeEffect(ObjectiveEffectConditionType.ObjectiveSuccess)); + btnRemoveSuccess.setEnabled(false); + + btnRemoveFailure = new JButton(resourceMap.getString("btnRemove.text")); + btnRemoveFailure.addActionListener(e -> this.removeEffect(ObjectiveEffectConditionType.ObjectiveFailure)); + btnRemoveFailure.setEnabled(false); + + JPanel panBottom = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + gbc.insets = new Insets(5,5,0,5); + panBottom.add(lblSuccessEffects, gbc); + gbc.gridx++; + gbc.gridx++; + panBottom.add(lblFailureEffects, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.anchor = GridBagConstraints.NORTHWEST; + JScrollPane scrSuccessEffects = new JScrollPane(successEffects); + scrSuccessEffects.setMinimumSize(new Dimension(300, 100)); + scrSuccessEffects.setPreferredSize(new Dimension(300, 100)); + panBottom.add(scrSuccessEffects, gbc); + gbc.gridx++; + panBottom.add(btnRemoveSuccess, gbc); + gbc.gridx++; + JScrollPane scrFailureEffects = new JScrollPane(failureEffects); + scrFailureEffects.setMinimumSize(new Dimension(300, 100)); + scrFailureEffects.setPreferredSize(new Dimension(300, 100)); + panBottom.add(scrFailureEffects, gbc); + gbc.gridx++; + panBottom.add(btnRemoveFailure, gbc); + + gbcLeft.gridy++; + gbcLeft.gridwidth = 3; + gbcLeft.anchor = GridBagConstraints.WEST; + gbcLeft.fill = GridBagConstraints.BOTH; + gbcLeft.weightx = 1.0; + gbcLeft.weighty = 1.0; + panObjectiveEffect.add(panBottom, gbcLeft); + + } + + private void initTimeLimitPanel() { + panTimeLimits = new JPanel(new GridBagLayout()); + + cboTimeLimitDirection = new JComboBox<>(); + cboTimeLimitDirection.addItem("at most"); + cboTimeLimitDirection.addItem("at least"); + + cboTimeScaling = new JComboBox<>(); + for (TimeLimitType timeLimitType : TimeLimitType.values()) { + cboTimeScaling.addItem(timeLimitType); + } + cboTimeScaling.addActionListener(e -> this.updateTimeLimitUI()); + + spnTimeLimit = new JSpinner(new SpinnerNumberModel(1, 1, null, 1)); + + GridBagConstraints localGbc = new GridBagConstraints(); + localGbc.gridx = 0; + localGbc.gridy = 0; + localGbc.anchor = GridBagConstraints.NORTHWEST; + localGbc.insets = new Insets(0, 0, 0, 5); + + panTimeLimits.add(cboTimeLimitDirection, localGbc); + localGbc.gridx++; + panTimeLimits.add(cboTimeScaling, localGbc); + localGbc.gridx++; + localGbc.weightx = 1.0; + panTimeLimits.add(spnTimeLimit, localGbc); + } + + private void switchNumberModel() { + int value = (int) spnAmount.getValue(); + if(cboCountType.getSelectedItem() == ObjectiveAmountType.Percentage) { + if(value > 100) { + value = 100; + } + modelPercent.setValue(value); + spnAmount.setModel(modelPercent); + } else { + modelFixed.setValue(value); + spnAmount.setModel(modelFixed); + } + } + + /** + * Event handler for the 'add' button for scenario effects + */ + private void addEffect() { + + ObjectiveEffect effect = new ObjectiveEffect(); + effect.howMuch = (int) spnScore.getValue(); + effect.effectScaling = (EffectScalingType) cboScalingType.getSelectedItem(); + effect.effectType = (ObjectiveEffectType) cboEffectType.getSelectedItem(); + + if (cboEffectCondition.getSelectedItem() == ObjectiveEffectConditionType.ObjectiveSuccess) { + successEffectsModel.addElement(effect); + successEffects.repaint(); + } else { + failureEffectsModel.addElement(effect); + } + + pack(); + } + + private void removeEffect(ObjectiveEffectConditionType conditionType) { + JList targetList; + DefaultListModel modelToUpdate; + + if (conditionType == ObjectiveEffectConditionType.ObjectiveSuccess) { + targetList = successEffects; + modelToUpdate = (DefaultListModel) successEffects.getModel(); + btnRemoveSuccess.setEnabled(false); + } else { + targetList = failureEffects; + modelToUpdate = (DefaultListModel) failureEffects.getModel(); + btnRemoveFailure.setEnabled(false); + } + + for (ObjectiveEffect effectToRemove : targetList.getSelectedValuesList()) { + modelToUpdate.removeElement(effectToRemove); + } + } + + private void addForce() { + forceModel.addElement(cboForceName.getSelectedItem().toString()); + pack(); + } + + private void removeForce() { + for (String forceName : forceNames.getSelectedValuesList()) { + forceModel.removeElement(forceName); + } + btnRemove.setEnabled(false); + pack(); + } + + private void addDetail(JTextField field) { + detailModel.addElement(field.getText()); + } + + private void removeDetails() { + for (String detail : lstDetails.getSelectedValuesList()) { + detailModel.removeElement(detail); + } + } + + private void setDirectionDropdownVisibility() { + switch ((ObjectiveCriterion) cboObjectiveType.getSelectedItem()) { + case PreventReachMapEdge: + case ReachMapEdge: + cboDirection.setVisible(true); + break; + default: + cboDirection.setVisible(false); + break; + } + } + + private void updateTimeLimitUI() { + boolean enable = !cboTimeScaling.getSelectedItem().equals(TimeLimitType.None); + spnTimeLimit.setEnabled(enable); + cboTimeLimitDirection.setEnabled(enable); + } + + public ScenarioObjective getObjective() { + return objective; + } + + private void saveObjectiveAndClose() { + objective.setObjectiveCriterion((ObjectiveCriterion) cboObjectiveType.getSelectedItem()); + objective.setDescription(txtShortDescription.getText()); + int number = (int) spnAmount.getValue(); + if (cboCountType.getSelectedItem().equals(ObjectiveAmountType.Percentage)) { + objective.setPercentage(number); + objective.setFixedAmount(null); + } else { + objective.setFixedAmount(number); + } + + objective.clearForces(); + for (int i = 0; i< forceModel.getSize(); i++) { + objective.addForce(forceModel.getElementAt(i)); + } + + + objective.clearSuccessEffects(); + for (int i = 0; i< successEffectsModel.getSize(); i++) { + objective.addSuccessEffect(successEffectsModel.getElementAt(i)); + } + + objective.clearFailureEffects(); + for (int i = 0; i< failureEffectsModel.getSize(); i++) { + objective.addFailureEffect(failureEffectsModel.getElementAt(i)); + } + + objective.clearDetails(); + for (int i = 0; i< detailModel.getSize(); i++) { + objective.addDetail(detailModel.getElementAt(i)); + } + + if (cboDirection.isVisible() && cboDirection.getSelectedIndex() > 0) { + objective.setDestinationEdge(OffBoardDirection.getDirection(cboDirection.getSelectedIndex() - 1)); + } else { + objective.setDestinationEdge(OffBoardDirection.NONE); + } + + int timeLimit = (int) spnTimeLimit.getValue(); + objective.setTimeLimitType((TimeLimitType) cboTimeScaling.getSelectedItem()); + if (spnTimeLimit.isEnabled()) { + if (objective.getTimeLimitType() == TimeLimitType.ScaledToPrimaryUnitCount) { + objective.setTimeLimitScaleFactor(timeLimit); + } else { + objective.setTimeLimit(timeLimit); + } + } + if (cboTimeLimitDirection.isEnabled()) { + objective.setTimeLimitAtMost(cboTimeLimitDirection.getSelectedIndex() == 0); + } + + setVisible(false); + } +} diff --git a/MekHQ/src/mekhq/gui/dialog/EditDeploymentDialog.java b/MekHQ/src/mekhq/gui/dialog/EditDeploymentDialog.java new file mode 100644 index 0000000000..e43e59d121 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/EditDeploymentDialog.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.gui.dialog; + +import megamek.client.ui.GBC; +import megamek.client.ui.swing.util.UIUtil; +import megamek.client.ui.swing.util.UIUtil.TipButton; +import megamek.common.IStartingPositions; +import megamek.common.Player; +import mekhq.MekHQ; + +import javax.swing.*; +import javax.swing.text.DefaultFormatterFactory; +import javax.swing.text.NumberFormatter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.NumberFormat; +import java.util.ResourceBundle; + +import static megamek.client.ui.swing.util.UIUtil.*; +import static megamek.client.ui.swing.util.UIUtil.guiScaledFontHTML; + +public class EditDeploymentDialog extends JDialog { + + Player player; + + private int currentStartPos; + + private final JPanel panStartButtons = new JPanel(); + private final TipButton[] butStartPos = new TipButton[11]; + private final NumberFormatter numFormatter = new NumberFormatter(NumberFormat.getIntegerInstance()); + private final DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(numFormatter); + private final JFormattedTextField txtOffset = new JFormattedTextField(formatterFactory, 0); + private final JFormattedTextField txtWidth = new JFormattedTextField(formatterFactory, 3); + private JSpinner spinStartingAnyNWx; + private JSpinner spinStartingAnyNWy; + private JSpinner spinStartingAnySEx; + private JSpinner spinStartingAnySEy; + + public EditDeploymentDialog(JFrame parent, boolean modal, Player player) { + super(parent, modal); + this.player = player; + currentStartPos = player.getStartingPos(); + initComponents(); + setLocationRelativeTo(parent); + pack(); + } + + private void initComponents() { + + final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.EditDeploymentDialog", + MekHQ.getMHQOptions().getLocale()); + + panStartButtons.setAlignmentX(Component.LEFT_ALIGNMENT); + for (int i = 0; i < 11; i++) { + butStartPos[i] = new TipButton(""); + butStartPos[i].addActionListener(startListener); + } + panStartButtons.setLayout(new GridLayout(4, 3)); + panStartButtons.add(butStartPos[1]); + panStartButtons.add(butStartPos[2]); + panStartButtons.add(butStartPos[3]); + panStartButtons.add(butStartPos[8]); + panStartButtons.add(butStartPos[10]); + panStartButtons.add(butStartPos[4]); + panStartButtons.add(butStartPos[7]); + panStartButtons.add(butStartPos[6]); + panStartButtons.add(butStartPos[5]); + panStartButtons.add(butStartPos[0]); + panStartButtons.add(butStartPos[9]); + updateStartGrid(); + + SpinnerNumberModel mStartingAnyNWx = new SpinnerNumberModel(player.getStartingAnyNWx()+1, 0, + null, 1); + spinStartingAnyNWx = new JSpinner(mStartingAnyNWx); + SpinnerNumberModel mStartingAnyNWy = new SpinnerNumberModel(player.getStartingAnyNWy()+1, 0, + null, 1); + spinStartingAnyNWy = new JSpinner(mStartingAnyNWy); + SpinnerNumberModel mStartingAnySEx = new SpinnerNumberModel(player.getStartingAnySEx()+1, 0, + null, 1); + spinStartingAnySEx = new JSpinner(mStartingAnySEx); + SpinnerNumberModel mStartingAnySEy = new SpinnerNumberModel(player.getStartingAnySEy()+1, 0, + null, 1); + spinStartingAnySEy = new JSpinner(mStartingAnySEy); + + GridBagLayout gbl = new GridBagLayout(); + JPanel main = new JPanel(gbl); + + JLabel lblOffset = new JLabel(resourceMap.getString("labDeploymentOffset.text")); + lblOffset.setToolTipText(resourceMap.getString("labDeploymentOffset.tip")); + JLabel lblWidth = new JLabel(resourceMap.getString("labDeploymentWidth.text")); + lblWidth.setToolTipText(resourceMap.getString("labDeploymentWidth.tip")); + + txtOffset.setColumns(4); + txtWidth.setColumns(4); + txtWidth.setText(Integer.toString(player.getStartWidth())); + txtOffset.setText(Integer.toString(player.getStartOffset())); + + main.add(lblOffset, GBC.std()); + main.add(txtOffset, GBC.eol()); + main.add(lblWidth, GBC.std()); + main.add(txtWidth, GBC.eol()); + + main.add(new JLabel(resourceMap.getString("labDeploymentAnyNW.text")), GBC.std()); + main.add(spinStartingAnyNWx, GBC.std()); + main.add(spinStartingAnyNWy, GBC.eol()); + main.add(new JLabel(resourceMap.getString("labDeploymentAnySE.text")), GBC.std()); + main.add(spinStartingAnySEx, GBC.std()); + main.add(spinStartingAnySEy, GBC.eol()); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(main, BorderLayout.CENTER); + getContentPane().add(panStartButtons, BorderLayout.PAGE_START); + + JPanel panButtons = new JPanel(new FlowLayout()); + JButton btnOK = new JButton(resourceMap.getString("btnOK.text")); + btnOK.addActionListener(this::done); + JButton btnCancel = new JButton(resourceMap.getString("btnCancel.text")); + btnCancel.addActionListener(this::cancel); + panButtons.add(btnOK); + panButtons.add(btnCancel); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + + } + + private void updateStartGrid() { + StringBuilder[] butText = new StringBuilder[11]; + StringBuilder[] butTT = new StringBuilder[11]; + + for (int i = 0; i < 11; i++) { + butText[i] = new StringBuilder(); + butTT[i] = new StringBuilder(); + } + + for (int i = 0; i < 11; i++) { + butText[i].append("

"); + //butTT[i].append(Messages.getString("PlayerSettingsDialog.invalidStartPosTT")); + butText[i].append(IStartingPositions.START_LOCATION_NAMES[i]).append("
"); + } + + butText[currentStartPos].append(guiScaledFontHTML(uiGreen())); + butText[currentStartPos].append("\u2B24"); + + for (int i = 0; i < 11; i++) { + butStartPos[i].setText(butText[i].toString()); + if (butTT[i].length() > 0) { + butStartPos[i].setToolTipText(butTT[i].toString()); + } + } + } + + ActionListener startListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Deployment buttons + for (int i = 0; i < 11; i++) { + if (butStartPos[i].equals(e.getSource())) { + currentStartPos = i; + updateStartGrid(); + } + } + } + }; + + private void done(ActionEvent evt) { + player.setStartingPos(currentStartPos); + player.setStartWidth(parseField(txtWidth)); + player.setStartOffset(parseField(txtOffset)); + player.setStartingAnyNWx(Math.min((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()) - 1); + player.setStartingAnyNWy(Math.min((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()) - 1); + player.setStartingAnySEx(Math.max((Integer) spinStartingAnyNWx.getValue(), (Integer) spinStartingAnySEx.getValue()) - 1); + player.setStartingAnySEy(Math.max((Integer) spinStartingAnyNWy.getValue(), (Integer) spinStartingAnySEy.getValue()) - 1); + this.setVisible(false); + } + + private void cancel(ActionEvent evt) { + this.setVisible(false); + } + + /** + * Parse the given field and return the integer it contains or 0, if + * the field cannot be parsed. + */ + private int parseField(JTextField field) { + try { + return Integer.parseInt(field.getText()); + } catch (NumberFormatException ex) { + return 0; + } + } + +} diff --git a/MekHQ/src/mekhq/gui/dialog/EditMapSettingsDialog.java b/MekHQ/src/mekhq/gui/dialog/EditMapSettingsDialog.java new file mode 100644 index 0000000000..f64437b443 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/EditMapSettingsDialog.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * 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 . + */ +package mekhq.gui.dialog; + +import megamek.client.ui.swing.lobby.LobbyUtility; +import megamek.client.ui.swing.minimap.Minimap; +import megamek.common.Board; +import megamek.common.BoardDimensions; +import megamek.common.Configuration; +import megamek.common.MapSettings; +import megamek.common.util.fileUtils.MegaMekFile; +import megamek.server.GameManager; +import megamek.server.ServerBoardHelper; +import mekhq.MekHQ; +import mekhq.campaign.mission.Scenario; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class EditMapSettingsDialog extends JDialog { + + private int mapSizeX; + private int mapSizeY; + private String map; + private boolean usingFixedMap; + private int boardType; + + private JCheckBox checkFixed; + private JComboBox comboBoardType; + private JComboBox comboMapSize; + private JSpinner spnMapX; + private JSpinner spnMapY; + private JScrollPane scrChooseMap; + private JList listMapGenerators; + private JList listFixedMaps; + DefaultListModel generatorModel = new DefaultListModel<>(); + DefaultListModel fixedMapModel = new DefaultListModel<>(); + + JPanel panSizeRandom; + JPanel panSizeFixed; + + private Map mapIcons = new HashMap<>(); + private Map baseImages = new HashMap<>(); + + private ImageLoader loader; + + + public EditMapSettingsDialog(JFrame parent, boolean modal, int boardType, boolean usingFixedMap, String map, + int mapSizeX, int mapSizeY) { + + super(parent, modal); + this.boardType = boardType; + this.usingFixedMap = usingFixedMap; + this.map = map; + this.mapSizeX = mapSizeX; + this.mapSizeY = mapSizeY; + loader = new ImageLoader(); + loader.execute(); + + initComponents(); + setLocationRelativeTo(parent); + pack(); + } + + public int getBoardType() { + return boardType; + } + + public boolean getUsingFixedMap() { + return usingFixedMap; + } + + public String getMap() { + return map; + } + + public int getMapSizeX() { + return mapSizeX; + } + + public int getMapSizeY() { + return mapSizeY; + } + + private void initComponents() { + final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.EditMapSettingsDialog", + MekHQ.getMHQOptions().getLocale()); + setTitle(resourceMap.getString("dialog.title")); + + getContentPane().setLayout(new BorderLayout()); + JPanel panMain = new JPanel(new GridBagLayout()); + panSizeRandom = new JPanel(new GridBagLayout()); + panSizeFixed = new JPanel(new BorderLayout()); + JPanel panButtons = new JPanel(new FlowLayout()); + + scrChooseMap = new JScrollPane(); + scrChooseMap.setMinimumSize(new Dimension(600, 800)); + scrChooseMap.setPreferredSize(new Dimension(600, 800)); + + + checkFixed = new JCheckBox(resourceMap.getString("checkFixed.text")); + checkFixed.setSelected(usingFixedMap); + checkFixed.addActionListener(evt -> changeMapType()); + + spnMapX = new JSpinner(new SpinnerNumberModel(mapSizeX, 0, null, 1)); + spnMapY = new JSpinner(new SpinnerNumberModel(mapSizeY, 0, null, 1)); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 5, 5, 5); + panSizeRandom.add(spnMapX, gbc); + gbc.gridx++; + panSizeRandom.add(new JLabel("x")); + gbc.gridx++; + gbc.weightx = 1.0; + panSizeRandom.add(spnMapY); + + comboMapSize = new JComboBox<>(); + for (BoardDimensions size : GameManager.getBoardSizes()) { + comboMapSize.addItem(size); + } + if (mapSizeX > 0 & mapSizeY > 0) { + comboMapSize.setSelectedItem(new BoardDimensions(mapSizeX, mapSizeY)); + } else { + // if no board size yet set, use the default + comboMapSize.setSelectedItem(new BoardDimensions(16, 17)); + } + comboMapSize.addActionListener(evt -> refreshBoardList()); + panSizeFixed.add(comboMapSize, BorderLayout.CENTER); + + listMapGenerators = new JList<>(generatorModel); + listMapGenerators.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + generatorModel.addElement(resourceMap.getString("listMapGenerators.none")); + File dir = new File("data/mapgen/"); + File[] directoryListing = dir.listFiles(); + ArrayList generators = new ArrayList<>(); + if (directoryListing != null) { + for (File child : directoryListing) { + if (child.isFile()) { + String s = child.getName().replace(".xml", ""); + generators.add(s); + } + } + } + Collections.sort(generators); + generatorModel.addAll(generators); + + listFixedMaps = new JList<>(fixedMapModel); + listFixedMaps.setCellRenderer(new BoardNameRenderer()); + listFixedMaps.setLayoutOrientation(JList.HORIZONTAL_WRAP); + listFixedMaps.setVisibleRowCount(-1); + listFixedMaps.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + refreshBoardList(); + + if (usingFixedMap) { + listFixedMaps.setSelectedValue(map, true); + scrChooseMap.setViewportView(listFixedMaps); + } else { + listMapGenerators.setSelectedValue(map, true); + scrChooseMap.setViewportView(listMapGenerators); + } + + comboBoardType = new JComboBox<>(); + for (int i = Scenario.T_GROUND; i <= Scenario.T_SPACE; i++) { + comboBoardType.addItem(Scenario.getBoardTypeName(i)); + } + comboBoardType.addActionListener(evt -> changeBoardType()); + comboBoardType.setSelectedIndex(boardType); + + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.WEST; + gbc.fill = GridBagConstraints.NONE; + gbc.insets = new Insets(5, 5, 5, 5); + panMain.add(new JLabel(resourceMap.getString("lblBoardType.text")), gbc); + gbc.weightx = 1.0; + gbc.gridx++; + panMain.add(comboBoardType, gbc); + + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0.0; + panMain.add(new JLabel(resourceMap.getString("lblMapSize.text")), gbc); + gbc.gridx++; + gbc.weightx = 1.0; + panMain.add(panSizeRandom, gbc); + panMain.add(panSizeFixed, gbc); + if (usingFixedMap) { + panSizeRandom.setVisible(false); + } else { + panSizeFixed.setVisible(false); + } + + gbc.gridwidth = 2; + gbc.gridx = 0; + gbc.gridy++; + panMain.add(checkFixed, gbc); + + gbc.gridy++; + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1.0; + panMain.add(scrChooseMap, gbc); + + JButton btnOK = new JButton(resourceMap.getString("btnOK.text")); + btnOK.addActionListener(evt -> done()); + JButton btnCancel = new JButton(resourceMap.getString("btnCancel.text")); + btnCancel.addActionListener(evt -> cancel()); + panButtons.add(btnOK); + panButtons.add(btnCancel); + + getContentPane().add(panMain, BorderLayout.CENTER); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + } + + private void changeBoardType() { + if (comboBoardType.getSelectedIndex() == Scenario.T_SPACE) { + checkFixed.setSelected(false); + checkFixed.setEnabled(false); + panSizeRandom.setVisible(true); + panSizeFixed.setVisible(false); + listMapGenerators.setSelectedIndex(0); + listMapGenerators.setEnabled(false); + listFixedMaps.setEnabled(false); + scrChooseMap.setViewportView(listMapGenerators); + } else { + checkFixed.setEnabled(true); + listMapGenerators.setEnabled(true); + listFixedMaps.setEnabled(true); + } + } + + private void changeMapType() { + if (checkFixed.isSelected()) { + panSizeRandom.setVisible(false); + panSizeFixed.setVisible(true); + scrChooseMap.setViewportView(listFixedMaps); + } else { + panSizeRandom.setVisible(true); + panSizeFixed.setVisible(false); + scrChooseMap.setViewportView(listMapGenerators); + } + } + + private void refreshBoardList() { + listFixedMaps.setFixedCellHeight(-1); + listFixedMaps.setFixedCellWidth(-1); + MapSettings mapSettings = MapSettings.getInstance(); + BoardDimensions boardSize = (BoardDimensions) comboMapSize.getSelectedItem(); + mapSettings.setBoardSize(boardSize.width(), boardSize.height()); + List boards = ServerBoardHelper.scanForBoards(mapSettings); + fixedMapModel.removeAllElements(); + fixedMapModel.addAll(boards); + listFixedMaps.clearSelection(); + } + + public void done() { + boardType = comboBoardType.getSelectedIndex(); + usingFixedMap = checkFixed.isSelected(); + if (usingFixedMap) { + map = listFixedMaps.getSelectedValue(); + BoardDimensions boardSize = (BoardDimensions) comboMapSize.getSelectedItem(); + mapSizeX = boardSize.width(); + mapSizeY = boardSize.height(); + } else { + map = listMapGenerators.getSelectedValue(); + if (listMapGenerators.getSelectedIndex() == 0) { + map = null; + } + mapSizeX = (int) spnMapX.getValue(); + mapSizeY = (int) spnMapY.getValue(); + } + setVisible(false); + } + + public void cancel() { + setVisible(false); + } + + /** + * A modified version of the thumbnail board rendered from Megamek.ChatLounge + */ + private class BoardNameRenderer extends DefaultListCellRenderer { + + private Image image; + private ImageIcon icon; + + @Override + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + + String board = (String) value; + // For generated boards, add the size to have different images for different sizes + //if (board.startsWith(MapSettings.BOARD_GENERATED)) { + // board += comboMapSize.getSelectedItem(); + //} + + // If an icon is present for the current board, use it + icon = mapIcons.get(board); + if (icon != null) { + setIcon(icon); + } else { + // The icon is not present, see if there's a base image + synchronized (baseImages) { + image = baseImages.get(board); + } + if (image == null) { + // There's no base image: trigger loading it and, for now, return the base list's panel + // The [GENERATED] entry will always land here as well + loader.add(board); + setToolTipText(null); + return super.getListCellRendererComponent(list, new File(board).getName(), index, isSelected, cellHasFocus); + } else { + icon = new ImageIcon(image); + + mapIcons.put(board, icon); + setIcon(icon); + } + } + + // Found or created an icon; finish the panel + setText(""); + if (listFixedMaps.isEnabled()) { + setToolTipText(board); + } else { + setToolTipText(null); + } + + if (isSelected) { + setForeground(list.getSelectionForeground()); + setBackground(list.getSelectionBackground()); + } else { + setForeground(list.getForeground()); + setBackground(list.getBackground()); + } + + return this; + } + } + + private class ImageLoader extends SwingWorker { + + private BlockingQueue boards = new LinkedBlockingQueue<>(); + + private synchronized void add(String name) { + if (!boards.contains(name)) { + try { + boards.put(name); + } catch (Exception e) { + LogManager.getLogger().error("", e); + } + } + } + + private Image prepareImage(String boardName) { + MapSettings mapSettings = MapSettings.getInstance(); + BoardDimensions boardSize = (BoardDimensions) comboMapSize.getSelectedItem(); + mapSettings.setBoardSize(boardSize.width(), boardSize.height()); + File boardFile = new MegaMekFile(Configuration.boardsDir(), boardName + ".board").getFile(); + Board board; + StringBuffer errs = new StringBuffer(); + if (boardFile.exists()) { + board = new Board(); + try (InputStream is = new FileInputStream(boardFile)) { + board.load(is, errs, true); + } catch (IOException ex) { + board = Board.createEmptyBoard(mapSettings.getBoardWidth(), mapSettings.getBoardHeight()); + } + } else { + board = Board.createEmptyBoard(mapSettings.getBoardWidth(), mapSettings.getBoardHeight()); + } + + // Determine a minimap zoom from the board size and gui scale. + // This is very magic numbers but currently the minimap has only fixed zoom states. + int largerEdge = Math.max(board.getWidth(), board.getHeight()); + int zoom = 3; + if (largerEdge < 17) { + zoom = 4; + } + if (largerEdge > 20) { + zoom = 2; + } + if (largerEdge > 30) { + zoom = 1; + } + if (largerEdge > 40) { + zoom = 0; + } + if (board.getWidth() < 25) { + zoom = Math.max(zoom, 3); + } + float scale = 1; + zoom = (int) (scale*zoom); + if (zoom > 6) { + zoom = 6; + } + if (zoom < 0) { + zoom = 0; + } + BufferedImage bufImage = Minimap.getMinimapImage(board, zoom); + + // Add the board name label and the server-side board label if necessary + String text = LobbyUtility.cleanBoardName(boardName, mapSettings); + Graphics g = bufImage.getGraphics(); + LobbyUtility.drawMinimapLabel(text, bufImage.getWidth(), bufImage.getHeight(), g, errs.length() != 0); + g.dispose(); + + synchronized(baseImages) { + baseImages.put(boardName, bufImage); + } + return bufImage; + } + + + @Override + protected Void doInBackground() throws Exception { + Image image; + while (!isCancelled()) { + // Create thumbnails for the MapSettings boards + String boardName = boards.poll(1, TimeUnit.SECONDS); + if (boardName != null && !baseImages.containsKey(boardName)) { + image = prepareImage(boardName); + redrawMapTable(image); + } + } + return null; + } + + private void redrawMapTable(Image image) { + if (image != null) { + if (listFixedMaps.getFixedCellHeight() != image.getHeight(null) + || listFixedMaps.getFixedCellWidth() != image.getWidth(null)) { + listFixedMaps.setFixedCellHeight(image.getHeight(null)); + listFixedMaps.setFixedCellWidth(image.getWidth(null)); + } + listFixedMaps.repaint(); + } + } + } +} diff --git a/MekHQ/src/mekhq/gui/dialog/EditScenarioDeploymentLimitDialog.java b/MekHQ/src/mekhq/gui/dialog/EditScenarioDeploymentLimitDialog.java new file mode 100644 index 0000000000..cc193feba4 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/EditScenarioDeploymentLimitDialog.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.gui.dialog; + +import megamek.client.ui.baseComponents.MMComboBox; +import megamek.common.UnitType; +import mekhq.MekHQ; +import mekhq.campaign.mission.ScenarioDeploymentLimit; +import mekhq.campaign.mission.ScenarioDeploymentLimit.CountType; +import mekhq.campaign.mission.ScenarioDeploymentLimit.QuantityType; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.ResourceBundle; + +public class EditScenarioDeploymentLimitDialog extends JDialog { + + private ScenarioDeploymentLimit deploymentLimit; + private boolean newLimit; + + private JSpinner spnQuantity; + private MMComboBox choiceQuantityType; + private MMComboBox choiceCountType; + private JCheckBox checkAllUnits; + private JCheckBox[] checkAllowedUnits; + + public EditScenarioDeploymentLimitDialog(JFrame parent, boolean modal, ScenarioDeploymentLimit limit) { + super(parent, modal); + if (limit == null) { + deploymentLimit = new ScenarioDeploymentLimit(); + newLimit = true; + } else { + deploymentLimit = limit; + newLimit = false; + } + initComponents(); + setLocationRelativeTo(parent); + pack(); + } + + private void initComponents() { + final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.EditScenarioDeploymentLimitsDialog", + MekHQ.getMHQOptions().getLocale()); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle(resourceMap.getString("dialog.title")); + + getContentPane().setLayout(new BorderLayout()); + JPanel panMain = new JPanel(new GridBagLayout()); + JPanel panButtons = new JPanel(new FlowLayout()); + + GridBagConstraints leftGbc = new GridBagConstraints(); + leftGbc.gridx = 0; + leftGbc.gridy = 0; + leftGbc.gridwidth = 1; + leftGbc.weightx = 0.0; + leftGbc.weighty = 0.0; + leftGbc.insets = new Insets(5, 5, 5, 10); + leftGbc.fill = GridBagConstraints.NONE; + leftGbc.anchor = GridBagConstraints.NORTHWEST; + + GridBagConstraints rightGbc = new GridBagConstraints(); + rightGbc.gridx = 1; + rightGbc.gridy = 0; + rightGbc.gridwidth = 1; + rightGbc.weightx = 1.0; + rightGbc.weighty = 0.0; + rightGbc.insets = new Insets(5, 10, 5, 5); + rightGbc.fill = GridBagConstraints.HORIZONTAL; + rightGbc.anchor = GridBagConstraints.NORTHWEST; + + panMain.add(new JLabel(resourceMap.getString("lblQuantityType.text")), leftGbc); + choiceQuantityType = new MMComboBox<>("choiceQuantityType", QuantityType.values()); + choiceQuantityType.setSelectedItem(deploymentLimit.getQuantityType()); + choiceQuantityType.addActionListener(this::setQuantityModel); + panMain.add(choiceQuantityType, rightGbc); + + + leftGbc.gridy++; + panMain.add(new JLabel(resourceMap.getString("lblCountType.text")), leftGbc); + choiceCountType = new MMComboBox<>("choiceCountType", CountType.values()); + choiceCountType.setSelectedItem(deploymentLimit.getCountType()); + choiceCountType.addActionListener(this::setQuantityModel); + rightGbc.gridy++; + panMain.add(choiceCountType, rightGbc); + + + leftGbc.gridy++; + panMain.add(new JLabel(resourceMap.getString("lblQuantity.text")), leftGbc); + spnQuantity = new JSpinner(); + spnQuantity.setValue(deploymentLimit.getQuantityLimit()); + setQuantityModel(null); + rightGbc.gridy++; + panMain.add(spnQuantity, rightGbc); + + JPanel panAllowedUnits = new JPanel(new GridLayout(UnitType.SIZE+1, 1)); + panAllowedUnits.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 10, 0), + BorderFactory.createTitledBorder(resourceMap.getString("panAllowedUnits.title")))); + checkAllUnits = new JCheckBox(resourceMap.getString("checkAllUnits.text")); + checkAllUnits.setSelected(deploymentLimit.getAllowedUnitTypes().isEmpty()); + checkAllUnits.addActionListener(this::checkAllUnits); + panAllowedUnits.add(checkAllUnits); + checkAllowedUnits = new JCheckBox [UnitType.SIZE]; + for (int i = UnitType.MEK; i < UnitType.SIZE; i++) { + JCheckBox check = new JCheckBox(UnitType.getTypeName(i)); + check.setSelected(deploymentLimit.getAllowedUnitTypes().contains(i)); + check.setEnabled(!checkAllUnits.isSelected()); + checkAllowedUnits[i] = check; + panAllowedUnits.add(check); + } + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 2; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.anchor = GridBagConstraints.NORTHWEST; + rightGbc.insets = new Insets(5, 5, 5, 5); + gbc.gridheight = 3; + panMain.add(panAllowedUnits, gbc); + + JButton btnOk = new JButton(resourceMap.getString("btnOK.text")); + btnOk.addActionListener(this::complete); + JButton btnClose = new JButton(resourceMap.getString("btnCancel.text")); + btnClose.addActionListener(this::cancel); + panButtons.add(btnOk); + panButtons.add(btnClose); + + getContentPane().add(panMain, BorderLayout.CENTER); + getContentPane().add(panButtons, BorderLayout.PAGE_END); + } + + private void checkAllUnits(ActionEvent evt) { + for (JCheckBox box : checkAllowedUnits) { + if (checkAllUnits.isSelected()) { + box.setSelected(false); + box.setEnabled(false); + } else { + box.setEnabled(true); + } + } + } + + private void setQuantityModel(ActionEvent evt) { + int currentQuantity = (int) spnQuantity.getValue(); + if (currentQuantity < 1) { + currentQuantity = 1; + } + CountType currentCountType = choiceCountType.getSelectedItem(); + QuantityType currentQuantityType = choiceQuantityType.getSelectedItem(); + if (currentQuantityType == QuantityType.PERCENT) { + if (currentQuantity > 100) { + currentQuantity = 100; + } + spnQuantity.setModel(new SpinnerNumberModel(currentQuantity, 1, 100, 5)); + } else { + if (currentCountType == CountType.UNIT) { + spnQuantity.setModel(new SpinnerNumberModel(currentQuantity, 1, null, 1)); + } else { + spnQuantity.setModel(new SpinnerNumberModel(currentQuantity, 1, null, 500)); + } + } + } + + private void complete(ActionEvent evt) { + deploymentLimit.setQuantityLimit((int) spnQuantity.getValue()); + deploymentLimit.setQuantityType(choiceQuantityType.getSelectedItem()); + deploymentLimit.setCountType(choiceCountType.getSelectedItem()); + ArrayList allowed = new ArrayList<>(); + if (!checkAllUnits.isSelected()) { + for (int i = UnitType.MEK; i < UnitType.SIZE; i++) { + if (checkAllowedUnits[i].isSelected()) { + allowed.add(i); + } + } + } + deploymentLimit.setAllowedUnitTypes(allowed); + this.setVisible(false); + } + + private void cancel(ActionEvent evt) { + if (newLimit) { + deploymentLimit = null; + } + this.setVisible(false); + } + + public ScenarioDeploymentLimit getDeploymentLimit() { + return deploymentLimit; + } +} diff --git a/MekHQ/src/mekhq/gui/model/BotForceTableModel.java b/MekHQ/src/mekhq/gui/model/BotForceTableModel.java new file mode 100644 index 0000000000..1ba43f5f5c --- /dev/null +++ b/MekHQ/src/mekhq/gui/model/BotForceTableModel.java @@ -0,0 +1,192 @@ +/* + * BotForceTableModel.java + * + * Copyright (c) 2009 Jay Lawson (jaylawson39 at yahoo.com). All rights reserved. + * Copyright (c) 2020 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.gui.model; + + +import mekhq.Utilities; +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.BotForce; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.util.List; + +public class BotForceTableModel extends AbstractTableModel { + + //region Variable Declarations + protected String[] columnNames; + protected List data; + private Campaign campaign; + + public final static int COL_NAME = 0; + public final static int COL_IFF = 1; + public final static int COL_FIXED = 2; + public final static int COL_RANDOM = 3; + public final static int COL_DEPLOYMENT = 4; + public final static int N_COL = 5; + //endregion Variable Declarations + + public BotForceTableModel(List entries, Campaign c) { + data = entries; + this.campaign = c; + } + + @Override + public int getRowCount() { + return data.size(); + } + + @Override + public int getColumnCount() { + return N_COL; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case COL_NAME: + return "Name"; + case COL_IFF: + return "IFF"; + case COL_FIXED: + return "Fixed"; + case COL_RANDOM: + return "Random"; + case COL_DEPLOYMENT: + return "Deployment"; + default: + return "?"; + } + } + + @Override + public Object getValueAt(int row, int col) { + BotForce botForce; + if (data.isEmpty()) { + return ""; + } else { + botForce = getBotForceAt(row); + } + + switch (col) { + case COL_NAME: + return botForce.getName(); + case COL_IFF: + return (botForce.getTeam() == 1) ? "Allied" : "Enemy (Team " + botForce.getTeam() + ")"; + case COL_FIXED: + return botForce.getFixedEntityList().size() + " Units, BV: " + botForce.getFixedBV(); + case COL_RANDOM: + return ((null == botForce.getBotForceRandomizer()) ? "" : botForce.getBotForceRandomizer(). + getShortDescription()); + case COL_DEPLOYMENT: + return Utilities.getDeploymentString(botForce); + default: + return "?"; + } + } + + @Override + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + + public BotForce getBotForceAt(int row) { + return data.get(row); + } + + public void addForce(BotForce botForce) { + data.add(botForce); + fireTableDataChanged(); + } + + public List getAllBotForces() { + return data; + } + + public int getColumnWidth(int col) { + switch (col) { + case COL_NAME: + return 80; + case COL_DEPLOYMENT: + return 20; + default: + return 30; + } + } + + public int getAlignment(int col) { + switch (col) { + case COL_NAME: + case COL_IFF: + return SwingConstants.LEFT; + case COL_DEPLOYMENT: + return SwingConstants.CENTER; + default: + return SwingConstants.RIGHT; + } + } + + public String getTooltip(int row, int col) { + BotForce botForce; + if (data.isEmpty()) { + return ""; + } else { + botForce = getBotForceAt(row); + } + + switch (col) { + case COL_RANDOM: + return ((null == botForce.getBotForceRandomizer()) ? "" : botForce.getBotForceRandomizer(). + getDescription(campaign)); + default: + return null; + } + } + + //fill table with values + public void setData(List botForce) { + data = botForce; + fireTableDataChanged(); + } + + public BotForceTableModel.Renderer getRenderer() { + return new BotForceTableModel.Renderer(); + } + + public class Renderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setOpaque(true); + int actualCol = table.convertColumnIndexToModel(column); + int actualRow = table.convertRowIndexToModel(row); + setHorizontalAlignment(getAlignment(actualCol)); + setToolTipText(getTooltip(actualRow, actualCol)); + + return this; + } + } +} diff --git a/MekHQ/src/mekhq/gui/model/ObjectiveTableModel.java b/MekHQ/src/mekhq/gui/model/ObjectiveTableModel.java new file mode 100644 index 0000000000..d4c6c6fef2 --- /dev/null +++ b/MekHQ/src/mekhq/gui/model/ObjectiveTableModel.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * 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 . + */ +package mekhq.gui.model; + +import mekhq.campaign.mission.ObjectiveEffect; +import mekhq.campaign.mission.ScenarioObjective; +import mekhq.campaign.mission.ScenarioObjective.ObjectiveAmountType; +import mekhq.campaign.mission.ScenarioObjective.TimeLimitType; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.util.List; + +/** + * TableModel for displaying information about a scenario objective + */ +public class ObjectiveTableModel extends AbstractTableModel { + //region Variable Declarations + protected String[] columnNames; + protected List data; + + public final static int COL_CRITERION = 0; + public final static int COL_AMOUNT = 1; + public final static int COL_TIME = 2; + public final static int COL_SUCCESS_EFFECT = 3; + public final static int COL_FAILURE_EFFECT = 4; + public final static int N_COL = 5; + //endregion Variable Declarations + + public ObjectiveTableModel(List entries) { + data = entries; + } + + @Override + public int getRowCount() { + return data.size(); + } + + @Override + public int getColumnCount() { + return N_COL; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case COL_CRITERION: + return "Type"; + case COL_AMOUNT: + return "Amount"; + case COL_TIME: + return "Time limits"; + case COL_SUCCESS_EFFECT: + return "On Success"; + case COL_FAILURE_EFFECT: + return "On Failure"; + default: + return "?"; + } + } + + public void addObjective(ScenarioObjective objective) { + data.add(objective); + fireTableDataChanged(); + } + + @Override + public Object getValueAt(int row, int col) { + ScenarioObjective objective; + if (data.isEmpty()) { + return ""; + } else { + objective = getObjectiveAt(row); + } + + switch (col) { + case COL_CRITERION: + return objective.getObjectiveCriterion().toString(); + case COL_AMOUNT: + return objective.getAmountType().equals(ObjectiveAmountType.Percentage) ? + objective.getPercentage() + "%" : objective.getAmount() + " units"; + case COL_TIME: + if(objective.getTimeLimitType().equals(TimeLimitType.None)) { + return "None"; + } + String timeDirection = objective.isTimeLimitAtMost() ? "At most " : "At least "; + return objective.getTimeLimitType().equals(TimeLimitType.Fixed) ? + timeDirection + objective.getTimeLimit() + " turns" : + timeDirection + '(' + objective.getTimeLimitScaleFactor() + "x unit count) turns"; + case COL_SUCCESS_EFFECT: + return objective.getSuccessEffects().size() + " Effect(s)"; + case COL_FAILURE_EFFECT: + return objective.getFailureEffects().size() + " Effect(s)"; + default: + return "?"; + } + } + + public ScenarioObjective getObjectiveAt(int row) { + return data.get(row); + } + + public int getColumnWidth(int c) { + switch (c) { + default: + return 20; + } + } + + public int getAlignment(int col) { + switch (col) { + default: + return SwingConstants.LEFT; + } + } + + public String getTooltip(int row, int col) { + ScenarioObjective objective; + if (data.isEmpty()) { + return null; + } else { + objective = getObjectiveAt(row); + } + StringBuilder sb; + + switch (col) { + case COL_CRITERION: + return "" + String.join("
", objective.getAssociatedForceNames()) +""; + case COL_SUCCESS_EFFECT: + sb = new StringBuilder(); + sb.append(""); + for(ObjectiveEffect effect : objective.getSuccessEffects()) { + sb.append(effect.toString()).append("
"); + } + sb.append(""); + return sb.toString(); + case COL_FAILURE_EFFECT: + sb = new StringBuilder(); + sb.append(""); + for(ObjectiveEffect effect : objective.getFailureEffects()) { + sb.append(effect.toString()).append("
"); + } + sb.append(""); + return sb.toString(); + default: + return null; + } + } + + //fill table with values + public void setData(List objectives) { + data = objectives; + fireTableDataChanged(); + } + + public Renderer getRenderer() { + return new Renderer(); + } + + public class Renderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setOpaque(true); + int actualCol = table.convertColumnIndexToModel(column); + int actualRow = table.convertRowIndexToModel(row); + setHorizontalAlignment(getAlignment(actualCol)); + setToolTipText(getTooltip(actualRow, actualCol)); + + return this; + } + } +} diff --git a/MekHQ/src/mekhq/gui/view/AtBScenarioViewPanel.java b/MekHQ/src/mekhq/gui/view/AtBScenarioViewPanel.java index e6752f9059..19662c9272 100644 --- a/MekHQ/src/mekhq/gui/view/AtBScenarioViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/AtBScenarioViewPanel.java @@ -28,6 +28,7 @@ import megamek.common.planetaryconditions.Atmosphere; import megamek.common.planetaryconditions.PlanetaryConditions; import mekhq.MekHQ; +import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.force.ForceStub; import mekhq.campaign.force.UnitStub; @@ -123,9 +124,9 @@ public AtBScenarioViewPanel(AtBScenario s, Campaign c, JFrame frame) { if (s.getStatus().isCurrent()) { s.refresh(c); this.playerForces = new ForceStub(s.getForces(campaign), campaign); - attachedAllyStub = s.generateEntityStub(s.getAlliesPlayer()); + attachedAllyStub = Utilities.generateEntityStub(s.getAlliesPlayer()); for (int i = 0; i < s.getNumBots(); i++) { - botStubs.add(s.generateBotStub(s.getBotForce(i), campaign)); + botStubs.add(s.getBotForce(i).generateStub(campaign)); } } else { this.playerForces = s.getForceStub(); diff --git a/MekHQ/src/mekhq/gui/view/ScenarioViewPanel.java b/MekHQ/src/mekhq/gui/view/ScenarioViewPanel.java index eef9caa389..48b6db6657 100644 --- a/MekHQ/src/mekhq/gui/view/ScenarioViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/ScenarioViewPanel.java @@ -80,7 +80,7 @@ public ScenarioViewPanel(JFrame f, Campaign c, Scenario s) { botStubs = new ArrayList<>(); if (s.getStatus().isCurrent()) { for (int i = 0; i < s.getNumBots(); i++) { - botStubs.add(s.generateBotStub(s.getBotForce(i), campaign)); + botStubs.add(s.getBotForce(i).generateStub(campaign)); } } else { botStubs = s.getBotForcesStubs();