1 /* $Id$ */ 2 /*************************************************************************** 3 * (C) Copyright 2003 - Marauroa * 4 *************************************************************************** 5 *************************************************************************** 6 * * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2 of the License, or * 10 * (at your option) any later version. * 11 * * 12 ***************************************************************************/ 13 package games.stendhal.client; 14 15 import static games.stendhal.common.constants.Actions.MOVE_CONTINUOUS; 16 import static java.io.File.separator; 17 18 import java.awt.Dimension; 19 import java.io.File; 20 import java.security.AccessControlException; 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.Locale; 24 import java.util.concurrent.CountDownLatch; 25 26 import javax.swing.SwingUtilities; 27 import javax.swing.UIManager; 28 import javax.swing.UnsupportedLookAndFeelException; 29 30 import org.apache.log4j.Logger; 31 32 import games.stendhal.client.actions.MoveContinuousAction; 33 import games.stendhal.client.gui.StendhalFirstScreen; 34 import games.stendhal.client.gui.j2DClient; 35 import games.stendhal.client.gui.login.LoginDialog; 36 import games.stendhal.client.gui.login.Profile; 37 import games.stendhal.client.gui.styled.StyledLookAndFeel; 38 import games.stendhal.client.gui.styled.styles.StyleFactory; 39 import games.stendhal.client.gui.wt.core.WtWindowManager; 40 import games.stendhal.client.update.ClientGameConfiguration; 41 import games.stendhal.common.Debug; 42 import games.stendhal.common.MathHelper; 43 import games.stendhal.common.Version; 44 import marauroa.common.Log4J; 45 import marauroa.common.MarauroaUncaughtExceptionHandler; 46 47 /** 48 * Main game class. 49 */ 50 public final class stendhal { 51 52 private static final String LOG_FOLDER = "log/"; 53 private static final Logger logger = Logger.getLogger(stendhal.class); 54 55 // Use getGameFolder() where you need the real game data location 56 private static final String STENDHAL_FOLDER; 57 public static final String GAME_NAME; 58 /** 59 * Directory for storing the persistent game data. 60 */ 61 private static String gameFolder; 62 /** 63 * Just a try to get Webstart working without additional rights. 64 */ 65 static boolean WEB_START_SANDBOX = false; 66 67 // detect web start sandbox and init STENDHAL_FOLDER otherwise 68 static { 69 try { 70 System.getProperty("user.home"); 71 } catch (final AccessControlException e) { 72 WEB_START_SANDBOX = true; 73 } 74 75 /** We set the main game folder to the game name */ 76 GAME_NAME = ClientGameConfiguration.get("GAME_NAME"); 77 STENDHAL_FOLDER = separator + GAME_NAME.toLowerCase(Locale.ENGLISH) + separator; initGameFolder()78 initGameFolder(); 79 } 80 81 public static final String VERSION = Version.getVersion(); 82 83 /** Display sizes optimized for different screen resolutions */ 84 private static final List<Dimension> displaySizes = new ArrayList<Dimension>(3); 85 public static final Integer SIZE_INDEX = 0; 86 public static final Integer SIZE_INDEX_LARGE = 1; 87 public static final Integer SIZE_INDEX_WIDE = 2; 88 public static final Integer DISPLAY_SIZE_INDEX = SIZE_INDEX; 89 90 public static final boolean SHOW_COLLISION_DETECTION = false; 91 92 public static final boolean SHOW_EVERYONE_ATTACK_INFO = false; 93 94 public static final boolean FILTER_ATTACK_MESSAGES = true; 95 96 static final int FPS_LIMIT = 25; 97 /** For keeping the login status. Blocks until logged in. */ 98 private static final CountDownLatch latch = new CountDownLatch(1); 99 100 /** 101 * Make the class non-instantiable. 102 */ stendhal()103 private stendhal() { 104 } 105 106 /** 107 * Initialize the client game directory. 108 * <p> 109 * NOTE: IF YOU CHANGE THIS, CHANGE ALSO CORRESPONDING CODE IN 110 * Bootstrap.java 111 */ initGameFolder()112 private static void initGameFolder() { 113 String defaultFolder = System.getProperty("user.home") + STENDHAL_FOLDER; 114 /* 115 * Add any previously unrecognized unix like systems here. These will 116 * try to use ~/.config/stendhal if the user does not have saved data 117 * in ~/stendhal. 118 * 119 * OS X is counted in here too, but should it? 120 * 121 * List taken from: 122 * http://mindprod.com/jgloss/properties.html#OSNAME 123 */ 124 String unixLikes = "AIX|Digital Unix|FreeBSD|HP UX|Irix|Linux|Mac OS X|Solaris"; 125 String system = System.getProperty("os.name"); 126 if (system.matches(unixLikes)) { 127 // Check first if the user has important data in the default folder. 128 File f = new File(defaultFolder + "user.dat"); 129 if (!f.exists()) { 130 gameFolder = System.getProperty("user.home") + separator 131 + ".config" + separator + STENDHAL_FOLDER; 132 return; 133 } 134 } 135 // Everyone else should use the default top level directory in $HOME 136 gameFolder = defaultFolder; 137 } 138 139 /** 140 * Notify that the login has been performed and the client should proceed 141 * to show the game window. 142 */ setDoLogin()143 public static void setDoLogin() { 144 latch.countDown(); 145 } 146 147 /** 148 * Get the maximum size of the visible game area. 149 * 150 * @return screen dimensions 151 */ getDisplaySize()152 public static Dimension getDisplaySize() { 153 String spec = System.getProperty("display.index"); 154 int sizeIndex = MathHelper.parseIntDefault(spec, DISPLAY_SIZE_INDEX); 155 156 try { 157 return displaySizes.get(sizeIndex); 158 } catch (IndexOutOfBoundsException e) { 159 logger.error("Invalid client size index: " + spec + " (" + sizeIndex + ")", e); 160 } 161 162 return displaySizes.get(DISPLAY_SIZE_INDEX); 163 } 164 165 /** 166 * Initialize list of dimensions that can be used for the clients 167 * display area. 168 */ initUsableDisplaySizes()169 private static void initUsableDisplaySizes() { 170 // Optimized display dimensions for display resolutions 171 displaySizes.add(new Dimension(640, 480)); // Smaller 4:3 (1024x768 and smaller) 172 displaySizes.add(new Dimension(800, 600)); // Larger 4:3 (1280x1024) 173 displaySizes.add(new Dimension(864, 486)); // Larger 16:9 (1366x768 and larger) 174 } 175 176 /** 177 * Starts the LogSystem. 178 */ startLogSystem()179 private static void startLogSystem() { 180 prepareLoggingSystemEnviroment(); 181 182 logger.debug("XXXXXXX"); 183 184 logger.info("-Setting base at :" + STENDHAL_FOLDER); 185 186 Log4J.init("data/conf/log4j.properties"); 187 logger.debug("XXXXXXX"); 188 189 logger.info("Setting base at :" + STENDHAL_FOLDER); 190 logger.info("Stendhal " + VERSION); 191 logger.info(Debug.PRE_RELEASE_VERSION); 192 logger.info("Logging to directory: " + getLogFolder()); 193 194 String patchLevel = System.getProperty("sun.os.patch.level"); 195 if ((patchLevel == null) || (patchLevel.equals("unknown"))) { 196 patchLevel = ""; 197 } 198 199 logger.info("OS: " + System.getProperty("os.name") + " " + patchLevel 200 + " " + System.getProperty("os.version") + " " 201 + System.getProperty("os.arch")); 202 logger.info("Java-Runtime: " + System.getProperty("java.runtime.name") 203 + " " + System.getProperty("java.runtime.version") + " from " 204 + System.getProperty("java.home")); 205 logger.info("Java-VM: " + System.getProperty("java.vm.vendor") + " " 206 + System.getProperty("java.vm.name") + " " 207 + System.getProperty("java.vm.version")); 208 LogUncaughtExceptionHandler.setup(); 209 } 210 211 /** 212 * Initialize the logging system. 213 */ prepareLoggingSystemEnviroment()214 private static void prepareLoggingSystemEnviroment() { 215 // property configuration relies on this parameter 216 System.setProperty("log.directory", getLogFolder()); 217 //create the log directory if not yet existing: 218 //removed code as log4j is now capable of doing that automatically 219 } 220 221 /** 222 * @return the name of the log folder 223 */ getLogFolder()224 private static String getLogFolder() { 225 return getGameFolder() + LOG_FOLDER; 226 } 227 228 /** 229 * A loop which simply waits for the login to be completed. 230 */ waitForLogin()231 private static void waitForLogin() { 232 try { 233 latch.await(); 234 } catch (final InterruptedException e) { 235 logger.error("Unexpected interrupt", e); 236 Thread.currentThread().interrupt(); 237 } 238 } 239 240 /** 241 * Get the location of persistent game client data. 242 * 243 * @return game's home directory 244 */ getGameFolder()245 public static String getGameFolder() { 246 return gameFolder; 247 } 248 249 /** 250 * Main Entry point. 251 * 252 * @param args 253 * command line arguments 254 */ main(final String[] args)255 public static void main(final String[] args) { 256 startLogSystem(); 257 MarauroaUncaughtExceptionHandler.setup(false); 258 initUsableDisplaySizes(); 259 new Startup(args); 260 } 261 262 private static class Startup { 263 StendhalFirstScreen splash; 264 Startup(String[] args)265 Startup(String[] args) { 266 final UserContext userContext = new UserContext(); 267 final PerceptionDispatcher perceptionDispatch = new PerceptionDispatcher(); 268 final StendhalClient client = new StendhalClient(userContext, perceptionDispatch); 269 270 try { 271 WtWindowManager wm = WtWindowManager.getInstance(); 272 String style = wm.getProperty("ui.style", "Wood (default)"); 273 StyledLookAndFeel look = new StyledLookAndFeel(StyleFactory.createStyle(style)); 274 UIManager.setLookAndFeel(look); 275 /* 276 * Prevents the click event at closing a popup menu by clicking 277 * outside it being passed to the component underneath. 278 */ 279 UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE); 280 int fontSize = wm.getPropertyInt("ui.font_size", 12); 281 look.setDefaultFontSize(fontSize); 282 } catch (UnsupportedLookAndFeelException e) { 283 /* 284 * Should not happen as StyledLookAndFeel always returns true for 285 * isSupportedLookAndFeel() 286 */ 287 logger.error("Failed to set Look and Feel", e); 288 } 289 290 UIManager.getLookAndFeelDefaults().put("ClassLoader", stendhal.class.getClassLoader()); 291 292 final Profile profile = Profile.createFromCommandline(args); 293 294 SwingUtilities.invokeLater(new Runnable() { 295 @Override 296 public void run() { 297 if (profile.isValid()) { 298 new LoginDialog(null, client).connect(profile); 299 } else { 300 splash = new StendhalFirstScreen(client); 301 } 302 } 303 }); 304 305 waitForLogin(); 306 CStatusSender.send(); 307 308 /* 309 * Pass the continuous movement setting is to the server. 310 * It is done in game loop to ensure that the server version is 311 * known before sending the command, to avoid sending invalid 312 * commands. 313 */ 314 GameLoop.get().runOnce(new Runnable() { 315 @Override 316 public void run() { 317 boolean moveContinuous = WtWindowManager.getInstance().getPropertyBoolean(MOVE_CONTINUOUS, false); 318 if (moveContinuous) { 319 new MoveContinuousAction().sendAction(true, false); 320 } 321 } 322 }); 323 324 SwingUtilities.invokeLater(new Runnable() { 325 @Override 326 public void run() { 327 j2DClient locclient = new j2DClient(client, userContext, splash); 328 perceptionDispatch.register(locclient.getPerceptionListener()); 329 locclient.startGameLoop(); 330 } 331 }); 332 } 333 } 334 } 335