/**

The following doc is in MediaWiki text.  They can be read as is or using a sand box/preview.

== How does it work ==

Program initialisation:

# All code loads are done in index.html, at the bottom with plain <script src='...'>.
#*  Codes are loaded in two stages: First stage is core system and data, second stage is sheet generators.
#*  Most data come coded in a very condensed form, usually in a string or many strings.
# Once first stage codes are loaded, chargen.init() will walk through the data and initialise them one by one.
#*  Initialisers for each type of data can be found at top of that file.
#*  When completed, all base data would be in form of prototyped class.  To do this, initialisers will parse strings and otherwise create/expand classes.
# A new character is created, and static UI populated by cgevent.initialise(), then cgevent.update() will build the rest.
#*  The character will also be initialised by the few really static boxes, like ability scores or character name.
#*  Static UI means races, levels, classs, options, etc., choices that are independent of character and so only need to be generated once.
#*  cgevent.update() will, in the process of building UI, force a choice for mandatory options.  In this case the update will be automatically redone.
# The generator is now usable. Second stage (sheet generators) will begins to load.


Coding notes:

# Most elements has a 'hint' property that will be read by the global mouseover handler.
#*  Usually, the hint property is dynamically built through a getter that calls methods in chargen_ui_desc.
# When *any* change is made, cgevent.update() will be called and non-static part of UI will be rebuilt.
#*  cgevent.update() will also check and removes invalid choices, thus the need to update even for changing deity.
#*  Profiling shows that the update is finished in split second.  Laying out & rendering rebuilt UI may takes much longer.
# cgevent.update() will, in turn, call Character.update() to make sure it get the latest numbers.
#*  Character.update() essentially rebuild the character in stages. On each stage, all character components will get a chance to make relevant changes.
#*  The stages are: first call, ability, FFPE slots, stats, misc. info, skills, proficiencies, FFPE changes (multiple rounds), and final call
#**   For the slot stages, old slots will be replaced by new slots (with initial state), update is then called to add/change slots, finally options of old slots will be copied over.
#**   Other stages will see the state stored in base race or class, so that rebuilt would be possible on update.
#*  Power effect is a special case in that, sicne power reset once for each applicable weapons/implements, power effect adjustments are placed in 'resetPower' and 'setPower' instead of FFPE changes.

# Sheet printing is straight forward. For each sheet there is a 'generate' function that takes a character and output html.



== Terminologies in code ==

; FFPE, FPE
:   (Feature,) Feat, Power, Equipment: Their architectures are mostly the same, thus many routines can be shared.

; Slot
:   Each of character's FFPE must be contained in a slot.  A slot may be fixed or may be pick-able from a list of options.

; Selectable list
:   An associated array (plain object) of name/id => current select state.  State constants sees chargen.LIST.
:   Used for languages, skills, and proficiencies.

; Rule text
:   Potentially variable stuffs like power hit effects or by-tier feat effects are stored in form of rule text and may contains multiple static / dynamic parts.
:   Rule text usually renders differently result for description (for character design) and for final values (for card generation).
:   Rule text classes are stored in chargen.Rule and can be found in chargen_rule.js.
:   Often placed in FFPE's 'rules' properties.  The pre-compilation alternative, 'text' remains but is depreciated.

; Number
:   A part of rule text that does bonus stacking.
:   Used for all potentially variable numbers, often used independently for numbers like skills, power hit, or ability attacks.



*/

