/*
* Initiative - A role playing utility to track turns
* Copyright (C) 2002 Devon D Jones
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* PcgCombatant.java
*
* Created on January 16, 2002, 12:27 PM
*/
package gmgen.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Element;
import pcgen.base.lang.StringUtil;
import pcgen.cdom.base.Constants;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.PCAttribute;
import pcgen.cdom.enumeration.StringKey;
import pcgen.cdom.reference.CDOMSingleRef;
import pcgen.core.Domain;
import pcgen.core.Equipment;
import pcgen.core.Globals;
import pcgen.core.PCCheck;
import pcgen.core.PCClass;
import pcgen.core.PCStat;
import pcgen.core.PObject;
import pcgen.core.PlayerCharacter;
import pcgen.core.Skill;
import pcgen.core.analysis.SkillModifier;
import pcgen.core.analysis.SkillRankControl;
import pcgen.core.character.CharacterSpell;
import pcgen.core.display.CharacterDisplay;
import pcgen.core.display.DescriptionFormatting;
import pcgen.core.display.SkillDisplay;
import pcgen.core.spell.Spell;
import pcgen.pluginmgr.PCGenMessageHandler;
import pcgen.pluginmgr.messages.RequestOpenPlayerCharacterMessage;
import pcgen.util.Logging;
import pcgen.util.enumeration.View;
/**
*@author devon
*/
public class PcgCombatant extends Combatant
{
protected PlayerCharacter pc;
private CharacterDisplay display;
protected PcRenderer renderer;
private float crAdj = 0;
private final PCGenMessageHandler messageHandler;
/**
* Creates new PcgCombatant
*
*@param pc PCGen pc that this combatant represents
*/
public PcgCombatant(PlayerCharacter pc, PCGenMessageHandler mh)
{
messageHandler = mh;
this.pc = pc;
display = pc.getDisplay();
this.init = new PcgSystemInitiative(pc);
PCStat stat = Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(PCStat.class, "CON");
this.hitPoints = new SystemHP(new SystemAttribute("Constitution",
pc.getTotalStatFor(stat)), pc.hitPoints(), pc
.hitPoints());
setCombatantType("PC");
}
/**
* Constructor for the PcgCombatant object
*
*@param pc PCGen pc that this combatant represents
*@param type PC/Enemy/Ally/Non Combatant
*/
public PcgCombatant(PlayerCharacter pc, String type, PCGenMessageHandler mh)
{
this(pc, mh);
setCombatantType(type);
}
public PcgCombatant(Element combatant, PCGenMessageHandler comp, PCGenMessageHandler mh)
{
messageHandler = mh;
try
{
String pcgFilename = combatant.getChild("PCG").getAttribute("file")
.getValue();
if (StringUtils.isNotBlank(pcgFilename))
{
File pcgFile =
new File(pcgFilename);
RequestOpenPlayerCharacterMessage msg =
new RequestOpenPlayerCharacterMessage(comp, pcgFile, true);
messageHandler.handleMessage(msg);
this.pc = msg.getPlayerCharacter();
}
if (pc == null)
{
pc = new PlayerCharacter();
pc.setName(combatant.getAttributeValue("name"));
}
this.display = pc.getDisplay();
this.init = new PcgSystemInitiative(pc);
PCStat stat = Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(PCStat.class, "CON");
this.hitPoints =
new SystemHP(new SystemAttribute("Constitution", pc.getTotalStatFor(stat)), pc.hitPoints(), pc
.hitPoints());
setStatus(State.getState(combatant.getAttribute("status").getValue()));
setCombatantType(combatant.getAttribute("type").getValue());
init.setBonus(combatant.getChild("Initiative")
.getAttribute("bonus").getIntValue());
try
{
init.setCurrentInitiative(combatant.getChild("Initiative")
.getAttribute("current").getIntValue());
}
catch (Exception e)
{
//Not necessarily set
}
hitPoints.setMax(combatant.getChild("HitPoints")
.getAttribute("max").getIntValue());
hitPoints.setCurrent(combatant.getChild("HitPoints").getAttribute(
"current").getIntValue());
hitPoints.setSubdual(combatant.getChild("HitPoints").getAttribute(
"subdual").getIntValue());
hitPoints.setState(State.getState(combatant.getChild("HitPoints").getAttribute(
"state").getValue()));
}
catch (Exception e)
{
Logging.errorPrint("Initiative", e);
}
}
/**
* Adjusts the CR for this combatant
*
* TODO I'm not sure that it should be current - the newly calculated or an entire replacement
* It appears it's called from 2 different places, one which adjusts the CR, the other ?
*
* In the case of adjusting the CR the calculation is wrong anyhow, surely it should be calculated
* - the value passed in?
*
* @param cr new CR value
*/
@Override
public void setCR(float cr)
{
this.crAdj = cr - pc.getDisplay().calcCR();
}
/**
* Gets the CR for the character
*
* @return CR
*/
@Override
public float getCR()
{
return pc.getDisplay().calcCR() + crAdj;
}
/**
* Sets the name of the character
*
*@param name The new name
*/
@Override
public void setName(String name)
{
pc.setName(name);
}
/**
* Gets the name of the PC
*
*@return The name
*/
@Override
public String getName()
{
return display.getName();
}
/**
* Gets the PCGen PC of the PcgCombatant object
*
*@return The PCGen PC
*/
public PlayerCharacter getPC()
{
return pc;
}
/**
* Sets the player's name of the PcgCombatant object
*
*@param player The new player's name
*/
public void setPlayer(String player)
{
pc.setPCAttribute(PCAttribute.PLAYERSNAME, player);
}
/**
* Gets the player's name of the PcgCombatant object
*
*@return The player's name
*/
@Override
public String getPlayer()
{
return display.getPlayersName();
}
@Override
public Element getSaveElement()
{
Element retElement = new Element("PcgCombatant");
Element initiative = new Element("Initiative");
Element hp = new Element("HitPoints");
Element pcg = new Element("PCG");
pcg.setAttribute("file", pc.getFileName() + "");
retElement.addContent(pcg);
initiative.setAttribute("bonus", init.getModifier() + "");
if (init.getCurrentInitiative() > 0)
{
initiative
.setAttribute("current", init.getCurrentInitiative() + "");
}
retElement.addContent(initiative);
hp.setAttribute("current", hitPoints.getCurrent() + "");
hp.setAttribute("subdual", hitPoints.getSubdual() + "");
hp.setAttribute("max", hitPoints.getMax() + "");
hp.setAttribute("state", hitPoints.getState() + "");
retElement.addContent(hp);
retElement.setAttribute("name", getName());
retElement.setAttribute("player", getPlayer());
retElement.setAttribute("status", getStatus().name());
retElement.setAttribute("type", getCombatantType());
return retElement;
}
/**
* Set the experience value for this character
*
*@param experience Experience value
*/
@Override
public void setXP(int experience)
{
pc.setXP(experience);
}
/**
* Gets the experience value for the character
*
*@return Experience value
*/
@Override
public int getXP()
{
return display.getXP();
}
/**
* changes the value of a table field in the backend data set
*
*@param columnOrder A list of columns in order for the table
*@param colNumber What column number has been edited
*@param data The new value for the field
*/
@Override
public void editRow(List<String> columnOrder, int colNumber, Object data)
{
String columnName = columnOrder.get(colNumber);
String strData = String.valueOf(data);
//Determine which row was edited
switch (columnName)
{
case "Name":
// Character's Name
setName(strData);
break;
case "Player":
// Player's Name
setPlayer(strData);
break;
case "Status":
// XML Combatant's Status
setStatus(State.getStateLocalised(strData));
break;
case "+":
{
// Initative bonus
Integer intData = Integer.valueOf(strData);
init.setBonus(intData.intValue());
break;
}
case "Init":
{
// Initative
Integer intData = Integer.valueOf(strData);
init.setCurrentInitiative(intData.intValue());
break;
}
case "#":
{
// Number (for tokens)
Integer intData = Integer.valueOf(strData);
setNumber(intData.intValue());
break;
}
case "HP":
{
// Current Hit Points
Integer intData = Integer.valueOf(strData);
hitPoints.setCurrent(intData.intValue());
break;
}
case "HP Max":
{
// Maximum Hit Points
Integer intData = Integer.valueOf(strData);
hitPoints.setMax(intData.intValue());
break;
}
case "Dur":
{
// Duration
Integer intData = Integer.valueOf(strData);
setDuration(intData.intValue());
break;
}
case "Type":
// Type
setCombatantType(strData);
break;
}
}
@Override
public String toHtmlString()
{
if (renderer == null)
{
renderer = new PcRenderer();
}
return renderer.getHtmlText();
}
protected class PcRenderer
{
protected String htmlString;
protected int serial = 0;
public PcRenderer()
{
// Do Nothing
}
/**
* <p>
* This sets the text of the JTextPane for the specified PC. It uses an
* output sheet template, specified by the templateName option; it uses
* {@code pcgen.io.ExportHandler} to transform the template file
* into an StringWriter, and then sets the text of the text pane as html.
* This allows us easy access to changing the content or format of the stat
* block, and also allows us to easily use a different output format if
* necessary.
* </p>
* @return HTML text
*/
public String getHtmlText()
{
if (serial < pc.getSerial() || htmlString == null)
{
StringBuilder statBuf = new StringBuilder();
statBuf.append("<html>");
statBuf.append(getStatBlockHeader());
statBuf.append("<body class='Normal' lang='EN-US'>");
statBuf.append(getStatBlockTitle());
statBuf.append(getStatBlockCore());
statBuf.append("<DIV style='MARGIN: 0px 10px'>");
statBuf.append(getStatBlockLineSkills());
statBuf.append(getStatBlockLinePossessions());
try
{
statBuf.append(getStatBlockLineSpells());
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
statBuf.append("</DIV>");
statBuf.append("<br>");
statBuf.append("</html>");
serial = pc.getSerial();
htmlString = statBuf.toString();
}
return htmlString;
//TODO: As Outputsheet is *always* false, I commented out the unreachable code.
//TODO: I will delete it sometime after january unless this has been changed. JK 2003-12-28
//TODO: This was changed this way so we could easily re-enable the bypassed code if we managed
// to get the ExportHandler stuff working any faster. RML 2004-01-30
//boolean outputSheet = false;
//if(outputSheet) {
// String baseDir = SettingsHandler.getGmgenPluginDir().toString();
// String initiativeDir =
// SettingsHandler.getGMGenOption(InitiativePlugin.LOG_NAME + ".initiativeDir", "Initiative");
// String templateName = SettingsHandler.getGMGenOption(InitiativePlugin.LOG_NAME + ".templateName", "csheet_gmgen_statblock.htm");
// File template = new File(baseDir + File.separator + initiativeDir + File.separator + templateName);
// ExportHandler export = new ExportHandler(template);
// StringWriter sWriter = new StringWriter();
// export.write(pc,new BufferedWriter(sWriter));
// aPane.setEditorKit(aPane.getEditorKitForContentType("text/html"));
// aPane.setText(sWriter.toString());
//}
//else {
//int bonus = 0;
}
protected String getStatBlockCore()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
statBuf.append("<font class='type'>CR</font> ");
statBuf.append(pcOut.getCR()); //|CR|
statBuf.append("; ");
statBuf.append("<font class='type'>Size</font> ");
statBuf.append(pcOut.getSize()); //|SIZE|
statBuf.append("; ");
statBuf.append("<font class='type'>Type</font> ");
statBuf.append(pcOut.getRaceType()); //|TYPE|
statBuf.append("; ");
statBuf.append("<font class='type'>HD</font> ");
statBuf.append(pcOut.getHitDice()); //|HITDICE|
statBuf.append("; ");
statBuf.append("<font class='type'>hp</font> ");
statBuf.append(pcOut.getHitPoints()); //|HP|
statBuf.append("; ");
statBuf
.append("<font class='type'>Init</font> <font class='highlight'>");
statBuf.append(pcOut.getInitTotal()); //|INITIATIVEMOD|
statBuf.append("</font> (");
statBuf.append(pcOut.getInitStatMod()); //|STAT.1.MOD|
statBuf.append(" Dex, ");
statBuf.append(pcOut.getInitMiscMod()); //|INITIATIVEMISC|
statBuf.append(" Misc); ");
statBuf.append("<font class='type'>Spd</font> ");
statBuf.append(pcOut.getSpeed()); //|MOVEMENT|
statBuf.append(";<br>");
statBuf
.append("<font class='type'>AC</font> <font class='highlight'>");
statBuf.append(pcOut.getAC()); //|AC.Total|
statBuf.append("</font> (flatfooted <font class='highlight'>");
statBuf.append(pcOut.getACFlatFooted()); //|AC.Flatfooted|
statBuf.append("</font>, touch <font class='highlight'>");
statBuf.append(pcOut.getACTouch()); //|AC.Touch|
statBuf.append("</font>);<br>");
statBuf
.append("<font class='type'>Melee:</font> <a href='attack:Melee\\");
statBuf.append(pcOut.getMeleeTotal()); //|ATTACK.MELEE.TOTAL|
statBuf.append("' class='highlight'>");
statBuf.append(pcOut.getMeleeTotal()); //|ATTACK.MELEE.TOTAL|
statBuf.append("</a>; ");
statBuf
.append("<font class='type'>Ranged:</font> <a href='attack:Ranged\\");
statBuf.append(pcOut.getRangedTotal()); //|ATTACK.RANGED.TOTAL|
statBuf.append("' class='highlight'>");
statBuf.append(pcOut.getRangedTotal()); //|ATTACK.RANGED.TOTAL|
statBuf.append("</a>; ");
statBuf.append("<font class='type'>Weapons:</font>");
List<Equipment> weaponList =
pc.getExpandedWeapons(Constants.MERGE_ALL);
for (int i = 0; i < weaponList.size(); i++)
{
Equipment eq = weaponList.get(i);
statBuf.append("<a href=" + '"' + "attack:");
statBuf.append(pcOut.getWeaponName(eq)); //|WEAPON.%weap.NAME|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponToHit(i)); //|WEAPON.%weap.TOTALHIT|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponRange(eq)); //|WEAPON.%weap.RANGE|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponType(eq)); //|WEAPON.%weap.TYPE|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponDamage(i)); //|WEAPON.%weap.DAMAGE|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponCritRange(i)); //|WEAPON.%weap.CRIT|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponCritMult(i)); //|WEAPON.%weap.MULT|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponHand(eq)); //|WEAPON.%weap.HAND|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponSize(eq)); //|WEAPON.%weap.SIZE|
statBuf.append("\\");
statBuf.append(pcOut.getWeaponSpecialProperties(eq)); //|WEAPON.%weap.SPROP|
statBuf.append('"' + " class=" + '"' + "dialog" + '"' + "> ");
statBuf.append(pcOut.getWeaponName(eq)); //|WEAPON.%weap.NAME|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponToHit(i)); //|WEAPON.%weap.TOTALHIT|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponRange(eq)); //|WEAPON.%weap.RANGE|
statBuf.append("/");
statBuf.append(pcOut.getWeaponType(eq)); //|WEAPON.%weap.TYPE|
statBuf.append(" (");
statBuf.append(pcOut.getWeaponDamage(i)); //|WEAPON.%weap.DAMAGE|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponCritRange(i)); //|WEAPON.%weap.CRIT|
statBuf.append("/x");
statBuf.append(pcOut.getWeaponCritMult(i)); //|WEAPON.%weap.MULT|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponHand(eq)); //|WEAPON.%weap.HAND|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponSize(eq)); //|WEAPON.%weap.SIZE|
statBuf.append(" ");
statBuf.append(pcOut.getWeaponSpecialProperties(eq)); //|WEAPON.%weap.SPROP|
statBuf.append(") </a> or ");
}
//Unarmed attack
statBuf.append("<a href=" + '"' + "attack:Unarmed\\");
statBuf.append(pcOut.getExportToken("WEAPONH.TOTALHIT")); //|WEAPONH.TOTALHIT|
statBuf.append("\\\\B\\");
statBuf.append(pcOut.getExportToken("WEAPONH.DAMAGE")); //|WEAPONH.DAMAGE|
statBuf.append("\\");
statBuf.append(pcOut.getExportToken("WEAPONH.CRIT")); //|WEAPONH.CRIT|
statBuf.append("\\");
statBuf.append(pcOut.getExportToken("WEAPONH.MULT")); //|WEAPONH.MULT|
statBuf.append('"' + " class=" + '"' + "dialog" + '"' + "> ");
statBuf.append("Unarmed ");
statBuf.append(pcOut.getExportToken("WEAPONH.TOTALHIT")); //|WEAPONH.TOTALHIT|
statBuf.append(" (");
statBuf.append(pcOut.getExportToken("WEAPONH.DAMAGE")); //|WEAPONH.DAMAGE|
statBuf.append(" ");
statBuf.append(pcOut.getExportToken("WEAPONH.CRIT")); //|WEAPONH.CRIT|
statBuf.append("/x");
statBuf.append(pcOut.getExportToken("WEAPONH.MULT")); //|WEAPONH.MULT|
statBuf.append(") </a> or ");
//End unarmed attack
//Grapple
statBuf.append("<a href=" + '"' + "attack:Grapple\\");
statBuf.append(pcOut.getExportToken("ATTACK.GRAPPLE.TOTAL")); //|WEAPONH.TOTALHIT|
statBuf.append("\\\\B\\");
statBuf.append(pcOut.getExportToken("WEAPONH.DAMAGE")); //|WEAPONH.DAMAGE|
statBuf.append("\\");
statBuf.append(pcOut.getExportToken("WEAPONH.CRIT")); //|WEAPONH.CRIT|
statBuf.append("\\");
statBuf.append(pcOut.getExportToken("WEAPONH.MULT")); //|WEAPONH.MULT|
statBuf.append('"' + " class=" + '"' + "dialog" + '"' + "> ");
statBuf.append("Grapple ");
statBuf.append(pcOut.getExportToken("ATTACK.GRAPPLE.TOTAL")); //|WEAPONH.TOTALHIT|
statBuf.append(" (");
statBuf.append(pcOut.getExportToken("WEAPONH.DAMAGE")); //|WEAPONH.DAMAGE|
statBuf.append(" ");
statBuf.append(pcOut.getExportToken("WEAPONH.CRIT")); //|WEAPONH.CRIT|
statBuf.append("/x");
statBuf.append(pcOut.getExportToken("WEAPONH.MULT")); //|WEAPONH.MULT|
statBuf.append(")</a>;<br>");
//End Grapple
statBuf.append("<font class='type'>SA:</font> ");
statBuf.append(pcOut.getSpecialAbilities()); //|SPECIALLIST|
int turnTimes =
pc.getVariableValue("TurnTimesUndead", "").intValue();
if (turnTimes > 0)
{
int turnDieNumber =
pc.getVariableValue("TurnDiceUndead", "").intValue();
int turnDieSize =
pc.getVariableValue("TurnDieSizeUndead", "").intValue();
int turnDamage =
pc.getVariableValue("TurnDamagePlusUndead", "")
.intValue();
int turnLevel =
pc.getVariableValue("TurnLevelUndead", "").intValue();
int turnCheck =
pc.getVariableValue("TurnCheckUndead", "").intValue();
statBuf
.append("; <font class='type'>Turn/Rebuke Undead:</font> Turning level "
+ "<a href="
+ '"'
+ "dice:Turn Undead (Max HD Affected)\\"
+ "max(min(max((ceil((1d20"
+ (turnCheck > 0 ? "+" : "")
+ turnCheck
+ ")/3)-4),-4),4)+"
+ turnLevel
+ ",0)"
+ '"'
+ " class="
+ '"'
+ "dialog"
+ '"'
+ "> "
+ turnLevel
+ "</a>, Turn Damage: "
+ "<a href="
+ '"'
+ "dice:Turn Damage (Total HD Affected)\\"
+ "max("
+ turnDieNumber
+ "d"
+ turnDieSize
+ (turnDamage > 0 ? "+" : "")
+ turnDamage
+ ",0)"
+ '"'
+ " class="
+ '"'
+ "dialog"
+ '"'
+ "> "
+ turnDieNumber
+ "d"
+ turnDieSize
+ (turnDamage > 0 ? "+" : "")
+ turnDamage
+ "</a>, "
+ turnTimes + "/day");
}
statBuf.append("; ");
statBuf.append("<font class='type'>Vision:</font> ");
statBuf.append(pcOut.getVision()); //|VISION|
statBuf.append(" ");
statBuf.append("<font class='type'>AL:</font> ");
statBuf.append(pcOut.getAlignmentShort()); //|ALIGNMENT.SHORT|
statBuf.append("; ");
statBuf
.append("<font class='type'>Sv:</font> ");
boolean firstChk = true;
for (PCCheck chk : Globals.getContext().getReferenceContext()
.getOrderSortedCDOMObjects(PCCheck.class))
{
if (!firstChk)
{
statBuf.append(", ");
}
firstChk = false;
statBuf.append(chk.getDisplayName());
statBuf.append(" <font class='highlight'>");
statBuf.append("<a href='save:").append(chk.getDisplayName()).append("\\");
statBuf.append(pc.calculateSaveBonus(chk, "TOTAL")); //|CHECK.FORTITUDE.TOTAL|
statBuf.append("' class='highlight'> ");
statBuf.append(pc.calculateSaveBonus(chk, "TOTAL")); //|CHECK.FORTITUDE.TOTAL|
statBuf.append("</a></font>");
}
statBuf.append(";<br>");
for (PCStat stat : pcOut.getUnmodifiableStatList())
{
String statAbb = stat.getKeyName();
if (display.isNonAbility(stat))
{
statBuf.append("<font class='type'>");
statBuf.append(statAbb); //|STAT.%stat.NAME|
statBuf.append("</font>");
statBuf.append("*"); //|STAT.%stat|
statBuf.append(" (");
statBuf.append("0"); //|STAT.%stat.MOD|
statBuf.append(") ");
}
else
{
statBuf.append("<font class='type'>");
statBuf.append(statAbb); //|STAT.%stat.NAME|
statBuf.append("</font> ");
statBuf.append(pcOut.getStat(stat)); //|STAT.%stat|
statBuf.append(" (");
statBuf.append("<a href='check:");
statBuf.append(statAbb); //|STAT.%stat.NAME|
statBuf.append("\\1d20");
statBuf.append(pcOut.getStatMod(stat)); //|STAT.%stat.MOD|
statBuf.append("' class='dialog'>");
statBuf.append(pcOut.getStatMod(stat)); //|STAT.%stat.MOD|
statBuf.append("</a>) ");
}
}
statBuf.append("</p>");
return statBuf.toString();
}
protected String getStatBlockHeader()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
statBuf.append("<head><title>");
statBuf.append(pcOut.getName()); //|NAME|
statBuf.append(" - ");
statBuf.append(display.getPlayersName()); //|PLAYERNAME|
statBuf.append("(");
statBuf.append(pc.getCostPool()); //|POOL.COST|
statBuf.append(" Points) in GMGEN Statblock Format");
statBuf.append("</title>");
statBuf.append("<style type='text/css'>");
statBuf.append("a:link {color: #006699}");
statBuf.append("a:visited {color: #006699}");
statBuf.append("a:hover {color: #006699}");
statBuf.append("a:active {color: #006699}");
statBuf.append(".type {color:#555555;font-weight:bold}");
statBuf.append(".highlight {color:#FF0000}");
statBuf.append(".dialog {color:#006699}");
statBuf.append("</style></head>");
return statBuf.toString();
}
protected String getStatBlockLinePossessions()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
statBuf.append("<p><font class='type'>Possessions:</font> ");
statBuf.append(pcOut.getEquipmentList()); //|FOR.0,(COUNT[EQUIPMENT]+1),1, \EQ.%.QTY\ \EQ.%.NAME\, ,COMMA,1|
statBuf.append("</p>");
return statBuf.toString();
}
protected String getStatBlockLineSkills()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
statBuf
.append("<p><font class='type'>Skills and Feats:</font> ");
//force refresh of skills
List<Skill> skillList =
SkillDisplay.getSkillListInOutputOrder(pc, display
.getPartialSkillList(View.VISIBLE_EXPORT));
boolean firstLine = true;
for (Skill skill : skillList)
{
if (!firstLine)
{
statBuf.append(", ");
}
firstLine = false;
int modSkill;
CDOMSingleRef<PCStat> keyStat = skill.get(ObjectKey.KEY_STAT);
if (keyStat != null)
{
modSkill =
SkillModifier.modifier(skill, pc).intValue()
- pc.getStatModFor(keyStat.get());
Logging.debugPrint("modSkill: " + modSkill);
}
int temp =
SkillModifier.modifier(skill, pc).intValue()
+ SkillRankControl.getTotalRank(pc, skill).intValue();
statBuf.append("<a href='skill:");
statBuf.append(skill.getOutputName()); //|SKILL.%skill|
statBuf.append("\\1d20");
statBuf.append(((temp < 0) ? Integer.toString(temp) : "+"
+ temp)); //|SKILL.%skill.TOTAL|
statBuf.append("' class='dialog'> ");
statBuf.append(skill.getOutputName()); //|SKILL.%skill|
statBuf.append(" (");
statBuf.append(temp); //|SKILL.%skill.TOTAL|
statBuf.append(")</a>");
}
statBuf.append("; ");
statBuf.append(pcOut.getFeatList()); //|FEATLIST|
statBuf.append("</p>");
return statBuf.toString();
}
protected String getStatBlockLineSpells()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
if (display.hasDomains())
{
//Domains
//Deity
statBuf.append("<p>");
statBuf.append("<font class='type'>Deity:</font>");
statBuf.append(pcOut.getDeity());
statBuf.append("<br>");
statBuf.append("<font class='type'>Domains:</font> ");
//Domain List with powers
boolean firstLine = true;
for (Domain dom : display.getSortedDomainSet())
{
if (!firstLine)
{
statBuf.append(", ");
}
firstLine = false;
statBuf.append(pcOut.getDomainName(dom)); //|DOMAIN|
statBuf.append(" (");
statBuf.append(DescriptionFormatting.piWrapDesc(dom, pc.getDescription(dom), true)); //|DOMAIN.POWER|
statBuf.append(")");
}
statBuf.append("</p>");
}
statBuf.append("<p>");
/*
<p>
<!-- Start Racial Innate Spells -->
|FOR,%spellrace,COUNT[SPELLRACE],COUNT[SPELLRACE],1,0|
|IIF(%spellrace:0)|
<!-- No innate spells -->
|ELSE|
|FOR,%spellbook,1,1,1,1|
|FOR,%class,0,0,1,1|
|FOR,%level,0,0,1,1|
|%SPELLLISTBOOK%class.%level.%spellbook|
<font class="type">Racial Innate Spells</font>
<br>
<!-- Start Racial Innate Spell listing -->
|FOR,%spell,0,COUNT[SPELLSINBOOK%class.%spellbook.%level]-1,1,0|
<a href="spell:|SPELLMEM.%class.%spellbook.%level.%spell.NAME|\|SPELLMEM.%class.%spellbook.%level.%spell.DESC|\|SPELLMEM.%class.%spellbook.%level.%spell.RANGE|\|SPELLMEM.%class.%spellbook.%level.%spell.CASTINGTIME|\|SPELLMEM.%class.%spellbook.%level.%spell.SAVEINFO|\|SPELLMEM.%class.%spellbook.%level.%spell.DURATION|\|SPELLMEM.%class.%spellbook.%level.%spell.TARGET|" class="dialog">
|SPELLMEM.%class.%spellbook.%level.%spell.NAME|
</a>
(|SPELLMEM.%class.%spellbook.%level.%spell.TIMES|)(DC:|SPELLMEM.%class.%spellbook.%level.%spell.DC|),
|ENDFOR|
|%|
|ENDFOR|
|ENDFOR|
|ENDFOR|
<!-- End Racial Innate Spells -->
<!-- Start Other Innate Spells -->
|FOR,%spellbook,2,COUNT[SPELLBOOKS]-1,1,0|
<br>
|FOR,%class,0,0,1,1|
|FOR,%level,0,0,1,1|
|%SPELLLISTBOOK%class.%level.%spellbook|
<br>
<font class="type">|SPELLBOOKNAME.%spellbook| Innate Spells</font>
<br>
|FOR,%spell,0,COUNT[SPELLSINBOOK%class.%spellbook.%level]-1,1,0|
<a href="spell:|SPELLMEM.%class.%spellbook.%level.%spell.NAME|\|SPELLMEM.%class.%spellbook.%level.%spell.DESC|\|SPELLMEM.%class.%spellbook.%level.%spell.RANGE|\|SPELLMEM.%class.%spellbook.%level.%spell.CASTINGTIME|\|SPELLMEM.%class.%spellbook.%level.%spell.SAVEINFO|\|SPELLMEM.%class.%spellbook.%level.%spell.DURATION|\|SPELLMEM.%class.%spellbook.%level.%spell.TARGET|" class="dialog">
|SPELLMEM.%class.%spellbook.%level.%spell.NAME|
</a>
(|SPELLMEM.%class.%spellbook.%level.%spell.TIMES|)(DC:|SPELLMEM.%class.%spellbook.%level.%spell.DC|),
|ENDFOR|
|%|
|ENDFOR|
|ENDFOR|
|ENDFOR|
<!-- End Other Innate Spells -->
|ENDIF|
|ENDFOR|
<!-- End Innate Spells -->
|FOR,%spellbook,0,0,1,0|
|FOR,%class,COUNT[SPELLRACE],COUNT[SPELLRACE]+COUNT[CLASSES]-1,1,1|
|%SPELLLISTCLASS%class|
<!-- START Spell list Header Table (Known) -->
<br>
<font class="type">|SPELLLISTCLASS.%class|
|IIF(SPELLLISTCLASS.%class:Psychic Warrior.OR.SPELLLISTCLASS.%class:Psion)|
Powers
|ELSE|
Spells Known
|ENDIF|
</font>
<br>
<!-- End Spell List Header Table (Known) -->
<!-- Start Known Spells -->
|FOR,%level,0,9,1,1|
|FOR,%spellcount,COUNT[SPELLSINBOOK%class.%spellbook.%level],COUNT[SPELLSINBOOK%class.%spellbook.%level],1,0|
|IIF(%spellcount:0)|
|ELSE|
<br>
<font class="type">Level %level</font>
<br>
|FOR,%spell,0,COUNT[SPELLSINBOOK%class.%spellbook.%level]-1,1,0|
<a href="spell:|SPELLMEM.%class.%spellbook.%level.%spell.NAME|\|SPELLMEM.%class.%spellbook.%level.%spell.DESC|\|SPELLMEM.%class.%spellbook.%level.%spell.RANGE|\|SPELLMEM.%class.%spellbook.%level.%spell.CASTINGTIME|\|SPELLMEM.%class.%spellbook.%level.%spell.SAVEINFO|\|SPELLMEM.%class.%spellbook.%level.%spell.DURATION|\|SPELLMEM.%class.%spellbook.%level.%spell.TARGET|" class="dialog">
|SPELLMEM.%class.%spellbook.%level.%spell.NAME|
</a>,
|IIF(SPELLLISTCLASS.%class:Psychic Warrior.OR.SPELLLISTCLASS.%class:Psion)|
|FOR,%ppcost,(%level*2)-1,(%level*2)-1,1,1|
|IIF(%ppcost:-1)|
<i>PP:</i> 0/1
|ELSE|
<i>PP:</i> %ppcost
|ENDIF|
|ENDFOR|
|ENDIF|
|ENDFOR|
|ENDIF|
|ENDFOR|
|ENDFOR|
<br>
|%|
|ENDFOR|
|ENDFOR|
<!-- End Known Spells -->
<!-- ================================================================ -->
<!-- Start Prepared Spells -->
|FOR,%memorised,COUNT[SPELLRACE]+COUNT[SPELLBOOKS]-2,COUNT[SPELLRACE]+COUNT[SPELLBOOKS]-2,1,0|
|IIF(%memorised:0)|
|ELSE|
<!-- Start Regular Prepared -->
|FOR,%spellbook,2,COUNT[SPELLBOOKS]-1,1,0|
|FOR,%foo,COUNT[SPELLRACE],COUNT[SPELLRACE],1,1|
|FOR,%bar,COUNT[SPELLSINBOOK0.%spellbook.0],COUNT[SPELLSINBOOK0.%spellbook.0],1,1|
|IIF(%foo:0.OR.%bar:0)|
<br>
<font class="type">|SPELLBOOKNAME.%spellbook| Spellbook:</font>
<br>
|FOR,%class,COUNT[SPELLRACE],COUNT[SPELLRACE]+COUNT[CLASSES]-1,1,1|
<br>
<font class="type">|SPELLLISTCLASS.%class|</font>
<br>
|FOR,%level,0,9,1,1|
|FOR,%spelllevelcount,COUNT[SPELLSINBOOK%class.%spellbook.%level],COUNT[SPELLSINBOOK%class.%spellbook.%level],1,0|
|IIF(%spelllevelcount:0)|
<!-- no memorized spells for SPELLSINBOOK%class %spellbook %level -->
|ELSE|
<br>
<font class="type">Level %level</font>
<br>
|FOR,%spell,0,COUNT[SPELLSINBOOK%class.%spellbook.%level]-1,1,0|
<a href="spell:|SPELLMEM.%class.%spellbook.%level.%spell.NAME|\|SPELLMEM.%class.%spellbook.%level.%spell.DESC|\|SPELLMEM.%class.%spellbook.%level.%spell.RANGE|\|SPELLMEM.%class.%spellbook.%level.%spell.CASTINGTIME|\|SPELLMEM.%class.%spellbook.%level.%spell.SAVEINFO|\|SPELLMEM.%class.%spellbook.%level.%spell.DURATION|\|SPELLMEM.%class.%spellbook.%level.%spell.TARGET|" class="dialog">
|SPELLMEM.%class.%spellbook.%level.%spell.NAME|
</a>
(|SPELLMEM.%class.%spellbook.%level.%spell.TIMES|)(DC:|SPELLMEM.%class.%spellbook.%level.%spell.DC|),
|ENDFOR|
|ENDIF|
|ENDFOR|
<!-- END FOR,%spellcount,COUNT[SPELLSINBOOK%class.%spellbook.0],COUNT[SPELLSINBOOK%class.%spellbook.0],1,0 -->
|ENDFOR|
<!-- END SPELLLISTCLASS%class -->
|%|
<!-- END FOR,%class,COUNT[SPELLRACE],COUNT[SPELLRACE]+COUNT[CLASSES]-1,1,1 -->
|ENDFOR|
|ELSE|
|ENDIF|
<!-- END FOR,%bar,COUNT[SPELLSINBOOK0.%spellbook.0],COUNT[SPELLSINBOOK0.%spellbook.0],1,1 -->
|ENDFOR|
<!-- END FOR,%foo,COUNT[SPELLRACE],COUNT[SPELLRACE],1,1 -->
|ENDFOR|
<!-- END FOR,%spellbook,2,COUNT[SPELLBOOKS]-1,1,0 -->
|ENDFOR|
<!-- ### END class Spellbook memorized spells ### -->
<!-- START FALSE IIF(%memorised:0) -->
|ENDIF|
|ENDFOR|
<!-- ### END MEMORIZED ### -->
<!-- End Prepared Spells -->
*/
ArrayList<PObject> classList =
new ArrayList<>(display.getClassSet());
classList.add(display.getRace());
Set<String> bookList = new HashSet<>(pc.getDisplay().getSpellBookNames());
bookList.add(Globals.getDefaultSpellBook());
for (String book : bookList)
{
statBlockLineSpellBook(pc, statBuf, classList, book);
}
return statBuf.toString();
}
protected void statBlockLineSpellBook(PlayerCharacter aPC, StringBuilder statBuf, Collection<PObject> classList, String spellBookName)
{
Set<PObject> classes = new HashSet<>();
classes.addAll(classList);
for ( PObject pObj : classes )
{
if (pObj != null)
{
int maxLevel = 100;
if (pObj instanceof PCClass)
{
PCClass theClass = (PCClass) pObj;
maxLevel = (aPC.getDisplay().getLevel(theClass) ==0) ? maxLevel: aPC.getSpellSupport(theClass).getMaxCastLevel(aPC);
}
StringBuilder spellBuff = new StringBuilder();
for (int level = 0; level <=maxLevel; level++)
{
List<CharacterSpell> spellList = aPC.getCharacterSpells(pObj, null, spellBookName, level);
if (spellList.size() >= 1)
{
spellBuff.append("<font class='type'>Level " + level + ":</font> ");
boolean firstLine = true;
for ( CharacterSpell cs : spellList )
{
if (!firstLine)
{
spellBuff.append(", ");
}
firstLine = false;
Spell spell = cs.getSpell();
spellBuff.append("<a href=" + '"' + "spell:");
spellBuff.append(spell.getDisplayName());
spellBuff.append("\\");
spellBuff.append(aPC.parseSpellString(cs, aPC.getDescription(spell)));
spellBuff.append("\\");
spellBuff.append(StringUtil.joinToStringBuilder(spell.getListFor(ListKey.RANGE), ", "));
spellBuff.append("\\");
spellBuff.append(spell.getListAsString(ListKey.CASTTIME));
spellBuff.append("\\");
spellBuff.append(StringUtil.joinToStringBuilder(spell.getListFor(ListKey.SAVE_INFO), ", "));
spellBuff.append("\\");
spellBuff.append(aPC.parseSpellString(cs, spell.getListAsString(ListKey.DURATION)));
spellBuff.append("\\");
spellBuff.append(aPC.parseSpellString(cs, spell.getSafe(StringKey.TARGET_AREA)));
spellBuff.append('"' + " class=" + '"' + "dialog" + '"' + ">");
spellBuff.append(spell.getDisplayName());
spellBuff.append("</a>");
}
spellBuff.append("<br>");
}
}
if (spellBuff.length() >0 )
{
statBuf.append("<br><font class='type'>" + spellBookName + ":</font><br> ");
statBuf.append("<font class='type'>" + pObj.getDisplayName() + ":</font><br> ");
statBuf.append(spellBuff);
}
}
}
}
protected String getStatBlockTitle()
{
StringBuilder statBuf = new StringBuilder();
PlayerCharacterOutput pcOut = new PlayerCharacterOutput(pc);
statBuf.append("<p class='gork'><font size='+1'><b>");
statBuf.append(pcOut.getName()); //|NAME|
statBuf.append(", ");
statBuf.append(pcOut.getGender()); //|GENDER|
statBuf.append(" ");
statBuf.append(pcOut.getRaceName()); //|RACE|
statBuf.append(" ");
String region = pcOut.getRegion(); //|REGION|.|%|
if (region != null && !region.isEmpty() && (region != null)
&& !"None".equals(region))
{
statBuf.append(" From " + region + " ");
}
statBuf.append(pcOut.getClasses() + " "); //|CLASSLIST|
statBuf.append("</b></font></p>");
return statBuf.toString();
}
}
}