/* * MekBayApp.java * * Copyright (c) 2009 Jay Lawson <jaylawson39 at yahoo.com>. 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 <http://www.gnu.org/licenses/>. */ package mekhq; import java.awt.FileDialog; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Properties; import java.util.ResourceBundle; import javax.swing.JOptionPane; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import megamek.client.Client; import megamek.common.event.EventBus; import megamek.common.event.GameBoardChangeEvent; import megamek.common.event.GameBoardNewEvent; import megamek.common.event.GameCFREvent; import megamek.common.event.GameEndEvent; import megamek.common.event.GameEntityChangeEvent; import megamek.common.event.GameEntityNewEvent; import megamek.common.event.GameEntityNewOffboardEvent; import megamek.common.event.GameEntityRemoveEvent; import megamek.common.event.GameListener; import megamek.common.event.GameMapQueryEvent; import megamek.common.event.GameNewActionEvent; import megamek.common.event.GamePhaseChangeEvent; import megamek.common.event.GamePlayerChangeEvent; import megamek.common.event.GamePlayerChatEvent; import megamek.common.event.GamePlayerConnectedEvent; import megamek.common.event.GamePlayerDisconnectedEvent; import megamek.common.event.GameReportEvent; import megamek.common.event.GameSettingsChangeEvent; import megamek.common.event.GameTurnChangeEvent; import megamek.common.event.GameVictoryEvent; import megamek.common.event.MMEvent; import megamek.common.util.EncodeControl; import megamek.server.Server; import mekhq.campaign.Campaign; import mekhq.campaign.ResolveScenarioTracker; import mekhq.campaign.event.ScenarioResolvedEvent; import mekhq.campaign.handler.XPHandler; import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.Scenario; import mekhq.campaign.unit.Unit; import mekhq.gui.CampaignGUI; import mekhq.gui.StartUpGUI; import mekhq.gui.dialog.LaunchGameDialog; import mekhq.gui.dialog.ResolveScenarioWizardDialog; import mekhq.gui.dialog.RetirementDefectionDialog; /** * The main class of the application. */ public class MekHQ implements GameListener { //TODO: This is intended as a debug/production type thing. // So it should be backed down to 1 for releases... // It's intended for 1 to be critical, 3 to be typical, and 5 to be debug/informational. public static int VERBOSITY_LEVEL = 5; public static String CAMPAIGN_DIRECTORY = "./campaigns/"; public static String PROPERTIES_FILE = "mmconf/mekhq.properties"; public static String PRESET_DIR = "./mmconf/mhqPresets/"; private static final EventBus EVENT_BUS = new EventBus(); //stuff related to MM games private Server myServer = null; private GameThread gameThread = null; private Scenario currentScenario = null; private Client client = null; //the actual campaign - this is where the good stuff is private Campaign campaign; private CampaignGUI campaigngui; private IconPackage iconPackage = new IconPackage(); private Properties preferences; /** * Designed to centralize output and logging. * Purely a pass-through to the version with a log level. * Default to log level 3. * * @param msg The message you want to log. */ public static void logMessage(String msg) { logMessage(msg, 3); } /** * Designed to centralize output and logging. * * @param msg The message you want to log. * @param logLevel The log level of the message. */ public static void logMessage(String msg, int logLevel) { if (logLevel <= VERBOSITY_LEVEL) System.out.println(msg); } public static void logError(String err) { System.err.println(err); } public static void logError(Exception ex) { System.err.println(ex); ex.printStackTrace(); } protected static MekHQ getInstance() { return new MekHQ(); } /** * At startup create and show the main frame of the application. */ protected void startup() { ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.MekHQ", new EncodeControl()); //$NON-NLS-1$ MekHQ.logMessage(resourceMap.getString("Application.name") + " " + resourceMap.getString("Application.version")); //read in preferences readPreferences(); setLookAndFeel(); initEventHandlers(); //create a start up frame and display it StartUpGUI sud = new StartUpGUI(this); sud.setVisible(true); } public void exit() { if(JOptionPane.showConfirmDialog(null, "Do you really want to quit MekHQ?", "Quit?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { if(null != campaigngui) { campaigngui.getFrame().dispose(); } savePreferences(); System.exit(0); } } public void showNewView() { campaigngui = new CampaignGUI(this); campaigngui.showOverviewTab(campaign.isOverviewLoadingValue()); } /** * Main method launching the application. */ public static void main(String[] args) { System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("com.apple.mrj.application.apple.menu.about.name","MekHQ"); //redirect output to log file redirectOutput(); MekHQ.getInstance().startup(); } protected static Properties setDefaultPreferences() { Properties defaults = new Properties(); defaults.setProperty("laf", UIManager.getSystemLookAndFeelClassName()); return defaults; } protected void readPreferences() { preferences = new Properties(setDefaultPreferences()); try { preferences.load(new FileInputStream(PROPERTIES_FILE)); MekHQ.logMessage("loading mekhq properties from " + PROPERTIES_FILE); } catch (FileNotFoundException e) { MekHQ.logMessage("No mekhq properties file found. Reverting to defaults."); } catch (IOException e) { e.printStackTrace(); } } protected void savePreferences() { preferences.setProperty("laf", UIManager.getLookAndFeel().getClass().getName()); try { preferences.store(new FileOutputStream(PROPERTIES_FILE), "MekHQ Preferences"); } catch (FileNotFoundException e) { MekHQ.logMessage("could not save preferences to " + PROPERTIES_FILE); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } protected void setLookAndFeel() { //TODO: we can extend this with other look and feel options try { UIManager.setLookAndFeel(preferences.getProperty("laf")); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (UnsupportedLookAndFeelException e) { e.printStackTrace(); } } /** * This function redirects the standard error and output streams to the * given File name. * */ private static void redirectOutput() { try { System.out.println("Redirecting output to mekhqlog.txt"); //$NON-NLS-1$ File logDir = new File("logs"); if (!logDir.exists()) { logDir.mkdir(); } PrintStream ps = new PrintStream(new BufferedOutputStream(new FileOutputStream("logs" + File.separator + "mekhqlog.txt"), 64)); System.setOut(ps); System.setErr(ps); } catch (Exception e) { System.err.println("Unable to redirect output to mekhqlog.txt"); //$NON-NLS-1$ e.printStackTrace(); } } public Server getMyServer() { return myServer; } public Campaign getCampaign() { return campaign; } public void setCampaign(Campaign c) { campaign = c; } /** * @return the campaigngui */ public CampaignGUI getCampaigngui() { return campaigngui; } /** * @param campaigngui the campaigngui to set */ public void setCampaigngui(CampaignGUI campaigngui) { this.campaigngui = campaigngui; } public void joinGame(Scenario scenario, ArrayList<Unit> meks) { LaunchGameDialog lgd = new LaunchGameDialog(campaigngui.getFrame(), false, campaign); lgd.setVisible(true); try { client = new Client(lgd.playerName, lgd.serverAddr, lgd.port); } catch (Exception ex) { MekHQ.logMessage("Failed to connect to server properly"); MekHQ.logError(ex); return; } client.getGame().addGameListener(this); currentScenario = scenario; //Start the game thread gameThread = new GameThread(lgd.playerName, client, this, meks, false); gameThread.start(); } public void startHost(Scenario scenario, boolean loadSavegame, ArrayList<Unit> meks) { LaunchGameDialog lgd = new LaunchGameDialog(campaigngui.getFrame(), true, campaign); lgd.setVisible(true); try { myServer = new Server("", lgd.port); if (loadSavegame) { FileDialog f = new FileDialog(campaigngui.getFrame(), "Load Savegame"); f.setDirectory(System.getProperty("user.dir") + "/savegames"); f.setVisible(true); if (null != f.getFile()) { myServer.loadGame(new File(f.getDirectory(), f.getFile())); } else { stopHost(); throw new FileNotFoundException(); } } } catch (FileNotFoundException ex) { // The dialog was cancelled or the file not found // Return to the UI stopHost(); return; } catch (Exception ex) { MekHQ.logMessage("Failed to start up server properly"); MekHQ.logError(ex); stopHost(); return; } client = new Client(lgd.playerName, "127.0.0.1", lgd.port); client.getGame().addGameListener(this); currentScenario = scenario; //Start the game thread if (campaign.getCampaignOptions().getUseAtB() && scenario instanceof AtBScenario) { gameThread = new AtBGameThread(lgd.playerName, client, this, meks, (AtBScenario)scenario); } else { gameThread = new GameThread(lgd.playerName, client, this, meks); } gameThread.start(); } // Stop & send the close game event to the Server public synchronized void stopHost() { if(null != myServer) { //myServer.getGame().removeGameListener(this); myServer.die(); myServer = null; } currentScenario = null; } @Override public void gameBoardChanged(GameBoardChangeEvent e) { // TODO Auto-generated method stub } @Override public void gameBoardNew(GameBoardNewEvent e) { // TODO Auto-generated method stub } @Override public void gameEnd(GameEndEvent e) { } @Override public void gameEntityChange(GameEntityChangeEvent e) { // TODO Auto-generated method stub } @Override public void gameEntityNew(GameEntityNewEvent e) { // TODO Auto-generated method stub } @Override public void gameEntityNewOffboard(GameEntityNewOffboardEvent e) { // TODO Auto-generated method stub } @Override public void gameEntityRemove(GameEntityRemoveEvent e) { // TODO Auto-generated method stub } @Override public void gameMapQuery(GameMapQueryEvent e) { // TODO Auto-generated method stub } @Override public void gameNewAction(GameNewActionEvent e) { // TODO Auto-generated method stub } @Override public void gamePhaseChange(GamePhaseChangeEvent e) { } @Override public void gameVictory(GameVictoryEvent gve) { // Prevent double run if (gameThread.stopRequested()) { return; } try { boolean control = JOptionPane.showConfirmDialog(campaigngui.getFrame(), "Did your side control the battlefield at the end of the scenario?", "Control of Battlefield?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; ResolveScenarioTracker tracker = new ResolveScenarioTracker(currentScenario, campaign, control); tracker.setClient(gameThread.getClient()); tracker.setEvent(gve); tracker.processGame(); ResolveScenarioWizardDialog resolveDialog = new ResolveScenarioWizardDialog(campaigngui.getFrame(), true, tracker); resolveDialog.setVisible(true); if (campaigngui.getCampaign().getCampaignOptions().getUseAtB() && campaign.getMission(currentScenario.getMissionId()) instanceof AtBContract && campaigngui.getCampaign().getRetirementDefectionTracker().getRetirees().size() > 0) { RetirementDefectionDialog rdd = new RetirementDefectionDialog(campaigngui, (AtBContract)campaign.getMission(currentScenario.getMissionId()), false); rdd.setVisible(true); if (!rdd.wasAborted()) { getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments()); } } gameThread.requestStop(); /*Megamek dumps these in the deployment phase to free memory*/ if (getCampaign().getCampaignOptions().getUseAtB()) { megamek.client.RandomUnitGenerator.getInstance(); megamek.client.RandomNameGenerator.getInstance(); } MekHQ.triggerEvent(new ScenarioResolvedEvent(currentScenario)); campaigngui.initReport(); }// end try catch (Exception ex) { logError(ex); } } @Override public void gamePlayerChange(GamePlayerChangeEvent e) { // TODO Auto-generated method stub } @Override public void gamePlayerChat(GamePlayerChatEvent e) { // TODO Auto-generated method stub } @Override public void gamePlayerConnected(GamePlayerConnectedEvent e) { // TODO Auto-generated method stub } @Override public void gamePlayerDisconnected(GamePlayerDisconnectedEvent e) { // TODO Auto-generated method stub } @Override public void gameReport(GameReportEvent e) { // TODO Auto-generated method stub } @Override public void gameSettingsChange(GameSettingsChangeEvent e) { // TODO Auto-generated method stub } @Override public void gameTurnChange(GameTurnChangeEvent e) { // TODO Auto-generated method stub } public void gameClientFeedbackRquest(GameCFREvent e) { // TODO Auto-generated method stub } public IconPackage getIconPackage() { return iconPackage; } /* * Access methods for event bus. */ static public void registerHandler(Object handler) { EVENT_BUS.register(handler); } static public boolean triggerEvent(MMEvent event) { return EVENT_BUS.trigger(event); } static public void unregisterHandler(Object handler) { EVENT_BUS.unregister(handler); } // TODO: This needs to be way more flexible, but it will do for now. private void initEventHandlers() { EVENT_BUS.register(new XPHandler()); } }