// // MUStatusComponent.java // Thud // // Copyright (c) 2001-2007 Anthony Parker & the THUD team. // All rights reserved. See LICENSE.TXT for more information. // package net.sourceforge.btthud.ui.status; import net.sourceforge.btthud.data.MUData; import net.sourceforge.btthud.data.MUPrefs; import net.sourceforge.btthud.data.MUConstants; import net.sourceforge.btthud.data.MUColors; import net.sourceforge.btthud.data.MUWeapon; import net.sourceforge.btthud.data.MUMyInfo; import net.sourceforge.btthud.data.MUUnitInfo; import net.sourceforge.btthud.data.MUUnitWeapon; import net.sourceforge.btthud.data.MUUnitAmmo; import net.sourceforge.btthud.util.JTextPaneWriter; import net.sourceforge.btthud.util.BulkStyledDocument; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.text.*; import javax.swing.text.DefaultStyledDocument.ElementSpec; import java.util.*; import java.text.*; /** * Implements a status report window that displays heading, speed, heat, and * weapon information very similar to the MUX's 'status'. * @author tkrajcar */ public class MUStatusComponent extends JScrollPane { private MUPrefs prefs; private final JTextPane statusPane; private final JTextPaneWriter statusPaneWriter; private Font mFont; private SimpleAttributeSet conRegular, conIrregular; private final ArrayList<ElementSpec> elements = new ArrayList<ElementSpec> (); public MUStatusComponent (final MUPrefs prefs) { super (VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER); // Setup our new status pane statusPane = new JTextPane (); statusPane.setBackground(Color.black); statusPane.setEditable(false); statusPaneWriter = new JTextPaneWriter (statusPane); newPreferences(prefs); statusPane.setStyledDocument(new BulkStyledDocument (100, mFont)); setViewportView(statusPane); } private void initAttributeSets () { conRegular = new SimpleAttributeSet (); StyleConstants.setFontFamily(conRegular, prefs.mainFont); StyleConstants.setFontSize(conRegular, prefs.statusFontSize); StyleConstants.setForeground(conRegular, Color.white); conIrregular = new SimpleAttributeSet (); StyleConstants.setFontFamily(conIrregular, prefs.mainFont); StyleConstants.setFontSize(conIrregular, prefs.statusFontSize); StyleConstants.setForeground(conIrregular, Color.white); StyleConstants.setBold(conIrregular, true); } public void newPreferences (final MUPrefs prefs) { this.prefs = prefs; mFont = new Font(prefs.mainFont, Font.PLAIN, prefs.statusFontSize); statusPane.setFont(mFont); initAttributeSets(); } public void refresh (final MUData data) { if (!data.hudRunning) return; final MUMyInfo mydata = data.myUnit; elements.clear(); String s; // Move/heat block s = mydata.leftJust(mydata.name, 14, true) + "[" + mydata.id + "]" + " XYZ:" + mydata.rightJust(String.valueOf(mydata.getX()), 4, false) + "," + mydata.rightJust(String.valueOf(mydata.getY()), 4, false) + "," + mydata.rightJust(String.valueOf(mydata.getZ()), 4, false) + " " + "Heat Prod: " + mydata.rightJust(String.valueOf(mydata.heat), 3, false) + " deg C."; addString(s, conRegular); addBlankLine(); final NumberFormat speedFormatter = new DecimalFormat ("##0.0"); s = "Speed: " + mydata.rightJust(speedFormatter.format(mydata.speed), 6, false) + " KPH Heading:" + mydata.rightJust(String.valueOf(mydata.heading), 6, false) + " deg Heat Sinks: " + mydata.rightJust(String.valueOf(mydata.heatSinks), 3, false); addString(s, conRegular); addBlankLine(); s = "Des.Spd:" + mydata.rightJust(speedFormatter.format(mydata.desiredSpeed), 6, false) + " KPH Des.Hdg:" + mydata.rightJust(String.valueOf(mydata.desiredHeading), 6, false) + " deg Heat Dissp: " + mydata.rightJust(String.valueOf(mydata.heatDissipation), 3, false) + " deg C."; addString(s, conRegular); addBlankLine(); final StringBuilder sb = new StringBuilder (); if (mydata.maxVerticalSpeed != 0) { sb.append("Vrt Spd:" + mydata.rightJust(speedFormatter.format(mydata.verticalSpeed), 6, false) + " KPH " + "Des.VSp:" + mydata.rightJust(speedFormatter.format(mydata.desiredVerticalSpeed), 6, false) + " KPH "); } if (mydata.maxFuel != 0) { sb.append("Fuel: " + mydata.rightJust(String.valueOf(mydata.fuel), 4, false) + "/" + mydata.rightJust(String.valueOf(mydata.maxFuel), 4, false) + " (" + speedFormatter.format(mydata.percentFuelLeft()) + "%)"); } if (sb.length() > 0) { addString(sb.toString(), conRegular); sb.setLength(0); } addBlankLine(); if (mydata.canHaveTurret()) { s = "Turret Hdg: " + mydata.rightJust(String.valueOf((mydata.turretHeading + mydata.heading + 180) % 360), 5, false) + " deg"; addString(s, conRegular); addBlankLine(); } // Add heat scale. // TODO: This would work better if HEAT_LEVEL_NONE were -17. int minHeat = mydata.heatDissipation / 10; int barHeat = mydata.heat / 10 - minHeat; if (minHeat > MUConstants.HEAT_LEVEL_NONE) { addString("Temp:<", conRegular); addHeatBar(MUColors.hx, MUConstants.HEAT_LEVEL_LGREEN - MUConstants.HEAT_LEVEL_NONE, MUConstants.HEAT_LEVEL_LGREEN, barHeat); // Black portion } else { addString("Temp: ", conRegular); addHeatBar(MUColors.hx, MUConstants.HEAT_LEVEL_LGREEN - minHeat, MUConstants.HEAT_LEVEL_LGREEN, barHeat); // Black portion } StyleConstants.setForeground(conIrregular, MUColors.hg); // Divider addString("|", conIrregular); addHeatBar(MUColors.g, MUConstants.HEAT_LEVEL_LGREEN, MUConstants.HEAT_LEVEL_BGREEN, barHeat); // Green portion addHeatBar(MUColors.hg, MUConstants.HEAT_LEVEL_BGREEN, MUConstants.HEAT_LEVEL_LYELLOW, barHeat); // Bright green portion StyleConstants.setForeground(conIrregular, MUColors.hy); // Divider addString("|", conIrregular); addHeatBar(MUColors.y, MUConstants.HEAT_LEVEL_LYELLOW, MUConstants.HEAT_LEVEL_BYELLOW, barHeat); // Yellow portion addHeatBar(MUColors.hy, MUConstants.HEAT_LEVEL_BYELLOW, MUConstants.HEAT_LEVEL_LRED, barHeat); // Bright yellow portion StyleConstants.setForeground(conIrregular, MUColors.hr); // Divider addString("|", conIrregular); addHeatBar(MUColors.r, MUConstants.HEAT_LEVEL_LRED, MUConstants.HEAT_LEVEL_BRED, barHeat); // Red portion addHeatBar(MUColors.hr, MUConstants.HEAT_LEVEL_BRED, MUConstants.HEAT_LEVEL_TOP, barHeat); // Bright red portion StyleConstants.setForeground(conIrregular, MUColors.h); // Divider addString("|", conIrregular); addBlankLine(); // Status flags. if (mydata.status.length() > 0 && !mydata.status.equals("-")) { for (final char sc: mydata.status.toCharArray()) { // loop through mydata.status addString(getFlagName(sc)); addString(" ", conRegular); } } else { // Using addBlankLine() here won't work because it // won't render two starttag-endtag pairs in a row - it // eats one addString("\n",conRegular); } addBlankLine(); // Weapon list. addString("------- Weapon ------- [##] Loc - Status || --- Ammo Type --- Rds", conRegular); addBlankLine(); final MUUnitWeapon[] weapons = mydata.unitWeapons; final MUUnitAmmo[] ammo = mydata.unitAmmo; // TODO: Find out if we're doing something special with // weapons.length to ensure that if we have more ammo than // weapons, that we handle that case. int weaponLines = (ammo.length > weapons.length) ? ammo.length : weapons.length; for (int ii = 0; ii < weaponLines; ii++) { // FIXME: Apparently, we just allocate a bunch of slots // and hope it's enough. if (weapons[ii] == null && ammo[ii] == null) break; // Weapons column. if (ii < weapons.length && weapons[ii] != null) { final MUUnitWeapon weapon = weapons[ii]; final MUWeapon weapontype = MUUnitInfo.getWeapon(weapon.typeNumber); String weapname = weapontype.name; weapname = weapname.replaceAll("IS\\.",""); weapname = weapname.replaceAll("Clan\\.",""); weapname = weapname.replaceAll("CL\\.",""); sb.setLength(0); sb.append(" " + mydata.leftJust(weapname, 19, true)); if (weapon.fireMode.equals("-")) { sb.append(' '); } else { sb.append(weapon.fireMode); } if (weapon.ammoType.equals("-")) { sb.append(" "); } else { sb.append(weapon.ammoType); } sb.append(" [" + mydata.rightJust(String.valueOf(weapon.number),2,false) + "] " + mydata.rightJust(weapon.loc, 3, false) + " "); addString(sb.toString(), conRegular); // Weapon status. addString(getWeaponStatusString(weapon.status)); } else { // Padding for # ammo > # weapons. addString(" ", conRegular); } addString(" || ", conRegular); // Ammo column. if (ii < ammo.length && ammo[ii] != null) { final MUUnitAmmo thisAmmo = ammo[ii]; final MUWeapon thisWeapon = MUUnitInfo.getWeapon(thisAmmo.weaponTypeNumber); String weapname = thisWeapon.name; weapname = weapname.replaceAll("IS\\.",""); weapname = weapname.replaceAll("Clan\\.",""); weapname = weapname.replaceAll("CL\\.",""); String mode = thisAmmo.ammoMode; if (mode.equals("-")) mode = " "; s = " " + mydata.leftJust(weapname, 15, false) + mode + " "; addString(s, conRegular); s = mydata.rightJust(String.valueOf(thisAmmo.roundsRemaining), 3, false); StyleConstants.setForeground(conIrregular, MUUnitInfo.colorForPercent(mydata.percentAmmoLeft(thisAmmo))); addString(s, conIrregular); } // End of column. addBlankLine(); } final BulkStyledDocument doc = (BulkStyledDocument)statusPane.getDocument(); statusPaneWriter.reset(); doc.insertParsedString(elements.toArray(new ElementSpec[0])); } /** * Adds a blank line to ArrayList elements and sends it back. Used to * eliminate code duplication * * @param elements ArrayList to append blank line elements to */ private void addBlankLine () { elements.add(new ElementSpec (conRegular, ElementSpec.EndTagType)); elements.add(new ElementSpec (conRegular, ElementSpec.StartTagType)); } /** * Adds a given line to ArrayList elements and sends it back. Used to * eliminate code duplication * * @param elements ArrayList to append blank line elements to * @param s String of text to append * @param attrs Attributes to use when adding string */ private void addString (String s, MutableAttributeSet attrs) { elements.add(new ElementSpec (new SimpleAttributeSet (attrs), ElementSpec.ContentType, s.toCharArray(), 0, s.length())); } // Draw heat bars for heat values from [min,max). // TODO: This API could use some improvement. For example, the color // and bar min/max are not independent parameters. private void addHeatBar (final Color color, final int min, final int max, final int heat) { final StringBuilder sb = new StringBuilder (); // FIXME: Using for loops to append() is stupid when we can // just append 'c'xN characters, isn't it? for (int ii = min; ii < max; ii++) { sb.append((ii < heat) ? ':' : '.'); } StyleConstants.setForeground(conIrregular, color); addString(sb.toString(), conIrregular); } // Expand status flag characters to (styled) names. static private class ColorString { private final Color color; private final String string; private ColorString (final Color color, final String string) { this.color = color; this.string = string; } } private void addString (ColorString cs) { if (cs.color != null) { StyleConstants.setForeground(conIrregular, cs.color); addString(cs.string, conIrregular); } else { addString(cs.string, conRegular); } } static private ColorString getFlagName (final char fc) { Color color = null; String name = null; switch (fc) { case 'B': color = MUColors.hr; name = "BURNING"; break; case 'C': name = "CARRYING CLUB"; break; case 'D': name = "DUG IN"; break; case 'e': color = MUColors.hy; name = "AFFECTED BY ECM"; break; case 'E': color = MUColors.hy; name = "EMITTING ECM"; break; case 'f': color = MUColors.hy; name = "STANDING UP"; break; case 'F': color = MUColors.hr; name = "FALLEN"; break; case 'h': color = MUColors.hy; name = "GOING HULL DOWN"; break; case 'H': color = MUColors.hy; name = "HULL DOWN"; break; case 'I': color = MUColors.hr; name = "ON FIRE"; break; case 'J': color = MUColors.hy; name = "JUMPING"; break; case 'l': color = MUColors.hy; name = "ILLUMINATED"; break; case 'L': color = MUColors.hy; name = "ILLUMINATING"; break; case 'M': color = MUColors.hy; name = "SPRINTING"; break; case 'm': color = MUColors.hy; name = "EVADING"; break; case 'n': color = MUColors.hy; name = "ENEMY NARC ATTACHED"; break; case 'N': color = MUColors.hy; name = "FRIENDLY NARC ATTACHED"; break; case '+': color = MUColors.hy; name = "OVERHEATING"; break; case 'O': color = MUColors.hy; name = "ORBITAL DROPPING"; break; case 'p': color = MUColors.hy; name = "PROTECTED BY ECM"; break; case 'P': color = MUColors.hy; name = "PROTECTED BY ECCM"; break; case 's': color = MUColors.hy; name = "STARTING UP"; break; case 'S': color = MUColors.hr; name = "SHUTDOWN"; break; case 'T': color = MUColors.hy; name = "BEING TOWED"; break; case 't': color = MUColors.hy; name = "TOWING"; break; case 'W': color = MUColors.hy; name = "SWARMING"; break; case 'X': color = MUColors.hy; name = "SPINNING"; break; default: name = "???"; break; } return new ColorString (color, name); } static private ColorString getWeaponStatusString (final String ws) { Color color = null; String string = null; // TODO: A HashMap would be more efficient. if (ws.equals("R")) { color = MUColors.g; string = " Ready"; } else if (ws.equals("*")) { color = MUColors.hx; string = " *****"; } else if (ws.equals("A") || ws.equals("a") || ws.equals("J")) { color = MUColors.r; string = "JAMMED"; } else if (ws.equals("D")) { color = MUColors.hx; string = "DISBLD"; } else if (ws.equals("S")) { color = MUColors.r; string = "SHORTD"; } else { // FIXME: rightJust() shouldn't be in MUUnitInfo. string = MUUnitInfo.rightJust(ws, 6, false); } return new ColorString (color, string); } }