/** Main chargen namespace. */
var chargen = {
  LIST: { UNAVAILABLE: 0, AVAILABLE: 1, SELECTED: 2, MIXED: 3, MANDATORY: 4 }, // Selectable list item states

    SIZE: [], // Size from -2 to 3, populated below; e.g. chargen.SIZE[-2] = 'tiny';
   STATS: ['str','con','dex','int','wis','cha'],
  VISION: ['normal','lowlight','darkvision'],
  POWER_TYPES: ['atwill','encounter','daily','utility'],
  DEITIES: {
    '': [''],
    'phb': ['Avandra', 'Bahamut', 'Corellon', 'Erathis', 'Ioun', 'Kord', 'Melora', 'Moradin', 'Pelor', 'RavenQueen', 'Sehanine'],
    'dmg': ['Asmodeus', 'Bane', 'Gruumsh', 'Lolth', 'Tiamat', 'Torog', 'Vecna', 'Zehir']
  },
  LANGUAGES: {
    'phb': {
      'common':'common', 'deep':'rellanic', 'draconic':'iokharic', 'dwarven':'davek', 'elven':'rellanic',
      'giant':'davek', 'goblin':'common', 'primordal':'barazhad', 'supernal':'supernal', 'abyssal':'barazhad' }
  },
   ARMOURS: ['Cloth','Leather','Hide','Chain','Scale','Plate','LightShield','HeavyShield'],
  EQ_SLOTS: ['melee','offhand','ranged','natural','body','arms'],
  ONE_HANDED_WEAPON_DAMAGE: ['1d4', '1d6', '1d8', '1d10', '1d12', '2d6', '2d8', '2d10'],
  TWO_HANDED_WEAPON_DAMAGE: ['1d8', '2d4', '1d10', '1d12', '2d6', '2d8', '2d10'],

  NO_STACKINGS: ['feat','item','proficiency','enhancement','armor','shield','power','racial'], // Non-stacking bonus.
  BONUS_TYPE: ['feat','item','proficiency','enhancement','armor','shield','power','racial'],

  MULTI_ATTACKS: ['', '2', '3'], // *.attack, *.attack2, *.attack3, etc.
  CHOICES: ['', '2', '3', '_ability', '_handcount'], // *.choice, *.choice2, *.choice3, *.choice_handcount
  ENCOUNTER_POWERS	: [27, 23, 17, 13, 7, 3, 1],
  DAILY_POWERS		: [29, 25, 19, 15, 9, 5, 1],
  UTILITY_POWERS	: [22, 16, 10, 6, 2],

  Character: function() {}, // Character class, populated below
  Skills: {
    acrobatics:'dex', arcana  :'int', athletics:'str', bluff     :'cha', diplomacy :'cha', dungeoneering:'wis',
    endurance :'con', heal    :'wis', history  :'int', insight   :'wis', intimidate:'cha', nature       :'wis',
    perception:'wis', religion:'int', stealth  :'dex', streetwise:'cha', thievery  :'dex'
    },
  armourPenaltySkills: ['acrobatics', 'athletics', 'endurance', 'stealth', 'thievery'],

  // Data objects. Features, Powers, Feats and Equipments are grouped in group objects.
  Races: {},
  Classes: {},
  Features: {},
  Powers: {},
  Feats: {},
  Equipments: {},
  Rituals: {},

  // General, non-class/race specific feats. Populated by chargen.System.initFeat()
  generalFeats: [],

  // Go through all the data and initialise them.
  init: function() {
    for each (let [name, race] in Iterator(this.Races))
      this.System.Race.initRace(name, race);
    for each (let [name, cls] in Iterator(this.Classes))
      this.System.Class.initClass(name, cls);
    for each (let [group, features] in Iterator(this.Features))
      for each (let [name, feature] in Iterator(features))
        this.System.Feature.initFeature(group, name, feature);
    for each (let [group, powers] in Iterator(this.Powers))
      for each (let [name, power] in Iterator(powers))
        this.System.Power.initPower(group, name, power);
    for each (let [group, feats] in Iterator(this.Feats))
      for each (let [name, feat] in Iterator(feats))
        this.System.Feat.initFeat(group, name, feat);
    for each (let [group, eqs] in Iterator(this.Equipments))
      for each (let [name, eq] in Iterator(eqs))
        this.System.Equipment.initEquipment(group, name, eq);
    for each (let cls in this.Classes)
      this.System.Class.initClass2(cls);
  },

  // General functions and sub-system constants/vars.
  System: {
    Commons: {
      /** Initialise and realise feature, feat, or power of an object.
       * Turns ['Dwarf.Feature1','Racial.LowLightVision'] into a list of initialised class.
       * @param string Type of things to init ('feat','power', etc.)
       * @targed object Base object containing the list of items to be initialised.
       * @field string Field of base object to initialise.
       */
      initFFP : function(type, target, field) {
        let result = {};
        let title = chargen.System.cap(type);
        let init = chargen.System[title]
        let data = chargen[title+'s'];
        if (field == undefined) field = type + 's';
        let temp = [];
        let l = target[field].length;
        for each (let ffp in target[field]) {
          if (typeof ffp != 'string') continue;
          let [group, name] = ffp.split('.',2);
          let p = data[group] ? data[group][name] || undefined : undefined;
          if (!p || typeof p == 'object') {
            init['init'+title](group, name, p);
            p = data[group][name];
          }
          temp.push(p);
        }
        target[field] = temp;
      }
    },

    // Add '+' if not negative. Return a string no matter what.
    modifier: function(subject) {
      if (!subject)
        return '+0';
      else if (typeof subject == 'string') {
        return subject.charAt(0) == '-' || subject.charAt(0) == '+' ? subject : '+'+subject;
      } else
        return subject >= 0 ? '+'+subject : String(subject);
    },

    // Combine a list with glue, ignoring empty items
    glue: function(items, glue) {
      if (!items) return "";
      if (glue === undefined) glue = ', ';
      var result = '';
      for each (let i in items)
        if (i) result += i + glue;
      return result.substring(0, result.length - glue.length);
    },

    // Loop through an array and map its items to text
    textualise: function(prefix, list, postfix) {
      var result = [];
      var text = window.text;
      for each (let item in list) {
        let key = item;
        if (prefix)
          if (isString(prefix))
            key = prefix+key;
          else
            key = prefix[key];
        if (postfix)
          key += postfix;
        result.push(text[key] || key);
      }
      return result;
    },

    // Standard race/class feature updator
    featureUpdator: function(chr) {
      for each (let feature in this.features)
        chr.setFeature(feature.prototype.id, new feature());
    },
    // Standard race/class powers updator
    powerUpdator: function(chr) {
      for each (let power in this.powers)
        chr.setPower(power.prototype.id, new power());
    },
    // Standard race/class feat updator
    featUpdator: function(chr) {
      for each (let feat in this.auto_feats)
        chr.setFeat(feat.prototype.id, new feat());
    },


    // Cap the first character of a string
    cap: function(name) {
      return name[0].toUpperCase()+name.slice(1);
    },
  }
};
chargen.SIZE[-2] = 'tiny';
chargen.SIZE[-1] = 'small';
chargen.SIZE[0] = 'medium';
chargen.SIZE[1] = 'large';
chargen.SIZE[2] = 'huge';
chargen.SIZE[3] = 'gargantuan';



