chargen.System.Feat = {
  // Standard slot comming from level progression
  FeatSlot: function(){},
  // Standard text replacing function
  TextReplacingFeat: function(power_id, feature_id, fields) {
    this.validate = function(chr) { return chr.features[feature_id] || chr.powers[power_id]; }
    if (feature_id)
      this.updateFFPStats = function(chr) {
        if (chr.features[feature_id])
          for each (let field in fields)
            chr.features[feature_id].selection.rules[field].Op('replace', this.text.find, this.text.replace)
      };
    if (power_id)
      this.resetPower = function(power){
        if (power.id == power_id)
          for each (let field in fields)
            power.rules[field].Op('replace', this.text.find, this.text.replace);
      };
  },

  initFeat: function(pgroup, pname, p) {
    let base = p;
    if (!p) {
      p = function(){};
      chargen.Feats[pgroup][pname] = base = p;
      p = p.prototype;
    } else {
      if (!p.prototype) {
        chargen.Feats[pgroup][pname] = base = function() {};
        base.prototype = p;
      } else {
        p = p.prototype;
      }
      if (p._initialised) return;
    }
    p.id = pname;
    p._type = 'feat';
    p._Types = 'Feats';
    if (!p.book)  p.book   = 'phb';
    if (!p.group) p.group = pgroup;
    let txt = text.Feats[pgroup][pname];
    if (chargen.Races[pgroup]) {
      let feats = chargen.Races[pgroup].prototype.feats;
      feats.push(base);
      if (!p.owner) p.owner = 'race_'+pgroup;
      txt.prerequisite = text['race_'+pgroup] + ((txt.prerequisite) ? ', '+txt.prerequisite : '');
    } else if (chargen.Classes[pgroup]) {
      let feats = chargen.Classes[pgroup].prototype.feats;
      feats.push(base);
      if (!p.owner) p.owner = 'class_'+pgroup;
      txt.prerequisite = text['class_'+pgroup] + ((txt.prerequisite) ? ', '+txt.prerequisite : '');
    } else {
      // Heroic / multiclass?
      chargen.generalFeats.push(base);
      if (!p.owner) p.owner =  pgroup.toLowerCase();
    }
    if (!p.multi) p.multi = false;
    if (!p.reset) p.reset = this.reset;
    p.text = text.Feats[pgroup][pname];
    p.rules = chargen.Rule.parseAll(p.text, p);
    p._initialised = true;
    if (p.init) { p.init(); delete p.init; }
  },

  /* Reset feat stat to initial, unadjusted state */
  reset: function() {
    this.text = cloneObject(text.Feats[this.group][this.id]);
    this.rules = chargen.Rule.parseAll(this.text, this);
//    let proto = chargen.Feats[this.group][this.id].prototype;
//    for each (let field in ['hit','hit2','hit3','miss','miss2','miss3'])
//      if (proto[field]) this[field] = proto[field];
  },

  damageRollInc: function(power, source, element1, element2) {
    let value = power.char.counts.tier+1;
    if (element1 || element2)
      if (!power.energy || (power.energy.indexOf(element1) < 0 && power.energy.indexOf(element2) < 0) )
        return;
    if (!power.rules) return;
    for each (let rule in power.rules) {
      let target = rule.parts || [rule];
      for each (let part in target)
        if (part.properties && part.properties.indexOf('damage') >= 0 && part.properties.indexOf('roll') >= 0)
          if ( (!element1 && !element2) || (part.properties.indexOf(element1) >= 0 || part.properties.indexOf(element2) >= 0) )
            part.addBonus(value, source, 'feat');
    }
  },

  effectNumberInc: function(power, source, effect, value, effectFilter) {
    if (effectFilter && (!power.effect || power.effect.indexOf(effectFilter) < 0 )) return;
    if (!power.rules) return;
    for each (let rule in power.rules) {
      if (rule.parts) {
        for each (let part in rule.parts)
          if (part.properties && part.properties.indexOf(effect) >= 0)
            part.addBonus(value, source);
      } else if (rule.properties && rule.properties.indexOf(effect) >= 0) {
        rule.addBonus(value, source);
      }
    }
  }
};
chargen.System.Feat.FeatSlot.prototype = {
  bonus_options: [],
  get options() {
    if (!this.char.temp.featSlotList) {
      let tempList = chargen.generalFeats.concat();
      for each (let race in this.char.races) if (race.feats) tempList = tempList.concat(race.feats);
      for each (let cls in this.char.classes) if (cls.feats) tempList = tempList.concat(cls.feats);
      this.char.temp.featSlotList = tempList;
    }
    return this.char.temp.featSlotList.concat(this.bonus_options);
  }
};

