/**
* Project: PFRPG-Toolset
* Created: Aug 13, 2006 by bebopJMM
*------------------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lostkingdomsfrontier.pfrpg.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import javax.xml.bind.annotation.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lostkingdomsfrontier.pfrpg.AdjustableValue;
import org.lostkingdomsfrontier.pfrpg.Adjustment;
import org.lostkingdomsfrontier.pfrpg.AdjustmentChoiceSelection;
import org.lostkingdomsfrontier.pfrpg.AdjustmentChoices;
import org.lostkingdomsfrontier.pfrpg.ChoiceHelper;
import org.lostkingdomsfrontier.pfrpg.encounter.combat.Attacks;
import org.lostkingdomsfrontier.pfrpg.encounter.combat.Defense;
import org.lostkingdomsfrontier.pfrpg.entity.HitPoints.LifeState;
import org.lostkingdomsfrontier.pfrpg.entity.Movement.EncumberanceType;
import org.lostkingdomsfrontier.pfrpg.entity.classes.CharacterClass;
import org.lostkingdomsfrontier.pfrpg.entity.races.Race;
import org.lostkingdomsfrontier.pfrpg.entity.races.RacialLevel;
import org.lostkingdomsfrontier.pfrpg.entity.talents.ManagedSkill;
import org.lostkingdomsfrontier.pfrpg.entity.talents.Skill;
import org.lostkingdomsfrontier.pfrpg.entity.talents.SkillsCollection;
import org.lostkingdomsfrontier.pfrpg.entity.talents.Talent;
import org.lostkingdomsfrontier.pfrpg.items.Inventory;
import org.lostkingdomsfrontier.pfrpg.items.ItemInstance;
/**
* An actor is an entity in the game capable of performing independent actions.
*
* @author bebopjmm
* @since sprint-0.1
*/
@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ActorType", namespace = "java:org.rollinitiative.d20.entity", propOrder = {
"baseScores", "description", "choiceSelections"})
// , "characterLevels", "inventory"
public abstract class Actor implements Comparable, Serializable
{
static final Log LOG = LogFactory.getLog(Actor.class);
public static final String DEFAULT_NAME = "Nameless Actor";
public enum Modifiers {
AbilityMod;
}
public enum Values {
Ability, Save, Skill;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@XmlTransient
private Long persistID;
/**
* Unique identifier for this actor within the game world.
*
* @since sprint-0.1
*/
@Transient
@XmlTransient
private final UUID uuid;
/**
* Full name for this actor.
*
* @since sprint-0.1
*/
@XmlAttribute(required = true)
private String name;
@ManyToOne(cascade = CascadeType.ALL)
@XmlIDREF
@XmlAttribute(required = true)
private Race race;
@XmlTransient
Size currentSize;
@XmlAttribute(required = true)
Alignment alignment;
@XmlElement(name = "Description", required = true)
Description description;
@XmlTransient
int level = 1;
// --- Ability Information ---
@Embedded
@XmlElement(name = "BaseAbilityScores", required = true)
AbilityScoresData baseScores = new AbilityScoresData();
/**
* Map used internally for tracking and applying modifiers to abilities.
*
* @since sprint-0.1
*/
@Transient
@XmlTransient
EnumMap<Ability, AbilityValue> abilities;
/**
* Map used internally for tracking and applying modifiers to saving throws.
*
* @since sprint-0.1
*/
@Transient
@XmlTransient
EnumMap<SavingThrow, AbilityListenerValue> saves;
// Advancement characterLevels;
/**
* Map used internally for managing skills
*
* @since sprint-0.2
*/
@XmlTransient
@Transient
Hashtable<Skill, ManagedSkill> skillsTable;
@Embedded
ChoiceHelper choiceSelections = new ChoiceHelper();
// @XmlTransient
// HashMap<CharacterClass, Integer> classMap = new HashMap<CharacterClass, Integer>();
//
// ArrayList<Talent> talents;
//
// AbilityListenerValue initiativeMod;
//
// Attacks attackStats;
//
// Defense defenseStats;
//
@Transient
@XmlTransient
Movement movement;
@Transient
@XmlTransient
EncumberanceType currentEncumberance = EncumberanceType.LIGHT;
// HitPoints hp;
//
// @XmlElement(name = "Inventory", required = true, nillable = true)
// Inventory inventory;
/**
* Instantiates a new actor with DEFAULT_NAME. All abilities are initialized to
* AbilityValue.DEFAULT_VALUE.
*
* @since sprint-0.1
*/
public Actor()
{
this(Actor.DEFAULT_NAME, null);
}
/**
* Instantiates a new actor with the provided name. All abilities are initialized to
* AbilityValue.DEFAULT_VALUE.
*
* @param name The full name of this Actor
* @since sprint-0.1
*/
public Actor(String name, SkillsCollection rulesetSkills)
{
this(name, null, new Description(), rulesetSkills);
}
public Actor(String name, Race race, Description description, SkillsCollection rulesetSkills)
{
LOG.debug("Initiating new actor: " + name);
this.uuid = UUID.randomUUID();
this.name = name;
this.description = description;
// Initialize the abilities to the DEFAULT_VALUE
LOG.debug("All actor abilities initialized to DEFAULT: " + AbilityValue.DEFAULT_VALUE);
this.abilities = new EnumMap<Ability, AbilityValue>(Ability.class);
for (Ability a : EnumSet.range(Ability.STR, Ability.CHA)) {
this.abilities.put(a, new AbilityValue(AbilityValue.DEFAULT_VALUE, a));
}
// Initialize the saves now that we have the abilities initialized
this.saves = new EnumMap<SavingThrow, AbilityListenerValue>(SavingThrow.class);
AbilityListenerValue fortSave = new AbilityListenerValue("FORT", 0, abilities
.get(Ability.CON));
this.saves.put(SavingThrow.FORTITUDE, fortSave);
AbilityListenerValue reflSave = new AbilityListenerValue("REFL", 0, abilities
.get(Ability.DEX));
this.saves.put(SavingThrow.REFLEX, reflSave);
AbilityListenerValue willSave = new AbilityListenerValue("WILL", 0, abilities
.get(Ability.WIS));
this.saves.put(SavingThrow.WILL, willSave);
// // initialize hit points.
// this.hp = new HitPoints(abilities.get(Ability.CON), true);
// initialize the skills
this.skillsTable = new Hashtable<Skill, ManagedSkill>();
if (rulesetSkills != null) {
initializeSkills(rulesetSkills);
}
// // initialize with no known talents
// this.talents = new ArrayList<Talent>();
currentSize = Size.MEDIUM;
this.description = new Description();
this.movement = new Movement(30, 20, 20);
if (race != null) {
setRace(race);
}
// this.characterLevels = new Advancement();
//
// initiativeMod = new AbilityListenerValue("INIT", 0, abilities.get(Ability.DEX));
// attackStats = new Attacks(currentSize, abilities.get(Ability.STR), abilities
// .get(Ability.DEX), abilities.get(Ability.STR));
// defenseStats = new Defense(currentSize, abilities.get(Ability.DEX));
//
// this.inventory = new Inventory();
}
/**
* Returns the unique identifier of the actor.
*
* @return the unique identifier
* @since sprint-0.1
*/
@Transient
public UUID getActorID()
{
return uuid;
}
public Long getPersistID()
{
return persistID;
}
public void setPersistID(Long persistID)
{
this.persistID = persistID;
}
/**
* @return the level
*/
public int getLevel()
{
return level;
}
/**
* @param level the level to set
*/
@XmlTransient
public void setLevel(int level)
{
this.level = level;
processRaceLevels();
}
// public HitPoints getHitPoints()
// {
// return hp;
// }
//
//
// /**
// * This method returns the current hit points for the actor.
// *
// * @return current hit points for the actor.
// */
// public int getHP()
// {
// return hp.getCurrentHP();
// }
// /**
// * This method returns the maximum hit points for the actor.
// *
// * @return maximum hit points for the actor.
// */
// public int getMaxHP()
// {
// return hp.getMaxHP();
// }
//
//
// /**
// * This method inflicts the designated amount of hit point damage to the actor.
// *
// * @param hp amount of damage to inflict
// * @return LifeState following the damage.
// */
// public LifeState damage(int hp)
// {
// synchronized (this.hp) {
// return this.hp.damage(hp);
// }
// }
//
//
// /**
// * This method cures the designated amount of hit points to the actor.
// *
// * @param hp amount of curing to apply
// * @return LifeState following the curing
// */
// public LifeState heal(int hp)
// {
// synchronized (this.hp) {
// return this.hp.heal(hp);
// }
// }
//
//
// /**
// * The actor's initiative modifier is a value derived from dexterity bonus and other
// modifiers.
// *
// * @return the actor's modifier to initiative rolls.
// */
// public AbilityListenerValue getInitiativeMod()
// {
// return initiativeMod;
// }
/**
* Returns the adjustable value associated with the specified ability.
*
* @param ability Ability to retrieve
* @return the adjustable value
* @since sprint-0.1
*/
public AdjustableValue getAbility(Ability ability)
{
return abilities.get(ability);
}
/**
* This method revises the baseValue of the designated ability.
*
* @param ability Ability to be modified
* @param baseValue new base value for the designated ability
* @since sprint-0.1
*/
public void setAbilityBaseValue(Ability ability, short baseValue)
{
LOG.debug("Setting " + ability + " baseValue to " + baseValue);
baseScores.setScore(ability, baseValue);
AbilityValue abilVal = abilities.get(ability);
abilVal.setBase(baseValue);
}
/**
* Accessor for the Actor's name.
*
* @return Returns the name.
* @since sprint-0.1
*/
public String getName()
{
return name;
}
/**
* Allows specifying the name of this Actor.
*
* @param name The name to set.
* @since sprint-0.1
*/
public void setName(String name)
{
this.name = name;
}
/**
* @return this Actor's associated Race.
* @since sprint-0.2
*/
public Race getRace()
{
return this.race;
}
/**
* @param race Race to associated with this Actor
* @since sprint-0.2
*/
public void setRace(Race race)
{
LOG.debug("Assignment of race and applicatin of racial modifiers: " + race.getName());
this.race = race;
setCurrentSize(race.getSize());
// Configure Movement
for (Movement.EncumberanceType encumberance : EnumSet.range(EncumberanceType.LIGHT,
EncumberanceType.HEAVY)) {
int speed = race.getMovement().getSpeed(encumberance);
setBaseMovement(encumberance, speed);
LOG.debug("Base movement for encumberance: " + encumberance.toString() + " = " + speed);
}
processRaceLevels();
}
/**
* Accessor for the Actor's current Size.
*
* @return the actor's currentSize
* @since sprint-0.1
*/
public Size getCurrentSize()
{
return currentSize;
}
/**
* Allows specifying the Size of an Actor
*
* @param currentSize new size for the actor
* @since sprint-0.1
*/
public void setCurrentSize(Size currentSize)
{
this.currentSize = currentSize;
// attackStats.setSize(this.currentSize);
// defenseStats.setSize(this.currentSize);
}
/**
* @return the alignment
* @since sprint-0.1
*/
public Alignment getAlignment()
{
return alignment;
}
/**
* @param alignment the alignment to set
* @since sprint-0.1
*/
public void setAlignment(Alignment alignment)
{
this.alignment = alignment;
}
/**
* @return the description
*/
public Description getDescription()
{
return description;
}
/**
* @param description the description to set
*/
public void setDescription(Description description)
{
this.description = description;
}
// /**
// * @return the characterLevels
// */
// public Advancement getCharacterLevels()
// {
// return characterLevels;
// }
//
//
// /**
// * @param characterLevels the characterLevels to set
// */
// @XmlElement(name = "Advancement", required = true)
// public void setCharacterLevels(Advancement characterLevels)
// {
// this.characterLevels = characterLevels;
// this.level = characterLevels.levels.size();
// }
/**
* @param save the saving throw enumerated value to return
* @return AdjustableValue for the requested saving throw type
*
* @since sprint-0.1
*/
public AdjustableValue getSave(SavingThrow save)
{
return saves.get(save);
}
/**
* @param skill specific skill to retrieve
* @return AdjustableValue for the requested skill, null if not known by the actor
*
* @since sprint-0.2
*/
public AdjustableValue getSkillValue(Skill skill)
{
ManagedSkill mSkill = skillsTable.get(skill);
if (mSkill == null) {
LOG.warn("Requested Skill (" + skill.getId() + ") is not known by this actor: " + name);
return null;
}
else {
return mSkill.getSkillValue();
}
}
/**
* @return the set of skills for this actor
*
* @since sprint-0.2
*/
public Set<Skill> getSkills()
{
return skillsTable.keySet();
}
/**
* @param skill Skill to query
* @return true if the specified skill is a class skill for this actor.
* @since sprint-0.2
*/
public boolean isClassSkill(Skill skill)
{
// Set<CharacterClass> charClasses = classMap.keySet();
// for (CharacterClass characterClass : charClasses) {
// if (characterClass.isClassSkill(skill.getId())) {
// return true;
// }
// }
return false;
}
/**
* This method returns true if the actor has the specified skill in their repertoire of known
* skills.
*
* @param skill Skill to check for
* @return true if the actor has the specified skill in their repertoire of known skills,
* otherwise false.
*
* @since sprint-0.2
*/
public boolean hasSkill(Skill skill)
{
return skillsTable.containsKey(skill);
}
// public Talent[] getTalents()
// {
// return talents.toArray(new Talent[talents.size()]);
// }
//
//
// public void addTalent(Talent newTalent)
// {
// if (talents.contains(newTalent)) {
// LOG.warn("Ignoring attempt to add talent (" + newTalent.getName()
// + ") that already exists for actor: " + this.name);
// return;
// }
// else {
// LOG.debug("Adding new talent (" + newTalent.getName() + ") to actor: " + this.name);
// talents.add(newTalent);
// }
// }
public int getCurrentSpeed()
{
return movement.getSpeed(currentEncumberance);
}
// public Attacks getAttacks()
// {
// return this.attackStats;
// }
//
//
// public Defense getDefense()
// {
// return this.defenseStats;
// }
//
//
// public void setMaxBaseAttackBonus(int maxBonus)
// {
// this.attackStats.setBaseAttackBonus(maxBonus);
// }
//
//
// public ItemInstance[] getCurrentInventory()
// {
// return inventory.getContents();
// }
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object object)
{
if (object instanceof Actor) {
return uuid.compareTo(((Actor) object).uuid);
}
return 0;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (obj instanceof Actor) {
Actor a = (Actor) obj;
return a.uuid.equals(this.uuid);
}
return super.equals(obj);
}
/**
* @param actor
* @param descriptor
* @return
* @throws IllegalArgumentException
*
* @since sprint-0.2
*/
public static AdjustableValue findAdjustableValue(Actor actor, StringTokenizer descriptor)
throws IllegalArgumentException
{
Values category = Values.valueOf(descriptor.nextToken());
// i.e. Ability.WIS or Defense.AC_Reg
switch (category) {
case Ability:
Ability ability = Ability.valueOf(descriptor.nextToken());
return actor.abilities.get(ability);
case Save:
SavingThrow save = SavingThrow.valueOf(descriptor.nextToken());
return actor.saves.get(save);
case Skill:
String skillName = descriptor.nextToken();
for (Skill skill : actor.skillsTable.keySet()) {
if (skill.getName().equalsIgnoreCase(skillName))
return actor.getSkillValue(skill);
}
return null;
default:
return null;
}
}
/**
* @param actor
* @param descriptor
* @return
* @throws IllegalArgumentException
*
* @since sprint-0.2
*/
public static Adjustment findAdjustment(Actor actor, StringTokenizer descriptor)
throws IllegalArgumentException
{
String token = null;
Modifiers category = Modifiers.valueOf(descriptor.nextToken());
// i.e. Ability.WIS
switch (category) {
case AbilityMod:
token = descriptor.nextToken();
LOG.debug("Looking up ability with token: " + token);
Ability ability = Ability.valueOf(token);
return actor.abilities.get(ability).getModifier();
default:
break;
}
return null;
}
/**
* @param actor
* @param adjustment
*
* @since sprint-0.2
*/
public static void mapAdjustment(Actor actor, Adjustment adjustment)
{
AdjustableValue value = null;
StringTokenizer descriptor = new StringTokenizer(adjustment.getMapping(), ".");
Values category = Values.valueOf(descriptor.nextToken());
switch (category) {
case Ability:
Ability ability = Ability.valueOf(descriptor.nextToken());
value = actor.getAbility(ability);
break;
case Save:
SavingThrow save = SavingThrow.valueOf(descriptor.nextToken());
value = actor.getSave(save);
break;
case Skill:
String skillName = descriptor.nextToken();
for (Skill skill : actor.skillsTable.keySet()) {
if (skill.getName().equalsIgnoreCase(skillName)) {
value = actor.getSkillValue(skill);
break;
}
}
default:
break;
}
if (value != null) {
value.addAdjustment(adjustment);
}
}
/**
* Assigns values from the baseScores data structure to their corresponding AbilityValue.
*
* @since sprint-0.1
*/
protected void initAbilities()
{
setAbilityBaseValue(Ability.STR, this.baseScores.getStr());
setAbilityBaseValue(Ability.DEX, this.baseScores.getDex());
setAbilityBaseValue(Ability.CON, this.baseScores.getCon());
setAbilityBaseValue(Ability.INT, this.baseScores.getInt_());
setAbilityBaseValue(Ability.WIS, this.baseScores.getWis());
setAbilityBaseValue(Ability.CHA, this.baseScores.getCha());
}
/**
* @param skill
*
* @since sprint-0.2
*/
protected void initSkill(Skill skill)
{
ManagedSkill newSkill = new ManagedSkill(skill, abilities.get(skill.getKeyAbility()));
synchronized (skillsTable) {
if (skillsTable.containsKey(skill)) {
LOG.warn("Ignoring attempt to initialize skill already known: " + skill.getName());
return;
}
skillsTable.put(skill, newSkill);
}
}
void setBaseMovement(EncumberanceType encumberance, int speed)
{
AdjustableValue move = movement.getSpeedValue(encumberance);
move.setBase(speed);
}
/**
* An actor has knowledge of any skill that does not require training.
*
* @param rulesetSkills
*
* @since sprint-0.2
*/
private void initializeSkills(SkillsCollection rulesetSkills)
{
ManagedSkill newSkill = null;
for (Skill skill : rulesetSkills.getSkills()) {
if (skill.isUntrained()) {
LOG.debug("Initializing untrained skill:" + skill.getName());
newSkill = new ManagedSkill(skill, abilities.get(skill.getKeyAbility()));
skillsTable.put(skill, newSkill);
}
else {
LOG.debug("Ignoring skill that requires training: " + skill.getName());
}
}
}
/**
* @param race
*
* @since sprint-0.2
*/
private void processRaceLevels()
{
// Apply racial modifiers to abilities
int nLevels = this.level;
if (race.getAdvancement().getLevels().size() < nLevels) {
nLevels = race.getAdvancement().getLevels().size();
LOG.debug("Total racial levels based on race, = " + nLevels);
}
RacialLevel level;
for (int i = 0; i < nLevels; i++) {
level = race.getAdvancement().getLevels().get(i);
// Map all adjustments to the correct AdjustableValues
Set<Adjustment> adjustments = level.getAdjustments();
for (Adjustment adjustment : adjustments) {
mapAdjustment(this, adjustment);
}
// Add any choices to be made
// if (!level.getAdjustmentChoices().isEmpty()) {
// remainingChoices.addAll(level.getAdjustmentChoices());
// }
}
}
@XmlType(name = "CharacterLevelListType", namespace = "java:org.rollinitiative.d20.entity")
public static class Advancement
{
@XmlElement(name = "LevelEntry", required = true)
ArrayList<CharacterLevel> levels = new ArrayList<CharacterLevel>();
}
}