let (p = chargen.Character.prototype) {
  p.abilities = {
    str:10, con:10, dex:10,
    int:10, wis:10, cha:10,
    strBase:10, conBase:10, dexBase:10,
    intBase:10, wisBase:10, chaBase:10,
    strAdj:0, conAdj:0, dexAdj:0,
    intAdj:0, wisAdj:0, chaAdj:0,
    strAtk:0, conAtk:0, dexAtk:0, // These will be replaced by Numbers
    intAtk:0, wisAtk:0, chaAtk:0
  }

  p.strMod = p.strM = 0;
  p.conMod = p.conM = 0;
  p.dexMod = p.dexM = 0;
  p.intMod = p.intM = 0;
  p.wisMod = p.wisM = 0;
  p.chaMod = p.chaM = 0;

  // Ability increasements from level up
  p.abilities.ups = [];

  p.races = {}; // Races and template
  p.classes = {}; // Heroic Class, Paragon Class, Epic Destiny
  p.features = {}; // Features
  p.powers = {}; // Powers
  p.attacks = []; // Basic attacks
  p.feats = {}; // Feats
  p.equipments = {}; // Equipments
  p.hooks = {}; // External hooks, one of the few things not reset on update

  p.misc = { name: '', gender: 'female', height:'', weight:'', age:'', avatar:'', desc:'', xp:0, money:'', deity:'' }; // Misc info
  p.keywords = []; // Character keywords...
  p.languages = {}; // Array of languages
  p.alignment = 'u'; // Alignment: lg, g, u, e, ce
//  p.deity = '';  // Parton deity, either match a deity's text or some custom god
  p.vision = 0; // 0 = Normal, 1 = Low, 2 = Dark

  p.skills = {}; // Final general ability check and skill rank in Number ; Skill training stored in class, not here
  p.trainedSkills = {}; // Trained skills, Fully populated Selectable list
  p.armour = {}; // Fully populated selectable list
  p.proficiencies = {}; // Selectable list

  p.lv = 1;

  p.stats = {
    ac : 0, // new chargen.Rule.Number(0),
    fort : 0, //new chargen.Rule.Number(0),
    ref : 0, //new chargen.Rule.Number(0),
    will : 0, //new chargen.Rule.Number(0),

    hp : 0, // Hit point
    hshp : 0, // Healing Surge HP
    hs : 0, // Healing Surge per day
    bloodied : 0, // Bloodied hp

    speed : 6 //new chargen.Rule.Number(6), // Speed
  };
  p.counts = {
    language_count : 0,
    hands : 2
  };

  p.temp = {}; // Temporary data cleared on every update reset


  p.update = function() {
    let i = 0;
    let lv = this.lv;
    let half_lv = Math.floor(lv/2);
    this.counts.halfLv = half_lv;
    this.counts.tier = lv <= 10 ? 0 : lv <= 20 ? 1 : 2; // Heroic = 0, Paragon = 1, Epic = 2
    this.counts.epic = lv <= 20 ? 0 : 1;
    this.counts.halfTier5 = lv < 5 ? 0 : lv < 11 ? 1 : lv < 15 ? 2 : lv < 21 ? 3 : lv < 25 ? 4 : 5; // 0:1-4, 1:5-10, 2:11-14
    this.counts.halfTier6 = lv < 6 ? 0 : lv < 11 ? 1 : lv < 16 ? 2 : lv < 21 ? 3 : lv < 26 ? 4 : 5; // 0:1-5, 1:6-10, 2:11-15
    this.counts.halfTier16 = lv < 16 ? 0 : 1;
    let temp = p.temp = {};

    this._updates('updateInit', this);

    // update ability scores
    let abilities = this.abilities;
    for each (let stat in chargen.STATS)   abilities[stat] = abilities[stat+'Base'] + abilities[stat+'Adj']; // Recalc base score
    let ability_up_count = ( Math.floor((lv%10)/4) + this.counts.tier*2 + (lv%10==0 ? 2: 0) )*2;
    abilities.ups.length = ability_up_count;
    for (i = 0; i < ability_up_count; i++)
      if (abilities.ups[i])
         ++abilities[abilities.ups[i]];
    if (this.counts.tier)
      for each (let stat in chargen.STATS)
        this.abilities[stat] += this.counts.tier
    this._updates('updateAbilities', this);
    for each (let stat in chargen.STATS) {
      this[stat+'M'] = this[stat+'Mod'] = !abilities[stat] ? 0 : Math.floor((abilities[stat]-10)/2); // Calc ability modifiers
      abilities[stat+'Atk'] = new chargen.Rule.Number(stat+'M');
      abilities[stat+'Atk'].addBonus(half_lv, 'level', 'level');
      abilities[stat+'Atk'].properties.push('modifier');
    }

    // update features
    temp.features = this.features;
    this.features = {};
    this._updates('updateFeatures', this);
    this._cloneSlots('features');

    // update powers
    let power_count = {
      atwill: [1, 1],
      encounter: [i for each (i in chargen.ENCOUNTER_POWERS) if (i <= lv) ].splice(0, 3),
      daily: [i for each (i in chargen.DAILY_POWERS) if (i <= lv) ].splice(0, 3),
      utility: [i for each (i in chargen.UTILITY_POWERS) if (i <= lv) ]
    };
    temp.powers = this.powers;
    this.powers = {};
    for each (let [type, slots] in Iterator(power_count)) {
      for (i = slots.length; i > 0; i--) {
        this.setPowerSlot(type+i, new chargen.System.Power[type+'ClassPowerSlot']());
        this.powers[type+i].level = slots[slots.length-i];
      }
    }
    this._updates('updatePowers', this);
    this._cloneSlots('powers');

    // update feats
    temp.feats = this.feats;
    this.feats = {};
    let feat_count = half_lv + Math.floor((lv-1)/10) + 1;
    for (i = 1; i <= feat_count; i++)
      this.setFeatSlot('feat'+i, new chargen.System.Feat.FeatSlot());
    this._updates('updateFeats', this);
    this._cloneSlots('feats');

    // Equipmen updates
    temp.equipments = this.equipments;
    this.equipments = {};
    for each (let slot in chargen.EQ_SLOTS)
      this.setEquipmentSlot(slot, new chargen.System.Equipment[slot+'Slot']());
    this._updates('updateEquipments', this);
    this._cloneSlots('equipments');

    // Calculate hands used in melee and ranged combat, useful for validation
    this.counts.hands = 2;
    this.counts.melee_hands = this.counts.ranged_hands = this.getSlotProp('equipments', 'arms', 'hand', 0);
    this.counts.melee_hands += this.getSlotProp('equipments', 'melee', 'hand', 0) + this.getSlotProp('equipments', 'offhand', 'hand', 0);
    this.counts.ranged_hands += this.getSlotProp('equipments', 'ranged', 'hand', 0);

    // update defenses
    abilities.half_lv = half_lv;
    this.stats.initiative = new chargen.Rule.Number(0).addBonus(half_lv, 'level', 'level').addBonus(this.dexMod, 'dex', 'ability');
    this.stats.fort = new chargen.Rule.Number(10).addBonus(half_lv, 'level', 'level').addBonus(Math.max(this.strMod, this.conMod), this.strMod >= this.conMod ? 'str' : 'con', 'ability');
    this.stats.ref  = new chargen.Rule.Number(10).addBonus(half_lv, 'level', 'level').addBonus(Math.max(this.dexMod, this.intMod), this.dexMod >= this.intMod ? 'dex' : 'int', 'ability');
    this.stats.will = new chargen.Rule.Number(10).addBonus(half_lv, 'level', 'level').addBonus(Math.max(this.wisMod, this.chaMod), this.wisMod >= this.chaMod ? 'wis' : 'cha', 'ability');
    this.stats.ac   = new chargen.Rule.Number(10).addBonus(half_lv, 'level', 'level').addBonus(Math.max(this.dexMod, this.intMod), this.dexMod >= this.intMod ? 'dex' : 'int', 'ability');
    this.stats.hp   = abilities.con + this.classes.heroic.basehp + (this.lv-1) * this.classes.heroic.hp;
    this.stats.hshp = new chargen.Rule.Number( Math.floor(this.stats.hp/4) );
    this.stats.hs   = new chargen.Rule.Number( this.conMod + this.classes.heroic.hs );
    this._updates('updateDefenses', this);
    this.stats.bloodied = Math.floor(this.stats.hp/2);

    // update basic misc stats
    this.stats.speed = new chargen.Rule.Number(this.races.base.speed);
    this.stats.size = this.races.base.size || 0;
    this.keywords = [];
    this._updates('updateMisc', this);

    // update skills
    this.trainedSkills = {};
    for each (let cls in this.classes) cls.skill_slot = chargen.Classes[cls.id].prototype.skill_slot;
    let Number = chargen.Rule.Number;
    for each (let [skill, rank] in Iterator(chargen.Skills)) {
      this.skills[skill] = new Number(half_lv + this[chargen.Skills[skill]+'Mod']);
      this.trainedSkills[skill] = chargen.LIST.UNAVAILABLE;
    }
    for each (let stat in chargen.STATS) this.skills[stat] = new Number(half_lv + this[stat+'Mod']); // General stat-based skill
    this._updates('updateSkills', this);
    for each (let [skill,status] in Iterator(this.trainedSkills))
      if (status >= chargen.LIST.SELECTED)
        this.skills[skill].addBonus(5, 'trained', 'trained');

    // Update language, armour, and weapon prof
    this.languages = {};
    this.counts.language_count = 0;
    for each (let [book, langs] in Iterator(chargen.LANGUAGES))
      for each (let [lang, script] in Iterator(langs))
        this.languages[lang] = chargen.LIST.AVAILABLE;
    this.armour = {};
    for each (let a in chargen.ARMOURS) this.armour[a] = chargen.LIST.UNAVAILABLE;
    this.proficiencies = {};
    this._updates('updateProfs', this);
    if (this.counts.language_count < 3) {
      // No outter plane languages with initial racial languages, please
      if (this.languages.supernal == chargen.LIST.AVAILABLE) this.languages.supernal = chargen.LIST.UNAVAILABLE;
      if (this.languages.abyssal  == chargen.LIST.AVAILABLE) this.languages.abyssal  = chargen.LIST.UNAVAILABLE;
    }

    // Create basic attacks
    this.attacks = [];
    this._updates('updateAttacks', this);
    for each (let atk in this.attacks) {
      atk.char = this;
      atk.reset();
    }

    // Number updates
    this._updates('updateFFPStats', this); // Most effects should be here
    this._updates('updateFFPStats2', this); // Secondary effects including Dwarf's speed

    //this._updates('updateFinal', this); // Absolutely cleanup effects

    // clean up
//    p.temp = {};
  };

  // Clone slot options. Type can be 'features', 'feats', 'powers', or 'equipments'
  p._cloneSlots = function(type) {
    var subject = this[type];
    var olds = this.temp[type];
    var total = 0, selected = 0;
    for each (let [name, slot] in Iterator(subject)) {
      ++total;
      if (olds[name]) {
        let old = olds[name];
        if (old.selection) {
          slot.selection = old.selection;
        }
      }
      if (slot.selection) {
        ++selected;
        if (slot.selection.reset)
          slot.selection.reset();
      }
    }
    this.counts[type + '_total'] = total;
    this.counts[type + '_count'] = selected;
  };

  // Set a feature
  p.setFeature = function(id, p) {
    p.char = this;
//    p.reset();
    p.rules = chargen.Rule.parseAll(p.text, this);
    p.text = cloneObject(p.text);
    this.features[id] = { selection: p, char:this };
  };

  // Set a fixed feat / power
  p.setFeat = function(id, p) { this.setFPE('feat', id, p); };
  p.setPower = function(id, p) { this.setFPE('power', id, p); };
  p.setEquipment = function(id, p) { this.setFPE('equipment', id, p); };

  // Set a feat / power slot
  p.setFeatSlot = function(id, slot, hint) { this.setFPESlot('feat', id, slot, hint); };
  p.setPowerSlot = function(id, slot, hint) { this.setFPESlot('power', id, slot, hint); };
  p.setEquipmentSlot = function(id, slot, hint) { this.setFPESlot('equipment', id, slot, hint); };

  // Set Feat / Power / Equipment
  p.setFPE = function(type, id, p) {
    let slot = this[type+'s'][id] || null;
    if (!slot) {
      slot = { selection: p, noOptions: true, char:this };
      this.setFPESlot(type, id, slot);
      p.char = this;
    } else {
      if (p) {
        p.char = this;
        slot.selection = p;
      } else
        delete slot.selection;
    }
  };

  // Set Feat / Power / Equipment Slot
  p.setFPESlot = function(type, id, slot, hint) {
    var p = slot.selection;
    if (!slot.id) slot.id = id;
    if (hint) {
      slot._hint = '<b>'+hint.title+'</b>';
      if (hint.desc || hint.hint) slot._hint += '<br>'+(hint.hint || hint.desc);
      slot.__defineGetter__('raw_hint', function(){return this.selection || this._hint});
    }
    if (p) {
      if (!p.char) p.char = this;
      if (!slot.type)
        if (type == 'power' && p.type=='attack') slot.type = p.frequency;
        else if (p.type) slot.type = p.type;
    }
    if (!slot.char) slot.char = this;
    this[type+'s'][id] = slot;
  };

  /** Get property of a slot, with default value */
  p.getSlotProp = function(field, slot, prop, ifnone) {
    let result = this[field][slot];
    if (!result) return ifnone;
    result = result.selection || undefined;
    if (!result) return ifnone;
    result = result[prop] || undefined;
    return result == 'undefined' ? ifnone : result;
  };

  /** Check if character is of a race, class, or blood */
  p.is = function(target) {
    target = target.toLowerCase();
    return this.keywords.indexOf(target) >= 0;
  };

  /** Set selected status of a selectable list, if not already selected at a higher state */
  p.setSelected = function(fields, field, state) {
    if (!this[fields][field] || this[fields][field] < state)
      this[fields][field] = state;
  };

  /** Go through hooks, races, classes, and FFPE to invoke given function with given param */
  p._updates = function(function_name, param) {
    if (navigator.userAgent.indexOf('Firefox/2.') && location.href.indexOf('http://localhost') == 0) {
      this._non_safe_updates(function_name, param); // I need full error stack trace.
      return;
    }
    for each (let components in [this.hooks, this.races, this.classes])
      for each (let m in components)
        if (m[function_name]) try { m[function_name](param); } catch(err) { cerror(err); }
    for each (let slots in [this.features, this.feats, this.powers, this.equipments])
      for each (let m in slots) {
        if (m[function_name]) try { m[function_name](param); } catch(err) { cerror(err); }
        if (m.selection && m.selection[function_name])
          try {m.selection[function_name](param);  } catch(err) { cerror(err); }
      }
  };

  /** Same as update, except that no try-catch blocks error stack trace */
  p._non_safe_updates = function(function_name, param) {
    for each (let components in [this.hooks, this.races, this.classes])
      for each (let m in components)
        if (m[function_name]) m[function_name](param);
    for each (let slots in [this.features, this.feats, this.powers, this.equipments])
      for each (let m in slots) {
        if (m[function_name]) m[function_name](param);
        if (m.selection && m.selection[function_name])
          m.selection[function_name](param);
      }
  };

  /** Set a race, template, or class.  Its FFP should be already initialised */
  p.setRaceClass = function(type, position, newRaceClass) {
    let original = this[type][position] || null;
    if (original && original.id == newRaceClass.id) return;
    newRaceClass.char = this;
    this[type][position] = newRaceClass;
  };

};

chargen.Character.createNew = function() {
  let chr = new chargen.Character();
  chr.setRaceClass('races', 'base', new chargen.Races.Dragonborn());
  chr.setRaceClass('classes', 'heroic', new chargen.Classes.Cleric());
//  chr.setEquipment('melee', new chargen.Equipments.BaseWeapon.Unarmed());
//  chr.update(); // Would be done by UI anyway. Thus far no point in wasting CPU cycles.
  return chr;
}