//
// MUUnitInfo.java
// Thud
//
// Created by asp on Tue Nov 20 2001.
// Copyright (c) 2001-2006 Anthony Parker & the THUD team.
// All rights reserved. See LICENSE.TXT for more information.
//
package net.sourceforge.btthud.data;
import java.awt.*;
import java.awt.geom.*;
import java.awt.font.*;
/**
* This class is for storing all the information about a single unit (typically a contact, enemy or friendly).
*
* @author Anthony Parker
* @version 1.0, 11.20.01
*/
public class MUUnitInfo extends Object implements Comparable {
public boolean friend = false;
public boolean target = false;
protected String sTargettedUnit = null;
public String getTargettedUnit() {
return sTargettedUnit;
}
public void setTargettedUnit(String targettedUnit) {
sTargettedUnit = targettedUnit;
}
public String type = null;
public String team = null;
public String id = null;
public String name = " ";
public String arc = null;
public String status = " ";
public float range, speed, verticalSpeed;
public final MUPoint position = new MUPoint ();
public int heading, desiredHeading, bearing, jumpHeading;
public int maxFuel;
public int turretHeading;
public int weight;
public int apparentHeat;
public boolean jumping = false;
public boolean primarySensor = false, secondarySensor = false;
// After 'cyclesLeft - isOldAt' cycles they will turn grey
// After 'cyclesLeft' cycles, they will disappear from the contacts screen
// These are default values, overriden from prefs by constructor
private int cyclesLeft = 30;
private int isOldAt = 27;
static public MUWeapon weapons[] = new MUWeapon[200]; // data that stores info on /all/ weapons ... assume 200 of them for now
Font font = new Font("Monospaced", Font.BOLD, 10); // used for drawing armor
FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true);
// ------------------
static final int NO_SECTION = 0;
static final int A = 1;
static final int AS = 2;
static final int C = 3;
static final int CT = 4;
static final int CTr = 5;
static final int E = 6;
static final int F = 7;
static final int FLLr = 8;
static final int FLS = 9;
static final int FRLr = 10;
static final int FRS = 11;
static final int FS = 12;
static final int H = 13;
static final int Hr = 14;
static final int LA = 15;
static final int LAr = 16;
static final int LL = 17;
static final int LLr = 18;
static final int LRW = 19;
static final int LS = 20;
static final int LT = 21;
static final int LTr = 22;
static final int LW = 23;
static final int N = 24;
static final int R = 25;
static final int RA = 26;
static final int RAr = 27;
static final int RL = 28;
static final int RLr = 29;
static final int RLS = 30;
static final int RRS = 31;
static final int RRW = 32;
static final int RS = 33;
static final int RT = 34;
static final int RTr = 35;
static final int RW = 36;
static final int S1 = 37;
static final int S2 = 38;
static final int S3 = 39;
static final int S4 = 40;
static final int S5 = 41;
static final int S6 = 42;
static final int S7 = 43;
static final int S8 = 44;
static final int T = 45;
static final int FLL = 46;
static final int FRL = 47;
static final int RLL = 48;
static final int RRL = 49;
static final String sectionNames[] = {
"NO", "A", "AS", "C", "CT", "CTr", "E", "F", "FLLr", "FLS", "FLRr",
"FRS", "FS", "H", "Hr", "LA", "LAr", "LL", "LLr", "LRW", "LS",
"LT", "LTr", "LW", "N", "R", "RA", "RAr", "RL", "RLr", "RLS",
"RRS", "RRW", "RS", "RT", "RTr", "RW", "S1", "S2", "S3", "S4",
"S5", "S6", "S7", "S8", "T", "FLL", "FRL", "RLL", "RRL"};
static final int TOTAL_SECTIONS = 50;
static final int TYPE_UNKNOWN = 0;
static final int BIPED = 1;
static final int HOVER = 2;
static final int TRACKED = 3;
static final int WHEELED = 4;
static final int NAVAL_SURFACE = 5;
static final int NAVAL_HYDROFOIL = 6;
static final int NAVAL_SUBMARINE = 7;
static final int VTOL = 8;
static final int AEROFIGHTER = 9;
static final int AERODYNE_DS = 10;
static final int SPHEROID_DS = 11;
static final int BATTLESUIT = 12;
static final int INFANTRY = 13;
static final int INSTALLATION = 14;
static final int TOTAL_MOVETYPES = 15;
// --------------------------
public MUUnitInfo()
{
turretHeading = 180;
}
public MUUnitInfo(MUPrefs prefs) {
cyclesLeft = prefs.contactsAge;
isOldAt = cyclesLeft - 3;
}
// For debugging purposes only
public String toString()
{
String out = "\nMUUnitInfo: ";
return (out + "ID:" + id + " Team:" + team + " Type:" + type + " Name:" + name +
" Range:" + String.valueOf(range) + " Speed:" + String.valueOf(speed) +
" Position:" + position +
" Heading:" + String.valueOf(heading) + " Bearing:" + String.valueOf(bearing) +
" Friend:" + String.valueOf(friend));
}
public boolean isTank()
{
if (type.equals("H") || type.equals("T") || type.equals("W") ||
type.equals("N") || type.equals("Y") || type.equals("U"))
return true;
else
return false;
}
public boolean isMech()
{
if (type.equals("B") || type.equals("Q"))
return true;
else
return false;
}
public boolean isAero()
{
if (type.equals("F"))
return true;
else
return false;
}
public boolean isFlying() {
if(type.equals("V") ||
type.equals("F") ||
type.equals("A") ||
type.equals("D")) {
return true;
} else {
return false;
}
}
public boolean hasHeat()
{
if (isMech() || isAero())
return true;
else
return false;
}
public boolean isExpired()
{
if (cyclesLeft <= 0)
return true;
else
return false;
}
public boolean isOld()
{
if (cyclesLeft <= isOldAt)
return true;
else
return false;
}
public boolean isFriend()
{
return friend;
}
public boolean isTarget()
{
return target;
}
public boolean isJumping()
{
return jumping;
}
public void expireMore()
{
cyclesLeft--;
}
/** Returns the cliff differential for this unit. */
public int cliffDiff() {
if(this.isMech() || this.type.equals("S")) { // Mech or battlesuit
return 2;
} else {
return 1;
}
}
/**
* Make a human-readable contact string, similar to standard .c for display
*/
public String makeContactString()
{
/* Example:
PSl[cm]B Mi Swayback x: 40 y: 38 z: 0 r: 6.3 b:169 s: 0.0 h:180 S:F
*/
StringBuffer sb = new StringBuffer();
sb.append(primarySensor ? 'P' : ' ');
sb.append(secondarySensor ? 'S' : ' ');
sb.append(arc);
sb.append('[');
sb.append(id);
sb.append(']');
sb.append(type);
sb.append(' ');
sb.append(leftJust(name, 12, true));
sb.append(' ');
sb.append('x'); sb.append(':');
sb.append(rightJust(String.valueOf(getX()), 3, false));
sb.append(' ');
sb.append('y'); sb.append(':');
sb.append(rightJust(String.valueOf(getY()), 3, false));
sb.append(' ');
sb.append('z'); sb.append(':');
sb.append(rightJust(String.valueOf(getZ()), 3, false));
sb.append(' ');
sb.append('r'); sb.append(':');
sb.append(rightJust(String.valueOf(range), 4, true));
sb.append(' ');
sb.append('b'); sb.append(':');
sb.append(rightJust(String.valueOf(bearing), 3, false));
sb.append(' ');
sb.append('s'); sb.append(':');
sb.append(rightJust(String.valueOf(speed), 5, true));
sb.append(' ');
sb.append('h'); sb.append(':');
sb.append(rightJust(String.valueOf(heading), 3, false));
sb.append(' ');
sb.append('S'); sb.append(':');
sb.append(status);
return sb.toString();
}
public String leftJust(String l, int w, boolean trunc)
{
if (l.length() < w)
{
// Need to add spaces to the end
StringBuffer sb = new StringBuffer(l);
for (int i = 0; i < w - l.length(); i++)
sb.append(' ');
return sb.toString();
}
else if (l.length() == w)
{
// Leave it the way it is
return l;
}
else
{
// Choice here: truncate the string or leave the way it is
if (trunc)
{
return l.substring(0, w);
}
else
{
return l;
}
}
}
static public String rightJust(String l, int w, boolean trunc)
{
if (l.length() < w)
{
// Need to add spaces to the beginning
StringBuffer sb = new StringBuffer(l);
for (int i = 0; i < w - l.length(); i++)
sb.insert(0, ' ');
return sb.toString();
}
else if (l.length() == w)
{
// Leave it the way it is
return l;
}
else
{
// Choice here: truncate the string or leave the way it is
if (trunc)
{
return l.substring(0, w);
}
else
{
return l;
}
}
}
/** This implements Comparable
* Right now we sort based on range, for easy contact lists.
* Destroyed units should come last.
*/
public int compareTo(Object o2)
{
if (type.equals("i"))
return 1;
if (isDestroyed())
return 1;
if (range < ((MUUnitInfo) o2).range)
return -1;
else if (range > ((MUUnitInfo) o2).range)
return 1;
else
{
// We don't want to return 0 unless they are exactly the same unit. Otherwise, it doesn't matter which is first
return id.compareTo(((MUUnitInfo) o2).id);
}
}
/**
* Determine if the unit is destroyed or not
*/
public boolean isDestroyed()
{
return stringHasCharacter(status, 'D');
}
/**
* Determine if the unit is fallen or not
*/
public boolean isFallen()
{
return (stringHasCharacter(status, 'F') || stringHasCharacter(status, 'f'));
}
/**
* Determine if a specific character is in this string
*/
public boolean stringHasCharacter(String s, char c)
{
if (s == null)
return false;
for (int i = 0; i < s.length(); i++)
{
if (s.charAt(i) == c)
return true;
}
return false;
}
/**
* Return an efficient hash code
*/
public int hashCode()
{
// Use the hash code for the id
return id.hashCode();
}
/*********************************************************************/
/**
* Returns a GeneralPath which represents this particular unit.
* @param h The height of the icon (not neccesarily the height of a hex)
* @param drawArmor If true, draw any known armor information for this unit (TODO)
*/
public GeneralPath icon(int h, boolean drawArmor)
{
// We do everything on a scale of 20 pixels. We scale up the path later if the height is > 20.
//BufferedImage unitImage = new BufferedImage(h, h, BufferedImage.TYPE_INT_ARGB);
GeneralPath unitOutline = new GeneralPath();
AffineTransform xform = new AffineTransform();
switch (type.charAt(0))
{
case 'B': // Biped
drawBiped(unitOutline,0,0);
break;
case 'Q': // Quad
unitOutline.moveTo(8, 7);
unitOutline.lineTo(8, 10);
unitOutline.lineTo(5, 10);
unitOutline.lineTo(3, 8);
unitOutline.lineTo(1, 8);
unitOutline.lineTo(2, 20);
unitOutline.lineTo(4, 20);
unitOutline.lineTo(3, 12);
unitOutline.moveTo(4, 13);
unitOutline.lineTo(6, 20);
unitOutline.lineTo(8, 20);
unitOutline.lineTo(6, 13);
unitOutline.moveTo(7, 14);
unitOutline.lineTo(13, 14);
unitOutline.moveTo(14, 13);
unitOutline.lineTo(12, 20);
unitOutline.lineTo(14, 20);
unitOutline.lineTo(16, 13);
unitOutline.moveTo(17, 12);
unitOutline.lineTo(16, 20);
unitOutline.lineTo(18, 20);
unitOutline.lineTo(19, 8);
unitOutline.lineTo(17, 8);
unitOutline.lineTo(15, 10);
unitOutline.lineTo(12, 10);
unitOutline.lineTo(12, 7);
unitOutline.lineTo(8, 7);
break;
case 'H': // Hover
case 'T': // Tracked
case 'W': // Wheeled
case 'N': // Naval Surface Displacement
case 'Y': // Naval Hydrofoil
case 'U': // Naval Submarine
unitOutline.moveTo(5, 2);
unitOutline.lineTo(3, 5);
unitOutline.lineTo(3, 15);
unitOutline.lineTo(5, 18);
unitOutline.lineTo(15, 18);
unitOutline.lineTo(17, 15);
unitOutline.lineTo(17, 5);
unitOutline.lineTo(15, 2);
unitOutline.lineTo(5, 2);
// Should check to see if there is a turret here before we go around drawing it
unitOutline.moveTo(8, 10);
unitOutline.lineTo(8, 14);
unitOutline.lineTo(12, 14);
unitOutline.lineTo(12, 10);
unitOutline.lineTo(8, 10);
// Maybe rotate this according to turret heading?
unitOutline.moveTo((float) 9.5, 10);
unitOutline.lineTo((float) 9.5, 6);
unitOutline.lineTo((float) 10.5, 6);
unitOutline.lineTo((float) 10.5, 10);
break;
case 'S': // BattleSuit
case 'I': // Infantry
drawBiped(unitOutline, 0, 0); // Top left figure
drawBiped(unitOutline,20, 0); // Top right figure
drawBiped(unitOutline, 0,21); // Bottom left figure
drawBiped(unitOutline,20,21); // Bottom right figure
// Scale it down
xform.scale(0.5, 0.5);
break;
case 'V': // VTOL
unitOutline.moveTo(8, 2);
unitOutline.lineTo(7, 3);
unitOutline.lineTo(7, 10);
unitOutline.lineTo(9, 13);
unitOutline.lineTo(9, 18);
unitOutline.lineTo(8, 18);
unitOutline.lineTo(8, 19);
unitOutline.lineTo(12, 19);
unitOutline.lineTo(12, 18);
unitOutline.lineTo(11, 18);
unitOutline.lineTo(11, 13);
unitOutline.lineTo(13, 10);
unitOutline.lineTo(13, 3);
unitOutline.lineTo(12, 2);
unitOutline.lineTo(8, 2);
unitOutline.moveTo(2, 6);
unitOutline.lineTo(2, 7);
unitOutline.lineTo(9, 7);
unitOutline.lineTo(9, 8);
unitOutline.lineTo(11, 8);
unitOutline.lineTo(11, 7);
unitOutline.lineTo(18, 7);
unitOutline.lineTo(18, 6);
unitOutline.lineTo(11, 6);
unitOutline.lineTo(11, 5);
unitOutline.lineTo(9, 5);
unitOutline.lineTo(9, 6);
unitOutline.lineTo(2, 6);
break;
case 'F': // AeroFighter
break;
case 'A': // Aerodyne DropShip
break;
case 'D': // Spheroid DropShip
unitOutline.moveTo(5, 1);
unitOutline.lineTo(1, 5);
unitOutline.lineTo(1, 15);
unitOutline.lineTo(5, 19);
unitOutline.lineTo(15, 19);
unitOutline.lineTo(19, 15);
unitOutline.lineTo(19, 5);
unitOutline.lineTo(15, 1);
unitOutline.lineTo(5, 1);
//No longer scale, as dropship hexes are actually transformed on the map.
//if (!drawArmor)
// xform.scale(h / 4.0, h / 4.0);
break;
case 'i': // Installation
unitOutline.moveTo(4, 4);
unitOutline.lineTo(2, 19);
unitOutline.lineTo(18, 19);
unitOutline.lineTo(16, 4);
unitOutline.lineTo(4, 4);
unitOutline.moveTo(5, 6);
unitOutline.lineTo(5, 8);
unitOutline.lineTo(15, 8);
unitOutline.lineTo(15, 6);
unitOutline.lineTo(5, 6);
break;
default: // just draw a box
unitOutline.moveTo(7, 7);
unitOutline.lineTo(7, 13);
unitOutline.lineTo(13, 13);
unitOutline.lineTo(13, 7);
unitOutline.lineTo(7, 7);
break;
}
// Draw the unit
// only rotate if it's a 'Mech, fallen, and not for the status display
if (type.charAt(0) == 'B' && isFallen() && !drawArmor)
xform.rotate(Math.PI / 2, 10, 10);
xform.scale((float) h / 20.0, (float) h / 20.0);
unitOutline.transform(xform);
return unitOutline;
}
/**
* Draws a biped figure, given an x offset and y offset, into a path.
*/
private void drawBiped(GeneralPath path, int xOffset, int yOffset) {
path.moveTo(xOffset+8, yOffset+ 0);
path.lineTo(xOffset+8, yOffset+ 3);
path.lineTo(xOffset+5, yOffset+ 3);
path.lineTo(xOffset+1, yOffset+ 8);
path.lineTo(xOffset+4, yOffset+10);
path.lineTo(xOffset+6, yOffset+ 7);
path.lineTo(xOffset+7, yOffset+12);
path.lineTo(xOffset+4, yOffset+20);
path.lineTo(xOffset+8, yOffset+20);
path.lineTo(xOffset+10, yOffset+13);
path.lineTo(xOffset+12, yOffset+20);
path.lineTo(xOffset+16, yOffset+20);
path.lineTo(xOffset+13, yOffset+12);
path.lineTo(xOffset+14, yOffset+ 7);
path.lineTo(xOffset+16, yOffset+10);
path.lineTo(xOffset+19, yOffset+ 8);
path.lineTo(xOffset+15, yOffset+ 3);
path.lineTo(xOffset+12, yOffset+ 3);
path.lineTo(xOffset+12, yOffset+ 0);
path.lineTo(xOffset+8, yOffset+ 0);
}
/**
* Returns true if this unit has the possibility of having a turret (note: doesn't check to see if it actually does have one)
*/
public boolean canHaveTurret()
{
int mType = movementForType(type);
if (mType == HOVER || mType == TRACKED || mType == WHEELED || mType == NAVAL_SURFACE)
return true;
else
return false;
}
/***************************************************************************/
// Static methods
/**
* Return a color for displaying an armor percentage (green is minty, etc).
*/
static public Color colorForPercent(float p)
{
if (p > 90)
return MUColors.hg; // Bright green
else if (p > 70)
return MUColors.g; // Darker green
else if (p > 45)
return MUColors.hy; // Bright yellow
else if (p > 1)
return MUColors.r; // Dark red
else
return MUColors.hx; // (visible) "Black"
}
/**
* Return a color with Transparency
*/
static public Color colorForPercent(float p, int a)
{
if (p > 90)
return MUColors.withTransparency(MUColors.hg, a); // Bright green
else if (p > 70)
return MUColors.withTransparency(MUColors.g, a); // Darker green
else if (p > 45)
return MUColors.withTransparency(MUColors.hy, a); // Bright yellow
else if (p > 1)
return MUColors.withTransparency(MUColors.r, a); // Dark red
else
return MUColors.withTransparency(MUColors.hx, a); // (visible) "black"
}
/**
* Return a constant representing our movement type
*/
static public int movementForType(String s)
{
if (s.equals("B"))
return BIPED;
if (s.equals("H"))
return HOVER;
if (s.equals("T"))
return TRACKED;
if (s.equals("W"))
return WHEELED;
if (s.equals("N"))
return NAVAL_SURFACE;
if (s.equals("Y"))
return NAVAL_HYDROFOIL;
if (s.equals("U"))
return NAVAL_SUBMARINE;
if (s.equals("F"))
return AEROFIGHTER;
if (s.equals("A"))
return AERODYNE_DS;
if (s.equals("D"))
return SPHEROID_DS;
if (s.equals("S"))
return SPHEROID_DS;
if (s.equals("I"))
return INFANTRY;
if (s.equals("i"))
return INSTALLATION;
return TYPE_UNKNOWN;
}
/**
* Return the index in an array for a specific section.
* @param s A string representation of the section we're looking for.
*/
static public int indexForSection(String sec)
{
// I could have assumed the incoming string is intern()ed but I thought it was poor style... so I'll do it here
String s = sec.intern();
if (s == "A")
return A;
if (s == "AS")
return AS;
if (s == "C")
return C;
if (s == "CT")
return CT;
if (s == "CTr")
return CTr;
if (s == "E")
return E;
if (s == "F")
return F;
if (s == "FLLr")
return FLLr;
if (s == "FLS")
return FLS;
if (s == "FRLr")
return FRLr;
if (s == "FRS")
return FRS;
if (s == "FS")
return FS;
if (s == "H")
return H;
if (s == "Hr")
return Hr;
if (s == "LA")
return LA;
if (s == "LAr")
return LAr;
if (s == "LL")
return LL;
if (s == "LLr")
return LLr;
if (s == "LRW")
return LRW;
if (s == "LS")
return LS;
if (s == "LT")
return LT;
if (s == "LTr")
return LTr;
if (s == "LW")
return LW;
if (s == "N")
return N;
if (s == "R")
return R;
if (s == "RA")
return RA;
if (s == "RAr")
return RAr;
if (s == "RL")
return RL;
if (s == "RLr")
return RLr;
if (s == "RLS")
return RLS;
if (s == "RRS")
return RRS;
if (s == "RRW")
return RRW;
if (s == "RS")
return RS;
if (s == "RT")
return RT;
if (s == "RTr")
return RTr;
if (s == "RW")
return RW;
if (s == "S1")
return S1;
if (s == "S2")
return S2;
if (s == "S3")
return S3;
if (s == "S4")
return S4;
if (s == "S5")
return S5;
if (s == "S6")
return S6;
if (s == "S7")
return S7;
if (s == "S8")
return S8;
if (s == "T")
return T;
if (s == "FLL")
return FLL;
if (s == "FRL")
return FRL;
if (s == "RLL")
return RLL;
if (s == "RRL")
return RRL;
// Default
return NO_SECTION;
}
/**
* Returns true if the weapon can be considered in the 'front' Arc (Only good for 'Mechs and Tanks at the moment).
*/
static public boolean isInFrontArc(int sNum)
{
if (sNum == H || sNum == LA || sNum == LT || sNum == CT || sNum == RT || sNum == RA ||
sNum == LL || sNum == RL || sNum == FS)
return true;
else
return false;
}
/**
* Returns true if the weapon can be considered in the 'front' Arc (Only good for Tanks at the moment)
*/
static public boolean isInTurretArc(int sNum)
{
if (sNum == T)
return true;
else
return false;
}
/**
* Returns true if the weapon can be considered in the left Arc (Only good for 'Mechs and Tanks at the moment)
*/
static public boolean isInLeftArc(int sNum)
{
if (sNum == LA || sNum == LS)
return true;
else
return false;
}
/**
* Returns true if the weapon can be considered in the right Arc (Only good for 'Mechs and Tanks at the moment).
*/
static public boolean isInRightArc(int sNum)
{
if (sNum == RA || sNum == RS)
return true;
else
return false;
}
/**
* Returns true if the weapon can be considered in the rear or aft Arc (Only good for 'Mechs and Tanks at the moment)
*/
static public boolean isInRearArc(int sNum)
{
if (sNum == Hr || sNum == LAr || sNum == LTr || sNum == CTr || sNum == RTr || sNum == RAr ||
sNum == LLr || sNum == RLr || sNum == AS)
return true;
else
return false;
}
/**
* Adds a new weapon to our list of weapons, or updates an existing one
*/
static public void newWeapon(MUWeapon w)
{
try {
weapons[w.typeNumber] = w;
}
catch (Exception e) {
System.out.println("Error: newWeapon: " + e);
}
}
/**
* Gets a weapon based on its weapon number
*/
static public MUWeapon getWeapon(int number)
{
try {
return weapons[number];
}
catch (Exception e) {
System.out.println("Error: getWeapon: " + e);
return null;
}
}
//
// Convenience methods for getting the hex positions. Because they're a
// bit of a pain to type out in full each time.
//
public int getX () {
return position.getHexX();
}
public int getY () {
return position.getHexY();
}
public int getZ () {
return position.getHexZ();
}
}