/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.character;
import com.trollworks.gcs.advantage.Advantage;
import com.trollworks.gcs.character.names.USCensusNames;
import com.trollworks.gcs.feature.BonusAttributeType;
import com.trollworks.gcs.notes.Note;
import com.trollworks.gcs.preferences.SheetPreferences;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.io.xml.XMLNodeType;
import com.trollworks.toolkit.io.xml.XMLReader;
import com.trollworks.toolkit.io.xml.XMLWriter;
import com.trollworks.toolkit.ui.RetinaIcon;
import com.trollworks.toolkit.ui.image.StdImage;
import com.trollworks.toolkit.utility.FileType;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.Preferences;
import com.trollworks.toolkit.utility.text.Text;
import com.trollworks.toolkit.utility.units.LengthUnits;
import com.trollworks.toolkit.utility.units.LengthValue;
import com.trollworks.toolkit.utility.units.WeightUnits;
import com.trollworks.toolkit.utility.units.WeightValue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
/** Holds the character profile. */
public class Profile {
@Localize("{0}, {1}, {2}")
@Localize(locale = "de", value = "{0}, {1}, {2}")
private static String HAIR_FORMAT;
@Localize("MMMM d")
@Localize(locale = "de", value = "d. MMM.")
@Localize(locale = "es", value = "d. MMMM")
private static String BIRTHDAY_FORMAT;
@Localize("Brown")
@Localize(locale = "de", value = "Braun")
@Localize(locale = "ru", value = "Коричнев(ые,ая)")
@Localize(locale = "es", value = "Castaño")
private static String BROWN;
@Localize("Black")
@Localize(locale = "de", value = "Schwarz")
@Localize(locale = "ru", value = "Чёрный")
@Localize(locale = "es", value = "Moreno")
private static String BLACK;
@Localize("Blond")
@Localize(locale = "de", value = "Blond")
@Localize(locale = "ru", value = "Белокурые")
@Localize(locale = "es", value = "Rubio")
private static String BLOND;
@Localize("Redhead")
@Localize(locale = "de", value = "Rot")
@Localize(locale = "ru", value = "Рыжые")
@Localize(locale = "es", value = "Pelirojo")
private static String REDHEAD;
@Localize("Bald")
@Localize(locale = "de", value = "Glatze")
@Localize(locale = "ru", value = "Лысый")
@Localize(locale = "es", value = "Calvo")
private static String BALD;
@Localize("Straight")
@Localize(locale = "de", value = "glatt")
@Localize(locale = "ru", value = "Прямые")
@Localize(locale = "es", value = "Liso")
private static String STRAIGHT;
@Localize("Curly")
@Localize(locale = "de", value = "lockig")
@Localize(locale = "ru", value = "Вьющиеся")
@Localize(locale = "es", value = "Rizado")
private static String CURLY;
@Localize("Wavy")
@Localize(locale = "de", value = "gewellt")
@Localize(locale = "ru", value = "Волнистые")
@Localize(locale = "es", value = "Ondulado")
private static String WAVY;
@Localize("Short")
@Localize(locale = "de", value = "kurz")
@Localize(locale = "ru", value = "Короткие")
@Localize(locale = "es", value = "Corto")
private static String SHORT;
@Localize("Medium")
@Localize(locale = "de", value = "mittel")
@Localize(locale = "ru", value = "Средние")
@Localize(locale = "es", value = "Medio")
private static String MEDIUM;
@Localize("Long")
@Localize(locale = "de", value = "lang")
@Localize(locale = "ru", value = "Длинные")
@Localize(locale = "es", value = "Largo")
private static String LONG;
@Localize("Blue")
@Localize(locale = "de", value = "Blau")
@Localize(locale = "ru", value = "Синие")
@Localize(locale = "es", value = "Azul")
private static String BLUE;
@Localize("Green")
@Localize(locale = "de", value = "Grün")
@Localize(locale = "ru", value = "Зелёные")
@Localize(locale = "es", value = "Verde")
private static String GREEN;
@Localize("Grey")
@Localize(locale = "de", value = "Grau")
@Localize(locale = "ru", value = "Серые")
@Localize(locale = "es", value = "Gris")
private static String GREY;
@Localize("Violet")
@Localize(locale = "de", value = "Violett")
@Localize(locale = "ru", value = "Фиолетовые")
@Localize(locale = "es", value = "Violeta")
private static String VIOLET;
@Localize("Freckled")
@Localize(locale = "de", value = "Sommersprossen")
@Localize(locale = "ru", value = "Веснушчатая")
@Localize(locale = "es", value = "Pecoso")
private static String FRECKLED;
@Localize("Tan")
@Localize(locale = "de", value = "Gebräunt")
@Localize(locale = "ru", value = "Загорелая")
@Localize(locale = "es", value = "Bronceado")
private static String TAN;
@Localize("Light Tan")
@Localize(locale = "de", value = "Leicht gebräunt")
@Localize(locale = "ru", value = "Слегка загорелая")
@Localize(locale = "es", value = "Ligeramente bronceado")
private static String LIGHT_TAN;
@Localize("Dark Tan")
@Localize(locale = "de", value = "Stark gebräunt")
@Localize(locale = "ru", value = "Сильно-загорелая")
@Localize(locale = "es", value = "Muy bronceado")
private static String DARK_TAN;
@Localize("Light Brown")
@Localize(locale = "de", value = "Hellbraun")
@Localize(locale = "ru", value = "Светло-коричневая")
@Localize(locale = "es", value = "ligeramente bronceado")
private static String LIGHT_BROWN;
@Localize("Dark Brown")
@Localize(locale = "de", value = "Dunkelbraun")
@Localize(locale = "ru", value = "Тёмно-коричневая")
@Localize(locale = "es", value = "Muy bronceado")
private static String DARK_BROWN;
@Localize("Pale")
@Localize(locale = "de", value = "Bleich")
@Localize(locale = "ru", value = "Бледная")
@Localize(locale = "es", value = "Pálido")
private static String PALE;
@Localize("Right")
@Localize(locale = "de", value = "Rechts")
@Localize(locale = "ru", value = "Правая")
@Localize(locale = "es", value = "Diestro")
private static String RIGHT;
@Localize("Left")
@Localize(locale = "de", value = "Links")
@Localize(locale = "ru", value = "Левая")
@Localize(locale = "es", value = "Zurdo")
private static String LEFT;
@Localize("Male")
@Localize(locale = "de", value = "Männlich")
@Localize(locale = "ru", value = "муж.")
@Localize(locale = "es", value = "Varón")
private static String MALE;
@Localize("Female")
@Localize(locale = "de", value = "Weiblich")
@Localize(locale = "ru", value = "жен.")
@Localize(locale = "es", value = "Hembra")
private static String FEMALE;
@Localize("Human")
@Localize(locale = "de", value = "Mensch")
@Localize(locale = "ru", value = "Человек")
@Localize(locale = "es", value = "Humano")
private static String DEFAULT_RACE;
@Localize("Name Change")
@Localize(locale = "de", value = "Namen ändern")
@Localize(locale = "ru", value = "Изменить имя")
@Localize(locale = "es", value = "Cambiar nombre")
private static String NAME_UNDO;
@Localize("Title Change")
@Localize(locale = "de", value = "Titel ändern")
@Localize(locale = "ru", value = "Изменить статус")
@Localize(locale = "es", value = "Cambiar título")
private static String TITLE_UNDO;
@Localize("Age Change")
@Localize(locale = "de", value = "Alter ändern")
@Localize(locale = "ru", value = "Изменить возраст")
@Localize(locale = "es", value = "Cambiar edad")
private static String AGE_UNDO;
@Localize("Birthday Change")
@Localize(locale = "de", value = "Geburtstag ändern")
@Localize(locale = "ru", value = "Смена дня рождения")
@Localize(locale = "es", value = "Cambiar cumpleaños")
private static String BIRTHDAY_UNDO;
@Localize("Eye Color Change")
@Localize(locale = "de", value = "Augenfarbe ändern")
@Localize(locale = "ru", value = "Изменить цвет глаз")
@Localize(locale = "es", value = "Cambiae el color de los ojos")
private static String EYE_COLOR_UNDO;
@Localize("Hair Change")
@Localize(locale = "de", value = "Haar ändern")
@Localize(locale = "ru", value = "Изменить причёску")
@Localize(locale = "es", value = "Cambiar pelo")
private static String HAIR_UNDO;
@Localize("Skin Color Change")
@Localize(locale = "de", value = "Hautfarbe ändern")
@Localize(locale = "ru", value = "Изменить цвет кожи")
@Localize(locale = "es", value = "Cambiar color de la piel")
private static String SKIN_COLOR_UNDO;
@Localize("Handedness Change")
@Localize(locale = "de", value = "Händigkeit ändern")
@Localize(locale = "ru", value = "Смена основной руки")
@Localize(locale = "es", value = "Cambiar Mano dominante")
private static String HANDEDNESS_UNDO;
@Localize("Height Change")
@Localize(locale = "de", value = "Höhe ändern")
@Localize(locale = "ru", value = "Изменить рост")
@Localize(locale = "es", value = "Cambiar Altura")
private static String HEIGHT_UNDO;
@Localize("Weight Change")
@Localize(locale = "de", value = "Gewicht ändern")
@Localize(locale = "ru", value = "Изменить вес")
@Localize(locale = "es", value = "Cambiar Peso")
private static String WEIGHT_UNDO;
@Localize("Gender Change")
@Localize(locale = "de", value = "Geschlecht ändern")
@Localize(locale = "ru", value = "Изменить пол")
@Localize(locale = "es", value = "Cambiar género")
private static String GENDER_UNDO;
@Localize("Race Change")
@Localize(locale = "de", value = "Rasse ändern")
@Localize(locale = "ru", value = "Смена расы")
@Localize(locale = "es", value = "Cambiar raza")
private static String RACE_UNDO;
@Localize("Religion Change")
@Localize(locale = "de", value = "Religion ändern")
@Localize(locale = "ru", value = "Изменить религию")
@Localize(locale = "es", value = "Cambiar Religión")
private static String RELIGION_UNDO;
@Localize("Player Name Change")
@Localize(locale = "de", value = "Spielernamen ändern")
@Localize(locale = "ru", value = "Изменить имя игрока")
@Localize(locale = "es", value = "Cambiar Nombre del Jugador")
private static String PLAYER_NAME_UNDO;
@Localize("Campaign Change")
@Localize(locale = "de", value = "Kampagne ändern")
@Localize(locale = "ru", value = "Изменить компанию")
@Localize(locale = "es", value = "Cambiar Nombre de Camapaña")
private static String CAMPAIGN_UNDO;
@Localize("Size Modifier Change")
@Localize(locale = "de", value = "Größenmodifikator ändern")
@Localize(locale = "ru", value = "Изменить модификатор размера")
@Localize(locale = "es", value = "Cambiar Modificador por talla")
private static String SIZE_MODIFIER_UNDO;
@Localize("Tech Level Change")
@Localize(locale = "de", value = "Techlevel ändern")
@Localize(locale = "ru", value = "Смена технологического уровня")
@Localize(locale = "es", value = "Cambiar Nivel Tecnológico")
private static String TECH_LEVEL_UNDO;
@Localize("Portrait Change")
@Localize(locale = "de", value = "Portrait ändern")
@Localize(locale = "ru", value = "Смена изображения")
@Localize(locale = "es", value = "Cambiar Retrato")
private static String PORTRAIT_UNDO;
@Localize("The portrait is a PNG file encoded as Base64.")
@Localize(locale = "de", value = "Das Portrait ist eine Base64-kodierte PNG-Datei.")
@Localize(locale = "ru", value = "Изображение представляет собой файл PNG (закодирован в Base64).")
@Localize(locale = "es", value = "El retrato es un archivo PNG codificado como Base64 ")
private static String PORTRAIT_COMMENT;
@Localize("Could not write portrait.")
@Localize(locale = "de", value = "Kann Portrait nicht schreiben.")
@Localize(locale = "ru", value = "Не удалось записать изображение.")
@Localize(locale = "es", value = "No se puede escribir el retrato")
private static String PORTRAIT_WRITE_ERROR;
@Localize("Body Type Change")
private static String BODY_TYPE_UNDO;
static {
Localization.initialize();
}
/** The root XML tag. */
public static final String TAG_ROOT = "profile"; //$NON-NLS-1$
/** The preferences module name. */
public static final String MODULE = "GURPSCharacter"; //$NON-NLS-1$
/** The prefix used in front of all IDs for profile. */
public static final String PROFILE_PREFIX = GURPSCharacter.CHARACTER_PREFIX + "pi."; //$NON-NLS-1$
/** The field ID for portrait changes. */
public static final String ID_PORTRAIT = PROFILE_PREFIX + "Portrait"; //$NON-NLS-1$
/** The field ID for name changes. */
public static final String ID_NAME = PROFILE_PREFIX + "Name"; //$NON-NLS-1$
/** The field ID for title changes. */
public static final String ID_TITLE = PROFILE_PREFIX + "Title"; //$NON-NLS-1$
/** The field ID for age changes. */
public static final String ID_AGE = PROFILE_PREFIX + "Age"; //$NON-NLS-1$
/** The field ID for birthday changes. */
public static final String ID_BIRTHDAY = PROFILE_PREFIX + "Birthday"; //$NON-NLS-1$
/** The field ID for eye color changes. */
public static final String ID_EYE_COLOR = PROFILE_PREFIX + "EyeColor"; //$NON-NLS-1$
/** The field ID for hair color changes. */
public static final String ID_HAIR = PROFILE_PREFIX + "Hair"; //$NON-NLS-1$
/** The field ID for skin color changes. */
public static final String ID_SKIN_COLOR = PROFILE_PREFIX + "SkinColor"; //$NON-NLS-1$
/** The field ID for handedness changes. */
public static final String ID_HANDEDNESS = PROFILE_PREFIX + "Handedness"; //$NON-NLS-1$
/** The field ID for height changes. */
public static final String ID_HEIGHT = PROFILE_PREFIX + "Height"; //$NON-NLS-1$
/** The field ID for weight changes. */
public static final String ID_WEIGHT = PROFILE_PREFIX + "Weight"; //$NON-NLS-1$
/** The field ID for gender changes. */
public static final String ID_GENDER = PROFILE_PREFIX + "Gender"; //$NON-NLS-1$
/** The field ID for race changes. */
public static final String ID_RACE = PROFILE_PREFIX + "Race"; //$NON-NLS-1$
/** The field ID for religion changes. */
public static final String ID_RELIGION = PROFILE_PREFIX + "Religion"; //$NON-NLS-1$
/** The field ID for player name changes. */
public static final String ID_PLAYER_NAME = PROFILE_PREFIX + "PlayerName"; //$NON-NLS-1$
/** The field ID for campaign changes. */
public static final String ID_CAMPAIGN = PROFILE_PREFIX + "Campaign"; //$NON-NLS-1$
/** The field ID for tech level changes. */
public static final String ID_TECH_LEVEL = PROFILE_PREFIX + "TechLevel"; //$NON-NLS-1$
/** The field ID for size modifier changes. */
public static final String ID_SIZE_MODIFIER = PROFILE_PREFIX + BonusAttributeType.SM.name();
/** The field ID for body type changes. */
public static final String ID_BODY_TYPE = PROFILE_PREFIX + "BodyType"; //$NON-NLS-1$
/** The default portrait marker. */
public static final String DEFAULT_PORTRAIT = "!\000"; //$NON-NLS-1$
/** The default Tech Level. */
public static final String DEFAULT_TECH_LEVEL = "3"; //$NON-NLS-1$
/** The height, in 1/72nds of an inch, of the portrait. */
public static final int PORTRAIT_HEIGHT = 96;
/** The width, in 1/72nds of an inch, of the portrait. */
public static final int PORTRAIT_WIDTH = 3 * PORTRAIT_HEIGHT / 4;
private static final String TAG_PLAYER_NAME = "player_name"; //$NON-NLS-1$
private static final String TAG_CAMPAIGN = "campaign"; //$NON-NLS-1$
private static final String TAG_NAME = "name"; //$NON-NLS-1$
private static final String TAG_TITLE = "title"; //$NON-NLS-1$
private static final String TAG_AGE = "age"; //$NON-NLS-1$
private static final String TAG_BIRTHDAY = "birthday"; //$NON-NLS-1$
private static final String TAG_EYES = "eyes"; //$NON-NLS-1$
private static final String TAG_HAIR = "hair"; //$NON-NLS-1$
private static final String TAG_SKIN = "skin"; //$NON-NLS-1$
private static final String TAG_HANDEDNESS = "handedness"; //$NON-NLS-1$
private static final String TAG_HEIGHT = "height"; //$NON-NLS-1$
private static final String TAG_WEIGHT = "weight"; //$NON-NLS-1$
private static final String TAG_GENDER = "gender"; //$NON-NLS-1$
private static final String TAG_RACE = "race"; //$NON-NLS-1$
private static final String TAG_TECH_LEVEL = "tech_level"; //$NON-NLS-1$
private static final String TAG_RELIGION = "religion"; //$NON-NLS-1$
private static final String TAG_PORTRAIT = "portrait"; //$NON-NLS-1$
private static final String TAG_OLD_NOTES = "notes"; //$NON-NLS-1$
private static final String TAG_BODY_TYPE = "body_type"; //$NON-NLS-1$
private static final String EMPTY = ""; //$NON-NLS-1$
private static final Random RANDOM = new Random();
private static final String[] EYE_OPTIONS = new String[] { BROWN, BROWN, BLUE, BLUE, GREEN, GREY, VIOLET };
private static final String[] SKIN_OPTIONS = new String[] { FRECKLED, TAN, LIGHT_TAN, DARK_TAN, BROWN, LIGHT_BROWN, DARK_BROWN, PALE };
private static final String[] HANDEDNESS_OPTIONS = new String[] { RIGHT, RIGHT, RIGHT, LEFT };
private static final String[] GENDER_OPTIONS = new String[] { MALE, MALE, MALE, FEMALE };
private static final String[] HAIR_OPTIONS;
private GURPSCharacter mCharacter;
private boolean mCustomPortrait;
private RetinaIcon mPortrait;
private String mName;
private String mTitle;
private int mAge;
private String mBirthday;
private String mEyeColor;
private String mHair;
private String mSkinColor;
private String mHandedness;
private LengthValue mHeight;
private WeightValue mWeight;
private int mSizeModifier;
private int mSizeModifierBonus;
private String mGender;
private String mRace;
private String mReligion;
private String mPlayerName;
private String mCampaign;
private String mTechLevel;
private HitLocationTable mHitLocationTable;
Profile(GURPSCharacter character, boolean full) {
mCharacter = character;
mCustomPortrait = false;
mPortrait = null;
mTitle = EMPTY;
mAge = full ? getRandomAge() : 0;
mBirthday = full ? getRandomMonthAndDay() : EMPTY;
mEyeColor = full ? getRandomEyeColor() : EMPTY;
mHair = full ? getRandomHair() : EMPTY;
mSkinColor = full ? getRandomSkinColor() : EMPTY;
mHandedness = full ? getRandomHandedness() : EMPTY;
mHeight = full ? getRandomHeight(mCharacter.getStrength(), getSizeModifier()) : new LengthValue(0, SheetPreferences.getLengthUnits());
mWeight = full ? getRandomWeight(mCharacter.getStrength(), getSizeModifier(), 1.0) : new WeightValue(0, SheetPreferences.getWeightUnits());
mGender = full ? getRandomGender() : EMPTY;
mName = full && SheetPreferences.isNewCharacterAutoNamed() ? USCensusNames.INSTANCE.getFullName(mGender == MALE) : EMPTY;
mRace = full ? DEFAULT_RACE : EMPTY;
mTechLevel = full ? getDefaultTechLevel() : EMPTY;
mReligion = EMPTY;
mPlayerName = full ? getDefaultPlayerName() : EMPTY;
mCampaign = full ? getDefaultCampaign() : EMPTY;
mPortrait = createPortrait(getPortraitFromPortraitPath(getDefaultPortraitPath()));
mHitLocationTable = HitLocationTable.HUMANOID;
}
void load(XMLReader reader) throws IOException {
String marker = reader.getMarker();
do {
if (reader.next() == XMLNodeType.START_TAG) {
String tag = reader.getName();
if (!loadTag(reader, tag)) {
reader.skipTag(tag);
}
}
} while (reader.withinMarker(marker));
}
boolean loadTag(XMLReader reader, String tag) throws IOException {
if (TAG_PLAYER_NAME.equals(tag)) {
mPlayerName = reader.readText();
} else if (TAG_CAMPAIGN.equals(tag)) {
mCampaign = reader.readText();
} else if (TAG_NAME.equals(tag)) {
mName = reader.readText();
} else if (TAG_OLD_NOTES.equals(tag)) {
Note note = new Note(mCharacter, false);
note.setDescription(Text.standardizeLineEndings(reader.readText()));
mCharacter.getNotesRoot().addRow(note, false);
} else if (TAG_TITLE.equals(tag)) {
mTitle = reader.readText();
} else if (TAG_AGE.equals(tag)) {
mAge = reader.readInteger(0);
} else if (TAG_BIRTHDAY.equals(tag)) {
mBirthday = reader.readText();
} else if (TAG_EYES.equals(tag)) {
mEyeColor = reader.readText();
} else if (TAG_HAIR.equals(tag)) {
mHair = reader.readText();
} else if (TAG_SKIN.equals(tag)) {
mSkinColor = reader.readText();
} else if (TAG_HANDEDNESS.equals(tag)) {
mHandedness = reader.readText();
} else if (TAG_HEIGHT.equals(tag)) {
mHeight = LengthValue.extract(reader.readText(), false);
} else if (TAG_WEIGHT.equals(tag)) {
mWeight = WeightValue.extract(reader.readText(), false);
} else if (BonusAttributeType.SM.getXMLTag().equals(tag) || "size_modifier".equals(tag)) { //$NON-NLS-1$
mSizeModifier = reader.readInteger(0);
} else if (TAG_GENDER.equals(tag)) {
mGender = reader.readText();
} else if (TAG_RACE.equals(tag)) {
mRace = reader.readText();
} else if (TAG_BODY_TYPE.equals(tag)) {
mHitLocationTable = HitLocationTable.MAP.get(reader.readText());
if (mHitLocationTable == null) {
mHitLocationTable = HitLocationTable.HUMANOID;
}
} else if (TAG_TECH_LEVEL.equals(tag)) {
mTechLevel = reader.readText();
} else if (TAG_RELIGION.equals(tag)) {
mReligion = reader.readText();
} else if (TAG_PORTRAIT.equals(tag)) {
try {
mPortrait = createPortrait(StdImage.loadImage(Base64.getMimeDecoder().decode(reader.readText())));
mCustomPortrait = true;
} catch (Exception imageException) {
// Ignore
}
} else {
return false;
}
return true;
}
void save(XMLWriter out) {
out.startSimpleTagEOL(TAG_ROOT);
out.simpleTagNotEmpty(TAG_PLAYER_NAME, mPlayerName);
out.simpleTagNotEmpty(TAG_CAMPAIGN, mCampaign);
out.simpleTagNotEmpty(TAG_NAME, mName);
out.simpleTagNotEmpty(TAG_TITLE, mTitle);
out.simpleTag(TAG_AGE, mAge);
out.simpleTagNotEmpty(TAG_BIRTHDAY, mBirthday);
out.simpleTagNotEmpty(TAG_EYES, mEyeColor);
out.simpleTagNotEmpty(TAG_HAIR, mHair);
out.simpleTagNotEmpty(TAG_SKIN, mSkinColor);
out.simpleTagNotEmpty(TAG_HANDEDNESS, mHandedness);
if (mHeight.getNormalizedValue() != 0) {
out.simpleTag(TAG_HEIGHT, mHeight.toString(false));
}
if (mWeight.getNormalizedValue() != 0) {
out.simpleTag(TAG_WEIGHT, mWeight.toString(false));
}
out.simpleTag(BonusAttributeType.SM.getXMLTag(), mSizeModifier);
out.simpleTagNotEmpty(TAG_GENDER, mGender);
out.simpleTagNotEmpty(TAG_RACE, mRace);
out.simpleTag(TAG_BODY_TYPE, mHitLocationTable.getKey());
out.simpleTagNotEmpty(TAG_TECH_LEVEL, mTechLevel);
out.simpleTagNotEmpty(TAG_RELIGION, mReligion);
if (mCustomPortrait && mPortrait != null) {
try {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(mPortrait.getRetina(), FileType.PNG_EXTENSION, baos);
out.writeComment(PORTRAIT_COMMENT);
out.startSimpleTagEOL(TAG_PORTRAIT);
out.println(Text.standardizeLineEndings(Base64.getMimeEncoder().encodeToString(baos.toByteArray())));
out.endTagEOL(TAG_PORTRAIT, true);
}
} catch (Exception ex) {
throw new RuntimeException(PORTRAIT_WRITE_ERROR);
}
}
out.endTagEOL(TAG_ROOT, true);
}
void update() {
setSizeModifierBonus(mCharacter.getIntegerBonusFor(GURPSCharacter.ATTRIBUTES_PREFIX + BonusAttributeType.SM.name()));
}
/** @return The portrait. */
public RetinaIcon getPortrait() {
return mPortrait;
}
/**
* Sets the portrait.
*
* @param portrait The new portrait.
*/
public void setPortrait(StdImage portrait) {
if (portrait == null ? mPortrait != null : mPortrait.getRetina() != portrait) {
mCustomPortrait = true;
mCharacter.postUndoEdit(PORTRAIT_UNDO, ID_PORTRAIT, mPortrait, portrait);
mPortrait = createPortrait(portrait);
mCharacter.notifySingle(ID_PORTRAIT, mPortrait);
}
}
public static RetinaIcon createPortrait(StdImage image) {
if (image == null) {
return null;
}
StdImage normal;
StdImage retina;
int width = image.getWidth();
int height = image.getHeight();
if (width == PORTRAIT_WIDTH * 2 && height == PORTRAIT_HEIGHT * 2) {
retina = image;
normal = StdImage.scale(retina, PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
} else if (width == PORTRAIT_WIDTH && height == PORTRAIT_HEIGHT) {
normal = image;
retina = StdImage.scale(normal, PORTRAIT_WIDTH * 2, PORTRAIT_HEIGHT * 2);
} else {
retina = StdImage.scale(image, PORTRAIT_WIDTH * 2, PORTRAIT_HEIGHT * 2);
normal = StdImage.scale(retina, PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
}
return new RetinaIcon(normal, retina);
}
/** @return The name. */
public String getName() {
return mName;
}
/**
* Sets the name.
*
* @param name The new name.
*/
public void setName(String name) {
if (!mName.equals(name)) {
mCharacter.postUndoEdit(NAME_UNDO, ID_NAME, mName, name);
mName = name;
mCharacter.notifySingle(ID_NAME, mName);
}
}
/** @return The gender. */
public String getGender() {
return mGender;
}
/**
* Sets the gender.
*
* @param gender The new gender.
*/
public void setGender(String gender) {
if (!mGender.equals(gender)) {
mCharacter.postUndoEdit(GENDER_UNDO, ID_GENDER, mGender, gender);
mGender = gender;
mCharacter.notifySingle(ID_GENDER, mGender);
}
}
/** @return The race. */
public String getRace() {
return mRace;
}
/**
* Sets the race.
*
* @param race The new race.
*/
public void setRace(String race) {
if (!mRace.equals(race)) {
mCharacter.postUndoEdit(RACE_UNDO, ID_RACE, mRace, race);
mRace = race;
mCharacter.notifySingle(ID_RACE, mRace);
}
}
/** @return The religion. */
public String getReligion() {
return mReligion;
}
/**
* Sets the religion.
*
* @param religion The new religion.
*/
public void setReligion(String religion) {
if (!mReligion.equals(religion)) {
mCharacter.postUndoEdit(RELIGION_UNDO, ID_RELIGION, mReligion, religion);
mReligion = religion;
mCharacter.notifySingle(ID_RELIGION, mReligion);
}
}
/** @return The player's name. */
public String getPlayerName() {
return mPlayerName;
}
/**
* Sets the player's name.
*
* @param player The new player's name.
*/
public void setPlayerName(String player) {
if (!mPlayerName.equals(player)) {
mCharacter.postUndoEdit(PLAYER_NAME_UNDO, ID_PLAYER_NAME, mPlayerName, player);
mPlayerName = player;
mCharacter.notifySingle(ID_PLAYER_NAME, mPlayerName);
}
}
/** @return The campaign. */
public String getCampaign() {
return mCampaign;
}
/**
* Sets the campaign.
*
* @param campaign The new campaign.
*/
public void setCampaign(String campaign) {
if (!mCampaign.equals(campaign)) {
mCharacter.postUndoEdit(CAMPAIGN_UNDO, ID_CAMPAIGN, mCampaign, campaign);
mCampaign = campaign;
mCharacter.notifySingle(ID_CAMPAIGN, mCampaign);
}
}
/** @return The tech level. */
public String getTechLevel() {
return mTechLevel;
}
/**
* Sets the tech level.
*
* @param techLevel The new tech level.
*/
public void setTechLevel(String techLevel) {
if (!mTechLevel.equals(techLevel)) {
mCharacter.postUndoEdit(TECH_LEVEL_UNDO, ID_TECH_LEVEL, mTechLevel, techLevel);
mTechLevel = techLevel;
mCharacter.notifySingle(ID_TECH_LEVEL, mTechLevel);
}
}
/** @return The title. */
public String getTitle() {
return mTitle;
}
/**
* Sets the title.
*
* @param title The new title.
*/
public void setTitle(String title) {
if (!mTitle.equals(title)) {
mCharacter.postUndoEdit(TITLE_UNDO, ID_TITLE, mTitle, title);
mTitle = title;
mCharacter.notifySingle(ID_TITLE, mTitle);
}
}
/** @return The age. */
public int getAge() {
return mAge;
}
/**
* Sets the age.
*
* @param age The new age.
*/
public void setAge(int age) {
if (mAge != age) {
Integer value = new Integer(age);
mCharacter.postUndoEdit(AGE_UNDO, ID_AGE, new Integer(mAge), value);
mAge = age;
mCharacter.notifySingle(ID_AGE, value);
}
}
/** @return A random age. */
public int getRandomAge() {
Advantage lifespan = mCharacter.getAdvantageNamed("Unaging"); //$NON-NLS-1$
int base = 16;
int mod = 7;
int levels;
if (lifespan != null) {
return 18 + RANDOM.nextInt(7);
}
if (RANDOM.nextInt(3) == 1) {
mod += 7;
if (RANDOM.nextInt(4) == 1) {
mod += 13;
}
}
lifespan = mCharacter.getAdvantageNamed("Short Lifespan"); //$NON-NLS-1$
if (lifespan != null) {
levels = lifespan.getLevels();
base = base >> levels;
mod = mod >> levels;
} else {
lifespan = mCharacter.getAdvantageNamed("Extended Lifespan"); //$NON-NLS-1$
if (lifespan != null) {
levels = lifespan.getLevels();
base = base << levels;
mod = mod << levels;
}
}
if (mod < 1) {
mod = 1;
}
return base + RANDOM.nextInt(mod);
}
/** @return The date of birth. */
public String getBirthday() {
return mBirthday;
}
/**
* Sets the date of birth.
*
* @param birthday The new date of birth.
*/
public void setBirthday(String birthday) {
if (!mBirthday.equals(birthday)) {
mCharacter.postUndoEdit(BIRTHDAY_UNDO, ID_BIRTHDAY, mBirthday, birthday);
mBirthday = birthday;
mCharacter.notifySingle(ID_BIRTHDAY, mBirthday);
}
}
/** @return The eye color. */
public String getEyeColor() {
return mEyeColor;
}
/**
* Sets the eye color.
*
* @param eyeColor The new eye color.
*/
public void setEyeColor(String eyeColor) {
if (!mEyeColor.equals(eyeColor)) {
mCharacter.postUndoEdit(EYE_COLOR_UNDO, ID_EYE_COLOR, mEyeColor, eyeColor);
mEyeColor = eyeColor;
mCharacter.notifySingle(ID_EYE_COLOR, mEyeColor);
}
}
/** @return The hair. */
public String getHair() {
return mHair;
}
/**
* Sets the hair.
*
* @param hair The new hair.
*/
public void setHair(String hair) {
if (!mHair.equals(hair)) {
mCharacter.postUndoEdit(HAIR_UNDO, ID_HAIR, mHair, hair);
mHair = hair;
mCharacter.notifySingle(ID_HAIR, mHair);
}
}
/** @return The skin color. */
public String getSkinColor() {
return mSkinColor;
}
/**
* Sets the skin color.
*
* @param skinColor The new skin color.
*/
public void setSkinColor(String skinColor) {
if (!mSkinColor.equals(skinColor)) {
mCharacter.postUndoEdit(SKIN_COLOR_UNDO, ID_SKIN_COLOR, mSkinColor, skinColor);
mSkinColor = skinColor;
mCharacter.notifySingle(ID_SKIN_COLOR, mSkinColor);
}
}
/** @return The handedness. */
public String getHandedness() {
return mHandedness;
}
/**
* Sets the handedness.
*
* @param handedness The new handedness.
*/
public void setHandedness(String handedness) {
if (!mHandedness.equals(handedness)) {
mCharacter.postUndoEdit(HANDEDNESS_UNDO, ID_HANDEDNESS, mHandedness, handedness);
mHandedness = handedness;
mCharacter.notifySingle(ID_HANDEDNESS, mHandedness);
}
}
/** @return The height. */
public LengthValue getHeight() {
return mHeight;
}
/**
* Sets the height.
*
* @param height The new height.
*/
public void setHeight(LengthValue height) {
if (!mHeight.equals(height)) {
height = new LengthValue(height);
mCharacter.postUndoEdit(HEIGHT_UNDO, ID_HEIGHT, new LengthValue(mHeight), height);
mHeight = height;
mCharacter.notifySingle(ID_HEIGHT, height);
}
}
/** @return The weight. */
public WeightValue getWeight() {
return mWeight;
}
/**
* Sets the weight.
*
* @param weight The new weight.
*/
public void setWeight(WeightValue weight) {
if (!mWeight.equals(weight)) {
weight = new WeightValue(weight);
mCharacter.postUndoEdit(WEIGHT_UNDO, ID_WEIGHT, new WeightValue(mWeight), weight);
mWeight = weight;
mCharacter.notifySingle(ID_WEIGHT, weight);
}
}
/** @return The multiplier compared to average weight for this character. */
public double getWeightMultiplier() {
if (mCharacter.hasAdvantageNamed("Very Fat")) { //$NON-NLS-1$
return 2.0;
} else if (mCharacter.hasAdvantageNamed("Fat")) { //$NON-NLS-1$
return 1.5;
} else if (mCharacter.hasAdvantageNamed("Overweight")) { //$NON-NLS-1$
return 1.3;
} else if (mCharacter.hasAdvantageNamed("Skinny")) { //$NON-NLS-1$
return 0.67;
}
return 1.0;
}
/** @return The size modifier. */
public int getSizeModifier() {
return mSizeModifier + mSizeModifierBonus;
}
/** @return The size modifier bonus. */
public int getSizeModifierBonus() {
return mSizeModifierBonus;
}
/** @param size The new size modifier. */
public void setSizeModifier(int size) {
int totalSizeModifier = getSizeModifier();
if (totalSizeModifier != size) {
Integer value = new Integer(size);
mCharacter.postUndoEdit(SIZE_MODIFIER_UNDO, ID_SIZE_MODIFIER, new Integer(totalSizeModifier), value);
mSizeModifier = size - mSizeModifierBonus;
mCharacter.notifySingle(ID_SIZE_MODIFIER, value);
}
}
/** @param bonus The new size modifier bonus. */
public void setSizeModifierBonus(int bonus) {
if (mSizeModifierBonus != bonus) {
mSizeModifierBonus = bonus;
mCharacter.notifySingle(ID_SIZE_MODIFIER, new Integer(getSizeModifier()));
}
}
/**
* @param id The field ID to retrieve the data for.
* @return The value of the specified field ID, or <code>null</code> if the field ID is invalid.
*/
public Object getValueForID(String id) {
if (id != null && id.startsWith(PROFILE_PREFIX)) {
if (ID_NAME.equals(id)) {
return getName();
} else if (ID_TITLE.equals(id)) {
return getTitle();
} else if (ID_AGE.equals(id)) {
return new Integer(getAge());
} else if (ID_BIRTHDAY.equals(id)) {
return getBirthday();
} else if (ID_EYE_COLOR.equals(id)) {
return getEyeColor();
} else if (ID_HAIR.equals(id)) {
return getHair();
} else if (ID_SKIN_COLOR.equals(id)) {
return getSkinColor();
} else if (ID_HANDEDNESS.equals(id)) {
return getHandedness();
} else if (ID_HEIGHT.equals(id)) {
return new LengthValue(getHeight());
} else if (ID_WEIGHT.equals(id)) {
return new WeightValue(getWeight());
} else if (ID_GENDER.equals(id)) {
return getGender();
} else if (ID_RACE.equals(id)) {
return getRace();
} else if (ID_RELIGION.equals(id)) {
return getReligion();
} else if (ID_PLAYER_NAME.equals(id)) {
return getPlayerName();
} else if (ID_CAMPAIGN.equals(id)) {
return getCampaign();
} else if (ID_TECH_LEVEL.equals(id)) {
return getTechLevel();
} else if (ID_SIZE_MODIFIER.equals(id)) {
return new Integer(getSizeModifier());
} else if (ID_BODY_TYPE.equals(id)) {
return mHitLocationTable;
}
}
return null;
}
/**
* @param id The field ID to set the value for.
* @param value The value to set.
*/
public void setValueForID(String id, Object value) {
if (id != null && id.startsWith(PROFILE_PREFIX)) {
if (ID_NAME.equals(id)) {
setName((String) value);
} else if (ID_TITLE.equals(id)) {
setTitle((String) value);
} else if (ID_AGE.equals(id)) {
setAge(((Integer) value).intValue());
} else if (ID_BIRTHDAY.equals(id)) {
setBirthday((String) value);
} else if (ID_EYE_COLOR.equals(id)) {
setEyeColor((String) value);
} else if (ID_HAIR.equals(id)) {
setHair((String) value);
} else if (ID_SKIN_COLOR.equals(id)) {
setSkinColor((String) value);
} else if (ID_HANDEDNESS.equals(id)) {
setHandedness((String) value);
} else if (ID_HEIGHT.equals(id)) {
setHeight((LengthValue) value);
} else if (ID_WEIGHT.equals(id)) {
setWeight((WeightValue) value);
} else if (ID_GENDER.equals(id)) {
setGender((String) value);
} else if (ID_RACE.equals(id)) {
setRace((String) value);
} else if (ID_RELIGION.equals(id)) {
setReligion((String) value);
} else if (ID_PLAYER_NAME.equals(id)) {
setPlayerName((String) value);
} else if (ID_CAMPAIGN.equals(id)) {
setCampaign((String) value);
} else if (ID_TECH_LEVEL.equals(id)) {
setTechLevel((String) value);
} else if (ID_PORTRAIT.equals(id)) {
if (value instanceof StdImage) {
setPortrait((StdImage) value);
}
} else if (ID_SIZE_MODIFIER.equals(id)) {
setSizeModifier(((Integer) value).intValue());
} else if (ID_BODY_TYPE.equals(id)) {
setHitLocationTable((HitLocationTable) value);
}
}
}
static {
ArrayList<String> hair = new ArrayList<>(100);
String[] colors = { BROWN, BROWN, BROWN, BLACK, BLACK, BLACK, BLOND, BLOND, REDHEAD };
String[] styles = { STRAIGHT, CURLY, WAVY };
String[] lengths = { SHORT, MEDIUM, LONG };
for (String element : colors) {
for (String style : styles) {
for (String length : lengths) {
hair.add(MessageFormat.format(HAIR_FORMAT, element, style, length));
}
}
}
hair.add(BALD);
HAIR_OPTIONS = hair.toArray(new String[hair.size()]);
}
/** @return A random hair color, style & length. */
public static String getRandomHair() {
return HAIR_OPTIONS[RANDOM.nextInt(HAIR_OPTIONS.length)];
}
/** @return A random eye color. */
public static String getRandomEyeColor() {
return EYE_OPTIONS[RANDOM.nextInt(EYE_OPTIONS.length)];
}
/** @return A random sking color. */
public static String getRandomSkinColor() {
return SKIN_OPTIONS[RANDOM.nextInt(SKIN_OPTIONS.length)];
}
/** @return A random handedness. */
public static String getRandomHandedness() {
return HANDEDNESS_OPTIONS[RANDOM.nextInt(HANDEDNESS_OPTIONS.length)];
}
/** @return A random gender. */
public static String getRandomGender() {
return GENDER_OPTIONS[RANDOM.nextInt(GENDER_OPTIONS.length)];
}
/** @return A random month and day. */
public static String getRandomMonthAndDay() {
SimpleDateFormat formatter = new SimpleDateFormat(BIRTHDAY_FORMAT);
return formatter.format(new Date(RANDOM.nextLong()));
}
/**
* @param strength The strength to base the height on.
* @param sm The size modifier to use.
* @return A random height.
*/
public static LengthValue getRandomHeight(int strength, int sm) {
int base;
if (strength < 7) {
base = 52;
} else if (strength < 10) {
base = 55 + (strength - 7) * 3;
} else if (strength == 10) {
base = 63;
} else if (strength < 14) {
base = 65 + (strength - 11) * 3;
} else {
base = 74;
}
boolean useMetric = SheetPreferences.getWeightUnits().isMetric();
if (useMetric) {
base = (int) Math.round(LengthUnits.CM.convert(LengthUnits.FT_IN, base));
base += RANDOM.nextInt(16);
} else {
base += RANDOM.nextInt(11);
}
if (sm != 0) {
base = (int) Math.max(Math.round(base * Math.pow(10.0, sm / 6.0)), 1);
}
LengthUnits calcUnits = useMetric ? LengthUnits.CM : LengthUnits.FT_IN;
LengthUnits desiredUnits = SheetPreferences.getLengthUnits();
return new LengthValue(desiredUnits.convert(calcUnits, base), desiredUnits);
}
/**
* @param strength The strength to base the weight on.
* @param sm The size modifier to use.
* @param multiplier The weight multiplier for being under- or overweight.
* @return A random weight.
*/
public static WeightValue getRandomWeight(int strength, int sm, double multiplier) {
int base;
int range;
if (strength < 7) {
base = 60;
range = 61;
} else if (strength < 10) {
base = 75 + (strength - 7) * 15;
range = 61;
} else if (strength == 10) {
base = 115;
range = 61;
} else if (strength < 14) {
base = 125 + (strength - 11) * 15;
range = 71 + (strength - 11) * 10;
} else {
base = 170;
range = 101;
}
boolean useMetric = SheetPreferences.getWeightUnits().isMetric();
if (useMetric) {
base = (int) Math.round(WeightUnits.KG.convert(WeightUnits.LB, base));
range = (int) Math.round(WeightUnits.KG.convert(WeightUnits.LB, range - 1)) + 1;
}
base += RANDOM.nextInt(range);
if (sm != 0) {
base = (int) Math.round(base * Math.pow(1000.0, sm / 6.0));
}
base = (int) Math.max(Math.round(base * multiplier), 1);
WeightUnits calcUnits = useMetric ? WeightUnits.KG : WeightUnits.LB;
WeightUnits desiredUnits = SheetPreferences.getWeightUnits();
return new WeightValue(desiredUnits.convert(calcUnits, base), desiredUnits);
}
/** @return The default player name. */
public static String getDefaultPlayerName() {
return Preferences.getInstance().getStringValue(MODULE, Profile.ID_NAME, System.getProperty("user.name")); //$NON-NLS-1$
}
/** @param name The default player name. */
public static void setDefaultPlayerName(String name) {
Preferences.getInstance().setValue(MODULE, Profile.ID_NAME, name);
}
/** @return The default campaign value. */
public static String getDefaultCampaign() {
return Preferences.getInstance().getStringValue(MODULE, Profile.ID_CAMPAIGN, EMPTY);
}
/** @param campaign The default campaign value. */
public static void setDefaultCampaign(String campaign) {
Preferences.getInstance().setValue(MODULE, Profile.ID_CAMPAIGN, campaign);
}
/** @return The default tech level. */
public static String getDefaultTechLevel() {
return Preferences.getInstance().getStringValue(MODULE, Profile.ID_TECH_LEVEL, DEFAULT_TECH_LEVEL);
}
/** @param techLevel The default tech level. */
public static void setDefaultTechLevel(String techLevel) {
Preferences.getInstance().setValue(MODULE, Profile.ID_TECH_LEVEL, techLevel);
}
/**
* @param path The path to load.
* @return The portrait.
*/
public static StdImage getPortraitFromPortraitPath(String path) {
if (DEFAULT_PORTRAIT.equals(path)) {
return StdImage.get("default_portrait"); //$NON-NLS-1$
}
return path != null ? StdImage.loadImage(new File(path)) : null;
}
/** @return The default portrait path. */
public static String getDefaultPortraitPath() {
return Preferences.getInstance().getStringValue(MODULE, Profile.ID_PORTRAIT, DEFAULT_PORTRAIT);
}
/** @param path The default portrait path. */
public static void setDefaultPortraitPath(String path) {
Preferences.getInstance().setValue(MODULE, Profile.ID_PORTRAIT, path);
}
/** @return The hit location table. */
public HitLocationTable getHitLocationTable() {
return mHitLocationTable;
}
/** @param table The hit location table. */
public void setHitLocationTable(HitLocationTable table) {
if (mHitLocationTable != table) {
mCharacter.postUndoEdit(BODY_TYPE_UNDO, ID_BODY_TYPE, mHitLocationTable, table);
mHitLocationTable = table;
mCharacter.notifySingle(ID_BODY_TYPE, mHitLocationTable);
}
}
}