Crossfire JXClient, Trunk  R20561
JXClient.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff.
19  * Copyright (C) 2006-2018 Andreas Kirschbaum.
20  */
21 
22 package com.realtime.crossfire.jxclient.main;
23 
97 import java.io.File;
98 import java.io.FileOutputStream;
99 import java.io.IOException;
100 import java.io.OutputStreamWriter;
101 import java.io.Writer;
102 import java.lang.reflect.InvocationTargetException;
103 import java.util.MissingResourceException;
104 import java.util.ResourceBundle;
105 import javax.swing.SwingUtilities;
106 import org.jetbrains.annotations.NotNull;
107 import org.jetbrains.annotations.Nullable;
108 
118 public class JXClient {
119 
124  public static void main(@NotNull final String[] args) {
125  Thread.currentThread().setName("JXClient:Main");
126  final String buildNumber = getBuildNumber();
127  System.out.println("JXClient "+buildNumber+" - Crossfire Java Client");
128  System.out.println("(C)2005-2008 by Yann \"Lauwenmark\" Chachkoff.");
129  System.out.println("(C)2006-2018 Andreas Kirschbaum.");
130  System.out.println("This software is placed under the GPL License");
131  final Options options = new Options();
132  options.parse(args);
133  //noinspection InstantiationOfUtilityClass
134  new JXClient(options, buildNumber);
135  }
136 
141  @NotNull
142  private static String getBuildNumber() {
143  try {
144  return ResourceBundle.getBundle("build").getString("build.number");
145  } catch (final MissingResourceException ignored) {
146  return "unknown";
147  }
148  }
149 
156  private JXClient(@NotNull final Options options, @NotNull final String buildNumber) {
157  try {
158  final Writer debugProtocolOutputStreamWriter = openDebugStream(options.getDebugProtocolFilename());
159  try {
160  final Writer debugKeyboardOutputStreamWriter = openDebugStream(options.getDebugKeyboardFilename());
161  try {
162  final Writer debugMouseOutputStreamWriter = openDebugStream(options.getDebugMouseFilename());
163  try {
164  final Writer debugScreenOutputStreamWriter = openDebugStream(options.getDebugScreenFilename());
165  try {
166  final Writer debugSoundOutputStreamWriter = openDebugStream(options.getDebugSoundFilename());
167  try {
168  final Settings settings = new Settings(Filenames.getSettingsFile());
169  settings.remove("resolution"); // delete obsolete entry
170  settings.remove("width"); // delete obsolete entry
171  settings.remove("height"); // delete obsolete entry
172  settings.remove("skin"); // delete obsolete entry
173  final OptionManager optionManager = new OptionManager(settings);
174  final MetaserverModel metaserverModel = new MetaserverModel();
175  final CharacterModel characterModel = new CharacterModel();
176  final Model model = new Model();
177  final CrossfireServerConnection server = new DefaultCrossfireServerConnection(model, debugProtocolOutputStreamWriter == null ? null : new DebugWriter(debugProtocolOutputStreamWriter), "JXClient "+buildNumber);
178  server.start();
179  try {
180  final AskfaceFaceQueue askfaceFaceQueue = new AskfaceFaceQueue(server);
181  model.setAskfaceFaceQueue(askfaceFaceQueue);
183  final FacesManager facesManager = new DefaultFacesManager(model.getFaceCache(), facesQueue);
184  model.setItemsManager(facesManager);
185  final InventoryView inventoryView = new InventoryView(model.getItemSet(), new InventoryComparator());
186  final FloorView floorView = new FloorView(model.getItemSet());
187  final Metaserver metaserver = new Metaserver(Filenames.getMetaserverCacheFile(), metaserverModel);
188  new MetaserverProcessor(metaserver, model.getGuiStateManager());
189  final SoundManager soundManager = new SoundManager(model.getGuiStateManager(), debugSoundOutputStreamWriter == null ? null : new DebugWriter(debugSoundOutputStreamWriter));
190  try {
191  optionManager.addOption("sound_enabled", "Whether sound is enabled.", new SoundCheckBoxOption(soundManager));
192  } catch (final OptionException ex) {
193  throw new AssertionError(ex);
194  }
195 
196  final MouseTracker mouseTracker = new MouseTracker(options.isDebugGui(), debugMouseOutputStreamWriter);
197  new MusicWatcher(server, soundManager);
198  new SoundWatcher(server, soundManager);
199  new PoisonWatcher(model.getStats(), server);
200  new ActiveSkillWatcher(model.getStats(), server);
201  final Macros macros = new Macros(server);
202  final MapUpdaterState mapUpdaterState = new MapUpdaterState(facesManager, model.getGuiStateManager());
203  new CfMapUpdater(mapUpdaterState, server, facesManager, model.getGuiStateManager());
204  final SpellsView spellsView = new SpellsView(model.getSpellsManager(), facesManager);
205  final SpellSkillView spellSkillsView = new SpellSkillView(model.getSpellsManager(), facesManager);
206  final QuestsView questsView = new QuestsView(model.getQuestsManager(), facesManager);
207  final KnowledgeView knowledgeView = new KnowledgeView(facesManager, model.getKnowledgeManager());
208  final KnowledgeTypeView knowledgeTypesView = new KnowledgeTypeView(facesManager, model.getKnowledgeManager());
209  final CommandQueue commandQueue = new CommandQueue(server, model.getGuiStateManager());
210  final ScriptManager scriptManager = new ScriptManager(commandQueue, server, model.getStats(), floorView, model.getItemSet(), model.getSpellsManager(), mapUpdaterState, model.getSkillSet());
211  final Shortcuts shortcuts = new Shortcuts(commandQueue, model.getSpellsManager());
212  final Logger logger = new Logger(server, null, settings.getBoolean(SettingsEntries.MESSAGE_LOG_SETTINGS_ENTRY));
213 
214  final Exiter exiter = new Exiter();
215  final JXCWindow[] window = new JXCWindow[1];
216  SwingUtilities.invokeAndWait(() -> {
217  final JXCWindowRenderer windowRenderer = new JXCWindowRenderer(mouseTracker, server, debugScreenOutputStreamWriter);
218  new StatsWatcher(model.getStats(), windowRenderer, server, soundManager);
219  final TooltipManagerImpl tooltipManager = new TooltipManagerImpl();
220  final Pickup characterPickup;
221  try {
222  characterPickup = new Pickup(commandQueue, optionManager);
223  } catch (final OptionException ex) {
224  throw new AssertionError(ex);
225  }
226  final GuiManagerCommandCallback commandCallback = new GuiManagerCommandCallback(exiter, server);
227  final ScreenshotFiles screenshotFiles = new ScreenshotFiles();
228  final Commands commands = new Commands();
229  final CommandExecutor commandExecutor = new CommandExecutorImpl(commandQueue, commands);
230  final CommandHistoryFactory commandHistoryFactory = new CommandHistoryFactory();
231  final GUICommandFactory guiCommandFactory = new GUICommandFactoryImpl(commandCallback, commandExecutor, macros);
232  commands.addCommand(new BindCommand(server, commandCallback, guiCommandFactory));
233  commands.addCommand(new UnbindCommand(commandCallback, server));
234  commands.addCommand(new ScreenshotCommand(windowRenderer, server, screenshotFiles));
235  commands.addCommand(new ScriptCommand(scriptManager, server));
236  commands.addCommand(new ScriptkillCommand(scriptManager, server));
237  commands.addCommand(new ScriptkillallCommand(scriptManager, server));
238  commands.addCommand(new ScriptsCommand(scriptManager, server));
239  commands.addCommand(new ScripttellCommand(scriptManager, server));
240  commands.addCommand(new ExecCommand(commandCallback, server));
241  commands.addCommand(new SetCommand(server, optionManager));
242  commands.addCommand(new ClearCommand(windowRenderer, server));
243  commands.addCommand(new DebugMessagesCommand(server));
244  commands.addCommand(new AgainCommand(server, commandExecutor, commandHistoryFactory.getCommandHistory("command")));
245  final File keybindingsFile;
246  try {
247  keybindingsFile = Filenames.getKeybindingsFile(null, null);
248  } catch (final IOException ex) {
249  System.err.println("Cannot read keybindings file: "+ex.getMessage());
250  exiter.terminate();
251  return;
252  }
253  final KeybindingsManager keybindingsManager = new KeybindingsManager(keybindingsFile, guiCommandFactory);
254  commands.addCommand(new BindingsCommand(server, keybindingsManager));
255  final JXCConnection connection = new JXCConnection(keybindingsManager, shortcuts, settings, characterPickup, server, model.getGuiStateManager(), logger);
256  final GuiFactory guiFactory = new GuiFactory(guiCommandFactory);
257  final GuiManager guiManager = new GuiManager(model.getGuiStateManager(), tooltipManager, settings, server, windowRenderer, guiFactory, keybindingsManager, connection);
258  commandCallback.init(guiManager);
259  final KeyBindings defaultKeyBindings = new KeyBindings(null, guiCommandFactory);
260  final JXCSkinLoader jxcSkinLoader = new JXCSkinLoader(model, inventoryView, floorView, spellsView, spellSkillsView, facesManager, mapUpdaterState, defaultKeyBindings, optionManager, options.getTileSize(), keybindingsManager, questsView, commandHistoryFactory, knowledgeView, knowledgeTypesView, options.isAvoidCopyArea(), guiManager);
261  final SkinLoader skinLoader = new SkinLoader(commandCallback, metaserverModel, options.getResolution(), macros, windowRenderer, server, model.getGuiStateManager(), tooltipManager, commandQueue, jxcSkinLoader, commandExecutor, shortcuts, characterModel, model.getSmoothFaces(), guiCommandFactory);
262  new FacesTracker(model.getGuiStateManager(), facesManager);
263  new PlayerNameTracker(model.getGuiStateManager(), connection, model.getItemSet());
264  new OutputCountTracker(model.getGuiStateManager(), server, commandQueue);
265  final DefaultKeyHandler defaultKeyHandler = new DefaultKeyHandler(exiter, guiManager, server, model.getGuiStateManager());
266  final KeyHandler keyHandler = new KeyHandler(debugKeyboardOutputStreamWriter, keybindingsManager, commandQueue, windowRenderer, defaultKeyHandler);
267  window[0] = new JXCWindow(exiter, server, optionManager, model.getGuiStateManager(), windowRenderer, commandQueue, guiManager, keyHandler, characterModel, connection);
268  window[0].init(options.getResolution(), options.getSkin(), options.isFullScreen(), skinLoader);
269  keybindingsManager.loadKeybindings();
270  final String serverInfo = options.getServer();
271  if (serverInfo == null) {
273  } else {
274  model.getGuiStateManager().connect(serverInfo);
275  }
276  });
277  exiter.waitForTermination();
278  SwingUtilities.invokeAndWait(window[0]::term);
279  soundManager.shutdown();
280  } finally {
281  server.stop();
282  }
283  } finally {
284  if (debugSoundOutputStreamWriter != null) {
285  debugSoundOutputStreamWriter.close();
286  }
287  }
288  } finally {
289  if (debugScreenOutputStreamWriter != null) {
290  debugScreenOutputStreamWriter.close();
291  }
292  }
293  } finally {
294  if (debugMouseOutputStreamWriter != null) {
295  debugMouseOutputStreamWriter.close();
296  }
297  }
298  } finally {
299  if (debugKeyboardOutputStreamWriter != null) {
300  debugKeyboardOutputStreamWriter.close();
301  }
302  }
303  } finally {
304  if (debugProtocolOutputStreamWriter != null) {
305  debugProtocolOutputStreamWriter.close();
306  }
307  }
308  } catch (final InterruptedException|InvocationTargetException|IOException ex) {
309  //noinspection CallToPrintStackTrace
310  ex.printStackTrace();
311  System.exit(1);
312  throw new AssertionError(ex);
313  }
314 
315  System.exit(0);
316  }
317 
323  @Nullable
324  private static Writer openDebugStream(@Nullable final String filename) {
325  if (filename == null) {
326  return null;
327  }
328 
329  Writer writer = null;
330  try {
331  final FileOutputStream outputStream = new FileOutputStream(filename);
332  try {
333  //noinspection IOResourceOpenedButNotSafelyClosed
334  writer = new OutputStreamWriter(outputStream, "UTF-8");
335  } finally {
336  if (writer == null) {
337  outputStream.close();
338  }
339  }
340  } catch (final IOException ex) {
341  System.err.println(filename+": cannot create output file: "+ex.getMessage());
342  return null;
343  }
344  return writer;
345  }
346 
347 }
static File getScaledImageCacheDir()
Returns the image cache directory for double size images.
Definition: Filenames.java:62
Provides a view of all quests a character is doing.
Definition: QuestsView.java:34
static void main(@NotNull final String[] args)
The program entry point.
Definition: JXClient.java:124
void remove(@NotNull final String key)
Removes a key.
Definition: Settings.java:168
Utility class to return references to settings files.
Definition: Filenames.java:34
Provides a view of all items in the current player's inventory.
void init(@NotNull final GuiManager guiManager)
Creates a new instance.
static final boolean DISABLE_START_GUI
TODO: Remove when more options are implemented in the start screen gui.
Definition: JXCWindow.java:72
Parser for loading JXCSkin instances from JXCSkinSources.
Implements the map model which is shown in the map and magic map views.
Definition: CfMap.java:22
A Comparator that compares CfItem instances in inventory view order.
static String getBuildNumber()
Returns the build number as a string.
Definition: JXClient.java:142
Writer debug information to a log file.
This is the entry point for JXClient.
Definition: JXClient.java:118
void addOption(@NotNull final String optionName, @NotNull final String documentation, @NotNull final Option option)
Adds a new option.
Manages image information ("faces") needed to display the map view, items, and spell icons...
The main FaceQueue for loading faces.
Definition: FacesQueue.java:32
Helper class to synthesize an "is poisoned" stat value.
static File getSettingsFile()
Returns the main settings file.
Definition: Filenames.java:93
void changeGUI(@NotNull final GuiState guiState)
Sets a new GuiState.
Defines constants for pickup mode.
Definition: Pickup.java:33
Displays knowledge items the player knows.
Helper class for creating file names for screenshot files.
Utility class to update a CfMap model from protocol commands.
Monitors stat changes and generates appropriate sound effects.
boolean getBoolean(@NotNull final SettingsEntry< Boolean > key)
Returns the boolean associated with the specified key at a node or.
Definition: Settings.java:90
void addCommand(@NotNull final Command command)
Adds an executable Command.
Definition: Commands.java:45
void connect(@NotNull final String serverInfo)
Connects to a Crossfire server.
Manages macro expansion in command strings.
Definition: Macros.java:37
Combines all model classes that are updated.
Definition: Model.java:44
Monitors music commands and generates appropriate sound effects.
A disk based cache for image files.
Definition: FileCache.java:38
Maintains currently running script processes.
Provides a view of all spells a character knows.
Definition: SpellsView.java:35
Tracks a GuiStateManager and updates a JXCConnection&#39;s character name.
static File getKeybindingsFile(@Nullable final CharSequence hostname, @Nullable final CharSequence character)
Returns the keybindings file.
Definition: Filenames.java:122
Factory for creating Gui instances.
Definition: GuiFactory.java:34
All defined entries in the settings file.
Helper class to synthesize an "active skill" stat value.
Maintains a set of key/value pairs.
Definition: Settings.java:43
Queries Crossfire&#39;s metaserver to learn about existing servers.
Definition: Metaserver.java:39
Maintains the application&#39;s main GUI state.
Definition: GuiManager.java:60
Implements the "bindings" command, listing currently active keybindings (for both a character and the...
Handles keyboard input processing.
Definition: KeyHandler.java:44
Update a CfMap model from protocol commands.
Asynchronously queries the metaserver and updates a MetaserverModel instance.
static File getOriginalImageCacheDir()
Returns the image cache directory.
Definition: Filenames.java:47
void setItemsManager(@NotNull final FacesManager facesManager)
Definition: Model.java:89
static Writer openDebugStream(@Nullable final String filename)
Opens an debug output stream.
Definition: JXClient.java:324
Factory for creating GUICommand instances from string representation.
Factory for creating GUICommand instances from string representation.
JXClient(@NotNull final Options options, @NotNull final String buildNumber)
The constructor of the class.
Definition: JXClient.java:156
Maintains the character list for an account.
static final SettingsEntry< Boolean > MESSAGE_LOG_SETTINGS_ENTRY
Whether to record of all text messages received from the server.
void init(@Nullable final Resolution resolution, @NotNull final String skinName, final boolean fullScreen, @NotNull final SkinLoader skinLoader)
Initializes the instance: loads and displays the skin.
Definition: JXCWindow.java:481
Tracks a GuiStateManager and resets known faces whenever a new server connection has been established...
A FaceQueue requesting faces by "askface" commands sent to the Crossfire server.
Allows to exit the application.
Definition: Exiter.java:30
A KeyHandlerListener which updates the state of a GuiManager.
Logs received messages to a file.
Definition: Logger.java:42
Factory for creating CommandHistory instances by name.
Adds encoding/decoding of crossfire protocol packets to a ServerConnection.
Maintains the pending (ncom) commands sent to the server.
METASERVER
Display the server selection screen.
Definition: GuiState.java:38
Loader for JXCSkins and attaching them to the client.
Definition: SkinLoader.java:59
Tracks mouse actions and delivers mouse events to affected GUIElement.
Tracks received Crossfire messages and resets the "output-count" setting whenever a player logs in...
void setAskfaceFaceQueue(@NotNull final AskfaceFaceQueue askfaceFaceQueue)
Definition: Model.java:94
Command line argument parser.
Definition: Options.java:34
void parse(@NotNull final String[] args)
Parse command line arguments.
Definition: Options.java:129
CommandHistory getCommandHistory(@NotNull final String ident)
Returns a CommandHistory instance by name.
static File getMetaserverCacheFile()
Returns the metaserver cache file.
Definition: Filenames.java:132
Monitors sound and sound2 commands received from the server to generate sound effects.
Provides a view to all items comprising the current floor view.
Definition: FloorView.java:35
static File getMagicMapImageCacheDir()
Returns the image cache directory for magic map sized images.
Definition: Filenames.java:77
A CheckBoxOption that enables/disables sound support.
Parses and executes client-side commands.
Definition: Commands.java:33
Maintains a mapping of face numbers to face data.