let (feat = chargen.Feats) {
  feat.Heroic = {};
  feat.Paragon = {};
  feat.Epic = {};
  feat.Multiclass = {};
  feat.Power = {};

  feat.Human = {};
  feat.Dragonborn = {};
  feat.Dwarf = {};
  feat.Eladrin = {};
  feat.Elf = {};
  feat.HalfElf = {};
  feat.Halfling = {};
  feat.Tiefling = {};

  feat.Cleric = {};
  feat.Fighter = {};
  feat.Paladin = {};
  feat.Ranger = {};
  feat.Rogue = {};
  feat.Warlock = {};
  feat.Warlord = {};
  feat.Wizard = {};

  feat.Dragonborn.DragonbornSenses = {
    updateMisc : function(chr) {
      chr.vision = Math.max(1, chr.vision);
      chr.skills.perception.addBonus(1, this, 'feat');
    } };

  feat.Dragonborn.EnlargedDragonBreath = {
    validate : function(chr) { return chr.powers.DragonbornBreath; },
    resetPower : function(power) {
      if (power.id == 'DragonbornBreath') power.area_size = this.text.replace;
    } };

  feat.Dwarf.DwarvenWeaponTraining = {
    updateProfs: function(chr) {
      chr.setSelected('proficiencies', 'axe', chargen.LIST.MIXED);
      chr.setSelected('proficiencies', 'hammer', chargen.LIST.MIXED);
      delete chr.proficiencies.ThrowingHammer;
      delete chr.proficiencies.Warhammer;
    },
    onSetPower: function(param) {
//      if (param.eq && (param.eq.is('axe') || param.eq.is('hammer')) && param.power)
      chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 2, this, 'feat', 'axe');
      chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 2, this, 'feat', 'hammer');
    }
  };

  feat.Eladrin.EladrinSoldier = {
    updateProfs: function(chr) {
      chr.setSelected('proficiencies', 'Longsword', chargen.LIST.MIXED);
      chr.setSelected('proficiencies', 'spear', chargen.LIST.MIXED);
    },
    onSetPower: function(param) {
//      if (param.eq && (param.eq.is('Longsword') || param.eq.is('spear')) && param.power)
        chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 2, this, 'feat', 'Longsword');
        chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 2, this, 'feat', 'spear');
    }
  };

  feat.Elf.ElvenPrecision = new chargen.System.Feat.TextReplacingFeat('ElvenAccuracy', null, ['effect','summary']);

  feat.Elf.LightStep = {
    updateSkills : function(chr) {
      chr.skills.acrobatics.addBonus(1, this, 'racial');
      chr.skills.stealth.addBonus(1, this, 'racial');
    } };

  feat.Halfling.HalflingAgility = new chargen.System.Feat.TextReplacingFeat('SecondChance', null, ['effect']);

  feat.Halfling.LostInTheCrowd = function() {};

  feat.Tiefling.FerociousRebuke = new chargen.System.Feat.TextReplacingFeat('InfernalWrath', null, ['effect']);

  feat.Tiefling.HellfireBlood = {
    onSetPower : function(param) {
      let power = param.power;
      if ( (power.energy && power.energy.indexOf('fire') >= 0) ||
           (power.effect && power.effect.indexOf('fear') >= 0)
          ) {
        chargen.System.Commons.addBonusToAll(power, 'attack', 1, this, 'feat');
        chargen.System.Feat.damageRollInc(param.power, this, null, null);
      }
    } };

  feat.Paladin.HealingHands = {
    validate : function(chr) { return chr.powers.LayOnHands; },
    resetPower : function(power) {
      if (power.id == 'LayOnHands') chargen.Rule.getFirstNumber(power.rules.effect).addBonus('chaM', this);
    } };

  feat.Heroic.Alertness = { updateSkills : function(chr) { chr.skills.perception.addBonus(2, this, 'feat'); } };

  feat.Heroic.ArmorProficiencyLeather = {
    validate : function(chr) { return (chr.armour.Leather || 0) <= chargen.LIST.MIXED; },
    updateProfs : function(chr) { chr.setSelected('armour', 'Leather', chargen.LIST.MIXED); } };

  feat.Heroic.ArmorProficiencyHide = {
    validate : function(chr) { return (chr.armour.Hide || 0) <= chargen.LIST.MIXED && chr.armour.Leather >= chargen.LIST.SELECTED && chr.abilities.str >= 13 && chr.abilities.con >= 13; },
    updateProfs :function(chr) { chr.setSelected('armour', 'Hide', chargen.LIST.MIXED); } };

  feat.Heroic.ArmorProficiencyChainmail = {
    validate : function(chr) { return (chr.armour.Chain || 0) <= chargen.LIST.MIXED && chr.abilities.str >= 13 && chr.abilities.con >= 13 &&
    (chr.armour.Leather >= chargen.LIST.SELECTED || chr.armour.Hide >= chargen.LIST.SELECTED); },
    updateProfs :function(chr) { chr.setSelected('armour', 'Chain', chargen.LIST.MIXED); } };

  feat.Heroic.ArmorProficiencyScale = {
    validate : function(chr) { return (chr.armour.Scale || 0) <= chargen.LIST.MIXED && chr.armour.Chain >= chargen.LIST.SELECTED && chr.abilities.str >= 13 && chr.abilities.con >= 13; },
    updateProfs :function(chr) { chr.setSelected('armour', 'Scale', chargen.LIST.MIXED);  } };

  feat.Heroic.ArmorProficiencyPlate = {
    validate : function(chr) { return (chr.armour.Plate || 0) <= chargen.LIST.MIXED && chr.armour.Scale >= chargen.LIST.SELECTED && chr.abilities.str >= 15 && chr.abilities.con >= 15; },
    updateProfs :function(chr) { chr.setSelected('armour', 'Plate', chargen.LIST.MIXED); } };

  feat.Heroic.AstralFire = {
    validate : function(chr) { return chr.abilities.cha >= 13 && chr.abilities.dex >= 13; },
    onSetPower : function(param) {chargen.System.Feat.damageRollInc(param.power, this, 'fire', 'radiant'); } };

  feat.Ranger.AgileHunter = {
    validate : function(chr) { return chr.abilities.dex >= 15 && chr.features.HuntersQuarry; },
  }

  feat.Rogue.Backstabber = new chargen.System.Feat.TextReplacingFeat(null, 'SneakAttack', ['desc','feature']);

  feat.Heroic.BladeOpportunist = { validate : function(chr) { return chr.abilities.str >= 13 && chr.abilities.dex >= 13; } };

  feat.Heroic.BurningBlizzard = {
    validate : function(chr) { return chr.abilities.int >= 13 && chr.abilities.wis >= 13; },
    onSetPower : function(param) {chargen.System.Feat.damageRollInc(param.power, this, 'acid', 'cold'); } };

  feat.Heroic.CombatReflexes = { validate : function(chr) { return chr.abilities.dex >= 13; } };

  feat.Heroic.DarkFury = {
    validate : function(chr) { return chr.abilities.con >= 13 && chr.abilities.wis >= 13; },
    onSetPower : function(param) {chargen.System.Feat.damageRollInc(param.power, this, 'necrotic', 'psychic'); } };

  feat.Heroic.DefensiveMobility = function() {};

  feat.Fighter.DistractingShield = {
    validate : function(chr) { return chr.abilities.con >= 15 && chr.features.CombatChallenge; },
    updateFFPStats : function(chr) {
      if (chr.features.CombatChallenge && chr.equipments.arms.selection && chr.equipments.arms.selection.type == 'shield')
        for each (let field in ['desc', 'feature'])
          chr.features.CombatChallenge.selection.rules[field].Op('replace', this.text.find, this.text.replace)
    }
  };

  feat.Heroic.Durable = { updateDefenses : function(chr) { chr.stats.hs.addBonus(2, this); } };

  feat.Heroic.EscapeArtist = { validate : function(chr) { return chr.trainedSkills.acrobatics >= chargen.LIST.SELECTED; } };

  feat.Wizard.ExpandedSpellbook = {
    validate : function(chr) { return chr.abilities.wis >= 13 && chr.features.Spellbook; },
    updatePowers : function(chr) {
      for each (let [name, slot] in Iterator(chr.powers))
        if (startsWith(name, 'daily') && !isNaN(+(name.slice(5))) ) {
          let book = new chargen.System.Power.dailyClassPowerSlot();
          book.validate = function(item) { return item.group == 'Wizard' };
          book.level = slot.level;
          chr.setPowerSlot(name+'_ex_spellbook', book, text.Feats.Wizard.ExpandedSpellbook);
        }
    },
    updateDefenses : function(chr) {
      for each (let [name, slot] in Iterator(chr.powers))
        if (slot && slot.selection && slot.selection.group != 'Wizard' && startsWith(name, 'daily') && !isNaN(+(name.slice(5))))
          delete chr.powers[name+'_ex_spellbook'];
    }
  };

  feat.Heroic.FarShot = {
    validate : function(chr) { return chr.abilities.dex >= 13; },
    updateFFPStats : function(chr){
      for each (let [,weapon] in Iterator(chr.equipments)) {
        if (weapon.selection && weapon.selection.normalrange) {
          weapon = weapon.selection;
          if (weapon.property.heavythrown || weapon.property.heavythrown) ;
          else { // For now, just assume everything not thrown is shot
            if (weapon.longrange == +weapon.longrange) weapon.longrange = +weapon.longrange + 5;
            if (weapon.normalrange == +weapon.normalrange) weapon.normalrange = +weapon.normalrange + 5;
          }
        }
      }
    } };

  feat.Heroic.FarThrow = {
    validate : function(chr) { return chr.abilities.str >= 13; },
    updateFFPStats : function(chr){
      for each (let [,weapon] in Iterator(chr.equipments)) {
        if (weapon.selection && weapon.selection.normalrange) {
          weapon = weapon.selection;
          if (weapon.property.heavythrown || weapon.property.heavythrown) {
            if (weapon.longrange == +weapon.longrange) weapon.longrange = +weapon.longrange + 2;
            if (weapon.normalrange == +weapon.normalrange) weapon.normalrange = +weapon.normalrange + 2;
          }
        }
      }
    } };

  feat.Heroic.FastRunner = { validate : function(chr) { return chr.abilities.con >= 13; } };

  feat.Paladin.HealingHands = {
    validate : function(chr) { return chr.powers.LayOnHands ? true : false; },
    resetPower : function(power) {
      if (power.id == 'LayOnHands') chargen.Rule.getFirstNumber(power.rules.effect).addBonus('chaM', this);
    }
  };

  feat.Warlock.ImprovedDarkOnesBlessing = {
    validate : function(chr) { return chr.abilities.con >= 15 && chr.features.DarkOnesBlessing; },
    updateFFPStats : function(chr) {
      if (chr.features.DarkOnesBlessing) {
        chargen.Rule.getFirstNumber(chr.features.DarkOnesBlessing.selection.rules.feature).addBonus(3, this);
        for each (let field in ['desc', 'feature'])
          chr.features.DarkOnesBlessing.selection.rules[field].Op('replace', this.text.find, this.text.replace);
      }
    }
  };

  feat.Warlock.ImprovedFateOfTheVoid = new chargen.System.Feat.TextReplacingFeat(null, 'FateOfTheVoid', ['desc', 'feature']);
  feat.Warlock.ImprovedFateOfTheVoid.validate = function(chr) { return (chr.abilities.con >= 13 || chr.abilities.cha >= 13) && chr.features.FateOfTheVoid; },

  feat.Warlock.ImprovedMistyStep = {
    validate : function(chr) { return chr.abilities.int >= 13 && chr.features.MistyStep; },
    updateFFPStats : function(chr) {
      if (chr.features.MistyStep) {
        chargen.Rule.getFirstNumber(chr.features.MistyStep.selection.rules.desc).addBonus(2, this);
        chargen.Rule.getFirstNumber(chr.features.MistyStep.selection.rules.feature).addBonus(2, this);
      }
    }
  };

  feat.Heroic.ImprovedInitiative = { updateSkills : function(chr) { chr.stats.initiative.addBonus(4, this, 'feat'); } };

  feat.Heroic.JackOfAllTrade = {
    validate : function(chr) { return chr.abilities.int >= 13; },
    updateSkills : function(chr) {
      for each (let [skill,rank] in Iterator(chr.skills))
        if (chr.trainedSkills[skill] < chargen.LIST.SELECTED)
          rank.addBonus(2, this, 'feat');
    }
  };

  feat.Ranger.LethalHunter = new chargen.System.Feat.TextReplacingFeat(null, 'HuntersQuarry', ['effect', 'feature']);

  feat.Heroic.LongJumper = {
    validate : function(chr) { return chr.trainedSkills.athletics >= chargen.LIST.SELECTED; },
    updateSkills : function(chr) { chr.skills.athletics.addBonus(1, this, 'feat'); }
  };


  feat.Heroic.Linguist = {
    multi : true,
    updateProfs : function(chr) { chr.counts.language_count += 3; },
    validate : function(chr) { return chr.abilities.int >= 13; }
  };

  feat.Heroic.MountedCombat = function() {};

  feat.Heroic.NimbleBlade = { validate : function(chr) { return chr.abilities.dex >= 15; } };

  feat.Fighter.PotentChallenge = new chargen.System.Feat.TextReplacingFeat(null, 'CombatChallenge', ['desc', 'feature']);
  feat.Fighter.PotentChallenge.validate = function(chr) { return chr.abilities.con >= 15 && chr.features.CombatChallenge; },

  feat.Heroic.PowerAttack = { validate : function(chr) { return chr.abilities.str >= 15; } };

  feat.Heroic.PowerfulCharge = { validate : function(chr) { return chr.abilities.str >= 13; } };

  feat.Ranger.PreciseHunter = { validate : function(chr) { return chr.abilities.wis >= 15 && chr.features.HuntersQuarry; } };

  feat.Rogue.PressTheAdvantage = { validate : function(chr) { return chr.abilities.cha >= 15; } };

  feat.Heroic.QuickDraw = {
    validate : function(chr) { return chr.abilities.dex >= 13; },
    updateSkills : function(chr) { chr.stats.initiative.addBonus(2, this, 'feat'); }
  };

  feat.Heroic.RagingStorm = {
    validate : function(chr) { return chr.abilities.con >= 13 && chr.abilities.dex >= 13; },
    onSetPower : function(param) {chargen.System.Feat.damageRollInc(param.power, this, 'lightning', 'thunder'); }
  };

  feat.Heroic.RitualCaster = function() {};

  feat.Heroic.ShieldProficiencyLight = {
    validate : function(chr) { return chr.abilities.str >= 13 && (chr.armour.LightShield || 0) <= chargen.LIST.MIXED; },
    updateProfs : function(chr) { chr.setSelected('armour', 'LightShield', chargen.LIST.MIXED); }
  };

  feat.Heroic.ShieldProficiencyHeavy = {
    validate : function(chr) { return chr.abilities.str >= 15 && (chr.armour.LightShield || 0) >= chargen.LIST.SELECTED && (chr.armour.HeavyShield || 0) <= chargen.LIST.MIXED; },
    updateProfs :function(chr) { chr.setSelected('armour', 'HeavyShield', chargen.LIST.MIXED); }
  };

  feat.Fighter.ShieldPush = {
    validate : function(chr) { return chr.features.CombatChallenge; },
    updateFFPStats : function(chr) {
      if (chr.features.CombatChallenge && chr.equipments.arms.selection && chr.equipments.arms.selection.type == 'shield')
        for each (let field in ['desc', 'feature'])
          chr.features.CombatChallenge.selection.rules[field].Op('replace', this.text.find, this.text.replace)
    }
  };

  feat.Heroic.SkillFocus = {
    multi : true,
    get options() {
      var result = {};
      for each (let [skill,] in Iterator(chargen.Skills))
        if (chr.trainedSkills[skill] && chr.trainedSkills[skill] >= chargen.LIST.SELECTED)
          result[skill] = 'skill_'+skill;
      return result;
    },
    updateSkills : function(chr) {
      if (this.choice) chr.skills[this.choice].addBonus(3, this, 'feat');
    }
  };

  feat.Heroic.SkillTraining = {
    multi : true,
    options : {},
    updateSkills : function(chr) {
      if (this.choice) chr.setSelected('trainedSkills', this.choice, chargen.LIST.MIXED);
    }
  };
  for each (let [skill,] in Iterator(chargen.Skills)) feat.Heroic.SkillTraining.options[skill] = 'skill_'+skill;

  feat.Heroic.SureClimber = {
    validate : function(chr) { return chr.trainedSkills.athletics >= chargen.LIST.SELECTED; },
    updateSkills : function(chr) { chr.skills.athletics.addBonus(1, this, 'feat'); }
  };

  feat.Rogue.SurpriseKnockdown = { validate : function(chr) { return chr.abilities.str >= 15; } };

  feat.Warlord.TacticalAssault = { validate : function(chr) { return chr.abilities.str >= 15; } };

  feat.Heroic.Toughness = {
    updateDefenses : function(chr) {
      let old_hp = chr.stats.hp;
      chr.stats.hp += 5 + chr.counts.tier * 5;
      var d = Math.floor(chr.stats.hp/4) - Math.floor(old_hp/4);
      if (d) chr.stats.hshp.addBonus(d, this) ; // Amend healing surge
    }
  };

  feat.Heroic.TwoWeaponFighting = {
    validate : function(chr) { return chr.abilities.dex >= 13; },
    onSetPower: function(param) {
//      if (param.eq && param.eq == this.char.equipments.melee.selection && this.char.equipments.offhand.selection)
        if (param.power.attack_type == 'melee' && this.char.equipments.offhand.selection)
          chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 1, this, 'untyped', this.char.equipments.melee.selection);
    }
  };

  feat.Heroic.TwoWeaponDefense = {
    validate : function(chr) {
      if (chr.abilities.dex < 13) return false;
      for each (let feat in chr.feats)
        if (feat.selection && feat.selection.id == 'TwoWeaponFighting') return true;
      return false;
    },
    updateSkills : function(chr) {
      if (chr.equipments.melee.selection && chr.equipments.offhand.selection) {
        chr.stats.ac.addBonus(1, this, 'shield');
        chr.stats.ref.addBonus(1, this, 'shield');
      }
    }
  };

  feat.Heroic.WeaponFocus = {
    multi: true,
    get options() {
      if (this._options) return this._options;
      this._options = [];
      for each (let group in chargen.System.Equipment.list_WeaponGroup)
        this._options[group] = 'weapon_'+group;
      return this._options;
    },
    onSetPower: function(param) {
      if (this.choice) // && param.eq && param.eq.is(this.choice))
        chargen.System.Commons.addBonusForEq(param.power, 'hit_damage', 1+this.char.counts.tier, this, 'feat', this.choice);
    }
  };

  feat.Heroic.WeaponProficiency = {
    multi : true,
    get options() {
      let options = {};
      if (!this.char) return options;
      for each (let weapon in chargen.Equipments.BaseWeapon) {
        let p = weapon.prototype;
        if (p && p.id && !p.classes.improvised && (p.id == this.choice || !p.checkProficiency(this.char)) ) {
          let id = p.id;
          options[id] = 'baseweapon_'+id;
          if (!text['baseweapon_'+id]) text['baseweapon_'+id] = p.text.title;
        }
      }
      return options;
    },
    updateProfs : function(chr) { if (this.choice) chr.setSelected('proficiencies', this.choice, chargen.LIST.MIXED); }
  };

  feat.Heroic.Wintertouched = function() {};


  chargen.System.Feat.FeaturedPowerFeat = function(feature, power, exclusive_group) {
    this.req_feature = feature;
    this.feat_power = power;
    if (exclusive_group) {
      this.exclusive_group = exclusive_group;
      this.validate = function(chr, slot) {
        if (!chr.features[this.req_feature]) return false;
        if (slot.selection && slot.selection.exclusive_group == this.exclusive_group) return true;
        for each (let slot in chr.feats)
          if (slot.selection && slot.selection.exclusive_group == this.exclusive_group) return false;
        return true;
      }
    }
  };
  chargen.System.Feat.FeaturedPowerFeat.prototype = {
    validate : function(chr) { return chr.features[this.req_feature]; },
    updatePowers : function(chr) {
      let [group, id] = this.feat_power.split('.', 2);
      chr.setPower(id, new chargen.Powers[group][id]());
    }
  };

  feat.Power.ArmorOfBahamut = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.ArmorOfBahamut', 'CDPowerFeat');
  feat.Power.AvandrasRescue = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.AvandrasRescue', 'CDPowerFeat');
  feat.Power.CorellonsGrace = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.CorellonsGrace', 'CDPowerFeat');
  feat.Power.HarmonyOfErathis = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.HarmonyOfErathis', 'CDPowerFeat');
  feat.Power.IounsPoise = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.IounsPoise', 'CDPowerFeat');
  feat.Power.KordsFavor = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.KordsFavor', 'CDPowerFeat');
  feat.Power.MelorasTide = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.MelorasTide', 'CDPowerFeat');
  feat.Power.MoradinsResolve = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.MoradinsResolve', 'CDPowerFeat');
  feat.Power.PelorsRadiance = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.PelorsRadiance', 'CDPowerFeat');
  feat.Power.RavenQueensBlessing = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.RavenQueensBlessing', 'CDPowerFeat');
  feat.Power.SehaninesReversal = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Feat.SehaninesReversal', 'CDPowerFeat');
  //feat.Cleric.PowerOfAmaunator = new chargen.System.Feat.FeaturedPowerFeat('ChannelDivinity', 'Cleric.PowerOfAmaunator', 'CDPowerFeat');

  /** Multiclass base feat, handles single stat requirement, add trained skill, keywords, implements, etc. */
  chargen.System.Feat.MulticlassFeat = function(stat, skill, class, powers, features) {
    this.multiclass = class;
    if (stat) this.validate = function(chr, slot) { return chr.abilities[stat] >= 13 && this.validateMultiAndClass(chr, slot); };
    else this.validate = this.validateMultiAndClass;
    if (skill === true) skill = chargen.Classes[class].prototype.class_skills;
    if (skill)
      if (typeof(skill) == 'string')
        this.updateSkills = function(chr) { chr.setSelected('trainedSkills', skill, chargen.LIST.MIXED) };
      else {
        this.options2 = {};
        for each (let s in skill) this.options2[s] = 'skill_'+s;
        this.updateSkills = function(chr) { if (this.choice2) chr.setSelected('trainedSkills', this.choice2, chargen.LIST.MIXED) };
      }
    if (powers) {
      // Add multiclass powers
      if (powers !== true) this.powers = powers;
      this.updatePowers = function(chr) {
        for each (let power in this.powers) {
          let [group, id] = power[1].split('.', 2);
          let p = new chargen.Powers[group][id]();
          chr.setPowerSlot(id+'Multiclass', new chargen.System.Power.FrequencyPowerSlot(power[0], p) );
        }
      };
    }
  };
  chargen.System.Feat.MulticlassFeat.prototype = {
    updateMisc : function(chr) { chr.keywords.push('multiclass'); chr.keywords.push(this.multiclass.toLowerCase()); },
    /** Check that character is not already in current class AND not already multiclassed AND allow self */
    validateMultiAndClass : function(chr, slot) {
      if (chr.classes.heroic.id == this.multiclass) return false;
      if (slot.selection && slot.selection.multiclass) return true;
      for each (let feat in chr.feats) if (feat.selection && feat.selection.multiclass) return feat.selection.id == this.id;
      return true;
    },
    updateValidations : function(chr) {
      for each (let slot in chr.feats)
        if (slot.bonus_options)
          slot.bonus_options = slot.bonus_options.concat(chargen.Classes[this.multiclass].prototype.feats)
    }
  };


  feat.Multiclass.InitiateOfTheFaith = new chargen.System.Feat.MulticlassFeat('wis', 'religion', 'Cleric', [['daily', 'Cleric.HealingWord']]);

  feat.Multiclass.InitiateOfTheSword = new chargen.System.Feat.MulticlassFeat('str', true, 'Fighter');
  feat.Multiclass.InitiateOfTheSword.options = { 1:'hand_1_handed', 2:'hand_2_handed' };

  feat.Multiclass.SoldierOfTheFaith = new chargen.System.Feat.MulticlassFeat('str', true, 'Paladin', [['encounter', 'Paladin.DivineChallenge']]);
  feat.Multiclass.SoldierOfTheFaith.validate = function(chr, slot) { return chr.abilities.str >= 13 && chr.abilities.cha >= 13  && this.validateMultiAndClass(chr, slot); };

  feat.Multiclass.WarriorOfTheWild = new chargen.System.Feat.MulticlassFeat('str', true, 'Ranger');
  feat.Multiclass.WarriorOfTheWild.validate = function(chr, slot) { return (chr.abilities.str >= 13 || chr.abilities.dex >= 13)  && this.validateMultiAndClass(chr, slot); };
  feat.Multiclass.WarriorOfTheWild.updateFeatures = function(chr) {
    this.hasFeature = chr.features.HuntersQuarry ? true : false;
    if (!this.hasFeature) chr.setFeature('HuntersQuarry', new chargen.Features.Ranger.HuntersQuarry());
  }
  feat.Multiclass.WarriorOfTheWild.updateFFPStats = function(chr) {
    if (!this.hasFeature) chr.features.HuntersQuarry.selection.rules.feature.Op('replace', this.text.find, this.text.replace);
  }

  feat.Multiclass.SneakOfShadows = new chargen.System.Feat.MulticlassFeat('dex', 'thievery', 'Rogue');
  feat.Multiclass.SneakOfShadows.updateFeatures = function(chr) {
    this.hasFeature = chr.features.SneakAttack ? true : false;
    if (!this.hasFeature) chr.setFeature('SneakAttack', new chargen.Features.Rogue.SneakAttack());
  }
  feat.Multiclass.SneakOfShadows.updateFFPStats = function(chr) {
    if (!this.hasFeature) chr.features.SneakAttack.selection.rules.feature.Op('replace', this.text.find, this.text.replace);
  }

  feat.Multiclass.PactInitiate = new chargen.System.Feat.MulticlassFeat('cha', true, 'Warlock', true);
  feat.Multiclass.PactInitiate.options = ['Fey', 'Infernal', 'Star'];
  feat.Multiclass.PactInitiate.__defineGetter__('powers', function() {
    if (this.choice) return [['encounter', 'Warlock.' + (this.choice=='Fey' ? 'EyeBite' : (this.choice=='Infernal'? 'HellishRebuke' : 'DireRadiance' ))]];
    else return []; } );

  feat.Multiclass.StudentOfBattle = new chargen.System.Feat.MulticlassFeat('str', true, 'Warlord', [['daily', 'Warlord.InspiringWord']]);

  feat.Multiclass.ArcaneInitiate = new chargen.System.Feat.MulticlassFeat('int', 'arcana', 'Wizard', true);
  feat.Multiclass.ArcaneInitiate.init = function() {
    this.options = {};
    for each (let power in chargen.Powers.Wizard)
      if (power.prototype.level == 1 && power.prototype.frequency == 'atwill')
        this.options[power.prototype.id] = power.prototype.text.title;
  }
  feat.Multiclass.ArcaneInitiate.__defineGetter__('powers', function() { if (this.choice) return [['encounter', 'Wizard.'+this.choice]]; else return []; } );



  chargen.System.Feat.SwapPowerFeat = function(type, level) {
    this.swap = type;
    this.req_level = level;
  }
  chargen.System.Feat.SwapPowerFeat.prototype = {
    validate : function(chr) { return chr.lv >= this.req_level && chr.is('multiclass'); },
    updateValidations : function(chr) {
      let multiclass = chargen.System.cap(chr.keywords[chr.keywords.indexOf('multiclass')+1]);
      let slots = chr.powers;
      for each (let slot in slots) // scan for swapped power
        if (slot.type == this.swap && slot.bonus_options && slot.selection && endsWith(slot.selection.owner, multiclass)) {
          slots = [slot]; // Allow replacement only in this slot, if found
          break;
        }
      for each (let slot in slots) if (slot.type == this.swap && slot.bonus_options )
        slot.bonus_options = slot.bonus_options.concat(chargen.Classes[multiclass].prototype[slot.type]);
    }
  }

  feat.Multiclass.NovicePower = new chargen.System.Feat.SwapPowerFeat('encounter', 4);
  feat.Multiclass.AcolytePower = new chargen.System.Feat.SwapPowerFeat('utility', 8);
  feat.Multiclass.AdeptPower = new chargen.System.Feat.SwapPowerFeat('daily', 10);

}