/******************************************************************************* * Copyright (c) 2014, 2015 Scott Clarke (scott@dawg6.com). * * This file is part of Dawg6's Demon Hunter DPS Calculator. * * Dawg6's Demon Hunter DPS Calculator 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 3 of the License, or * (at your option) any later version. * * Dawg6's Demon Hunter DPS Calculator 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package com.dawg6.web.dhcalc.server; import java.io.ByteArrayOutputStream; import java.util.Hashtable; import java.util.Map; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFDataFormat; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellUtil; import com.dawg6.web.dhcalc.shared.calculator.ActiveSkill; import com.dawg6.web.dhcalc.shared.calculator.AttributeData; import com.dawg6.web.dhcalc.shared.calculator.Damage; import com.dawg6.web.dhcalc.shared.calculator.DamageHolder; import com.dawg6.web.dhcalc.shared.calculator.DamageSource; import com.dawg6.web.dhcalc.shared.calculator.DamageType; import com.dawg6.web.dhcalc.shared.calculator.ExportData; import com.dawg6.web.dhcalc.shared.calculator.GemAttributeData; import com.dawg6.web.dhcalc.shared.calculator.GemSkill; import com.dawg6.web.dhcalc.shared.calculator.ItemHolder; import com.dawg6.web.dhcalc.shared.calculator.ItemSet; import com.dawg6.web.dhcalc.shared.calculator.Passive; import com.dawg6.web.dhcalc.shared.calculator.Rune; import com.dawg6.web.dhcalc.shared.calculator.Slot; import com.dawg6.web.dhcalc.shared.calculator.SpecialItemType; public class ExportExcel { private final ExportData data; private final Map<String, Cell> inputCells = new Hashtable<String, Cell>(); private Font boldFont; private CellStyle boldStyle; private HSSFWorkbook wb; private HSSFSheet inputs; private HSSFCellStyle doubleStyle; private HSSFDataFormat doubleFormat; private HSSFCellStyle intStyle; private HSSFDataFormat intFormat; private HSSFCellStyle pctStyle; private HSSFDataFormat pctFormat; private HSSFCellStyle largeDoubleStyle; private HSSFDataFormat largeDoubleFormat; private HSSFSheet summary; private HSSFCellStyle timeStyle; private HSSFDataFormat timeFormat; public ExportExcel(ExportData data) { this.data = data; } public byte[] export() throws Exception { wb = new HSSFWorkbook(); boldFont = wb.createFont(); boldFont.setFontHeightInPoints((short) 10); boldFont.setFontName("Arial"); boldFont.setBoldweight(Font.BOLDWEIGHT_BOLD); boldStyle = wb.createCellStyle(); boldStyle.setFont(boldFont); doubleStyle = wb.createCellStyle(); doubleFormat = wb.createDataFormat(); doubleStyle.setDataFormat(doubleFormat.getFormat("#,##0.0###")); timeStyle = wb.createCellStyle(); timeFormat = wb.createDataFormat(); timeStyle.setDataFormat(timeFormat.getFormat("0.00")); largeDoubleStyle = wb.createCellStyle(); largeDoubleFormat = wb.createDataFormat(); largeDoubleStyle.setDataFormat(largeDoubleFormat.getFormat("#,###")); intStyle = wb.createCellStyle(); intFormat = wb.createDataFormat(); intStyle.setDataFormat(intFormat.getFormat("#,##0")); pctStyle = wb.createCellStyle(); pctFormat = wb.createDataFormat(); pctStyle.setDataFormat(pctFormat.getFormat("0.00%")); inputs = wb.createSheet("Inputs"); summary = wb.createSheet("Summary"); HSSFSheet damageLog = wb.createSheet("DamageLog"); HSSFSheet typeSummary = wb.createSheet("DamageTypeSummary"); HSSFSheet skillSummary = wb.createSheet("SkillSummary"); HSSFSheet shooterSummary = wb.createSheet("ShooterSummary"); addInputs(); addSummary(); addDamageLog(damageLog); addTypeSummary(typeSummary); addSkillSummary(skillSummary); addShooterSummary(shooterSummary); ByteArrayOutputStream stream = new ByteArrayOutputStream(); wb.write(stream); return stream.toByteArray(); } private void addSummary() { createInputHeader(summary, "Summary of Damage Log"); createInput(summary, data.totalDamage, "Total Damage over " + data.output.duration + " seconds", largeDoubleStyle); summary.autoSizeColumn(0, true); summary.autoSizeColumn(1, true); } private void addSkillSummary(HSSFSheet skillSummary) { createTableHeader(skillSummary, 0, "Skill"); createTableHeader(skillSummary, 1, "# Attacks"); createTableHeader(skillSummary, 2, "Per Attack"); createTableHeader(skillSummary, 3, "Total"); createTableHeader(skillSummary, 4, "DPS"); createTableHeader(skillSummary, 5, "% of Total"); double total = 0; for (Damage d : data.output.damages) { total += d.actualDamage; } int i = 1; for (Map.Entry<DamageSource, DamageHolder> e : data.skillDamages .entrySet()) { Row row = skillSummary.createRow(i++); ActiveSkill skill = e.getKey().skill; GemSkill gem = e.getKey().gem; DamageHolder d = e.getValue(); addTableCell( row, 0, (skill != null) ? skill.getLongName() : gem .getDisplayName()); addTableCell(row, 1, d.attacks); addTableCell(row, 2, Math.round((d.damage) / d.attacks)); addTableCell(row, 3, d.damage); addTableCell(row, 4, Math.round((d.damage) / data.output.duration)); addTableCell(row, 5, Math.round(10000.0 * d.damage / total) / 10000.0, pctStyle); } for (i = 0; i < 6; i++) { skillSummary.autoSizeColumn(i, true); } } private void addShooterSummary(HSSFSheet shooterSummary) { createTableHeader(shooterSummary, 0, "Shooter"); createTableHeader(shooterSummary, 1, "# Attacks"); createTableHeader(shooterSummary, 2, "Per Attack"); createTableHeader(shooterSummary, 3, "Total"); createTableHeader(shooterSummary, 4, "DPS"); createTableHeader(shooterSummary, 5, "% of Total"); double total = 0; for (Damage d : data.output.damages) { total += d.actualDamage; } int i = 1; for (Map.Entry<String, DamageHolder> e : data.shooterDamages.entrySet()) { Row row = shooterSummary.createRow(i++); DamageHolder d = e.getValue(); addTableCell(row, 0, e.getKey()); addTableCell(row, 1, d.attacks); addTableCell(row, 2, Math.round((d.damage) / d.attacks)); addTableCell(row, 3, d.damage); addTableCell(row, 4, Math.round((d.damage) / data.output.duration)); addTableCell(row, 5, Math.round(10000.0 * d.damage / total) / 10000.0, pctStyle); } for (i = 0; i < 6; i++) { shooterSummary.autoSizeColumn(i, true); } } private void addTypeSummary(HSSFSheet typeSummary) { createTableHeader(typeSummary, 0, "Type"); createTableHeader(typeSummary, 1, "# Attacks"); createTableHeader(typeSummary, 2, "Per Attack"); createTableHeader(typeSummary, 3, "Total"); createTableHeader(typeSummary, 4, "DPS"); createTableHeader(typeSummary, 5, "% of Total"); double total = 0; for (Damage d : data.output.damages) { total += d.actualDamage; } int i = 1; for (Map.Entry<DamageType, DamageHolder> e : data.types.entrySet()) { Row row = typeSummary.createRow(i++); DamageHolder d = e.getValue(); addTableCell(row, 0, e.getKey().name()); addTableCell(row, 1, d.attacks); addTableCell(row, 2, Math.round((d.damage) / d.attacks)); addTableCell(row, 3, d.damage); addTableCell(row, 4, Math.round((d.damage) / data.output.duration)); addTableCell(row, 5, Math.round(10000.0 * d.damage / total) / 10000.0, pctStyle); } for (i = 0; i < 6; i++) { typeSummary.autoSizeColumn(i, true); } } private void addDamageLog(HSSFSheet damageLog) { int col = 0; createTableHeader(damageLog, col++, "Time"); createTableHeader(damageLog, col++, "Shooter"); createTableHeader(damageLog, col++, "Skill"); createTableHeader(damageLog, col++, "Rune"); createTableHeader(damageLog, col++, "Type"); createTableHeader(damageLog, col++, "+/- Hatred"); createTableHeader(damageLog, col++, "Hatred"); createTableHeader(damageLog, col++, "+/- Disc"); createTableHeader(damageLog, col++, "Disc"); createTableHeader(damageLog, col++, "Damage"); createTableHeader(damageLog, col++, "Target HP"); createTableHeader(damageLog, col++, "% HP"); createTableHeader(damageLog, col++, "Target"); createTableHeader(damageLog, col++, "Notes"); createTableHeader(damageLog, col++, "Calculations"); double total = 0; for (Damage d : data.output.damages) { total += d.actualDamage; } for (int i = 0; i < data.output.damages.length; i++) { Damage d = data.output.damages[i]; Row row = damageLog.createRow(i + 1); col = 0; addTableCellD(row, col++, Math.round(d.time * 100.0) / 100.0); addTableCell(row, col++, d.shooter); if (d.source != null) { ActiveSkill skill = d.source.skill; GemSkill gem = d.source.gem; Rune rune = d.source.rune; addTableCell(row, col++, (skill != null) ? skill.getLongName() : gem.getDisplayName()); addTableCell(row, col++, (rune != null) ? rune.getLongName() : "N/A"); } else { col += 2; } if (d.type != null) { addTableCell(row, col++, d.type.name()); } else { col++; } if (d.hatred != 0) { addTableCellD(row, col++, Math.round(d.hatred * 10.0) / 10.0); } else { col++; } addTableCellD(row, col++, Math.round(d.currentHatred * 10.0) / 10.0); if (d.disc != 0) { addTableCellD(row, col++, Math.round(d.disc * 10.0) / 10.0); } else { col++; } addTableCellD(row, col++, Math.round(d.currentDisc * 10.0) / 10.0); if (d.damage > 0) { addTableCell(row, col++, Math.round(d.damage)); addTableCell(row, col++, (double)Math.round(d.targetHp)); addTableCell(row, col++, Math.round(d.targetHpPercent * 1000.0) / 10.0 + "%"); } else { col += 3; } if (d.target != null) { addTableCell(row, col++, d.target.toString()); } else { col += 2; } if (d.note != null) { addTableCell(row, col++, d.note); } else { col++; } if (d.log != null) { addTableCell(row, col++, d.log); } else { col++; } } for (int i = 0; i < 12; i++) { damageLog.autoSizeColumn(i, true); } } private Cell addTableCell(Row row, int col, String label) { Cell cell = row.createCell(col); cell.setCellValue(label); CellUtil.setAlignment(cell, row.getSheet().getWorkbook(), CellStyle.ALIGN_LEFT); return cell; } @SuppressWarnings("unused") private Cell addTableCell(Row row, int col, Boolean value) { Cell cell = row.createCell(col); cell.setCellValue(String.valueOf(value)); CellUtil.setAlignment(cell, row.getSheet().getWorkbook(), CellStyle.ALIGN_CENTER); return cell; } private Cell addTableCell(Row row, int col, int value) { Cell cell = row.createCell(col); cell.setCellValue(String.valueOf(value)); cell.setCellStyle(intStyle); CellUtil.setAlignment(cell, row.getSheet().getWorkbook(), CellStyle.ALIGN_RIGHT); return cell; } private Cell addTableCellD(Row row, int col, double value) { return addTableCell(row, col, value, doubleStyle); } private Cell addTableCell(Row row, int col, double value) { return addTableCell(row, col, value, largeDoubleStyle); } private Cell addTableCell(Row row, int col, double value, HSSFCellStyle style) { Cell cell = row.createCell(col); cell.setCellValue(value); cell.setCellStyle(style); CellUtil.setAlignment(cell, row.getSheet().getWorkbook(), CellStyle.ALIGN_RIGHT); return cell; } private Cell createTableHeader(HSSFSheet sheet, int col, String label) { int n = sheet.getPhysicalNumberOfRows(); Row row = null; if (n < 1) row = sheet.createRow(0); else row = sheet.getRow(0); Cell cell = row.createCell(col); cell.setCellValue(label); cell.setCellStyle(boldStyle); CellUtil.setAlignment(cell, sheet.getWorkbook(), CellStyle.ALIGN_CENTER); return cell; } private void addInputs() { createInputHeader(inputs, "Character Data"); createInput(inputs, data.data.getCritChance(), "Crit Chance ", pctStyle); createInput(inputs, data.data.getCritHitDamage(), "Crit Hit Damage ", pctStyle); createInput(inputs, data.data.getWeaponDamage(), "Weapon Damage"); createInput(inputs, data.data.getDexterity(), "Total Dexterity"); createInput(inputs, data.data.getEquipmentDexterity(), "Equipment Dexterity"); createInput(inputs, data.data.getLevel(), "Level"); createInput(inputs, data.data.getCdr(), "Effective CDR", this.pctStyle); createInput(inputs, data.data.getRcr(), "Effective RCR", this.pctStyle); createInput(inputs, data.data.getWeaponType().getName(), "WeaponType"); createInput(inputs, data.data.getAps(), "Player APS"); createInput(inputs, data.data.getSentryAps(), "Sentry APS"); createInput(inputs, 8.0 * (1 - data.data.getCdr()), "Sentry Cooldown (sec)", timeStyle); createInput(inputs, data.data.getTotalEliteDamage(), "Total Elite Damage", pctStyle); createInput(inputs, data.data.getMaxHatred(), "Max Hatred"); createInput(inputs, data.data.getHatredPerSecond(), "Hatred Per Second"); createInput(inputs, data.data.getEquipmentDiscipline(), "Equipment +Max Discipline"); createInput(inputs, data.data.getParagonDexterity(), "Paragon Dexterity"); createInput(inputs, data.data.getParagon(), "Paragon Level"); createInput(inputs, data.data.getParagonIAS(), "Paragon IAS"); createInput(inputs, data.data.getParagonCC(), "Paragon CC"); createInput(inputs, data.data.getParagonCHD(), "Paragon CHD"); createInput(inputs, data.data.getParagonCDR(), "Paragon CDR"); createInput(inputs, data.data.getParagonHatred(), "Paragon Hatred"); createInput(inputs, data.data.getParagonRCR(), "Paragon RCR"); createInput(inputs, data.data.getParagonAD(), "Paragon Area Damage"); createInputHeader(inputs, "Active Skills"); for (Map.Entry<ActiveSkill, Rune> e : data.data.getSkills().entrySet()) { createInput(inputs, e.getValue().getLongName(), e.getKey() .getLongName()); } createInput(inputs, data.data.getCaltropsUptime(), "Caltrops Uptime", pctStyle); createInput(inputs, data.data.isSyncWithCoe(), "Sync Cooldowns with CoE"); createInput(inputs, data.data.getMfdUptime(), "Marked for Death Primary Target Uptime", pctStyle); createInput(inputs, data.data.getMfdAddUptime(), "Marked for Death Additional Targets Uptime", pctStyle); createInput(inputs, data.data.getNumSentries(), "# of Sentries"); createInputHeader(inputs, "Passive Skills"); for (Passive p : data.data.getPassives()) { createInput(inputs, Boolean.TRUE, p.getLongName()); } createInputHeader(inputs, "Situational"); createInput(inputs, data.data.getRiftLevel(), "Greater Rift Level"); createInput(inputs, data.data.getNumPlayers(), "Number of Players"); createInput(inputs, data.data.getPrimaryTargetType().toString(), "Primary Target Type"); createInput(inputs, data.data.getPrimaryTargetHealth(), "Primary Target Health"); createInput(inputs, data.data.getNumAdditional(), "# Additional Targets"); createInput(inputs, data.data.getAdditionalTargetType().toString(), "Additional Target Type"); createInput(inputs, data.data.getAdditionalTargetHealth(), "Additional Target Health"); createInput(inputs, data.data.getPercentSlowedChilled(), "Percent Slowed/Chilled", pctStyle); createInput(inputs, data.data.getPercentControlled(), "percent Control Impaired", pctStyle); createInput(inputs, data.data.getPercentAtLeast10Yards(), "Percent of targets at least 10 Yards away", pctStyle); createInput(inputs, data.data.getDistanceToTarget(), "Distance to Target(s)(yards)"); createInput(inputs, data.data.getTargetSpacing(), "Spacing between Targets(yards)"); createInput(inputs, data.data.getTargetSize().getDisplayName(), "Target Size"); createInput(inputs, data.data.getDelay(), "Player Action Delay"); createInput(inputs, data.data.getPercentMoving(), "% of time moving", pctStyle); createInputHeader(inputs, "Elemental Damage Modifiers"); for (Map.Entry<DamageType, Double> e : data.data.getElementalDamage() .entrySet()) { createInput(inputs, e.getValue(), e.getKey().getLongName(), pctStyle); } createInputHeader(inputs, "Skill Damage Modifiers"); for (Map.Entry<ActiveSkill, Double> e : data.data.getSkillDamage() .entrySet()) { createInput(inputs, e.getValue(), e.getKey().getLongName(), pctStyle); } createInputHeader(inputs, "Item Data"); createInput(inputs, data.data.getNumAncients(), "# of Ancient Items"); createInput(inputs, data.data.getEliteDamage(), "Equipment Elite Damage (minus BotP passive)", pctStyle); createInput(inputs, data.data.getAreaDamageEquipment(), "Equipment Area Damage", pctStyle); for (Map.Entry<Slot, ItemHolder> e : data.data.getSpecialItems().entrySet()) { Slot slot = e.getKey(); ItemHolder item = e.getValue(); SpecialItemType type = item.getType(); AttributeData ad = item.getAttributes(); createInput(inputs, type.getName(), slot.getSlot()); for (SpecialItemType.Attribute a : type.getAttributes()) { Integer i = ad.get(a.getLabel()); if (i != null) { createInput(inputs, i, type.getName() + " " + a.getLabel()); } } } createInputHeader(inputs, "Set Items"); createInput(inputs, data.data.isOtherSets(), "Other Sets are Worn"); for (ItemSet set : ItemSet.values()) { Integer i = data.data.getSetCount(set.getSlug()); if ((i != null) && (i > 0)) { createInput(inputs, i, set.getName()); } } createInputHeader(inputs, "Legendary Gems"); for (Map.Entry<GemSkill, GemAttributeData> e : data.data.getGems().entrySet()) { GemSkill gem = e.getKey(); GemAttributeData gd = e.getValue(); createInput(inputs, gd.level, gem.getDisplayName() + " Level"); for (Map.Entry<String, Integer> a : gd.entrySet()) { createInput(inputs, a.getValue(), gem.getDisplayName() + " " + a.getKey()); } } createInputHeader(inputs, "Follower Buffs"); createInput(inputs, data.data.isFocusedMind(), "Enchantress Focused Mind"); createInput(inputs, data.data.isAnatomy(), "Scoundrel Anatomy"); createInput(inputs, data.data.isHysteria(), "Scoundrel Hysteria"); createInput(inputs, data.data.isInspire(), "Templar Inspire"); createInput(inputs, data.data.isFollowerOculus(), "Follower Oculus Ring"); createInput(inputs, data.data.getFollowerOculusPercent(), "Follower Oculus Ring Percent", pctStyle); createInput(inputs, data.data.getFollowerOculusUptime(), "Follower Oculus Ring Uptime", pctStyle); createInputHeader(inputs, "Player Buffs"); createInput(inputs, data.data.isWolf(), "Wolf Companion"); createInput(inputs, data.data.getWolfUptime(), "Wolf Companion Uptime", pctStyle); createInput(inputs, data.data.isBbv(), "Big Bad Voodoo"); createInput(inputs, data.data.getBbvUptime(), "Big Bad Voodoo Uptime", pctStyle); createInput(inputs, data.data.isSlamDance(), "Slam Dance"); createInput(inputs, data.data.isTimeWarp(), "Slow Time/Time Warp"); createInput(inputs, data.data.getTimeWarpUptime(), "Slow Time/Time Warp Uptime", pctStyle); createInput(inputs, data.data.isStretchTime(), "Slow Time/Stretch Time"); createInput(inputs, data.data.getStretchTimeUptime(), "Slow Time/Stretch Time Uptime", pctStyle); createInput(inputs, data.data.isMassConfusion(), "Mass Connfusion/Paranoia"); createInput(inputs, data.data.getMassConfusionUptime(), "Mass Connfusion/Paranoia Uptime", pctStyle); createInput(inputs, data.data.isPiranhas(), "Piranhas"); createInput(inputs, data.data.getPiranhasUptime(), "Piranhas Uptime", pctStyle); createInput(inputs, data.data.isInnerSanctuary(), "Inner Sanctuary/Forbidden Palace"); createInput(inputs, data.data.getInnerSanctuaryUptime(), "Inner Sanctuary/Forbidden Palace Uptime", pctStyle); createInput(inputs, data.data.isCripplingWave(), "Crippling Wave/Breaking Wave"); createInput(inputs, data.data.getCripplingWaveUptime(), "Crippling Wave/Breaking Wave Uptime", pctStyle); createInput(inputs, data.data.isValor(), "Laws of Valor"); createInput(inputs, data.data.getValorActiveUptime(), "Laws of Valor Active Uptime", pctStyle); createInput(inputs, data.data.getValorPassiveUptime(), "Laws of Valor Passive Uptime", pctStyle); createInput(inputs, data.data.isRetribution(), "Mantra of Retribution"); createInput(inputs, data.data.getRetributionUptime(), "Mantra of Retribution Uptime", pctStyle); createInput(inputs, data.data.isConviction(), "Conviction"); createInput(inputs, data.data.isOverawe(), "Overawe"); createInput(inputs, data.data.getConvictionPassiveUptime(), "Conviction Passive Uptime", pctStyle); createInput(inputs, data.data.getConvictionActiveUptime(), "Conviction Active Uptime", pctStyle); createInput(inputs, data.data.isPartyOculus(), "Party Oculus Ring"); createInput(inputs, data.data.getPartyOculusPercent(), "Party Oculus Ring Percent", pctStyle); createInput(inputs, data.data.getPartyOculusUptime(), "Party Oculus Ring Uptime", pctStyle); inputs.autoSizeColumn(0, true); inputs.autoSizeColumn(1, true); } private Cell createInputHeader(Sheet sheet, String label) { Row row = sheet.createRow(sheet.getPhysicalNumberOfRows()); Cell cell1 = row.createCell(0); cell1.setCellValue(label); cell1.setCellStyle(boldStyle); return cell1; } private Cell createInput(Sheet sheet, double value, String label) { return createInput(sheet, value, label, doubleStyle); } private Cell createInput(Sheet sheet, double value, String label, HSSFCellStyle style) { Cell cell = createInputCell(sheet, label); cell.setCellValue(value); cell.setCellStyle(style); return cell; } private Cell createInput(Sheet sheet, boolean value, String label) { Cell cell = createInputCell(sheet, label); cell.setCellValue(value); return cell; } private Cell createInput(Sheet sheet, int value, String label) { Cell cell = createInputCell(sheet, label); cell.setCellValue(value); cell.setCellStyle(intStyle); return cell; } private Cell createInput(Sheet sheet, String text, String label) { Cell cell = createInputCell(sheet, label); cell.setCellValue(text); return cell; } private Cell createInputCell(Sheet sheet, String label) { Row row = sheet.createRow(sheet.getPhysicalNumberOfRows()); Cell cell1 = row.createCell(0); cell1.setCellValue(label + ":"); Cell cell2 = row.createCell(1); CellUtil.setAlignment(cell2, sheet.getWorkbook(), CellStyle.ALIGN_RIGHT); inputCells.put(label, cell2); return cell2; } }