// // MUParse.java // Thud // // Created by asp on Mon Nov 19 2001. // Copyright (c) 2001-2006 Anthony Parker & the THUD team. // All rights reserved. See LICENSE.TXT for more information. // package net.sourceforge.btthud.engine; import net.sourceforge.btthud.data.*; import javax.swing.*; import java.util.*; import net.sourceforge.btthud.util.*; /* Note: We use a lot of == comparison in this class, because it's faster than .equals(). When coding here, we have to make sure to intern() the temporary strings so they compare properly. */ public class MUParse { private final JTextPaneWriter textPaneWriter; private final BulkStyledDocument doc; private final ANSIEmulation emulation = new ANSIEmulation (); MUData data = null; MUPrefs prefs = null; MUCommands commands = null; String sessionKey; String hudInfoStart = new String("#HUD:"); // --------------------- /** * Constructor. */ public MUParse (final JTextPaneWriter textPaneWriter, final MUData data, final MUPrefs prefs) { this.textPaneWriter = textPaneWriter; doc = (BulkStyledDocument)textPaneWriter.getDocument(); this.data = data; this.prefs = prefs; } public String getSessionKey() { return sessionKey; } public void setSessionKey(String newKey) { sessionKey = newKey; hudInfoStart = new String("#HUD:" + sessionKey + ":").intern(); } public void setCommands(MUCommands commands) { this.commands = commands; } // ------------------------------------------------------- /** * Check to see if a line needs to be matched, then insert it into the * document. * * @param l The line we are parsing */ void parseLine (final String l) { if (l == null) return; //System.out.println ("parseLine :" + l + ":"); try { // Technically, we should probably parse everything for // ANSI codes first, but we're pretty sure hudinfo // lines won't contain any. if (matchHudInfoCommand(l)) return; // Perform ANSI terminal emulation. We need to do this // even when we mute the main window text, although in // that case, we just discard the text. final List<ANSIEmulation.StyledString> styledLine = emulation.parseLine(l, data.mainWindowMuted); // TODO: This should probably take into account the // ANSI-parsed text, but we'll probably just foist it // on to the scripting engine anyway. Anyway, it // doesn't currently match any colorized lines. matchForCommandSending(l); // Output to main window. if (!data.mainWindowMuted) { for (final ANSIEmulation.StyledString element: styledLine) { printStyledString(element); } textPaneWriter.println(); } } catch (final Exception e) { System.out.println("Error: parseLine: " + e); } } private void printStyledString (final ANSIEmulation.StyledString str) { final String style = str.style; final String text = str.text; if (!textPaneWriter.hasStyle(style)) { textPaneWriter.addStyle(style, ANSIEmulation.getStyle(style)); } textPaneWriter.print(text, style); } /** * Just like parseLine, but designed for messages from the HUD. These * don't need to be matched, so we can save ourselves some CPU time. * * @param l The line that we are parsing */ public void messageLine (String l) { textPaneWriter.println(); textPaneWriter.println(l, BulkStyledDocument.STYLE_HUD_MESSAGE); } // Triggers. public boolean matchForCommandSending(String l) { if (l.startsWith("Pos changed to ") && data.hudRunning) commands.forceTactical(); return false; } /* Warning: This code relies on major version 0 of the hudinfo spec, and a minimum of minor version 6 */ public boolean matchHudInfoCommand(String l) { if (l.startsWith(hudInfoStart)) { synchronized (data) { // We got some data! data.lastDataTime = System.currentTimeMillis(); // Must be a result for us to parse StringTokenizer st = new StringTokenizer(l); // Get the first word, ie #HUD:key:GS:R# String firstWord = st.nextToken(); // And get the part which specifies which command we're looking at StringTokenizer st2 = new StringTokenizer(firstWord, ":"); // Skip the #HUD and key st2.nextToken(); st2.nextToken(); String whichCommand = st2.nextToken().intern(); // Get the rest of our string, for passing to other functions StringBuffer restOfCommandBuf = new StringBuffer(st.nextToken()); while (st.hasMoreTokens()) { restOfCommandBuf.append(" "); restOfCommandBuf.append(st.nextToken()); } String restOfCommand = restOfCommandBuf.toString().intern(); if (data.hudStarted && data.hudRunning && (whichCommand == "???" || restOfCommand == "Not in a BattleTech unit")) { data.hudRunning = false; messageLine("*** Display suspended: " + restOfCommand + " ****"); } if(data.hudStarted && data.hudRunning && (restOfCommand == "You are destroyed!")) { data.hudRunning = false; data.hudStarted = false; commands.endTimers(); messageLine("*** Display Stopped: Unit Destroyed ***"); } // Now we check it against everything if (whichCommand == "GS") // general status parseHudInfoGS(restOfCommand); else if (whichCommand == "C") // contacts parseHudInfoC(restOfCommand); else if (whichCommand == "CB") // building contacts parseHudInfoCB(restOfCommand); else if (whichCommand == "T") // tactical { // Now we're expecting an 'S', an 'L', or a 'D' String subCommand = st2.nextToken().intern(); if (subCommand == "S#") parseHudInfoTS(restOfCommand); else if (subCommand == "L#") parseHudInfoTL(restOfCommand); else if (subCommand == "D#") parseHudInfoTD(restOfCommand); } else if (whichCommand == "SGI") // static general information parseHudInfoSGI(restOfCommand); else if (whichCommand == "CO") parseHudInfoCO(restOfCommand); else if (whichCommand == "AS") // Armor status parseHudInfoAS(restOfCommand); else if (whichCommand == "OAS") // Original armor status parseHudInfoOAS(restOfCommand); else if (whichCommand == "KEY") // 'Key set' .. don't need to do anything further return true; else if (whichCommand == "WL") // Weapon list parseHudInfoWL(restOfCommand); else if (whichCommand == "WE") // Our own weapons parseHudInfoWE(restOfCommand); else if (whichCommand == "AM") // Our own ammo parseHudInfoAM(restOfCommand); else messageLine("> Unrecognized HUDINFO data: " + whichCommand); return true; } } else if (l.startsWith("#HUD hudinfo")) { parseHudInfoVersion(l); return true; } else if (l.startsWith("#HUD:")) { // This could be reached if the key didn't match or something.. we'll just keep it from showing up return true; } return false; } /** * Parse a HUDINFO version number. * @param l The entire string line. */ public void parseHudInfoVersion(String l) { // #HUD hudinfo version 1.0 [options: <option flags>] StringTokenizer st = new StringTokenizer(l); // Skip #HUD hudinfo version st.nextToken(); st.nextToken(); st.nextToken(); StringTokenizer st2 = new StringTokenizer(st.nextToken(), "."); data.setHudInfoMajorVersion(Integer.parseInt(st2.nextToken())); data.setHudInfoMinorVersion(Integer.parseInt(st2.nextToken())); } /** * Parse a string that represents 'general status.' * @param l The data from the hudinfo command, minus the header with the key. */ public void parseHudInfoGS(String l) { try { StringTokenizer st = new StringTokenizer(l, ","); //System.out.println ("GS PARSE COUNT TOKENS 001 :" + new Integer (st.countTokens ()) + ":"); MUMyInfo info = data.myUnit; String tempStr; if (info == null) info = new MUMyInfo(); // See hudinfospec.txt for exact formatting information info.id = st.nextToken(); //info.id = "**"; // we don't want our own ID for now final int hex_x = Integer.parseInt(st.nextToken()); final int hex_y = Integer.parseInt(st.nextToken()); final int hex_z = Integer.parseInt(st.nextToken()); info.heading = Integer.parseInt(st.nextToken()); info.desiredHeading = Integer.parseInt(st.nextToken()); info.speed = Float.parseFloat(st.nextToken()); info.desiredSpeed = Float.parseFloat(st.nextToken()); info.heat = Integer.parseInt(st.nextToken()); info.heatDissipation = Integer.parseInt(st.nextToken()); tempStr = st.nextToken().intern(); if (tempStr != "-") info.fuel = Integer.parseInt(tempStr); info.verticalSpeed = Float.parseFloat(st.nextToken()); info.desiredVerticalSpeed = Float.parseFloat(st.nextToken()); final float rtc = Float.parseFloat(st.nextToken()); final int ibtc = Integer.parseInt(st.nextToken()); info.position.setFromCenterLocation(hex_x, hex_y, hex_z, rtc, ibtc); tempStr = st.nextToken().intern(); if (tempStr != "-") info.turretHeading = Integer.parseInt(tempStr); // also corresponds to rottorso if (st.hasMoreTokens()) info.status = st.nextToken(); else info.status = ""; if (data.hiSupportsOwnJumpInfo()) { // We have our jump target code now tempStr = st.nextToken().intern(); if (tempStr != "-") { info.jumping = true; info.jumpTargetX = Integer.parseInt(tempStr); info.jumpTargetY = Integer.parseInt(st.nextToken()); } } //System.out.println ("GS PARSE COUNT TOKENS 002 :" + new Integer (st.countTokens ()) + ":"); String sTk = ""; while (st.hasMoreTokens ()) { sTk = st.nextToken (); } //System.out.println ("GS PARSE LAST TOKEN :" + sTk + ":"); if (sTk.length () > 0) { // Check to see if we can see this unit in the contacts list int i, iSize; MUUnitInfo uiUnit; iSize = 0; if (data.contacts.size () > 0) iSize = data.contacts.size (); for (i = 0; i < iSize; ++i) { uiUnit = data.contacts.get(i); if (uiUnit.id.equals(sTk)) { uiUnit.target = true; data.myUnit.setTargettedUnit (sTk); } else uiUnit.target = false; // System.out.println ("parse uiUnit [" + new Integer(i).toString () + "] " + uiUnit.id + " Friend " + // new Boolean (uiUnit.isFriend ()).toString () + " Target " + // new Boolean (uiUnit.isTarget ()).toString ()); } } } catch (Exception e) { System.out.println("Error: parseHudInfoGS: " + e); } } /** * Parse a string which represents 'static general information' - usually 1 time only. * @param l The string, minus the header. */ public void parseHudInfoSGI(String l) { /* #HUD:<key>:SGI# TC,RF,NM,WS,RS,BS,VS,TF,HS,AT TC: unit type character RF: string, unit referece NM: string, unit name WS: speed, unit max walking/cruise speed RS: speed, unit max running/flank speed BS: speed, unit max reverse speed VS: speed, unit max vertical speed TF: fuel, or '-' for n/a HS: integer, number of templated (single) heatsinks AT: advtech, advanced technology available */ /* #HUD:bajl:SGI:R# i,ObservationVTO,ObservationVTOL,0.000,0.000,-0.000,0.000,-,10, */ StringTokenizer st = new StringTokenizer(l, ","); MUMyInfo info = data.myUnit; String tempStr; if (info == null) info = new MUMyInfo(); info.type = st.nextToken(); if (info.type.equals("I")) prefs.hudinfoTacHeight = 5; // Because MechWarriors with long tacticals slow down the MUX else prefs.hudinfoTacHeight = 40; if(st.hasMoreTokens()) { String ref = st.nextToken(); if(ref.compareToIgnoreCase(info.ref) != 0 && info.ref.length() > 1) { // New ref = new unit. messageLine("*** Unit Change Detected - Refreshing Data ***"); data.clearData(); } info.ref = ref; info.name = st.nextToken(); info.walkSpeed = Float.parseFloat(st.nextToken()); info.runSpeed = Float.parseFloat(st.nextToken()); info.backSpeed = Float.parseFloat(st.nextToken()); info.maxVerticalSpeed = Float.parseFloat(st.nextToken()); tempStr = st.nextToken().intern(); if (tempStr != "-") info.maxFuel = Integer.parseInt(tempStr); info.heatSinks = Integer.parseInt(st.nextToken()); if (st.hasMoreTokens()) info.advTech = st.nextToken(); /* Since we were able to succesfully do a sgi, let's see if we the hud was suspended. * If so, restart */ if(data.hudRunning == false) { messageLine("*** Display Resumed ***"); data.hudRunning = true; commands.forceTactical(); data.lastDataTime = 0; } } } /** * Parse a string which represents conditions/weather information * @param l The string, minus the header. */ public void parseHudInfoCO(String l) { /* #HUD:<key>:CO:R# LT,VR,GR,TP,FL LT: light type VR: range, visibility range GR: integer, gravity in 100th G's TP: heatmeasure, ambient temperature FL: map condition flags light type: One of the following: 'D': Daylight 'T': Twilight 'N': Night map condition flags: '-', or at least one of the following: 'V': Map is Vacuum 'U': Map is Underground (no jumping, VTOLs) 'D': Map is Dark (no tactical beyond sensors) */ StringTokenizer st = new StringTokenizer(l, ","); MUWeather weather = data.weather; String tempStr; if (weather == null) weather = new MUWeather(); tempStr = st.nextToken().intern(); if (tempStr == "D") weather.light = MUWeather.LIGHT_DAY; else if(tempStr == "T") weather.light = MUWeather.LIGHT_DAWN_DUSK; else if(tempStr == "N") weather.light = MUWeather.LIGHT_NIGHT; weather.visibility = Integer.parseInt(st.nextToken()); weather.gravity = Integer.parseInt(st.nextToken()); weather.ambientTemperature = Integer.parseInt(st.nextToken()); tempStr = st.nextToken(); if(tempStr.contains("V")) weather.isVacuum = true; if(tempStr.contains("U")) weather.isUnderground = true; if(tempStr.contains("D")) weather.isDark = true; } /** * Parse a string which represents a single contact. * @param l The contact information string, minus the header. */ public void parseHudInfoC(String l) { if (l == "Done") return; try { StringTokenizer st = new StringTokenizer(l, ","); MUUnitInfo con = new MUUnitInfo(prefs); String tempStr; // See hudinfospec.txt for detailed formatting information // #HUD:asdfk:C:L# DH,v,-,B,CG Ostroc,182,105,1,120.031,358,86.000,0.000,240,-,0.040,60,60,0,- con.id = st.nextToken(); con.friend = Character.isLowerCase(con.id.charAt(0)); con.target = false; // no way of knowing for now con.arc = st.nextToken(); tempStr = st.nextToken().intern(); if (tempStr == "PS") { con.primarySensor = true; con.secondarySensor = true; } else if (tempStr == "P") { con.primarySensor = true; con.secondarySensor = false; } else if (tempStr == "S") { con.primarySensor = false; con.secondarySensor = true; } else { con.primarySensor = false; con.secondarySensor = false; } // Version 0.7 and above doesn't let us return a nil argument if (data.hiSupportsAllArgumentHudinfo()) con.type = st.nextToken(); else { if (!con.primarySensor && !con.secondarySensor) { // If both primary and secondary sensor are false, then the token we were just looking at is actually the type if (!con.primarySensor && !con.secondarySensor) con.type = tempStr; else con.type = st.nextToken(); } } con.name = st.nextToken().intern(); if (con.name == "-") con.name = "Unknown"; // need to split name up into name, team final int hex_x = Integer.parseInt(st.nextToken()); final int hex_y = Integer.parseInt(st.nextToken()); final int hex_z = Integer.parseInt(st.nextToken()); con.range = Float.parseFloat(st.nextToken()); con.bearing = Integer.parseInt(st.nextToken()); con.speed = Float.parseFloat(st.nextToken()); con.verticalSpeed = Float.parseFloat(st.nextToken()); con.heading = Integer.parseInt(st.nextToken()); tempStr = st.nextToken().intern(); if (tempStr != "-") { con.jumpHeading = Integer.parseInt(tempStr); con.jumping = true; } else { con.jumpHeading = 0; con.jumping = false; } final float rtc = Float.parseFloat(st.nextToken()); final int ibtc = Integer.parseInt(st.nextToken()); con.position.setFromCenterLocation(hex_x, hex_y, hex_z, rtc, ibtc); con.weight = Integer.parseInt(st.nextToken()); con.apparentHeat = Integer.parseInt(st.nextToken()); if (data.hiSupportsAllArgumentHudinfo()) { tempStr = st.nextToken(); if (tempStr.equals("-")) con.status = ""; else con.status = new String(tempStr); } else { if (st.hasMoreTokens()) con.status = st.nextToken(); else con.status = ""; } // if (con == null) // System.out.println ("CON IS NULL"); // else // System.out.println ("CON IS NOT NULL"); // // if (data.myUnit == null) // System.out.println ("myUnit IS NULL"); // else // { // System.out.println ("myUnit IS NOT NULL"); // // String sX = data.myUnit.getTargettedUnit (); // if (sX == null) // System.out.println ("targetted IS NULL"); // else // System.out.println ("targetted IS NOT NULL"); // } con.target = false; if (con != null && data.myUnit != null) { String sX = data.myUnit.getTargettedUnit (); if (sX != null) { if (sX.equals (con.id)) con.target = true; } } // Give our new contact info to the data object data.newContact(con); } catch (Exception e) { System.out.println("Error: parseHudInfoC: " + e); } } /* f. Building Contacts command: hudinfo cb response: Zero or more: #HUD:<key>:CB:L# AC,BN,X,Y,Z,RN,BR,CF,MCF,BS Exactly once: #HUD:<KEY>:CB:D# Done AC: arc, weapon arc the building is in BN: string, name of the building, or '-' if unknown X, Y, Z: coordinates of building RN: range, range to building BR: degree, bearing to building CF: integer, current construction factor of building MCF: integer, maximum construction factor of building BS: building status string Example: > hudinfo cb < #HUD:C58x2:CB:L# *,Underground Hangar,55,66,7,25.1,180,1875,2000,X < #HUD:C58x2:CB:D# Done */ public void parseHudInfoCB(String l) { if (l == "Done") return; try { StringTokenizer st = new StringTokenizer(l, ","); MUBuildingInfo building = new MUBuildingInfo(); building.type = "i"; // installation type building.friend = true; // All buildings are friendly and inviting. :) building.arc = st.nextToken(); building.name = st.nextToken(); final int hex_x = Integer.parseInt(st.nextToken()); final int hex_y = Integer.parseInt(st.nextToken()); final int hex_z = Integer.parseInt(st.nextToken()); final float rtc = Float.parseFloat(st.nextToken()); final int ibtc = Integer.parseInt(st.nextToken()); building.position.setFromCenterLocation(hex_x, hex_y, hex_z, rtc, ibtc); building.cf = Integer.parseInt(st.nextToken()); building.maxCf = Integer.parseInt(st.nextToken()); if (st.hasMoreTokens()) building.status = st.nextToken(); // We don't have any way to uniquely id a building, so we'll just stick with the name and coords for now building.id = building.name + building.position.getHexX() + building.position.getHexY(); data.newContact(building); } catch (Exception e) { System.out.println("Error: parseHudInfoCB: " + e); } } protected int tacSX, tacSY, tacEX, tacEY; /** * Parse a string which represents tactical information. (TS = tactical start) * @param l A single line of the tactical info. */ public void parseHudInfoTS(String l) { StringTokenizer st = new StringTokenizer(l, ","); tacSX = Integer.parseInt(st.nextToken()); tacSY = Integer.parseInt(st.nextToken()); tacEX = Integer.parseInt(st.nextToken()); tacEY = Integer.parseInt(st.nextToken()); if (data.hiSupportsExtendedMapInfo()) { // We have some more info about our map data.mapId = st.nextToken(); data.mapName = st.nextToken(); data.mapVersion = st.nextToken(); // Is this a LOS-only map? if (st.hasMoreTokens()) data.mapLOSOnly = st.nextToken().equals("l"); } } /** * Parse a string which represents tactical information. (TD = tactical done) * @param l A single line of the tactical info. */ public void parseHudInfoTD(String l) { data.setTerrainChanged(true); } /** * Parse a string which represents armor status. * @param l A single line of the armor status. */ public void parseHudInfoAS(String l) { StringTokenizer st = new StringTokenizer(l, ","); MUMyInfo info = data.myUnit; if (info == null) info = new MUMyInfo(); String location = st.nextToken().intern(); String f, i, r; if (location == "Done") return; // Get the values f = st.nextToken().intern(); r = st.nextToken().intern(); i = st.nextToken().intern(); // Then stick them into the section if (f != "-") info.armor[MUUnitInfo.indexForSection(location)].f = Integer.parseInt(f); if (r != "-") info.armor[MUUnitInfo.indexForSection(location)].r = Integer.parseInt(r); if (i != "-") info.armor[MUUnitInfo.indexForSection(location)].i = Integer.parseInt(i); } /** * Parse a string which represents original armor status. * @param l A single line of the original armor status. */ public void parseHudInfoOAS(String l) { StringTokenizer st = new StringTokenizer(l, ","); MUMyInfo info = data.myUnit; if (info == null) info = new MUMyInfo(); String location = st.nextToken().intern(); String f, i, r; if (location == "Done") return; // Get the values f = st.nextToken().intern(); r = st.nextToken().intern(); i = st.nextToken().intern(); // Then stick them into the section if (f != "-") info.armor[MUUnitInfo.indexForSection(location)].of = Integer.parseInt(f); else info.armor[MUUnitInfo.indexForSection(location)].of = 0; if (r != "-") info.armor[MUUnitInfo.indexForSection(location)].or = Integer.parseInt(r); else info.armor[MUUnitInfo.indexForSection(location)].or = 0; if (i != "-") info.armor[MUUnitInfo.indexForSection(location)].oi = Integer.parseInt(i); else info.armor[MUUnitInfo.indexForSection(location)].oi = 0; } /** * Parse a string which represents tactical information. (TL = tactical line) * @param l A single line of the tactical info. */ public void parseHudInfoTL(String l) { // See hudinfospec.txt for complete format explanation // Ok it must be a data line StringTokenizer st = new StringTokenizer(l, ","); int thisY = Integer.parseInt(st.nextToken()); String tacData = st.nextToken(); // Format: TerrElevTerrElevTerrElev... for (int i = 0; i <= tacEX - tacSX; i++) { char terrTypeChar = tacData.charAt(2 * i); char terrElevChar = tacData.charAt(2 * i + 1); //tacData.substring(2*i+1, 2*i+2); int terrElev; if (Character.isDigit(terrElevChar)) terrElev = Character.digit(terrElevChar, 10); // Get the actual integer value, in base 10 else terrElev = 0; // Elev was probably a ? (ie, underground map) // Water and ice are negative elevation if ((terrTypeChar == '~' || terrTypeChar == '-') && terrElev != 0) terrElev = -terrElev; if (terrTypeChar != '?') data.setHex(tacSX + i, thisY, terrTypeChar, terrElev); else { // ? in the terrain = LOS info. String hashkey = String.valueOf(tacSX + i) + " " + String.valueOf(thisY); data.LOSinfo.put(hashkey, (Boolean)false); } } } /** * Parse a string which represents a weapon information string (a specific weapon on our own unit) * @param l A single line of the weapon info. */ public void parseHudInfoWE(String l) { if (l == "Done") return; StringTokenizer st = new StringTokenizer(l, ","); MUUnitWeapon w = new MUUnitWeapon(); w.number = Integer.parseInt(st.nextToken()); w.typeNumber = Integer.parseInt(st.nextToken()); w.quality = Integer.parseInt(st.nextToken()); w.loc = st.nextToken(); w.status = st.nextToken(); w.fireMode = st.nextToken(); w.ammoType = st.nextToken(); data.myUnit.newUnitWeapon(w); } /** * Parse a string which represents our unit's ammo status * @param l A single line of the ammo info. */ public void parseHudInfoAM(String l) { if (l == "Done") return; StringTokenizer st = new StringTokenizer(l, ","); MUUnitAmmo a = new MUUnitAmmo(); a.number = Integer.parseInt(st.nextToken()); a.weaponTypeNumber = Integer.parseInt(st.nextToken()); a.ammoMode = st.nextToken(); a.roundsRemaining = Integer.parseInt(st.nextToken()); a.roundsOriginal = Integer.parseInt(st.nextToken()); data.myUnit.newUnitAmmo(a); } /** * Parse a string which represents a weapon information string (not a particular unit's weapon) * @param l A single line of the weapon info. */ public void parseHudInfoWL(String l) { // See hudinfospec.txt for complete format explanation if (l == "Done") return; StringTokenizer st = new StringTokenizer(l, ","); MUWeapon w = new MUWeapon(); // I'm not sure if the HUD will return -1 or - for invalid (ie underwater LRMs). It looks as if -1 at the moment, but the spec says - w.typeNumber = Integer.parseInt(st.nextToken()); w.name = st.nextToken(); w.minRange = Integer.parseInt(st.nextToken()); w.shortRange = Integer.parseInt(st.nextToken()); w.medRange = Integer.parseInt(st.nextToken()); w.longRange = Integer.parseInt(st.nextToken()); w.minRangeWater = Integer.parseInt(st.nextToken()); w.shortRangeWater = Integer.parseInt(st.nextToken()); w.medRangeWater = Integer.parseInt(st.nextToken()); w.longRangeWater = Integer.parseInt(st.nextToken()); w.criticalSize = Integer.parseInt(st.nextToken()); w.weight = Integer.parseInt(st.nextToken()); w.damage = Integer.parseInt(st.nextToken()); w.recycle = Integer.parseInt(st.nextToken()); if (data.hiSupportsWLHeatInfo()) { // Get the last data items - supported in minor version 7 and above w.fireModes = st.nextToken(); w.ammoModes = st.nextToken(); w.damageType = st.nextToken(); w.heat = Integer.parseInt(st.nextToken()); } MUUnitInfo.newWeapon(w); } }