/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom
* the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package com.googlecode.logVisualizer.util.textualLogs;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.googlecode.logVisualizer.Settings;
import com.googlecode.logVisualizer.logData.Item;
import com.googlecode.logVisualizer.logData.LogDataHolder;
import com.googlecode.logVisualizer.logData.LogDataHolder.StatClass;
import com.googlecode.logVisualizer.logData.MPGain;
import com.googlecode.logVisualizer.logData.MeatGain;
import com.googlecode.logVisualizer.logData.Skill;
import com.googlecode.logVisualizer.logData.Statgain;
import com.googlecode.logVisualizer.logData.consumables.Consumable;
import com.googlecode.logVisualizer.logData.consumables.Consumable.ConsumableVersion;
import com.googlecode.logVisualizer.logData.logSummary.AreaStatgains;
import com.googlecode.logVisualizer.logData.logSummary.LevelData;
import com.googlecode.logVisualizer.logData.turn.SingleTurn;
import com.googlecode.logVisualizer.logData.turn.SingleTurn.TurnVersion;
import com.googlecode.logVisualizer.logData.turn.TurnInterval;
import com.googlecode.logVisualizer.logData.turn.TurnInterval.FreeRunaways;
import com.googlecode.logVisualizer.logData.turn.turnAction.DayChange;
import com.googlecode.logVisualizer.logData.turn.turnAction.FamiliarChange;
import com.googlecode.logVisualizer.logData.turn.turnAction.PlayerSnapshot;
import com.googlecode.logVisualizer.logData.turn.turnAction.Pull;
import com.googlecode.logVisualizer.parser.UsefulPatterns;
import com.googlecode.logVisualizer.util.DataCounter;
import com.googlecode.logVisualizer.util.DataNumberPair;
import com.googlecode.logVisualizer.util.DataTablesHandler;
import com.googlecode.logVisualizer.util.Pair;
/**
* This utility class creates a parsed ascension log from a
* {@link LogDataHolder}. The format of the parsed log is similar to the one
* which the AFH parser uses.
* <p>
* Note that this class should only be used to create parsed ascension logs from
* mafia logs. Using pre-parsed logs as the basis will not work, because those
* do not contain enough data.
* <p>
* All methods in this class throw a {@link NullPointerException} if a null
* object reference is passed in any parameter.
*/
public final class TextLogCreator {
private static final Map<String, String> TEXT_LOG_ADDITIONS_MAP = new HashMap<>();
private static final Map<String, String> HTML_LOG_ADDITIONS_MAP = new HashMap<>();
private static final Map<String, String> BBCODE_LOG_ADDITIONS_MAP = new HashMap<>();
private static final String NEW_LINE = System.getProperty("line.separator");
private static final String COMMA = ", ";
private static final String OPENING_TURN_BRACKET = " [";
private static final String CLOSING_TURN_BRACKET = "] ";
private static final String ITEM_PREFIX = " +>";
private static final String ITEM_MIDDLE_STRING = "Got ";
private static final String CONSUMABLE_PREFIX = " o> ";
private static final String PULL_PREFIX = " #> Turn";
private static final String LEVEL_CHANGE_PREFIX = " => Level ";
private static final String HUNTED_COMBAT_PREFIX = " *>";
private static final String HUNTED_COMBAT_MIDDLE_STRING = "Started hunting ";
private static final String DISINTEGRATED_COMBAT_PREFIX = " }>";
private static final String DISINTEGRATED_COMBAT_MIDDLE_STRING = "Disintegrated ";
private static final String FAMILIAR_CHANGE_PREFIX = " -> Turn";
private static final String SEMIRARE_PREFIX = " #>";
private static final String SEMIRARE_MIDDLE_STRING = "Semirare: ";
private static final String BAD_MOON_PREFIX = " %>";
private static final String BAD_MOON_MIDDLE_STRING = "Badmoon: ";
private static final String FREE_RUNAWAYS_PREFIX = " &> ";
private static final String ADVENTURES_LEFT_STRING = "Adventure count at day start: ";
private static final String CURRENT_MEAT_STRING = "Current meat: ";
private static final DayChange NO_DAY_CHANGE = new DayChange(
Integer.MAX_VALUE, Integer.MAX_VALUE);
static {
TextLogCreator.TEXT_LOG_ADDITIONS_MAP.put("notesStart", "[/code]");
TextLogCreator.TEXT_LOG_ADDITIONS_MAP.put("notesEnd", "[code]");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("logHeaderStart", "<i>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("logHeaderEnd", "</i>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("turnStart", "<b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("turnEnd", "</b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("dayChangeLineStart", "<b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("dayChangeLineEnd", "</b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("statgainStart",
"<font color=#808080>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("statgainEnd", "</font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("pullStart",
"<font color=#008B8B>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("pullEnd", "</font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("consumableStart",
"<font color=#009933><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("consumableEnd",
"</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("itemStart",
"<font color=#0000CD>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("itemEnd", "</font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("familiarStart",
"<font color=#B03030>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("familiarEnd", "</font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("huntedStart",
"<font color=#006400><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("huntedEnd", "</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("yellowRayStart",
"<font color=#B8860B><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP
.put("yellowRayEnd", "</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("specialEncounterStart",
"<font color=#8B008B><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("specialEncounterEnd",
"</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("levelStart",
"<font color=#DC143C><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("levelEnd", "</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("runawayStart",
"<font color=#CD853F><b>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("runawayEnd", "</b></font>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("notesStart", "<br>");
TextLogCreator.HTML_LOG_ADDITIONS_MAP.put("notesEnd", "<br><br>");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("logHeaderStart", "[i]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("logHeaderEnd",
"[/i][quote]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("turnRundownEnd",
"[/quote]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("turnStart", "[b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("turnEnd", "[/b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP
.put("dayChangeLineStart", "[b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("dayChangeLineEnd", "[/b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("statgainStart",
"[color=#808080]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("statgainEnd", "[/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("pullStart",
"[color=#008B8B]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("pullEnd", "[/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("consumableStart",
"[color=#009933][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("consumableEnd",
"[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("itemStart",
"[color=#0000CD]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("itemEnd", "[/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("familiarStart",
"[color=#B03030]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("familiarEnd", "[/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("huntedStart",
"[color=#006400][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP
.put("huntedEnd", "[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("yellowRayStart",
"[color=#B8860B][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("yellowRayEnd",
"[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("specialEncounterStart",
"[color=#8B008B][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("specialEncounterEnd",
"[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("levelStart",
"[color=#DC143C][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("levelEnd", "[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("runawayStart",
"[color=#CD853F][b]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("runawayEnd",
"[/b][/color]");
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("notesStart", "[/quote]"
+ TextLogCreator.NEW_LINE);
TextLogCreator.BBCODE_LOG_ADDITIONS_MAP.put("notesEnd",
TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE + "[quote]");
}
private final Map<String, String> logAdditionsMap;
private final Set<String> localeOnetimeItemsSet = new HashSet<>(
DataTablesHandler.getOnetimeItems());
private final StringBuilder log;
private final Iterator<FamiliarChange> familiarChangeIter;
private FamiliarChange currentFamChange;
private final Iterator<Pull> pullIter;
private Pull currentPull;
private final Iterator<LevelData> levelIter;
private LevelData nextLevel;
private final Iterator<DataNumberPair<String>> huntedCombatIter;
private DataNumberPair<String> currentHuntedCombat;
private final Iterator<DataNumberPair<String>> disintegratedCombatIter;
private DataNumberPair<String> currentDisintegratedCombat;
private boolean isShowNotes = true;
/**
* Creates a list of all turn interval print-outs as they are composed in a
* turn rundown inside a textual ascension log.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @return The turn rundown list.
*/
public static List<String> getTurnRundownList(final LogDataHolder logData) {
final TextLogCreator logCreator = new TextLogCreator(logData,
TextualLogVersion.TEXT_LOG);
logCreator.isShowNotes = false;
return logCreator.createTurnRundownList(logData);
}
/**
* Creates a parsed ascension log from the given {@link LogDataHolder} and
* returns it as a String.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @param logVersion
* The wanted version of the textual log output.
* @return The textual ascension log.
*/
public static String getTextualLog(final LogDataHolder logData,
final TextualLogVersion logVersion) {
// Sometimes, geek jokes are fun! ;)
int logDate = 404;
if (UsefulPatterns.USUAL_FORMAT_LOG_NAME.matcher(logData.getLogName())
.matches()) {
logDate = UsefulPatterns.getLogDate(logData.getLogName());
}
return TextLogCreator.getTextualLog(logData, logDate, logVersion);
}
/**
* Creates a parsed ascension log from the given {@link LogDataHolder} and
* returns it as a String.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @param ascensionStartDate
* The real-time start date of the ascension as saved by
* KolMafia.
* @param logVersion
* The wanted version of the textual log output.
* @return The textual ascension log.
*/
public static String getTextualLog(final LogDataHolder logData,
final int ascensionStartDate, final TextualLogVersion logVersion) {
final TextLogCreator logCreator = new TextLogCreator(logData,
logVersion);
final String logOutput = logCreator.createTextLog(logData,
ascensionStartDate);
if (logVersion == TextualLogVersion.HTML_LOG) {
return "<html><body>"
+ logOutput.replace(TextLogCreator.NEW_LINE, "<br>"
+ TextLogCreator.NEW_LINE) + "</body></html>";
} else {
return logOutput;
}
}
/**
* Creates a parsed ascension log from the given {@link LogDataHolder} and
* saves it to the given file.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @param saveDest
* The file in which the parsed ascension log should be saved in.
* @param logVersion
* The wanted version of the textual log output.
* @throws IllegalArgumentException
* if saveDest doesn't exist or is a directory
*/
public static void saveTextualLogToFile(final LogDataHolder logData,
final File saveDest, final TextualLogVersion logVersion)
throws IOException {
if (!saveDest.exists()) {
throw new IllegalArgumentException("The file doesn't exist.");
}
if (saveDest.isDirectory()) {
throw new IllegalArgumentException("The file is a directory.");
}
try (final PrintWriter writer = new PrintWriter(new BufferedWriter(
new FileWriter(saveDest), 50000))) {
writer.print(TextLogCreator.getTextualLog(logData, logVersion));
writer.close();
}
}
/**
* Creates a parsed ascension log from the given {@link LogDataHolder} and
* saves it to the given file.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @param ascensionStartDate
* The real-time start date of the ascension as saved by
* KolMafia.
* @param saveDest
* The file in which the parsed ascension log should be saved in.
* @param logVersion
* The wanted version of the textual log output.
* @throws IllegalArgumentException
* if saveDest doesn't exist or is a directory
*/
public static void saveTextualLogToFile(final LogDataHolder logData,
final int ascensionStartDate, final File saveDest,
final TextualLogVersion logVersion) throws IOException {
if (!saveDest.exists()) {
throw new IllegalArgumentException("The file doesn't exist.");
}
if (saveDest.isDirectory()) {
throw new IllegalArgumentException("The file is a directory.");
}
try (final PrintWriter writer = new PrintWriter(new BufferedWriter(
new FileWriter(saveDest), 50000))) {
writer.print(TextLogCreator.getTextualLog(logData,
ascensionStartDate, logVersion));
writer.close();
}
}
/**
* Sets up a TextLogCreator instance for further use.
*
* @param logData
* The ascension log data from which the parsed ascension log
* should be created.
* @param logVersion
* The wanted version of the textual log output.
*/
private TextLogCreator(final LogDataHolder logData,
final TextualLogVersion logVersion) {
if (logData == null) {
throw new NullPointerException(
"The LogDataHolder must not be null.");
}
switch (logVersion) {
case HTML_LOG:
this.logAdditionsMap = Collections
.unmodifiableMap(TextLogCreator.HTML_LOG_ADDITIONS_MAP);
break;
case BBCODE_LOG:
this.logAdditionsMap = Collections
.unmodifiableMap(TextLogCreator.BBCODE_LOG_ADDITIONS_MAP);
break;
default:
this.logAdditionsMap = Collections
.unmodifiableMap(TextLogCreator.TEXT_LOG_ADDITIONS_MAP);
}
// Most logs stay below 50000 characters.
this.log = new StringBuilder(50000);
this.familiarChangeIter = logData.getFamiliarChanges().iterator();
this.pullIter = logData.getPulls().iterator();
this.levelIter = logData.getLevels().iterator();
this.huntedCombatIter = logData.getLogSummary().getHuntedCombats()
.iterator();
this.disintegratedCombatIter = logData.getLogSummary()
.getDisintegratedCombats().iterator();
}
/**
* Creates a parsed ascension log in a style similar to the format used by
* the AFH parser.
*
* @param logData
* The LogDataHolder from which the ascension log should be
* created.
* @param ascensionStartDate
* The real-time start date of the ascension as saved by
* KolMafia.
*/
private List<String> createTurnRundownList(final LogDataHolder logData) {
final List<String> turnRundown = new ArrayList<>(logData
.getTurnsSpent().size());
this.currentFamChange = this.familiarChangeIter.hasNext() ? this.familiarChangeIter
.next() : null;
this.currentPull = this.pullIter.hasNext() ? this.pullIter.next()
: null;
this.currentHuntedCombat = this.huntedCombatIter.hasNext() ? this.huntedCombatIter
.next() : null;
this.currentDisintegratedCombat = this.disintegratedCombatIter
.hasNext() ? this.disintegratedCombatIter.next() : null;
// Level 1 can be skipped.
this.levelIter.next();
this.nextLevel = this.levelIter.hasNext() ? this.levelIter.next()
: null;
// Day 1 day change is handled differently and can be ignored here.
final Iterator<DayChange> dayChangeIter = logData.getDayChanges()
.iterator();
DayChange nextDayChange = dayChangeIter.next();
nextDayChange = dayChangeIter.hasNext() ? dayChangeIter.next()
: TextLogCreator.NO_DAY_CHANGE;
int currentDayNumber = 1;
for (final TurnInterval ti : logData.getTurnsSpent()) {
if (!nextDayChange.equals(TextLogCreator.NO_DAY_CHANGE)
&& (ti.getEndTurn() >= nextDayChange.getTurnNumber())) {
if (ti.getEndTurn() == nextDayChange.getTurnNumber()) {
this.printTurnIntervalContents(ti, currentDayNumber);
final int currentStringLenght = this.log.length();
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
this.log.delete(currentStringLenght, this.log.length());
// Consumables usage or pulls that happened nominally on the
// last turn before the day change, but were actually done
// on the next day.
this.printCurrentConsumables(ti.getConsumablesUsed(),
currentDayNumber);
this.printCurrentPulls(currentDayNumber, ti.getEndTurn());
} else if (ti.getStartTurn() < nextDayChange.getTurnNumber()) {
SingleTurn dayChangeTurn = null;
for (final SingleTurn st : ti.getTurns()) {
if (st.getTurnNumber() > nextDayChange.getTurnNumber()) {
dayChangeTurn = st;
break;
}
}
if (dayChangeTurn != null) {
final TurnInterval turnsBeforeDayChange = new TurnInterval(
ti.getTurns().headSet(dayChangeTurn),
dayChangeTurn.getAreaName());
final TurnInterval turnsAfterDayChange = new TurnInterval(
ti.getTurns().tailSet(dayChangeTurn),
dayChangeTurn.getAreaName());
this.printTurnIntervalContents(turnsBeforeDayChange,
currentDayNumber);
final int currentStringLenght = this.log.length();
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
this.log.delete(currentStringLenght, this.log.length());
// Consumables usage or pulls that happened nominally on
// the
// last turn before the day change, but were actually
// done
// on the next day.
this.printCurrentConsumables(
turnsBeforeDayChange.getConsumablesUsed(),
currentDayNumber);
this.printCurrentPulls(currentDayNumber,
turnsBeforeDayChange.getEndTurn());
this.log.append(TextLogCreator.NEW_LINE);
this.printTurnIntervalContents(turnsAfterDayChange,
currentDayNumber);
}
} else {
final int currentStringLenght = this.log.length();
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
this.log.delete(currentStringLenght, this.log.length());
this.printTurnIntervalContents(ti, currentDayNumber);
}
} else {
this.printTurnIntervalContents(ti, currentDayNumber);
}
turnRundown.add(this.log.toString());
this.log.delete(0, this.log.length());
}
return turnRundown;
}
/**
* Creates a parsed ascension log in a style similar to the format used by
* the AFH parser.
*
* @param logData
* The LogDataHolder from which the ascension log should be
* created.
* @param ascensionStartDate
* The real-time start date of the ascension as saved by
* KolMafia.
*/
private String createTextLog(final LogDataHolder logData,
final int ascensionStartDate) {
this.currentFamChange = this.familiarChangeIter.hasNext() ? this.familiarChangeIter
.next() : null;
this.currentPull = this.pullIter.hasNext() ? this.pullIter.next()
: null;
this.currentHuntedCombat = this.huntedCombatIter.hasNext() ? this.huntedCombatIter
.next() : null;
this.currentDisintegratedCombat = this.disintegratedCombatIter
.hasNext() ? this.disintegratedCombatIter.next() : null;
// Level 1 can be skipped.
this.levelIter.next();
this.nextLevel = this.levelIter.hasNext() ? this.levelIter.next()
: null;
// Day 1 day change is handled differently and can be ignored here.
final Iterator<DayChange> dayChangeIter = logData.getDayChanges()
.iterator();
DayChange nextDayChange = dayChangeIter.next();
nextDayChange = dayChangeIter.hasNext() ? dayChangeIter.next()
: TextLogCreator.NO_DAY_CHANGE;
int currentDayNumber = 1;
// Add the log file header.
this.write("NEW " + logData.getCharacterClass() + " ASCENSION STARTED "
+ ascensionStartDate + TextLogCreator.NEW_LINE);
this.write("------------------------------" + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
this.write(this.logAdditionsMap.get("logHeaderStart"));
this.write("This log was created by the Ascension Log Visualizer "
+ Settings.getSettingString("Version") + "."
+ TextLogCreator.NEW_LINE);
this.write("The basic idea and the format of this parser have been burrowed from the AFH MafiaLog Parser by VladimirPootin and QuantumNightmare."
+ TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
this.write(this.logAdditionsMap.get("logHeaderEnd"));
this.write(this.logAdditionsMap.get("dayChangeLineStart"));
this.write("===Day 1===");
this.write(this.logAdditionsMap.get("dayChangeLineEnd"));
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
for (final TurnInterval ti : logData.getTurnsSpent()) {
if (!nextDayChange.equals(TextLogCreator.NO_DAY_CHANGE)
&& (ti.getEndTurn() >= nextDayChange.getTurnNumber())) {
if (ti.getEndTurn() == nextDayChange.getTurnNumber()) {
this.printTurnIntervalContents(ti, currentDayNumber);
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
// Consumables usage or pulls that happened nominally on the
// last turn before the day change, but were actually done
// on the next day.
this.printCurrentConsumables(ti.getConsumablesUsed(),
currentDayNumber);
this.printCurrentPulls(currentDayNumber, ti.getEndTurn());
} else if (ti.getStartTurn() < nextDayChange.getTurnNumber()) {
SingleTurn dayChangeTurn = null;
for (final SingleTurn st : ti.getTurns()) {
if (st.getTurnNumber() > nextDayChange.getTurnNumber()) {
dayChangeTurn = st;
break;
}
}
if (dayChangeTurn != null) {
final TurnInterval turnsBeforeDayChange = new TurnInterval(
ti.getTurns().headSet(dayChangeTurn),
dayChangeTurn.getAreaName());
final TurnInterval turnsAfterDayChange = new TurnInterval(
ti.getTurns().tailSet(dayChangeTurn),
dayChangeTurn.getAreaName());
turnsAfterDayChange.incrementSuccessfulFreeRunaways(ti
.getFreeRunaways()
.getNumberOfSuccessfulRunaways());
this.printTurnIntervalContents(turnsBeforeDayChange,
currentDayNumber);
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
// Consumables usage or pulls that happened nominally on
// the
// last turn before the day change, but were actually
// done
// on the next day.
this.printCurrentConsumables(
turnsBeforeDayChange.getConsumablesUsed(),
currentDayNumber);
this.printCurrentPulls(currentDayNumber,
turnsBeforeDayChange.getEndTurn());
this.printTurnIntervalContents(turnsAfterDayChange,
currentDayNumber);
// Print the notes from the actual interval.
this.printNotes(ti);
}
} else {
final Pair<Integer, DayChange> newDayChangeData = this
.printDayChanges(logData, ti.getEndTurn(),
nextDayChange, dayChangeIter);
currentDayNumber = newDayChangeData.getVar1();
nextDayChange = newDayChangeData.getVar2();
this.printTurnIntervalContents(ti, currentDayNumber);
}
} else {
this.printTurnIntervalContents(ti, currentDayNumber);
}
}
this.write(TextLogCreator.NEW_LINE + "Turn rundown finished!");
this.write(this.logAdditionsMap.get("turnRundownEnd"));
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
this.printLogSummaries(logData);
return this.log.toString();
}
/**
* Prints all day changes that occurred and returns the new current day
* number and the next day change. If no day change occurred, the old values
* will be returned.
*/
private Pair<Integer, DayChange> printDayChanges(
final LogDataHolder logData, final int currentTurnNumber,
DayChange nextDayChange, final Iterator<DayChange> dayChangeIter) {
int currentDayNumber = nextDayChange.getDayNumber() - 1;
while (!nextDayChange.equals(TextLogCreator.NO_DAY_CHANGE)
&& (currentTurnNumber >= nextDayChange.getTurnNumber())) {
final PlayerSnapshot currentSnapshot = logData
.getFirstPlayerSnapshotAfterTurn(nextDayChange
.getTurnNumber());
this.write(TextLogCreator.NEW_LINE);
this.write(this.logAdditionsMap.get("dayChangeLineStart"));
this.write(nextDayChange.toString());
this.write(this.logAdditionsMap.get("dayChangeLineEnd"));
if (currentSnapshot != null) {
this.write(TextLogCreator.NEW_LINE);
this.write(TextLogCreator.ADVENTURES_LEFT_STRING);
this.write(currentSnapshot.getAdventuresLeft());
this.write(TextLogCreator.NEW_LINE);
this.write(TextLogCreator.CURRENT_MEAT_STRING);
this.write(currentSnapshot.getCurrentMeat());
}
this.write(TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE);
currentDayNumber = nextDayChange.getDayNumber();
nextDayChange = dayChangeIter.hasNext() ? dayChangeIter.next()
: TextLogCreator.NO_DAY_CHANGE;
}
return Pair.of(currentDayNumber, nextDayChange);
}
/**
* Prints all pulls from the given day up to the given turn number.
*/
private void printCurrentPulls(final int currentDayNumber,
final int currentTurnNumber) {
while ((this.currentPull != null)
&& (currentTurnNumber >= this.currentPull.getTurnNumber())) {
// Only pulls of the current day should be added here.
if (this.currentPull.getDayNumber() > currentDayNumber) {
break;
}
this.write(TextLogCreator.PULL_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(this.currentPull.getTurnNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write("pulled");
this.write(UsefulPatterns.WHITE_SPACE);
this.write(this.logAdditionsMap.get("pullStart"));
this.write(this.currentPull.getAmount());
this.write(UsefulPatterns.WHITE_SPACE);
this.write(this.currentPull.getItemName());
this.write(this.logAdditionsMap.get("pullEnd"));
this.write(TextLogCreator.NEW_LINE);
this.currentPull = this.pullIter.hasNext() ? this.pullIter.next()
: null;
}
}
/**
* Prints all consumables from the given day.
*/
private void printCurrentConsumables(
final Collection<Consumable> consumables, final int currentDayNumber) {
for (final Consumable c : consumables) {
if (c.getDayNumberOfUsage() == currentDayNumber) {
if ((c.getAdventureGain() > 0)
|| !c.getStatGain().isAllStatsZero()) {
this.write(TextLogCreator.CONSUMABLE_PREFIX);
if (c.getConsumableVersion() == ConsumableVersion.FOOD) {
this.write("Ate ");
} else if (c.getConsumableVersion() == ConsumableVersion.BOOZE) {
this.write("Drank ");
} else {
this.write("Used ");
}
this.write(this.logAdditionsMap.get("consumableStart"));
this.write(c.getAmount());
this.write(UsefulPatterns.WHITE_SPACE);
this.write(c.getName());
this.write(this.logAdditionsMap.get("consumableEnd"));
if ((c.getAdventureGain() > 0)
|| (c.getConsumableVersion() == ConsumableVersion.FOOD)
|| (c.getConsumableVersion() == ConsumableVersion.BOOZE)) {
this.write(UsefulPatterns.WHITE_SPACE);
this.write(UsefulPatterns.ROUND_BRACKET_OPEN);
this.write(c.getAdventureGain());
this.write(UsefulPatterns.WHITE_SPACE);
this.write("adventures gained");
this.write(UsefulPatterns.ROUND_BRACKET_CLOSE);
}
this.write(UsefulPatterns.WHITE_SPACE);
this.write(this.logAdditionsMap.get("statgainStart"));
this.write(c.getStatGain().toString());
this.write(this.logAdditionsMap.get("statgainEnd"));
this.write(TextLogCreator.NEW_LINE);
}
}
}
}
/**
* Prints the notes contained inside the given turn interval. If the
* interval contains no notes, this method won't print anything.
*/
private void printNotes(final TurnInterval ti) {
if (ti.getNotes().length() > 0) {
this.write(this.logAdditionsMap.get("notesStart"));
this.write(ti.getNotes().replaceAll("[\r\n]|\r\n",
TextLogCreator.NEW_LINE));
this.write(this.logAdditionsMap.get("notesEnd"));
this.write(TextLogCreator.NEW_LINE);
}
}
private void printItemAcquisitionStartString(final int turnNumber) {
this.write(TextLogCreator.ITEM_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(turnNumber);
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(TextLogCreator.ITEM_MIDDLE_STRING);
}
/**
* @param ti
* The turn interval whose contents should be printed.
*/
private void printTurnIntervalContents(final TurnInterval ti,
final int currentDayNumber) {
this.write(this.logAdditionsMap.get("turnStart"));
this.write(UsefulPatterns.SQUARE_BRACKET_OPEN);
if (ti.getTotalTurns() > 1) {
this.write(ti.getStartTurn() + 1);
this.write(UsefulPatterns.MINUS);
}
this.write(ti.getEndTurn());
this.write(UsefulPatterns.SQUARE_BRACKET_CLOSE);
this.write(this.logAdditionsMap.get("turnEnd"));
this.write(UsefulPatterns.WHITE_SPACE);
this.write(ti.getAreaName());
this.write(UsefulPatterns.WHITE_SPACE);
this.write(this.logAdditionsMap.get("statgainStart"));
this.write(ti.getStatGain().toString());
this.write(this.logAdditionsMap.get("statgainEnd"));
this.write(TextLogCreator.NEW_LINE);
for (final SingleTurn st : ti.getTurns()) {
if (DataTablesHandler.isSemirareEncounter(st)) {
this.write(TextLogCreator.SEMIRARE_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(st.getTurnNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(TextLogCreator.SEMIRARE_MIDDLE_STRING);
this.write(this.logAdditionsMap.get("specialEncounterStart"));
this.write(st.getEncounterName());
this.write(this.logAdditionsMap.get("specialEncounterEnd"));
this.write(TextLogCreator.NEW_LINE);
}
if (DataTablesHandler.isBadMoonEncounter(st)) {
this.write(TextLogCreator.BAD_MOON_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(st.getTurnNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(TextLogCreator.BAD_MOON_MIDDLE_STRING);
this.write(this.logAdditionsMap.get("specialEncounterStart"));
this.write(st.getEncounterName());
this.write(this.logAdditionsMap.get("specialEncounterEnd"));
this.write(TextLogCreator.NEW_LINE);
}
final List<Item> importantItems = new ArrayList<>();
for (final Item i : st.getDroppedItems()) {
final String itemName = i.getName().toLowerCase(Locale.ENGLISH);
if (DataTablesHandler.isImportantItem(itemName)) {
importantItems.add(i);
}
if (this.localeOnetimeItemsSet.contains(itemName)) {
importantItems.add(i);
this.localeOnetimeItemsSet.remove(itemName);
}
}
final Iterator<Item> aquiredItemsIter = importantItems.iterator();
if (aquiredItemsIter.hasNext()) {
this.printItemAcquisitionStartString(st.getTurnNumber());
int itemCounter = 0;
while (aquiredItemsIter.hasNext()) {
final Item currentItem = aquiredItemsIter.next();
for (int i = currentItem.getAmount(); i > 0; i--) {
this.write(this.logAdditionsMap.get("itemStart"));
this.write(currentItem.getName());
this.write(this.logAdditionsMap.get("itemEnd"));
itemCounter++;
if ((aquiredItemsIter.hasNext() || (i > 1))
&& (itemCounter >= 4)) {
this.write(TextLogCreator.NEW_LINE);
this.printItemAcquisitionStartString(st
.getTurnNumber());
itemCounter = 0;
} else if (i > 1) {
this.write(TextLogCreator.COMMA);
}
}
if (aquiredItemsIter.hasNext() && (itemCounter != 0)) {
this.write(TextLogCreator.COMMA);
}
}
this.write(TextLogCreator.NEW_LINE);
}
}
this.printCurrentConsumables(ti.getConsumablesUsed(), currentDayNumber);
this.printCurrentPulls(currentDayNumber, ti.getEndTurn());
while ((this.currentHuntedCombat != null)
&& (ti.getEndTurn() >= this.currentHuntedCombat.getNumber())) {
this.write(TextLogCreator.HUNTED_COMBAT_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(this.currentHuntedCombat.getNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(TextLogCreator.HUNTED_COMBAT_MIDDLE_STRING);
this.write(this.logAdditionsMap.get("huntedStart"));
this.write(this.currentHuntedCombat.getData());
this.write(this.logAdditionsMap.get("huntedEnd"));
this.write(TextLogCreator.NEW_LINE);
this.currentHuntedCombat = this.huntedCombatIter.hasNext() ? this.huntedCombatIter
.next() : null;
}
while ((this.currentDisintegratedCombat != null)
&& (ti.getEndTurn() >= this.currentDisintegratedCombat
.getNumber())) {
this.write(TextLogCreator.DISINTEGRATED_COMBAT_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(this.currentDisintegratedCombat.getNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(TextLogCreator.DISINTEGRATED_COMBAT_MIDDLE_STRING);
this.write(this.logAdditionsMap.get("yellowRayStart"));
this.write(this.currentDisintegratedCombat.getData());
this.write(this.logAdditionsMap.get("yellowRayEnd"));
this.write(TextLogCreator.NEW_LINE);
this.currentDisintegratedCombat = this.disintegratedCombatIter
.hasNext() ? this.disintegratedCombatIter.next() : null;
}
while ((this.currentFamChange != null)
&& (ti.getEndTurn() >= this.currentFamChange.getTurnNumber())) {
this.write(TextLogCreator.FAMILIAR_CHANGE_PREFIX);
this.write(TextLogCreator.OPENING_TURN_BRACKET);
this.write(this.currentFamChange.getTurnNumber());
this.write(TextLogCreator.CLOSING_TURN_BRACKET);
this.write(this.logAdditionsMap.get("familiarStart"));
this.write(this.currentFamChange.getFamiliarName());
this.write(this.logAdditionsMap.get("familiarEnd"));
this.write(TextLogCreator.NEW_LINE);
this.currentFamChange = this.familiarChangeIter.hasNext() ? this.familiarChangeIter
.next() : null;
}
final FreeRunaways freeRunaways = ti.getFreeRunaways();
if (freeRunaways.getNumberOfAttemptedRunaways() > 0) {
this.write(this.logAdditionsMap.get("runawayStart"));
this.write(TextLogCreator.FREE_RUNAWAYS_PREFIX);
this.write(freeRunaways.getNumberOfSuccessfulRunaways());
this.write(UsefulPatterns.WHITE_SPACE);
this.write("/");
this.write(UsefulPatterns.WHITE_SPACE);
this.write(freeRunaways.getNumberOfAttemptedRunaways());
this.write(UsefulPatterns.WHITE_SPACE);
this.write("free retreats");
this.write(this.logAdditionsMap.get("runawayEnd"));
this.write(TextLogCreator.NEW_LINE);
}
while ((this.nextLevel != null)
&& (ti.getEndTurn() >= this.nextLevel.getLevelReachedOnTurn())) {
final int musStat = (int) Math.sqrt(this.nextLevel
.getStatsAtLevelReached().mus);
final int mystStat = (int) Math.sqrt(this.nextLevel
.getStatsAtLevelReached().myst);
final int moxStat = (int) Math.sqrt(this.nextLevel
.getStatsAtLevelReached().mox);
this.write(this.logAdditionsMap.get("levelStart"));
this.write(TextLogCreator.LEVEL_CHANGE_PREFIX);
this.write(this.nextLevel.getLevelNumber());
this.write(" (Turn ");
this.write(this.nextLevel.getLevelReachedOnTurn());
this.write(")! (");
this.write(musStat);
this.write("/");
this.write(mystStat);
this.write("/");
this.write(moxStat);
this.write(UsefulPatterns.ROUND_BRACKET_CLOSE);
this.write(this.logAdditionsMap.get("levelEnd"));
this.write(TextLogCreator.NEW_LINE);
this.nextLevel = this.levelIter.hasNext() ? this.levelIter.next()
: null;
}
if (this.isShowNotes) {
this.printNotes(ti);
}
}
private void printLogSummaries(final LogDataHolder logData) {
// Turns spent per area summary
this.write("ADVENTURES" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dn : logData.getLogSummary()
.getTurnsPerArea()) {
this.write(dn.getData());
this.write(": ");
this.write(dn.getNumber());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Quest Turns summary
this.write("QUEST TURNS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
this.write("Mosquito Larva: "
+ logData.getLogSummary().getQuestTurncounts()
.getMosquitoQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Opening the Hidden Temple: "
+ logData.getLogSummary().getQuestTurncounts()
.getTempleOpeningTurns() + TextLogCreator.NEW_LINE);
this.write("Tavern quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getTavernQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Bat quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getBatQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Cobb's Knob quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getKnobQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Friars' part 1: "
+ logData.getLogSummary().getQuestTurncounts()
.getFriarsQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Defiled Cyrpt quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getCyrptQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Trapzor quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getTrapzorQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Orc Chasm quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getChasmQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Airship: "
+ logData.getLogSummary().getQuestTurncounts()
.getAirshipQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Giant's Castle: "
+ logData.getLogSummary().getQuestTurncounts()
.getCastleQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Opening the Ballroom: "
+ logData.getLogSummary().getQuestTurncounts()
.getBallroomOpeningTurns() + TextLogCreator.NEW_LINE);
this.write("Pirate quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getPirateQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Black Forest quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getBlackForrestQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Desert Oasis quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getDesertOasisQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Spookyraven quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getSpookyravenQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Hidden City quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getTempleCityQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Palindome quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getPalindomeQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Pyramid quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getPyramidQuestTurns() + TextLogCreator.NEW_LINE);
this.write("Starting the War: "
+ logData.getLogSummary().getQuestTurncounts()
.getWarIslandOpeningTurns() + TextLogCreator.NEW_LINE);
this.write("War Island quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getWarIslandQuestTurns() + TextLogCreator.NEW_LINE);
this.write("DoD quest: "
+ logData.getLogSummary().getQuestTurncounts()
.getDodQuestTurns() + TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Pulls summary
this.write("PULLS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
final DataCounter<String> pullsCounter = new DataCounter<>(
(int) (logData.getPulls().size() * 1.4) + 1);
for (final Pull p : logData.getPulls()) {
pullsCounter.addDataElement(p.getItemName(), p.getAmount());
}
final List<DataNumberPair<String>> pulls = pullsCounter
.getCountedData();
// ordered from highest to lowest amount
Collections.sort(pulls, new Comparator<DataNumberPair<String>>() {
@Override
public int compare(final DataNumberPair<String> o1,
final DataNumberPair<String> o2) {
return o2.compareTo(o1);
}
});
for (final DataNumberPair<String> dn : pulls) {
this.write("Pulled ");
this.write(dn.getNumber());
this.write(" ");
this.write(dn.getData());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Level summary
this.write("LEVELS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
final NumberFormat formatter = NumberFormat
.getNumberInstance(Locale.ENGLISH);
formatter.setMaximumFractionDigits(1);
formatter.setMinimumFractionDigits(1);
LevelData lastLevel = null;
for (final LevelData ld : logData.getLevels()) {
final int turnDifference = lastLevel != null ? ld
.getLevelReachedOnTurn()
- lastLevel.getLevelReachedOnTurn() : 0;
final double statsPerTurn = lastLevel != null ? lastLevel
.getStatGainPerTurn() : 0;
final int combatTurns = lastLevel != null ? lastLevel
.getCombatTurns() : 0;
final int noncombatTurns = lastLevel != null ? lastLevel
.getNoncombatTurns() : 0;
final int otherTurns = lastLevel != null ? lastLevel
.getOtherTurns() : 0;
this.write(ld.toString());
this.write(TextLogCreator.COMMA);
this.write(turnDifference);
this.write(" from last level. (");
this.write(formatter.format(statsPerTurn));
this.write(" substats / turn)");
this.write(TextLogCreator.NEW_LINE);
this.write(" Combats: ");
this.write(combatTurns);
this.write(TextLogCreator.NEW_LINE);
this.write(" Noncombats: ");
this.write(noncombatTurns);
this.write(TextLogCreator.NEW_LINE);
this.write(" Other: ");
this.write(otherTurns);
this.write(TextLogCreator.NEW_LINE);
lastLevel = ld;
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
final int totalTurns = logData.getTurnsSpent().last().getEndTurn();
this.write("Total COMBATS: "
+ logData.getLogSummary().getTotalTurnsCombat()
+ " ("
+ (Math.round((logData.getLogSummary().getTotalTurnsCombat() * 1000.0)
/ totalTurns) / 10.0) + "%)" + TextLogCreator.NEW_LINE);
this.write("Total NONCOMBATS: "
+ logData.getLogSummary().getTotalTurnsNoncombat()
+ " ("
+ (Math.round((logData.getLogSummary().getTotalTurnsNoncombat() * 1000.0)
/ totalTurns) / 10.0) + "%)" + TextLogCreator.NEW_LINE);
this.write("Total OTHER: "
+ logData.getLogSummary().getTotalTurnsOther()
+ " ("
+ (Math.round((logData.getLogSummary().getTotalTurnsOther() * 1000.0)
/ totalTurns) / 10.0) + "%)" + TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Stats summary
this.write("STATS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
final Statgain totalStats = logData.getLogSummary().getTotalStatgains();
final Statgain combatStats = logData.getLogSummary()
.getCombatsStatgains();
final Statgain noncombatStats = logData.getLogSummary()
.getNoncombatsStatgains();
final Statgain otherStats = logData.getLogSummary()
.getOthersStatgains();
final Statgain foodStats = logData.getLogSummary()
.getFoodConsumablesStatgains();
final Statgain boozeStats = logData.getLogSummary()
.getBoozeConsumablesStatgains();
final Statgain usingStats = logData.getLogSummary()
.getUsedConsumablesStatgains();
this.write(" \tMuscle\tMyst\tMoxie" + TextLogCreator.NEW_LINE);
this.write("Totals: \t" + totalStats.mus + "\t" + totalStats.myst
+ "\t" + totalStats.mox + TextLogCreator.NEW_LINE);
this.write("Combats:\t" + combatStats.mus + "\t" + combatStats.myst
+ "\t" + combatStats.mox + TextLogCreator.NEW_LINE);
this.write("Noncombats:\t" + noncombatStats.mus + "\t"
+ noncombatStats.myst + "\t" + noncombatStats.mox
+ TextLogCreator.NEW_LINE);
this.write("Others: \t" + otherStats.mus + "\t" + otherStats.myst
+ "\t" + otherStats.mox + TextLogCreator.NEW_LINE);
this.write("Eating: \t" + foodStats.mus + "\t" + foodStats.myst
+ "\t" + foodStats.mox + TextLogCreator.NEW_LINE);
this.write("Drinking:\t" + boozeStats.mus + "\t" + boozeStats.myst
+ "\t" + boozeStats.mox + TextLogCreator.NEW_LINE);
this.write("Using: \t" + usingStats.mus + "\t" + usingStats.myst
+ "\t" + usingStats.mox + TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
final List<AreaStatgains> areas = new ArrayList<>(logData
.getLogSummary().getAreasStatgains());
Collections.sort(areas, new Comparator<AreaStatgains>() {
@Override
public int compare(final AreaStatgains o1, final AreaStatgains o2) {
if (logData.getCharacterClass().getStatClass() == StatClass.MUSCLE) {
return o2.getStatgain().mus - o1.getStatgain().mus;
} else if (logData.getCharacterClass().getStatClass() == StatClass.MYSTICALITY) {
return o2.getStatgain().myst - o1.getStatgain().myst;
} else {
return o2.getStatgain().mox - o1.getStatgain().mox;
}
}
});
this.write("Top 10 mainstat gaining areas:" + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
for (int i = 0; (i < areas.size()) && (i < 10); i++) {
this.write(areas.get(i).toString());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// +Stat Breakdown summary
final List<StatgiverItem> statGivers = new ArrayList<>(20);
for (final Pair<String, Double> p : DataTablesHandler.getStatsItems()) {
statGivers.add(new StatgiverItem(p.getVar1(), p.getVar2()));
}
final StatgiverItem serpentineSword = new StatgiverItem(
"serpentine sword", 1.25);
final StatgiverItem snakeShield = new StatgiverItem("snake shield",
1.25);
final Iterator<LevelData> lvlIndex = logData.getLevels().iterator();
LevelData nextLvl = lvlIndex.hasNext() ? lvlIndex.next() : null;
int currentLvlNumber = 1;
for (final TurnInterval ti : logData.getTurnsSpent()) {
for (final SingleTurn st : ti.getTurns()) {
while ((nextLvl != null)
&& (nextLvl.getLevelReachedOnTurn() < st
.getTurnNumber())) {
currentLvlNumber = nextLvl.getLevelNumber();
nextLvl = lvlIndex.hasNext() ? lvlIndex.next() : null;
}
if (currentLvlNumber >= 13) {
break;
}
if (st.getTurnVersion() == TurnVersion.COMBAT) {
for (final StatgiverItem sgi : statGivers) {
sgi.incrementLvlStatgain(
currentLvlNumber,
st.getUsedEquipment().getNumberOfEquips(
sgi.getItemName()));
}
// Special cases
final int serpentineSwordEquips = st.getUsedEquipment()
.getNumberOfEquips(serpentineSword.getItemName());
serpentineSword.incrementLvlStatgain(currentLvlNumber,
serpentineSwordEquips);
if (serpentineSwordEquips == 1) {
snakeShield.incrementLvlStatgain(
currentLvlNumber,
st.getUsedEquipment().getNumberOfEquips(
snakeShield.getItemName()));
}
}
}
}
// Add special cases to list for text print out.
statGivers.add(serpentineSword);
statGivers.add(snakeShield);
// Sort item list from highest total stat gain to lowest.
Collections.sort(statGivers, new Comparator<StatgiverItem>() {
@Override
public int compare(final StatgiverItem o1, final StatgiverItem o2) {
return o2.getTotalStats() - o1.getTotalStats();
}
});
this.write("+STAT BREAKDOWN" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
this.write("Need to gain level (last is total): \t10\t39\t105\t231\t441\t759\t1209\t1815\t2601\t3591\t4809\t6279\t21904"
+ TextLogCreator.NEW_LINE);
for (final StatgiverItem sgi : statGivers) {
if (sgi.getTotalStats() > 0) {
this.write(sgi.toString());
this.write(TextLogCreator.NEW_LINE);
}
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Familiars summary
this.write("FAMILIARS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dn : logData.getLogSummary()
.getFamiliarUsage()) {
this.write(dn.getData());
this.write(" : ");
this.write(dn.getNumber());
this.write(" combat turns (");
this.write(String.valueOf(Math.round((dn.getNumber() * 1000.0)
/ logData.getLogSummary().getTotalTurnsCombat()) / 10.0));
this.write("%)");
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Semi-rares summary
this.write("SEMI-RARES" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dn : logData.getLogSummary()
.getSemirares()) {
this.write(dn.getNumber());
this.write(" : ");
this.write(dn.getData());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Hunted combats summary
this.write("HUNTED COMBATS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dn : logData.getLogSummary()
.getHuntedCombats()) {
this.write(dn.getNumber());
this.write(" : ");
this.write(dn.getData());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Disintegrated combats summary
this.write("HE-BOULDER YELLOW RAYS" + TextLogCreator.NEW_LINE
+ "----------" + TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dn : logData.getLogSummary()
.getDisintegratedCombats()) {
this.write(dn.getNumber());
this.write(" : ");
this.write(dn.getData());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Skills cast summary
this.write("CASTS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
for (final Skill s : logData.getLogSummary().getSkillsCast()) {
this.write(s.toString());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + "------------------"
+ TextLogCreator.NEW_LINE + "| Total Casts | "
+ logData.getLogSummary().getTotalAmountSkillCasts()
+ TextLogCreator.NEW_LINE + "------------------"
+ TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + "------------------"
+ TextLogCreator.NEW_LINE + "| Total MP Spent | "
+ logData.getLogSummary().getTotalMPUsed()
+ TextLogCreator.NEW_LINE + "------------------"
+ TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// MP summary
final MPGain mpGains = logData.getLogSummary().getMPGains();
this.write("MP GAINS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
this.write("Total mp gained: " + mpGains.getTotalMPGains()
+ TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
this.write("Inside Encounters: " + mpGains.getEncounterMPGain()
+ TextLogCreator.NEW_LINE);
this.write("Starfish Familiars: " + mpGains.getStarfishMPGain()
+ TextLogCreator.NEW_LINE);
this.write("Resting: " + mpGains.getRestingMPGain()
+ TextLogCreator.NEW_LINE);
this.write("Outside Encounters: " + mpGains.getOutOfEncounterMPGain()
+ TextLogCreator.NEW_LINE);
this.write("Consumables: " + mpGains.getConsumableMPGain()
+ TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Consumables summary
this.write("EATING AND DRINKING AND USING" + TextLogCreator.NEW_LINE
+ "----------" + TextLogCreator.NEW_LINE);
this.write("Adventures gained eating: "
+ logData.getLogSummary().getTotalTurnsFromFood()
+ TextLogCreator.NEW_LINE);
this.write("Adventures gained drinking: "
+ logData.getLogSummary().getTotalTurnsFromBooze()
+ TextLogCreator.NEW_LINE);
this.write("Adventures gained using: "
+ logData.getLogSummary().getTotalTurnsFromOther()
+ TextLogCreator.NEW_LINE);
this.write("Adventures gained rollover: "
+ logData.getLogSummary().getTotalTurnsFromRollover()
+ TextLogCreator.NEW_LINE);
this.write(TextLogCreator.NEW_LINE);
for (final Consumable c : logData.getLogSummary()
.getFoodConsumablesUsed()) {
this.write(c.toString());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE);
for (final Consumable c : logData.getLogSummary()
.getBoozeConsumablesUsed()) {
this.write(c.toString());
this.write(TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE);
for (final Consumable c : logData.getLogSummary()
.getSpleenConsumablesUsed()) {
if (c.getAdventureGain() > 0) {
this.write(c.toString());
this.write(TextLogCreator.NEW_LINE);
}
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Meat summary
this.write("MEAT" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
this.write("Total meat gained: "
+ logData.getLogSummary().getTotalMeatGain()
+ TextLogCreator.NEW_LINE);
this.write("Total meat spent: "
+ logData.getLogSummary().getTotalMeatSpent()
+ TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE);
for (final DataNumberPair<MeatGain> dnp : logData.getLogSummary()
.getMeatSummary().getAllLevelsMeatData()) {
this.write("Level " + dnp.getNumber() + UsefulPatterns.COLON
+ TextLogCreator.NEW_LINE);
this.write(" Meat gain inside Encounters: "
+ dnp.getData().encounterMeatGain + TextLogCreator.NEW_LINE);
this.write(" Meat gain outside Encounters: "
+ dnp.getData().otherMeatGain + TextLogCreator.NEW_LINE);
this.write(" Meat spent: " + dnp.getData().meatSpent
+ TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE + TextLogCreator.NEW_LINE
+ TextLogCreator.NEW_LINE);
// Bottlenecks summary
final List<DataNumberPair<String>> lostCombats = logData
.getLostCombats();
this.write("BOTTLENECKS" + TextLogCreator.NEW_LINE + "----------"
+ TextLogCreator.NEW_LINE);
this.write("Sewered "
+ logData.getLogSummary().getSewer().getTurnsSpent()
+ " times for "
+ logData.getLogSummary().getSewer().getTrinketsFound()
+ " trinkets" + TextLogCreator.NEW_LINE);
this.write("Spent "
+ logData.getLogSummary().get8BitRealm().getTurnsSpent()
+ " turns in the 8-Bit Realm" + TextLogCreator.NEW_LINE);
this.write("Fought "
+ logData.getLogSummary().get8BitRealm().getBloopersFound()
+ " bloopers" + TextLogCreator.NEW_LINE);
this.write("Fought "
+ logData.getLogSummary().get8BitRealm().getBulletsFound()
+ " bullet bills" + TextLogCreator.NEW_LINE);
this.write("Spent "
+ logData.getLogSummary().getGoatlet().getTurnsSpent()
+ " turns in the Goatlet" + TextLogCreator.NEW_LINE);
this.write("Fought "
+ logData.getLogSummary().getGoatlet().getDairyGoatsFound()
+ " dairy goats for "
+ logData.getLogSummary().getGoatlet().getCheeseFound()
+ " cheeses and "
+ logData.getLogSummary().getGoatlet().getMilkFound()
+ " glasses of milk" + TextLogCreator.NEW_LINE);
this.write("Number of lost combats: " + lostCombats.size()
+ TextLogCreator.NEW_LINE);
for (final DataNumberPair<String> dnp : lostCombats) {
this.write(" " + dnp + TextLogCreator.NEW_LINE);
}
this.write(TextLogCreator.NEW_LINE);
this.write("Free runaways: ");
this.write(logData.getLogSummary().getFreeRunaways().toString());
this.write(" overall");
this.write(TextLogCreator.NEW_LINE);
}
private void write(final String s) {
if (s != null) {
this.log.append(s);
} else {
this.log.append(UsefulPatterns.EMPTY_STRING);
}
}
private void write(final int i) {
this.log.append(i);
}
/**
* Enumeration to specify the wanted textual log output.
*/
public static enum TextualLogVersion {
TEXT_LOG, HTML_LOG, BBCODE_LOG;
}
}