/* * MegaMekLab - Copyright (C) 2017 The MegaMek Team * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. */ package megameklab.com.ui.Infantry.Printing; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.StringJoiner; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.standard.PrintQuality; import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGException; import com.kitfox.svg.Text; import com.kitfox.svg.Tspan; import com.kitfox.svg.animation.AnimationElement; import megamek.common.AmmoType; import megamek.common.EntityMovementMode; import megamek.common.EquipmentType; import megamek.common.Infantry; import megamek.common.Mounted; import megamek.common.WeaponType; import megamek.common.weapons.ArtilleryWeapon; import megamek.common.weapons.infantry.InfantryWeapon; import megameklab.com.util.ImageHelper; /** * @author Neoancient * */ public class PrintInfantry implements Printable { /* Id tags of elements in the SVG file */ // private final static String ID_FLUFF_IMAGE = "imageFluff"; private final static String ID_PLATOON_NAME = "platoon_name"; private final static String ID_ARMOR_KIT = "armor_kit"; private final static String ID_ARMOR_DIVISOR = "armor_divisor"; private final static String ID_SOLDIER = "soldier_"; private final static String ID_NO_SOLDIER = "no_soldier_"; private final static String ID_DAMAGE = "damage_"; private final static String ID_RANGE_MOD = "range_mod_"; private final static String ID_UW_LABEL = "uw_range_modifier"; private final static String ID_UW_RANGE_MOD = "uw_range_mod_"; private final static String ID_FIELD_GUN_COLUMNS = "field_gun_columns"; private final static String ID_FIELD_GUN_QTY = "field_gun_qty"; private final static String ID_FIELD_GUN_TYPE = "field_gun_type"; private final static String ID_FIELD_GUN_DMG = "field_gun_dmg"; private final static String ID_FIELD_GUN_MIN_RANGE = "field_gun_min_range"; private final static String ID_FIELD_GUN_SHORT = "field_gun_short"; private final static String ID_FIELD_GUN_MED = "field_gun_med"; private final static String ID_FIELD_GUN_LONG = "field_gun_long"; private final static String ID_FIELD_GUN_AMMO = "field_gun_ammo"; private final static String ID_FIELD_GUN_CREW = "field_gun_crew"; private final static String ID_DEST_MODS = "dest_mods"; private final static String ID_SNEAK_CAMO_MODS = "sneak_camo_mods"; private final static String ID_SNEAK_IR_MODS = "sneak_ir_mods"; private final static String ID_BV = "bv"; private final static String ID_TRANSPORT_WT = "transport_wt"; private final static String ID_MP_1 = "mp_1"; private final static String ID_MODE_1 = "movement_mode_1"; private final static String ID_MP_2 = "mp_2"; private final static String ID_MODE_2 = "movement_mode_2"; private final static String ID_NOTES = "notes"; private final static String ID_NOTE_LINE = "note_line_"; private Infantry infantry = null; private ArrayList<Infantry> infantryList; PrinterJob masterPrintJob; private int currentPosition; public PrintInfantry(ArrayList<Infantry> list, PrinterJob masterPrintJob) { infantryList = list; this.masterPrintJob = masterPrintJob; } /* (non-Javadoc) * @see java.awt.print.Printable#print(java.awt.Graphics, java.awt.print.PageFormat, int) */ @Override public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { if (pageIndex != 0) { return Printable.NO_SUCH_PAGE; } Graphics2D g2d = (Graphics2D) graphics; printImage(g2d, pageFormat); return Printable.PAGE_EXISTS; } public void printImage(Graphics2D g2d, PageFormat pageFormat) { if (g2d == null) { return; } SVGDiagram diagram; int stop = Math.min(4, infantryList.size() - currentPosition); if (stop > 3) { diagram = ImageHelper.loadSVGImage(new File("data/images/recordsheets/Conventional_Infantry_no_tables.svg")); } else { diagram = ImageHelper.loadSVGImage(new File("data/images/recordsheets/Conventional_Infantry_tables.svg")); } try { Tspan tspan = (Tspan)diagram.getElement("text_copyright"); tspan.setText(String.format(tspan.getText(), Calendar.getInstance().get(Calendar.YEAR))); ((Text)tspan.getParent()).rebuild(); diagram.render(g2d); for (int pos = 0; pos < stop; pos++) { diagram = ImageHelper.loadSVGImage(new File("data/images/recordsheets/Conventional_Infantry_platoon_" + (pos + 1) + ".svg")); infantry = infantryList.get(pos + currentPosition); tspan = (Tspan)diagram.getElement(ID_PLATOON_NAME); if (infantry.getShortName().length() > 48) { tspan.setText(infantry.getChassis()); } else { tspan.setText(infantry.getShortName()); } ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_ARMOR_KIT); EquipmentType armor = infantry.getArmorKit(); if (armor != null) { tspan.setText(armor.getName()); ((Text)tspan.getParent()).rebuild(); } else if (infantry.hasDEST()) { tspan.setText("DEST"); ((Text)tspan.getParent()).rebuild(); } else { StringJoiner sj = new StringJoiner("/"); if (infantry.hasSneakCamo()) { sj.add("Camo"); } if (infantry.hasSneakIR()) { sj.add("IR"); } if (infantry.hasSneakECM()) { sj.add("ECM"); } if (sj.length() > 0) { tspan.setText("Sneak(" + sj.toString() + ")"); ((Text)tspan.getParent()).rebuild(); } } tspan = (Tspan)diagram.getElement(ID_ARMOR_DIVISOR); tspan.setText(String.valueOf(infantry.getDamageDivisor() + (infantry.isArmorEncumbering()? "E" : ""))); ((Text)tspan.getParent()).rebuild(); for (int j = 1; j <= 30; j++) { if (j > infantry.getShootingStrength()) { diagram.getElement(ID_SOLDIER + j) .addAttribute("display", AnimationElement.AT_XML, "none"); diagram.getElement(ID_NO_SOLDIER + j) .removeAttribute("display", AnimationElement.AT_XML); } else { tspan = (Tspan)diagram.getElement(ID_DAMAGE + j); tspan.setText(String.valueOf((int)Math.round(infantry.getDamagePerTrooper() * j))); ((Text)tspan.getParent()).rebuild(); } } diagram.updateTime(0); InfantryWeapon rangeWeapon = infantry.getPrimaryWeapon(); if (infantry.getSecondaryWeapon() != null && infantry.getSecondaryN() > 1 && !infantry.getSecondaryWeapon().hasFlag(WeaponType.F_TAG)) { rangeWeapon = infantry.getSecondaryWeapon(); } boolean scuba = infantry.getMovementMode() == EntityMovementMode.INF_UMU || infantry.getMovementMode() == EntityMovementMode.SUBMARINE; if (scuba) { diagram.getElement(ID_UW_LABEL).removeAttribute("display", AnimationElement.AT_XML); } InfantryWeapon singleSecondary = (infantry.getSecondaryN() == 1)? infantry.getSecondaryWeapon() : null; for (int j = 0; j <= 21; j++) { tspan = (Tspan)diagram.getElement(ID_RANGE_MOD + j); tspan.setText(rangeMod(j, rangeWeapon, singleSecondary, false)); if (scuba) { tspan = (Tspan)diagram.getElement(ID_UW_RANGE_MOD + j); tspan.setText(rangeMod(j, rangeWeapon, singleSecondary, true)); } ((Text)tspan.getParent()).rebuild(); } int numGuns = 0; int numShots = 0; WeaponType gun = null; for (Mounted m : infantry.getEquipment()) { if (m.getLocation() == Infantry.LOC_FIELD_GUNS) { if (m.getType() instanceof WeaponType) { gun = (WeaponType)m.getType(); numGuns++; } else if (m.getType() instanceof AmmoType) { numShots += ((AmmoType)m.getType()).getShots(); } } } if (gun == null) { diagram.getElement(ID_FIELD_GUN_COLUMNS).addAttribute("display", AnimationElement.AT_XML, "none"); } else { tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_QTY); tspan.setText(Integer.toString(numGuns)); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_TYPE); tspan.setText(gun.getName()); ((Text)tspan.getParent()).rebuild(); /* We don't use StringUnits.getEquipmentInfo() to format the damage * string because gauss explosion flags do not apply, and switchable * only applies for non-LBX. */ tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_DMG); if (gun instanceof ArtilleryWeapon) { tspan.setText(gun.getRackSize() + " [AE,S,F]"); } else { StringBuilder sb = new StringBuilder(Integer.toString(gun.getDamage())); switch (gun.getAmmoType()) { case AmmoType.T_AC_ULTRA: case AmmoType.T_AC_ULTRA_THB: sb.append("/Sht, R2 [DB,R/S/C]"); break; case AmmoType.T_AC_ROTARY: sb.append("/Sht, R6 [DB,R/S/C]"); break; case AmmoType.T_AC: case AmmoType.T_AC_PRIMITIVE: case AmmoType.T_LAC: sb.append(" [DB,C/S/F]"); break; case AmmoType.T_AC_LBX: case AmmoType.T_AC_LBX_THB: sb.append(" [DB,C/F]"); break; default: sb.append(" [DB]"); } tspan.setText(sb.toString()); } ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_MIN_RANGE); if (gun.getMinimumRange() > 0) { tspan.setText(Integer.toString(gun.getMinimumRange())); } else { tspan.setText("—"); } ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_SHORT); tspan.setText(Integer.toString(gun.getShortRange())); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_MED); tspan.setText(Integer.toString(gun.getMediumRange())); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_LONG); tspan.setText(Integer.toString(gun.getLongRange())); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_AMMO); tspan.setText(Integer.toString(numShots)); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_FIELD_GUN_CREW); tspan.setText(Integer.toString((int)Math.ceil(gun.getTonnage(infantry)))); ((Text)tspan.getParent()).rebuild(); } if (infantry.hasDEST()) { diagram.getElement(ID_DEST_MODS).removeAttribute("display", AnimationElement.AT_XML); diagram.getElement(ID_SNEAK_IR_MODS).removeAttribute("display", AnimationElement.AT_XML); } else if (infantry.hasSneakCamo()) { diagram.getElement(ID_SNEAK_CAMO_MODS).removeAttribute("display", AnimationElement.AT_XML); } if (infantry.hasSneakIR()) { diagram.getElement(ID_SNEAK_IR_MODS).removeAttribute("display", AnimationElement.AT_XML); } tspan = (Tspan)diagram.getElement(ID_BV); tspan.setText(Integer.toString(infantry.calculateBattleValue())); ((Text)tspan.getParent()).rebuild(); tspan = (Tspan)diagram.getElement(ID_TRANSPORT_WT); tspan.setText(String.format("%.1f tons", infantry.getWeight())); ((Text)tspan.getParent()).rebuild(); Tspan mp1 = (Tspan)diagram.getElement(ID_MP_1); Tspan mode1 = (Tspan)diagram.getElement(ID_MODE_1); Tspan mp2 = (Tspan)diagram.getElement(ID_MP_2); Tspan mode2 = (Tspan)diagram.getElement(ID_MODE_2); switch(infantry.getMovementMode()) { case INF_JUMP: mp1.setText(Integer.toString(infantry.getJumpMP(false))); mode1.setText("Jump"); mp2.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode2.setText("Ground"); ((Text)mp2.getParent()).rebuild(); ((Text)mode2.getParent()).rebuild(); break; case INF_UMU: mp1.setText(Integer.toString(infantry.getActiveUMUCount())); if (infantry.getOriginalJumpMP() > 1) { mode1.setText("SCUBA (Motorized)"); } else { mode1.setText("SCUBA"); } mp2.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode2.setText("Ground"); ((Text)mp2.getParent()).rebuild(); ((Text)mode2.getParent()).rebuild(); break; case HOVER: mp1.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode1.setText("Mechanized Hover"); break; case TRACKED: mp1.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode1.setText("Mechanized Tracked"); break; case WHEELED: mp1.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode1.setText("Mechanized Wheeled"); break; case VTOL: mp1.setText(Integer.toString(infantry.getJumpMP(false))); if (infantry.hasMicrolite()) { mode1.setText("VTOL (Microlite)"); } else { mode1.setText("VTOL (Micro-copter)"); } break; case SUBMARINE: mp1.setText(Integer.toString(infantry.getActiveUMUCount())); mode1.setText("Mechanized SCUBA"); break; case INF_MOTORIZED: mp1.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode1.setText("Motorized"); break; case INF_LEG: default: mp1.setText(Integer.toString(infantry.getWalkMP(true, true, false))); mode1.setText("Ground"); break; } if (mp1.getText().equals("0")) { mp1.setText("0*"); } ((Text)mp1.getParent()).rebuild(); ((Text)mode1.getParent()).rebuild(); List<String> notes = new ArrayList<>(); if (infantry.isMechanized() || infantry.isArmorEncumbering()) { notes.add("Cannot make anti-'Mech attacks."); } if (infantry.hasSpaceSuit()) { notes.add("Can operate in vacuum."); } if (rangeWeapon.hasFlag(WeaponType.F_INF_BURST)) { notes.add("+1D6 damage vs. conventional infantry."); } if (rangeWeapon.hasFlag(WeaponType.F_INF_NONPENETRATING)) { notes.add("Can only damage conventional infantry."); } if (infantry.getPrimaryWeapon().hasFlag(WeaponType.F_INFERNO) || (infantry.getSecondaryWeapon() != null && infantry.getSecondaryWeapon().hasFlag(WeaponType.F_INFERNO))) { notes.add("Flame-based weapon."); } else { for (int i = 0; i < infantry.getPrimaryWeapon().getModesCount(); i++) { if (infantry.getPrimaryWeapon().getMode(i).equals("Heat")) { notes.add("Flame-based weapon."); break; } } if (infantry.getSecondaryWeapon() != null) { for (int i = 0; i < infantry.getSecondaryWeapon().getModesCount(); i++) { if (infantry.getSecondaryWeapon().getMode(i).equals("Heat")) { notes.add("Flame-based weapon."); } } } } if (infantry.getPrimaryWeapon().hasFlag(WeaponType.F_INF_AA) || (infantry.getSecondaryWeapon() != null && infantry.getSecondaryWeapon().hasFlag(WeaponType.F_INF_AA))) { notes.add("Can attack airborn units."); } if (infantry.hasSpecialization(Infantry.BRIDGE_ENGINEERS)) { notes.add("Bridge-building equipment"); } if (infantry.hasSpecialization(Infantry.DEMO_ENGINEERS)) { notes.add("Equipped with demolition gear"); } if (infantry.hasSpecialization(Infantry.FIRE_ENGINEERS)) { notes.add("Firefighting equipment"); } if (infantry.hasSpecialization(Infantry.MINE_ENGINEERS)) { notes.add("Minesweeper equipment"); } if (infantry.hasSpecialization(Infantry.TRENCH_ENGINEERS)) { notes.add("Trench/Fieldwork equipment"); } if (infantry.hasSpecialization(Infantry.MARINES)) { notes.add("No penalties for vacuum or zero-G"); } if (infantry.hasSpecialization(Infantry.MOUNTAIN_TROOPS)) { notes.add("Mountain climbing equipment"); } if (infantry.hasSpecialization(Infantry.PARAMEDICS)) { notes.add("Paramedic equipment."); } if (infantry.hasSpecialization(Infantry.PARATROOPS)) { notes.add("Can make atmospheric drops."); } if (infantry.hasSpecialization(Infantry.SENSOR_ENGINEERS)) { notes.add("Surveillance and communication equipment"); } if (infantry.hasSpecialization(Infantry.TAG_TROOPS)) { notes.add("Equipped with TAG (Range 3/6/9)"); } if (infantry.hasSneakECM()) { notes.add("Invisible to standard/light active probes."); } for (int i = 0; i < Math.min(8, notes.size()); i++) { tspan = (Tspan)diagram.getElement(ID_NOTE_LINE + i); tspan.setText(notes.get(i)); } ((Text)diagram.getElement(ID_NOTES)).rebuild(); diagram.updateTime(0); diagram.render(g2d); } } catch (SVGException ex) { ex.printStackTrace(); } g2d.scale(pageFormat.getImageableWidth(), pageFormat.getImageableHeight()); } private static final int[][] RANGE_MODS = { {0}, {-2, 0, 2, 4}, {-2, 0, 0, 2, 2, 4, 4}, {-2, 0, 0, 0, 2, 2, 2, 4, 4, 4}, {-2, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4}, {-1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4}, {-1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 4, 4, 4, 5, 5, 5}, {-1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 6, 6, 6, 6} }; /** * Calculate range mod as a string value. * @param range - the range to the target. * @param weapon - the primary weapon if there are no more than one secondary, otherwise secondary * @param singleSecondary - secondary weapon if there is exactly one, otherwise null. This is used * to account for point blank or encumbering penalties when the secondary * weapon is not the basis for range mods. * @param underwater - whether the base range should be halved for underwater use by SCUBA platoons. * @return - the range mod as a formatted String. */ private String rangeMod(int range, InfantryWeapon weapon, InfantryWeapon otherWeapon, boolean underwater) { int[] mods = RANGE_MODS[weapon.getInfantryRange()]; if (underwater) { mods = RANGE_MODS[weapon.getInfantryRange() / 2]; } if (range >= mods.length) { return "—"; } int mod = mods[range]; if (range == 0) { if (weapon.hasFlag(WeaponType.F_INF_BURST)) { mod--; } if (weapon.hasFlag(WeaponType.F_INF_POINT_BLANK) || (otherWeapon != null && otherWeapon.hasFlag(WeaponType.F_INF_POINT_BLANK))) { mod++; } if (weapon.hasFlag(WeaponType.F_INF_ENCUMBER) || (otherWeapon != null && otherWeapon.hasFlag(WeaponType.F_INF_ENCUMBER))) { mod++; } } if (mod > 0) { return "+" + mod; } return Integer.toString(mod); } public void print(HashPrintRequestAttributeSet aset) { try { for (; currentPosition < infantryList.size(); currentPosition += 4) { PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPrintService(masterPrintJob.getPrintService()); aset.add(PrintQuality.HIGH); PageFormat pageFormat = new PageFormat(); pageFormat = pj.getPageFormat(null); Paper p = pageFormat.getPaper(); p.setImageableArea(0, 0, p.getWidth(), p.getHeight()); pageFormat.setPaper(p); pj.setPrintable(this, pageFormat); infantry = infantryList.get(currentPosition); pj.setJobName(infantry.getChassis() + " " + infantry.getModel()); try { pj.print(aset); } catch (Exception ex) { ex.printStackTrace(); } System.gc(); } } catch (Exception ex) { ex.printStackTrace(); } } }