diff --git a/megamek/data/scenarios/Example.mms b/megamek/data/scenarios/Example.mms index e373b386c54..ab354f91206 100644 --- a/megamek/data/scenarios/Example.mms +++ b/megamek/data/scenarios/Example.mms @@ -1,10 +1,6 @@ # # A MegaMek Scenario file # -# Future features for the scenario language -# Alternate victory conditions -# Staggered entry (reinforcements) -# Specified critical slot damage # # Versionstamp required to be recognized as a Scenario file @@ -16,6 +12,7 @@ Name=Example Scenario # Scenario description Description=This is an example scenario to show different scenario features +# Map Setup ------------------------------------------------------ # Size of the map in mapboards BoardWidth=2 BoardHeight=1 @@ -27,10 +24,59 @@ RandomDirs=MapSet2,MapSet3,MapSet4,MapSet5,MapSet6,MapSet7 # Any unspecified boards will be set to RANDOM Maps=RANDOM,RANDOM -# Faction list +# Game/Rule Options ---------------------------------------------- +# This is an xml file which can be created by copying your +# mmconf/gameoptions.xml +# path is specified relative to the scenario file +# This is one way to set victory conditions +GameOptionsFile=Example_options.xml +# The Game Options can be fixed. In this case the Game Options Dialog shown before the +# scenario starts is skipped. +FixedGameOptions=true + +# Planetary Conditions ------------------------------------------ +# Planetary Conditions can be fixed. In this case the Planetary Conditions Dialog shown before the +# scenario starts is skipped. +FixedPlanetaryConditions=true +# Temperature: Only integer values are allowed +PlanetaryConditionsTemperature=-14 +PlanetaryConditionsGravity=1.12 +# Light: Default = Daylight; 1 = Dusk; 2 = Full Moon Night; 3 = Moonless Night; 4 = Pitch Black +PlanetaryConditionsLight=1 +# Weather: Default = None; 1/2/3/4 = Light/Moderate/Heavy/Gusting Rain; 5 = Downpour; +# 6/7/9 Light/Moderate/Heavy Snow; 8 = Snow Flurries; 10 = Sleet; 11 = Blizzard; +# 12 = Ice Storm; 13/14 = Light/Heavy Hail +PlanetaryConditionsWeather=13 +# Wind: Default = None; 1/2/3 = Light/Moderate/Strong Gale; 4 = Storm; 5 = Tornado F1-3; 6 = Tornado F4 +PlanetaryConditionsWind=4 +# Wind Direction: Default = Random; 0 = N; 1 = NE; 2 = SE; 3 = S; 4 = SW; 5 = NW +PlanetaryConditionsWindDir=2 +# Atmospheric Pressure: Default = Standard; 0 = Vacuum; 1 = Trace; 2 = Thin; 4 = High; 5 = Very High +PlanetaryConditionsAtmosphere=2 +# Fog: Default = No Fog; 1 = Light Fog; 2 = Heavy Fog +PlanetaryConditionsFog=1 +# Shifting Wind +# Strength: Default = off; default min. Wind = 0 (see Wind above); default max. Wind = 6 +# Direction: Default = off; +PlanetaryConditionsWindShiftingStr=true +PlanetaryConditionsWindMin=1 +PlanetaryConditionsWindMax=3 +PlanetaryConditionsWindShiftingDir=true +# Blowing Sand: Default = off +PlanetaryConditionsBlowingSand=true +# EMI: Default = off +PlanetaryConditionsEMI=true +# Allow Terrain Changes: Default = on +PlanetaryConditionsAllowTerrainChanges=false + +# Faction (= Player) list --------------------------------------- +# A scenario can be set to single player style. In this case the first player is +# the human player and all other players are Princess bots. This will skip the +# Player/Camo assignment dialog and the "Host game" dialog and directly connect +# to a localhost Server and use the correct player name. +SinglePlayer=true # The player name used to log into the server MUST match this name to play as # that faction. Player names can *not* include spaces. -# Factions=PlayerA,PlayerB,PlayerC # Faction location @@ -53,7 +99,13 @@ Team_PlayerC=2 Minefields_PlayerA=2,0,2 Minefields_PlayerB=1,0,3 -# Mechlist for each faction +# Player Camos +# Assigns a camo to a player; advisable in single player scenarios where the player can't do this +# The directory and filename must be separated by a comma and the directory must end in a / +Camo_PlayerA=Clans/Wolf/,Alpha Galaxy.jpg +Camo_PlayerB=Clans/Burrock/,Alpha.jpg + +# Mechlist for each faction ------------------------------------------------- # # Units are constructed as Unit__<#>, where the faction name # matches the one listed in the Faction property and the # is a sequential @@ -88,6 +140,11 @@ Unit_PlayerA_1_Commander=true Unit_PlayerB_1_Commander=true Unit_PlayerC_1_Commander=true +# Unit Camos +# Assigns a camo to a unit, overriding any player camo +# The directory and filename must be separated by a comma and the directory must end in a / +Unit_PlayerA_1_Camo=Clans/Wolf/,Alpha Galaxy.jpg + # To initially damage units, you can use a unit armor property, which specifies # armor and internal values. Values above the unit's nominal value for that # location will be ignored. @@ -198,9 +255,3 @@ Unit_PlayerA_2_SetAmmoType=3:1-ISAC20 Flak Ammo # # Unit_Kurita_666_Altitude=3 -# Set game options file -# This is an xml file which can be created by copying your -# mmconf/gameoptions.xml -# path is specified relative to the scenario file -# This is one way to set victory conditions -GameOptionsFile=Example_options.xml diff --git a/megamek/src/megamek/client/ui/swing/MegaMekGUI.java b/megamek/src/megamek/client/ui/swing/MegaMekGUI.java index 423ca6c5c46..40068f6c9de 100644 --- a/megamek/src/megamek/client/ui/swing/MegaMekGUI.java +++ b/megamek/src/megamek/client/ui/swing/MegaMekGUI.java @@ -46,6 +46,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Vector; @@ -675,71 +676,99 @@ public String getDescription() { } // popup options dialog - GameOptionsDialog god = new GameOptionsDialog(frame, g.getOptions(), false); - god.update(g.getOptions()); - god.setEditable(true); - god.setVisible(true); - for (IBasicOption opt : god.getOptions()) { - IOption orig = g.getOptions().getOption(opt.getName()); - orig.setValue(opt.getValue()); + if (!sl.hasFixedGameOptions()) { + GameOptionsDialog god = new GameOptionsDialog(frame, g.getOptions(), false); + god.update(g.getOptions()); + god.setEditable(true); + god.setVisible(true); + for (IBasicOption opt : god.getOptions()) { + IOption orig = g.getOptions().getOption(opt.getName()); + orig.setValue(opt.getValue()); + } } // popup planetary conditions dialog - PlanetaryConditionsDialog pcd = new PlanetaryConditionsDialog(frame, g.getPlanetaryConditions()); - pcd.update(g.getPlanetaryConditions()); - pcd.setVisible(true); - g.setPlanetaryConditions(pcd.getConditions()); - - // get player types and colors set + if (!sl.hasFixedPlanetCond()) { + PlanetaryConditionsDialog pcd = new PlanetaryConditionsDialog(frame, g.getPlanetaryConditions()); + pcd.update(g.getPlanetaryConditions()); + pcd.setVisible(true); + g.setPlanetaryConditions(pcd.getConditions()); + } + + String playerName; + int port; + String serverPW; + String localName; Player[] pa = new Player[g.getPlayersVector().size()]; + int[] playerTypes = new int[pa.length]; g.getPlayersVector().copyInto(pa); - ScenarioDialog sd = new ScenarioDialog(frame, pa); - sd.setVisible(true); - if (!sd.bSet) { - return; - } - - HostDialog hd = new HostDialog(frame); boolean hasSlot = false; - if (!("".equals(sd.localName))) { - hasSlot = true; - } - hd.setPlayerName(sd.localName); - hd.setVisible(true); - if (!hd.dataValidation("MegaMek.HostScenarioAlert.title")) { - return; - } + // get player types and colors set + if (!sl.isSinglePlayer()) { + ScenarioDialog sd = new ScenarioDialog(frame, pa); + sd.setVisible(true); + if (!sd.bSet) { + return; + } + + HostDialog hd = new HostDialog(frame); + if (!("".equals(sd.localName))) { + hasSlot = true; + } + hd.setPlayerName(sd.localName); + hd.setVisible(true); - sd.localName = hd.getPlayerName(); + if (!hd.dataValidation("MegaMek.HostScenarioAlert.title")) { + return; + } + + sd.localName = hd.getPlayerName(); + localName = hd.getPlayerName(); + playerName = hd.getPlayerName(); + port = hd.getPort(); + serverPW = hd.getServerPass(); + playerTypes = Arrays.copyOf(sd.playerTypes, playerTypes.length); + + } else { + hasSlot = true; + playerName = pa[0].getName(); + localName = playerName; + port = 2346; + serverPW = ""; + playerTypes[0] = 0; + for (int i = 1; i < playerTypes.length; i++) { + playerTypes[i] = ScenarioDialog.T_BOT; + } + } // kick off a RNG check Compute.d6(); // start server try { - server = new Server(hd.getServerPass(), hd.getPort()); + server = new Server(serverPW, port); } catch (Exception ex) { - MegaMek.getLogger().error("Could not create server socket on port " + hd.getPort(), ex); + MegaMek.getLogger().error("Could not create server socket on port " + port, ex); JOptionPane.showMessageDialog(frame, - Messages.getFormattedString("MegaMek.StartServerError", hd.getPort(), ex.getMessage()), + Messages.getFormattedString("MegaMek.StartServerError", port, ex.getMessage()), Messages.getString("MegaMek.HostScenarioAlert.title"), JOptionPane.ERROR_MESSAGE); return; } server.setGame(g); - + // apply any scenario damage sl.applyDamage(server); ClientGUI gui = null; - if (!"".equals(sd.localName)) { + if (!"".equals(localName)) { // initialize game - client = new Client(hd.getPlayerName(), "localhost", hd.getPort()); + client = new Client(playerName, "localhost", port); gui = new ClientGUI(client, controller); controller.clientgui = gui; gui.initialize(); if (!client.connect()) { JOptionPane.showMessageDialog(frame, - Messages.getFormattedString("MegaMek.ServerConnectionError", "localhost", hd.getPort()), + Messages.getFormattedString("MegaMek.ServerConnectionError", "localhost", port), Messages.getString("MegaMek.HostScenarioAlert.title"), JOptionPane.ERROR_MESSAGE); frame.setVisible(false); client.die(); @@ -752,14 +781,14 @@ public String getDescription() { // setup any bots for (int x = 0; x < pa.length; x++) { - if (sd.playerTypes[x] == ScenarioDialog.T_BOT) { + if (playerTypes[x] == ScenarioDialog.T_BOT) { MegaMek.getLogger().info("Adding bot " + pa[x].getName() + " as Princess"); - BotClient c = new Princess(pa[x].getName(), "localhost", hd.getPort(), LogLevel.ERROR); + BotClient c = new Princess(pa[x].getName(), "localhost", port, LogLevel.ERROR); c.getGame().addGameListener(new BotGUI(c)); c.connect(); - } else if (sd.playerTypes[x] == ScenarioDialog.T_OBOT) { + } else if (playerTypes[x] == ScenarioDialog.T_OBOT) { MegaMek.getLogger().info("Adding bot " + pa[x].getName() + " as TestBot"); - BotClient c = new TestBot(pa[x].getName(), "localhost", hd.getPort()); + BotClient c = new TestBot(pa[x].getName(), "localhost", port); c.getGame().addGameListener(new BotGUI(c)); c.connect(); } @@ -771,7 +800,7 @@ public String getDescription() { Enumeration pE = server.getGame().getPlayers(); while (pE.hasMoreElements()) { IPlayer tmpP = pE.nextElement(); - if (tmpP.getName().equals(sd.localName)) { + if (tmpP.getName().equals(localName)) { tmpP.setObserver(true); } } diff --git a/megamek/src/megamek/server/ScenarioLoader.java b/megamek/src/megamek/server/ScenarioLoader.java index d683d7639cc..5c26280ee86 100644 --- a/megamek/src/megamek/server/ScenarioLoader.java +++ b/megamek/src/megamek/server/ScenarioLoader.java @@ -89,8 +89,27 @@ public class ScenarioLoader { private static final String PARAM_MMSVERSION = "MMSVersion"; private static final String PARAM_GAME_OPTIONS_FILE = "GameOptionsFile"; + private static final String PARAM_GAME_OPTIONS_FIXED = "FixedGameOptions"; private static final String PARAM_GAME_EXTERNAL_ID = "ExternalId"; private static final String PARAM_FACTIONS = "Factions"; + private static final String PARAM_SINGLEPLAYER = "SinglePlayer"; + + private static final String PARAM_PLANETCOND_FIXED = "FixedPlanetaryConditions"; + private static final String PARAM_PLANETCOND_TEMP = "PlanetaryConditionsTemperature"; + private static final String PARAM_PLANETCOND_GRAV = "PlanetaryConditionsGravity"; + private static final String PARAM_PLANETCOND_LIGHT = "PlanetaryConditionsLight"; + private static final String PARAM_PLANETCOND_WEATHER = "PlanetaryConditionsWeather"; + private static final String PARAM_PLANETCOND_WIND = "PlanetaryConditionsWind"; + private static final String PARAM_PLANETCOND_WINDDIR = "PlanetaryConditionsWindDir"; + private static final String PARAM_PLANETCOND_ATMOS = "PlanetaryConditionsAtmosphere"; + private static final String PARAM_PLANETCOND_FOG = "PlanetaryConditionsFog"; + private static final String PARAM_PLANETCOND_WINDSHIFTINGSTR = "PlanetaryConditionsWindShiftingStr"; + private static final String PARAM_PLANETCOND_WINDMIN = "PlanetaryConditionsWindMin"; + private static final String PARAM_PLANETCOND_WINDMAX = "PlanetaryConditionsWindMax"; + private static final String PARAM_PLANETCOND_WINDSHIFTINGDIR = "PlanetaryConditionsWindShiftingDir"; + private static final String PARAM_PLANETCOND_BLOWINGSAND = "PlanetaryConditionsBlowingSand"; + private static final String PARAM_PLANETCOND_EMI = "PlanetaryConditionsEMI"; + private static final String PARAM_PLANETCOND_TERRAINCHANGES = "PlanetaryConditionsAllowTerrainChanges"; private static final String PARAM_MAP_WIDTH = "MapWidth"; private static final String PARAM_MAP_HEIGHT = "MapHeight"; @@ -127,6 +146,19 @@ public class ScenarioLoader { // Used to set ammo Spec Ammounts private final List ammoPlans = new ArrayList<>(); + /** When true, the Game Options Dialog is skipped. */ + private boolean fixedGameOptions = false; + + /** When true, the Planetary Conditions Dialog is skipped. */ + private boolean fixedPlanetCond; + + /** + * When true, the Player assignment/camo Dialog and the host dialog are skipped. + * The first faction (player) is assumed to be the local player and the rest + * are assumed to be Princess. + */ + private boolean singlePlayer; + public ScenarioLoader(File f) { scenarioFile = f; } @@ -389,9 +421,14 @@ public IGame createGame() throws Exception { } else { g.getOptions().loadOptions(new MegaMekFile(scenarioFile.getParentFile(), optionFile).getFile(), true); } + fixedGameOptions = parseBoolean(p, PARAM_GAME_OPTIONS_FIXED, false); // set wind + parsePlanetaryConditions(g, p); g.getPlanetaryConditions().determineWind(); + fixedPlanetCond = parseBoolean(p, PARAM_PLANETCOND_FIXED, false); + + singlePlayer = parseBoolean(p, PARAM_SINGLEPLAYER, false); // Set up the teams (for initiative) g.setupTeams(); @@ -409,6 +446,68 @@ public IGame createGame() throws Exception { return g; } + private void parsePlanetaryConditions(Game g, StringMultiMap p) { + if (p.containsKey(PARAM_PLANETCOND_TEMP)) { + g.getPlanetaryConditions().setTemperature(Integer.parseInt(p.getString(PARAM_PLANETCOND_TEMP))); + } + + if (p.containsKey(PARAM_PLANETCOND_GRAV)) { + g.getPlanetaryConditions().setGravity(Float.parseFloat(p.getString(PARAM_PLANETCOND_GRAV))); + } + + if (p.containsKey(PARAM_PLANETCOND_FOG)) { + g.getPlanetaryConditions().setFog(Integer.parseInt(p.getString(PARAM_PLANETCOND_FOG))); + } + + if (p.containsKey(PARAM_PLANETCOND_ATMOS)) { + g.getPlanetaryConditions().setAtmosphere(Integer.parseInt(p.getString(PARAM_PLANETCOND_ATMOS))); + } + + if (p.containsKey(PARAM_PLANETCOND_LIGHT)) { + g.getPlanetaryConditions().setLight(Integer.parseInt(p.getString(PARAM_PLANETCOND_LIGHT))); + } + + if (p.containsKey(PARAM_PLANETCOND_WEATHER)) { + g.getPlanetaryConditions().setWeather(Integer.parseInt(p.getString(PARAM_PLANETCOND_WEATHER))); + } + + if (p.containsKey(PARAM_PLANETCOND_WIND)) { + g.getPlanetaryConditions().setWindStrength(Integer.parseInt(p.getString(PARAM_PLANETCOND_WIND))); + } + + if (p.containsKey(PARAM_PLANETCOND_WINDDIR)) { + g.getPlanetaryConditions().setWindDirection(Integer.parseInt(p.getString(PARAM_PLANETCOND_WINDDIR))); + } + + if (p.containsKey(PARAM_PLANETCOND_WINDSHIFTINGDIR)) { + g.getPlanetaryConditions().setShiftingWindDirection(parseBoolean(p, PARAM_PLANETCOND_WINDSHIFTINGDIR, false)); + } + + if (p.containsKey(PARAM_PLANETCOND_WINDSHIFTINGSTR)) { + g.getPlanetaryConditions().setShiftingWindStrength(parseBoolean(p, PARAM_PLANETCOND_WINDSHIFTINGSTR, false)); + } + + if (p.containsKey(PARAM_PLANETCOND_WINDMIN)) { + g.getPlanetaryConditions().setMinWindStrength(Integer.parseInt(p.getString(PARAM_PLANETCOND_WINDMIN))); + } + + if (p.containsKey(PARAM_PLANETCOND_WINDMAX)) { + g.getPlanetaryConditions().setMaxWindStrength(Integer.parseInt(p.getString(PARAM_PLANETCOND_WINDMAX))); + } + + if (p.containsKey(PARAM_PLANETCOND_EMI)) { + g.getPlanetaryConditions().setEMI(parseBoolean(p, PARAM_PLANETCOND_EMI, false)); + } + + if (p.containsKey(PARAM_PLANETCOND_TERRAINCHANGES)) { + g.getPlanetaryConditions().setTerrainAffected(parseBoolean(p, PARAM_PLANETCOND_TERRAINCHANGES, true)); + } + + if (p.containsKey(PARAM_PLANETCOND_BLOWINGSAND)) { + g.getPlanetaryConditions().setBlowingSand(parseBoolean(p, PARAM_PLANETCOND_BLOWINGSAND, false)); + } + } + private Collection buildFactionEntities(StringMultiMap p, IPlayer player) throws ScenarioLoaderException { String faction = player.getName(); Pattern unitPattern = Pattern.compile(String.format("^Unit_\\Q%s\\E_[^_]+$", faction)); @@ -864,6 +963,37 @@ private int parseExternalGameId(StringMultiMap p) { } return ExternalGameId; } + + public boolean hasFixedGameOptions() { + return fixedGameOptions; + } + + public boolean hasFixedPlanetCond() { + return fixedPlanetCond; + } + + public boolean isSinglePlayer() { + return singlePlayer; + } + + /** + * Parses a boolean value. When the key is not present, returns the given + * defaultValue. When the key is present, interprets "true" and "on" and "1" + * as true and everything else as false. + */ + private boolean parseBoolean(StringMultiMap p, String key, boolean defaultValue) { + boolean result = defaultValue; + if (p.containsKey(key)) { + if (p.getString(key).equalsIgnoreCase("true") + || p.getString(key).equalsIgnoreCase("on") + || p.getString(key).equalsIgnoreCase("1")) { + result = true; + } else { + result = false; + } + } + return result; + } public static void main(String[] saArgs) throws Exception { ScenarioLoader sl = new ScenarioLoader(new File(saArgs[0])); @@ -1099,4 +1229,5 @@ public int getNumValues(String key) { return (values == null) ? 0 : values.size(); } } + }