MediaWiki:Gadget-dps-sandbox-core.js: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
(Created page with "// OSRS DPS Calculator // Version 1.0 // Made by Gau Cho // // // Thanks to Bitterkoekje, Elessar2, Gaz, Koekenpan, riblet15, TehKittyCat // import Gadget-dps-data.js // import Gadget-dps-data2.js →‎globals equipment, monster, $, rswiki, OO: →‎jshint es3: true: 'use strict'; //Debug var ticTime = performance.now(); function tic() { ticTime = performance.now(); } function toc() { var ticTocTime = performance.now() - ticTime; console.log('Milliseconds...")
 
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 17: Line 17:
var ticTime = performance.now();
var ticTime = performance.now();
function tic() {
function tic() {
ticTime = performance.now();
ticTime = performance.now();
}
}
function toc() {
function toc() {
var ticTocTime = performance.now() - ticTime;
var ticTocTime = performance.now() - ticTime;
console.log('Milliseconds elapsed: ' + ticTocTime);
console.log('Milliseconds elapsed: ' + ticTocTime);
return ticTocTime;
return ticTocTime;
}
}
//End Debug
//End Debug
Line 28: Line 28:
//'Chivalry': {AttackBonus:1.15, StrengthBonus:1.18, DefenceBonus:1.2, DrainSpeed:24, vi:0, TurnsOff:['Thick Skin','Burst of Strength','Clarity of Thought','Rock Skin','Superhuman Strength','Improved Reflexes','Steel Skin','Ultimate Strength','Incredible Reflexes','Piety','Rigour','Augury']},
//'Chivalry': {AttackBonus:1.15, StrengthBonus:1.18, DefenceBonus:1.2, DrainSpeed:24, vi:0, TurnsOff:['Thick Skin','Burst of Strength','Clarity of Thought','Rock Skin','Superhuman Strength','Improved Reflexes','Steel Skin','Ultimate Strength','Incredible Reflexes','Piety','Rigour','Augury']},
equipment.prayers = {
equipment.prayers = {
'Thick Skin': {
'Thick Skin': {DefenceBonus:1.05, DrainSpeed:3, vi:0, TurnsOff:['Rock Skin','Steel Skin','Chivalry','Piety','Rigour','Augury']},
DefenceBonus: 1.05,
'Burst of Strength': {StrengthBonus:1.05, DrainSpeed:3, vi:0, TurnsOff:['Superhuman Strength','Ultimate Strength','Chivalry','Piety']},
DrainSpeed: 3,
'Clarity of Thought': {AttackBonus:1.05, DrainSpeed:3, vi:0, TurnsOff:['Improved Reflexes','Incredible Reflexes','Chivalry','Piety']},
vi: 0,
'Sharp Eye': {RangedAttackBonus:1.05, RangedStrengthBonus:1.05, DrainSpeed:3, vi:0, TurnsOff:['Hawk Eye','Eagle Eye','Rigour']},
'Mystic Will': {MagicBonus:1.05, DrainSpeed:3, vi:0, TurnsOff:['Mystic Lore','Mystic Might','Augury']},
TurnsOff: ['Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
},
'Rock Skin': {DefenceBonus:1.1, DrainSpeed:6, vi:0, TurnsOff:['Thick Skin','Steel Skin','Chivalry','Piety','Rigour','Augury']},
'Burst of Strength': {
'Superhuman Strength': {StrengthBonus:1.1, DrainSpeed:6, vi:0, TurnsOff:['Burst of Strength','Ultimate Strength','Chivalry','Piety']},
StrengthBonus: 1.05,
'Improved Reflexes': {AttackBonus:1.1, DrainSpeed:6, vi:0, TurnsOff:['Clarity of Thought','Incredible Reflexes','Chivalry','Piety']},
'Rapid Restore': {DrainSpeed:1, vi:1, TurnsOff:[]},
DrainSpeed: 3,
vi: 0,
'Rapid Heal': {DrainSpeed:2, vi:1, TurnsOff:[]},
TurnsOff: ['Superhuman Strength', 'Ultimate Strength', 'Chivalry', 'Piety']
'Protect Item': {DrainSpeed:2, vi:1, TurnsOff:[]},
},
'Hawk Eye': {RangedAttackBonus:1.1, RangedStrengthBonus:1.1, DrainSpeed:6, vi:0, TurnsOff:['Sharp Eye','Eagle Eye','Rigour']},
'Clarity of Thought': {
'Mystic Lore': {MagicBonus:1.1, DrainSpeed:6, vi:0, TurnsOff:['Mystic Will','Mystic Might','Augury']},
AttackBonus: 1.05,
'Steel Skin': {DefenceBonus:1.15, DrainSpeed:12, vi:0, TurnsOff:['Thick Skin','Rock Skin','Chivalry','Piety','Rigour','Augury']},
DrainSpeed: 3,
'Ultimate Strength': {StrengthBonus:1.15, DrainSpeed:12, vi:0, TurnsOff:['Burst of Strength','Superhuman Strength','Chivalry','Piety']},
vi: 0,
'Incredible Reflexes': {AttackBonus:1.15, DrainSpeed:12, vi:0, TurnsOff:['Clarity of Thought','Improved Reflexes','Chivalry','Piety']},
TurnsOff: ['Improved Reflexes', 'Incredible Reflexes', 'Chivalry', 'Piety']
'Protect from Magic': {DrainSpeed:12, vi:1, TurnsOff:['Protect from Missiles','Protect from Melee','Retribution','Redemption','Smite']},
},
'Protect from Missiles': {DrainSpeed:12, vi:1, TurnsOff:['Protect from Magic','Protect from Melee','Retribution','Redemption','Smite']},
'Sharp Eye': {
'Protect from Melee': {DrainSpeed:12, vi:1, TurnsOff:['Protect from Magic','Protect from Missiles','Retribution','Redemption','Smite']},
RangedAttackBonus: 1.05,
'Eagle Eye': {RangedAttackBonus:1.15, RangedStrengthBonus:1.15, DrainSpeed:12, vi:2, TurnsOff:['Sharp Eye','Hawk Eye','Rigour']},
RangedStrengthBonus: 1.05,
'Mystic Might': {MagicBonus:1.15, DrainSpeed:12, vi:2, TurnsOff:['Mystic Will','Mystic Lore','Augury']},
DrainSpeed: 3,
'Retribution': {DrainSpeed:3, vi:0, TurnsOff:['Protect from Magic','Protect from Missiles','Protect from Melee','Redemption','Smite']},
vi: 0,
'Redemption': {DrainSpeed:6, vi:0, TurnsOff:['Protect from Magic','Protect from Missiles','Protect from Melee','Retribution','Smite']},
TurnsOff: ['Hawk Eye', 'Eagle Eye', 'Rigour']
'Smite': {DrainSpeed:18, vi:0, TurnsOff:['Protect from Magic','Protect from Missiles','Protect from Melee','Retribution','Redemption']},
},
'Preserve': {DrainSpeed:2, vi:1, TurnsOff:[]},
'Mystic Will': {
'Chivalry': {AttackBonus:1.15, StrengthBonus:1.18, DefenceBonus:1.2, DrainSpeed:24, vi:0, TurnsOff:['Thick Skin','Burst of Strength','Clarity of Thought','Rock Skin','Superhuman Strength','Improved Reflexes','Steel Skin','Ultimate Strength','Incredible Reflexes','Piety','Rigour','Augury']},
MagicBonus: 1.05,
'Piety': {AttackBonus:1.2, StrengthBonus:1.23, DefenceBonus:1.25, DrainSpeed:24, vi:2, TurnsOff:['Thick Skin','Burst of Strength','Clarity of Thought','Rock Skin','Superhuman Strength','Improved Reflexes','Steel Skin','Ultimate Strength','Incredible Reflexes','Chivalry','Rigour','Augury']},
DrainSpeed: 3,
'Rigour': {DefenceBonus:1.25, RangedAttackBonus:1.2, RangedStrengthBonus:1.23, DrainSpeed:24, vi:2, TurnsOff:['Thick Skin','Rock Skin','Steel Skin','Chivalry','Piety','Sharp Eye','Hawk Eye','Eagle Eye','Augury']},
vi: 0,
'Augury': {DefenceBonus:1.25, MagicBonus:1.25, DrainSpeed:24, vi:2, TurnsOff:['Thick Skin','Rock Skin','Steel Skin','Chivalry','Piety','Mystic Will','Mystic Lore','Mystic Might','Rigour']},
TurnsOff: ['Mystic Lore', 'Mystic Might', 'Augury']
},
'Rock Skin': {
DefenceBonus: 1.1,
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Thick Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
},
'Superhuman Strength': {
StrengthBonus: 1.1,
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Burst of Strength', 'Ultimate Strength', 'Chivalry', 'Piety']
},
'Improved Reflexes': {
AttackBonus: 1.1,
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Clarity of Thought', 'Incredible Reflexes', 'Chivalry', 'Piety']
},
'Rapid Restore': {
DrainSpeed: 1,
vi: 1,
TurnsOff: []
},
'Rapid Heal': {
DrainSpeed: 2,
vi: 1,
TurnsOff: []
},
'Protect Item': {
DrainSpeed: 2,
vi: 1,
TurnsOff: []
},
'Hawk Eye': {
RangedAttackBonus: 1.1,
RangedStrengthBonus: 1.1,
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Sharp Eye', 'Eagle Eye', 'Rigour']
},
'Mystic Lore': {
MagicBonus: 1.1,
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Mystic Will', 'Mystic Might', 'Augury']
},
'Steel Skin': {
DefenceBonus: 1.15,
DrainSpeed: 12,
vi: 0,
TurnsOff: ['Thick Skin', 'Rock Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
},
'Ultimate Strength': {
StrengthBonus: 1.15,
DrainSpeed: 12,
vi: 0,
TurnsOff: ['Burst of Strength', 'Superhuman Strength', 'Chivalry', 'Piety']
},
'Incredible Reflexes': {
AttackBonus: 1.15,
DrainSpeed: 12,
vi: 0,
TurnsOff: ['Clarity of Thought', 'Improved Reflexes', 'Chivalry', 'Piety']
},
'Protect from Magic': {
DrainSpeed: 12,
vi: 1,
TurnsOff: ['Protect from Missiles', 'Protect from Melee', 'Retribution', 'Redemption', 'Smite']
},
'Protect from Missiles': {
DrainSpeed: 12,
vi: 1,
TurnsOff: ['Protect from Magic', 'Protect from Melee', 'Retribution', 'Redemption', 'Smite']
},
'Protect from Melee': {
DrainSpeed: 12,
vi: 1,
TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Retribution', 'Redemption', 'Smite']
},
'Eagle Eye': {
RangedAttackBonus: 1.15,
RangedStrengthBonus: 1.15,
DrainSpeed: 12,
vi: 2,
TurnsOff: ['Sharp Eye', 'Hawk Eye', 'Rigour']
},
'Mystic Might': {
MagicBonus: 1.15,
DrainSpeed: 12,
vi: 2,
TurnsOff: ['Mystic Will', 'Mystic Lore', 'Augury']
},
'Retribution': {
DrainSpeed: 3,
vi: 0,
TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Redemption', 'Smite']
},
'Redemption': {
DrainSpeed: 6,
vi: 0,
TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Retribution', 'Smite']
},
'Smite': {
DrainSpeed: 18,
vi: 0,
TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Retribution', 'Redemption']
},
'Preserve': {
DrainSpeed: 2,
vi: 1,
TurnsOff: []
},
'Chivalry': {
AttackBonus: 1.15,
StrengthBonus: 1.18,
DefenceBonus: 1.2,
DrainSpeed: 24,
vi: 0,
TurnsOff: ['Thick Skin', 'Burst of Strength', 'Clarity of Thought', 'Rock Skin', 'Superhuman Strength', 'Improved Reflexes', 'Steel Skin', 'Ultimate Strength', 'Incredible Reflexes', 'Piety', 'Rigour', 'Augury']
},
'Piety': {
AttackBonus: 1.2,
StrengthBonus: 1.23,
DefenceBonus: 1.25,
DrainSpeed: 24,
vi: 2,
TurnsOff: ['Thick Skin', 'Burst of Strength', 'Clarity of Thought', 'Rock Skin', 'Superhuman Strength', 'Improved Reflexes', 'Steel Skin', 'Ultimate Strength', 'Incredible Reflexes', 'Chivalry', 'Rigour', 'Augury']
},
'Rigour': {
DefenceBonus: 1.25,
RangedAttackBonus: 1.2,
RangedStrengthBonus: 1.23,
DrainSpeed: 24,
vi: 2,
TurnsOff: ['Thick Skin', 'Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Sharp Eye', 'Hawk Eye', 'Eagle Eye', 'Augury']
},
'Augury': {
DefenceBonus: 1.25,
MagicBonus: 1.25,
DrainSpeed: 24,
vi: 2,
TurnsOff: ['Thick Skin', 'Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Mystic Will', 'Mystic Lore', 'Mystic Might', 'Rigour']
}
};
};


// 'Super combat potion':{AttackAdd:5,AttackMult:1.15,StrengthAdd:5,StrengthMult:1.15,DefenceAdd:5,DefenceMult:1.15,vi:2},
// 'Super combat potion':{AttackAdd:5,AttackMult:1.15,StrengthAdd:5,StrengthMult:1.15,DefenceAdd:5,DefenceMult:1.15,vi:2},
equipment.potions = {
equipment.potions = {
'Bastion potion': {
'Bastion potion':{DefenceAdd:5,DefenceMult:1.15,RangedAdd:4,RangedMult:1.1,vi:2,im:'Bastion potion(4)'},
DefenceAdd: 5,
'Battlemage potion':{DefenceAdd:5,DefenceMult:1.15,MagicAdd:4,MagicMult:1,vi:2,im:'Battlemage potion(4)'},
DefenceMult: 1.15,
'Dragon battleaxe':{StrengthAdd:-1,StrengthMult:-1,vi:2,im:'Dragon battleaxe'},
RangedAdd: 4,
'Imbued heart':{MagicAdd:1,MagicMult:1.1,vi:2,im:'Imbued heart'},
RangedMult: 1.1,
'Overload (+)':{AttackAdd:6,AttackMult:1.16,StrengthAdd:6,StrengthMult:1.16,DefenceAdd:6,DefenceMult:1.16,RangedAdd:6,RangedMult:1.16,MagicAdd:6,MagicMult:1.16,vi:2,im:'Overload (4) (Chambers of Xeric)'},
vi: 2,
'Overload (nightmare zone)':{AttackAdd:5,AttackMult:1.15,StrengthAdd:5,StrengthMult:1.15,DefenceAdd:5,DefenceMult:1.15,RangedAdd:5,RangedMult:1.15,MagicAdd:5,MagicMult:1.15,vi:2,im:'Overload (4)'},
'Ranging potion':{RangedAdd:4,RangedMult:1.1,vi:2,im:'Ranging potion(4)'},
im: 'Bastion potion(4)'
},
'Saradomin brew':{AttackAdd:-2,AttackMult:0.9,StrengthAdd:-2,StrengthMult:0.9,DefenceAdd:2,DefenceMult:1.2,RangedAdd:-2,RangedMult:0.9,MagicAdd:-2,MagicMult:0.9,vi:2,im:'Saradomin brew(4)'},
'Battlemage potion': {
'Super combat potion':{AttackAdd:5,AttackMult:1.15,StrengthAdd:5,StrengthMult:1.15,DefenceAdd:5,DefenceMult:1.15,vi:2,im:'Super combat potion(4)'},
DefenceAdd: 5,
'Super magic potion':{MagicAdd:5,MagicMult:1.15,vi:2,im:'Super magic potion (4)'},
DefenceMult: 1.15,
'Super ranging':{RangedAdd:5,RangedMult:1.15,vi:2,im:'Super ranging (4)'},
MagicAdd: 4,
'Xeric\'s aid (+)':{AttackAdd:-4,AttackMult:0.9,StrengthAdd:-4,StrengthMult:0.9,DefenceAdd:5,DefenceMult:1.2,RangedAdd:-4,RangedMult:0.9,MagicAdd:-4,MagicMult:0.9,vi:2,im:'Xeric\'s aid (4)'},
MagicMult: 1,
'Zamorak brew':{AttackAdd:2,AttackMult:1.2,StrengthAdd:2,StrengthMult:1.12,DefenceAdd:-2,DefenceMult:0.9,vi:2,im:'Zamorak brew(4)'},
vi: 2,
'Attack potion':{AttackAdd:3,AttackMult:1.1,vi:1,im:'Attack potion(4)'},
im: 'Battlemage potion(4)'
'Combat potion':{AttackAdd:3,AttackMult:1.1,StrengthAdd:3,StrengthMult:1.1,vi:1,im:'Combat potion(4)'},
},
'Defence potion':{DefenceAdd:3,DefenceMult:1.1,vi:1,im:'Defence potion(4)'},
'Dragon battleaxe': {
'Elder potion (+)':{AttackAdd:6,AttackMult:1.16,StrengthAdd:6,StrengthMult:1.16,DefenceAdd:6,DefenceMult:1.16,vi:1,im:'Elder potion (4)'},
StrengthAdd: -1,
'Elder potion':{AttackAdd:5,AttackMult:1.13,StrengthAdd:5,StrengthMult:1.13,DefenceAdd:5,DefenceMult:1.13,vi:1,im:'Elder potion (2)'},
StrengthMult: -1,
'Elder potion (-)':{AttackAdd:4,AttackMult:1.1,StrengthAdd:4,StrengthMult:1.1,DefenceAdd:4,DefenceMult:1.1,vi:1,im:'Elder potion (1)'},
vi: 2,
'Excalibur':{DefenceAdd:8,DefenceMult:1,vi:1,im:'Excalibur'},
im: 'Dragon battleaxe'
'Kodai potion (+)':{DefenceAdd:6,DefenceMult:1.16,MagicAdd:6,MagicMult:1.16,vi:1,im:'Kodai potion (4)'},
},
'Kodai potion':{DefenceAdd:5,DefenceMult:1.13,MagicAdd:5,MagicMult:1.13,vi:1,im:'Kodai potion (2)'},
'Imbued heart': {
'Kodai potion (-)':{DefenceAdd:4,DefenceMult:1.1,MagicAdd:4,MagicMult:1.1,vi:1,im:'Kodai potion (1)'},
'Magic essence':{MagicAdd:3,MagicMult:1,vi:1,im:'Magic essence(4)'},
MagicAdd: 1,
'Magic potion':{MagicAdd:4,MagicMult:1,vi:1,im:'Magic potion(4)'},
MagicMult: 1.1,
vi: 2,
'Overload':{AttackAdd:5,AttackMult:1.13,StrengthAdd:5,StrengthMult:1.13,DefenceAdd:5,DefenceMult:1.13,RangedAdd:5,RangedMult:1.13,MagicAdd:5,MagicMult:1.13,vi:1,im:'Overload (2) (Chambers of Xeric)'},
im: 'Imbued heart'
'Overload (-)':{AttackAdd:4,AttackMult:1.1,StrengthAdd:4,StrengthMult:1.1,DefenceAdd:4,DefenceMult:1.1,RangedAdd:4,RangedMult:1.1,MagicAdd:4,MagicMult:1.1,vi:1,im:'Overload (1) (Chambers of Xeric)'},
},
'Strength potion':{StrengthAdd:3,StrengthMult:1.1,vi:1,im:'Strength potion(4)'},
'Overload (+)': {
'Super attack':{AttackAdd:5,AttackMult:1.15,vi:1,im:'Super attack(4)'},
AttackAdd: 6,
'Super defence':{DefenceAdd:5,DefenceMult:1.15,vi:1,im:'Super defence(4)'},
AttackMult: 1.16,
'Super restore':{AttackAdd:0,AttackMult:1,StrengthAdd:0,StrengthMult:1,DefenceAdd:0,DefenceMult:1,RangedAdd:0,RangedMult:1,MagicAdd:0,MagicMult:1,vi:1,im:'Super restore(4)'},
'Super strength':{StrengthAdd:5,StrengthMult:1.15,vi:1,im:'Super strength(4)'},
StrengthAdd: 6,
StrengthMult: 1.16,
'Twisted potion (+)':{DefenceAdd:6,DefenceMult:1.16,RangedAdd:6,RangedMult:1.16,vi:1,im:'Twisted potion (4)'},
DefenceAdd: 6,
'Twisted potion':{DefenceAdd:5,DefenceMult:1.13,RangedAdd:5,RangedMult:1.13,vi:1,im:'Twisted potion (2)'},
DefenceMult: 1.16,
'Twisted potion (-)':{DefenceAdd:4,DefenceMult:1.1,RangedAdd:4,RangedMult:1.1,vi:1,im:'Twisted potion (1)'},
RangedAdd: 6,
'Dragon pickaxe':{MiningAdd:3,MiningMult:1,vi:1,im:'Dragon pickaxe'},
RangedMult: 1.16,
'Garden pie':{FarmingAdd:3,FarmingMult:1,vi:1,im:'Garden pie'},
MagicAdd: 6,
MagicMult: 1.16,
vi: 2,
im: 'Overload (4) (Chambers of Xeric)'
},
'Overload (nightmare zone)': {
AttackAdd: 5,
AttackMult: 1.15,
StrengthAdd: 5,
StrengthMult: 1.15,
DefenceAdd: 5,
DefenceMult: 1.15,
RangedAdd: 5,
RangedMult: 1.15,
MagicAdd: 5,
MagicMult: 1.15,
vi: 2,
im: 'Overload (4)'
},
'Ranging potion': {
RangedAdd: 4,
RangedMult: 1.1,
vi: 2,
im: 'Ranging potion(4)'
},
'Saradomin brew': {
AttackAdd: -2,
AttackMult: 0.9,
StrengthAdd: -2,
StrengthMult: 0.9,
DefenceAdd: 2,
DefenceMult: 1.2,
RangedAdd: -2,
RangedMult: 0.9,
MagicAdd: -2,
MagicMult: 0.9,
vi: 2,
im: 'Saradomin brew(4)'
},
'Super combat potion': {
AttackAdd: 5,
AttackMult: 1.15,
StrengthAdd: 5,
StrengthMult: 1.15,
DefenceAdd: 5,
DefenceMult: 1.15,
vi: 2,
im: 'Super combat potion(4)'
},
'Super magic potion': {
MagicAdd: 5,
MagicMult: 1.15,
vi: 2,
im: 'Super magic potion (4)'
},
'Super ranging': {
RangedAdd: 5,
RangedMult: 1.15,
vi: 2,
im: 'Super ranging (4)'
},
'Xeric\'s aid (+)': {
AttackAdd: -4,
AttackMult: 0.9,
StrengthAdd: -4,
StrengthMult: 0.9,
DefenceAdd: 5,
DefenceMult: 1.2,
RangedAdd: -4,
RangedMult: 0.9,
MagicAdd: -4,
MagicMult: 0.9,
vi: 2,
im: 'Xeric\'s aid (4)'
},
'Zamorak brew': {
AttackAdd: 2,
AttackMult: 1.2,
StrengthAdd: 2,
StrengthMult: 1.12,
DefenceAdd: -2,
DefenceMult: 0.9,
vi: 2,
im: 'Zamorak brew(4)'
},
'Attack potion': {
AttackAdd: 3,
AttackMult: 1.1,
vi: 1,
im: 'Attack potion(4)'
},
'Combat potion': {
AttackAdd: 3,
AttackMult: 1.1,
StrengthAdd: 3,
StrengthMult: 1.1,
vi: 1,
im: 'Combat potion(4)'
},
'Defence potion': {
DefenceAdd: 3,
DefenceMult: 1.1,
vi: 1,
im: 'Defence potion(4)'
},
'Elder potion (+)': {
AttackAdd: 6,
AttackMult: 1.16,
StrengthAdd: 6,
StrengthMult: 1.16,
DefenceAdd: 6,
DefenceMult: 1.16,
vi: 1,
im: 'Elder potion (4)'
},
'Elder potion': {
AttackAdd: 5,
AttackMult: 1.13,
StrengthAdd: 5,
StrengthMult: 1.13,
DefenceAdd: 5,
DefenceMult: 1.13,
vi: 1,
im: 'Elder potion (2)'
},
'Elder potion (-)': {
AttackAdd: 4,
AttackMult: 1.1,
StrengthAdd: 4,
StrengthMult: 1.1,
DefenceAdd: 4,
DefenceMult: 1.1,
vi: 1,
im: 'Elder potion (1)'
},
'Excalibur': {
DefenceAdd: 8,
DefenceMult: 1,
vi: 1,
im: 'Excalibur'
},
'Kodai potion (+)': {
DefenceAdd: 6,
DefenceMult: 1.16,
MagicAdd: 6,
MagicMult: 1.16,
vi: 1,
im: 'Kodai potion (4)'
},
'Kodai potion': {
DefenceAdd: 5,
DefenceMult: 1.13,
MagicAdd: 5,
MagicMult: 1.13,
vi: 1,
im: 'Kodai potion (2)'
},
'Kodai potion (-)': {
DefenceAdd: 4,
DefenceMult: 1.1,
MagicAdd: 4,
MagicMult: 1.1,
vi: 1,
im: 'Kodai potion (1)'
},
'Magic essence': {
MagicAdd: 3,
MagicMult: 1,
vi: 1,
im: 'Magic essence(4)'
},
'Magic potion': {
MagicAdd: 4,
MagicMult: 1,
vi: 1,
im: 'Magic potion(4)'
},
'Overload': {
AttackAdd: 5,
AttackMult: 1.13,
StrengthAdd: 5,
StrengthMult: 1.13,
DefenceAdd: 5,
DefenceMult: 1.13,
RangedAdd: 5,
RangedMult: 1.13,
MagicAdd: 5,
MagicMult: 1.13,
vi: 1,
im: 'Overload (2) (Chambers of Xeric)'
},
'Overload (-)': {
AttackAdd: 4,
AttackMult: 1.1,
StrengthAdd: 4,
StrengthMult: 1.1,
DefenceAdd: 4,
DefenceMult: 1.1,
RangedAdd: 4,
RangedMult: 1.1,
MagicAdd: 4,
MagicMult: 1.1,
vi: 1,
im: 'Overload (1) (Chambers of Xeric)'
},
'Strength potion': {
StrengthAdd: 3,
StrengthMult: 1.1,
vi: 1,
im: 'Strength potion(4)'
},
'Super attack': {
AttackAdd: 5,
AttackMult: 1.15,
vi: 1,
im: 'Super attack(4)'
},
'Super defence': {
DefenceAdd: 5,
DefenceMult: 1.15,
vi: 1,
im: 'Super defence(4)'
},
'Super restore': {
AttackAdd: 0,
AttackMult: 1,
StrengthAdd: 0,
StrengthMult: 1,
DefenceAdd: 0,
DefenceMult: 1,
RangedAdd: 0,
RangedMult: 1,
MagicAdd: 0,
MagicMult: 1,
vi: 1,
im: 'Super restore(4)'
},
'Super strength': {
StrengthAdd: 5,
StrengthMult: 1.15,
vi: 1,
im: 'Super strength(4)'
},
'Twisted potion (+)': {
DefenceAdd: 6,
DefenceMult: 1.16,
RangedAdd: 6,
RangedMult: 1.16,
vi: 1,
im: 'Twisted potion (4)'
},
'Twisted potion': {
DefenceAdd: 5,
DefenceMult: 1.13,
RangedAdd: 5,
RangedMult: 1.13,
vi: 1,
im: 'Twisted potion (2)'
},
'Twisted potion (-)': {
DefenceAdd: 4,
DefenceMult: 1.1,
RangedAdd: 4,
RangedMult: 1.1,
vi: 1,
im: 'Twisted potion (1)'
},
'Dragon pickaxe': {
MiningAdd: 3,
MiningMult: 1,
vi: 1,
im: 'Dragon pickaxe'
},
'Garden pie': {
FarmingAdd: 3,
FarmingMult: 1,
vi: 1,
im: 'Garden pie'
}
};
};


// 'Blood Barrage': {attackSpeed:5, maxHit:29, fireSpell:false, vi:1, im:'Blood Barrage icon'},
// 'Blood Barrage': {attackSpeed:5, maxHit:29, fireSpell:false, vi:1, im:'Blood Barrage icon'},
equipment.spell = {
equipment.spell = {
'None': {aS:5, mh:0, vi:0, im:'Spellbook'},
'None': {
aS: 5,
'Blood Barrage': {aS:5, mh:29, vi:1, im:'Blood Barrage icon'},
mh: 0,
'Blood Blitz': {aS:5, mh:25, vi:1, im:'Blood Blitz icon'},
vi: 0,
'Blood Burst': {aS:5, mh:21, vi:1, im:'Blood Burst icon'},
im: 'Spellbook'
'Blood Rush': {aS:5, mh:15, vi:1, im:'Blood Rush icon'},
},
'Claws of Guthix': {aS:5, mh:20, smoke:1, vi:1, im:'Claws of Guthix icon'},
'Blood Barrage': {
'Claws of Guthix (charged)': {aS:5, mh:30, smoke:1, vi:1, im:'Claws of Guthix icon'},
aS: 5,
'Crumble Undead': {aS:5, mh:15, smoke:1, vi:1, im:'Crumble Undead icon'},
mh: 29,
'Earth Blast': {aS:5, mh:15, smoke:1, vi:1, im:'Earth Blast icon'},
vi: 1,
'Earth Bolt': {aS:5, mh:11, smoke:1, bolt:1, vi:1, im:'Earth Bolt icon'},
im: 'Blood Barrage icon'
'Earth Strike': {aS:5, mh:6, smoke:1, vi:1, im:'Earth Strike icon'},
},
'Earth Surge': {aS:5, mh:23, smoke:1, vi:1, im:'Earth Surge icon'},
'Blood Blitz': {
'Earth Wave': {aS:5, mh:19, smoke:1, vi:1, im:'Earth Wave icon'},
aS: 5,
'Fire Blast': {aS:5, mh:16, fire:1, smoke:1, vi:1, im:'Fire Blast icon'},
mh: 25,
'Fire Bolt': {aS:5, mh:12, fire:1, bolt:1, smoke:1, vi:1, im:'Fire Bolt icon'},
vi: 1,
'Fire Strike': {aS:5, mh:8, fire:1, smoke:1, vi:1, im:'Fire Strike icon'},
im: 'Blood Blitz icon'
'Fire Surge': {aS:5, mh:24, fire:1, smoke:1, vi:1, im:'Fire Surge icon'},
},
'Fire Wave': {aS:5, mh:20, fire:1, smoke:1, vi:1, im:'Fire Wave icon'},
'Blood Burst': {
'Flames of Zamorak': {aS:5, mh:20, smoke:1, vi:1, im:'Flames of Zamorak icon'},
aS: 5,
'Flames of Zamorak (charged)': {aS:5, mh:30, smoke:1, vi:1, im:'Flames of Zamorak icon'},
mh: 21,
'Iban Blast': {aS:5, mh:25, smoke:1, vi:1, im:'Iban Blast icon'},
vi: 1,
'Ice Barrage': {aS:5, mh:30, vi:1, im:'Ice Barrage icon'},
'Ice Blitz': {aS:5, mh:26, vi:1, im:'Ice Blitz icon'},
im: 'Blood Burst icon'
},
'Ice Burst': {aS:5, mh:22, vi:1, im:'Ice Burst icon'},
'Ice Rush': {aS:5, mh:16, vi:1, im:'Ice Rush icon'},
'Blood Rush': {
aS: 5,
'Magic Dart': {aS:5, mh:-1, smoke:1, vi:1, im:'Magic Dart icon'},
mh: 15,
'Saradomin Strike': {aS:5, mh:20, smoke:1, vi:1, im:'Saradomin Strike icon'},
vi: 1,
'Saradomin Strike (charged)': {aS:5, mh:30, smoke:1, vi:1, im:'Saradomin Strike icon'},
'Shadow Barrage': {aS:5, mh:28, vi:1, im:'Shadow Barrage icon'},
im: 'Blood Rush icon'
},
'Shadow Blitz': {aS:5, mh:24, vi:1, im:'Shadow Blitz icon'},
'Claws of Guthix': {
'Shadow Burst': {aS:5, mh:18, vi:1, im:'Shadow Burst icon'},
aS: 5,
'Shadow Rush': {aS:5, mh:14, vi:1, im:'Shadow Rush icon'},
mh: 20,
'Smoke Barrage': {aS:5, mh:27, vi:1, im:'Smoke Barrage icon'},
smoke: 1,
'Smoke Blitz': {aS:5, mh:23, vi:1, im:'Smoke Blitz icon'},
vi: 1,
'Smoke Burst': {aS:5, mh:17, vi:1, im:'Smoke Burst icon'},
'Smoke Rush': {aS:5, mh:13, vi:1, im:'Smoke Rush icon'},
im: 'Claws of Guthix icon'
},
'Water Blast': {aS:5, mh:14, smoke:1, vi:1, im:'Water Blast icon'},
'Claws of Guthix (charged)': {
'Water Bolt': {aS:5, mh:10, smoke:1, bolt:1, vi:1, im:'Water Bolt icon'},
aS: 5,
'Water Strike': {aS:5, mh:4, smoke:1, vi:1, im:'Water Strike icon'},
mh: 30,
'Water Surge': {aS:5, mh:22, smoke:1, vi:1, im:'Water Surge icon'},
smoke: 1,
'Water Wave': {aS:5, mh:18, smoke:1, vi:1, im:'Water Wave icon'},
vi: 1,
'Wind Blast': {aS:5, mh:13, smoke:1, vi:1, im:'Wind Blast icon'},
im: 'Claws of Guthix icon'
'Wind Bolt': {aS:5, mh:9, smoke:1, bolt:1, vi:1, im:'Wind Bolt icon'},
},
'Wind Strike': {aS:5, mh:2, smoke:1, vi:1, im:'Wind Strike icon'},
'Crumble Undead': {
'Wind Surge': {aS:5, mh:21, smoke:1, vi:1, im:'Wind Surge icon'},
aS: 5,
'Wind Wave': {aS:5, mh:17, smoke:1, vi:1, im:'Wind Wave icon'}
mh: 15,
smoke: 1,
vi: 1,
im: 'Crumble Undead icon'
},
'Earth Blast': {
aS: 5,
mh: 15,
smoke: 1,
vi: 1,
im: 'Earth Blast icon'
},
'Earth Bolt': {
aS: 5,
mh: 11,
smoke: 1,
bolt: 1,
vi: 1,
im: 'Earth Bolt icon'
},
'Earth Strike': {
aS: 5,
mh: 6,
smoke: 1,
vi: 1,
im: 'Earth Strike icon'
},
'Earth Surge': {
aS: 5,
mh: 23,
smoke: 1,
vi: 1,
im: 'Earth Surge icon'
},
'Earth Wave': {
aS: 5,
mh: 19,
smoke: 1,
vi: 1,
im: 'Earth Wave icon'
},
'Fire Blast': {
aS: 5,
mh: 16,
fire: 1,
smoke: 1,
vi: 1,
im: 'Fire Blast icon'
},
'Fire Bolt': {
aS: 5,
mh: 12,
fire: 1,
bolt: 1,
smoke: 1,
vi: 1,
im: 'Fire Bolt icon'
},
'Fire Strike': {
aS: 5,
mh: 8,
fire: 1,
smoke: 1,
vi: 1,
im: 'Fire Strike icon'
},
'Fire Surge': {
aS: 5,
mh: 24,
fire: 1,
smoke: 1,
vi: 1,
im: 'Fire Surge icon'
},
'Fire Wave': {
aS: 5,
mh: 20,
fire: 1,
smoke: 1,
vi: 1,
im: 'Fire Wave icon'
},
'Flames of Zamorak': {
aS: 5,
mh: 20,
smoke: 1,
vi: 1,
im: 'Flames of Zamorak icon'
},
'Flames of Zamorak (charged)': {
aS: 5,
mh: 30,
smoke: 1,
vi: 1,
im: 'Flames of Zamorak icon'
},
'Iban Blast': {
aS: 5,
mh: 25,
smoke: 1,
vi: 1,
im: 'Iban Blast icon'
},
'Ice Barrage': {
aS: 5,
mh: 30,
vi: 1,
im: 'Ice Barrage icon'
},
'Ice Blitz': {
aS: 5,
mh: 26,
vi: 1,
im: 'Ice Blitz icon'
},
'Ice Burst': {
aS: 5,
mh: 22,
vi: 1,
im: 'Ice Burst icon'
},
'Ice Rush': {
aS: 5,
mh: 16,
vi: 1,
im: 'Ice Rush icon'
},
'Magic Dart': {
aS: 5,
mh: -1,
smoke: 1,
vi: 1,
im: 'Magic Dart icon'
},
'Saradomin Strike': {
aS: 5,
mh: 20,
smoke: 1,
vi: 1,
im: 'Saradomin Strike icon'
},
'Saradomin Strike (charged)': {
aS: 5,
mh: 30,
smoke: 1,
vi: 1,
im: 'Saradomin Strike icon'
},
'Shadow Barrage': {
aS: 5,
mh: 28,
vi: 1,
im: 'Shadow Barrage icon'
},
'Shadow Blitz': {
aS: 5,
mh: 24,
vi: 1,
im: 'Shadow Blitz icon'
},
'Shadow Burst': {
aS: 5,
mh: 18,
vi: 1,
im: 'Shadow Burst icon'
},
'Shadow Rush': {
aS: 5,
mh: 14,
vi: 1,
im: 'Shadow Rush icon'
},
'Smoke Barrage': {
aS: 5,
mh: 27,
vi: 1,
im: 'Smoke Barrage icon'
},
'Smoke Blitz': {
aS: 5,
mh: 23,
vi: 1,
im: 'Smoke Blitz icon'
},
'Smoke Burst': {
aS: 5,
mh: 17,
vi: 1,
im: 'Smoke Burst icon'
},
'Smoke Rush': {
aS: 5,
mh: 13,
vi: 1,
im: 'Smoke Rush icon'
},
'Water Blast': {
aS: 5,
mh: 14,
smoke: 1,
vi: 1,
im: 'Water Blast icon'
},
'Water Bolt': {
aS: 5,
mh: 10,
smoke: 1,
bolt: 1,
vi: 1,
im: 'Water Bolt icon'
},
'Water Strike': {
aS: 5,
mh: 4,
smoke: 1,
vi: 1,
im: 'Water Strike icon'
},
'Water Surge': {
aS: 5,
mh: 22,
smoke: 1,
vi: 1,
im: 'Water Surge icon'
},
'Water Wave': {
aS: 5,
mh: 18,
smoke: 1,
vi: 1,
im: 'Water Wave icon'
},
'Wind Blast': {
aS: 5,
mh: 13,
smoke: 1,
vi: 1,
im: 'Wind Blast icon'
},
'Wind Bolt': {
aS: 5,
mh: 9,
smoke: 1,
bolt: 1,
vi: 1,
im: 'Wind Bolt icon'
},
'Wind Strike': {
aS: 5,
mh: 2,
smoke: 1,
vi: 1,
im: 'Wind Strike icon'
},
'Wind Surge': {
aS: 5,
mh: 21,
smoke: 1,
vi: 1,
im: 'Wind Surge icon'
},
'Wind Wave': {
aS: 5,
mh: 17,
smoke: 1,
vi: 1,
im: 'Wind Wave icon'
}
};
};


// 'axe': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
// 'axe': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
equipment.combatStyleLoadout = {
equipment.combatStyleLoadout = {
'axe': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
'axe': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
'banner': ['Stab - Accurate','Slash - Aggressive','Crush - Shared','Stab - Defensive','Magic - Spell'],
'banner': ['Stab - Accurate', 'Slash - Aggressive', 'Crush - Shared', 'Stab - Defensive', 'Magic - Spell'],
'blaster': ['Magic - Spell'],
'blaster': ['Magic - Spell'],
'bludgeon': ['Crush - Aggressive','Crush - Aggressive','Crush - Aggressive','Magic - Spell'],
'bludgeon': ['Crush - Aggressive', 'Crush - Aggressive', 'Crush - Aggressive', 'Magic - Spell'],
'blunt': ['Crush - Accurate','Crush - Aggressive','Crush - Defensive','Magic - Spell'],
'blunt': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
'bow': ['Ranged - Accurate','Ranged - Rapid','Ranged - Longrange','Magic - Spell'],
'bow': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
'bulwark': ['Crush - Accurate','Block - N/A'],
'bulwark': ['Crush - Accurate', 'Block - N/A'],
'claws': ['Slash - Accurate','Slash - Aggressive','Stab - Shared','Slash - Defensive','Magic - Spell'],
'claws': ['Slash - Accurate', 'Slash - Aggressive', 'Stab - Shared', 'Slash - Defensive', 'Magic - Spell'],
'crossbow': ['Ranged - Accurate','Ranged - Rapid','Ranged - Longrange','Magic - Spell'],
'crossbow': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
'flamer': ['Slash - Aggressive','Ranged - Rapid','Magic - Accurate','Magic - Spell'],
'flamer': ['Slash - Aggressive', 'Ranged - Rapid', 'Magic - Accurate', 'Magic - Spell'],
'grenade': ['Ranged - Accurate','Ranged - Rapid','Ranged - Longrange','Magic - Spell'],
'grenade': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
'gun': ['Crush - Aggressive','Magic - Spell'],
'gun': ['Crush - Aggressive', 'Magic - Spell'],
'hacksword': ['Slash - Accurate','Slash - Aggressive','Stab - Shared','Slash - Defensive','Magic - Spell'],
'hacksword': ['Slash - Accurate', 'Slash - Aggressive', 'Stab - Shared', 'Slash - Defensive', 'Magic - Spell'],
'heavysword': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
'heavysword': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
'pickaxe': ['Stab - Accurate','Stab - Aggressive','Crush - Aggressive','Stab - Defensive','Magic - Spell'],
'pickaxe': ['Stab - Accurate', 'Stab - Aggressive', 'Crush - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
'polearm': ['Stab - Shared','Slash - Aggressive','Stab - Defensive','Magic - Spell'],
'polearm': ['Stab - Shared', 'Slash - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
'polestaff': ['Crush - Accurate','Crush - Aggressive','Crush - Defensive','Magic - Spell'],
'polestaff': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
'scythe': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
'scythe': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
'spear': ['Stab - Shared','Slash - Shared','Crush - Shared','Stab - Defensive','Magic - Spell'],
'spear': ['Stab - Shared', 'Slash - Shared', 'Crush - Shared', 'Stab - Defensive', 'Magic - Spell'],
'spiked': ['Crush - Accurate','Crush - Aggressive','Stab - Shared','Crush - Defensive','Magic - Spell'],
'spiked': ['Crush - Accurate', 'Crush - Aggressive', 'Stab - Shared', 'Crush - Defensive', 'Magic - Spell'],
'stabsword': ['Stab - Accurate','Stab - Aggressive','Slash - Aggressive','Stab - Defensive','Magic - Spell'],
'stabsword': ['Stab - Accurate', 'Stab - Aggressive', 'Slash - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
'staff': ['Crush - Accurate','Crush - Aggressive','Crush - Defensive','Magic - Spell','Magic - Defensive Spell'],
'staff': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell', 'Magic - Defensive Spell'],
'staff bladed': ['Stab - Accurate','Slash - Aggressive','Crush - Defensive','Magic - Spell','Magic - Defensive Spell'],
'staff bladed': ['Stab - Accurate', 'Slash - Aggressive', 'Crush - Defensive', 'Magic - Spell', 'Magic - Defensive Spell'],
'staff selfpowering': ['Magic - Accurate','Magic - Longrange','Magic - Spell'],
'staff selfpowering': ['Magic - Accurate', 'Magic - Longrange', 'Magic - Spell'],
'thrown': ['Ranged - Accurate','Ranged - Rapid','Ranged - Longrange','Magic - Spell'],
'thrown': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
'unarmed': ['Crush - Accurate','Crush - Aggressive','Crush - Defensive','Magic - Spell'],
'unarmed': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
'whip': ['Slash - Accurate','Slash - Shared','Slash - Defensive','Magic - Spell'],
'whip': ['Slash - Accurate', 'Slash - Shared', 'Slash - Defensive', 'Magic - Spell']
};
};

equipment.combatStyle = {
equipment.combatStyle = {
'None': {im: 'Combat icon'}
'None': {
im: 'Combat icon'
}
};
};


//returns item name from id
//returns item name from id
equipment.lookupItemID = function(id, slot){
equipment.lookupItemID = function (id, slot) {
for(var key in equipment[slot]){
for (var key in equipment[slot]) {
if(equipment[slot][key].id.includes(id)){
if (equipment[slot][key].id.includes(id)) {
return key;
return key;
}
}
}
}
return false;
return false;
};
};


//returns [item object (obj), success (bool)] from item name:
//returns [item object (obj), success (bool)] from item name:
equipment.getItem = function(name, slot){
equipment.getItem = function (name, slot) {
if(slot == 'spell' || slot == 'combatStyle') {
if (slot == 'spell' || slot == 'combatStyle') {
name = utils.titleCase(name);
name = utils.titleCase(name);
} else {
name = utils.itemCase(name);
}
if (name in equipment[slot]) {
return [equipment[slot][name], true];
} else {
return [equipment[slot]['None'], false];
}
};

//Override this prototype to update the function from osrs wiki's 2018-04-17T22:23:58Z version to the current version 2019-12-12T00:27:42Z
OO.ui.SelectWidget.prototype.getItemMatcher = function (query, mode) {
var normalizeForMatching = this.constructor["static"].normalizeForMatching,
normalizedQuery = normalizeForMatching(query);
mode = this.filterMode || mode; //Check filtermode since the old version of the widgets don't pass on filtermode
if (mode === true) {
// Support deprecated exact=true argument
mode = 'exact';
}
return function (item) {
var matchText = normalizeForMatching(item.getMatchText());
if (normalizedQuery === '') {
return mode !== 'exact'; // Empty string matches all, except if we are in 'exact' mode, where it doesn't match at all
}
switch (mode) {
case 'exact':
return matchText === normalizedQuery;
case 'substring':
return matchText.indexOf(normalizedQuery) !== -1;
default:
//prefix
return matchText.indexOf(normalizedQuery) === 0;
}
};
};
OO.ui.SelectWidget["static"].normalizeForMatching = function (text) {
var normalized = text.trim().replace(/\s+/, ' ').toLowerCase();
if (normalized.normalize) {
normalized = normalized.normalize();
}
return normalized;
};
var updateEquipmentBonus = function updateEquipmentBonus() {
var bonuses = ['at', 'al', 'ac', 'am', 'ar', 'dt', 'dl', 'dc', 'dm', 'dr', 'bs', 'br', 'bm', 'pr'];
var slots = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', /*'combatStyle',*/'shield', 'blowpipe' /*,'spell'*/];
bonuses.forEach(function (bonus) {
var bonusvalue = 0;
slots.forEach(function (slot) {
if (typeof settings.loadout.slot.slotItem[slot][bonus] !== 'undefined') {
bonusvalue += settings.loadout.slot.slotItem[slot][bonus];
}
});
settings.loadout.slot.equipmentBonus[bonus] = bonusvalue;
dpsForm.form.equipmentBonus.menu[bonus].setValue((bonusvalue < 0 ? '' : '+') + bonusvalue + (bonus === 'bm' ? '%' : '')); //with sign
});
};
var updateVisibleLevels = function updateVisibleLevels(skills) {
skills.forEach(function (skill) {
settings.loadout.playerLevel.visible[skill] = settings.loadout.playerLevel.current[skill];
settings.loadout.slot.potions.forEach(function (potion) {
var potionBoost;
if (skill + 'Add' in equipment.potions[potion]) {
if (potion === 'Dragon battleaxe') {
potionBoost = Math.floor((Math.floor(settings.loadout.playerLevel.current.Magic / 10) + Math.floor(settings.loadout.playerLevel.current.Defence / 10) + Math.floor(settings.loadout.playerLevel.current.Ranged / 10) + Math.floor(settings.loadout.playerLevel.current.Attack / 10)) / 4) + 10 + settings.loadout.playerLevel.current.Strength;
} else {
potionBoost = settings.loadout.playerLevel.current[skill] * equipment.potions[potion][skill + 'Mult'] + equipment.potions[potion][skill + 'Add'];
}
settings.loadout.playerLevel.visible[skill] = Math.max(settings.loadout.playerLevel.visible[skill], potionBoost);
}
});
settings.loadout.playerLevel.visible[skill] = Math.floor(settings.loadout.playerLevel.visible[skill]);
dpsForm.form.playerLevel.visible[skill].setValue(settings.loadout.playerLevel.visible[skill]);
});
};
var setMonster = function setMonster(name) {
var saveSkill = function saveSkill(setname, getname, defaultvalue, defaultsetting) {
var val;
if (typeof mobj[getname] === 'undefined') {
val = defaultsetting;
} else {
} else {
name = utils.itemCase(name);
val = mobj[getname];
}
}
settings.loadout.monster.current[setname] = val;
if(name in equipment[slot]) {
settings.loadout.monster.visible[setname] = val;
return [equipment[slot][name], true];
dpsForm.form.monster.menu['current' + setname].setValue(val);
dpsForm.form.monster.menu['visible' + setname].setValue(val);
};
var saveVar = function saveVar(setname, getname, defaultvalue, defaultsetting, addplus, addperc) {
var val;
if (typeof mobj[getname] === 'undefined') {
val = defaultsetting;
} else {
} else {
return [equipment[slot]['None'], false];
val = mobj[getname];
}
}
settings.loadout.monster[setname] = val;
};
val = (addplus ? val < 0 ? '' : '+' : '') + val + (addperc ? '%' : '');
dpsForm.form.monster.menu[setname].setValue(val);
return;
};
dpsForm.monsterUnlockedToggleButton = false; //prevent recursion
dpsForm.form.monster.levelToggle.setValue(false);
var mobj;
if (name in monster) {
mobj = monster[name];
saveSkill('Hitpoints', 'Hi', '', 1);
saveSkill('Attack', 'At', '', 1);
saveSkill('Strength', 'St', '', 1);
saveSkill('Defence', 'De', '', 1);
saveSkill('Ranged', 'Ra', '', 1);
saveSkill('Magic', 'Ma', '', 1);
saveVar('attackSpeed', 'aS', '', 4);
saveVar('maxHit', 'mh', '', 0, false, false);
saveVar('astab', 'at', '', 0, true, false);
saveVar('aslash', 'al', '', 0, true, false);
saveVar('acrush', 'ac', '', 0, true, false);
saveVar('amagic', 'am', '', 0, true, false);
saveVar('arange', 'ar', '', 0, true, false);
saveVar('dstab', 'dt', '', 0, true, false);
saveVar('dslash', 'dl', '', 0, true, false);
saveVar('dcrush', 'dc', '', 0, true, false);
saveVar('dmagic', 'dm', '', 0, true, false);
saveVar('drange', 'dr', '', 0, true, false);
saveVar('abns', 'ba', '', 0, true, false);
saveVar('str', 'bs', '', 0, true, false);
saveVar('rstr', 'br', '', 0, true, false);
saveVar('mdmg', 'bm', '', 0, true, true);
saveVar('xpBonus', 'xp', 0, 0, false, true);
saveVar('immunePoison', 'ip', 0, 0, false, false);
saveVar('immuneVenom', 'iv', 0, 0, false, false);
saveVar('size', 'si', 1, 1, false, false);


//combatType
//Override this prototype to update the function from osrs wiki's 2018-04-17T22:23:58Z version to the current version 2019-12-12T00:27:42Z
if (typeof mobj.cT === 'undefined') {
OO.ui.SelectWidget.prototype.getItemMatcher = function ( query, mode ) {
settings.loadout.monster.combatType = [];
var normalizeForMatching = this.constructor.static.normalizeForMatching,
dpsForm.form.monster.menu.combatType.clearItems();
normalizedQuery = normalizeForMatching( query );
} else {
mode = this.filterMode || mode; //Check filtermode since the old version of the widgets don't pass on filtermode
dpsForm.form.monster.menu.combatType.clearItems();
if ( mode === true ) { // Support deprecated exact=true argument
settings.loadout.monster.combatType = mobj.cT;
mode = 'exact';
settings.loadout.monster.combatType.forEach(function (item) {
dpsForm.form.monster.menu.combatType.addTag(item);
});
}
}

return function ( item ) {
//attackStyle
var matchText = normalizeForMatching( item.getMatchText() );
if ( normalizedQuery === '' ) {
if (typeof mobj.aC === 'undefined') {
settings.loadout.monster.attackStyle = 'Melee';
return mode !== 'exact'; // Empty string matches all, except if we are in 'exact' mode, where it doesn't match at all
dpsForm.form.monster.menu.attackStyle.setValue('Melee');
} else {
var styleChoices = ['Melee', 'Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Magical melee', 'Magical ranged', 'Ranged magic'];
styleChoices.forEach(function (choice) {
if (mobj.aC.indexOf(choice, 0) == 0) {
settings.loadout.monster.attackStyle = choice;
dpsForm.form.monster.menu.attackStyle.setValue(choice);
}
}
switch ( mode ) {
});
case 'exact':
return matchText === normalizedQuery;
case 'substring':
return matchText.indexOf( normalizedQuery ) !== -1;
default: //prefix
return matchText.indexOf( normalizedQuery ) === 0;
}
};
};
OO.ui.SelectWidget.static.normalizeForMatching = function ( text ) {
var normalized = text.trim().replace( /\s+/, ' ' ).toLowerCase();
if ( normalized.normalize ) {
normalized = normalized.normalize();
}
}
dpsForm.monsterUnlockedToggleButton = true;
return normalized;
return mobj;
} else {
mobj = monster['None'];
dpsForm.monsterUnlockedToggleButton = true;
return mobj;
}
};
};
var utils = {

//Capitalize strings in the style of runescape items
var updateEquipmentBonus = function(){
itemCase: function itemCase(val) {
var bonuses = ['at', 'al', 'ac', 'am', 'ar', 'dt', 'dl', 'dc', 'dm', 'dr', 'bs', 'br', 'bm', 'pr'];
return val.charAt(0).toUpperCase() + val.slice(1).toLowerCase();
var slots = ['head','cape','neck','ammo','torso','legs','gloves','boots','ring','weapon',/*'combatStyle',*/'shield','blowpipe'/*,'spell'*/];
},
bonuses.forEach(function(bonus) {
//Capitalize first letter of every word except of and (charged)
var bonusvalue = 0;
titleCase: function titleCase(val) {
slots.forEach(function(slot) {
val = val.replace(/\w+/g, function (word) {
if(typeof settings.loadout.slot.slotItem[slot][bonus] !== 'undefined') {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
bonusvalue += settings.loadout.slot.slotItem[slot][bonus];
}
});
settings.loadout.slot.equipmentBonus[bonus] = bonusvalue;
dpsForm.form.equipmentBonus.menu[bonus].setValue((bonusvalue<0?'':'+')+bonusvalue+(bonus==='bm'?'%':'')); //with sign
});
});
val = val.replace(/ Of /g, ' of '); //doesn't work for " Of Of ", or start/end of sentence but whatevs
val = val.replace(/\(Charged\)/g, '(charged)');
return val;
},
//only accepts digits and nothing else (positive ints only)
isPositiveInt: function isPositiveInt(val) {
return /^\d+$/.test(val);
},
//parses num, but returns def if NaN
parseIntDefault: function parseIntDefault(num, def) {
num = parseInt(num, 10);
return Number.isNaN(num) ? def : num;
},
//parses num, but returns def if NaN
parseFloatDefault: function parseFloatDefault(num, def) {
num = parseFloat(num);
return Number.isNaN(num) ? def : num;
}
};
};


// https://runescape.wiki/w/Application_programming_interface#Old_School_Hiscores
var updateVisibleLevels = function(skills){
var api = {
skills.forEach(function(skill){
dumpOrder: [null, 'Attack', 'Defence', 'Strength', 'Hitpoints', 'Ranged', 'Prayer', 'Magic', null, null, null, null, null, null, null, 'Mining', null, null, null, null, 'Farming'],
settings.loadout.playerLevel.visible[skill] = settings.loadout.playerLevel.current[skill];
lookup: function lookup(rsn) {
settings.loadout.slot.potions.forEach(function(potion){
var throwError = function throwError(error) {
var potionBoost;
dpsForm.form.playerLevel.rsnInputLayout.setErrors([error]);
if(skill+'Add' in equipment.potions[potion]) {
dpsForm.form.playerLevel.rsnInput.setFlags('invalid');
if(potion === 'Dragon battleaxe'){
};
potionBoost = Math.floor((Math.floor(settings.loadout.playerLevel.current.Magic/10)+Math.floor(settings.loadout.playerLevel.current.Defence/10)+Math.floor(settings.loadout.playerLevel.current.Ranged/10)+Math.floor(settings.loadout.playerLevel.current.Attack/10))/4)+10+settings.loadout.playerLevel.current.Strength;
} else {
if (rsn.length > 0 && rsn.length < 13 && rsn.match(/^[a-zA-Z0-9 _]+$/) !== null) {
dpsForm.form.playerLevel.rsnButton.setActive(true);
potionBoost = settings.loadout.playerLevel.current[skill]*equipment.potions[potion][skill+'Mult'] + equipment.potions[potion][skill+'Add'];
dpsForm.form.playerLevel.rsnInputLayout.setErrors([]);
dpsForm.form.playerLevel.rsnInput.setFlags({
invalid: false
});
$.ajax({
type: 'GET',
url: '/cors/m=hiscore_oldschool/index_lite.ws?player=' + rsn,
dataType: 'text',
error: function error(jqXHR, textStatus, errorThrown) {
console.warn('Hiscores API call for ' + rsn + 'failed: ' + textStatus + '. ' + errorThrown);
if (errorThrown === 'Not Found') {
throwError('Display name not found! Please confirm the spelling!');
} else {
throwError('Failed to lookup this display name! Please check your internet connection or try again later.');
}
dpsForm.form.playerLevel.rsnButton.setActive(false);
},
success: function success(data /*, textStatus, jqXHR*/) {
try {
data = data.split('\n');
for (var i = 0; i < api.dumpOrder.length; i++) {
if (api.dumpOrder[i]) {
var lvl_s = data[i].split(',', 2)[1];
var lvl_i = utils.parseIntDefault(lvl_s, 1);
var skill = api.dumpOrder[i];
if (api.dumpOrder[i] === 'Prayer' || api.dumpOrder[i] === 'Hitpoints') {
if (settings.loadout.playerLevel.visible[skill] === settings.loadout.playerLevel.current[skill]) {
dpsForm.form.playerLevel.visible[skill].setValue(lvl_s);
settings.loadout.playerLevel.visible[skill] = lvl_i;
}
}
}
settings.loadout.playerLevel.visible[skill] = Math.max(settings.loadout.playerLevel.visible[skill], potionBoost);
dpsForm.form.playerLevel.current[skill].setValue(lvl_s);
settings.loadout.playerLevel.current[skill] = lvl_i;
}
}
}
});
settings.save('playerLevel');
} catch (err) {
settings.loadout.playerLevel.visible[skill] = Math.floor(settings.loadout.playerLevel.visible[skill]);
console.error('Unable to parse hiscores api data! ' + err.message);
dpsForm.form.playerLevel.visible[skill].setValue(settings.loadout.playerLevel.visible[skill]);
throwError('Error processing hiscore data! Please report this issue on the Wiki\'s Discord at https://discord.gg/runescapewiki!');
});
}
};
dpsForm.form.playerLevel.rsnButton.setActive(false);

var setMonster = function(name){
var saveSkill = function(setname,getname,defaultvalue,defaultsetting) {
var val;
if(typeof mobj[getname] === 'undefined') {
val = defaultsetting;
} else {
val = mobj[getname];
}
}
});
settings.loadout.monster.current[setname] = val;
settings.loadout.monster.visible[setname] = val;
dpsForm.form.monster.menu['current'+setname].setValue(val);
dpsForm.form.monster.menu['visible'+setname].setValue(val);
};
var saveVar = function(setname,getname,defaultvalue,defaultsetting,addplus,addperc) {
var val;
if(typeof mobj[getname] === 'undefined') {
val = defaultsetting;
} else {
val = mobj[getname];
}
settings.loadout.monster[setname] = val;
val = (addplus?(val<0?'':'+'):'')+val+(addperc?'%':'');
dpsForm.form.monster.menu[setname].setValue(val);
return;
};
dpsForm.monsterUnlockedToggleButton = false; //prevent recursion
dpsForm.form.monster.levelToggle.setValue(false);

var mobj;
if(name in monster) {
mobj = monster[name];
saveSkill('Hitpoints','Hi','',1);
saveSkill('Attack','At','',1);
saveSkill('Strength','St','',1);
saveSkill('Defence','De','',1);
saveSkill('Ranged','Ra','',1);
saveSkill('Magic','Ma','',1);
saveVar('attackSpeed','aS','',4);
saveVar('maxHit','mh','',0,false,false);
saveVar('astab','at','',0,true,false);
saveVar('aslash','al','',0,true,false);
saveVar('acrush','ac','',0,true,false);
saveVar('amagic','am','',0,true,false);
saveVar('arange','ar','',0,true,false);
saveVar('dstab','dt','',0,true,false);
saveVar('dslash','dl','',0,true,false);
saveVar('dcrush','dc','',0,true,false);
saveVar('dmagic','dm','',0,true,false);
saveVar('drange','dr','',0,true,false);
saveVar('abns','ba','',0,true,false);
saveVar('str','bs','',0,true,false);
saveVar('rstr','br','',0,true,false);
saveVar('mdmg','bm','',0,true,true);
saveVar('xpBonus','xp',0,0,false,true);
saveVar('immunePoison','ip',0,0,false,false);
saveVar('immuneVenom','iv',0,0,false,false);
saveVar('size','si',1,1,false,false);
//combatType
if(typeof mobj.cT === 'undefined') {
settings.loadout.monster.combatType = [];
dpsForm.form.monster.menu.combatType.clearItems();
} else {
dpsForm.form.monster.menu.combatType.clearItems();
settings.loadout.monster.combatType = mobj.cT;
settings.loadout.monster.combatType.forEach(function(item) {
dpsForm.form.monster.menu.combatType.addTag(item);
});
}
//attackStyle
if(typeof mobj.aC === 'undefined') {
settings.loadout.monster.attackStyle = 'Melee';
dpsForm.form.monster.menu.attackStyle.setValue('Melee');
} else {
var styleChoices = ['Melee','Stab','Slash','Crush','Ranged','Magic','Magical melee','Magical ranged','Ranged magic'];
styleChoices.forEach(function(choice) {
if(mobj.aC.indexOf(choice,0) == 0) {
settings.loadout.monster.attackStyle = choice;
dpsForm.form.monster.menu.attackStyle.setValue(choice);
}
});
}

dpsForm.monsterUnlockedToggleButton = true;
return mobj;
} else {
} else {
mobj = monster['None'];
throwError('Please enter valid a display name!');
dpsForm.monsterUnlockedToggleButton = true;
return mobj;
}
}
}
};
};
var calc = {

var utils = {
roll: {
Roller: function Roller(probability, roll, special) {
//Capitalize strings in the style of runescape items
itemCase: function(val) {
var _this = {
'Prob': probability,
return val.charAt(0).toUpperCase() + val.slice(1).toLowerCase();
//Percentage chance of this roll occurring
'Roll': roll,
//Value of the roll (e.g. attack roll or max hit)
'Spec': special //Special parameter:
// 1 = 100% accurate
// 2 = Dinh's
// 4 = invalid parameters (e.g. Invalid Magic Dart)
// 8 = Karil proc (2nd hitsplat with 50% damage)
// 16 = Scythe hit 2
// 32 = Scythe hit 3
//
};
return _this;
},
},
ApplyFunc: function ApplyFunc(arr, args, func) {
//Capitalize first letter of every word except of and (charged)
console.log(args);
titleCase: function(val) {
val = val.replace(/\w+/g, function(word) {
for (var i = 0; i < arr.length; i++) {
arr[i] = func(arr[i], args);
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
return arr;
},
ApplyMult: function ApplyMult(arr, mult) {
if (mult === 1) {
return arr;
}
for (var i = 0; i < arr.length; i++) {
arr[i].Roll = Math.floor(arr[i].Roll * mult);
}
return arr;
},
ApplyAdd: function ApplyAdd(arr, add) {
if (add === 0) {
return arr;
}
for (var i = 0; i < arr.length; i++) {
arr[i].Roll = Math.floor(arr[i].Roll + add);
}
return arr;
},
ApplySplitMult: function ApplySplitMult(arr, splitarr) {
var newarr = [];
splitarr.forEach(function (splitar) {
arr.forEach(function (ar) {
newarr.push(calc.roll.Roller(ar.Prob * splitar.Prob, Math.floor(ar.Roll * splitar.Roll), ar.Spec | splitar.Spec));
});
});
});
val = val.replace(/ Of /g,' of '); //doesn't work for " Of Of ", or start/end of sentence but whatevs
return newarr;
val = val.replace(/\(Charged\)/g,'(charged)');
return val;
},
},
ApplySplitAdd: function ApplySplitAdd(arr, splitarr) {
//only accepts digits and nothing else (positive ints only)
var newarr = [];
isPositiveInt: function(val){
splitarr.forEach(function (splitar) {
return /^\d+$/.test(val);
arr.forEach(function (ar) {
newarr.push(calc.roll.Roller(ar.Prob * splitar.Prob, Math.floor(ar.Roll + splitar.Roll), ar.Spec | splitar.Spec));
});
});
return newarr;
}
},
check: {
//Returns elite if wearing elite void, normal if wearing normal void, and 1 if not wearing void.
MVoid: function MVoid(loadout, helmet, normal, elite) {
if (loadout.slot.label.head === helmet && loadout.slot.label.gloves === 'Void knight gloves' && (loadout.slot.label.torso === 'Elite void top' || loadout.slot.label.torso === 'Void knight top') && (loadout.slot.label.legs === 'Elite void robe' || loadout.slot.label.legs === 'Void knight robe')) {
if (loadout.slot.label.torso === 'Elite void top' && loadout.slot.label.legs === 'Elite void robe') {
return elite;
}
return normal;
}
return 1;
},
},
//Returns the multiplier if wearing salve/black mask. imbuedonly = true if the item needs to be imbued.
//parses num, but returns def if NaN
//mask/salve/salve_e contain the multipliers for each case.
parseIntDefault: function(num,def){
MMaskSalve: function MMaskSalve(loadout, imbuedonly, mask, salve, salve_e) {
num = parseInt(num,10);
var helmets;
return Number.isNaN(num)?def:num;
var amulets;
var amulets_e;
if (imbuedonly) {
helmets = ['Black mask (i)', 'Slayer helmet (i)', 'Black slayer helmet (i)', 'Green slayer helmet (i)', 'Red slayer helmet (i)', 'Purple slayer helmet (i)', 'Turquoise slayer helmet (i)', 'Hydra slayer helmet (i)', 'Twisted slayer helmet (i)'];
amulets = ['Salve amulet(i)'];
amulets_e = ['Salve amulet(ei)'];
} else {
helmets = ['Black mask', 'Black mask (i)', 'Slayer helmet', 'Slayer helmet (i)', 'Black slayer helmet', 'Black slayer helmet (i)', 'Green slayer helmet', 'Green slayer helmet (i)', 'Red slayer helmet', 'Red slayer helmet (i)', 'Purple slayer helmet', 'Purple slayer helmet (i)', 'Turquoise slayer helmet', 'Turquoise slayer helmet (i)', 'Hydra slayer helmet', 'Hydra slayer helmet (i)', 'Twisted slayer helmet', 'Twisted slayer helmet (i)'];
amulets = ['Salve amulet', 'Salve amulet(i)'];
amulets_e = ['Salve amulet (e)', 'Salve amulet(ei)'];
}
if (amulets_e.includes(loadout.slot.label.neck)) {
return salve_e;
}
if (amulets.includes(loadout.slot.label.neck)) {
return salve;
}
if (helmets.includes(loadout.slot.label.head) && loadout.monster.combatType.includes('slayer')) {
return mask;
}
return 1;
},
},
MSmokeStaff: function MSmokeStaff(loadout) {
//parses num, but returns def if NaN
if ((loadout.slot.label.weapon === 'Smoke battlestaff' || loadout.slot.label.weapon === 'Mystic smoke staff') && loadout.slot.label.spell in equipment.spell && equipment.spell[loadout.slot.label.spell].smoke) {
parseFloatDefault: function(num,def){
num = parseFloat(num);
return 1.1;
}
return Number.isNaN(num)?def:num;
return 1;
},
},
MLightsword: function MLightsword(loadout, darklight, silverlight) {
};
// TODO: Darklight, Silverlight, Silverlight (dyed)

if (loadout.monster.combatType.includes('demon')) {
// https://runescape.wiki/w/Application_programming_interface#Old_School_Hiscores
if (loadout.slot.label.weapon === 'Arclight') {
var api = {
return 1.7;
dumpOrder: [null, 'Attack', 'Defence', 'Strength', 'Hitpoints', 'Ranged', 'Prayer', 'Magic', null, null, null, null, null, null, null, 'Mining', null, null, null, null, 'Farming'],
}
lookup: function(rsn){
var throwError = function(error) {
if (loadout.slot.label.weapon === 'Darklight') {
return darklight;
dpsForm.form.playerLevel.rsnInputLayout.setErrors([error]);
}
dpsForm.form.playerLevel.rsnInput.setFlags('invalid');
if (loadout.slot.label.weapon === 'Silverlight' || loadout.slot.label.weapon === 'Silverlight (dyed)') {
};
return silverlight;
if(rsn.length > 0 && rsn.length < 13 && rsn.match(/^[a-zA-Z0-9 _]+$/) !== null) {
dpsForm.form.playerLevel.rsnButton.setActive(true);
dpsForm.form.playerLevel.rsnInputLayout.setErrors([]);
dpsForm.form.playerLevel.rsnInput.setFlags({invalid:false});
$.ajax({
type: 'GET',
url: '/cors/m=hiscore_oldschool/index_lite.ws?player='+rsn,
dataType: 'text',
error: function(jqXHR, textStatus, errorThrown) {
console.warn('Hiscores API call for '+rsn+'failed: ' + textStatus + '. ' + errorThrown);
if(errorThrown === 'Not Found') {
throwError('Display name not found! Please confirm the spelling!');
} else {
throwError('Failed to lookup this display name! Please check your internet connection or try again later.');
}
dpsForm.form.playerLevel.rsnButton.setActive(false);
},
success: function(data/*, textStatus, jqXHR*/) {
try {
data = data.split('\n');
for(var i = 0; i < api.dumpOrder.length; i++) {
if(api.dumpOrder[i]) {
var lvl_s = data[i].split(',', 2)[1];
var lvl_i = utils.parseIntDefault(lvl_s,1);
var skill = api.dumpOrder[i];
if(api.dumpOrder[i] === 'Prayer'||api.dumpOrder[i]==='Hitpoints'){
if(settings.loadout.playerLevel.visible[skill] === settings.loadout.playerLevel.current[skill]){
dpsForm.form.playerLevel.visible[skill].setValue(lvl_s);
settings.loadout.playerLevel.visible[skill] = lvl_i;
}
}
dpsForm.form.playerLevel.current[skill].setValue(lvl_s);
settings.loadout.playerLevel.current[skill] = lvl_i;
}
}
settings.save('playerLevel');
} catch (err) {
console.error('Unable to parse hiscores api data! '+err.message);
throwError('Error processing hiscore data! Please report this issue on the Wiki\'s Discord at https://discord.gg/runescapewiki!');
}
dpsForm.form.playerLevel.rsnButton.setActive(false);
}
});
} else {
throwError('Please enter valid a display name!');
}
}
//Darklight
//Silverlight
//Silverlight (dyed)
}
return 1;
},
},
MLeafyBaxe: function MLeafyBaxe(loadout) {
};
if (loadout.monster.combatType.includes('leafy')) {

if (loadout.slot.label.weapon === 'Leaf-bladed battleaxe') {
var calc = {
return 1.175;
roll: {
}
Roller: function(probability,roll,special){
var _this = {
}
return 1;
'Prob':probability, //Percentage chance of this roll occurring
'Roll':roll, //Value of the roll (e.g. attack roll or max hit)
'Spec':special, //Special parameter:
// 1 = 100% accurate
// 2 = Dinh's
// 4 = invalid parameters (e.g. Invalid Magic Dart)
// 8 = Karil proc (2nd hitsplat with 50% damage)
// 16 = Scythe hit 2
// 32 = Scythe hit 3
//
};
return _this;
},
ApplyFunc: function(arr,args,func){
console.log(args)
for(var i=0; i<arr.length; i++){

arr[i] = func(arr[i],args);
}
return arr;
},
ApplyMult: function(arr,mult){
if(mult === 1){
return arr;
}
for(var i=0; i<arr.length; i++){
arr[i].Roll = Math.floor(arr[i].Roll*mult);
}
return arr;
},
ApplyAdd: function(arr,add){
if(add === 0){
return arr;
}
for(var i=0; i<arr.length; i++){
arr[i].Roll = Math.floor(arr[i].Roll+add);
}
return arr;
},
ApplySplitMult: function(arr,splitarr){
var newarr = [];
splitarr.forEach(function(splitar){
arr.forEach(function(ar){
newarr.push(calc.roll.Roller(ar.Prob*splitar.Prob,Math.floor(ar.Roll*splitar.Roll),ar.Spec|splitar.Spec));
});
});
return newarr;
},
ApplySplitAdd: function(arr,splitarr){
var newarr = [];
splitarr.forEach(function(splitar){
arr.forEach(function(ar){
newarr.push(calc.roll.Roller(ar.Prob*splitar.Prob,Math.floor(ar.Roll+splitar.Roll),ar.Spec|splitar.Spec));
});
});
return newarr;
},
},
},
MDhcb: function MDhcb(loadout) {
check: {
if (loadout.monster.combatType.includes('dragon')) {
//Returns elite if wearing elite void, normal if wearing normal void, and 1 if not wearing void.
MVoid: function(loadout, helmet, normal, elite){
if (loadout.slot.label.weapon === 'Dragon hunter crossbow') {
return 1.3;
if(loadout.slot.label.head === helmet && loadout.slot.label.gloves === 'Void knight gloves' && (loadout.slot.label.torso === 'Elite void top' || loadout.slot.label.torso === 'Void knight top') && (loadout.slot.label.legs === 'Elite void robe' || loadout.slot.label.legs === 'Void knight robe')){
}
if(loadout.slot.label.torso === 'Elite void top' && loadout.slot.label.legs === 'Elite void robe'){
}
return elite;
}
return 1;
return normal;
}
return 1;
},
//Returns the multiplier if wearing salve/black mask. imbuedonly = true if the item needs to be imbued.
//mask/salve/salve_e contain the multipliers for each case.
MMaskSalve: function(loadout, imbuedonly, mask, salve, salve_e){
var helmets;
var amulets;
var amulets_e;
if(imbuedonly){
helmets = ['Black mask (i)','Slayer helmet (i)','Black slayer helmet (i)','Green slayer helmet (i)','Red slayer helmet (i)','Purple slayer helmet (i)','Turquoise slayer helmet (i)','Hydra slayer helmet (i)','Twisted slayer helmet (i)'];
amulets = ['Salve amulet(i)'];
amulets_e = ['Salve amulet(ei)'];
} else {
helmets = ['Black mask','Black mask (i)','Slayer helmet','Slayer helmet (i)','Black slayer helmet','Black slayer helmet (i)','Green slayer helmet','Green slayer helmet (i)','Red slayer helmet','Red slayer helmet (i)','Purple slayer helmet','Purple slayer helmet (i)','Turquoise slayer helmet','Turquoise slayer helmet (i)','Hydra slayer helmet','Hydra slayer helmet (i)','Twisted slayer helmet','Twisted slayer helmet (i)'];
amulets = ['Salve amulet','Salve amulet(i)'];
amulets_e = ['Salve amulet (e)','Salve amulet(ei)'];
}
if(amulets_e.includes(loadout.slot.label.neck)){
return salve_e;
}
if(amulets.includes(loadout.slot.label.neck)){
return salve;
}
if(helmets.includes(loadout.slot.label.head) && loadout.monster.combatType.includes('slayer')){
return mask;
}
return 1;
},
MSmokeStaff: function(loadout){
if((loadout.slot.label.weapon === 'Smoke battlestaff' || loadout.slot.label.weapon === 'Mystic smoke staff') && loadout.slot.label.spell in equipment.spell && equipment.spell[loadout.slot.label.spell].smoke){
return 1.1;
}
return 1;
},
MLightsword: function(loadout,darklight,silverlight){
// TODO: Darklight, Silverlight, Silverlight (dyed)
if(loadout.monster.combatType.includes('demon')){
if(loadout.slot.label.weapon === 'Arclight'){
return 1.7;
}
if(loadout.slot.label.weapon === 'Darklight'){
return darklight;
}
if(loadout.slot.label.weapon === 'Silverlight' || loadout.slot.label.weapon === 'Silverlight (dyed)'){
return silverlight;
}
//Darklight
//Silverlight
//Silverlight (dyed)
}
return 1;
},
MLeafyBaxe: function(loadout){
if(loadout.monster.combatType.includes('leafy')){
if(loadout.slot.label.weapon === 'Leaf-bladed battleaxe'){
return 1.175;
}
}
return 1;
},
MDhcb: function(loadout){
if(loadout.monster.combatType.includes('dragon')){
if(loadout.slot.label.weapon === 'Dragon hunter crossbow'){
return 1.3;
}
}
return 1;
},
MDhl: function(loadout){
if(loadout.monster.combatType.includes('dragon')){
if(loadout.slot.label.weapon === 'Dragon hunter lance'){
return 1.2;
}
}
return 1;
},
MHolyWater: function(loadout){
if(loadout.monster.combatType.includes('demon')){
if(loadout.slot.label.weapon === 'Holy water'){
return 1.6;
}
}
return 1;
},
MWildyWeap: function(loadout, weapon, bonus){
if(loadout.slot.wilderness && (loadout.slot.label.weapon === weapon || loadout.slot.label.weapon === weapon + ' (u)')){
return bonus;
}
return 1;
},
MTbow: function(loadout,modifier){
if(loadout.slot.label.weapon === 'Twisted bow'){
var calc;
if(modifier){ //maxhit modifier
calc = 250 + 100*loadout.monster.combatType.includes('raids');
calc = Math.min(calc,Math.max(loadout.monster.Magic,loadout.monster.am));
calc = Math.min(250,250+Math.trunc((10*3*calc/10-14)/100)-Math.trunc(Math.pow(3*calc/10-140,2)/100))/100;
return calc;
} else { //accuracy modifier
calc = 250 + 100*loadout.monster.combatType.includes('raids');
calc = Math.min(calc,Math.max(loadout.monster.Magic,loadout.monster.am));
calc = Math.min(140,140+Math.trunc((10*3*calc/10-10)/100)-Math.trunc(Math.pow(3*calc/10-100,2)/100))/100;
return calc;
}
}
return 1;
},
MObbyArmour: function(loadout){
//TODO does staff count as melee?
if(loadout.slot.label.head === 'Obsidian helmet' && loadout.slot.label.torso === 'Obsidian platebody' && loadout.slot.label.legs === 'Obsidian platelegs' && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')){
return 1.1;
}
return 1;
},
MObbyAmmy: function(loadout){
//TODO does staff count as melee?
if((loadout.slot.label.neck === 'Berserker necklace' || loadout.slot.label.neck === 'Berserker necklace (or)') && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')){
return 1.2;
}
return 1;
},
MCrystalArmour: function(loadout,modifier){
if(loadout.slot.label.weapon === 'Crystal bow'){
var bonus = 0;
if(loadout.slot.label.head === 'Crystal helm'){
bonus += 1;
}
if(loadout.slot.label.torso === 'Crystal body'){
bonus += 1;
}
if(loadout.slot.label.legs === 'Crystal legs'){
bonus += 1;
}
if(bonus === 3){
bonus = 5;
}
if(modifier){ //maxhit modifier
bonus = 1+bonus*0.03;
} else { //accuracy modifier
bonus = 1+bonus*0.06;
}
return bonus;
}
return 1;
},
MChinchompa: function(loadout,stance2){
if(loadout.slot.distance && (loadout.slot.label.weapon === 'Chinchompa' || loadout.slot.label.weapon === 'Red chinchompa' || loadout.slot.label.weapon === 'Black chinchompa')){
if(stance2 === 'Accurate'){
if(loadout.slot.distance <= 3){
return 0;
} else if (loadout.slot.distance <= 6) {
return 0.25;
} else {
return 0.5;
}
} else if(stance2 === 'Rapid'){
if(loadout.slot.distance <= 3){
return 0.25;
} else if (loadout.slot.distance <= 6) {
return 0;
} else {
return 0.25;
}
} else if(stance2 === 'Longrange'){
if(loadout.slot.distance <= 3){
return 0.5;
} else if (loadout.slot.distance <= 6) {
return 0.25;
} else {
return 0;
}
}
}
return 0;
},
HasGadder: function(loadout){
return loadout.slot.label.weapon === 'Gadderhammer';
},
HasBrimstone: function(loadout){
return loadout.slot.label.ring === 'Brimstone ring';
},
HasVestaLongsword: function(loadout){
return loadout.slot.label.ring === 'Vesta\'s longsword';
},
HasKeris: function(loadout){
return loadout.slot.label.weapon === 'Keris'||loadout.slot.label.weapon === 'Keris(p)'||loadout.slot.label.weapon === 'Keris(p+)'||loadout.slot.label.weapon === 'Keris(p++)';
},
HasVerac: function(loadout){
if(loadout.slot.label.head === 'Verac\'s helm' && loadout.slot.label.torso === 'Verac\'s brassard' && loadout.slot.label.legs === 'Verac\'s plateskirt' && loadout.slot.label.weapon === 'Verac\'s flail'){
if(loadout.slot.label.neck === 'Amulet of the damned'){
return 2;
}
return 1;
}
return 0;
},
HasAhrim: function(loadout){
if(loadout.slot.label.head === 'Ahrim\'s hood' && loadout.slot.label.torso === 'Ahrim\'s robetop' && loadout.slot.label.legs === 'Ahrim\'s robeskirt' && loadout.slot.label.weapon === 'Ahrim\'s staff'){
if(loadout.slot.label.neck === 'Amulet of the damned'){
return 2;
}
return 1;
}
return 0;
},
HasDharok: function(loadout){
if(loadout.slot.label.head === 'Dharok\'s helm' && loadout.slot.label.torso === 'Dharok\'s platebody' && loadout.slot.label.legs === 'Dharok\'s platelegs' && loadout.slot.label.weapon === 'Dharok\'s greataxe'){
if(loadout.slot.label.neck === 'Amulet of the damned'){
return 2;
}
return 1;
}
return 0;
},
HasKaril: function(loadout){
if(loadout.slot.label.head === 'Karil\'s coif' && loadout.slot.label.torso === 'Karil\'s leathertop' && loadout.slot.label.legs === 'Karil\'s leatherskirt' && loadout.slot.label.weapon === 'Karil\'s crossbow'){
if(loadout.slot.label.neck === 'Amulet of the damned'){
return 2;
}
return 1;
}
return 0;
},
MDharok: function(loadout){
//TODO - add hitpoints
if(calc.check.HasDharok(loadout)){
if(true/*TODO*/){
return 1;
}
return 1 + Math.max(0,loadout.playerLevel.current.Hitpoints-loadout.playerLevel.visible.Hitpoints)*loadout.playerLevel.current.Hitpoints/10000;
}
return 1;
},
MGuardians: function(loadout){
//TODO: Mining level
//TODO: What happens if not using pickaxe?
if(loadout.monster.name === 'Guardian (Chambers of Xeric, Challenge Mode)' || loadout.monster.name === 'Guardian (Chambers of Xeric)'){
var weapon = Math.min(61,loadout.slot.slotItem.weapon.mL || 0);
var level = Math.min(100,loadout.playerLevel.current.Mining || 1); //TODO
return (50+level+weapon)/150;
}
return 1;
},
//TODO Check "ontask"
MaxMagicDart: function(loadout){
var staves = ['Slayer\'s staff','Slayer\'s staff (e)','Toxic staff of the dead','Toxic staff (uncharged)','Staff of the dead','Staff of balance','Staff of light'];
if(staves.includes(loadout.slot.label.weapon)){
if(loadout.slot.label.weapon === 'Slayer\'s staff (e)' && loadout.monster.combatType.includes('slayer')){
return Math.floor(13+loadout.playerLevel.visible.Magic/6);
}
return Math.floor(10+loadout.playerLevel.visible.Magic/10);
}
return 0;
},
MaxTrident: function(loadout){
if(loadout.slot.label.weapon === 'Trident of the seas' || loadout.slot.label.weapon === 'Trident of the seas (e)'){
return Math.max(1,Math.floor(loadout.playerLevel.visible.Magic/3 - 5));
} else if(loadout.slot.label.weapon === 'Trident of the swamp' || loadout.slot.label.weapon === 'Trident of the swamp (e)' || loadout.slot.label.weapon === 'Uncharged toxic trident' || loadout.slot.label.weapon === 'Uncharged toxic trident (e)'){
return Math.max(4,Math.floor(loadout.playerLevel.visible.Magic/3 - 2));
} else if(loadout.slot.label.weapon === 'Sanguinesti staff' || loadout.slot.label.weapon === 'Sanguinesti staff (beta)'){
return Math.max(5,Math.floor(loadout.playerLevel.visible.Magic/3 - 1));
} else if(loadout.slot.label.weapon === 'Starter staff'){
return 8;
} else {
return false;
}
},
MaxSalamander: function(loadout){
if(loadout.slot.label.weapon === 'Black salamander'){
return Math.floor(0.5+loadout.playerLevel.visible.Magic*0.24375);
}
if(loadout.slot.label.weapon === 'Red salamander'){
return Math.floor(0.5+loadout.playerLevel.visible.Magic*0.2203125);
}
if(loadout.slot.label.weapon === 'Orange salamander'){
return Math.floor(0.5+loadout.playerLevel.visible.Magic*0.1921875);
}
if(loadout.slot.label.weapon === 'Swamp lizard'){
return Math.floor(0.5+loadout.playerLevel.visible.Magic*0.1875);
}
return false;
},
MChaosGauntlet: function(loadout){
if(loadout.slot.label.gloves === 'Chaos gauntlets' && loadout.slot.slotItem.spell.bolt){
return 3;
}
return 0;
},
MTomeOfFire: function(loadout){
if(loadout.slot.label.shield === 'Tome of fire' && loadout.slot.slotItem.spell.fire){
return 1.5;
}
return 1;
},
MCastleWars: function(loadout){
if(loadout.monster.combatType.includes('castle wars flagholder')){
return 1.2;
}
return 1;
},
},
},
MDhl: function MDhl(loadout) {

if (loadout.monster.combatType.includes('dragon')) {
naiveDPS: function(accarr,rollmin,rollmax,typeless,ticks){
if (loadout.slot.label.weapon === 'Dragon hunter lance') {
//TODO: Scythe
var dps = 0;
return 1.2;
for(var i = 0; i < accarr.length; i++) {
var hit;
if(accarr[i].Spec & 8){
hit = (rollmax[i].Roll + rollmin[i].Roll)/2 + Math.floor((rollmax[i].Roll + rollmin[i].Roll)/2)/2;
} else {
hit = (rollmax[i].Roll + rollmin[i].Roll)/2;
}
dps += accarr[i].Prob*accarr[i].Roll*hit+typeless;
}
}
return dps/ticks/0.6;
}
return 1;
},
},
MHolyWater: function MHolyWater(loadout) {

if (loadout.monster.combatType.includes('demon')) {
accuracy: function(aroll,droll){
if(aroll>droll){
if (loadout.slot.label.weapon === 'Holy water') {
return 1-(droll+2)/(2*aroll+2);
return 1.6;
}
}
}
return aroll/(2*droll+2);
return 1;
},
},
MWildyWeap: function MWildyWeap(loadout, weapon, bonus) {

if (loadout.slot.wilderness && (loadout.slot.label.weapon === weapon || loadout.slot.label.weapon === weapon + ' (u)')) {
accuracies: function(arollers,drollers){
var newarr = [];
return bonus;
}
arollers.forEach(function(aroller){
var roll;
return 1;
if(aroller.Spec & 1){ //guaranteed hit
roll = 1;
} else if (aroller.Spec & 6){ //Dinh's (2) or invalid (4)
roll = 0;
} else {
roll = 0;
drollers.forEach(function(droller){
roll += droller.Prob*calc.accuracy(aroller.Roll,droller.Roll);
});
}
newarr.push(calc.roll.Roller(aroller.Prob,roll,aroller.Spec));
});
return newarr;
},
},
MTbow: function MTbow(loadout, modifier) {

if (loadout.slot.label.weapon === 'Twisted bow') {
monsterDefence: function(loadout,style,spec){
var roll;
var calc;
if(style === 'dmagic'){
if (modifier) {
//maxhit modifier
roll = [calc.roll.Roller(1,Math.floor((loadout.monster.visible.Magic+9)*(64+loadout.monster.dmagic)),0)];
if(calc.check.HasBrimstone(loadout)){
calc = 250 + 100 * loadout.monster.combatType.includes('raids');
calc = Math.min(calc, Math.max(loadout.monster.Magic, loadout.monster.am));
roll = calc.roll.ApplySplitMult(roll,[calc.roll.Roller(0.75,1,0),calc.roll.Roller(0.25,0.9,0)]);
calc = Math.min(250, 250 + Math.trunc((10 * 3 * calc / 10 - 14) / 100) - Math.trunc(Math.pow(3 * calc / 10 - 140, 2) / 100)) / 100;
}
return calc;
} else {
} else {
//accuracy modifier
roll = [calc.roll.Roller(1,Math.floor((loadout.monster.visible.Defence+9)*(64+loadout.monster[style])),0)];
if(spec && calc.check.HasVestaLongsword(loadout)){
calc = 250 + 100 * loadout.monster.combatType.includes('raids');
roll = calc.roll.ApplyMult(roll,0.25);
calc = Math.min(calc, Math.max(loadout.monster.Magic, loadout.monster.am));
calc = Math.min(140, 140 + Math.trunc((10 * 3 * calc / 10 - 10) / 100) - Math.trunc(Math.pow(3 * calc / 10 - 100, 2) / 100)) / 100;
}
return calc;
}
}
return roll;
}
return 1;

},
MObbyArmour: function MObbyArmour(loadout) {
//TODO does staff count as melee?
if (loadout.slot.label.head === 'Obsidian helmet' && loadout.slot.label.torso === 'Obsidian platebody' && loadout.slot.label.legs === 'Obsidian platelegs' && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')) {
return 1.1;
}
return 1;
},
MObbyAmmy: function MObbyAmmy(loadout) {
//TODO does staff count as melee?
if ((loadout.slot.label.neck === 'Berserker necklace' || loadout.slot.label.neck === 'Berserker necklace (or)') && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')) {
return 1.2;
}
return 1;
},
},
MCrystalArmour: function MCrystalArmour(loadout, modifier) {
//Input: Loadout
if (loadout.slot.label.weapon === 'Crystal bow') {
//Output: min roll, max roll, guaranteed typeless damage, attack tick duration
var bonus = 0;
playerMaxHit: function(loadout){
if (loadout.slot.label.head === 'Crystal helm') {
//Todo: Ba level to min + max hit
//Todo: Holy water
bonus += 1;
//todo: Bolts
//Todo: Clarify which dragons are fiery

var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
if(!(res && ['Stab','Slash','Crush','Ranged','Magic','Block'].includes(res[1]) && ['Accurate','Aggressive','Defensive','Shared','Rapid','Longrange','Spell','Defensive Spell'].includes(res[2]))){
if(loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout){
res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
} else {
res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
}
}
}
if (loadout.slot.label.torso === 'Crystal body') {
var stance1 = res[1];
var stance2 = res[2];
bonus += 1;
}

if (loadout.slot.label.legs === 'Crystal legs') {
var rollmax;
var rollmin = [calc.roll.Roller(1,0,0)];
bonus += 1;
}
var typeless = 0; //dmg even if miss
if (bonus === 3) {
var attackticks = loadout.slot.slotItem.weapon.aS;
var args;
bonus = 5;
var tomeoffire = 1;
}
if (modifier) {

//maxhit modifier

if(stance1 === 'Block'){
bonus = 1 + bonus * 0.03;
rollmax = [calc.roll.Roller(1,0,2)];
} else if(stance1 === 'Ranged'){
rollmax = [calc.roll.Roller(1,loadout.playerLevel.visible.Ranged,0)];
loadout.slot.prayers.forEach(function(prayer){
rollmax = calc.roll.ApplyMult(rollmax,equipment.prayers[prayer].RangedStrengthBonus || 1);
});
rollmax = calc.roll.ApplyAdd(rollmax,8);
if(stance2 === 'Accurate'){
rollmax = calc.roll.ApplyAdd(rollmax,3);
} else if(stance2 === 'Rapid'){
attackticks -= 1;
}
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MVoid(loadout,'Void ranger helm',1.1,1.125));
rollmax[0].Roll = Math.floor(0.5+rollmax[0].Roll*(64+loadout.slot.equipmentBonus.br)/640); //cheating out of laziness
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MMaskSalve(loadout,true,1.15,1.15,1.2));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MDhcb(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MHolyWater(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MWildyWeap(loadout,'Craw\'s bow',1.5));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MTbow(loadout,true));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MCrystalArmour(loadout,true));
if(calc.check.HasKaril(loadout)){
rollmax = calc.roll.ApplySplitAdd(rollmax,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,0,8)]);
rollmin = calc.roll.ApplySplitAdd(rollmin,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,0,0)]);
}
} else if(stance1 === 'Magic') {
if(stance2 === 'Spell' || stance2 === 'Defensive Spell'){
attackticks = loadout.slot.slotItem.spell.aS;
if(loadout.slot.label.spell === 'Magic Dart'){
args = calc.check.MaxMagicDart(loadout);
rollmax = [calc.roll.Roller(1,args,args ? 0 : 4)];
} else {
rollmax = [calc.roll.Roller(1,loadout.slot.slotItem.spell.mh,loadout.slot.slotItem.spell.mh ? 0 : 4)];
calc.roll.ApplyAdd(rollmax,calc.check.MChaosGauntlet(loadout));
tomeoffire = calc.check.MTomeOfFire(loadout);
}
} else {
args = calc.check.MaxTrident(loadout);
if(args === false){
args = calc.check.MaxSalamander(loadout);
}
if(args === false){
rollmax = [calc.roll.Roller(1,0,4)];
} else {
rollmax = [calc.roll.Roller(1,args,0)];
}
}
var mbns = loadout.slot.equipmentBonus.bm;
var mask = 1;
mbns += calc.check.MSmokeStaff(loadout);
args = calc.check.MMaskSalve(loadout,true,0,1.15,1.2);
if(args === 0){
mask = 1.15;
} else {
args = args - 1;
}
mbns += args;
mbns += calc.check.MWildyWeap(loadout,'Thammaron\'s sceptre',1.25)-1;
mbns += calc.check.MVoid(loadout,'Void mage helm',1,1.025)-1;
rollmax = calc.roll.ApplyMult(rollmax,mbns);
rollmax = calc.roll.ApplyMult(rollmax,tomeoffire);
rollmax = calc.roll.ApplyMult(rollmax,mask);
if(calc.check.HasAhrim(loadout) === 2){
rollmax = calc.roll.ApplySplitMult(rollmax,[calc.roll.Roller(0.75,1,0),calc.roll.Roller(0.25,1.3,0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,0,0)]);
}
} else {
} else {
//accuracy modifier
rollmax = [calc.roll.Roller(1,loadout.playerLevel.visible.Strength,0)];
loadout.slot.prayers.forEach(function(prayer){
bonus = 1 + bonus * 0.06;
rollmax = calc.roll.ApplyMult(rollmax,equipment.prayers[prayer].StrengthBonus || 1);
});
rollmax = calc.roll.ApplyAdd(rollmax,8);
if(stance2 === 'Aggressive'){
rollmax = calc.roll.ApplyAdd(rollmax,3);
} else if(stance2 === 'Shared') {
rollmax = calc.roll.ApplyAdd(rollmax,1);
}
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MVoid(loadout,'Void melee helm',1.1,1.1));
rollmax[0].Roll = Math.floor(0.5+rollmax[0].Roll*(64+loadout.slot.equipmentBonus.bs)/640); //cheating out of laziness
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MMaskSalve(loadout,true,7/6,7/6,1.2));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MLightsword(loadout,1.6,1.5/*???TODO???*/));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MLeafyBaxe(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MDhl(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MWildyWeap(loadout,'Viggora\'s chainmace',1.5));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MObbyArmour(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MObbyAmmy(loadout)); //which order with obsidian?
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MDharok(loadout));
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MGuardians(loadout));
if(calc.check.HasGadder(loadout)){
rollmax = calc.roll.ApplySplitMult(rollmax,[calc.roll.Roller(0.95,1.25,0),calc.roll.Roller(0.05,2,0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin,[calc.roll.Roller(0.95,0,0),calc.roll.Roller(0.05,0,0)]);
}
if(calc.check.HasKeris(loadout)){
rollmax = calc.roll.ApplySplitMult(rollmax,[calc.roll.Roller(50/51,4/3,0),calc.roll.Roller(1/51,3,0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin,[calc.roll.Roller(50/51,0,0),calc.roll.Roller(1/51,0,0)]);
}
if(calc.check.HasVerac(loadout)){
rollmax = calc.roll.ApplySplitAdd(rollmax,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,1,1)]);
rollmin = calc.roll.ApplySplitAdd(rollmin,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,1,1)]);
}
}
}
return bonus;
rollmax = calc.roll.ApplyMult(rollmax,calc.check.MCastleWars(loadout));
}
//TODO: If ba, typeless += attack level
return 1;

return [rollmin,rollmax,typeless,attackticks];
},
},
playerAttack: function(loadout){
MChinchompa: function MChinchompa(loadout, stance2) {
if (loadout.slot.distance && (loadout.slot.label.weapon === 'Chinchompa' || loadout.slot.label.weapon === 'Red chinchompa' || loadout.slot.label.weapon === 'Black chinchompa')) {
//Todo:
// Raids parameter
if (stance2 === 'Accurate') {
// Wilderness parameter
if (loadout.slot.distance <= 3) {
// distance parameter
return 0;
// Check if Twisted bow truncates 3*Magic/10
} else if (loadout.slot.distance <= 6) {
// Slayer ontask parameter
return 0.25;
// Rune claw attack delay special attack
} else {
// Todo special attacks
return 0.5;
}

//Todo: bolt specials
} else if (stance2 === 'Rapid') {
var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
if (loadout.slot.distance <= 3) {
return 0.25;
if(!(res && ['Stab','Slash','Crush','Ranged','Magic','Block'].includes(res[1]) && ['Accurate','Aggressive','Defensive','Shared','Rapid','Longrange','Spell','Defensive Spell'].includes(res[2]))){
if(loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout){
} else if (loadout.slot.distance <= 6) {
return 0;
res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
} else {
} else {
res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
return 0.25;
}
}
} else if (stance2 === 'Longrange') {
if (loadout.slot.distance <= 3) {
return 0.5;
} else if (loadout.slot.distance <= 6) {
return 0.25;
} else {
return 0;
}
}
}
var stance1 = res[1];
}
var stance2 = res[2];
return 0;
},

HasGadder: function HasGadder(loadout) {
var rollacc;
return loadout.slot.label.weapon === 'Gadderhammer';
var args;
},
var defencestyle;
HasBrimstone: function HasBrimstone(loadout) {

if(stance1 === 'Block'){
return loadout.slot.label.ring === 'Brimstone ring';
},
return [[calc.roll.Roller(1,0,2)],''];
HasVestaLongsword: function HasVestaLongsword(loadout) {
} else if(stance1 === 'Ranged'){
defencestyle = 'drange';
return loadout.slot.label.ring === 'Vesta\'s longsword';
},
rollacc = [calc.roll.Roller(1,loadout.playerLevel.visible.Ranged,0)];
HasKeris: function HasKeris(loadout) {
loadout.slot.prayers.forEach(function(prayer){
return loadout.slot.label.weapon === 'Keris' || loadout.slot.label.weapon === 'Keris(p)' || loadout.slot.label.weapon === 'Keris(p+)' || loadout.slot.label.weapon === 'Keris(p++)';
rollacc = calc.roll.ApplyMult(rollacc,equipment.prayers[prayer].RangedAttackBonus || 1);
});
},
HasVerac: function HasVerac(loadout) {
rollacc = calc.roll.ApplyAdd(rollacc,8);
if (loadout.slot.label.head === 'Verac\'s helm' && loadout.slot.label.torso === 'Verac\'s brassard' && loadout.slot.label.legs === 'Verac\'s plateskirt' && loadout.slot.label.weapon === 'Verac\'s flail') {
if(stance2 === 'Accurate'){
if (loadout.slot.label.neck === 'Amulet of the damned') {
rollacc = calc.roll.ApplyAdd(rollacc,3);
}
return 2;
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MVoid(loadout,'Void ranger helm',1.1,1.1));
rollacc = calc.roll.ApplyMult(rollacc,64+loadout.slot.equipmentBonus.ar);
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MMaskSalve(loadout,true,1.15,1.15,1.2));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MDhcb(loadout));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MWildyWeap(loadout,'Craw\'s bow',1.5));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MTbow(loadout,false));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MCrystalArmour(loadout,false));
args = calc.check.MChinchompa(loadout,stance2);
rollacc = calc.roll.ApplyFunc(rollacc,args,function(rollaccer_i,chin){
rollaccer_i.Roll = rollaccer_i.Roll - Math.floor(rollaccer_i.Roll*chin);
return rollaccer_i;
});
if(calc.check.HasKaril(loadout)){
rollacc = calc.roll.ApplySplitAdd(rollacc,[calc.roll.Roller(0.75,0,0),calc.roll.Roller(0.25,0,8)]);
}
} else if(stance1 === 'Magic'){
defencestyle = 'dmagic';
rollacc = [calc.roll.Roller(1,loadout.playerLevel.visible.Magic,0)];
loadout.slot.prayers.forEach(function(prayer){
rollacc = calc.roll.ApplyMult(rollacc,equipment.prayers[prayer].MagicBonus || 1);
});
rollacc = calc.roll.ApplyAdd(rollacc,8);
if(stance2 === 'Accurate'){
rollacc = calc.roll.ApplyAdd(rollacc,3);
}
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MVoid(loadout,'Void mage helm',1.45,1.45));
rollacc = calc.roll.ApplyMult(rollacc,64+loadout.slot.equipmentBonus.am);
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MSmokeStaff(loadout));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MMaskSalve(loadout,true,1.15,1.15,1.2));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MWildyWeap(loadout,'Thammaron\'s sceptre',2));
if(calc.check.HasAhrim(loadout) === 2){ //split to keep in line with max hit above
rollacc = calc.roll.ApplySplitMult(rollacc,[calc.roll.Roller(0.75,1,0),calc.roll.Roller(0.25,1,0)]);
}
} else {
rollacc = [calc.roll.Roller(1,loadout.playerLevel.visible.Attack,0)];
loadout.slot.prayers.forEach(function(prayer){
rollacc = calc.roll.ApplyMult(rollacc,equipment.prayers[prayer].AttackBonus || 1);
});
rollacc = calc.roll.ApplyAdd(rollacc,8);
if(stance2 === 'Accurate'){
rollacc = calc.roll.ApplyAdd(rollacc,3);
} else if(stance2 === 'Shared') {
rollacc = calc.roll.ApplyAdd(rollacc,1);
}
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MVoid(loadout,'Void melee helm',1.1,1.1));
if(stance1 === 'Stab'){
defencestyle = 'dstab';
rollacc = calc.roll.ApplyMult(rollacc,64+loadout.slot.equipmentBonus.at);
} else if (stance1 === 'Slash'){
defencestyle = 'dslash';
rollacc = calc.roll.ApplyMult(rollacc,64+loadout.slot.equipmentBonus.al);
} else {
defencestyle = 'dcrush';
rollacc = calc.roll.ApplyMult(rollacc,64+loadout.slot.equipmentBonus.ac);
}
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MMaskSalve(loadout,true,7/6,7/6,1.2));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MLightsword(loadout,1,1));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MDhl(loadout));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MWildyWeap(loadout,'Viggora\'s chainmace',1.5));
rollacc = calc.roll.ApplyMult(rollacc,calc.check.MObbyArmour(loadout));
if(calc.check.HasGadder(loadout)){
rollacc = calc.roll.ApplySplitMult(rollacc,[calc.roll.Roller(0.95,1,0),calc.roll.Roller(0.05,1,0)]);
}
if(calc.check.HasKeris(loadout)){
rollacc = calc.roll.ApplySplitMult(rollacc,[calc.roll.Roller(50/51,1,0),calc.roll.Roller(1/51,1,0)]);
}
if(calc.check.HasVerac(loadout)){
rollacc = calc.roll.ApplySplitMult(rollacc,[calc.roll.Roller(0.75,1,0),calc.roll.Roller(0.25,1,1)]);
}
}
}
return [rollacc,defencestyle];
return 1;
}
return 0;
},
},
HasAhrim: function HasAhrim(loadout) {
};
if (loadout.slot.label.head === 'Ahrim\'s hood' && loadout.slot.label.torso === 'Ahrim\'s robetop' && loadout.slot.label.legs === 'Ahrim\'s robeskirt' && loadout.slot.label.weapon === 'Ahrim\'s staff') {

if (loadout.slot.label.neck === 'Amulet of the damned') {
// Users settings
return 2;
var settings = {
storedLoadouts: [],
loadout: {},
/*
Object playerLevel
string rsn

Object current
number Attack
number Strength
number Defence
number Ranged
number Magic
Object visible
number Attack
number Strength
number Defence
number Ranged
number Magic

boolean forceLookup (force a hiscore lookup on page load, used when rsn is defined in query string)
Object slot
Object label
string weapon
string combatStyle
string blowpipe
string spell
string head
string cape
string neck
string ammo
string torso
string shield
string legs
string gloves
string boots
string ring
array prayers
array potions
boolean visibleBlowpipe
boolean visibleSpell
boolean disabledShield
string combatStyleLoadout (combat style options name e.g. 'unarmed')
boolean wilderness TODO
number distance TODO

Object slotItem (equipment[slot].item for each slot)
Object weapon
Object combatStyle
Object blowpipe
Object spell
Object head
Object cape
Object neck
Object ammo
Object torso
Object shield
Object legs
Object gloves
Object boots
Object ring
(there is no prayers or potions object)

Object equipmentBonus
number at
number al
number ac
number am
number ar
number dt
number dl
number dc
number dm
number dr
number bs
number br
number bm

boolean manualLevel
Object monster
Object visible
number Hitpoints
number Attack
number Strength
number Defence
number Ranged
number Magic
Object current
number Hitpoints
number Attack
number Strength
number Defence
number Ranged
number Magic
string name
array combatType
string attackStyle
number attackSpeed
number maxHit
number astab
number aslash
number acrush
number amagic
number arange
number dstab
number dslash
number dcrush
number dmagic
number drange
number abns
number str
number rstr
number mdmg
number xpBonus
number immunePoison
number immuneVenom

boolean manualLevel
*/

// Load from localStorage
load: function(type) {
if (!rswiki.hasLocalStorage()) {
console.warn('Browser does not support localStorage');
return;
}
}
return 1;

switch (type) {
}
return 0;
case 'storedLoadouts':
try {
},
HasDharok: function HasDharok(loadout) {
settings.storedLoadouts = JSON.parse(localStorage.getItem('rsw-dps-storedloadouts'));
if (loadout.slot.label.head === 'Dharok\'s helm' && loadout.slot.label.torso === 'Dharok\'s platebody' && loadout.slot.label.legs === 'Dharok\'s platelegs' && loadout.slot.label.weapon === 'Dharok\'s greataxe') {
} catch (err) {
if (loadout.slot.label.neck === 'Amulet of the damned') {
settings.storedLoadouts = [];
return 2;
console.warn('Error loading storedLoadouts from localStorage');
}
if (settings.storedLoadouts === null) {
settings.storedLoadouts = [];
}
console.log('Localstorage loaded - storedLoadouts');
console.log(settings.storedLoadouts);
return settings.storedLoadouts;

case 'playerLevel':
try {
settings.loadout.playerLevel = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
} catch (err) {
settings.loadout.playerLevel = {};
console.warn('Error loading loadout from localStorage');
}
if (settings.loadout.playerLevel === null) {
settings.loadout.playerLevel = {};
}
console.log('Localstorage loaded - loadout.playerLevel');
console.log(settings.loadout.playerLevel);
return settings.loadout.playerLevel;

case 'slot':
try {
settings.loadout.slot = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
} catch (err) {
settings.loadout.slot = {};
console.warn('Error loading loadout from localStorage');
}
if (settings.loadout.slot === null) {
settings.loadout.slot = {};
}
console.log('Localstorage loaded - loadout.slot');
console.log(settings.loadout.slot);
return settings.loadout.slot;
case 'monster':
try {
settings.loadout.monster = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
} catch (err) {
settings.loadout.monster = {};
console.warn('Error loading loadout from localStorage');
}
if (settings.loadout.monster === null) {
settings.loadout.monster = {};
}
console.log('Localstorage loaded - loadout.monster');
console.log(settings.loadout.monster);
return settings.loadout.monster;

case 'loadout':
settings.load('playerLevel');
settings.load('slot');
settings.load('monster');
return true;
case 'all':
settings.load('storedLoadouts');
settings.load('loadout');
return true;

default:
console.error('Invalid localStorage type: '+type);
return null;
}
}
return 1;
}
return 0;
},
},
HasKaril: function HasKaril(loadout) {

if (loadout.slot.label.head === 'Karil\'s coif' && loadout.slot.label.torso === 'Karil\'s leathertop' && loadout.slot.label.legs === 'Karil\'s leatherskirt' && loadout.slot.label.weapon === 'Karil\'s crossbow') {
//Load from query string on page load
if (loadout.slot.label.neck === 'Amulet of the damned') {
loadQueryString: function() {
var urlParams = {};
return 2;
var match;
var pl = /\+/g;
var search = /([^&=]+)=?([^&]*)/g;
var decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); };
var query = window.location.search.substring(1);
while (match = search.exec(query)) { // eslint-disable-line no-cond-assign
urlParams[decode(match[1])] = decode(match[2]);
}
}
return 1;
}
var skill_iterable = ['Attack', 'Strength', 'Defence', 'Ranged', 'Magic'];
return 0;
skill_iterable.forEach(function(skill){
},
if(skill in urlParams){
MDharok: function MDharok(loadout) {
settings.loadout.playerLevel.current[skill] = utils.parseIntDefault(urlParams[skill], 1);
//TODO - add hitpoints
console.log('Loaded query string '+skill+' - '+settings.loadout.playerLevel.current[skill]);
if (calc.check.HasDharok(loadout)) {
}
});
if (true /*TODO*/) {
return 1;
if('rsn' in urlParams){
settings.loadout.playerLevel.rsn = urlParams.rsn;
settings.loadout.playerLevel.forceLookup = true;
console.log('Loaded query string rsn - ' +settings.loadout.playerLevel.rsn);
}
}
return 1 + Math.max(0, loadout.playerLevel.current.Hitpoints - loadout.playerLevel.visible.Hitpoints) * loadout.playerLevel.current.Hitpoints / 10000;
var slot_iterable = ['head','cape','neck','ammo','torso','legs','gloves','boots','ring','weapon','combatStyle','shield','blowpipe','spell']; //shield/blowpipe/spell/combatStyle after weapon
}
slot_iterable.forEach(function(slot){
return 1;
if(slot in urlParams){
if(slot == 'blowpipe' && !settings.loadout.slot.visibleBlowpipe) {
return;
} else if(slot == 'shield' && settings.loadout.slot.disabledShield){
return;
} else if(slot == 'spell' && !settings.loadout.slot.visibleSpell){
return;
}
var slot_id = urlParams[slot];
if(utils.isPositiveInt(slot_id)){
var slot_name = equipment.lookupItemID(parseInt(slot_id), slot);
if(slot_name) {
slot_id = slot_name;
}
}
slot_id = utils.itemCase(slot_id);
console.log('Loaded query string '+slot+' - ' +slot_id);
settings.loadout.slot.label[slot] = slot_id;
settings.loadout.slot.slotItem[slot] = equipment.getItem(slot_id, slot)[0];
if(slot == 'weapon'){
settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
if(settings.loadout.slot.disabledShield == true) {
settings.loadout.slot.label.shield = '';
}
settings.loadout.slot.visibleBlowpipe = slot_id == 'Toxic blowpipe';
if(settings.loadout.slot.visibleBlowpipe == false) {
settings.loadout.slot.label.blowpipe = '';
}
settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
} else if(slot == 'combatStyle') {
settings.loadout.slot.visibleSpell = slot_id == 'Magic - spell' || slot_id == 'Magic - defensive spell';
if(settings.loadout.slot.visibleSpell == false) {
settings.loadout.slot.label.spell = '';
}
}
}
});
settings.save('all');
},
},
MGuardians: function MGuardians(loadout) {
// Save to localStorage
//TODO: Mining level
//TODO: What happens if not using pickaxe?
save: function (type) {
if (loadout.monster.name === 'Guardian (Chambers of Xeric, Challenge Mode)' || loadout.monster.name === 'Guardian (Chambers of Xeric)') {
if (!rswiki.hasLocalStorage()) {
var weapon = Math.min(61, loadout.slot.slotItem.weapon.mL || 0);
console.warn('Browser does not support localStorage');
var level = Math.min(100, loadout.playerLevel.current.Mining || 1); //TODO
return;
return (50 + level + weapon) / 150;
}
return 1;
},
//TODO Check "ontask"
MaxMagicDart: function MaxMagicDart(loadout) {
var staves = ['Slayer\'s staff', 'Slayer\'s staff (e)', 'Toxic staff of the dead', 'Toxic staff (uncharged)', 'Staff of the dead', 'Staff of balance', 'Staff of light'];
if (staves.includes(loadout.slot.label.weapon)) {
if (loadout.slot.label.weapon === 'Slayer\'s staff (e)' && loadout.monster.combatType.includes('slayer')) {
return Math.floor(13 + loadout.playerLevel.visible.Magic / 6);
}
}
return Math.floor(10 + loadout.playerLevel.visible.Magic / 10);
var ret;
var stringified;
}
var save;
return 0;
},
switch (type) {
MaxTrident: function MaxTrident(loadout) {
case 'storedLoadouts':
if (loadout.slot.label.weapon === 'Trident of the seas' || loadout.slot.label.weapon === 'Trident of the seas (e)') {
stringified = JSON.stringify(settings.storedLoadouts);
return Math.max(1, Math.floor(loadout.playerLevel.visible.Magic / 3 - 5));
localStorage.setItem('rsw-dps-storedLoadouts', stringified);
} else if (loadout.slot.label.weapon === 'Trident of the swamp' || loadout.slot.label.weapon === 'Trident of the swamp (e)' || loadout.slot.label.weapon === 'Uncharged toxic trident' || loadout.slot.label.weapon === 'Uncharged toxic trident (e)') {
// Check for save
return Math.max(4, Math.floor(loadout.playerLevel.visible.Magic / 3 - 2));
try {
} else if (loadout.slot.label.weapon === 'Sanguinesti staff' || loadout.slot.label.weapon === 'Sanguinesti staff (beta)') {
ret = JSON.parse(localStorage.getItem('rsw-dps-storedLoadouts'));
return Math.max(5, Math.floor(loadout.playerLevel.visible.Magic / 3 - 1));
} catch (err) {
} else if (loadout.slot.label.weapon === 'Starter staff') {
console.warn('Error saving storedLoadouts to localStorage');
return false;
return 8;
}
} else {
return false;
console.log('Localstorage saved - storedLoadouts: ' + ret);
return true;
}
},
MaxSalamander: function MaxSalamander(loadout) {
case 'playerLevel':
if (loadout.slot.label.weapon === 'Black salamander') {
stringified = JSON.stringify(settings.loadout.playerLevel);
localStorage.setItem('rsw-dps-loadout-playerLevel', stringified);
return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.24375);
// Check for save
}
try {
if (loadout.slot.label.weapon === 'Red salamander') {
return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.2203125);
ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
} catch (err) {
}
console.warn('Error saving loadout.playerLevel to localStorage');
if (loadout.slot.label.weapon === 'Orange salamander') {
return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.1921875);
return false;
}
}
if (loadout.slot.label.weapon === 'Swamp lizard') {
console.log('Localstorage saved - loadout.playerLevel');
return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.1875);
return true;
}
case 'slot':
return false;
},
stringified = JSON.stringify(settings.loadout.slot);
MChaosGauntlet: function MChaosGauntlet(loadout) {
localStorage.setItem('rsw-dps-loadout-slot', stringified);
if (loadout.slot.label.gloves === 'Chaos gauntlets' && loadout.slot.slotItem.spell.bolt) {
// Check for save
try {
return 3;
}
ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
} catch (err) {
return 0;
},
console.warn('Error saving loadout.slot to localStorage');
MTomeOfFire: function MTomeOfFire(loadout) {
return false;
if (loadout.slot.label.shield === 'Tome of fire' && loadout.slot.slotItem.spell.fire) {
}
return 1.5;
console.log('Localstorage saved - loadout.slot');
return true;
}
return 1;
},
case 'monster':
MCastleWars: function MCastleWars(loadout) {
stringified = JSON.stringify(settings.loadout.monster);
localStorage.setItem('rsw-dps-loadout-monster', stringified);
if (loadout.monster.combatType.includes('castle wars flagholder')) {
// Check for save
return 1.2;
try {
}
return 1;
ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
}
} catch (err) {
},
console.warn('Error saving loadout.monster to localStorage');
naiveDPS: function naiveDPS(accarr, rollmin, rollmax, typeless, ticks) {
return false;
}
//TODO: Scythe
var dps = 0;
console.log('Localstorage saved - loadout.monster');
return true;
for (var i = 0; i < accarr.length; i++) {
var hit;
case 'loadout':
if (accarr[i].Spec & 8) {
hit = (rollmax[i].Roll + rollmin[i].Roll) / 2 + Math.floor((rollmax[i].Roll + rollmin[i].Roll) / 2) / 2;
save = settings.save('playerLevel');
} else {
save = settings.save('slot') && save;
save = settings.save('monster') && save;
hit = (rollmax[i].Roll + rollmin[i].Roll) / 2;
return save;
}
dps += accarr[i].Prob * accarr[i].Roll * hit + typeless;
}
return dps / ticks / 0.6;
},
accuracy: function accuracy(aroll, droll) {
if (aroll > droll) {
return 1 - (droll + 2) / (2 * aroll + 2);
}
return aroll / (2 * droll + 2);
},
accuracies: function accuracies(arollers, drollers) {
var newarr = [];
arollers.forEach(function (aroller) {
var roll;
if (aroller.Spec & 1) {
//guaranteed hit
roll = 1;
} else if (aroller.Spec & 6) {
//Dinh's (2) or invalid (4)
roll = 0;
} else {
roll = 0;
drollers.forEach(function (droller) {
roll += droller.Prob * calc.accuracy(aroller.Roll, droller.Roll);
});
}
newarr.push(calc.roll.Roller(aroller.Prob, roll, aroller.Spec));
});
return newarr;
},
monsterDefence: function monsterDefence(loadout, style, spec) {
var roll;
if (style === 'dmagic') {
roll = [calc.roll.Roller(1, Math.floor((loadout.monster.visible.Magic + 9) * (64 + loadout.monster.dmagic)), 0)];
if (calc.check.HasBrimstone(loadout)) {
roll = calc.roll.ApplySplitMult(roll, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 0.9, 0)]);
}
} else {
roll = [calc.roll.Roller(1, Math.floor((loadout.monster.visible.Defence + 9) * (64 + loadout.monster[style])), 0)];
if (spec && calc.check.HasVestaLongsword(loadout)) {
roll = calc.roll.ApplyMult(roll, 0.25);
}
}
return roll;
},
//Input: Loadout
//Output: min roll, max roll, guaranteed typeless damage, attack tick duration
playerMaxHit: function playerMaxHit(loadout) {
//Todo: Ba level to min + max hit
//Todo: Holy water
//todo: Bolts
//Todo: Clarify which dragons are fiery


var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
case 'all':
if (!(res && ['Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Block'].includes(res[1]) && ['Accurate', 'Aggressive', 'Defensive', 'Shared', 'Rapid', 'Longrange', 'Spell', 'Defensive Spell'].includes(res[2]))) {
save = settings.save('storedLoadouts');
if (loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout) {
save = settings.save('loadout') && save;
res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
return save;
} else {

res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
default:
}
console.error('Invalid localStorage type '+type);
return null;
}
}
}
var stance1 = res[1];
};
var stance2 = res[2];

var rollmax;
var dpsForm = {
var rollmin = [calc.roll.Roller(1, 0, 0)];
// Placeholder for form elements
var typeless = 0; //dmg even if miss
form: {},
var attackticks = loadout.slot.slotItem.weapon.aS;
// Create the form
var args;
create: function () {
var tomeoffire = 1;
var arr;
if (stance1 === 'Block') {
/* format:
rollmax = [calc.roll.Roller(1, 0, 2)];
} else if (stance1 === 'Ranged') {
Initialize setting variables
rollmax = [calc.roll.Roller(1, loadout.playerLevel.visible.Ranged, 0)];
Create widget
loadout.slot.prayers.forEach(function (prayer) {
Create event hooks*/
rollmax = calc.roll.ApplyMult(rollmax, equipment.prayers[prayer].RangedStrengthBonus || 1);
// // // // // // //
});
rollmax = calc.roll.ApplyAdd(rollmax, 8);
// Display Name //
// // // // // // //
if (stance2 === 'Accurate') {
if(settings.loadout.playerLevel == null) {
rollmax = calc.roll.ApplyAdd(rollmax, 3);
settings.loadout.playerLevel = {};
} else if (stance2 === 'Rapid') {
attackticks -= 1;
}
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MVoid(loadout, 'Void ranger helm', 1.1, 1.125));
rollmax[0].Roll = Math.floor(0.5 + rollmax[0].Roll * (64 + loadout.slot.equipmentBonus.br) / 640); //cheating out of laziness
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDhcb(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MHolyWater(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MWildyWeap(loadout, 'Craw\'s bow', 1.5));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MTbow(loadout, true));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MCrystalArmour(loadout, true));
if (calc.check.HasKaril(loadout)) {
rollmax = calc.roll.ApplySplitAdd(rollmax, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 8)]);
rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 0)]);
}
} else if (stance1 === 'Magic') {
if (stance2 === 'Spell' || stance2 === 'Defensive Spell') {
attackticks = loadout.slot.slotItem.spell.aS;
if (loadout.slot.label.spell === 'Magic Dart') {
args = calc.check.MaxMagicDart(loadout);
rollmax = [calc.roll.Roller(1, args, args ? 0 : 4)];
} else {
rollmax = [calc.roll.Roller(1, loadout.slot.slotItem.spell.mh, loadout.slot.slotItem.spell.mh ? 0 : 4)];
calc.roll.ApplyAdd(rollmax, calc.check.MChaosGauntlet(loadout));
tomeoffire = calc.check.MTomeOfFire(loadout);
}
}
} else {
if (settings.loadout.playerLevel.rsn == null) {
settings.loadout.playerLevel.rsn = '';
args = calc.check.MaxTrident(loadout);
if (args === false) {
args = calc.check.MaxSalamander(loadout);
}
}
dpsForm.form.playerLevel = {};
if (args === false) {
rollmax = [calc.roll.Roller(1, 0, 4)];
} else {
rollmax = [calc.roll.Roller(1, args, 0)];
}
}
var mbns = loadout.slot.equipmentBonus.bm;
var mask = 1;
mbns += calc.check.MSmokeStaff(loadout);
args = calc.check.MMaskSalve(loadout, true, 0, 1.15, 1.2);
if (args === 0) {
mask = 1.15;
} else {
args = args - 1;
}
mbns += args;
mbns += calc.check.MWildyWeap(loadout, 'Thammaron\'s sceptre', 1.25) - 1;
mbns += calc.check.MVoid(loadout, 'Void mage helm', 1, 1.025) - 1;
rollmax = calc.roll.ApplyMult(rollmax, mbns);
rollmax = calc.roll.ApplyMult(rollmax, tomeoffire);
rollmax = calc.roll.ApplyMult(rollmax, mask);
if (calc.check.HasAhrim(loadout) === 2) {
rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1.3, 0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 0)]);
}
} else {
rollmax = [calc.roll.Roller(1, loadout.playerLevel.visible.Strength, 0)];
loadout.slot.prayers.forEach(function (prayer) {
rollmax = calc.roll.ApplyMult(rollmax, equipment.prayers[prayer].StrengthBonus || 1);
});
rollmax = calc.roll.ApplyAdd(rollmax, 8);
if (stance2 === 'Aggressive') {
rollmax = calc.roll.ApplyAdd(rollmax, 3);
} else if (stance2 === 'Shared') {
rollmax = calc.roll.ApplyAdd(rollmax, 1);
}
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MVoid(loadout, 'Void melee helm', 1.1, 1.1));
rollmax[0].Roll = Math.floor(0.5 + rollmax[0].Roll * (64 + loadout.slot.equipmentBonus.bs) / 640); //cheating out of laziness
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MMaskSalve(loadout, true, 7 / 6, 7 / 6, 1.2));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MLightsword(loadout, 1.6, 1.5 /*???TODO???*/));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MLeafyBaxe(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDhl(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MWildyWeap(loadout, 'Viggora\'s chainmace', 1.5));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MObbyArmour(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MObbyAmmy(loadout)); //which order with obsidian?
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDharok(loadout));
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MGuardians(loadout));
if (calc.check.HasGadder(loadout)) {
rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(0.95, 1.25, 0), calc.roll.Roller(0.05, 2, 0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.95, 0, 0), calc.roll.Roller(0.05, 0, 0)]);
}
if (calc.check.HasKeris(loadout)) {
rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(50 / 51, 4 / 3, 0), calc.roll.Roller(1 / 51, 3, 0)]);
rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(50 / 51, 0, 0), calc.roll.Roller(1 / 51, 0, 0)]);
}
if (calc.check.HasVerac(loadout)) {
rollmax = calc.roll.ApplySplitAdd(rollmax, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 1, 1)]);
rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 1, 1)]);
}
}
rollmax = calc.roll.ApplyMult(rollmax, calc.check.MCastleWars(loadout));
//TODO: If ba, typeless += attack level


return [rollmin, rollmax, typeless, attackticks];
dpsForm.form.playerLevel.rsnInput = new OO.ui.TextInputWidget({
},
autocomplete: true,
playerAttack: function playerAttack(loadout) {
placeholder: 'Display name',
//Todo:
id: 'dps-player-rsnInput',
// Raids parameter
value: settings.loadout.playerLevel.rsn,
// Wilderness parameter
icon: 'search'
});
// distance parameter
// Check if Twisted bow truncates 3*Magic/10
dpsForm.form.playerLevel.rsnButton = new OO.ui.ButtonWidget({
// Slayer ontask parameter
label: 'Hiscore Lookup',
// Rune claw attack delay special attack
id: 'dps-player-rsnButton'
});
// Todo special attacks
dpsForm.form.playerLevel.rsnInput.on('enter', dpsForm.rsnInputSubmit);
dpsForm.form.playerLevel.rsnButton.on('click', dpsForm.rsnInputSubmit);


//Todo: bolt specials
dpsForm.form.playerLevel.rsnInputLayout = new OO.ui.ActionFieldLayout(
var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
dpsForm.form.playerLevel.rsnInput,
if (!(res && ['Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Block'].includes(res[1]) && ['Accurate', 'Aggressive', 'Defensive', 'Shared', 'Rapid', 'Longrange', 'Spell', 'Defensive Spell'].includes(res[2]))) {
dpsForm.form.playerLevel.rsnButton,
if (loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout) {
{id: 'dps-player-rsnInput-layout'}
res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
);
} else {
res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
}
}
var stance1 = res[1];
var stance2 = res[2];
var rollacc;
var args;
var defencestyle;
if (stance1 === 'Block') {
return [[calc.roll.Roller(1, 0, 2)], ''];
} else if (stance1 === 'Ranged') {
defencestyle = 'drange';
rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Ranged, 0)];
loadout.slot.prayers.forEach(function (prayer) {
rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].RangedAttackBonus || 1);
});
rollacc = calc.roll.ApplyAdd(rollacc, 8);
if (stance2 === 'Accurate') {
rollacc = calc.roll.ApplyAdd(rollacc, 3);
}
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void ranger helm', 1.1, 1.1));
rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.ar);
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MDhcb(loadout));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Craw\'s bow', 1.5));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MTbow(loadout, false));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MCrystalArmour(loadout, false));
args = calc.check.MChinchompa(loadout, stance2);
rollacc = calc.roll.ApplyFunc(rollacc, args, function (rollaccer_i, chin) {
rollaccer_i.Roll = rollaccer_i.Roll - Math.floor(rollaccer_i.Roll * chin);
return rollaccer_i;
});
if (calc.check.HasKaril(loadout)) {
rollacc = calc.roll.ApplySplitAdd(rollacc, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 8)]);
}
} else if (stance1 === 'Magic') {
defencestyle = 'dmagic';
rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Magic, 0)];
loadout.slot.prayers.forEach(function (prayer) {
rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].MagicBonus || 1);
});
rollacc = calc.roll.ApplyAdd(rollacc, 8);
if (stance2 === 'Accurate') {
rollacc = calc.roll.ApplyAdd(rollacc, 3);
}
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void mage helm', 1.45, 1.45));
rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.am);
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MSmokeStaff(loadout));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Thammaron\'s sceptre', 2));
if (calc.check.HasAhrim(loadout) === 2) {
//split to keep in line with max hit above
rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1, 0)]);
}
} else {
rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Attack, 0)];
loadout.slot.prayers.forEach(function (prayer) {
rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].AttackBonus || 1);
});
rollacc = calc.roll.ApplyAdd(rollacc, 8);
if (stance2 === 'Accurate') {
rollacc = calc.roll.ApplyAdd(rollacc, 3);
} else if (stance2 === 'Shared') {
rollacc = calc.roll.ApplyAdd(rollacc, 1);
}
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void melee helm', 1.1, 1.1));
if (stance1 === 'Stab') {
defencestyle = 'dstab';
rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.at);
} else if (stance1 === 'Slash') {
defencestyle = 'dslash';
rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.al);
} else {
defencestyle = 'dcrush';
rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.ac);
}
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 7 / 6, 7 / 6, 1.2));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MLightsword(loadout, 1, 1));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MDhl(loadout));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Viggora\'s chainmace', 1.5));
rollacc = calc.roll.ApplyMult(rollacc, calc.check.MObbyArmour(loadout));
if (calc.check.HasGadder(loadout)) {
rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.95, 1, 0), calc.roll.Roller(0.05, 1, 0)]);
}
if (calc.check.HasKeris(loadout)) {
rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(50 / 51, 1, 0), calc.roll.Roller(1 / 51, 1, 0)]);
}
if (calc.check.HasVerac(loadout)) {
rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1, 1)]);
}
}
return [rollacc, defencestyle];
}
};


// Users settings
// // // // // // //
var settings = {
// Player Skills //
storedLoadouts: [],
// // // // // // //
loadout: {},
if(settings.loadout.playerLevel.current == null) {
/*
settings.loadout.playerLevel.current = {};
Object playerLevel
}
string rsn
if(settings.loadout.playerLevel.visible == null) {
Object current
settings.loadout.playerLevel.visible = {};
}
number Attack
number Strength
dpsForm.form.playerLevel.current = {};
number Defence
dpsForm.form.playerLevel.visible = {};
number Ranged
dpsForm.form.playerLevel.skillIcon = {};
number Magic
dpsForm.form.playerLevel.slash = {};
dpsForm.form.playerLevel.layout = {};
Object visible

number Attack
var addSkill = function(skill, icon, width, height, visible){
number Strength
if(settings.loadout.playerLevel.current[skill] == null) {
number Defence
settings.loadout.playerLevel.current[skill] = 1;
}
number Ranged
number Magic
dpsForm.form.playerLevel.current[skill] = new OO.ui.NumberInputWidget( {
boolean forceLookup (force a hiscore lookup on page load, used when rsn is defined in query string)
min: 0,
Object slot
max: 9999,
step: 1,
Object label
showButtons: false,
string weapon
string combatStyle
value: settings.loadout.playerLevel.current[skill],
string blowpipe
classes: ['dps-player-skill-current','dps-skill-input'],
});
string spell
string head
dpsForm.form.playerLevel.current[skill].on('change', dpsForm.playerLevelChange, [skill,visible]);
string cape
dpsForm.form.playerLevel.skillIcon[skill] = new OO.ui.LabelWidget({
string neck
label:$('<img alt="'+name+'" src="'+icon+'" data-file-width="'+width+'" data-file-height="'+height+'" width="'+width+'" height="'+height+'">'),
string ammo
classes: ['dps-player-skill-icon','dps-skill-icon'],
});
string torso
string shield
dpsForm.form.playerLevel.slash[skill] = new OO.ui.LabelWidget({
label: '/',
string legs
string gloves
classes: ['dps-player-skill-slash','dps-skill-slash'],
});
string boots
string ring
if(settings.loadout.playerLevel.visible[skill] == null) {
settings.loadout.playerLevel.visible[skill] = 1;
}
array prayers
array potions
dpsForm.form.playerLevel.visible[skill] = new OO.ui.NumberInputWidget( {
min: 0,
max: 9999,
boolean visibleBlowpipe
step: 1,
boolean visibleSpell
boolean disabledShield
showButtons: false,
string combatStyleLoadout (combat style options name e.g. 'unarmed')
disabled: (visible === 1),
value: settings.loadout.playerLevel.visible[skill],
boolean wilderness TODO
classes: ['dps-player-skill-visible','dps-player-skill-visible-'+skill,'dps-skill-input'],
});
number distance TODO
Object slotItem (equipment[slot].item for each slot)
if(visible === 2){
Object weapon
dpsForm.form.playerLevel.visible[skill].on('change', dpsForm.playerLevelChange, [skill,3]);
}
Object combatStyle
Object blowpipe
dpsForm.form.playerLevel.layout[skill] = new OO.ui.HorizontalLayout({
items: [
Object spell
Object head
dpsForm.form.playerLevel.skillIcon[skill],
Object cape
dpsForm.form.playerLevel.visible[skill],
Object neck
dpsForm.form.playerLevel.slash[skill],
Object ammo
dpsForm.form.playerLevel.current[skill],
],
Object torso
Object shield
classes: ['dps-player-skill-layout','dps-skill-layout']
});
Object legs
};
Object gloves
Object boots
if(settings.loadout.playerLevel.forceLookup) {
Object ring
settings.loadout.playerLevel.forceLookup = false;
(there is no prayers or potions object)
api.lookup(settings.loadout.playerLevel.rsn);
}
Object equipmentBonus
number at
number al
number ac
number am
number ar
number dt
number dl
number dc
number dm
number dr
number bs
number br
number bm
boolean manualLevel
Object monster
Object visible
number Hitpoints
number Attack
number Strength
number Defence
number Ranged
number Magic
Object current
number Hitpoints
number Attack
number Strength
number Defence
number Ranged
number Magic
string name
array combatType
string attackStyle
number attackSpeed
number maxHit
number astab
number aslash
number acrush
number amagic
number arange
number dstab
number dslash
number dcrush
number dmagic
number drange
number abns
number str
number rstr
number mdmg
number xpBonus
number immunePoison
number immuneVenom
boolean manualLevel
addSkill('Attack','/images/f/fe/Attack_icon.png?b4bce',25,25,1);
*/
addSkill('Strength','/images/1/1b/Strength_icon.png?e6e0c',16,20,1);
addSkill('Defence','/images/b/b7/Defence_icon.png?ca0cd',17,19,1);
addSkill('Ranged','/images/1/19/Ranged_icon.png?01b0e',23,23,1);
addSkill('Magic','/images/5/5c/Magic_icon.png?334cf',25,23,1);
addSkill('Prayer','/images/f/f2/Prayer_icon.png?ca0dc',23,23,2);
addSkill('Hitpoints','/images/9/96/Hitpoints_icon.png?a4819',23,20,2);
addSkill('Mining','/images/4/4a/Mining_icon.png?00870',23,23,1);
addSkill('Farming','/images/f/fc/Farming_icon.png?558fa',25,23,1);


// Load from localStorage
dpsForm.form.playerLevel.skillLayout1 = new OO.ui.HorizontalLayout({
load: function load(type) {
items: [
if (!rswiki.hasLocalStorage()) {
dpsForm.form.playerLevel.layout.Attack,
console.warn('Browser does not support localStorage');
dpsForm.form.playerLevel.layout.Strength,
return;
dpsForm.form.playerLevel.layout.Defence,
}
dpsForm.form.playerLevel.layout.Magic,
switch (type) {
dpsForm.form.playerLevel.layout.Ranged,
],
case 'storedLoadouts':
try {
id: 'dps-player-skill-skillLayout1'
settings.storedLoadouts = JSON.parse(localStorage.getItem('rsw-dps-storedloadouts'));
});
} catch (err) {
dpsForm.form.playerLevel.skillLayout2 = new OO.ui.HorizontalLayout({
items: [
settings.storedLoadouts = [];
console.warn('Error loading storedLoadouts from localStorage');
dpsForm.form.playerLevel.layout.Hitpoints,
dpsForm.form.playerLevel.layout.Prayer,
dpsForm.form.playerLevel.layout.Mining,
dpsForm.form.playerLevel.layout.Farming,
],
id: 'dps-player-skill-skillLayout2'
});

// // // // // // // // // //
// Boosts (Prayer/Potion) //
// // // // // // // // // //
if(settings.loadout.slot == null) {
settings.loadout.slot = {};
}
}
dpsForm.form.slot = {};
if (settings.storedLoadouts === null) {
settings.storedLoadouts = [];

if(settings.loadout.slot.prayers == null) {
settings.loadout.slot.prayers = [];
}
}
console.log('Localstorage loaded - storedLoadouts');
arr = [];
console.log(settings.storedLoadouts);
for(var item in equipment.prayers) {
return settings.storedLoadouts;
if(equipment.prayers[item].vi || true) { //Add all prayers for now
case 'playerLevel':
arr.push({data:item,icon:'search'});
}
try {
settings.loadout.playerLevel = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
} catch (err) {
settings.loadout.playerLevel = {};
console.warn('Error loading loadout from localStorage');
}
}
if (settings.loadout.playerLevel === null) {
dpsForm.form.slot.prayers = new OO.ui.MenuTagMultiselectWidget({
settings.loadout.playerLevel = {};
allowArbitrary: false,
inputPosition: 'outline',
spellcheck: false,
menu: {
highlightOnFilter: true,
hideOnChoose: false,
},
options: arr,
classes: ['dps-slot-menu','prayers'],
});
dpsForm.form.slot.prayers.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
settings.loadout.slot.prayers.forEach(function(prayer) {
dpsForm.form.slot.prayers.addTag(prayer);
});
dpsForm.form.slot.prayers.on('change', dpsForm.prayerChange);
if(settings.loadout.slot.potions == null) {
settings.loadout.slot.potions = [];
}
}
console.log('Localstorage loaded - loadout.playerLevel');
arr = [];
console.log(settings.loadout.playerLevel);
for(var potion in equipment.potions) {
return settings.loadout.playerLevel;
if(equipment.potions[potion].vi) { //only add if visible >= 1
case 'slot':
arr.push({data:potion});
}
try {
settings.loadout.slot = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
} catch (err) {
settings.loadout.slot = {};
console.warn('Error loading loadout from localStorage');
}
}
dpsForm.form.slot.potions = new OO.ui.MenuTagMultiselectWidget({
if (settings.loadout.slot === null) {
allowArbitrary: false,
settings.loadout.slot = {};
inputPosition: 'outline',
spellcheck: false,
menu: {
highlightOnFilter: true,
hideOnChoose: false,
},
options: arr,
classes: ['dps-slot-menu','potions'],
});
dpsForm.form.slot.potions.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
settings.loadout.slot.potions.forEach(function(potion) {
dpsForm.form.slot.potions.addTag(potion);
});
dpsForm.form.slot.potions.on('change', dpsForm.potionChange);

// // // // // // // //
// Equipment slots //
// // // // // // // //
if(settings.loadout.slot.label == null) {
settings.loadout.slot.label = {};
}
}
if(settings.loadout.slot.slotItem == null) {
console.log('Localstorage loaded - loadout.slot');
settings.loadout.slot.slotItem = {};
console.log(settings.loadout.slot);
return settings.loadout.slot;
case 'monster':
try {
settings.loadout.monster = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
} catch (err) {
settings.loadout.monster = {};
console.warn('Error loading loadout from localStorage');
}
}
if(settings.loadout.slot.visibleBlowpipe == null) {
if (settings.loadout.monster === null) {
settings.loadout.slot.visibleBlowpipe = false;
settings.loadout.monster = {};
}
}
if(settings.loadout.slot.visibleSpell == null) {
console.log('Localstorage loaded - loadout.monster');
settings.loadout.slot.visibleSpell = false;
console.log(settings.loadout.monster);
return settings.loadout.monster;
case 'loadout':
settings.load('playerLevel');
settings.load('slot');
settings.load('monster');
return true;
case 'all':
settings.load('storedLoadouts');
settings.load('loadout');
return true;
default:
console.error('Invalid localStorage type: ' + type);
return null;
}
},
//Load from query string on page load
loadQueryString: function loadQueryString() {
var urlParams = {};
var match;
var pl = /\+/g;
var search = /([^&=]+)=?([^&]*)/g;
var decode = function decode(s) {
return decodeURIComponent(s.replace(pl, ' '));
};
var query = window.location.search.substring(1);
while (match = search.exec(query)) {
// eslint-disable-line no-cond-assign
urlParams[decode(match[1])] = decode(match[2]);
}
var skill_iterable = ['Attack', 'Strength', 'Defence', 'Ranged', 'Magic'];
skill_iterable.forEach(function (skill) {
if (skill in urlParams) {
settings.loadout.playerLevel.current[skill] = utils.parseIntDefault(urlParams[skill], 1);
console.log('Loaded query string ' + skill + ' - ' + settings.loadout.playerLevel.current[skill]);
}
});
if ('rsn' in urlParams) {
settings.loadout.playerLevel.rsn = urlParams.rsn;
settings.loadout.playerLevel.forceLookup = true;
console.log('Loaded query string rsn - ' + settings.loadout.playerLevel.rsn);
}
var slot_iterable = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', 'combatStyle', 'shield', 'blowpipe', 'spell']; //shield/blowpipe/spell/combatStyle after weapon
slot_iterable.forEach(function (slot) {
if (slot in urlParams) {
if (slot == 'blowpipe' && !settings.loadout.slot.visibleBlowpipe) {
return;
} else if (slot == 'shield' && settings.loadout.slot.disabledShield) {
return;
} else if (slot == 'spell' && !settings.loadout.slot.visibleSpell) {
return;
}
}
var slot_id = urlParams[slot];
if(settings.loadout.slot.disabledShield == null) {
if (utils.isPositiveInt(slot_id)) {
settings.loadout.slot.disabledShield = false;
var slot_name = equipment.lookupItemID(parseInt(slot_id), slot);
if (slot_name) {
slot_id = slot_name;
}
}
}
dpsForm.form.slot.icon = {};
slot_id = utils.itemCase(slot_id);
console.log('Loaded query string ' + slot + ' - ' + slot_id);
dpsForm.form.slot.label = {};
dpsForm.form.slot.layout = {};
settings.loadout.slot.label[slot] = slot_id;
settings.loadout.slot.slotItem[slot] = equipment.getItem(slot_id, slot)[0];
var addSlot = function(slot,label){
if (slot == 'weapon') {
if(settings.loadout.slot.label[slot] == null) {
settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
settings.loadout.slot.label[slot] = '';
if (settings.loadout.slot.disabledShield == true) {
}
settings.loadout.slot.label.shield = '';
}
if(settings.loadout.slot.slotItem[slot] == null) {
settings.loadout.slot.slotItem[slot] = equipment.getItem('None',slot)[0];
settings.loadout.slot.visibleBlowpipe = slot_id == 'Toxic blowpipe';
if (settings.loadout.slot.visibleBlowpipe == false) {
}
var arr = [];
settings.loadout.slot.label.blowpipe = '';
for(var item in equipment[slot]) {
}
settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
if(equipment[slot][item].vi) { //only add if visible == 1
arr.push({data:item});
} else if (slot == 'combatStyle') {
settings.loadout.slot.visibleSpell = slot_id == 'Magic - spell' || slot_id == 'Magic - defensive spell';
}
if (settings.loadout.slot.visibleSpell == false) {
}
dpsForm.form.slot[slot] = new OO.ui.ComboBoxInputWidget({
settings.loadout.slot.label.spell = '';
autocomplete: false,
}
required: false,
options: arr,
placeholder: label,
value: settings.loadout.slot.label[slot],
spellcheck: false,
menu: {
filterFromInput: true,
autoHide: true,
},
classes: ['dps-slot-menu',slot],
});
dpsForm.form.slot[slot].menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
dpsForm.form.slot[slot].on('change', dpsForm.slotChange, [slot]);
if(slot == 'spell' || slot == 'combatStyle') {
arr = utils.titleCase(settings.loadout.slot.label[slot]);
} else {
arr = utils.itemCase(settings.loadout.slot.label[slot]);
}
if(settings.loadout.slot.slotItem[slot].vi){ //visible selections have a css sprite - Alternatively, could always load the non-sprite image on first load to make the initial page load faster(?)
arr = $('<div class="dps-slot-icon '+slot+'"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-'+slot+' dps-img-'+slot+'-'+arr.replace(/ /g,'_')+'"/></div>');
} else { //invisible selections don't have a css sprite
arr = $('<div class="dps-slot-icon '+slot+'"><img src="'+rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im+'.png').replace('f/current', 'images') +'?abcde"></div>');
}
dpsForm.form.slot.icon[slot] = new OO.ui.LabelWidget({
label: arr
});

dpsForm.form.slot.layout[slot] = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.slot.icon[slot],
dpsForm.form.slot[slot],
]
});
};
addSlot('head','Head');
addSlot('cape','Cape');
addSlot('neck','Neck');
addSlot('ammo','Ammunition');
addSlot('torso','Body');
addSlot('shield','Shield');
addSlot('legs','Legs');
addSlot('gloves','Hand');
addSlot('boots','Feet');
addSlot('ring','Ring');
addSlot('blowpipe','Dart');
addSlot('spell','Spell');
addSlot('weapon','Weapon');

if(settings.loadout.slot.label.combatStyle == null) {
settings.loadout.slot.label.combatStyle = 'Crush - Accurate';
}
}
}
if(settings.loadout.slot.slotItem.combatStyle == null) {
});
settings.loadout.slot.slotItem.combatStyle = equipment.getItem('None','combatStyle')[0];
settings.save('all');
},
// Save to localStorage
save: function save(type) {
if (!rswiki.hasLocalStorage()) {
console.warn('Browser does not support localStorage');
return;
}
var ret;
var stringified;
var save;
switch (type) {
case 'storedLoadouts':
stringified = JSON.stringify(settings.storedLoadouts);
localStorage.setItem('rsw-dps-storedLoadouts', stringified);
// Check for save
try {
ret = JSON.parse(localStorage.getItem('rsw-dps-storedLoadouts'));
} catch (err) {
console.warn('Error saving storedLoadouts to localStorage');
return false;
}
}
console.log('Localstorage saved - storedLoadouts: ' + ret);
if(settings.loadout.slot.combatStyleLoadout == null) {
return true;
settings.loadout.slot.combatStyleLoadout = 'unarmed';
case 'playerLevel':
stringified = JSON.stringify(settings.loadout.playerLevel);
localStorage.setItem('rsw-dps-loadout-playerLevel', stringified);
// Check for save
try {
ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
} catch (err) {
console.warn('Error saving loadout.playerLevel to localStorage');
return false;
}
}
console.log('Localstorage saved - loadout.playerLevel');
dpsForm.form.slot.combatStyle = new OO.ui.DropdownInputWidget({
return true;
classes: ['dps-slot-menu','combatStyle'],
case 'slot':
options: [{data:settings.loadout.slot.label.combatStyle}], //placeholder options - it's generated at the end of the function after the element is attached
value: settings.loadout.slot.label.combatStyle
stringified = JSON.stringify(settings.loadout.slot);
localStorage.setItem('rsw-dps-loadout-slot', stringified);
});
// Check for save
dpsForm.form.slot.combatStyle.on('change', dpsForm.slotChange, ['combatStyle']);
try {

ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
dpsForm.form.slot.icon.combatStyle = new OO.ui.LabelWidget({
} catch (err) {
label: $('<div class="dps-slot-icon combatStyle"><img src="'+rswiki.getFileURLCached(settings.loadout.slot.slotItem.combatStyle.im+'.png').replace('f/current', 'images')+'?abcde"></div>')
console.warn('Error saving loadout.slot to localStorage');
});
return false;

dpsForm.form.slot.layout.combatStyle = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.slot.icon.combatStyle,
dpsForm.form.slot.combatStyle,
]
});

dpsForm.form.slotLayout = new OO.ui.FieldsetLayout( {
items: [
dpsForm.form.slot.layout.weapon, //includes 2h as well
dpsForm.form.slot.layout.blowpipe,
dpsForm.form.slot.layout.combatStyle,
dpsForm.form.slot.layout.spell,
dpsForm.form.slot.layout.head,
dpsForm.form.slot.layout.cape,
dpsForm.form.slot.layout.neck,
dpsForm.form.slot.layout.ammo,
dpsForm.form.slot.layout.torso,
dpsForm.form.slot.layout.shield,
dpsForm.form.slot.layout.legs,
dpsForm.form.slot.layout.gloves,
dpsForm.form.slot.layout.boots,
dpsForm.form.slot.layout.ring,
],
id: 'dps-slot-layout'
});

// // // // // // // //
// Equipment stats //
// // // // // // // //
if(settings.loadout.slot.equipmentBonus == null) {
settings.loadout.slot.equipmentBonus = {};
}
}
if(settings.loadout.slot.equipmentBonus.manualLevel == null) {
console.log('Localstorage saved - loadout.slot');
return true;
settings.loadout.slot.equipmentBonus.manualLevel = false;
case 'monster':
stringified = JSON.stringify(settings.loadout.monster);
localStorage.setItem('rsw-dps-loadout-monster', stringified);
// Check for save
try {
ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
} catch (err) {
console.warn('Error saving loadout.monster to localStorage');
return false;
}
}
console.log('Localstorage saved - loadout.monster');
dpsForm.form.equipmentBonus = {};
dpsForm.form.equipmentBonus.menu = {};
return true;
case 'loadout':
dpsForm.form.equipmentBonus.layout = {};
save = settings.save('playerLevel');
save = settings.save('slot') && save;
save = settings.save('monster') && save;
return save;
case 'all':
save = settings.save('storedLoadouts');
save = settings.save('loadout') && save;
return save;
default:
console.error('Invalid localStorage type ' + type);
return null;
}
}
};
var dpsForm = {
// Placeholder for form elements
form: {},
// Create the form
create: function create() {
var arr;
/* format:
Initialize setting variables
Create widget
Create event hooks*/


// // // // // // //
dpsForm.form.equipmentBonus.levelToggle = new OO.ui.ToggleSwitchWidget( {
// Display Name //
id: 'dps-equip-levelToggle',
// // // // // // //
value: settings.loadout.slot.equipmentBonus.manualLevel
if (settings.loadout.playerLevel == null) {
});
settings.loadout.playerLevel = {};
dpsForm.form.equipmentBonus.levelToggle.on('change', dpsForm.equipmentBonusToggleChange);
}
if (settings.loadout.playerLevel.rsn == null) {
dpsForm.form.equipmentBonus.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.levelToggle, {
settings.loadout.playerLevel.rsn = '';
label:'Manually override equipment stats',
}
align:'left',
dpsForm.form.playerLevel = {};
id:'dps-equip-levelToggleLayout'
dpsForm.form.playerLevel.rsnInput = new OO.ui.TextInputWidget({
});
autocomplete: true,
placeholder: 'Display name',
id: 'dps-player-rsnInput',
value: settings.loadout.playerLevel.rsn,
icon: 'search'
});
dpsForm.form.playerLevel.rsnButton = new OO.ui.ButtonWidget({
label: 'Hiscore Lookup',
id: 'dps-player-rsnButton'
});
dpsForm.form.playerLevel.rsnInput.on('enter', dpsForm.rsnInputSubmit);
dpsForm.form.playerLevel.rsnButton.on('click', dpsForm.rsnInputSubmit);
dpsForm.form.playerLevel.rsnInputLayout = new OO.ui.ActionFieldLayout(dpsForm.form.playerLevel.rsnInput, dpsForm.form.playerLevel.rsnButton, {
id: 'dps-player-rsnInput-layout'
});


// // // // // // //
var addEquipmentBonusForm = function(name, icon, width, height, placeholder){
// Player Skills //
if(settings.loadout.slot.equipmentBonus[name] == null) {
// // // // // // //
settings.loadout.slot.equipmentBonus[name] = '';
if (settings.loadout.playerLevel.current == null) {
}
settings.loadout.playerLevel.current = {};
dpsForm.form.equipmentBonus.menu[name] = new OO.ui.TextInputWidget( {
}
autocomplete: false,
if (settings.loadout.playerLevel.visible == null) {
value: (settings.loadout.slot.equipmentBonus[name]<0?'':'+')+settings.loadout.slot.equipmentBonus[name]+(name==='bm'?'%':''), //add + and %
settings.loadout.playerLevel.visible = {};
classes: ['dps-equip-menu',name],
}
validate: /^[-+]?\d+$/
dpsForm.form.playerLevel.current = {};
});
dpsForm.form.playerLevel.visible = {};
if(name === 'bm'){
dpsForm.form.equipmentBonus.menu[name].setValidation(/^[-+]?\d+%?$/);
dpsForm.form.playerLevel.skillIcon = {};
dpsForm.form.playerLevel.slash = {};
}
dpsForm.form.playerLevel.layout = {};
dpsForm.form.equipmentBonus.menu[name].on('change', dpsForm.equipmentBonusNumberChange, [name]);
var addSkill = function addSkill(skill, icon, width, height, visible) {
if (settings.loadout.playerLevel.current[skill] == null) {
dpsForm.form.equipmentBonus.layout[name] = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.menu[name], {
settings.loadout.playerLevel.current[skill] = 1;
label:$('<img alt="'+name+'" src="'+icon+'" data-file-width="'+width+'" data-file-height="'+height+'" width="'+width+'" height="'+height+'">'),
align:'left',
}
dpsForm.form.playerLevel.current[skill] = new OO.ui.NumberInputWidget({
classes: ['dps-equip-layout',name]
});
min: 0,
};
max: 9999,
step: 1,
addEquipmentBonusForm('at','/images/5/5c/White_dagger.png?51225',21,31,'Aggressive stab bonus');
showButtons: false,
addEquipmentBonusForm('al','/images/8/8b/White_scimitar.png?3ef63',27,30,'Aggressive slash bonus');
value: settings.loadout.playerLevel.current[skill],
addEquipmentBonusForm('ac','/images/6/6a/White_warhammer.png?121eb',22,29,'Aggressive crush bonus');
classes: ['dps-player-skill-current', 'dps-skill-input']
addEquipmentBonusForm('am','/images/5/5c/Magic_icon.png?334cf',25,23,'Aggressive magic bonus');
});
addEquipmentBonusForm('ar','/images/1/19/Ranged_icon.png?01b0e',23,23,'Aggressive ranged bonus');
dpsForm.form.playerLevel.current[skill].on('change', dpsForm.playerLevelChange, [skill, visible]);
addEquipmentBonusForm('dt','/images/5/5c/White_dagger.png?51225',21,31,'Defensive stab bonus');
dpsForm.form.playerLevel.skillIcon[skill] = new OO.ui.LabelWidget({
addEquipmentBonusForm('dl','/images/8/8b/White_scimitar.png?3ef63',27,30,'Defensive slash bonus');
label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
addEquipmentBonusForm('dc','/images/6/6a/White_warhammer.png?121eb',22,29,'Defensive crush bonus');
classes: ['dps-player-skill-icon', 'dps-skill-icon']
addEquipmentBonusForm('dm','/images/5/5c/Magic_icon.png?334cf',25,23,'Defensive magic bonus');
});
addEquipmentBonusForm('dr','/images/1/19/Ranged_icon.png?01b0e',23,23,'Defensive ranged bonus');
dpsForm.form.playerLevel.slash[skill] = new OO.ui.LabelWidget({
addEquipmentBonusForm('bs','/images/1/1b/Strength_icon.png?e6e0c',16,20,'Strength bonus');
label: '/',
addEquipmentBonusForm('br','/images/2/22/Ranged_Strength_icon.png?79763',26,25,'Ranged strength bonus');
classes: ['dps-player-skill-slash', 'dps-skill-slash']
addEquipmentBonusForm('bm','/images/c/cc/Magic_Damage_icon.png?63a1a',28,28,'Magic strength bonus');
});
addEquipmentBonusForm('pr','/images/f/f2/Prayer_icon.png?ca0dc',23,23,'Prayer bonus');
if (settings.loadout.playerLevel.visible[skill] == null) {
settings.loadout.playerLevel.visible[skill] = 1;
}
dpsForm.form.playerLevel.visible[skill] = new OO.ui.NumberInputWidget({
min: 0,
max: 9999,
step: 1,
showButtons: false,
disabled: visible === 1,
value: settings.loadout.playerLevel.visible[skill],
classes: ['dps-player-skill-visible', 'dps-player-skill-visible-' + skill, 'dps-skill-input']
});
if (visible === 2) {
dpsForm.form.playerLevel.visible[skill].on('change', dpsForm.playerLevelChange, [skill, 3]);
}
dpsForm.form.playerLevel.layout[skill] = new OO.ui.HorizontalLayout({
items: [dpsForm.form.playerLevel.skillIcon[skill], dpsForm.form.playerLevel.visible[skill], dpsForm.form.playerLevel.slash[skill], dpsForm.form.playerLevel.current[skill]],
classes: ['dps-player-skill-layout', 'dps-skill-layout']
});
};
if (settings.loadout.playerLevel.forceLookup) {
settings.loadout.playerLevel.forceLookup = false;
api.lookup(settings.loadout.playerLevel.rsn);
}
addSkill('Attack', '/images/f/fe/Attack_icon.png?b4bce', 25, 25, 1);
addSkill('Strength', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 1);
addSkill('Defence', '/images/b/b7/Defence_icon.png?ca0cd', 17, 19, 1);
addSkill('Ranged', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 1);
addSkill('Magic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 1);
addSkill('Prayer', '/images/f/f2/Prayer_icon.png?ca0dc', 23, 23, 2);
addSkill('Hitpoints', '/images/9/96/Hitpoints_icon.png?a4819', 23, 20, 2);
addSkill('Mining', '/images/4/4a/Mining_icon.png?00870', 23, 23, 1);
addSkill('Farming', '/images/f/fc/Farming_icon.png?558fa', 25, 23, 1);
dpsForm.form.playerLevel.skillLayout1 = new OO.ui.HorizontalLayout({
items: [dpsForm.form.playerLevel.layout.Attack, dpsForm.form.playerLevel.layout.Strength, dpsForm.form.playerLevel.layout.Defence, dpsForm.form.playerLevel.layout.Magic, dpsForm.form.playerLevel.layout.Ranged],
id: 'dps-player-skill-skillLayout1'
});
dpsForm.form.playerLevel.skillLayout2 = new OO.ui.HorizontalLayout({
items: [dpsForm.form.playerLevel.layout.Hitpoints, dpsForm.form.playerLevel.layout.Prayer, dpsForm.form.playerLevel.layout.Mining, dpsForm.form.playerLevel.layout.Farming],
id: 'dps-player-skill-skillLayout2'
});


// // // // // // // // // //
dpsForm.form.equipmentBonus.bigLayout1 = new OO.ui.HorizontalLayout({
// Boosts (Prayer/Potion) //
items: [
// // // // // // // // // //
dpsForm.form.equipmentBonus.layout.at,
if (settings.loadout.slot == null) {
dpsForm.form.equipmentBonus.layout.al,
settings.loadout.slot = {};
dpsForm.form.equipmentBonus.layout.ac,
}
dpsForm.form.equipmentBonus.layout.am,
dpsForm.form.equipmentBonus.layout.ar,
dpsForm.form.slot = {};
if (settings.loadout.slot.prayers == null) {
]
settings.loadout.slot.prayers = [];
}
arr = [];
for (var item in equipment.prayers) {
if (equipment.prayers[item].vi || true) {
//Add all prayers for now
arr.push({
data: item,
icon: 'search'
});
});
}
dpsForm.form.equipmentBonus.bigLayout2 = new OO.ui.HorizontalLayout({
}
items: [
dpsForm.form.equipmentBonus.layout.dt,
dpsForm.form.slot.prayers = new OO.ui.MenuTagMultiselectWidget({
allowArbitrary: false,
dpsForm.form.equipmentBonus.layout.dl,
inputPosition: 'outline',
dpsForm.form.equipmentBonus.layout.dc,
spellcheck: false,
dpsForm.form.equipmentBonus.layout.dm,
menu: {
dpsForm.form.equipmentBonus.layout.dr,
]
highlightOnFilter: true,
});
hideOnChoose: false
},
dpsForm.form.equipmentBonus.bigLayout3 = new OO.ui.HorizontalLayout({
items: [
options: arr,
classes: ['dps-slot-menu', 'prayers']
dpsForm.form.equipmentBonus.layout.bs,
});
dpsForm.form.equipmentBonus.layout.br,
dpsForm.form.slot.prayers.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
dpsForm.form.equipmentBonus.layout.bm,
settings.loadout.slot.prayers.forEach(function (prayer) {
dpsForm.form.equipmentBonus.layout.pr,
dpsForm.form.slot.prayers.addTag(prayer);
]
});
dpsForm.form.slot.prayers.on('change', dpsForm.prayerChange);
if (settings.loadout.slot.potions == null) {
settings.loadout.slot.potions = [];
}
arr = [];
for (var potion in equipment.potions) {
if (equipment.potions[potion].vi) {
//only add if visible >= 1
arr.push({
data: potion
});
});
}
}
dpsForm.form.slot.potions = new OO.ui.MenuTagMultiselectWidget({
allowArbitrary: false,
inputPosition: 'outline',
spellcheck: false,
menu: {
highlightOnFilter: true,
hideOnChoose: false
},
options: arr,
classes: ['dps-slot-menu', 'potions']
});
dpsForm.form.slot.potions.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
settings.loadout.slot.potions.forEach(function (potion) {
dpsForm.form.slot.potions.addTag(potion);
});
dpsForm.form.slot.potions.on('change', dpsForm.potionChange);


// // // // // // // //

// Equipment slots //

// // // // // // // //

if (settings.loadout.slot.label == null) {
// // // // // // // // //
settings.loadout.slot.label = {};
// Monster Name/Toggle //
}
// // // // // // // // //
dpsForm.form.monster = {};
if (settings.loadout.slot.slotItem == null) {
settings.loadout.slot.slotItem = {};

}
if(settings.loadout.monster.name == null) {
settings.loadout.monster.name = '';
if (settings.loadout.slot.visibleBlowpipe == null) {
settings.loadout.slot.visibleBlowpipe = false;
}
if (settings.loadout.slot.visibleSpell == null) {
settings.loadout.slot.visibleSpell = false;
}
if (settings.loadout.slot.disabledShield == null) {
settings.loadout.slot.disabledShield = false;
}
dpsForm.form.slot.icon = {};
dpsForm.form.slot.label = {};
dpsForm.form.slot.layout = {};
var addSlot = function addSlot(slot, label) {
if (settings.loadout.slot.label[slot] == null) {
settings.loadout.slot.label[slot] = '';
}
if (settings.loadout.slot.slotItem[slot] == null) {
settings.loadout.slot.slotItem[slot] = equipment.getItem('None', slot)[0];
}
var arr = [];
for (var item in equipment[slot]) {
if (equipment[slot][item].vi) {
//only add if visible == 1
arr.push({
data: item
});
}
}
}
dpsForm.form.slot[slot] = new OO.ui.ComboBoxInputWidget({
arr = [];
for(var mon in monster) {
autocomplete: false,
if(monster[mon].vi) {
required: false,
arr.push({data:mon});
options: arr,
}
placeholder: label,
value: settings.loadout.slot.label[slot],
}
spellcheck: false,
dpsForm.form.monster.name = new OO.ui.ComboBoxInputWidget({
autocomplete: false,
menu: {
required: false,
filterFromInput: true,
options: arr,
autoHide: true
placeholder: 'Monster name',
},
classes: ['dps-slot-menu', slot]
value: settings.loadout.monster.name,
});
spellcheck: false,
dpsForm.form.slot[slot].menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
menu: {
dpsForm.form.slot[slot].on('change', dpsForm.slotChange, [slot]);
filterFromInput: true,
autoHide: true,
if (slot == 'spell' || slot == 'combatStyle') {
arr = utils.titleCase(settings.loadout.slot.label[slot]);
}
});
} else {
arr = utils.itemCase(settings.loadout.slot.label[slot]);
dpsForm.form.monster.name.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
}
dpsForm.form.monster.name.on('change', dpsForm.monsterNameChange);
if (settings.loadout.slot.slotItem[slot].vi) {
//visible selections have a css sprite - Alternatively, could always load the non-sprite image on first load to make the initial page load faster(?)
arr = $('<div class="dps-slot-icon ' + slot + '"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + arr.replace(/ /g, '_') + '"/></div>');
} else {
//invisible selections don't have a css sprite
arr = $('<div class="dps-slot-icon ' + slot + '"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im + '.png').replace('f/current', 'images') + '?abcde"></div>');
}
dpsForm.form.slot.icon[slot] = new OO.ui.LabelWidget({
label: arr
});
dpsForm.form.slot.layout[slot] = new OO.ui.HorizontalLayout({
items: [dpsForm.form.slot.icon[slot], dpsForm.form.slot[slot]]
});
};
addSlot('head', 'Head');
addSlot('cape', 'Cape');
addSlot('neck', 'Neck');
addSlot('ammo', 'Ammunition');
addSlot('torso', 'Body');
addSlot('shield', 'Shield');
addSlot('legs', 'Legs');
addSlot('gloves', 'Hand');
addSlot('boots', 'Feet');
addSlot('ring', 'Ring');
addSlot('blowpipe', 'Dart');
addSlot('spell', 'Spell');
addSlot('weapon', 'Weapon');
if (settings.loadout.slot.label.combatStyle == null) {
settings.loadout.slot.label.combatStyle = 'Crush - Accurate';
}
if (settings.loadout.slot.slotItem.combatStyle == null) {
settings.loadout.slot.slotItem.combatStyle = equipment.getItem('None', 'combatStyle')[0];
}
if (settings.loadout.slot.combatStyleLoadout == null) {
settings.loadout.slot.combatStyleLoadout = 'unarmed';
}
dpsForm.form.slot.combatStyle = new OO.ui.DropdownInputWidget({
classes: ['dps-slot-menu', 'combatStyle'],
options: [{
data: settings.loadout.slot.label.combatStyle
}],
//placeholder options - it's generated at the end of the function after the element is attached
value: settings.loadout.slot.label.combatStyle
});
dpsForm.form.slot.combatStyle.on('change', dpsForm.slotChange, ['combatStyle']);
dpsForm.form.slot.icon.combatStyle = new OO.ui.LabelWidget({
label: $('<div class="dps-slot-icon combatStyle"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem.combatStyle.im + '.png').replace('f/current', 'images') + '?abcde"></div>')
});
dpsForm.form.slot.layout.combatStyle = new OO.ui.HorizontalLayout({
items: [dpsForm.form.slot.icon.combatStyle, dpsForm.form.slot.combatStyle]
});
dpsForm.form.slotLayout = new OO.ui.FieldsetLayout({
items: [dpsForm.form.slot.layout.weapon,
//includes 2h as well
dpsForm.form.slot.layout.blowpipe, dpsForm.form.slot.layout.combatStyle, dpsForm.form.slot.layout.spell, dpsForm.form.slot.layout.head, dpsForm.form.slot.layout.cape, dpsForm.form.slot.layout.neck, dpsForm.form.slot.layout.ammo, dpsForm.form.slot.layout.torso, dpsForm.form.slot.layout.shield, dpsForm.form.slot.layout.legs, dpsForm.form.slot.layout.gloves, dpsForm.form.slot.layout.boots, dpsForm.form.slot.layout.ring],
id: 'dps-slot-layout'
});


//Monster name selection > Layout
// // // // // // // //
// Equipment stats //
dpsForm.form.monster.nameLayout = new OO.ui.FieldLayout(dpsForm.form.monster.name, {
label:'Monster name'
// // // // // // // //
if (settings.loadout.slot.equipmentBonus == null) {
});
settings.loadout.slot.equipmentBonus = {};
}
if (settings.loadout.slot.equipmentBonus.manualLevel == null) {
settings.loadout.slot.equipmentBonus.manualLevel = false;
}
dpsForm.form.equipmentBonus = {};
dpsForm.form.equipmentBonus.menu = {};
dpsForm.form.equipmentBonus.layout = {};
dpsForm.form.equipmentBonus.levelToggle = new OO.ui.ToggleSwitchWidget({
id: 'dps-equip-levelToggle',
value: settings.loadout.slot.equipmentBonus.manualLevel
});
dpsForm.form.equipmentBonus.levelToggle.on('change', dpsForm.equipmentBonusToggleChange);
dpsForm.form.equipmentBonus.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.levelToggle, {
label: 'Manually override equipment stats',
align: 'left',
id: 'dps-equip-levelToggleLayout'
});
var addEquipmentBonusForm = function addEquipmentBonusForm(name, icon, width, height, placeholder) {
if (settings.loadout.slot.equipmentBonus[name] == null) {
settings.loadout.slot.equipmentBonus[name] = '';
}
dpsForm.form.equipmentBonus.menu[name] = new OO.ui.TextInputWidget({
autocomplete: false,
value: (settings.loadout.slot.equipmentBonus[name] < 0 ? '' : '+') + settings.loadout.slot.equipmentBonus[name] + (name === 'bm' ? '%' : ''),
//add + and %
classes: ['dps-equip-menu', name],
validate: /^[-+]?\d+$/
});
if (name === 'bm') {
dpsForm.form.equipmentBonus.menu[name].setValidation(/^[-+]?\d+%?$/);
}
dpsForm.form.equipmentBonus.menu[name].on('change', dpsForm.equipmentBonusNumberChange, [name]);
dpsForm.form.equipmentBonus.layout[name] = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.menu[name], {
label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
align: 'left',
classes: ['dps-equip-layout', name]
});
};
addEquipmentBonusForm('at', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Aggressive stab bonus');
addEquipmentBonusForm('al', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Aggressive slash bonus');
addEquipmentBonusForm('ac', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Aggressive crush bonus');
addEquipmentBonusForm('am', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Aggressive magic bonus');
addEquipmentBonusForm('ar', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Aggressive ranged bonus');
addEquipmentBonusForm('dt', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Defensive stab bonus');
addEquipmentBonusForm('dl', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Defensive slash bonus');
addEquipmentBonusForm('dc', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Defensive crush bonus');
addEquipmentBonusForm('dm', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Defensive magic bonus');
addEquipmentBonusForm('dr', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Defensive ranged bonus');
addEquipmentBonusForm('bs', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 'Strength bonus');
addEquipmentBonusForm('br', '/images/2/22/Ranged_Strength_icon.png?79763', 26, 25, 'Ranged strength bonus');
addEquipmentBonusForm('bm', '/images/c/cc/Magic_Damage_icon.png?63a1a', 28, 28, 'Magic strength bonus');
addEquipmentBonusForm('pr', '/images/f/f2/Prayer_icon.png?ca0dc', 23, 23, 'Prayer bonus');
dpsForm.form.equipmentBonus.bigLayout1 = new OO.ui.HorizontalLayout({
items: [dpsForm.form.equipmentBonus.layout.at, dpsForm.form.equipmentBonus.layout.al, dpsForm.form.equipmentBonus.layout.ac, dpsForm.form.equipmentBonus.layout.am, dpsForm.form.equipmentBonus.layout.ar]
});
dpsForm.form.equipmentBonus.bigLayout2 = new OO.ui.HorizontalLayout({
items: [dpsForm.form.equipmentBonus.layout.dt, dpsForm.form.equipmentBonus.layout.dl, dpsForm.form.equipmentBonus.layout.dc, dpsForm.form.equipmentBonus.layout.dm, dpsForm.form.equipmentBonus.layout.dr]
});
dpsForm.form.equipmentBonus.bigLayout3 = new OO.ui.HorizontalLayout({
items: [dpsForm.form.equipmentBonus.layout.bs, dpsForm.form.equipmentBonus.layout.br, dpsForm.form.equipmentBonus.layout.bm, dpsForm.form.equipmentBonus.layout.pr]
});


// Manual monster - ToggleSwitch
// // // // // // // // //
// Monster Name/Toggle //
if(settings.loadout.monster.manualLevel == null) {
// // // // // // // // //
settings.loadout.monster.manualLevel = false;
}
dpsForm.form.monster = {};
dpsForm.form.monster.levelToggle = new OO.ui.ToggleSwitchWidget( {
if (settings.loadout.monster.name == null) {
settings.loadout.monster.name = '';
id: 'dps-monster-levelToggle',
}
value: settings.loadout.monster.manualLevel
});
arr = [];
for (var mon in monster) {
dpsForm.form.monster.levelToggle.on('change', dpsForm.monsterToggleChange);
if (monster[mon].vi) {
// Manual monster > FieldLayout
arr.push({
data: mon
dpsForm.form.monster.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.monster.levelToggle, {
label:'Manually override monster stats',
align:'left',
id:'dps-monster-levelToggleLayout'
});
});
}
}
// // // // // // //
dpsForm.form.monster.name = new OO.ui.ComboBoxInputWidget({
// Monster Skill //
autocomplete: false,
// // // // // // //
required: false,
options: arr,
placeholder: 'Monster name',
value: settings.loadout.monster.name,
spellcheck: false,
menu: {
filterFromInput: true,
autoHide: true
}
});
dpsForm.form.monster.name.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
dpsForm.form.monster.name.on('change', dpsForm.monsterNameChange);


//Monster name selection > Layout
if(settings.loadout.monster.current == null) {
dpsForm.form.monster.nameLayout = new OO.ui.FieldLayout(dpsForm.form.monster.name, {
settings.loadout.monster.current = {};
}
label: 'Monster name'
});
if(settings.loadout.monster.visible == null) {
settings.loadout.monster.visible = {};
}
dpsForm.form.monster.menu = {};
dpsForm.form.monster.icon = {};
dpsForm.form.monster.layout = {};
dpsForm.form.monster.slash = {};
// Monster PARAMETERS


// Manual monster - ToggleSwitch
var addMonsterSkill = function(skill, icon, width, height){
if (settings.loadout.monster.manualLevel == null) {
settings.loadout.monster.manualLevel = false;
}
dpsForm.form.monster.levelToggle = new OO.ui.ToggleSwitchWidget({
id: 'dps-monster-levelToggle',
value: settings.loadout.monster.manualLevel
});
dpsForm.form.monster.levelToggle.on('change', dpsForm.monsterToggleChange);


// Numeric parameter - NumberInput
// Manual monster > FieldLayout
dpsForm.form.monster.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.monster.levelToggle, {
if(settings.loadout.monster.current[skill] == null) {
label: 'Manually override monster stats',
settings.loadout.monster.current[skill] = 1;
}
align: 'left',
if(settings.loadout.monster.visible[skill] == null) {
id: 'dps-monster-levelToggleLayout'
});
settings.loadout.monster.visible[skill] = 1;
}


// // // // // // //
dpsForm.form.monster.icon[skill] = new OO.ui.LabelWidget({
// Monster Skill //
label:$('<img alt="'+skill+'" src="'+icon+'" data-file-width="'+width+'" data-file-height="'+height+'" width="'+width+'" height="'+height+'">'),
// // // // // // //
classes: ['dps-monster-skill-icon','dps-skill-icon'],
});


dpsForm.form.monster.menu['current'+skill] = new OO.ui.NumberInputWidget({
if (settings.loadout.monster.current == null) {
settings.loadout.monster.current = {};
min: 0,
}
max: 9999,
if (settings.loadout.monster.visible == null) {
step: 1,
settings.loadout.monster.visible = {};
showButtons: false,
}
value: settings.loadout.monster.current[skill],
dpsForm.form.monster.menu = {};
classes: ['dps-monster-skill-current','dps-skill-input'],
dpsForm.form.monster.icon = {};
});
dpsForm.form.monster.menu['current'+skill].on('change', dpsForm.monsterSkillChange, [skill,2]);
dpsForm.form.monster.layout = {};
dpsForm.form.monster.slash = {};
// Monster PARAMETERS


var addMonsterSkill = function addMonsterSkill(skill, icon, width, height) {
dpsForm.form.monster.slash[skill] = new OO.ui.LabelWidget({
label: '/',
// Numeric parameter - NumberInput
if (settings.loadout.monster.current[skill] == null) {
classes: ['dps-monster-skill-slash','dps-skill-slash'],
settings.loadout.monster.current[skill] = 1;
});
}
if (settings.loadout.monster.visible[skill] == null) {
settings.loadout.monster.visible[skill] = 1;
}
dpsForm.form.monster.icon[skill] = new OO.ui.LabelWidget({
label: $('<img alt="' + skill + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
classes: ['dps-monster-skill-icon', 'dps-skill-icon']
});
dpsForm.form.monster.menu['current' + skill] = new OO.ui.NumberInputWidget({
min: 0,
max: 9999,
step: 1,
showButtons: false,
value: settings.loadout.monster.current[skill],
classes: ['dps-monster-skill-current', 'dps-skill-input']
});
dpsForm.form.monster.menu['current' + skill].on('change', dpsForm.monsterSkillChange, [skill, 2]);
dpsForm.form.monster.slash[skill] = new OO.ui.LabelWidget({
label: '/',
classes: ['dps-monster-skill-slash', 'dps-skill-slash']
});
dpsForm.form.monster.menu['visible' + skill] = new OO.ui.NumberInputWidget({
min: 0,
max: 9999,
step: 1,
showButtons: false,
value: settings.loadout.monster.visible[skill],
classes: ['dps-monster-skill-visible', 'dps-skill-input']
});
dpsForm.form.monster.menu['visible' + skill].on('change', dpsForm.monsterSkillChange, [skill, 3]);
dpsForm.form.monster.layout[skill] = new OO.ui.HorizontalLayout({
items: [dpsForm.form.monster.icon[skill], dpsForm.form.monster.menu['visible' + skill], dpsForm.form.monster.slash[skill], dpsForm.form.monster.menu['current' + skill]],
classes: ['dps-monster-skill-layout', 'dps-skill-layout']
});
};
addMonsterSkill('Hitpoints', '/images/9/96/Hitpoints_icon.png?a4819', 23, 20);
addMonsterSkill('Attack', '/images/f/fe/Attack_icon.png?b4bce', 25, 25);
addMonsterSkill('Strength', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20);
addMonsterSkill('Defence', '/images/b/b7/Defence_icon.png?ca0cd', 17, 19);
addMonsterSkill('Magic', '/images/5/5c/Magic_icon.png?334cf', 25, 23);
addMonsterSkill('Ranged', '/images/1/19/Ranged_icon.png?01b0e', 23, 23);


// // // // // // //
dpsForm.form.monster.menu['visible'+skill] = new OO.ui.NumberInputWidget({
// Monster Inputs //
min: 0,
max: 9999,
// // // // // // //
step: 1,
showButtons: false,
value: settings.loadout.monster.visible[skill],
classes: ['dps-monster-skill-visible','dps-skill-input'],
});
dpsForm.form.monster.menu['visible'+skill].on('change', dpsForm.monsterSkillChange, [skill,3]);


var addMonsterForm = function addMonsterForm(name, icon, width, height, placeholder, validate, addplus, addperc, defaultvalue) {
dpsForm.form.monster.layout[skill] = new OO.ui.HorizontalLayout({
items: [
// Numeric parameter - NumberInput
dpsForm.form.monster.icon[skill],
if (settings.loadout.monster[name] == null) {
dpsForm.form.monster.menu['visible'+skill],
settings.loadout.monster[name] = defaultvalue;
}
dpsForm.form.monster.slash[skill],
dpsForm.form.monster.menu['current'+skill],
dpsForm.form.monster.menu[name] = new OO.ui.TextInputWidget({
],
autcomplete: false,
value: (addplus ? settings.loadout.monster[name] < 0 ? '' : '+' : '') + settings.loadout.monster[name] + (addperc ? '%' : ''),
classes: ['dps-monster-skill-layout','dps-skill-layout']
});
//add + and %
};
validate: validate,
classes: ['dps-monster-input', 'dps-monster-input-' + name]
});
dpsForm.form.monster.menu[name].on('change', dpsForm.monsterNumberChange, [name, defaultvalue]);


// Numeric parameter > FieldLayout
addMonsterSkill('Hitpoints','/images/9/96/Hitpoints_icon.png?a4819',23,20);
dpsForm.form.monster.layout[name] = new OO.ui.FieldLayout(dpsForm.form.monster.menu[name], {
addMonsterSkill('Attack','/images/f/fe/Attack_icon.png?b4bce',25,25);
label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
addMonsterSkill('Strength','/images/1/1b/Strength_icon.png?e6e0c',16,20);
align: 'left',
addMonsterSkill('Defence','/images/b/b7/Defence_icon.png?ca0cd',17,19);
classes: ['dps-monster-input-layout']
addMonsterSkill('Magic','/images/5/5c/Magic_icon.png?334cf',25,23);
});
addMonsterSkill('Ranged','/images/1/19/Ranged_icon.png?01b0e',23,23);
};
addMonsterForm('maxHit', '/images/8/8f/Combat_icon.png?93d63', 19, 19, 'Max hit', /^\d+$/, false, false, 0); //#
addMonsterForm('astab', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Aggressive stab bonus', /^[-+]?\d+$/, true, false, 0); //+-#
addMonsterForm('aslash', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Aggressive slash bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('acrush', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Aggressive crush bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('amagic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Aggressive magic bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('arange', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Aggressive ranged bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('dstab', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Defensive stab bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('dslash', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Defensive slash bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('dcrush', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Defensive crush bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('dmagic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Defensive magic bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('drange', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Defensive ranged bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('abns', '/images/f/fe/Attack_icon.png?b4bce', 25, 25, 'Attack bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('str', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 'Strength bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('rstr', '/images/2/22/Ranged_Strength_icon.png?79763', 26, 25, 'Ranged strength bonus', /^[-+]?\d+$/, true, false, 0);
addMonsterForm('mdmg', '/images/c/cc/Magic_Damage_icon.png?63a1a', 28, 28, 'Magic strength bonus', /^[-+]?\d+%?$/, true, true, 0); //+-#%
addMonsterForm('xpBonus', '/images/b/bd/Stats_icon.png?1b467', 25, 23, 'XP bonus', /^\d*(0|0\.0|2\.5|5|5\.0|7\.5)%?$/, false, true, 0); //Multiple of 2.5 +- %
addMonsterForm('attackSpeed', '/images/a/aa/Monster_attack_speed_1.png?fe7f2', 32, 10, 'Attack speed', /^[1-9]\d?$/, false, false, 4); //1-99
addMonsterForm('size', '/images/c/c2/Boxing_ring_icon.png?64787', 27, 22, 'Tile size', /^[1-9]\d?$/, false, false, 1);


// // // // // // //
// attackStyle > DropdownWidget
if (settings.loadout.monster.attackStyle == null) {
// Monster Inputs //
settings.loadout.monster.attackStyle = 'Melee';
// // // // // // //
}
dpsForm.form.monster.menu.attackStyle = new OO.ui.DropdownInputWidget({
placeholder: 'Attack style',
options: [{
data: 'Melee'
}, {
data: 'Stab'
}, {
data: 'Slash'
}, {
data: 'Crush'
}, {
data: 'Ranged'
}, {
data: 'Magic'
}, {
data: 'Magical melee'
}, {
data: 'Magical ranged'
}, {
data: 'Ranged magic'
}],
value: settings.loadout.monster.attackStyle
});
dpsForm.form.monster.menu.attackStyle.on('change', dpsForm.monsterAttackStyleChange);


// attackStyle > FieldLayout
var addMonsterForm = function(name, icon, width, height, placeholder, validate, addplus, addperc, defaultvalue){
dpsForm.form.monster.layout.attackStyle = new OO.ui.FieldLayout(dpsForm.form.monster.menu.attackStyle, {
// Numeric parameter - NumberInput
label: $('<img alt="attackStyle" src="/images/8/8f/Combat_icon.png?93d63" data-file-width="19" data-file-height="19" width="19" height="19">'),
if(settings.loadout.monster[name] == null) {
align: 'left'
settings.loadout.monster[name] = defaultvalue;
}
});
dpsForm.form.monster.menu[name] = new OO.ui.TextInputWidget({
autcomplete: false,
value: (addplus?(settings.loadout.monster[name]<0?'':'+'):'')+settings.loadout.monster[name]+(addperc?'%':''), //add + and %
validate: validate,
classes: ['dps-monster-input','dps-monster-input-'+name],
});
dpsForm.form.monster.menu[name].on('change', dpsForm.monsterNumberChange, [name,defaultvalue]);
// Numeric parameter > FieldLayout
dpsForm.form.monster.layout[name] = new OO.ui.FieldLayout(dpsForm.form.monster.menu[name], {
label:$('<img alt="'+name+'" src="'+icon+'" data-file-width="'+width+'" data-file-height="'+height+'" width="'+width+'" height="'+height+'">'),
align:'left',
classes: ['dps-monster-input-layout'],
});
};
addMonsterForm('maxHit','/images/8/8f/Combat_icon.png?93d63',19,19,'Max hit',/^\d+$/,false,false,0); //#
addMonsterForm('astab','/images/5/5c/White_dagger.png?51225',21,31,'Aggressive stab bonus',/^[-+]?\d+$/,true,false,0); //+-#
addMonsterForm('aslash','/images/8/8b/White_scimitar.png?3ef63',27,30,'Aggressive slash bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('acrush','/images/6/6a/White_warhammer.png?121eb',22,29,'Aggressive crush bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('amagic','/images/5/5c/Magic_icon.png?334cf',25,23,'Aggressive magic bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('arange','/images/1/19/Ranged_icon.png?01b0e',23,23,'Aggressive ranged bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('dstab','/images/5/5c/White_dagger.png?51225',21,31,'Defensive stab bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('dslash','/images/8/8b/White_scimitar.png?3ef63',27,30,'Defensive slash bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('dcrush','/images/6/6a/White_warhammer.png?121eb',22,29,'Defensive crush bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('dmagic','/images/5/5c/Magic_icon.png?334cf',25,23,'Defensive magic bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('drange','/images/1/19/Ranged_icon.png?01b0e',23,23,'Defensive ranged bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('abns','/images/f/fe/Attack_icon.png?b4bce',25,25,'Attack bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('str','/images/1/1b/Strength_icon.png?e6e0c',16,20,'Strength bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('rstr','/images/2/22/Ranged_Strength_icon.png?79763',26,25,'Ranged strength bonus',/^[-+]?\d+$/,true,false,0);
addMonsterForm('mdmg','/images/c/cc/Magic_Damage_icon.png?63a1a',28,28,'Magic strength bonus',/^[-+]?\d+%?$/,true,true,0); //+-#%
addMonsterForm('xpBonus','/images/b/bd/Stats_icon.png?1b467',25,23,'XP bonus',/^\d*(0|0\.0|2\.5|5|5\.0|7\.5)%?$/,false,true,0); //Multiple of 2.5 +- %
addMonsterForm('attackSpeed','/images/a/aa/Monster_attack_speed_1.png?fe7f2',32,10,'Attack speed',/^[1-9]\d?$/,false,false,4); //1-99
addMonsterForm('size','/images/c/c2/Boxing_ring_icon.png?64787',27,22,'Tile size',/^[1-9]\d?$/,false,false,1);
// attackStyle > DropdownWidget
if(settings.loadout.monster.attackStyle == null) {
settings.loadout.monster.attackStyle = 'Melee';
}
dpsForm.form.monster.menu.attackStyle = new OO.ui.DropdownInputWidget({
placeholder: 'Attack style',
options: [
{data:'Melee'},
{data:'Stab'},
{data:'Slash'},
{data:'Crush'},
{data:'Ranged'},
{data:'Magic'},
{data:'Magical melee'},
{data:'Magical ranged'},
{data:'Ranged magic'},
],
value: settings.loadout.monster.attackStyle,
});
dpsForm.form.monster.menu.attackStyle.on('change', dpsForm.monsterAttackStyleChange);
// attackStyle > FieldLayout
dpsForm.form.monster.layout.attackStyle = new OO.ui.FieldLayout(dpsForm.form.monster.menu.attackStyle, {
label:$('<img alt="attackStyle" src="/images/8/8f/Combat_icon.png?93d63" data-file-width="19" data-file-height="19" width="19" height="19">'),
align:'left',
});
// immunePoison > DropdownWidget
if(settings.loadout.monster.immunePoison == null) {
settings.loadout.monster.immunePoison = 0;
}
dpsForm.form.monster.menu.immunePoison = new OO.ui.DropdownInputWidget({
placeholder: 'Poison immunity',
options: [
{data:0, label:'Not immune'},
{data:1, label:'Immune'},
{data:3, label:'Smoke spells only'},
],
value: settings.loadout.monster.immunePoison,
classes: ['dps-monster-input','dps-monster-input-immunePoison'],
});
dpsForm.form.monster.menu.immunePoison.dropdownWidget.menu.setIdealSize = function () { //Overriding another prototype to make the visible selection + the dropdown menu have different widths
this.idealWidth = 150; //override the width
if (!this.clipping) {
this.$clippable.css({width:this.idealWidth});
}
};
dpsForm.form.monster.menu.immunePoison.on('change', dpsForm.immunityChange, ['immunePoison']);
// immunePoison > FieldLayout
dpsForm.form.monster.layout.immunePoison = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immunePoison, {
label:$('<img alt="immunePoison" src="/images/c/ca/Poison_hitsplat.png?04b47" data-file-width="24" data-file-height="23" width="24" height="23">'),
align:'left',
classes: ['dps-monster-input-layout'],
});
// immuneVenom > DropdownWidget
if(settings.loadout.monster.immuneVenom == null) {
settings.loadout.monster.immuneVenom = 0;
}
dpsForm.form.monster.menu.immuneVenom = new OO.ui.DropdownInputWidget({
placeholder: 'Poison immunity',
options: [
{data:0, label:'Not immune'},
{data:1, label:'Immune'},
{data:2, label:'Poisons instead'},
],
value: settings.loadout.monster.immuneVenom,
classes: ['dps-monster-input','dps-monster-input-immuneVenom'],
});
dpsForm.form.monster.menu.immuneVenom.dropdownWidget.menu.setIdealSize = function () { //Overriding another prototype to make the visible selection + the dropdown menu have different widths
this.idealWidth = 150; //override the width
if (!this.clipping) {
this.$clippable.css({width:this.idealWidth});
}
};
dpsForm.form.monster.menu.immuneVenom.on('change', dpsForm.immunityChange, ['immuneVenom']);
// immuneVenom > FieldLayout
dpsForm.form.monster.layout.immuneVenom = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immuneVenom, {
label:$('<img alt="immuneVenom" src="/images/3/37/Venom_hitsplat.png?8843e" data-file-width="24" data-file-height="23" width="24" height="23">'),
align:'left',
classes: ['dps-monster-input-layout'],
});
// combatType > MenuTagMultiselectWidget
if(settings.loadout.monster.combatType == null) {
settings.loadout.monster.combatType = [];
}
dpsForm.form.monster.menu.combatType = new OO.ui.MenuTagMultiselectWidget({
allowArbitrary: false,
inputPosition: 'outline',
spellcheck: false,
placeholder: 'Combat attributes',
menu: {
highlightOnFilter: true,
hideOnChoose: false,
},
options: [
{data:'demon'},
{data:'dragon'},
{data:'fiery'},
{data:'kalphite'},
{data:'leafy'},
{data:'penance'},
{data:'shade'},
{data:'slayer'},
{data:'undead'},
{data:'vampyre1'},
{data:'vampyre2'},
{data:'vampyre3'},
{data:'castle wars flagholder'},
],
});
settings.loadout.monster.combatType.forEach(function(item) {
dpsForm.form.monster.menu.combatType.addTag(item);
});
dpsForm.form.monster.menu.combatType.on('change', dpsForm.monsterCombatTypeChange);
dpsForm.form.monsterLayout1 = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.monster.layout.Hitpoints,
dpsForm.form.monster.layout.Attack,
dpsForm.form.monster.layout.Strength,
dpsForm.form.monster.layout.Defence,
dpsForm.form.monster.layout.Magic,
dpsForm.form.monster.layout.Ranged,
]
});
dpsForm.form.monsterLayout2 = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.monster.layout.astab,
dpsForm.form.monster.layout.aslash,
dpsForm.form.monster.layout.acrush,
dpsForm.form.monster.layout.amagic,
dpsForm.form.monster.layout.arange,
]
});
dpsForm.form.monsterLayout3 = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.monster.layout.dstab,
dpsForm.form.monster.layout.dslash,
dpsForm.form.monster.layout.dcrush,
dpsForm.form.monster.layout.dmagic,
dpsForm.form.monster.layout.drange,
]
});
dpsForm.form.monsterLayout4 = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.monster.layout.abns,
dpsForm.form.monster.layout.str,
dpsForm.form.monster.layout.rstr,
dpsForm.form.monster.layout.mdmg,
dpsForm.form.monster.layout.immunePoison,
]
});
dpsForm.form.monsterLayout5 = new OO.ui.HorizontalLayout({
items: [
dpsForm.form.monster.layout.maxHit,
dpsForm.form.monster.layout.xpBonus,
dpsForm.form.monster.layout.attackSpeed,
dpsForm.form.monster.layout.size,
dpsForm.form.monster.layout.immuneVenom,
]
});
dpsForm.form.monsterLayout6 = new OO.ui.FieldsetLayout({
items: [
dpsForm.form.monster.layout.attackStyle,
dpsForm.form.monster.menu.combatType,
]
});


// immunePoison > DropdownWidget
// Button
if (settings.loadout.monster.immunePoison == null) {
dpsForm.form.calculateButton = new OO.ui.ButtonWidget( {
settings.loadout.monster.immunePoison = 0;
label: 'CALCULATE',
}
icon: 'die',
dpsForm.form.monster.menu.immunePoison = new OO.ui.DropdownInputWidget({
title: 'Remove'
placeholder: 'Poison immunity',
options: [{
data: 0,
label: 'Not immune'
}, {
data: 1,
label: 'Immune'
}, {
data: 3,
label: 'Smoke spells only'
}],
value: settings.loadout.monster.immunePoison,
classes: ['dps-monster-input', 'dps-monster-input-immunePoison']
});
dpsForm.form.monster.menu.immunePoison.dropdownWidget.menu.setIdealSize = function () {
//Overriding another prototype to make the visible selection + the dropdown menu have different widths
this.idealWidth = 150; //override the width
if (!this.clipping) {
this.$clippable.css({
width: this.idealWidth
});
});
}
dpsForm.form.calculateButton.on('click', dpsForm.calculateButtonClicked);
};
dpsForm.form.monster.menu.immunePoison.on('change', dpsForm.immunityChange, ['immunePoison']);


// immunePoison > FieldLayout
dpsForm.form.monster.layout.immunePoison = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immunePoison, {
//add item_id to parameters(?) + all items
label: $('<img alt="immunePoison" src="/images/c/ca/Poison_hitsplat.png?04b47" data-file-width="24" data-file-height="23" width="24" height="23">'),
$('.dps-main').empty()
align: 'left',
classes: ['dps-monster-input-layout']
.append($('<tr><th class="dps-subheader infobox-subheader">Player</th></tr>'))
});
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.playerLevel.rsnInputLayout.$element)
.append(dpsForm.form.playerLevel.skillLayout1.$element)
.append(dpsForm.form.playerLevel.skillLayout2.$element)
)
).append('<tr><th class="dps-subheader infobox-subheader">Boosts</th></tr>')
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.slot.potions.$element)
.append(dpsForm.form.slot.prayers.$element)
)
).append('<tr><th class="dps-subheader infobox-subheader">Equipment</th></tr>')
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.slotLayout.$element)
)
).append('<tr><th class="dps-subheader infobox-subheader">Equipment Stats</th></tr>')
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.equipmentBonus.levelToggleLayout.$element)
.append(dpsForm.form.equipmentBonus.bigLayout1.$element)
.append(dpsForm.form.equipmentBonus.bigLayout2.$element)
.append(dpsForm.form.equipmentBonus.bigLayout3.$element)
)
).append('<tr><th class="dps-subheader infobox-subheader">Monster</th></tr>')
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.monster.nameLayout.$element)
.append(dpsForm.form.monster.levelToggleLayout.$element)
.append(dpsForm.form.monsterLayout1.$element)
.append(dpsForm.form.monsterLayout2.$element)
.append(dpsForm.form.monsterLayout3.$element)
.append(dpsForm.form.monsterLayout4.$element)
.append(dpsForm.form.monsterLayout5.$element)
.append(dpsForm.form.monsterLayout6.$element)
)
).append('<tr><th class="dps-subheader infobox-subheader">Calculate</th></tr>')
.append($('<tr/>')
.append($('<td/>')
.append(dpsForm.form.calculateButton.$element)
)
).append($('<tr class="dps-testOutput"/>'));
//.after($(resultsContainer)).after($(resultsHeader));
//Injecting things with jquery because it's easier than modifying OO.ui
$('.dps-slot-menu.prayers > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder','Activate Prayer');
$('.dps-slot-menu.potions > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder','Use Stat Boosts');


// immuneVenom > DropdownWidget
var slots = ['head','cape','neck','ammo','torso','legs','gloves','boots','ring','weapon','shield','blowpipe','spell','potions','prayers'];
if (settings.loadout.monster.immuneVenom == null) {
slots.forEach(function(slot){
new Image().src = "";
settings.loadout.monster.immuneVenom = 0;
}
$('.dps-slot-menu.'+slot+' > div.oo-ui-menuSelectWidget > div.oo-ui-menuOptionWidget > span.oo-ui-labelElement-label').each(function (){
dpsForm.form.monster.menu.immuneVenom = new OO.ui.DropdownInputWidget({
var pname = $(this).text();
placeholder: 'Poison immunity',
$(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-'+slot+' dps-img-'+slot+'-'+pname.replace(/ /g,'_')+'"/></div>'));
});
options: [{
data: 0,
label: 'Not immune'
}, {
data: 1,
label: 'Immune'
}, {
data: 2,
label: 'Poisons instead'
}],
value: settings.loadout.monster.immuneVenom,
classes: ['dps-monster-input', 'dps-monster-input-immuneVenom']
});
dpsForm.form.monster.menu.immuneVenom.dropdownWidget.menu.setIdealSize = function () {
//Overriding another prototype to make the visible selection + the dropdown menu have different widths
this.idealWidth = 150; //override the width
if (!this.clipping) {
this.$clippable.css({
width: this.idealWidth
});
});
}
};
dpsForm.form.monster.menu.immuneVenom.on('change', dpsForm.immunityChange, ['immuneVenom']);


// immuneVenom > FieldLayout
//TODO: Build the dropdowns here to load the page faster
dpsForm.form.monster.layout.immuneVenom = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immuneVenom, {
label: $('<img alt="immuneVenom" src="/images/3/37/Venom_hitsplat.png?8843e" data-file-width="24" data-file-height="23" width="24" height="23">'),
align: 'left',
classes: ['dps-monster-input-layout']
});


// combatType > MenuTagMultiselectWidget
//needs to be after elements added to page
dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
if (settings.loadout.monster.combatType == null) {
dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
settings.loadout.monster.combatType = [];
}
dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
dpsForm.form.monster.menu.combatType = new OO.ui.MenuTagMultiselectWidget({
dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout,settings.loadout.slot.label.combatStyle);
allowArbitrary: false,
dpsForm.equipmentBonusToggleChange(settings.loadout.slot.equipmentBonus.manualLevel);
inputPosition: 'outline',
dpsForm.monsterToggleChange(settings.loadout.monster.manualLevel);
spellcheck: false,
updateVisibleLevels(['Attack','Strength','Defence','Magic','Ranged','Mining','Farming']);
placeholder: 'Combat attributes',
//Resave loadout to fix null parameters
menu: {
settings.save('loadout');
highlightOnFilter: true,
},
hideOnChoose: false
//When new rsn is submitted,
},
options: [{
//Lookup new skill level values.
data: 'demon'
rsnInputSubmit: function() {
}, {
if(dpsForm.form.playerLevel.rsnButton.isActive()){
return;
data: 'dragon'
}
}, {
console.log('rsnInputSubmit');
data: 'fiery'
}, {
var _rsn = dpsForm.form.playerLevel.rsnInput.getValue();
api.lookup(_rsn);
data: 'kalphite'
}, {
settings.loadout.playerLevel.rsn = _rsn;
settings.save('playerLevel');
data: 'leafy'
},
}, {
data: 'penance'
}, {
// When a player changes one of his current or visible skill levels,
data: 'shade'
playerLevelChange: function(skill, type, value) {
}, {
console.log('playerLevelChange: ' + skill + ' to ' + value);
data: 'slayer'
var lvl = utils.parseIntDefault(value,1);
}, {
if(type===1){ //Main skill like Attack
data: 'undead'
settings.loadout.playerLevel.current[skill] = lvl;
}, {
updateVisibleLevels([skill]);
data: 'vampyre1'
} else if(type===2){ //Current Prayer/Hitpoints
}, {
if(settings.loadout.playerLevel.current[skill] === settings.loadout.playerLevel.visible[skill]){
data: 'vampyre2'
settings.loadout.playerLevel.visible[skill] = lvl;
}, {
dpsForm.form.playerLevel.visible[skill].setValue(value);
}
data: 'vampyre3'
}, {
settings.loadout.playerLevel.current[skill] = lvl;
data: 'castle wars flagholder'
} else if(type===3){ //Visible Prayer/Hitpoints
}]
settings.loadout.playerLevel.visible[skill] = lvl;
}
});
settings.save('playerLevel');
settings.loadout.monster.combatType.forEach(function (item) {
dpsForm.form.monster.menu.combatType.addTag(item);
},
});
dpsForm.form.monster.menu.combatType.on('change', dpsForm.monsterCombatTypeChange);
// When a player changes one of his equipments
dpsForm.form.monsterLayout1 = new OO.ui.HorizontalLayout({
slotChange: function(slot, value) {
items: [dpsForm.form.monster.layout.Hitpoints, dpsForm.form.monster.layout.Attack, dpsForm.form.monster.layout.Strength, dpsForm.form.monster.layout.Defence, dpsForm.form.monster.layout.Magic, dpsForm.form.monster.layout.Ranged]
var itemname;
});
if(slot == 'spell' || slot == 'combatStyle') {
dpsForm.form.monsterLayout2 = new OO.ui.HorizontalLayout({
itemname = utils.titleCase(value);
items: [dpsForm.form.monster.layout.astab, dpsForm.form.monster.layout.aslash, dpsForm.form.monster.layout.acrush, dpsForm.form.monster.layout.amagic, dpsForm.form.monster.layout.arange]
} else {
});
itemname = utils.itemCase(value);
dpsForm.form.monsterLayout3 = new OO.ui.HorizontalLayout({
}
items: [dpsForm.form.monster.layout.dstab, dpsForm.form.monster.layout.dslash, dpsForm.form.monster.layout.dcrush, dpsForm.form.monster.layout.dmagic, dpsForm.form.monster.layout.drange]
console.log('slotChange: ' + slot + ' to ' + itemname);
});
settings.loadout.slot.label[slot] = itemname;
dpsForm.form.monsterLayout4 = new OO.ui.HorizontalLayout({
var ret = equipment.getItem(itemname, slot);
items: [dpsForm.form.monster.layout.abns, dpsForm.form.monster.layout.str, dpsForm.form.monster.layout.rstr, dpsForm.form.monster.layout.mdmg, dpsForm.form.monster.layout.immunePoison]
settings.loadout.slot.slotItem[slot] = ret[0];
if(ret[1]){
});
dpsForm.form.monsterLayout5 = new OO.ui.HorizontalLayout({
dpsForm.form.slot[slot].setFlags({invalid:false});
items: [dpsForm.form.monster.layout.maxHit, dpsForm.form.monster.layout.xpBonus, dpsForm.form.monster.layout.attackSpeed, dpsForm.form.monster.layout.size, dpsForm.form.monster.layout.immuneVenom]
} else {
});
dpsForm.form.slot[slot].setFlags('invalid'); //TODO: the flag gets insta-removed from the class for some unclear reason so it doesn't work. To investigate - probably need to set a validation function
dpsForm.form.monsterLayout6 = new OO.ui.FieldsetLayout({
}
items: [dpsForm.form.monster.layout.attackStyle, dpsForm.form.monster.menu.combatType]
if(settings.loadout.slot.slotItem[slot].vi){ //visible selections have a css sprite
});
dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon '+slot+'"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-'+slot+' dps-img-'+slot+'-'+itemname.replace(/ /g,'_')+'"/></div>'));
} else { //invisible selections don't have a css sprite
dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon '+slot+'"><img src="'+rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im+'.png').replace('f/current', 'images')+'?abcde"></div>'));
}
if(slot == 'weapon') {
settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
if(settings.loadout.slot.disabledShield == true) {
dpsForm.form.slot.shield.setValue('');
settings.loadout.slot.label.shield = '';
}
settings.loadout.slot.visibleBlowpipe = itemname == 'Toxic blowpipe';
dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
if(settings.loadout.slot.visibleBlowpipe == false) {
dpsForm.form.slot.blowpipe.setValue('');
settings.loadout.slot.label.blowpipe = '';
}
settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout);
}
if(slot == 'combatStyle') {
settings.loadout.slot.visibleSpell = itemname == 'Magic - Spell' || itemname == 'Magic - Defensive Spell';
dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
if(settings.loadout.slot.visibleSpell == false) {
dpsForm.form.slot.spell.setValue('');
settings.loadout.slot.label.spell = '';
}
} else if(!settings.loadout.slot.equipmentBonus.manualLevel) {
updateEquipmentBonus();
} else {
dpsForm.form.equipmentBonus.levelToggle.setValue(false);
}
settings.save('slot');
},
//Update the options for combat style. The selected option will default to the first option if value is undefined
loadedCombatStyleLoadout: '',
updateCombatStyleLoadout: function(style, value) {
if(style === dpsForm.loadedCombatStyleLoadout) {
return;
}
dpsForm.loadedCombatStyleLoadout = style;


// Button
var choices = equipment.combatStyleLoadout[style];
dpsForm.form.calculateButton = new OO.ui.ButtonWidget({
value = value || choices[0];
label: 'CALCULATE',
var arr = [];
icon: 'die',
title: 'Remove'
choices.forEach(function(choice) {
});
arr.push({data:choice});
dpsForm.form.calculateButton.on('click', dpsForm.calculateButtonClicked);
});
dpsForm.form.slot.combatStyle.setOptions(arr);
var i = 0;
$('.dps-slot-menu.combatStyle > .oo-ui-dropdownWidget > .oo-ui-menuSelectWidget >.oo-ui-labelElement >.oo-ui-labelElement-label').each(function (){
$(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-combatStyle dps-img-combatStyle-'+style.replace(/ /g,'_')+'-'+i+'"/></div>'));
i++;
});


//add item_id to parameters(?) + all items
dpsForm.form.slot.combatStyle.setValue(value);
settings.loadout.slot.label.combatStyle = value;
settings.save('slot');
},
// When a player selects a new prayer or changes order
// TODO: Event does not fire if player clicks X to remove a value
prayerChangeStop: false,
prayerChange: function(datalist) {
if(dpsForm.prayerChangeStop) { //prevent recursion when turning off other prayers
return;
}
dpsForm.prayerChangeStop = true;
var lastElement = datalist.length-1;
if(lastElement) { //skip if empty list
equipment.prayers[datalist[lastElement].data].TurnsOff.forEach(function(prayer) {
dpsForm.form.slot.prayers.removeTagByData(prayer);
});
}
settings.loadout.slot.prayers = dpsForm.form.slot.prayers.getValue();
dpsForm.prayerChangeStop = false;
settings.save('slot');
},
// When a player selects a new potion or changes order
// TODO: Event does not fire if player clicks X to remove a value
potionChange: function() {
settings.loadout.slot.potions = dpsForm.form.slot.potions.getValue();
updateVisibleLevels(['Attack','Strength','Defence','Magic','Ranged','Mining','Farming']);
settings.save('playerLevel');
settings.save('slot');
},
// When manual equipmentBonus toggle is changed, update all equipment bonuses
equipmentBonusToggleChange: function(value) {
console.log('equipmentBonusToggleChange');
settings.loadout.slot.equipmentBonus.manualLevel = value;
for(var menu in dpsForm.form.equipmentBonus.menu){
dpsForm.form.equipmentBonus.menu[menu].setDisabled(!value);
}
if (!value) {
updateEquipmentBonus();
}
settings.save('slot');
},


$('.dps-main').empty().append($('<tr><th class="dps-subheader infobox-subheader">Player</th></tr>')).append($('<tr/>').append($('<td/>').append(dpsForm.form.playerLevel.rsnInputLayout.$element).append(dpsForm.form.playerLevel.skillLayout1.$element).append(dpsForm.form.playerLevel.skillLayout2.$element))).append('<tr><th class="dps-subheader infobox-subheader">Boosts</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.slot.potions.$element).append(dpsForm.form.slot.prayers.$element))).append('<tr><th class="dps-subheader infobox-subheader">Equipment</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.slotLayout.$element))).append('<tr><th class="dps-subheader infobox-subheader">Equipment Stats</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.equipmentBonus.levelToggleLayout.$element).append(dpsForm.form.equipmentBonus.bigLayout1.$element).append(dpsForm.form.equipmentBonus.bigLayout2.$element).append(dpsForm.form.equipmentBonus.bigLayout3.$element))).append('<tr><th class="dps-subheader infobox-subheader">Monster</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.monster.nameLayout.$element).append(dpsForm.form.monster.levelToggleLayout.$element).append(dpsForm.form.monsterLayout1.$element).append(dpsForm.form.monsterLayout2.$element).append(dpsForm.form.monsterLayout3.$element).append(dpsForm.form.monsterLayout4.$element).append(dpsForm.form.monsterLayout5.$element).append(dpsForm.form.monsterLayout6.$element))).append('<tr><th class="dps-subheader infobox-subheader">Calculate</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.calculateButton.$element))).append($('<tr class="dps-testOutput"/>'));
// When a player changes equipmentBonus property
//.after($(resultsContainer)).after($(resultsHeader));
equipmentBonusNumberChange: function(param, value) {
settings.loadout.slot.equipmentBonus[param] = utils.parseIntDefault(value,0);
settings.save('slot');
},


//Injecting things with jquery because it's easier than modifying OO.ui
monsterUnlockedToggleButton: true, //prevent recursion
$('.dps-slot-menu.prayers > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder', 'Activate Prayer');
monsterNameChange: function(value){
$('.dps-slot-menu.potions > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder', 'Use Stat Boosts');
console.log(value);
var slots = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', 'shield', 'blowpipe', 'spell', 'potions', 'prayers'];
setMonster(value);
slots.forEach(function (slot) {
settings.loadout.monster.name = value;
settings.save('monster');
new Image().src = "";
$('.dps-slot-menu.' + slot + ' > div.oo-ui-menuSelectWidget > div.oo-ui-menuOptionWidget > span.oo-ui-labelElement-label').each(function () {
},
var pname = $(this).text();
$(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + pname.replace(/ /g, '_') + '"/></div>'));
});
});


//TODO: Build the dropdowns here to load the page faster
// When manual monster toggle is changed, update monster stats
monsterToggleChange: function(value) {
console.log('monsterToggleChange');
settings.loadout.monster.manualLevel = value;
for(var menu in dpsForm.form.monster.menu){
dpsForm.form.monster.menu[menu].setDisabled(!value);
}
if (!value && dpsForm.monsterUnlockedToggleButton) {
setMonster(settings.loadout.monster.name);
}
settings.save('monster');
},


//needs to be after elements added to page
monsterSkillChange: function(skill, type, value) {
dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
var lvl = utils.parseIntDefault(value,1);
dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
if(type===2){ //Current
if(settings.loadout.monster.current[skill] === settings.loadout.monster.visible[skill]){
dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout, settings.loadout.slot.label.combatStyle);
settings.loadout.monster.visible[skill] = lvl;
dpsForm.equipmentBonusToggleChange(settings.loadout.slot.equipmentBonus.manualLevel);
dpsForm.form.monster.menu['visible'+skill].setValue(lvl);
dpsForm.monsterToggleChange(settings.loadout.monster.manualLevel);
}
updateVisibleLevels(['Attack', 'Strength', 'Defence', 'Magic', 'Ranged', 'Mining', 'Farming']);
settings.loadout.monster.current[skill] = lvl;
//Resave loadout to fix null parameters
} else if(type===3){ //Visible
settings.loadout.monster.visible[skill] = lvl;
settings.save('loadout');
}
},
//When new rsn is submitted,
if(dpsForm.monsterUnlockedToggleButton) {
//Lookup new skill level values.
settings.save('monster');
rsnInputSubmit: function rsnInputSubmit() {
}
if (dpsForm.form.playerLevel.rsnButton.isActive()) {
},
return;

}
monsterNumberChange: function(param, defaultvalue, value) {
console.log('rsnInputSubmit');
if(param == 'xpBonus') {
var _rsn = dpsForm.form.playerLevel.rsnInput.getValue();
settings.loadout.monster[param] = Math.round(utils.parseFloatDefault(value,defaultvalue)/2.5)*2.5;
api.lookup(_rsn);
console.log('xpBonus'+settings.loadout.monster[param]);
settings.loadout.playerLevel.rsn = _rsn;
} else {
settings.save('playerLevel');
settings.loadout.monster[param] = utils.parseIntDefault(value,defaultvalue);
}
},
// When a player changes one of his current or visible skill levels,
if(dpsForm.monsterUnlockedToggleButton) {
playerLevelChange: function playerLevelChange(skill, type, value) {
settings.save('monster');
console.log('playerLevelChange: ' + skill + ' to ' + value);
}
var lvl = utils.parseIntDefault(value, 1);
},
if (type === 1) {
//Main skill like Attack
monsterCombatTypeChange: function() {
settings.loadout.monster.combatType = dpsForm.form.monster.menu.combatType.getValue();
settings.loadout.playerLevel.current[skill] = lvl;
updateVisibleLevels([skill]);
if(dpsForm.monsterUnlockedToggleButton) {
} else if (type === 2) {
settings.save('monster');
}
//Current Prayer/Hitpoints
if (settings.loadout.playerLevel.current[skill] === settings.loadout.playerLevel.visible[skill]) {
},
settings.loadout.playerLevel.visible[skill] = lvl;
dpsForm.form.playerLevel.visible[skill].setValue(value);
monsterAttackStyleChange: function() {
}
settings.loadout.monster.attackStyle = dpsForm.form.monster.menu.attackStyle.getValue();
settings.loadout.playerLevel.current[skill] = lvl;
if(dpsForm.monsterUnlockedToggleButton){
} else if (type === 3) {
settings.save('monster');
}
//Visible Prayer/Hitpoints
settings.loadout.playerLevel.visible[skill] = lvl;
},
}
settings.save('playerLevel');
immunityChange: function(slot, value) {
},
//console.log('immunityChange'+value);
// When a player changes one of his equipments
settings.loadout.monster[slot] = value;
slotChange: function slotChange(slot, value) {
if(dpsForm.monsterUnlockedToggleButton){
var itemname;
settings.save('monster');
if (slot == 'spell' || slot == 'combatStyle') {
}
itemname = utils.titleCase(value);
},
} else {
itemname = utils.itemCase(value);
calculateButtonClicked: function() {
}
var args = calc.playerAttack(settings.loadout);
console.log('slotChange: ' + slot + ' to ' + itemname);
var aroll = args[0];
settings.loadout.slot.label[slot] = itemname;
var dstyle = args[1];
args = calc.playerMaxHit(settings.loadout);
var ret = equipment.getItem(itemname, slot);
var drollmin = args[0];
settings.loadout.slot.slotItem[slot] = ret[0];
var drollmax = args[1];
if (ret[1]) {
dpsForm.form.slot[slot].setFlags({
var dtypeless = args[2];
var ticks = args[3];
invalid: false
});
var droll = calc.monsterDefence(settings.loadout,dstyle,false);
} else {
var acc = calc.accuracies(aroll,droll);
dpsForm.form.slot[slot].setFlags('invalid'); //TODO: the flag gets insta-removed from the class for some unclear reason so it doesn't work. To investigate - probably need to set a validation function
var dps = calc.naiveDPS(acc,drollmin,drollmax,dtypeless,ticks);
}

if (settings.loadout.slot.slotItem[slot].vi) {
var output = 'CALCS:<br><table><tr><td>%</td><td>Defence roll</td></tr>';
//visible selections have a css sprite
for(var i = 0; i < droll.length; i++) {
dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon ' + slot + '"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + itemname.replace(/ /g, '_') + '"/></div>'));
output += '<tr><td>'+droll[i].Prob.toPrecision(5)*100+'%</td><td>'+droll[i].Roll+'</td></tr>';
}
} else {
//invisible selections don't have a css sprite
output += '</table><br><table><tr><td>%</td><td>Attack roll</td><td>Hit</td><td>Accuracy</td><td>Special</td></tr>';
dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon ' + slot + '"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im + '.png').replace('f/current', 'images') + '?abcde"></div>'));
for(i = 0; i < aroll.length; i++) {
}
output += '<tr><td>'+aroll[i].Prob.toPrecision(5)*100+'%</td><td>'+aroll[i].Roll+'</td><td>'+(dtypeless+drollmin[i].Roll)+'-'+drollmax[i].Roll+'</td><td>'+acc[i].Roll.toPrecision(5)*100+'%</td><td>'+aroll[i].Spec+'</td></tr>';
}
if (slot == 'weapon') {
settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
output += '</table><br><br>DPS: '+dps.toPrecision(5);
dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
$('.dps-testOutput').empty().append(output);
if (settings.loadout.slot.disabledShield == true) {

dpsForm.form.slot.shield.setValue('');
},
settings.loadout.slot.label.shield = '';
}
settings.loadout.slot.visibleBlowpipe = itemname == 'Toxic blowpipe';
dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
if (settings.loadout.slot.visibleBlowpipe == false) {
dpsForm.form.slot.blowpipe.setValue('');
settings.loadout.slot.label.blowpipe = '';
}
settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout);
}
if (slot == 'combatStyle') {
settings.loadout.slot.visibleSpell = itemname == 'Magic - Spell' || itemname == 'Magic - Defensive Spell';
dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
if (settings.loadout.slot.visibleSpell == false) {
dpsForm.form.slot.spell.setValue('');
settings.loadout.slot.label.spell = '';
}
} else if (!settings.loadout.slot.equipmentBonus.manualLevel) {
updateEquipmentBonus();
} else {
dpsForm.form.equipmentBonus.levelToggle.setValue(false);
}
settings.save('slot');
},
//Update the options for combat style. The selected option will default to the first option if value is undefined
loadedCombatStyleLoadout: '',
updateCombatStyleLoadout: function updateCombatStyleLoadout(style, value) {
if (style === dpsForm.loadedCombatStyleLoadout) {
return;
}
dpsForm.loadedCombatStyleLoadout = style;
var choices = equipment.combatStyleLoadout[style];
value = value || choices[0];
var arr = [];
choices.forEach(function (choice) {
arr.push({
data: choice
});
});
dpsForm.form.slot.combatStyle.setOptions(arr);
var i = 0;
$('.dps-slot-menu.combatStyle > .oo-ui-dropdownWidget > .oo-ui-menuSelectWidget >.oo-ui-labelElement >.oo-ui-labelElement-label').each(function () {
$(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-combatStyle dps-img-combatStyle-' + style.replace(/ /g, '_') + '-' + i + '"/></div>'));
i++;
});
dpsForm.form.slot.combatStyle.setValue(value);
settings.loadout.slot.label.combatStyle = value;
settings.save('slot');
},
// When a player selects a new prayer or changes order
// TODO: Event does not fire if player clicks X to remove a value
prayerChangeStop: false,
prayerChange: function prayerChange(datalist) {
if (dpsForm.prayerChangeStop) {
//prevent recursion when turning off other prayers
return;
}
dpsForm.prayerChangeStop = true;
var lastElement = datalist.length - 1;
if (lastElement) {
//skip if empty list
equipment.prayers[datalist[lastElement].data].TurnsOff.forEach(function (prayer) {
dpsForm.form.slot.prayers.removeTagByData(prayer);
});
}
settings.loadout.slot.prayers = dpsForm.form.slot.prayers.getValue();
dpsForm.prayerChangeStop = false;
settings.save('slot');
},
// When a player selects a new potion or changes order
// TODO: Event does not fire if player clicks X to remove a value
potionChange: function potionChange() {
settings.loadout.slot.potions = dpsForm.form.slot.potions.getValue();
updateVisibleLevels(['Attack', 'Strength', 'Defence', 'Magic', 'Ranged', 'Mining', 'Farming']);
settings.save('playerLevel');
settings.save('slot');
},
// When manual equipmentBonus toggle is changed, update all equipment bonuses
equipmentBonusToggleChange: function equipmentBonusToggleChange(value) {
console.log('equipmentBonusToggleChange');
settings.loadout.slot.equipmentBonus.manualLevel = value;
for (var menu in dpsForm.form.equipmentBonus.menu) {
dpsForm.form.equipmentBonus.menu[menu].setDisabled(!value);
}
if (!value) {
updateEquipmentBonus();
}
settings.save('slot');
},
// When a player changes equipmentBonus property
equipmentBonusNumberChange: function equipmentBonusNumberChange(param, value) {
settings.loadout.slot.equipmentBonus[param] = utils.parseIntDefault(value, 0);
settings.save('slot');
},
monsterUnlockedToggleButton: true,
//prevent recursion
monsterNameChange: function monsterNameChange(value) {
console.log(value);
setMonster(value);
settings.loadout.monster.name = value;
settings.save('monster');
},
// When manual monster toggle is changed, update monster stats
monsterToggleChange: function monsterToggleChange(value) {
console.log('monsterToggleChange');
settings.loadout.monster.manualLevel = value;
for (var menu in dpsForm.form.monster.menu) {
dpsForm.form.monster.menu[menu].setDisabled(!value);
}
if (!value && dpsForm.monsterUnlockedToggleButton) {
setMonster(settings.loadout.monster.name);
}
settings.save('monster');
},
monsterSkillChange: function monsterSkillChange(skill, type, value) {
var lvl = utils.parseIntDefault(value, 1);
if (type === 2) {
//Current
if (settings.loadout.monster.current[skill] === settings.loadout.monster.visible[skill]) {
settings.loadout.monster.visible[skill] = lvl;
dpsForm.form.monster.menu['visible' + skill].setValue(lvl);
}
settings.loadout.monster.current[skill] = lvl;
} else if (type === 3) {
//Visible
settings.loadout.monster.visible[skill] = lvl;
}
if (dpsForm.monsterUnlockedToggleButton) {
settings.save('monster');
}
},
monsterNumberChange: function monsterNumberChange(param, defaultvalue, value) {
if (param == 'xpBonus') {
settings.loadout.monster[param] = Math.round(utils.parseFloatDefault(value, defaultvalue) / 2.5) * 2.5;
console.log('xpBonus' + settings.loadout.monster[param]);
} else {
settings.loadout.monster[param] = utils.parseIntDefault(value, defaultvalue);
}
if (dpsForm.monsterUnlockedToggleButton) {
settings.save('monster');
}
},
monsterCombatTypeChange: function monsterCombatTypeChange() {
settings.loadout.monster.combatType = dpsForm.form.monster.menu.combatType.getValue();
if (dpsForm.monsterUnlockedToggleButton) {
settings.save('monster');
}
},
monsterAttackStyleChange: function monsterAttackStyleChange() {
settings.loadout.monster.attackStyle = dpsForm.form.monster.menu.attackStyle.getValue();
if (dpsForm.monsterUnlockedToggleButton) {
settings.save('monster');
}
},
immunityChange: function immunityChange(slot, value) {
//console.log('immunityChange'+value);
settings.loadout.monster[slot] = value;
if (dpsForm.monsterUnlockedToggleButton) {
settings.save('monster');
}
},
calculateButtonClicked: function calculateButtonClicked() {
var args = calc.playerAttack(settings.loadout);
var aroll = args[0];
var dstyle = args[1];
args = calc.playerMaxHit(settings.loadout);
var drollmin = args[0];
var drollmax = args[1];
var dtypeless = args[2];
var ticks = args[3];
var droll = calc.monsterDefence(settings.loadout, dstyle, false);
var acc = calc.accuracies(aroll, droll);
var dps = calc.naiveDPS(acc, drollmin, drollmax, dtypeless, ticks);
var output = 'CALCS:<br><table><tr><td>%</td><td>Defence roll</td></tr>';
for (var i = 0; i < droll.length; i++) {
output += '<tr><td>' + droll[i].Prob.toPrecision(5) * 100 + '%</td><td>' + droll[i].Roll + '</td></tr>';
}
output += '</table><br><table><tr><td>%</td><td>Attack roll</td><td>Hit</td><td>Accuracy</td><td>Special</td></tr>';
for (i = 0; i < aroll.length; i++) {
output += '<tr><td>' + aroll[i].Prob.toPrecision(5) * 100 + '%</td><td>' + aroll[i].Roll + '</td><td>' + (dtypeless + drollmin[i].Roll) + '-' + drollmax[i].Roll + '</td><td>' + acc[i].Roll.toPrecision(5) * 100 + '%</td><td>' + aroll[i].Spec + '</td></tr>';
}
output += '</table><br><br>DPS: ' + dps.toPrecision(5);
$('.dps-testOutput').empty().append(output);
}
};
};

function init() {
function init() {
try {
try {
console.error('----------');
console.error('----------');
console.log('Combat Calculator Gadget Startup');
console.log('Combat Calculator Gadget Startup');
$('table.dps').empty();
$('table.dps').empty();
$('table.dps').append('<tr><th class="dps-header infobox-header">Combat Calculator</th></tr>');
$('table.dps').append('<tr><th class="dps-header infobox-header">Combat Calculator</th></tr>');
$('table.dps').append('<tr class="dps-main"><td>Loading combat calculator</td></tr>');
tic();
$('table.dps').append('<tr class="dps-main"><td>Loading combat calculator</td></tr>');
tic()
settings.load('all');
settings.load('all');
settings.loadQueryString();
toc();
settings.loadQueryString();
toc()
tic();
tic()
dpsForm.create();
dpsForm.create();
toc();
toc()
} catch (err) {
} catch(err) {
console.error(err);
}
console.error(err);
}
}
}

$(init);
$(init);

Latest revision as of 12:06, 20 October 2024

// OSRS DPS Calculator
// Version 1.0
// Made by Gau Cho
//
//
// Thanks to Bitterkoekje, Elessar2, Gaz, Koekenpan, riblet15, TehKittyCat

// import Gadget-dps-data.js
// import Gadget-dps-data2.js

/* globals equipment, monster, $, rswiki, OO */
/* jshint es3: true */

'use strict';

//Debug
var ticTime = performance.now();
function tic() {
  ticTime = performance.now();
}
function toc() {
  var ticTocTime = performance.now() - ticTime;
  console.log('Milliseconds elapsed: ' + ticTocTime);
  return ticTocTime;
}
//End Debug

//'Chivalry': {AttackBonus:1.15, StrengthBonus:1.18, DefenceBonus:1.2, DrainSpeed:24, vi:0, TurnsOff:['Thick Skin','Burst of Strength','Clarity of Thought','Rock Skin','Superhuman Strength','Improved Reflexes','Steel Skin','Ultimate Strength','Incredible Reflexes','Piety','Rigour','Augury']},
equipment.prayers = {
  'Thick Skin': {
    DefenceBonus: 1.05,
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
  },
  'Burst of Strength': {
    StrengthBonus: 1.05,
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Superhuman Strength', 'Ultimate Strength', 'Chivalry', 'Piety']
  },
  'Clarity of Thought': {
    AttackBonus: 1.05,
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Improved Reflexes', 'Incredible Reflexes', 'Chivalry', 'Piety']
  },
  'Sharp Eye': {
    RangedAttackBonus: 1.05,
    RangedStrengthBonus: 1.05,
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Hawk Eye', 'Eagle Eye', 'Rigour']
  },
  'Mystic Will': {
    MagicBonus: 1.05,
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Mystic Lore', 'Mystic Might', 'Augury']
  },
  'Rock Skin': {
    DefenceBonus: 1.1,
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Thick Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
  },
  'Superhuman Strength': {
    StrengthBonus: 1.1,
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Burst of Strength', 'Ultimate Strength', 'Chivalry', 'Piety']
  },
  'Improved Reflexes': {
    AttackBonus: 1.1,
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Clarity of Thought', 'Incredible Reflexes', 'Chivalry', 'Piety']
  },
  'Rapid Restore': {
    DrainSpeed: 1,
    vi: 1,
    TurnsOff: []
  },
  'Rapid Heal': {
    DrainSpeed: 2,
    vi: 1,
    TurnsOff: []
  },
  'Protect Item': {
    DrainSpeed: 2,
    vi: 1,
    TurnsOff: []
  },
  'Hawk Eye': {
    RangedAttackBonus: 1.1,
    RangedStrengthBonus: 1.1,
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Sharp Eye', 'Eagle Eye', 'Rigour']
  },
  'Mystic Lore': {
    MagicBonus: 1.1,
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Mystic Will', 'Mystic Might', 'Augury']
  },
  'Steel Skin': {
    DefenceBonus: 1.15,
    DrainSpeed: 12,
    vi: 0,
    TurnsOff: ['Thick Skin', 'Rock Skin', 'Chivalry', 'Piety', 'Rigour', 'Augury']
  },
  'Ultimate Strength': {
    StrengthBonus: 1.15,
    DrainSpeed: 12,
    vi: 0,
    TurnsOff: ['Burst of Strength', 'Superhuman Strength', 'Chivalry', 'Piety']
  },
  'Incredible Reflexes': {
    AttackBonus: 1.15,
    DrainSpeed: 12,
    vi: 0,
    TurnsOff: ['Clarity of Thought', 'Improved Reflexes', 'Chivalry', 'Piety']
  },
  'Protect from Magic': {
    DrainSpeed: 12,
    vi: 1,
    TurnsOff: ['Protect from Missiles', 'Protect from Melee', 'Retribution', 'Redemption', 'Smite']
  },
  'Protect from Missiles': {
    DrainSpeed: 12,
    vi: 1,
    TurnsOff: ['Protect from Magic', 'Protect from Melee', 'Retribution', 'Redemption', 'Smite']
  },
  'Protect from Melee': {
    DrainSpeed: 12,
    vi: 1,
    TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Retribution', 'Redemption', 'Smite']
  },
  'Eagle Eye': {
    RangedAttackBonus: 1.15,
    RangedStrengthBonus: 1.15,
    DrainSpeed: 12,
    vi: 2,
    TurnsOff: ['Sharp Eye', 'Hawk Eye', 'Rigour']
  },
  'Mystic Might': {
    MagicBonus: 1.15,
    DrainSpeed: 12,
    vi: 2,
    TurnsOff: ['Mystic Will', 'Mystic Lore', 'Augury']
  },
  'Retribution': {
    DrainSpeed: 3,
    vi: 0,
    TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Redemption', 'Smite']
  },
  'Redemption': {
    DrainSpeed: 6,
    vi: 0,
    TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Retribution', 'Smite']
  },
  'Smite': {
    DrainSpeed: 18,
    vi: 0,
    TurnsOff: ['Protect from Magic', 'Protect from Missiles', 'Protect from Melee', 'Retribution', 'Redemption']
  },
  'Preserve': {
    DrainSpeed: 2,
    vi: 1,
    TurnsOff: []
  },
  'Chivalry': {
    AttackBonus: 1.15,
    StrengthBonus: 1.18,
    DefenceBonus: 1.2,
    DrainSpeed: 24,
    vi: 0,
    TurnsOff: ['Thick Skin', 'Burst of Strength', 'Clarity of Thought', 'Rock Skin', 'Superhuman Strength', 'Improved Reflexes', 'Steel Skin', 'Ultimate Strength', 'Incredible Reflexes', 'Piety', 'Rigour', 'Augury']
  },
  'Piety': {
    AttackBonus: 1.2,
    StrengthBonus: 1.23,
    DefenceBonus: 1.25,
    DrainSpeed: 24,
    vi: 2,
    TurnsOff: ['Thick Skin', 'Burst of Strength', 'Clarity of Thought', 'Rock Skin', 'Superhuman Strength', 'Improved Reflexes', 'Steel Skin', 'Ultimate Strength', 'Incredible Reflexes', 'Chivalry', 'Rigour', 'Augury']
  },
  'Rigour': {
    DefenceBonus: 1.25,
    RangedAttackBonus: 1.2,
    RangedStrengthBonus: 1.23,
    DrainSpeed: 24,
    vi: 2,
    TurnsOff: ['Thick Skin', 'Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Sharp Eye', 'Hawk Eye', 'Eagle Eye', 'Augury']
  },
  'Augury': {
    DefenceBonus: 1.25,
    MagicBonus: 1.25,
    DrainSpeed: 24,
    vi: 2,
    TurnsOff: ['Thick Skin', 'Rock Skin', 'Steel Skin', 'Chivalry', 'Piety', 'Mystic Will', 'Mystic Lore', 'Mystic Might', 'Rigour']
  }
};

// 'Super combat potion':{AttackAdd:5,AttackMult:1.15,StrengthAdd:5,StrengthMult:1.15,DefenceAdd:5,DefenceMult:1.15,vi:2},
equipment.potions = {
  'Bastion potion': {
    DefenceAdd: 5,
    DefenceMult: 1.15,
    RangedAdd: 4,
    RangedMult: 1.1,
    vi: 2,
    im: 'Bastion potion(4)'
  },
  'Battlemage potion': {
    DefenceAdd: 5,
    DefenceMult: 1.15,
    MagicAdd: 4,
    MagicMult: 1,
    vi: 2,
    im: 'Battlemage potion(4)'
  },
  'Dragon battleaxe': {
    StrengthAdd: -1,
    StrengthMult: -1,
    vi: 2,
    im: 'Dragon battleaxe'
  },
  'Imbued heart': {
    MagicAdd: 1,
    MagicMult: 1.1,
    vi: 2,
    im: 'Imbued heart'
  },
  'Overload (+)': {
    AttackAdd: 6,
    AttackMult: 1.16,
    StrengthAdd: 6,
    StrengthMult: 1.16,
    DefenceAdd: 6,
    DefenceMult: 1.16,
    RangedAdd: 6,
    RangedMult: 1.16,
    MagicAdd: 6,
    MagicMult: 1.16,
    vi: 2,
    im: 'Overload (4) (Chambers of Xeric)'
  },
  'Overload (nightmare zone)': {
    AttackAdd: 5,
    AttackMult: 1.15,
    StrengthAdd: 5,
    StrengthMult: 1.15,
    DefenceAdd: 5,
    DefenceMult: 1.15,
    RangedAdd: 5,
    RangedMult: 1.15,
    MagicAdd: 5,
    MagicMult: 1.15,
    vi: 2,
    im: 'Overload (4)'
  },
  'Ranging potion': {
    RangedAdd: 4,
    RangedMult: 1.1,
    vi: 2,
    im: 'Ranging potion(4)'
  },
  'Saradomin brew': {
    AttackAdd: -2,
    AttackMult: 0.9,
    StrengthAdd: -2,
    StrengthMult: 0.9,
    DefenceAdd: 2,
    DefenceMult: 1.2,
    RangedAdd: -2,
    RangedMult: 0.9,
    MagicAdd: -2,
    MagicMult: 0.9,
    vi: 2,
    im: 'Saradomin brew(4)'
  },
  'Super combat potion': {
    AttackAdd: 5,
    AttackMult: 1.15,
    StrengthAdd: 5,
    StrengthMult: 1.15,
    DefenceAdd: 5,
    DefenceMult: 1.15,
    vi: 2,
    im: 'Super combat potion(4)'
  },
  'Super magic potion': {
    MagicAdd: 5,
    MagicMult: 1.15,
    vi: 2,
    im: 'Super magic potion (4)'
  },
  'Super ranging': {
    RangedAdd: 5,
    RangedMult: 1.15,
    vi: 2,
    im: 'Super ranging (4)'
  },
  'Xeric\'s aid (+)': {
    AttackAdd: -4,
    AttackMult: 0.9,
    StrengthAdd: -4,
    StrengthMult: 0.9,
    DefenceAdd: 5,
    DefenceMult: 1.2,
    RangedAdd: -4,
    RangedMult: 0.9,
    MagicAdd: -4,
    MagicMult: 0.9,
    vi: 2,
    im: 'Xeric\'s aid (4)'
  },
  'Zamorak brew': {
    AttackAdd: 2,
    AttackMult: 1.2,
    StrengthAdd: 2,
    StrengthMult: 1.12,
    DefenceAdd: -2,
    DefenceMult: 0.9,
    vi: 2,
    im: 'Zamorak brew(4)'
  },
  'Attack potion': {
    AttackAdd: 3,
    AttackMult: 1.1,
    vi: 1,
    im: 'Attack potion(4)'
  },
  'Combat potion': {
    AttackAdd: 3,
    AttackMult: 1.1,
    StrengthAdd: 3,
    StrengthMult: 1.1,
    vi: 1,
    im: 'Combat potion(4)'
  },
  'Defence potion': {
    DefenceAdd: 3,
    DefenceMult: 1.1,
    vi: 1,
    im: 'Defence potion(4)'
  },
  'Elder potion (+)': {
    AttackAdd: 6,
    AttackMult: 1.16,
    StrengthAdd: 6,
    StrengthMult: 1.16,
    DefenceAdd: 6,
    DefenceMult: 1.16,
    vi: 1,
    im: 'Elder potion (4)'
  },
  'Elder potion': {
    AttackAdd: 5,
    AttackMult: 1.13,
    StrengthAdd: 5,
    StrengthMult: 1.13,
    DefenceAdd: 5,
    DefenceMult: 1.13,
    vi: 1,
    im: 'Elder potion (2)'
  },
  'Elder potion (-)': {
    AttackAdd: 4,
    AttackMult: 1.1,
    StrengthAdd: 4,
    StrengthMult: 1.1,
    DefenceAdd: 4,
    DefenceMult: 1.1,
    vi: 1,
    im: 'Elder potion (1)'
  },
  'Excalibur': {
    DefenceAdd: 8,
    DefenceMult: 1,
    vi: 1,
    im: 'Excalibur'
  },
  'Kodai potion (+)': {
    DefenceAdd: 6,
    DefenceMult: 1.16,
    MagicAdd: 6,
    MagicMult: 1.16,
    vi: 1,
    im: 'Kodai potion (4)'
  },
  'Kodai potion': {
    DefenceAdd: 5,
    DefenceMult: 1.13,
    MagicAdd: 5,
    MagicMult: 1.13,
    vi: 1,
    im: 'Kodai potion (2)'
  },
  'Kodai potion (-)': {
    DefenceAdd: 4,
    DefenceMult: 1.1,
    MagicAdd: 4,
    MagicMult: 1.1,
    vi: 1,
    im: 'Kodai potion (1)'
  },
  'Magic essence': {
    MagicAdd: 3,
    MagicMult: 1,
    vi: 1,
    im: 'Magic essence(4)'
  },
  'Magic potion': {
    MagicAdd: 4,
    MagicMult: 1,
    vi: 1,
    im: 'Magic potion(4)'
  },
  'Overload': {
    AttackAdd: 5,
    AttackMult: 1.13,
    StrengthAdd: 5,
    StrengthMult: 1.13,
    DefenceAdd: 5,
    DefenceMult: 1.13,
    RangedAdd: 5,
    RangedMult: 1.13,
    MagicAdd: 5,
    MagicMult: 1.13,
    vi: 1,
    im: 'Overload (2) (Chambers of Xeric)'
  },
  'Overload (-)': {
    AttackAdd: 4,
    AttackMult: 1.1,
    StrengthAdd: 4,
    StrengthMult: 1.1,
    DefenceAdd: 4,
    DefenceMult: 1.1,
    RangedAdd: 4,
    RangedMult: 1.1,
    MagicAdd: 4,
    MagicMult: 1.1,
    vi: 1,
    im: 'Overload (1) (Chambers of Xeric)'
  },
  'Strength potion': {
    StrengthAdd: 3,
    StrengthMult: 1.1,
    vi: 1,
    im: 'Strength potion(4)'
  },
  'Super attack': {
    AttackAdd: 5,
    AttackMult: 1.15,
    vi: 1,
    im: 'Super attack(4)'
  },
  'Super defence': {
    DefenceAdd: 5,
    DefenceMult: 1.15,
    vi: 1,
    im: 'Super defence(4)'
  },
  'Super restore': {
    AttackAdd: 0,
    AttackMult: 1,
    StrengthAdd: 0,
    StrengthMult: 1,
    DefenceAdd: 0,
    DefenceMult: 1,
    RangedAdd: 0,
    RangedMult: 1,
    MagicAdd: 0,
    MagicMult: 1,
    vi: 1,
    im: 'Super restore(4)'
  },
  'Super strength': {
    StrengthAdd: 5,
    StrengthMult: 1.15,
    vi: 1,
    im: 'Super strength(4)'
  },
  'Twisted potion (+)': {
    DefenceAdd: 6,
    DefenceMult: 1.16,
    RangedAdd: 6,
    RangedMult: 1.16,
    vi: 1,
    im: 'Twisted potion (4)'
  },
  'Twisted potion': {
    DefenceAdd: 5,
    DefenceMult: 1.13,
    RangedAdd: 5,
    RangedMult: 1.13,
    vi: 1,
    im: 'Twisted potion (2)'
  },
  'Twisted potion (-)': {
    DefenceAdd: 4,
    DefenceMult: 1.1,
    RangedAdd: 4,
    RangedMult: 1.1,
    vi: 1,
    im: 'Twisted potion (1)'
  },
  'Dragon pickaxe': {
    MiningAdd: 3,
    MiningMult: 1,
    vi: 1,
    im: 'Dragon pickaxe'
  },
  'Garden pie': {
    FarmingAdd: 3,
    FarmingMult: 1,
    vi: 1,
    im: 'Garden pie'
  }
};

// 'Blood Barrage': {attackSpeed:5, maxHit:29, fireSpell:false, vi:1, im:'Blood Barrage icon'},
equipment.spell = {
  'None': {
    aS: 5,
    mh: 0,
    vi: 0,
    im: 'Spellbook'
  },
  'Blood Barrage': {
    aS: 5,
    mh: 29,
    vi: 1,
    im: 'Blood Barrage icon'
  },
  'Blood Blitz': {
    aS: 5,
    mh: 25,
    vi: 1,
    im: 'Blood Blitz icon'
  },
  'Blood Burst': {
    aS: 5,
    mh: 21,
    vi: 1,
    im: 'Blood Burst icon'
  },
  'Blood Rush': {
    aS: 5,
    mh: 15,
    vi: 1,
    im: 'Blood Rush icon'
  },
  'Claws of Guthix': {
    aS: 5,
    mh: 20,
    smoke: 1,
    vi: 1,
    im: 'Claws of Guthix icon'
  },
  'Claws of Guthix (charged)': {
    aS: 5,
    mh: 30,
    smoke: 1,
    vi: 1,
    im: 'Claws of Guthix icon'
  },
  'Crumble Undead': {
    aS: 5,
    mh: 15,
    smoke: 1,
    vi: 1,
    im: 'Crumble Undead icon'
  },
  'Earth Blast': {
    aS: 5,
    mh: 15,
    smoke: 1,
    vi: 1,
    im: 'Earth Blast icon'
  },
  'Earth Bolt': {
    aS: 5,
    mh: 11,
    smoke: 1,
    bolt: 1,
    vi: 1,
    im: 'Earth Bolt icon'
  },
  'Earth Strike': {
    aS: 5,
    mh: 6,
    smoke: 1,
    vi: 1,
    im: 'Earth Strike icon'
  },
  'Earth Surge': {
    aS: 5,
    mh: 23,
    smoke: 1,
    vi: 1,
    im: 'Earth Surge icon'
  },
  'Earth Wave': {
    aS: 5,
    mh: 19,
    smoke: 1,
    vi: 1,
    im: 'Earth Wave icon'
  },
  'Fire Blast': {
    aS: 5,
    mh: 16,
    fire: 1,
    smoke: 1,
    vi: 1,
    im: 'Fire Blast icon'
  },
  'Fire Bolt': {
    aS: 5,
    mh: 12,
    fire: 1,
    bolt: 1,
    smoke: 1,
    vi: 1,
    im: 'Fire Bolt icon'
  },
  'Fire Strike': {
    aS: 5,
    mh: 8,
    fire: 1,
    smoke: 1,
    vi: 1,
    im: 'Fire Strike icon'
  },
  'Fire Surge': {
    aS: 5,
    mh: 24,
    fire: 1,
    smoke: 1,
    vi: 1,
    im: 'Fire Surge icon'
  },
  'Fire Wave': {
    aS: 5,
    mh: 20,
    fire: 1,
    smoke: 1,
    vi: 1,
    im: 'Fire Wave icon'
  },
  'Flames of Zamorak': {
    aS: 5,
    mh: 20,
    smoke: 1,
    vi: 1,
    im: 'Flames of Zamorak icon'
  },
  'Flames of Zamorak (charged)': {
    aS: 5,
    mh: 30,
    smoke: 1,
    vi: 1,
    im: 'Flames of Zamorak icon'
  },
  'Iban Blast': {
    aS: 5,
    mh: 25,
    smoke: 1,
    vi: 1,
    im: 'Iban Blast icon'
  },
  'Ice Barrage': {
    aS: 5,
    mh: 30,
    vi: 1,
    im: 'Ice Barrage icon'
  },
  'Ice Blitz': {
    aS: 5,
    mh: 26,
    vi: 1,
    im: 'Ice Blitz icon'
  },
  'Ice Burst': {
    aS: 5,
    mh: 22,
    vi: 1,
    im: 'Ice Burst icon'
  },
  'Ice Rush': {
    aS: 5,
    mh: 16,
    vi: 1,
    im: 'Ice Rush icon'
  },
  'Magic Dart': {
    aS: 5,
    mh: -1,
    smoke: 1,
    vi: 1,
    im: 'Magic Dart icon'
  },
  'Saradomin Strike': {
    aS: 5,
    mh: 20,
    smoke: 1,
    vi: 1,
    im: 'Saradomin Strike icon'
  },
  'Saradomin Strike (charged)': {
    aS: 5,
    mh: 30,
    smoke: 1,
    vi: 1,
    im: 'Saradomin Strike icon'
  },
  'Shadow Barrage': {
    aS: 5,
    mh: 28,
    vi: 1,
    im: 'Shadow Barrage icon'
  },
  'Shadow Blitz': {
    aS: 5,
    mh: 24,
    vi: 1,
    im: 'Shadow Blitz icon'
  },
  'Shadow Burst': {
    aS: 5,
    mh: 18,
    vi: 1,
    im: 'Shadow Burst icon'
  },
  'Shadow Rush': {
    aS: 5,
    mh: 14,
    vi: 1,
    im: 'Shadow Rush icon'
  },
  'Smoke Barrage': {
    aS: 5,
    mh: 27,
    vi: 1,
    im: 'Smoke Barrage icon'
  },
  'Smoke Blitz': {
    aS: 5,
    mh: 23,
    vi: 1,
    im: 'Smoke Blitz icon'
  },
  'Smoke Burst': {
    aS: 5,
    mh: 17,
    vi: 1,
    im: 'Smoke Burst icon'
  },
  'Smoke Rush': {
    aS: 5,
    mh: 13,
    vi: 1,
    im: 'Smoke Rush icon'
  },
  'Water Blast': {
    aS: 5,
    mh: 14,
    smoke: 1,
    vi: 1,
    im: 'Water Blast icon'
  },
  'Water Bolt': {
    aS: 5,
    mh: 10,
    smoke: 1,
    bolt: 1,
    vi: 1,
    im: 'Water Bolt icon'
  },
  'Water Strike': {
    aS: 5,
    mh: 4,
    smoke: 1,
    vi: 1,
    im: 'Water Strike icon'
  },
  'Water Surge': {
    aS: 5,
    mh: 22,
    smoke: 1,
    vi: 1,
    im: 'Water Surge icon'
  },
  'Water Wave': {
    aS: 5,
    mh: 18,
    smoke: 1,
    vi: 1,
    im: 'Water Wave icon'
  },
  'Wind Blast': {
    aS: 5,
    mh: 13,
    smoke: 1,
    vi: 1,
    im: 'Wind Blast icon'
  },
  'Wind Bolt': {
    aS: 5,
    mh: 9,
    smoke: 1,
    bolt: 1,
    vi: 1,
    im: 'Wind Bolt icon'
  },
  'Wind Strike': {
    aS: 5,
    mh: 2,
    smoke: 1,
    vi: 1,
    im: 'Wind Strike icon'
  },
  'Wind Surge': {
    aS: 5,
    mh: 21,
    smoke: 1,
    vi: 1,
    im: 'Wind Surge icon'
  },
  'Wind Wave': {
    aS: 5,
    mh: 17,
    smoke: 1,
    vi: 1,
    im: 'Wind Wave icon'
  }
};

// 'axe': ['Slash - Accurate','Slash - Aggressive','Crush - Aggressive','Slash - Defensive','Magic - Spell'],
equipment.combatStyleLoadout = {
  'axe': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
  'banner': ['Stab - Accurate', 'Slash - Aggressive', 'Crush - Shared', 'Stab - Defensive', 'Magic - Spell'],
  'blaster': ['Magic - Spell'],
  'bludgeon': ['Crush - Aggressive', 'Crush - Aggressive', 'Crush - Aggressive', 'Magic - Spell'],
  'blunt': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
  'bow': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
  'bulwark': ['Crush - Accurate', 'Block - N/A'],
  'claws': ['Slash - Accurate', 'Slash - Aggressive', 'Stab - Shared', 'Slash - Defensive', 'Magic - Spell'],
  'crossbow': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
  'flamer': ['Slash - Aggressive', 'Ranged - Rapid', 'Magic - Accurate', 'Magic - Spell'],
  'grenade': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
  'gun': ['Crush - Aggressive', 'Magic - Spell'],
  'hacksword': ['Slash - Accurate', 'Slash - Aggressive', 'Stab - Shared', 'Slash - Defensive', 'Magic - Spell'],
  'heavysword': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
  'pickaxe': ['Stab - Accurate', 'Stab - Aggressive', 'Crush - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
  'polearm': ['Stab - Shared', 'Slash - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
  'polestaff': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
  'scythe': ['Slash - Accurate', 'Slash - Aggressive', 'Crush - Aggressive', 'Slash - Defensive', 'Magic - Spell'],
  'spear': ['Stab - Shared', 'Slash - Shared', 'Crush - Shared', 'Stab - Defensive', 'Magic - Spell'],
  'spiked': ['Crush - Accurate', 'Crush - Aggressive', 'Stab - Shared', 'Crush - Defensive', 'Magic - Spell'],
  'stabsword': ['Stab - Accurate', 'Stab - Aggressive', 'Slash - Aggressive', 'Stab - Defensive', 'Magic - Spell'],
  'staff': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell', 'Magic - Defensive Spell'],
  'staff bladed': ['Stab - Accurate', 'Slash - Aggressive', 'Crush - Defensive', 'Magic - Spell', 'Magic - Defensive Spell'],
  'staff selfpowering': ['Magic - Accurate', 'Magic - Longrange', 'Magic - Spell'],
  'thrown': ['Ranged - Accurate', 'Ranged - Rapid', 'Ranged - Longrange', 'Magic - Spell'],
  'unarmed': ['Crush - Accurate', 'Crush - Aggressive', 'Crush - Defensive', 'Magic - Spell'],
  'whip': ['Slash - Accurate', 'Slash - Shared', 'Slash - Defensive', 'Magic - Spell']
};
equipment.combatStyle = {
  'None': {
    im: 'Combat icon'
  }
};

//returns item name from id
equipment.lookupItemID = function (id, slot) {
  for (var key in equipment[slot]) {
    if (equipment[slot][key].id.includes(id)) {
      return key;
    }
  }
  return false;
};

//returns [item object (obj), success (bool)] from item name:
equipment.getItem = function (name, slot) {
  if (slot == 'spell' || slot == 'combatStyle') {
    name = utils.titleCase(name);
  } else {
    name = utils.itemCase(name);
  }
  if (name in equipment[slot]) {
    return [equipment[slot][name], true];
  } else {
    return [equipment[slot]['None'], false];
  }
};

//Override this prototype to update the function from osrs wiki's 2018-04-17T22:23:58Z version to the current version 2019-12-12T00:27:42Z
OO.ui.SelectWidget.prototype.getItemMatcher = function (query, mode) {
  var normalizeForMatching = this.constructor["static"].normalizeForMatching,
    normalizedQuery = normalizeForMatching(query);
  mode = this.filterMode || mode; //Check filtermode since the old version of the widgets don't pass on filtermode
  if (mode === true) {
    // Support deprecated exact=true argument
    mode = 'exact';
  }
  return function (item) {
    var matchText = normalizeForMatching(item.getMatchText());
    if (normalizedQuery === '') {
      return mode !== 'exact'; // Empty string matches all, except if we are in 'exact' mode, where it doesn't match at all
    }
    switch (mode) {
      case 'exact':
        return matchText === normalizedQuery;
      case 'substring':
        return matchText.indexOf(normalizedQuery) !== -1;
      default:
        //prefix
        return matchText.indexOf(normalizedQuery) === 0;
    }
  };
};
OO.ui.SelectWidget["static"].normalizeForMatching = function (text) {
  var normalized = text.trim().replace(/\s+/, ' ').toLowerCase();
  if (normalized.normalize) {
    normalized = normalized.normalize();
  }
  return normalized;
};
var updateEquipmentBonus = function updateEquipmentBonus() {
  var bonuses = ['at', 'al', 'ac', 'am', 'ar', 'dt', 'dl', 'dc', 'dm', 'dr', 'bs', 'br', 'bm', 'pr'];
  var slots = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', /*'combatStyle',*/'shield', 'blowpipe' /*,'spell'*/];
  bonuses.forEach(function (bonus) {
    var bonusvalue = 0;
    slots.forEach(function (slot) {
      if (typeof settings.loadout.slot.slotItem[slot][bonus] !== 'undefined') {
        bonusvalue += settings.loadout.slot.slotItem[slot][bonus];
      }
    });
    settings.loadout.slot.equipmentBonus[bonus] = bonusvalue;
    dpsForm.form.equipmentBonus.menu[bonus].setValue((bonusvalue < 0 ? '' : '+') + bonusvalue + (bonus === 'bm' ? '%' : '')); //with sign
  });
};
var updateVisibleLevels = function updateVisibleLevels(skills) {
  skills.forEach(function (skill) {
    settings.loadout.playerLevel.visible[skill] = settings.loadout.playerLevel.current[skill];
    settings.loadout.slot.potions.forEach(function (potion) {
      var potionBoost;
      if (skill + 'Add' in equipment.potions[potion]) {
        if (potion === 'Dragon battleaxe') {
          potionBoost = Math.floor((Math.floor(settings.loadout.playerLevel.current.Magic / 10) + Math.floor(settings.loadout.playerLevel.current.Defence / 10) + Math.floor(settings.loadout.playerLevel.current.Ranged / 10) + Math.floor(settings.loadout.playerLevel.current.Attack / 10)) / 4) + 10 + settings.loadout.playerLevel.current.Strength;
        } else {
          potionBoost = settings.loadout.playerLevel.current[skill] * equipment.potions[potion][skill + 'Mult'] + equipment.potions[potion][skill + 'Add'];
        }
        settings.loadout.playerLevel.visible[skill] = Math.max(settings.loadout.playerLevel.visible[skill], potionBoost);
      }
    });
    settings.loadout.playerLevel.visible[skill] = Math.floor(settings.loadout.playerLevel.visible[skill]);
    dpsForm.form.playerLevel.visible[skill].setValue(settings.loadout.playerLevel.visible[skill]);
  });
};
var setMonster = function setMonster(name) {
  var saveSkill = function saveSkill(setname, getname, defaultvalue, defaultsetting) {
    var val;
    if (typeof mobj[getname] === 'undefined') {
      val = defaultsetting;
    } else {
      val = mobj[getname];
    }
    settings.loadout.monster.current[setname] = val;
    settings.loadout.monster.visible[setname] = val;
    dpsForm.form.monster.menu['current' + setname].setValue(val);
    dpsForm.form.monster.menu['visible' + setname].setValue(val);
  };
  var saveVar = function saveVar(setname, getname, defaultvalue, defaultsetting, addplus, addperc) {
    var val;
    if (typeof mobj[getname] === 'undefined') {
      val = defaultsetting;
    } else {
      val = mobj[getname];
    }
    settings.loadout.monster[setname] = val;
    val = (addplus ? val < 0 ? '' : '+' : '') + val + (addperc ? '%' : '');
    dpsForm.form.monster.menu[setname].setValue(val);
    return;
  };
  dpsForm.monsterUnlockedToggleButton = false; //prevent recursion
  dpsForm.form.monster.levelToggle.setValue(false);
  var mobj;
  if (name in monster) {
    mobj = monster[name];
    saveSkill('Hitpoints', 'Hi', '', 1);
    saveSkill('Attack', 'At', '', 1);
    saveSkill('Strength', 'St', '', 1);
    saveSkill('Defence', 'De', '', 1);
    saveSkill('Ranged', 'Ra', '', 1);
    saveSkill('Magic', 'Ma', '', 1);
    saveVar('attackSpeed', 'aS', '', 4);
    saveVar('maxHit', 'mh', '', 0, false, false);
    saveVar('astab', 'at', '', 0, true, false);
    saveVar('aslash', 'al', '', 0, true, false);
    saveVar('acrush', 'ac', '', 0, true, false);
    saveVar('amagic', 'am', '', 0, true, false);
    saveVar('arange', 'ar', '', 0, true, false);
    saveVar('dstab', 'dt', '', 0, true, false);
    saveVar('dslash', 'dl', '', 0, true, false);
    saveVar('dcrush', 'dc', '', 0, true, false);
    saveVar('dmagic', 'dm', '', 0, true, false);
    saveVar('drange', 'dr', '', 0, true, false);
    saveVar('abns', 'ba', '', 0, true, false);
    saveVar('str', 'bs', '', 0, true, false);
    saveVar('rstr', 'br', '', 0, true, false);
    saveVar('mdmg', 'bm', '', 0, true, true);
    saveVar('xpBonus', 'xp', 0, 0, false, true);
    saveVar('immunePoison', 'ip', 0, 0, false, false);
    saveVar('immuneVenom', 'iv', 0, 0, false, false);
    saveVar('size', 'si', 1, 1, false, false);

    //combatType
    if (typeof mobj.cT === 'undefined') {
      settings.loadout.monster.combatType = [];
      dpsForm.form.monster.menu.combatType.clearItems();
    } else {
      dpsForm.form.monster.menu.combatType.clearItems();
      settings.loadout.monster.combatType = mobj.cT;
      settings.loadout.monster.combatType.forEach(function (item) {
        dpsForm.form.monster.menu.combatType.addTag(item);
      });
    }

    //attackStyle
    if (typeof mobj.aC === 'undefined') {
      settings.loadout.monster.attackStyle = 'Melee';
      dpsForm.form.monster.menu.attackStyle.setValue('Melee');
    } else {
      var styleChoices = ['Melee', 'Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Magical melee', 'Magical ranged', 'Ranged magic'];
      styleChoices.forEach(function (choice) {
        if (mobj.aC.indexOf(choice, 0) == 0) {
          settings.loadout.monster.attackStyle = choice;
          dpsForm.form.monster.menu.attackStyle.setValue(choice);
        }
      });
    }
    dpsForm.monsterUnlockedToggleButton = true;
    return mobj;
  } else {
    mobj = monster['None'];
    dpsForm.monsterUnlockedToggleButton = true;
    return mobj;
  }
};
var utils = {
  //Capitalize strings in the style of runescape items
  itemCase: function itemCase(val) {
    return val.charAt(0).toUpperCase() + val.slice(1).toLowerCase();
  },
  //Capitalize first letter of every word except of and (charged)
  titleCase: function titleCase(val) {
    val = val.replace(/\w+/g, function (word) {
      return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    });
    val = val.replace(/ Of /g, ' of '); //doesn't work for " Of Of ", or start/end of sentence but whatevs
    val = val.replace(/\(Charged\)/g, '(charged)');
    return val;
  },
  //only accepts digits and nothing else (positive ints only)
  isPositiveInt: function isPositiveInt(val) {
    return /^\d+$/.test(val);
  },
  //parses num, but returns def if NaN
  parseIntDefault: function parseIntDefault(num, def) {
    num = parseInt(num, 10);
    return Number.isNaN(num) ? def : num;
  },
  //parses num, but returns def if NaN
  parseFloatDefault: function parseFloatDefault(num, def) {
    num = parseFloat(num);
    return Number.isNaN(num) ? def : num;
  }
};

// https://runescape.wiki/w/Application_programming_interface#Old_School_Hiscores
var api = {
  dumpOrder: [null, 'Attack', 'Defence', 'Strength', 'Hitpoints', 'Ranged', 'Prayer', 'Magic', null, null, null, null, null, null, null, 'Mining', null, null, null, null, 'Farming'],
  lookup: function lookup(rsn) {
    var throwError = function throwError(error) {
      dpsForm.form.playerLevel.rsnInputLayout.setErrors([error]);
      dpsForm.form.playerLevel.rsnInput.setFlags('invalid');
    };
    if (rsn.length > 0 && rsn.length < 13 && rsn.match(/^[a-zA-Z0-9 _]+$/) !== null) {
      dpsForm.form.playerLevel.rsnButton.setActive(true);
      dpsForm.form.playerLevel.rsnInputLayout.setErrors([]);
      dpsForm.form.playerLevel.rsnInput.setFlags({
        invalid: false
      });
      $.ajax({
        type: 'GET',
        url: '/cors/m=hiscore_oldschool/index_lite.ws?player=' + rsn,
        dataType: 'text',
        error: function error(jqXHR, textStatus, errorThrown) {
          console.warn('Hiscores API call for ' + rsn + 'failed: ' + textStatus + '. ' + errorThrown);
          if (errorThrown === 'Not Found') {
            throwError('Display name not found! Please confirm the spelling!');
          } else {
            throwError('Failed to lookup this display name! Please check your internet connection or try again later.');
          }
          dpsForm.form.playerLevel.rsnButton.setActive(false);
        },
        success: function success(data /*, textStatus, jqXHR*/) {
          try {
            data = data.split('\n');
            for (var i = 0; i < api.dumpOrder.length; i++) {
              if (api.dumpOrder[i]) {
                var lvl_s = data[i].split(',', 2)[1];
                var lvl_i = utils.parseIntDefault(lvl_s, 1);
                var skill = api.dumpOrder[i];
                if (api.dumpOrder[i] === 'Prayer' || api.dumpOrder[i] === 'Hitpoints') {
                  if (settings.loadout.playerLevel.visible[skill] === settings.loadout.playerLevel.current[skill]) {
                    dpsForm.form.playerLevel.visible[skill].setValue(lvl_s);
                    settings.loadout.playerLevel.visible[skill] = lvl_i;
                  }
                }
                dpsForm.form.playerLevel.current[skill].setValue(lvl_s);
                settings.loadout.playerLevel.current[skill] = lvl_i;
              }
            }
            settings.save('playerLevel');
          } catch (err) {
            console.error('Unable to parse hiscores api data! ' + err.message);
            throwError('Error processing hiscore data! Please report this issue on the Wiki\'s Discord at https://discord.gg/runescapewiki!');
          }
          dpsForm.form.playerLevel.rsnButton.setActive(false);
        }
      });
    } else {
      throwError('Please enter valid a display name!');
    }
  }
};
var calc = {
  roll: {
    Roller: function Roller(probability, roll, special) {
      var _this = {
        'Prob': probability,
        //Percentage chance of this roll occurring
        'Roll': roll,
        //Value of the roll (e.g. attack roll or max hit)
        'Spec': special //Special parameter:
        //  1 = 100% accurate
        //  2 = Dinh's
        //  4 = invalid parameters (e.g. Invalid Magic Dart)
        //  8 = Karil proc (2nd hitsplat with 50% damage)
        //  16 = Scythe hit 2
        //  32 = Scythe hit 3
        //  
      };
      return _this;
    },
    ApplyFunc: function ApplyFunc(arr, args, func) {
      console.log(args);
      for (var i = 0; i < arr.length; i++) {
        arr[i] = func(arr[i], args);
      }
      return arr;
    },
    ApplyMult: function ApplyMult(arr, mult) {
      if (mult === 1) {
        return arr;
      }
      for (var i = 0; i < arr.length; i++) {
        arr[i].Roll = Math.floor(arr[i].Roll * mult);
      }
      return arr;
    },
    ApplyAdd: function ApplyAdd(arr, add) {
      if (add === 0) {
        return arr;
      }
      for (var i = 0; i < arr.length; i++) {
        arr[i].Roll = Math.floor(arr[i].Roll + add);
      }
      return arr;
    },
    ApplySplitMult: function ApplySplitMult(arr, splitarr) {
      var newarr = [];
      splitarr.forEach(function (splitar) {
        arr.forEach(function (ar) {
          newarr.push(calc.roll.Roller(ar.Prob * splitar.Prob, Math.floor(ar.Roll * splitar.Roll), ar.Spec | splitar.Spec));
        });
      });
      return newarr;
    },
    ApplySplitAdd: function ApplySplitAdd(arr, splitarr) {
      var newarr = [];
      splitarr.forEach(function (splitar) {
        arr.forEach(function (ar) {
          newarr.push(calc.roll.Roller(ar.Prob * splitar.Prob, Math.floor(ar.Roll + splitar.Roll), ar.Spec | splitar.Spec));
        });
      });
      return newarr;
    }
  },
  check: {
    //Returns elite if wearing elite void, normal if wearing normal void, and 1 if not wearing void.
    MVoid: function MVoid(loadout, helmet, normal, elite) {
      if (loadout.slot.label.head === helmet && loadout.slot.label.gloves === 'Void knight gloves' && (loadout.slot.label.torso === 'Elite void top' || loadout.slot.label.torso === 'Void knight top') && (loadout.slot.label.legs === 'Elite void robe' || loadout.slot.label.legs === 'Void knight robe')) {
        if (loadout.slot.label.torso === 'Elite void top' && loadout.slot.label.legs === 'Elite void robe') {
          return elite;
        }
        return normal;
      }
      return 1;
    },
    //Returns the multiplier if wearing salve/black mask. imbuedonly = true if the item needs to be imbued.
    //mask/salve/salve_e contain the multipliers for each case.
    MMaskSalve: function MMaskSalve(loadout, imbuedonly, mask, salve, salve_e) {
      var helmets;
      var amulets;
      var amulets_e;
      if (imbuedonly) {
        helmets = ['Black mask (i)', 'Slayer helmet (i)', 'Black slayer helmet (i)', 'Green slayer helmet (i)', 'Red slayer helmet (i)', 'Purple slayer helmet (i)', 'Turquoise slayer helmet (i)', 'Hydra slayer helmet (i)', 'Twisted slayer helmet (i)'];
        amulets = ['Salve amulet(i)'];
        amulets_e = ['Salve amulet(ei)'];
      } else {
        helmets = ['Black mask', 'Black mask (i)', 'Slayer helmet', 'Slayer helmet (i)', 'Black slayer helmet', 'Black slayer helmet (i)', 'Green slayer helmet', 'Green slayer helmet (i)', 'Red slayer helmet', 'Red slayer helmet (i)', 'Purple slayer helmet', 'Purple slayer helmet (i)', 'Turquoise slayer helmet', 'Turquoise slayer helmet (i)', 'Hydra slayer helmet', 'Hydra slayer helmet (i)', 'Twisted slayer helmet', 'Twisted slayer helmet (i)'];
        amulets = ['Salve amulet', 'Salve amulet(i)'];
        amulets_e = ['Salve amulet (e)', 'Salve amulet(ei)'];
      }
      if (amulets_e.includes(loadout.slot.label.neck)) {
        return salve_e;
      }
      if (amulets.includes(loadout.slot.label.neck)) {
        return salve;
      }
      if (helmets.includes(loadout.slot.label.head) && loadout.monster.combatType.includes('slayer')) {
        return mask;
      }
      return 1;
    },
    MSmokeStaff: function MSmokeStaff(loadout) {
      if ((loadout.slot.label.weapon === 'Smoke battlestaff' || loadout.slot.label.weapon === 'Mystic smoke staff') && loadout.slot.label.spell in equipment.spell && equipment.spell[loadout.slot.label.spell].smoke) {
        return 1.1;
      }
      return 1;
    },
    MLightsword: function MLightsword(loadout, darklight, silverlight) {
      // TODO: Darklight, Silverlight, Silverlight (dyed)
      if (loadout.monster.combatType.includes('demon')) {
        if (loadout.slot.label.weapon === 'Arclight') {
          return 1.7;
        }
        if (loadout.slot.label.weapon === 'Darklight') {
          return darklight;
        }
        if (loadout.slot.label.weapon === 'Silverlight' || loadout.slot.label.weapon === 'Silverlight (dyed)') {
          return silverlight;
        }
        //Darklight
        //Silverlight
        //Silverlight (dyed)
      }
      return 1;
    },
    MLeafyBaxe: function MLeafyBaxe(loadout) {
      if (loadout.monster.combatType.includes('leafy')) {
        if (loadout.slot.label.weapon === 'Leaf-bladed battleaxe') {
          return 1.175;
        }
      }
      return 1;
    },
    MDhcb: function MDhcb(loadout) {
      if (loadout.monster.combatType.includes('dragon')) {
        if (loadout.slot.label.weapon === 'Dragon hunter crossbow') {
          return 1.3;
        }
      }
      return 1;
    },
    MDhl: function MDhl(loadout) {
      if (loadout.monster.combatType.includes('dragon')) {
        if (loadout.slot.label.weapon === 'Dragon hunter lance') {
          return 1.2;
        }
      }
      return 1;
    },
    MHolyWater: function MHolyWater(loadout) {
      if (loadout.monster.combatType.includes('demon')) {
        if (loadout.slot.label.weapon === 'Holy water') {
          return 1.6;
        }
      }
      return 1;
    },
    MWildyWeap: function MWildyWeap(loadout, weapon, bonus) {
      if (loadout.slot.wilderness && (loadout.slot.label.weapon === weapon || loadout.slot.label.weapon === weapon + ' (u)')) {
        return bonus;
      }
      return 1;
    },
    MTbow: function MTbow(loadout, modifier) {
      if (loadout.slot.label.weapon === 'Twisted bow') {
        var calc;
        if (modifier) {
          //maxhit modifier
          calc = 250 + 100 * loadout.monster.combatType.includes('raids');
          calc = Math.min(calc, Math.max(loadout.monster.Magic, loadout.monster.am));
          calc = Math.min(250, 250 + Math.trunc((10 * 3 * calc / 10 - 14) / 100) - Math.trunc(Math.pow(3 * calc / 10 - 140, 2) / 100)) / 100;
          return calc;
        } else {
          //accuracy modifier
          calc = 250 + 100 * loadout.monster.combatType.includes('raids');
          calc = Math.min(calc, Math.max(loadout.monster.Magic, loadout.monster.am));
          calc = Math.min(140, 140 + Math.trunc((10 * 3 * calc / 10 - 10) / 100) - Math.trunc(Math.pow(3 * calc / 10 - 100, 2) / 100)) / 100;
          return calc;
        }
      }
      return 1;
    },
    MObbyArmour: function MObbyArmour(loadout) {
      //TODO does staff count as melee?
      if (loadout.slot.label.head === 'Obsidian helmet' && loadout.slot.label.torso === 'Obsidian platebody' && loadout.slot.label.legs === 'Obsidian platelegs' && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')) {
        return 1.1;
      }
      return 1;
    },
    MObbyAmmy: function MObbyAmmy(loadout) {
      //TODO does staff count as melee?
      if ((loadout.slot.label.neck === 'Berserker necklace' || loadout.slot.label.neck === 'Berserker necklace (or)') && (loadout.slot.label.weapon === 'Tzhaar-ket-om' || loadout.slot.label.weapon === 'Tzhaar-ket-om (t)' || loadout.slot.label.weapon === 'Tzhaar-ket-em' || loadout.slot.label.weapon === 'Toktz-xil-ak' || loadout.slot.label.weapon === 'Toktz-mej-tal' || loadout.slot.label.weapon === 'Toktz-xil-ek')) {
        return 1.2;
      }
      return 1;
    },
    MCrystalArmour: function MCrystalArmour(loadout, modifier) {
      if (loadout.slot.label.weapon === 'Crystal bow') {
        var bonus = 0;
        if (loadout.slot.label.head === 'Crystal helm') {
          bonus += 1;
        }
        if (loadout.slot.label.torso === 'Crystal body') {
          bonus += 1;
        }
        if (loadout.slot.label.legs === 'Crystal legs') {
          bonus += 1;
        }
        if (bonus === 3) {
          bonus = 5;
        }
        if (modifier) {
          //maxhit modifier
          bonus = 1 + bonus * 0.03;
        } else {
          //accuracy modifier
          bonus = 1 + bonus * 0.06;
        }
        return bonus;
      }
      return 1;
    },
    MChinchompa: function MChinchompa(loadout, stance2) {
      if (loadout.slot.distance && (loadout.slot.label.weapon === 'Chinchompa' || loadout.slot.label.weapon === 'Red chinchompa' || loadout.slot.label.weapon === 'Black chinchompa')) {
        if (stance2 === 'Accurate') {
          if (loadout.slot.distance <= 3) {
            return 0;
          } else if (loadout.slot.distance <= 6) {
            return 0.25;
          } else {
            return 0.5;
          }
        } else if (stance2 === 'Rapid') {
          if (loadout.slot.distance <= 3) {
            return 0.25;
          } else if (loadout.slot.distance <= 6) {
            return 0;
          } else {
            return 0.25;
          }
        } else if (stance2 === 'Longrange') {
          if (loadout.slot.distance <= 3) {
            return 0.5;
          } else if (loadout.slot.distance <= 6) {
            return 0.25;
          } else {
            return 0;
          }
        }
      }
      return 0;
    },
    HasGadder: function HasGadder(loadout) {
      return loadout.slot.label.weapon === 'Gadderhammer';
    },
    HasBrimstone: function HasBrimstone(loadout) {
      return loadout.slot.label.ring === 'Brimstone ring';
    },
    HasVestaLongsword: function HasVestaLongsword(loadout) {
      return loadout.slot.label.ring === 'Vesta\'s longsword';
    },
    HasKeris: function HasKeris(loadout) {
      return loadout.slot.label.weapon === 'Keris' || loadout.slot.label.weapon === 'Keris(p)' || loadout.slot.label.weapon === 'Keris(p+)' || loadout.slot.label.weapon === 'Keris(p++)';
    },
    HasVerac: function HasVerac(loadout) {
      if (loadout.slot.label.head === 'Verac\'s helm' && loadout.slot.label.torso === 'Verac\'s brassard' && loadout.slot.label.legs === 'Verac\'s plateskirt' && loadout.slot.label.weapon === 'Verac\'s flail') {
        if (loadout.slot.label.neck === 'Amulet of the damned') {
          return 2;
        }
        return 1;
      }
      return 0;
    },
    HasAhrim: function HasAhrim(loadout) {
      if (loadout.slot.label.head === 'Ahrim\'s hood' && loadout.slot.label.torso === 'Ahrim\'s robetop' && loadout.slot.label.legs === 'Ahrim\'s robeskirt' && loadout.slot.label.weapon === 'Ahrim\'s staff') {
        if (loadout.slot.label.neck === 'Amulet of the damned') {
          return 2;
        }
        return 1;
      }
      return 0;
    },
    HasDharok: function HasDharok(loadout) {
      if (loadout.slot.label.head === 'Dharok\'s helm' && loadout.slot.label.torso === 'Dharok\'s platebody' && loadout.slot.label.legs === 'Dharok\'s platelegs' && loadout.slot.label.weapon === 'Dharok\'s greataxe') {
        if (loadout.slot.label.neck === 'Amulet of the damned') {
          return 2;
        }
        return 1;
      }
      return 0;
    },
    HasKaril: function HasKaril(loadout) {
      if (loadout.slot.label.head === 'Karil\'s coif' && loadout.slot.label.torso === 'Karil\'s leathertop' && loadout.slot.label.legs === 'Karil\'s leatherskirt' && loadout.slot.label.weapon === 'Karil\'s crossbow') {
        if (loadout.slot.label.neck === 'Amulet of the damned') {
          return 2;
        }
        return 1;
      }
      return 0;
    },
    MDharok: function MDharok(loadout) {
      //TODO - add hitpoints
      if (calc.check.HasDharok(loadout)) {
        if (true /*TODO*/) {
          return 1;
        }
        return 1 + Math.max(0, loadout.playerLevel.current.Hitpoints - loadout.playerLevel.visible.Hitpoints) * loadout.playerLevel.current.Hitpoints / 10000;
      }
      return 1;
    },
    MGuardians: function MGuardians(loadout) {
      //TODO: Mining level
      //TODO: What happens if not using pickaxe?
      if (loadout.monster.name === 'Guardian (Chambers of Xeric, Challenge Mode)' || loadout.monster.name === 'Guardian (Chambers of Xeric)') {
        var weapon = Math.min(61, loadout.slot.slotItem.weapon.mL || 0);
        var level = Math.min(100, loadout.playerLevel.current.Mining || 1); //TODO
        return (50 + level + weapon) / 150;
      }
      return 1;
    },
    //TODO Check "ontask"
    MaxMagicDart: function MaxMagicDart(loadout) {
      var staves = ['Slayer\'s staff', 'Slayer\'s staff (e)', 'Toxic staff of the dead', 'Toxic staff (uncharged)', 'Staff of the dead', 'Staff of balance', 'Staff of light'];
      if (staves.includes(loadout.slot.label.weapon)) {
        if (loadout.slot.label.weapon === 'Slayer\'s staff (e)' && loadout.monster.combatType.includes('slayer')) {
          return Math.floor(13 + loadout.playerLevel.visible.Magic / 6);
        }
        return Math.floor(10 + loadout.playerLevel.visible.Magic / 10);
      }
      return 0;
    },
    MaxTrident: function MaxTrident(loadout) {
      if (loadout.slot.label.weapon === 'Trident of the seas' || loadout.slot.label.weapon === 'Trident of the seas (e)') {
        return Math.max(1, Math.floor(loadout.playerLevel.visible.Magic / 3 - 5));
      } else if (loadout.slot.label.weapon === 'Trident of the swamp' || loadout.slot.label.weapon === 'Trident of the swamp (e)' || loadout.slot.label.weapon === 'Uncharged toxic trident' || loadout.slot.label.weapon === 'Uncharged toxic trident (e)') {
        return Math.max(4, Math.floor(loadout.playerLevel.visible.Magic / 3 - 2));
      } else if (loadout.slot.label.weapon === 'Sanguinesti staff' || loadout.slot.label.weapon === 'Sanguinesti staff (beta)') {
        return Math.max(5, Math.floor(loadout.playerLevel.visible.Magic / 3 - 1));
      } else if (loadout.slot.label.weapon === 'Starter staff') {
        return 8;
      } else {
        return false;
      }
    },
    MaxSalamander: function MaxSalamander(loadout) {
      if (loadout.slot.label.weapon === 'Black salamander') {
        return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.24375);
      }
      if (loadout.slot.label.weapon === 'Red salamander') {
        return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.2203125);
      }
      if (loadout.slot.label.weapon === 'Orange salamander') {
        return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.1921875);
      }
      if (loadout.slot.label.weapon === 'Swamp lizard') {
        return Math.floor(0.5 + loadout.playerLevel.visible.Magic * 0.1875);
      }
      return false;
    },
    MChaosGauntlet: function MChaosGauntlet(loadout) {
      if (loadout.slot.label.gloves === 'Chaos gauntlets' && loadout.slot.slotItem.spell.bolt) {
        return 3;
      }
      return 0;
    },
    MTomeOfFire: function MTomeOfFire(loadout) {
      if (loadout.slot.label.shield === 'Tome of fire' && loadout.slot.slotItem.spell.fire) {
        return 1.5;
      }
      return 1;
    },
    MCastleWars: function MCastleWars(loadout) {
      if (loadout.monster.combatType.includes('castle wars flagholder')) {
        return 1.2;
      }
      return 1;
    }
  },
  naiveDPS: function naiveDPS(accarr, rollmin, rollmax, typeless, ticks) {
    //TODO: Scythe
    var dps = 0;
    for (var i = 0; i < accarr.length; i++) {
      var hit;
      if (accarr[i].Spec & 8) {
        hit = (rollmax[i].Roll + rollmin[i].Roll) / 2 + Math.floor((rollmax[i].Roll + rollmin[i].Roll) / 2) / 2;
      } else {
        hit = (rollmax[i].Roll + rollmin[i].Roll) / 2;
      }
      dps += accarr[i].Prob * accarr[i].Roll * hit + typeless;
    }
    return dps / ticks / 0.6;
  },
  accuracy: function accuracy(aroll, droll) {
    if (aroll > droll) {
      return 1 - (droll + 2) / (2 * aroll + 2);
    }
    return aroll / (2 * droll + 2);
  },
  accuracies: function accuracies(arollers, drollers) {
    var newarr = [];
    arollers.forEach(function (aroller) {
      var roll;
      if (aroller.Spec & 1) {
        //guaranteed hit
        roll = 1;
      } else if (aroller.Spec & 6) {
        //Dinh's (2) or invalid (4)
        roll = 0;
      } else {
        roll = 0;
        drollers.forEach(function (droller) {
          roll += droller.Prob * calc.accuracy(aroller.Roll, droller.Roll);
        });
      }
      newarr.push(calc.roll.Roller(aroller.Prob, roll, aroller.Spec));
    });
    return newarr;
  },
  monsterDefence: function monsterDefence(loadout, style, spec) {
    var roll;
    if (style === 'dmagic') {
      roll = [calc.roll.Roller(1, Math.floor((loadout.monster.visible.Magic + 9) * (64 + loadout.monster.dmagic)), 0)];
      if (calc.check.HasBrimstone(loadout)) {
        roll = calc.roll.ApplySplitMult(roll, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 0.9, 0)]);
      }
    } else {
      roll = [calc.roll.Roller(1, Math.floor((loadout.monster.visible.Defence + 9) * (64 + loadout.monster[style])), 0)];
      if (spec && calc.check.HasVestaLongsword(loadout)) {
        roll = calc.roll.ApplyMult(roll, 0.25);
      }
    }
    return roll;
  },
  //Input: Loadout
  //Output: min roll, max roll, guaranteed typeless damage, attack tick duration
  playerMaxHit: function playerMaxHit(loadout) {
    //Todo: Ba level to min + max hit
    //Todo: Holy water
    //todo: Bolts
    //Todo: Clarify which dragons are fiery

    var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
    if (!(res && ['Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Block'].includes(res[1]) && ['Accurate', 'Aggressive', 'Defensive', 'Shared', 'Rapid', 'Longrange', 'Spell', 'Defensive Spell'].includes(res[2]))) {
      if (loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout) {
        res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
      } else {
        res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
      }
    }
    var stance1 = res[1];
    var stance2 = res[2];
    var rollmax;
    var rollmin = [calc.roll.Roller(1, 0, 0)];
    var typeless = 0; //dmg even if miss
    var attackticks = loadout.slot.slotItem.weapon.aS;
    var args;
    var tomeoffire = 1;
    if (stance1 === 'Block') {
      rollmax = [calc.roll.Roller(1, 0, 2)];
    } else if (stance1 === 'Ranged') {
      rollmax = [calc.roll.Roller(1, loadout.playerLevel.visible.Ranged, 0)];
      loadout.slot.prayers.forEach(function (prayer) {
        rollmax = calc.roll.ApplyMult(rollmax, equipment.prayers[prayer].RangedStrengthBonus || 1);
      });
      rollmax = calc.roll.ApplyAdd(rollmax, 8);
      if (stance2 === 'Accurate') {
        rollmax = calc.roll.ApplyAdd(rollmax, 3);
      } else if (stance2 === 'Rapid') {
        attackticks -= 1;
      }
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MVoid(loadout, 'Void ranger helm', 1.1, 1.125));
      rollmax[0].Roll = Math.floor(0.5 + rollmax[0].Roll * (64 + loadout.slot.equipmentBonus.br) / 640); //cheating out of laziness
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDhcb(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MHolyWater(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MWildyWeap(loadout, 'Craw\'s bow', 1.5));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MTbow(loadout, true));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MCrystalArmour(loadout, true));
      if (calc.check.HasKaril(loadout)) {
        rollmax = calc.roll.ApplySplitAdd(rollmax, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 8)]);
        rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 0)]);
      }
    } else if (stance1 === 'Magic') {
      if (stance2 === 'Spell' || stance2 === 'Defensive Spell') {
        attackticks = loadout.slot.slotItem.spell.aS;
        if (loadout.slot.label.spell === 'Magic Dart') {
          args = calc.check.MaxMagicDart(loadout);
          rollmax = [calc.roll.Roller(1, args, args ? 0 : 4)];
        } else {
          rollmax = [calc.roll.Roller(1, loadout.slot.slotItem.spell.mh, loadout.slot.slotItem.spell.mh ? 0 : 4)];
          calc.roll.ApplyAdd(rollmax, calc.check.MChaosGauntlet(loadout));
          tomeoffire = calc.check.MTomeOfFire(loadout);
        }
      } else {
        args = calc.check.MaxTrident(loadout);
        if (args === false) {
          args = calc.check.MaxSalamander(loadout);
        }
        if (args === false) {
          rollmax = [calc.roll.Roller(1, 0, 4)];
        } else {
          rollmax = [calc.roll.Roller(1, args, 0)];
        }
      }
      var mbns = loadout.slot.equipmentBonus.bm;
      var mask = 1;
      mbns += calc.check.MSmokeStaff(loadout);
      args = calc.check.MMaskSalve(loadout, true, 0, 1.15, 1.2);
      if (args === 0) {
        mask = 1.15;
      } else {
        args = args - 1;
      }
      mbns += args;
      mbns += calc.check.MWildyWeap(loadout, 'Thammaron\'s sceptre', 1.25) - 1;
      mbns += calc.check.MVoid(loadout, 'Void mage helm', 1, 1.025) - 1;
      rollmax = calc.roll.ApplyMult(rollmax, mbns);
      rollmax = calc.roll.ApplyMult(rollmax, tomeoffire);
      rollmax = calc.roll.ApplyMult(rollmax, mask);
      if (calc.check.HasAhrim(loadout) === 2) {
        rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1.3, 0)]);
        rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 0)]);
      }
    } else {
      rollmax = [calc.roll.Roller(1, loadout.playerLevel.visible.Strength, 0)];
      loadout.slot.prayers.forEach(function (prayer) {
        rollmax = calc.roll.ApplyMult(rollmax, equipment.prayers[prayer].StrengthBonus || 1);
      });
      rollmax = calc.roll.ApplyAdd(rollmax, 8);
      if (stance2 === 'Aggressive') {
        rollmax = calc.roll.ApplyAdd(rollmax, 3);
      } else if (stance2 === 'Shared') {
        rollmax = calc.roll.ApplyAdd(rollmax, 1);
      }
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MVoid(loadout, 'Void melee helm', 1.1, 1.1));
      rollmax[0].Roll = Math.floor(0.5 + rollmax[0].Roll * (64 + loadout.slot.equipmentBonus.bs) / 640); //cheating out of laziness
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MMaskSalve(loadout, true, 7 / 6, 7 / 6, 1.2));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MLightsword(loadout, 1.6, 1.5 /*???TODO???*/));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MLeafyBaxe(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDhl(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MWildyWeap(loadout, 'Viggora\'s chainmace', 1.5));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MObbyArmour(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MObbyAmmy(loadout)); //which order with obsidian?
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MDharok(loadout));
      rollmax = calc.roll.ApplyMult(rollmax, calc.check.MGuardians(loadout));
      if (calc.check.HasGadder(loadout)) {
        rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(0.95, 1.25, 0), calc.roll.Roller(0.05, 2, 0)]);
        rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.95, 0, 0), calc.roll.Roller(0.05, 0, 0)]);
      }
      if (calc.check.HasKeris(loadout)) {
        rollmax = calc.roll.ApplySplitMult(rollmax, [calc.roll.Roller(50 / 51, 4 / 3, 0), calc.roll.Roller(1 / 51, 3, 0)]);
        rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(50 / 51, 0, 0), calc.roll.Roller(1 / 51, 0, 0)]);
      }
      if (calc.check.HasVerac(loadout)) {
        rollmax = calc.roll.ApplySplitAdd(rollmax, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 1, 1)]);
        rollmin = calc.roll.ApplySplitAdd(rollmin, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 1, 1)]);
      }
    }
    rollmax = calc.roll.ApplyMult(rollmax, calc.check.MCastleWars(loadout));
    //TODO: If ba, typeless += attack level

    return [rollmin, rollmax, typeless, attackticks];
  },
  playerAttack: function playerAttack(loadout) {
    //Todo:
    //  Raids parameter
    //  Wilderness parameter
    //  distance parameter
    //  Check if Twisted bow truncates 3*Magic/10
    //  Slayer ontask parameter
    //  Rune claw attack delay special attack
    //  Todo special attacks

    //Todo: bolt specials
    var res = loadout.slot.label.combatStyle.match(/^(.*) - (.*)$/);
    if (!(res && ['Stab', 'Slash', 'Crush', 'Ranged', 'Magic', 'Block'].includes(res[1]) && ['Accurate', 'Aggressive', 'Defensive', 'Shared', 'Rapid', 'Longrange', 'Spell', 'Defensive Spell'].includes(res[2]))) {
      if (loadout.slot.combatStyleLoadout in equipment.combatStyleLoadout) {
        res = equipment.combatStyleLoadout[loadout.slot.combatStyleLoadout][0].match(/^(.*) - (.*)$/);
      } else {
        res = 'Stab - Accurate'.match(/^(.*) - (.*)$/);
      }
    }
    var stance1 = res[1];
    var stance2 = res[2];
    var rollacc;
    var args;
    var defencestyle;
    if (stance1 === 'Block') {
      return [[calc.roll.Roller(1, 0, 2)], ''];
    } else if (stance1 === 'Ranged') {
      defencestyle = 'drange';
      rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Ranged, 0)];
      loadout.slot.prayers.forEach(function (prayer) {
        rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].RangedAttackBonus || 1);
      });
      rollacc = calc.roll.ApplyAdd(rollacc, 8);
      if (stance2 === 'Accurate') {
        rollacc = calc.roll.ApplyAdd(rollacc, 3);
      }
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void ranger helm', 1.1, 1.1));
      rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.ar);
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MDhcb(loadout));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Craw\'s bow', 1.5));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MTbow(loadout, false));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MCrystalArmour(loadout, false));
      args = calc.check.MChinchompa(loadout, stance2);
      rollacc = calc.roll.ApplyFunc(rollacc, args, function (rollaccer_i, chin) {
        rollaccer_i.Roll = rollaccer_i.Roll - Math.floor(rollaccer_i.Roll * chin);
        return rollaccer_i;
      });
      if (calc.check.HasKaril(loadout)) {
        rollacc = calc.roll.ApplySplitAdd(rollacc, [calc.roll.Roller(0.75, 0, 0), calc.roll.Roller(0.25, 0, 8)]);
      }
    } else if (stance1 === 'Magic') {
      defencestyle = 'dmagic';
      rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Magic, 0)];
      loadout.slot.prayers.forEach(function (prayer) {
        rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].MagicBonus || 1);
      });
      rollacc = calc.roll.ApplyAdd(rollacc, 8);
      if (stance2 === 'Accurate') {
        rollacc = calc.roll.ApplyAdd(rollacc, 3);
      }
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void mage helm', 1.45, 1.45));
      rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.am);
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MSmokeStaff(loadout));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 1.15, 1.15, 1.2));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Thammaron\'s sceptre', 2));
      if (calc.check.HasAhrim(loadout) === 2) {
        //split to keep in line with max hit above
        rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1, 0)]);
      }
    } else {
      rollacc = [calc.roll.Roller(1, loadout.playerLevel.visible.Attack, 0)];
      loadout.slot.prayers.forEach(function (prayer) {
        rollacc = calc.roll.ApplyMult(rollacc, equipment.prayers[prayer].AttackBonus || 1);
      });
      rollacc = calc.roll.ApplyAdd(rollacc, 8);
      if (stance2 === 'Accurate') {
        rollacc = calc.roll.ApplyAdd(rollacc, 3);
      } else if (stance2 === 'Shared') {
        rollacc = calc.roll.ApplyAdd(rollacc, 1);
      }
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MVoid(loadout, 'Void melee helm', 1.1, 1.1));
      if (stance1 === 'Stab') {
        defencestyle = 'dstab';
        rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.at);
      } else if (stance1 === 'Slash') {
        defencestyle = 'dslash';
        rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.al);
      } else {
        defencestyle = 'dcrush';
        rollacc = calc.roll.ApplyMult(rollacc, 64 + loadout.slot.equipmentBonus.ac);
      }
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MMaskSalve(loadout, true, 7 / 6, 7 / 6, 1.2));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MLightsword(loadout, 1, 1));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MDhl(loadout));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MWildyWeap(loadout, 'Viggora\'s chainmace', 1.5));
      rollacc = calc.roll.ApplyMult(rollacc, calc.check.MObbyArmour(loadout));
      if (calc.check.HasGadder(loadout)) {
        rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.95, 1, 0), calc.roll.Roller(0.05, 1, 0)]);
      }
      if (calc.check.HasKeris(loadout)) {
        rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(50 / 51, 1, 0), calc.roll.Roller(1 / 51, 1, 0)]);
      }
      if (calc.check.HasVerac(loadout)) {
        rollacc = calc.roll.ApplySplitMult(rollacc, [calc.roll.Roller(0.75, 1, 0), calc.roll.Roller(0.25, 1, 1)]);
      }
    }
    return [rollacc, defencestyle];
  }
};

// Users settings
var settings = {
  storedLoadouts: [],
  loadout: {},
  /*
  Object playerLevel
      string rsn
        Object current
          number Attack
          number Strength
          number Defence
          number Ranged
          number Magic
      
      Object visible
          number Attack
          number Strength
          number Defence
          number Ranged
          number Magic
        boolean forceLookup (force a hiscore lookup on page load, used when rsn is defined in query string)
  Object slot
      Object label
          string weapon
          string combatStyle
          string blowpipe
          string spell
          string head
          string cape
          string neck
          string ammo
          string torso
          string shield
          string legs
          string gloves
          string boots
          string ring
      
      array prayers
      array potions
      
      boolean visibleBlowpipe
      boolean visibleSpell
      boolean disabledShield
      string combatStyleLoadout (combat style options name e.g. 'unarmed')
      
      boolean wilderness TODO
      number distance TODO
        Object slotItem (equipment[slot].item for each slot)
          Object weapon
          Object combatStyle
          Object blowpipe
          Object spell
          Object head
          Object cape
          Object neck
          Object ammo
          Object torso
          Object shield
          Object legs
          Object gloves
          Object boots
          Object ring
          (there is no prayers or potions object)
        Object equipmentBonus
          number at
          number al
          number ac
          number am
          number ar
          number dt
          number dl
          number dc
          number dm
          number dr
          number bs
          number br
          number bm
            boolean manualLevel
  Object monster
      Object visible
          number Hitpoints
          number Attack
          number Strength
          number Defence
          number Ranged
          number Magic
      Object current
          number Hitpoints
          number Attack
          number Strength
          number Defence
          number Ranged
          number Magic
      string name
      array combatType
      string attackStyle
      number attackSpeed
      number maxHit
      number astab
      number aslash
      number acrush
      number amagic
      number arange
      number dstab
      number dslash
      number dcrush
      number dmagic
      number drange
      number abns
      number str
      number rstr
      number mdmg
      number xpBonus
      number immunePoison
      number immuneVenom
        
      boolean manualLevel
      */

  // Load from localStorage
  load: function load(type) {
    if (!rswiki.hasLocalStorage()) {
      console.warn('Browser does not support localStorage');
      return;
    }
    switch (type) {
      case 'storedLoadouts':
        try {
          settings.storedLoadouts = JSON.parse(localStorage.getItem('rsw-dps-storedloadouts'));
        } catch (err) {
          settings.storedLoadouts = [];
          console.warn('Error loading storedLoadouts from localStorage');
        }
        if (settings.storedLoadouts === null) {
          settings.storedLoadouts = [];
        }
        console.log('Localstorage loaded - storedLoadouts');
        console.log(settings.storedLoadouts);
        return settings.storedLoadouts;
      case 'playerLevel':
        try {
          settings.loadout.playerLevel = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
        } catch (err) {
          settings.loadout.playerLevel = {};
          console.warn('Error loading loadout from localStorage');
        }
        if (settings.loadout.playerLevel === null) {
          settings.loadout.playerLevel = {};
        }
        console.log('Localstorage loaded - loadout.playerLevel');
        console.log(settings.loadout.playerLevel);
        return settings.loadout.playerLevel;
      case 'slot':
        try {
          settings.loadout.slot = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
        } catch (err) {
          settings.loadout.slot = {};
          console.warn('Error loading loadout from localStorage');
        }
        if (settings.loadout.slot === null) {
          settings.loadout.slot = {};
        }
        console.log('Localstorage loaded - loadout.slot');
        console.log(settings.loadout.slot);
        return settings.loadout.slot;
      case 'monster':
        try {
          settings.loadout.monster = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
        } catch (err) {
          settings.loadout.monster = {};
          console.warn('Error loading loadout from localStorage');
        }
        if (settings.loadout.monster === null) {
          settings.loadout.monster = {};
        }
        console.log('Localstorage loaded - loadout.monster');
        console.log(settings.loadout.monster);
        return settings.loadout.monster;
      case 'loadout':
        settings.load('playerLevel');
        settings.load('slot');
        settings.load('monster');
        return true;
      case 'all':
        settings.load('storedLoadouts');
        settings.load('loadout');
        return true;
      default:
        console.error('Invalid localStorage type: ' + type);
        return null;
    }
  },
  //Load from query string on page load
  loadQueryString: function loadQueryString() {
    var urlParams = {};
    var match;
    var pl = /\+/g;
    var search = /([^&=]+)=?([^&]*)/g;
    var decode = function decode(s) {
      return decodeURIComponent(s.replace(pl, ' '));
    };
    var query = window.location.search.substring(1);
    while (match = search.exec(query)) {
      // eslint-disable-line no-cond-assign
      urlParams[decode(match[1])] = decode(match[2]);
    }
    var skill_iterable = ['Attack', 'Strength', 'Defence', 'Ranged', 'Magic'];
    skill_iterable.forEach(function (skill) {
      if (skill in urlParams) {
        settings.loadout.playerLevel.current[skill] = utils.parseIntDefault(urlParams[skill], 1);
        console.log('Loaded query string ' + skill + ' - ' + settings.loadout.playerLevel.current[skill]);
      }
    });
    if ('rsn' in urlParams) {
      settings.loadout.playerLevel.rsn = urlParams.rsn;
      settings.loadout.playerLevel.forceLookup = true;
      console.log('Loaded query string rsn - ' + settings.loadout.playerLevel.rsn);
    }
    var slot_iterable = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', 'combatStyle', 'shield', 'blowpipe', 'spell']; //shield/blowpipe/spell/combatStyle after weapon
    slot_iterable.forEach(function (slot) {
      if (slot in urlParams) {
        if (slot == 'blowpipe' && !settings.loadout.slot.visibleBlowpipe) {
          return;
        } else if (slot == 'shield' && settings.loadout.slot.disabledShield) {
          return;
        } else if (slot == 'spell' && !settings.loadout.slot.visibleSpell) {
          return;
        }
        var slot_id = urlParams[slot];
        if (utils.isPositiveInt(slot_id)) {
          var slot_name = equipment.lookupItemID(parseInt(slot_id), slot);
          if (slot_name) {
            slot_id = slot_name;
          }
        }
        slot_id = utils.itemCase(slot_id);
        console.log('Loaded query string ' + slot + ' - ' + slot_id);
        settings.loadout.slot.label[slot] = slot_id;
        settings.loadout.slot.slotItem[slot] = equipment.getItem(slot_id, slot)[0];
        if (slot == 'weapon') {
          settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
          if (settings.loadout.slot.disabledShield == true) {
            settings.loadout.slot.label.shield = '';
          }
          settings.loadout.slot.visibleBlowpipe = slot_id == 'Toxic blowpipe';
          if (settings.loadout.slot.visibleBlowpipe == false) {
            settings.loadout.slot.label.blowpipe = '';
          }
          settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
        } else if (slot == 'combatStyle') {
          settings.loadout.slot.visibleSpell = slot_id == 'Magic - spell' || slot_id == 'Magic - defensive spell';
          if (settings.loadout.slot.visibleSpell == false) {
            settings.loadout.slot.label.spell = '';
          }
        }
      }
    });
    settings.save('all');
  },
  // Save to localStorage
  save: function save(type) {
    if (!rswiki.hasLocalStorage()) {
      console.warn('Browser does not support localStorage');
      return;
    }
    var ret;
    var stringified;
    var save;
    switch (type) {
      case 'storedLoadouts':
        stringified = JSON.stringify(settings.storedLoadouts);
        localStorage.setItem('rsw-dps-storedLoadouts', stringified);
        // Check for save
        try {
          ret = JSON.parse(localStorage.getItem('rsw-dps-storedLoadouts'));
        } catch (err) {
          console.warn('Error saving storedLoadouts to localStorage');
          return false;
        }
        console.log('Localstorage saved - storedLoadouts: ' + ret);
        return true;
      case 'playerLevel':
        stringified = JSON.stringify(settings.loadout.playerLevel);
        localStorage.setItem('rsw-dps-loadout-playerLevel', stringified);
        // Check for save
        try {
          ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-playerLevel'));
        } catch (err) {
          console.warn('Error saving loadout.playerLevel to localStorage');
          return false;
        }
        console.log('Localstorage saved - loadout.playerLevel');
        return true;
      case 'slot':
        stringified = JSON.stringify(settings.loadout.slot);
        localStorage.setItem('rsw-dps-loadout-slot', stringified);
        // Check for save
        try {
          ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-slot'));
        } catch (err) {
          console.warn('Error saving loadout.slot to localStorage');
          return false;
        }
        console.log('Localstorage saved - loadout.slot');
        return true;
      case 'monster':
        stringified = JSON.stringify(settings.loadout.monster);
        localStorage.setItem('rsw-dps-loadout-monster', stringified);
        // Check for save
        try {
          ret = JSON.parse(localStorage.getItem('rsw-dps-loadout-monster'));
        } catch (err) {
          console.warn('Error saving loadout.monster to localStorage');
          return false;
        }
        console.log('Localstorage saved - loadout.monster');
        return true;
      case 'loadout':
        save = settings.save('playerLevel');
        save = settings.save('slot') && save;
        save = settings.save('monster') && save;
        return save;
      case 'all':
        save = settings.save('storedLoadouts');
        save = settings.save('loadout') && save;
        return save;
      default:
        console.error('Invalid localStorage type ' + type);
        return null;
    }
  }
};
var dpsForm = {
  // Placeholder for form elements
  form: {},
  // Create the form
  create: function create() {
    var arr;
    /* format:
        Initialize setting variables
        Create widget
        Create event hooks*/

    // // // // // // //
    //  Display Name  //
    // // // // // // //
    if (settings.loadout.playerLevel == null) {
      settings.loadout.playerLevel = {};
    }
    if (settings.loadout.playerLevel.rsn == null) {
      settings.loadout.playerLevel.rsn = '';
    }
    dpsForm.form.playerLevel = {};
    dpsForm.form.playerLevel.rsnInput = new OO.ui.TextInputWidget({
      autocomplete: true,
      placeholder: 'Display name',
      id: 'dps-player-rsnInput',
      value: settings.loadout.playerLevel.rsn,
      icon: 'search'
    });
    dpsForm.form.playerLevel.rsnButton = new OO.ui.ButtonWidget({
      label: 'Hiscore Lookup',
      id: 'dps-player-rsnButton'
    });
    dpsForm.form.playerLevel.rsnInput.on('enter', dpsForm.rsnInputSubmit);
    dpsForm.form.playerLevel.rsnButton.on('click', dpsForm.rsnInputSubmit);
    dpsForm.form.playerLevel.rsnInputLayout = new OO.ui.ActionFieldLayout(dpsForm.form.playerLevel.rsnInput, dpsForm.form.playerLevel.rsnButton, {
      id: 'dps-player-rsnInput-layout'
    });

    // // // // // // //
    // Player Skills  //
    // // // // // // //
    if (settings.loadout.playerLevel.current == null) {
      settings.loadout.playerLevel.current = {};
    }
    if (settings.loadout.playerLevel.visible == null) {
      settings.loadout.playerLevel.visible = {};
    }
    dpsForm.form.playerLevel.current = {};
    dpsForm.form.playerLevel.visible = {};
    dpsForm.form.playerLevel.skillIcon = {};
    dpsForm.form.playerLevel.slash = {};
    dpsForm.form.playerLevel.layout = {};
    var addSkill = function addSkill(skill, icon, width, height, visible) {
      if (settings.loadout.playerLevel.current[skill] == null) {
        settings.loadout.playerLevel.current[skill] = 1;
      }
      dpsForm.form.playerLevel.current[skill] = new OO.ui.NumberInputWidget({
        min: 0,
        max: 9999,
        step: 1,
        showButtons: false,
        value: settings.loadout.playerLevel.current[skill],
        classes: ['dps-player-skill-current', 'dps-skill-input']
      });
      dpsForm.form.playerLevel.current[skill].on('change', dpsForm.playerLevelChange, [skill, visible]);
      dpsForm.form.playerLevel.skillIcon[skill] = new OO.ui.LabelWidget({
        label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
        classes: ['dps-player-skill-icon', 'dps-skill-icon']
      });
      dpsForm.form.playerLevel.slash[skill] = new OO.ui.LabelWidget({
        label: '/',
        classes: ['dps-player-skill-slash', 'dps-skill-slash']
      });
      if (settings.loadout.playerLevel.visible[skill] == null) {
        settings.loadout.playerLevel.visible[skill] = 1;
      }
      dpsForm.form.playerLevel.visible[skill] = new OO.ui.NumberInputWidget({
        min: 0,
        max: 9999,
        step: 1,
        showButtons: false,
        disabled: visible === 1,
        value: settings.loadout.playerLevel.visible[skill],
        classes: ['dps-player-skill-visible', 'dps-player-skill-visible-' + skill, 'dps-skill-input']
      });
      if (visible === 2) {
        dpsForm.form.playerLevel.visible[skill].on('change', dpsForm.playerLevelChange, [skill, 3]);
      }
      dpsForm.form.playerLevel.layout[skill] = new OO.ui.HorizontalLayout({
        items: [dpsForm.form.playerLevel.skillIcon[skill], dpsForm.form.playerLevel.visible[skill], dpsForm.form.playerLevel.slash[skill], dpsForm.form.playerLevel.current[skill]],
        classes: ['dps-player-skill-layout', 'dps-skill-layout']
      });
    };
    if (settings.loadout.playerLevel.forceLookup) {
      settings.loadout.playerLevel.forceLookup = false;
      api.lookup(settings.loadout.playerLevel.rsn);
    }
    addSkill('Attack', '/images/f/fe/Attack_icon.png?b4bce', 25, 25, 1);
    addSkill('Strength', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 1);
    addSkill('Defence', '/images/b/b7/Defence_icon.png?ca0cd', 17, 19, 1);
    addSkill('Ranged', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 1);
    addSkill('Magic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 1);
    addSkill('Prayer', '/images/f/f2/Prayer_icon.png?ca0dc', 23, 23, 2);
    addSkill('Hitpoints', '/images/9/96/Hitpoints_icon.png?a4819', 23, 20, 2);
    addSkill('Mining', '/images/4/4a/Mining_icon.png?00870', 23, 23, 1);
    addSkill('Farming', '/images/f/fc/Farming_icon.png?558fa', 25, 23, 1);
    dpsForm.form.playerLevel.skillLayout1 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.playerLevel.layout.Attack, dpsForm.form.playerLevel.layout.Strength, dpsForm.form.playerLevel.layout.Defence, dpsForm.form.playerLevel.layout.Magic, dpsForm.form.playerLevel.layout.Ranged],
      id: 'dps-player-skill-skillLayout1'
    });
    dpsForm.form.playerLevel.skillLayout2 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.playerLevel.layout.Hitpoints, dpsForm.form.playerLevel.layout.Prayer, dpsForm.form.playerLevel.layout.Mining, dpsForm.form.playerLevel.layout.Farming],
      id: 'dps-player-skill-skillLayout2'
    });

    // // // // // // // // // //
    // Boosts (Prayer/Potion)  //
    // // // // // // // // // //
    if (settings.loadout.slot == null) {
      settings.loadout.slot = {};
    }
    dpsForm.form.slot = {};
    if (settings.loadout.slot.prayers == null) {
      settings.loadout.slot.prayers = [];
    }
    arr = [];
    for (var item in equipment.prayers) {
      if (equipment.prayers[item].vi || true) {
        //Add all prayers for now
        arr.push({
          data: item,
          icon: 'search'
        });
      }
    }
    dpsForm.form.slot.prayers = new OO.ui.MenuTagMultiselectWidget({
      allowArbitrary: false,
      inputPosition: 'outline',
      spellcheck: false,
      menu: {
        highlightOnFilter: true,
        hideOnChoose: false
      },
      options: arr,
      classes: ['dps-slot-menu', 'prayers']
    });
    dpsForm.form.slot.prayers.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
    settings.loadout.slot.prayers.forEach(function (prayer) {
      dpsForm.form.slot.prayers.addTag(prayer);
    });
    dpsForm.form.slot.prayers.on('change', dpsForm.prayerChange);
    if (settings.loadout.slot.potions == null) {
      settings.loadout.slot.potions = [];
    }
    arr = [];
    for (var potion in equipment.potions) {
      if (equipment.potions[potion].vi) {
        //only add if visible >= 1
        arr.push({
          data: potion
        });
      }
    }
    dpsForm.form.slot.potions = new OO.ui.MenuTagMultiselectWidget({
      allowArbitrary: false,
      inputPosition: 'outline',
      spellcheck: false,
      menu: {
        highlightOnFilter: true,
        hideOnChoose: false
      },
      options: arr,
      classes: ['dps-slot-menu', 'potions']
    });
    dpsForm.form.slot.potions.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
    settings.loadout.slot.potions.forEach(function (potion) {
      dpsForm.form.slot.potions.addTag(potion);
    });
    dpsForm.form.slot.potions.on('change', dpsForm.potionChange);

    // // // // // // // //
    //  Equipment slots  //
    // // // // // // // //
    if (settings.loadout.slot.label == null) {
      settings.loadout.slot.label = {};
    }
    if (settings.loadout.slot.slotItem == null) {
      settings.loadout.slot.slotItem = {};
    }
    if (settings.loadout.slot.visibleBlowpipe == null) {
      settings.loadout.slot.visibleBlowpipe = false;
    }
    if (settings.loadout.slot.visibleSpell == null) {
      settings.loadout.slot.visibleSpell = false;
    }
    if (settings.loadout.slot.disabledShield == null) {
      settings.loadout.slot.disabledShield = false;
    }
    dpsForm.form.slot.icon = {};
    dpsForm.form.slot.label = {};
    dpsForm.form.slot.layout = {};
    var addSlot = function addSlot(slot, label) {
      if (settings.loadout.slot.label[slot] == null) {
        settings.loadout.slot.label[slot] = '';
      }
      if (settings.loadout.slot.slotItem[slot] == null) {
        settings.loadout.slot.slotItem[slot] = equipment.getItem('None', slot)[0];
      }
      var arr = [];
      for (var item in equipment[slot]) {
        if (equipment[slot][item].vi) {
          //only add if visible == 1
          arr.push({
            data: item
          });
        }
      }
      dpsForm.form.slot[slot] = new OO.ui.ComboBoxInputWidget({
        autocomplete: false,
        required: false,
        options: arr,
        placeholder: label,
        value: settings.loadout.slot.label[slot],
        spellcheck: false,
        menu: {
          filterFromInput: true,
          autoHide: true
        },
        classes: ['dps-slot-menu', slot]
      });
      dpsForm.form.slot[slot].menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
      dpsForm.form.slot[slot].on('change', dpsForm.slotChange, [slot]);
      if (slot == 'spell' || slot == 'combatStyle') {
        arr = utils.titleCase(settings.loadout.slot.label[slot]);
      } else {
        arr = utils.itemCase(settings.loadout.slot.label[slot]);
      }
      if (settings.loadout.slot.slotItem[slot].vi) {
        //visible selections have a css sprite - Alternatively, could always load the non-sprite image on first load to make the initial page load faster(?)
        arr = $('<div class="dps-slot-icon ' + slot + '"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + arr.replace(/ /g, '_') + '"/></div>');
      } else {
        //invisible selections don't have a css sprite
        arr = $('<div class="dps-slot-icon ' + slot + '"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im + '.png').replace('f/current', 'images') + '?abcde"></div>');
      }
      dpsForm.form.slot.icon[slot] = new OO.ui.LabelWidget({
        label: arr
      });
      dpsForm.form.slot.layout[slot] = new OO.ui.HorizontalLayout({
        items: [dpsForm.form.slot.icon[slot], dpsForm.form.slot[slot]]
      });
    };
    addSlot('head', 'Head');
    addSlot('cape', 'Cape');
    addSlot('neck', 'Neck');
    addSlot('ammo', 'Ammunition');
    addSlot('torso', 'Body');
    addSlot('shield', 'Shield');
    addSlot('legs', 'Legs');
    addSlot('gloves', 'Hand');
    addSlot('boots', 'Feet');
    addSlot('ring', 'Ring');
    addSlot('blowpipe', 'Dart');
    addSlot('spell', 'Spell');
    addSlot('weapon', 'Weapon');
    if (settings.loadout.slot.label.combatStyle == null) {
      settings.loadout.slot.label.combatStyle = 'Crush - Accurate';
    }
    if (settings.loadout.slot.slotItem.combatStyle == null) {
      settings.loadout.slot.slotItem.combatStyle = equipment.getItem('None', 'combatStyle')[0];
    }
    if (settings.loadout.slot.combatStyleLoadout == null) {
      settings.loadout.slot.combatStyleLoadout = 'unarmed';
    }
    dpsForm.form.slot.combatStyle = new OO.ui.DropdownInputWidget({
      classes: ['dps-slot-menu', 'combatStyle'],
      options: [{
        data: settings.loadout.slot.label.combatStyle
      }],
      //placeholder options - it's generated at the end of the function after the element is attached
      value: settings.loadout.slot.label.combatStyle
    });
    dpsForm.form.slot.combatStyle.on('change', dpsForm.slotChange, ['combatStyle']);
    dpsForm.form.slot.icon.combatStyle = new OO.ui.LabelWidget({
      label: $('<div class="dps-slot-icon combatStyle"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem.combatStyle.im + '.png').replace('f/current', 'images') + '?abcde"></div>')
    });
    dpsForm.form.slot.layout.combatStyle = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.slot.icon.combatStyle, dpsForm.form.slot.combatStyle]
    });
    dpsForm.form.slotLayout = new OO.ui.FieldsetLayout({
      items: [dpsForm.form.slot.layout.weapon,
      //includes 2h as well
      dpsForm.form.slot.layout.blowpipe, dpsForm.form.slot.layout.combatStyle, dpsForm.form.slot.layout.spell, dpsForm.form.slot.layout.head, dpsForm.form.slot.layout.cape, dpsForm.form.slot.layout.neck, dpsForm.form.slot.layout.ammo, dpsForm.form.slot.layout.torso, dpsForm.form.slot.layout.shield, dpsForm.form.slot.layout.legs, dpsForm.form.slot.layout.gloves, dpsForm.form.slot.layout.boots, dpsForm.form.slot.layout.ring],
      id: 'dps-slot-layout'
    });

    // // // // // // // //
    //  Equipment stats  //
    // // // // // // // //
    if (settings.loadout.slot.equipmentBonus == null) {
      settings.loadout.slot.equipmentBonus = {};
    }
    if (settings.loadout.slot.equipmentBonus.manualLevel == null) {
      settings.loadout.slot.equipmentBonus.manualLevel = false;
    }
    dpsForm.form.equipmentBonus = {};
    dpsForm.form.equipmentBonus.menu = {};
    dpsForm.form.equipmentBonus.layout = {};
    dpsForm.form.equipmentBonus.levelToggle = new OO.ui.ToggleSwitchWidget({
      id: 'dps-equip-levelToggle',
      value: settings.loadout.slot.equipmentBonus.manualLevel
    });
    dpsForm.form.equipmentBonus.levelToggle.on('change', dpsForm.equipmentBonusToggleChange);
    dpsForm.form.equipmentBonus.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.levelToggle, {
      label: 'Manually override equipment stats',
      align: 'left',
      id: 'dps-equip-levelToggleLayout'
    });
    var addEquipmentBonusForm = function addEquipmentBonusForm(name, icon, width, height, placeholder) {
      if (settings.loadout.slot.equipmentBonus[name] == null) {
        settings.loadout.slot.equipmentBonus[name] = '';
      }
      dpsForm.form.equipmentBonus.menu[name] = new OO.ui.TextInputWidget({
        autocomplete: false,
        value: (settings.loadout.slot.equipmentBonus[name] < 0 ? '' : '+') + settings.loadout.slot.equipmentBonus[name] + (name === 'bm' ? '%' : ''),
        //add + and %
        classes: ['dps-equip-menu', name],
        validate: /^[-+]?\d+$/
      });
      if (name === 'bm') {
        dpsForm.form.equipmentBonus.menu[name].setValidation(/^[-+]?\d+%?$/);
      }
      dpsForm.form.equipmentBonus.menu[name].on('change', dpsForm.equipmentBonusNumberChange, [name]);
      dpsForm.form.equipmentBonus.layout[name] = new OO.ui.FieldLayout(dpsForm.form.equipmentBonus.menu[name], {
        label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
        align: 'left',
        classes: ['dps-equip-layout', name]
      });
    };
    addEquipmentBonusForm('at', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Aggressive stab bonus');
    addEquipmentBonusForm('al', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Aggressive slash bonus');
    addEquipmentBonusForm('ac', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Aggressive crush bonus');
    addEquipmentBonusForm('am', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Aggressive magic bonus');
    addEquipmentBonusForm('ar', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Aggressive ranged bonus');
    addEquipmentBonusForm('dt', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Defensive stab bonus');
    addEquipmentBonusForm('dl', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Defensive slash bonus');
    addEquipmentBonusForm('dc', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Defensive crush bonus');
    addEquipmentBonusForm('dm', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Defensive magic bonus');
    addEquipmentBonusForm('dr', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Defensive ranged bonus');
    addEquipmentBonusForm('bs', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 'Strength bonus');
    addEquipmentBonusForm('br', '/images/2/22/Ranged_Strength_icon.png?79763', 26, 25, 'Ranged strength bonus');
    addEquipmentBonusForm('bm', '/images/c/cc/Magic_Damage_icon.png?63a1a', 28, 28, 'Magic strength bonus');
    addEquipmentBonusForm('pr', '/images/f/f2/Prayer_icon.png?ca0dc', 23, 23, 'Prayer bonus');
    dpsForm.form.equipmentBonus.bigLayout1 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.equipmentBonus.layout.at, dpsForm.form.equipmentBonus.layout.al, dpsForm.form.equipmentBonus.layout.ac, dpsForm.form.equipmentBonus.layout.am, dpsForm.form.equipmentBonus.layout.ar]
    });
    dpsForm.form.equipmentBonus.bigLayout2 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.equipmentBonus.layout.dt, dpsForm.form.equipmentBonus.layout.dl, dpsForm.form.equipmentBonus.layout.dc, dpsForm.form.equipmentBonus.layout.dm, dpsForm.form.equipmentBonus.layout.dr]
    });
    dpsForm.form.equipmentBonus.bigLayout3 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.equipmentBonus.layout.bs, dpsForm.form.equipmentBonus.layout.br, dpsForm.form.equipmentBonus.layout.bm, dpsForm.form.equipmentBonus.layout.pr]
    });

    // // // // // // // // //
    //  Monster Name/Toggle //
    // // // // // // // // //
    dpsForm.form.monster = {};
    if (settings.loadout.monster.name == null) {
      settings.loadout.monster.name = '';
    }
    arr = [];
    for (var mon in monster) {
      if (monster[mon].vi) {
        arr.push({
          data: mon
        });
      }
    }
    dpsForm.form.monster.name = new OO.ui.ComboBoxInputWidget({
      autocomplete: false,
      required: false,
      options: arr,
      placeholder: 'Monster name',
      value: settings.loadout.monster.name,
      spellcheck: false,
      menu: {
        filterFromInput: true,
        autoHide: true
      }
    });
    dpsForm.form.monster.name.menu.filterMode = 'substring'; //For compatibility issues, the filterMode needs to be defined outside of the init as the current version of oojs.ui that the wiki has does not support filterMode
    dpsForm.form.monster.name.on('change', dpsForm.monsterNameChange);

    //Monster name selection > Layout
    dpsForm.form.monster.nameLayout = new OO.ui.FieldLayout(dpsForm.form.monster.name, {
      label: 'Monster name'
    });

    // Manual monster - ToggleSwitch
    if (settings.loadout.monster.manualLevel == null) {
      settings.loadout.monster.manualLevel = false;
    }
    dpsForm.form.monster.levelToggle = new OO.ui.ToggleSwitchWidget({
      id: 'dps-monster-levelToggle',
      value: settings.loadout.monster.manualLevel
    });
    dpsForm.form.monster.levelToggle.on('change', dpsForm.monsterToggleChange);

    // Manual monster > FieldLayout
    dpsForm.form.monster.levelToggleLayout = new OO.ui.FieldLayout(dpsForm.form.monster.levelToggle, {
      label: 'Manually override monster stats',
      align: 'left',
      id: 'dps-monster-levelToggleLayout'
    });

    // // // // // // //
    //  Monster Skill //
    // // // // // // //

    if (settings.loadout.monster.current == null) {
      settings.loadout.monster.current = {};
    }
    if (settings.loadout.monster.visible == null) {
      settings.loadout.monster.visible = {};
    }
    dpsForm.form.monster.menu = {};
    dpsForm.form.monster.icon = {};
    dpsForm.form.monster.layout = {};
    dpsForm.form.monster.slash = {};
    // Monster PARAMETERS

    var addMonsterSkill = function addMonsterSkill(skill, icon, width, height) {
      // Numeric parameter - NumberInput
      if (settings.loadout.monster.current[skill] == null) {
        settings.loadout.monster.current[skill] = 1;
      }
      if (settings.loadout.monster.visible[skill] == null) {
        settings.loadout.monster.visible[skill] = 1;
      }
      dpsForm.form.monster.icon[skill] = new OO.ui.LabelWidget({
        label: $('<img alt="' + skill + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
        classes: ['dps-monster-skill-icon', 'dps-skill-icon']
      });
      dpsForm.form.monster.menu['current' + skill] = new OO.ui.NumberInputWidget({
        min: 0,
        max: 9999,
        step: 1,
        showButtons: false,
        value: settings.loadout.monster.current[skill],
        classes: ['dps-monster-skill-current', 'dps-skill-input']
      });
      dpsForm.form.monster.menu['current' + skill].on('change', dpsForm.monsterSkillChange, [skill, 2]);
      dpsForm.form.monster.slash[skill] = new OO.ui.LabelWidget({
        label: '/',
        classes: ['dps-monster-skill-slash', 'dps-skill-slash']
      });
      dpsForm.form.monster.menu['visible' + skill] = new OO.ui.NumberInputWidget({
        min: 0,
        max: 9999,
        step: 1,
        showButtons: false,
        value: settings.loadout.monster.visible[skill],
        classes: ['dps-monster-skill-visible', 'dps-skill-input']
      });
      dpsForm.form.monster.menu['visible' + skill].on('change', dpsForm.monsterSkillChange, [skill, 3]);
      dpsForm.form.monster.layout[skill] = new OO.ui.HorizontalLayout({
        items: [dpsForm.form.monster.icon[skill], dpsForm.form.monster.menu['visible' + skill], dpsForm.form.monster.slash[skill], dpsForm.form.monster.menu['current' + skill]],
        classes: ['dps-monster-skill-layout', 'dps-skill-layout']
      });
    };
    addMonsterSkill('Hitpoints', '/images/9/96/Hitpoints_icon.png?a4819', 23, 20);
    addMonsterSkill('Attack', '/images/f/fe/Attack_icon.png?b4bce', 25, 25);
    addMonsterSkill('Strength', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20);
    addMonsterSkill('Defence', '/images/b/b7/Defence_icon.png?ca0cd', 17, 19);
    addMonsterSkill('Magic', '/images/5/5c/Magic_icon.png?334cf', 25, 23);
    addMonsterSkill('Ranged', '/images/1/19/Ranged_icon.png?01b0e', 23, 23);

    // // // // // // //
    // Monster Inputs //
    // // // // // // //

    var addMonsterForm = function addMonsterForm(name, icon, width, height, placeholder, validate, addplus, addperc, defaultvalue) {
      // Numeric parameter - NumberInput
      if (settings.loadout.monster[name] == null) {
        settings.loadout.monster[name] = defaultvalue;
      }
      dpsForm.form.monster.menu[name] = new OO.ui.TextInputWidget({
        autcomplete: false,
        value: (addplus ? settings.loadout.monster[name] < 0 ? '' : '+' : '') + settings.loadout.monster[name] + (addperc ? '%' : ''),
        //add + and %
        validate: validate,
        classes: ['dps-monster-input', 'dps-monster-input-' + name]
      });
      dpsForm.form.monster.menu[name].on('change', dpsForm.monsterNumberChange, [name, defaultvalue]);

      // Numeric parameter > FieldLayout
      dpsForm.form.monster.layout[name] = new OO.ui.FieldLayout(dpsForm.form.monster.menu[name], {
        label: $('<img alt="' + name + '" src="' + icon + '" data-file-width="' + width + '" data-file-height="' + height + '" width="' + width + '" height="' + height + '">'),
        align: 'left',
        classes: ['dps-monster-input-layout']
      });
    };
    addMonsterForm('maxHit', '/images/8/8f/Combat_icon.png?93d63', 19, 19, 'Max hit', /^\d+$/, false, false, 0); //#
    addMonsterForm('astab', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Aggressive stab bonus', /^[-+]?\d+$/, true, false, 0); //+-#
    addMonsterForm('aslash', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Aggressive slash bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('acrush', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Aggressive crush bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('amagic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Aggressive magic bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('arange', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Aggressive ranged bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('dstab', '/images/5/5c/White_dagger.png?51225', 21, 31, 'Defensive stab bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('dslash', '/images/8/8b/White_scimitar.png?3ef63', 27, 30, 'Defensive slash bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('dcrush', '/images/6/6a/White_warhammer.png?121eb', 22, 29, 'Defensive crush bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('dmagic', '/images/5/5c/Magic_icon.png?334cf', 25, 23, 'Defensive magic bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('drange', '/images/1/19/Ranged_icon.png?01b0e', 23, 23, 'Defensive ranged bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('abns', '/images/f/fe/Attack_icon.png?b4bce', 25, 25, 'Attack bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('str', '/images/1/1b/Strength_icon.png?e6e0c', 16, 20, 'Strength bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('rstr', '/images/2/22/Ranged_Strength_icon.png?79763', 26, 25, 'Ranged strength bonus', /^[-+]?\d+$/, true, false, 0);
    addMonsterForm('mdmg', '/images/c/cc/Magic_Damage_icon.png?63a1a', 28, 28, 'Magic strength bonus', /^[-+]?\d+%?$/, true, true, 0); //+-#%
    addMonsterForm('xpBonus', '/images/b/bd/Stats_icon.png?1b467', 25, 23, 'XP bonus', /^\d*(0|0\.0|2\.5|5|5\.0|7\.5)%?$/, false, true, 0); //Multiple of 2.5 +- %
    addMonsterForm('attackSpeed', '/images/a/aa/Monster_attack_speed_1.png?fe7f2', 32, 10, 'Attack speed', /^[1-9]\d?$/, false, false, 4); //1-99
    addMonsterForm('size', '/images/c/c2/Boxing_ring_icon.png?64787', 27, 22, 'Tile size', /^[1-9]\d?$/, false, false, 1);

    // attackStyle > DropdownWidget
    if (settings.loadout.monster.attackStyle == null) {
      settings.loadout.monster.attackStyle = 'Melee';
    }
    dpsForm.form.monster.menu.attackStyle = new OO.ui.DropdownInputWidget({
      placeholder: 'Attack style',
      options: [{
        data: 'Melee'
      }, {
        data: 'Stab'
      }, {
        data: 'Slash'
      }, {
        data: 'Crush'
      }, {
        data: 'Ranged'
      }, {
        data: 'Magic'
      }, {
        data: 'Magical melee'
      }, {
        data: 'Magical ranged'
      }, {
        data: 'Ranged magic'
      }],
      value: settings.loadout.monster.attackStyle
    });
    dpsForm.form.monster.menu.attackStyle.on('change', dpsForm.monsterAttackStyleChange);

    // attackStyle > FieldLayout
    dpsForm.form.monster.layout.attackStyle = new OO.ui.FieldLayout(dpsForm.form.monster.menu.attackStyle, {
      label: $('<img alt="attackStyle" src="/images/8/8f/Combat_icon.png?93d63" data-file-width="19" data-file-height="19" width="19" height="19">'),
      align: 'left'
    });

    // immunePoison > DropdownWidget
    if (settings.loadout.monster.immunePoison == null) {
      settings.loadout.monster.immunePoison = 0;
    }
    dpsForm.form.monster.menu.immunePoison = new OO.ui.DropdownInputWidget({
      placeholder: 'Poison immunity',
      options: [{
        data: 0,
        label: 'Not immune'
      }, {
        data: 1,
        label: 'Immune'
      }, {
        data: 3,
        label: 'Smoke spells only'
      }],
      value: settings.loadout.monster.immunePoison,
      classes: ['dps-monster-input', 'dps-monster-input-immunePoison']
    });
    dpsForm.form.monster.menu.immunePoison.dropdownWidget.menu.setIdealSize = function () {
      //Overriding another prototype to make the visible selection + the dropdown menu have different widths
      this.idealWidth = 150; //override the width
      if (!this.clipping) {
        this.$clippable.css({
          width: this.idealWidth
        });
      }
    };
    dpsForm.form.monster.menu.immunePoison.on('change', dpsForm.immunityChange, ['immunePoison']);

    // immunePoison > FieldLayout
    dpsForm.form.monster.layout.immunePoison = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immunePoison, {
      label: $('<img alt="immunePoison" src="/images/c/ca/Poison_hitsplat.png?04b47" data-file-width="24" data-file-height="23" width="24" height="23">'),
      align: 'left',
      classes: ['dps-monster-input-layout']
    });

    // immuneVenom > DropdownWidget
    if (settings.loadout.monster.immuneVenom == null) {
      settings.loadout.monster.immuneVenom = 0;
    }
    dpsForm.form.monster.menu.immuneVenom = new OO.ui.DropdownInputWidget({
      placeholder: 'Poison immunity',
      options: [{
        data: 0,
        label: 'Not immune'
      }, {
        data: 1,
        label: 'Immune'
      }, {
        data: 2,
        label: 'Poisons instead'
      }],
      value: settings.loadout.monster.immuneVenom,
      classes: ['dps-monster-input', 'dps-monster-input-immuneVenom']
    });
    dpsForm.form.monster.menu.immuneVenom.dropdownWidget.menu.setIdealSize = function () {
      //Overriding another prototype to make the visible selection + the dropdown menu have different widths
      this.idealWidth = 150; //override the width
      if (!this.clipping) {
        this.$clippable.css({
          width: this.idealWidth
        });
      }
    };
    dpsForm.form.monster.menu.immuneVenom.on('change', dpsForm.immunityChange, ['immuneVenom']);

    // immuneVenom > FieldLayout
    dpsForm.form.monster.layout.immuneVenom = new OO.ui.FieldLayout(dpsForm.form.monster.menu.immuneVenom, {
      label: $('<img alt="immuneVenom" src="/images/3/37/Venom_hitsplat.png?8843e" data-file-width="24" data-file-height="23" width="24" height="23">'),
      align: 'left',
      classes: ['dps-monster-input-layout']
    });

    // combatType > MenuTagMultiselectWidget
    if (settings.loadout.monster.combatType == null) {
      settings.loadout.monster.combatType = [];
    }
    dpsForm.form.monster.menu.combatType = new OO.ui.MenuTagMultiselectWidget({
      allowArbitrary: false,
      inputPosition: 'outline',
      spellcheck: false,
      placeholder: 'Combat attributes',
      menu: {
        highlightOnFilter: true,
        hideOnChoose: false
      },
      options: [{
        data: 'demon'
      }, {
        data: 'dragon'
      }, {
        data: 'fiery'
      }, {
        data: 'kalphite'
      }, {
        data: 'leafy'
      }, {
        data: 'penance'
      }, {
        data: 'shade'
      }, {
        data: 'slayer'
      }, {
        data: 'undead'
      }, {
        data: 'vampyre1'
      }, {
        data: 'vampyre2'
      }, {
        data: 'vampyre3'
      }, {
        data: 'castle wars flagholder'
      }]
    });
    settings.loadout.monster.combatType.forEach(function (item) {
      dpsForm.form.monster.menu.combatType.addTag(item);
    });
    dpsForm.form.monster.menu.combatType.on('change', dpsForm.monsterCombatTypeChange);
    dpsForm.form.monsterLayout1 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.monster.layout.Hitpoints, dpsForm.form.monster.layout.Attack, dpsForm.form.monster.layout.Strength, dpsForm.form.monster.layout.Defence, dpsForm.form.monster.layout.Magic, dpsForm.form.monster.layout.Ranged]
    });
    dpsForm.form.monsterLayout2 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.monster.layout.astab, dpsForm.form.monster.layout.aslash, dpsForm.form.monster.layout.acrush, dpsForm.form.monster.layout.amagic, dpsForm.form.monster.layout.arange]
    });
    dpsForm.form.monsterLayout3 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.monster.layout.dstab, dpsForm.form.monster.layout.dslash, dpsForm.form.monster.layout.dcrush, dpsForm.form.monster.layout.dmagic, dpsForm.form.monster.layout.drange]
    });
    dpsForm.form.monsterLayout4 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.monster.layout.abns, dpsForm.form.monster.layout.str, dpsForm.form.monster.layout.rstr, dpsForm.form.monster.layout.mdmg, dpsForm.form.monster.layout.immunePoison]
    });
    dpsForm.form.monsterLayout5 = new OO.ui.HorizontalLayout({
      items: [dpsForm.form.monster.layout.maxHit, dpsForm.form.monster.layout.xpBonus, dpsForm.form.monster.layout.attackSpeed, dpsForm.form.monster.layout.size, dpsForm.form.monster.layout.immuneVenom]
    });
    dpsForm.form.monsterLayout6 = new OO.ui.FieldsetLayout({
      items: [dpsForm.form.monster.layout.attackStyle, dpsForm.form.monster.menu.combatType]
    });

    // Button
    dpsForm.form.calculateButton = new OO.ui.ButtonWidget({
      label: 'CALCULATE',
      icon: 'die',
      title: 'Remove'
    });
    dpsForm.form.calculateButton.on('click', dpsForm.calculateButtonClicked);

    //add item_id to parameters(?) + all items

    $('.dps-main').empty().append($('<tr><th class="dps-subheader infobox-subheader">Player</th></tr>')).append($('<tr/>').append($('<td/>').append(dpsForm.form.playerLevel.rsnInputLayout.$element).append(dpsForm.form.playerLevel.skillLayout1.$element).append(dpsForm.form.playerLevel.skillLayout2.$element))).append('<tr><th class="dps-subheader infobox-subheader">Boosts</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.slot.potions.$element).append(dpsForm.form.slot.prayers.$element))).append('<tr><th class="dps-subheader infobox-subheader">Equipment</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.slotLayout.$element))).append('<tr><th class="dps-subheader infobox-subheader">Equipment Stats</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.equipmentBonus.levelToggleLayout.$element).append(dpsForm.form.equipmentBonus.bigLayout1.$element).append(dpsForm.form.equipmentBonus.bigLayout2.$element).append(dpsForm.form.equipmentBonus.bigLayout3.$element))).append('<tr><th class="dps-subheader infobox-subheader">Monster</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.monster.nameLayout.$element).append(dpsForm.form.monster.levelToggleLayout.$element).append(dpsForm.form.monsterLayout1.$element).append(dpsForm.form.monsterLayout2.$element).append(dpsForm.form.monsterLayout3.$element).append(dpsForm.form.monsterLayout4.$element).append(dpsForm.form.monsterLayout5.$element).append(dpsForm.form.monsterLayout6.$element))).append('<tr><th class="dps-subheader infobox-subheader">Calculate</th></tr>').append($('<tr/>').append($('<td/>').append(dpsForm.form.calculateButton.$element))).append($('<tr class="dps-testOutput"/>'));
    //.after($(resultsContainer)).after($(resultsHeader));

    //Injecting things with jquery because it's easier than modifying OO.ui
    $('.dps-slot-menu.prayers > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder', 'Activate Prayer');
    $('.dps-slot-menu.potions > .oo-ui-tagMultiselectWidget-input > .oo-ui-inputWidget-input').attr('placeholder', 'Use Stat Boosts');
    var slots = ['head', 'cape', 'neck', 'ammo', 'torso', 'legs', 'gloves', 'boots', 'ring', 'weapon', 'shield', 'blowpipe', 'spell', 'potions', 'prayers'];
    slots.forEach(function (slot) {
      new Image().src = "";
      $('.dps-slot-menu.' + slot + ' > div.oo-ui-menuSelectWidget > div.oo-ui-menuOptionWidget > span.oo-ui-labelElement-label').each(function () {
        var pname = $(this).text();
        $(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + pname.replace(/ /g, '_') + '"/></div>'));
      });
    });

    //TODO: Build the dropdowns here to load the page faster

    //needs to be after elements added to page
    dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
    dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
    dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
    dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout, settings.loadout.slot.label.combatStyle);
    dpsForm.equipmentBonusToggleChange(settings.loadout.slot.equipmentBonus.manualLevel);
    dpsForm.monsterToggleChange(settings.loadout.monster.manualLevel);
    updateVisibleLevels(['Attack', 'Strength', 'Defence', 'Magic', 'Ranged', 'Mining', 'Farming']);
    //Resave loadout to fix null parameters
    settings.save('loadout');
  },
  //When new rsn is submitted,
  //Lookup new skill level values.
  rsnInputSubmit: function rsnInputSubmit() {
    if (dpsForm.form.playerLevel.rsnButton.isActive()) {
      return;
    }
    console.log('rsnInputSubmit');
    var _rsn = dpsForm.form.playerLevel.rsnInput.getValue();
    api.lookup(_rsn);
    settings.loadout.playerLevel.rsn = _rsn;
    settings.save('playerLevel');
  },
  // When a player changes one of his current or visible skill levels,
  playerLevelChange: function playerLevelChange(skill, type, value) {
    console.log('playerLevelChange: ' + skill + ' to ' + value);
    var lvl = utils.parseIntDefault(value, 1);
    if (type === 1) {
      //Main skill like Attack
      settings.loadout.playerLevel.current[skill] = lvl;
      updateVisibleLevels([skill]);
    } else if (type === 2) {
      //Current Prayer/Hitpoints
      if (settings.loadout.playerLevel.current[skill] === settings.loadout.playerLevel.visible[skill]) {
        settings.loadout.playerLevel.visible[skill] = lvl;
        dpsForm.form.playerLevel.visible[skill].setValue(value);
      }
      settings.loadout.playerLevel.current[skill] = lvl;
    } else if (type === 3) {
      //Visible Prayer/Hitpoints
      settings.loadout.playerLevel.visible[skill] = lvl;
    }
    settings.save('playerLevel');
  },
  // When a player changes one of his equipments
  slotChange: function slotChange(slot, value) {
    var itemname;
    if (slot == 'spell' || slot == 'combatStyle') {
      itemname = utils.titleCase(value);
    } else {
      itemname = utils.itemCase(value);
    }
    console.log('slotChange: ' + slot + ' to ' + itemname);
    settings.loadout.slot.label[slot] = itemname;
    var ret = equipment.getItem(itemname, slot);
    settings.loadout.slot.slotItem[slot] = ret[0];
    if (ret[1]) {
      dpsForm.form.slot[slot].setFlags({
        invalid: false
      });
    } else {
      dpsForm.form.slot[slot].setFlags('invalid'); //TODO: the flag gets insta-removed from the class for some unclear reason so it doesn't work. To investigate - probably need to set a validation function
    }
    if (settings.loadout.slot.slotItem[slot].vi) {
      //visible selections have a css sprite
      dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon ' + slot + '"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-' + slot + ' dps-img-' + slot + '-' + itemname.replace(/ /g, '_') + '"/></div>'));
    } else {
      //invisible selections don't have a css sprite
      dpsForm.form.slot.icon[slot].setLabel($('<div class="dps-slot-icon ' + slot + '"><img src="' + rswiki.getFileURLCached(settings.loadout.slot.slotItem[slot].im + '.png').replace('f/current', 'images') + '?abcde"></div>'));
    }
    if (slot == 'weapon') {
      settings.loadout.slot.disabledShield = settings.loadout.slot.slotItem[slot].sl == '2h';
      dpsForm.form.slot.shield.setDisabled(settings.loadout.slot.disabledShield);
      if (settings.loadout.slot.disabledShield == true) {
        dpsForm.form.slot.shield.setValue('');
        settings.loadout.slot.label.shield = '';
      }
      settings.loadout.slot.visibleBlowpipe = itemname == 'Toxic blowpipe';
      dpsForm.form.slot.layout.blowpipe.toggle(settings.loadout.slot.visibleBlowpipe);
      if (settings.loadout.slot.visibleBlowpipe == false) {
        dpsForm.form.slot.blowpipe.setValue('');
        settings.loadout.slot.label.blowpipe = '';
      }
      settings.loadout.slot.combatStyleLoadout = settings.loadout.slot.slotItem[slot].cS;
      dpsForm.updateCombatStyleLoadout(settings.loadout.slot.combatStyleLoadout);
    }
    if (slot == 'combatStyle') {
      settings.loadout.slot.visibleSpell = itemname == 'Magic - Spell' || itemname == 'Magic - Defensive Spell';
      dpsForm.form.slot.layout.spell.toggle(settings.loadout.slot.visibleSpell);
      if (settings.loadout.slot.visibleSpell == false) {
        dpsForm.form.slot.spell.setValue('');
        settings.loadout.slot.label.spell = '';
      }
    } else if (!settings.loadout.slot.equipmentBonus.manualLevel) {
      updateEquipmentBonus();
    } else {
      dpsForm.form.equipmentBonus.levelToggle.setValue(false);
    }
    settings.save('slot');
  },
  //Update the options for combat style. The selected option will default to the first option if value is undefined
  loadedCombatStyleLoadout: '',
  updateCombatStyleLoadout: function updateCombatStyleLoadout(style, value) {
    if (style === dpsForm.loadedCombatStyleLoadout) {
      return;
    }
    dpsForm.loadedCombatStyleLoadout = style;
    var choices = equipment.combatStyleLoadout[style];
    value = value || choices[0];
    var arr = [];
    choices.forEach(function (choice) {
      arr.push({
        data: choice
      });
    });
    dpsForm.form.slot.combatStyle.setOptions(arr);
    var i = 0;
    $('.dps-slot-menu.combatStyle > .oo-ui-dropdownWidget > .oo-ui-menuSelectWidget >.oo-ui-labelElement >.oo-ui-labelElement-label').each(function () {
      $(this).prepend($('<div class="dps-injectedIcon"><img src="/images/4/47/Placeholder.png?abcde" class="dps-img-combatStyle dps-img-combatStyle-' + style.replace(/ /g, '_') + '-' + i + '"/></div>'));
      i++;
    });
    dpsForm.form.slot.combatStyle.setValue(value);
    settings.loadout.slot.label.combatStyle = value;
    settings.save('slot');
  },
  // When a player selects a new prayer or changes order
  // TODO: Event does not fire if player clicks X to remove a value
  prayerChangeStop: false,
  prayerChange: function prayerChange(datalist) {
    if (dpsForm.prayerChangeStop) {
      //prevent recursion when turning off other prayers
      return;
    }
    dpsForm.prayerChangeStop = true;
    var lastElement = datalist.length - 1;
    if (lastElement) {
      //skip if empty list
      equipment.prayers[datalist[lastElement].data].TurnsOff.forEach(function (prayer) {
        dpsForm.form.slot.prayers.removeTagByData(prayer);
      });
    }
    settings.loadout.slot.prayers = dpsForm.form.slot.prayers.getValue();
    dpsForm.prayerChangeStop = false;
    settings.save('slot');
  },
  // When a player selects a new potion or changes order
  // TODO: Event does not fire if player clicks X to remove a value
  potionChange: function potionChange() {
    settings.loadout.slot.potions = dpsForm.form.slot.potions.getValue();
    updateVisibleLevels(['Attack', 'Strength', 'Defence', 'Magic', 'Ranged', 'Mining', 'Farming']);
    settings.save('playerLevel');
    settings.save('slot');
  },
  // When manual equipmentBonus toggle is changed, update all equipment bonuses
  equipmentBonusToggleChange: function equipmentBonusToggleChange(value) {
    console.log('equipmentBonusToggleChange');
    settings.loadout.slot.equipmentBonus.manualLevel = value;
    for (var menu in dpsForm.form.equipmentBonus.menu) {
      dpsForm.form.equipmentBonus.menu[menu].setDisabled(!value);
    }
    if (!value) {
      updateEquipmentBonus();
    }
    settings.save('slot');
  },
  // When a player changes equipmentBonus property
  equipmentBonusNumberChange: function equipmentBonusNumberChange(param, value) {
    settings.loadout.slot.equipmentBonus[param] = utils.parseIntDefault(value, 0);
    settings.save('slot');
  },
  monsterUnlockedToggleButton: true,
  //prevent recursion
  monsterNameChange: function monsterNameChange(value) {
    console.log(value);
    setMonster(value);
    settings.loadout.monster.name = value;
    settings.save('monster');
  },
  // When manual monster toggle is changed, update monster stats
  monsterToggleChange: function monsterToggleChange(value) {
    console.log('monsterToggleChange');
    settings.loadout.monster.manualLevel = value;
    for (var menu in dpsForm.form.monster.menu) {
      dpsForm.form.monster.menu[menu].setDisabled(!value);
    }
    if (!value && dpsForm.monsterUnlockedToggleButton) {
      setMonster(settings.loadout.monster.name);
    }
    settings.save('monster');
  },
  monsterSkillChange: function monsterSkillChange(skill, type, value) {
    var lvl = utils.parseIntDefault(value, 1);
    if (type === 2) {
      //Current
      if (settings.loadout.monster.current[skill] === settings.loadout.monster.visible[skill]) {
        settings.loadout.monster.visible[skill] = lvl;
        dpsForm.form.monster.menu['visible' + skill].setValue(lvl);
      }
      settings.loadout.monster.current[skill] = lvl;
    } else if (type === 3) {
      //Visible
      settings.loadout.monster.visible[skill] = lvl;
    }
    if (dpsForm.monsterUnlockedToggleButton) {
      settings.save('monster');
    }
  },
  monsterNumberChange: function monsterNumberChange(param, defaultvalue, value) {
    if (param == 'xpBonus') {
      settings.loadout.monster[param] = Math.round(utils.parseFloatDefault(value, defaultvalue) / 2.5) * 2.5;
      console.log('xpBonus' + settings.loadout.monster[param]);
    } else {
      settings.loadout.monster[param] = utils.parseIntDefault(value, defaultvalue);
    }
    if (dpsForm.monsterUnlockedToggleButton) {
      settings.save('monster');
    }
  },
  monsterCombatTypeChange: function monsterCombatTypeChange() {
    settings.loadout.monster.combatType = dpsForm.form.monster.menu.combatType.getValue();
    if (dpsForm.monsterUnlockedToggleButton) {
      settings.save('monster');
    }
  },
  monsterAttackStyleChange: function monsterAttackStyleChange() {
    settings.loadout.monster.attackStyle = dpsForm.form.monster.menu.attackStyle.getValue();
    if (dpsForm.monsterUnlockedToggleButton) {
      settings.save('monster');
    }
  },
  immunityChange: function immunityChange(slot, value) {
    //console.log('immunityChange'+value);
    settings.loadout.monster[slot] = value;
    if (dpsForm.monsterUnlockedToggleButton) {
      settings.save('monster');
    }
  },
  calculateButtonClicked: function calculateButtonClicked() {
    var args = calc.playerAttack(settings.loadout);
    var aroll = args[0];
    var dstyle = args[1];
    args = calc.playerMaxHit(settings.loadout);
    var drollmin = args[0];
    var drollmax = args[1];
    var dtypeless = args[2];
    var ticks = args[3];
    var droll = calc.monsterDefence(settings.loadout, dstyle, false);
    var acc = calc.accuracies(aroll, droll);
    var dps = calc.naiveDPS(acc, drollmin, drollmax, dtypeless, ticks);
    var output = 'CALCS:<br><table><tr><td>%</td><td>Defence roll</td></tr>';
    for (var i = 0; i < droll.length; i++) {
      output += '<tr><td>' + droll[i].Prob.toPrecision(5) * 100 + '%</td><td>' + droll[i].Roll + '</td></tr>';
    }
    output += '</table><br><table><tr><td>%</td><td>Attack roll</td><td>Hit</td><td>Accuracy</td><td>Special</td></tr>';
    for (i = 0; i < aroll.length; i++) {
      output += '<tr><td>' + aroll[i].Prob.toPrecision(5) * 100 + '%</td><td>' + aroll[i].Roll + '</td><td>' + (dtypeless + drollmin[i].Roll) + '-' + drollmax[i].Roll + '</td><td>' + acc[i].Roll.toPrecision(5) * 100 + '%</td><td>' + aroll[i].Spec + '</td></tr>';
    }
    output += '</table><br><br>DPS: ' + dps.toPrecision(5);
    $('.dps-testOutput').empty().append(output);
  }
};
function init() {
  try {
    console.error('----------');
    console.log('Combat Calculator Gadget Startup');
    $('table.dps').empty();
    $('table.dps').append('<tr><th class="dps-header infobox-header">Combat Calculator</th></tr>');
    $('table.dps').append('<tr class="dps-main"><td>Loading combat calculator</td></tr>');
    tic();
    settings.load('all');
    settings.loadQueryString();
    toc();
    tic();
    dpsForm.create();
    toc();
  } catch (err) {
    console.error(err);
  }
}
$(init);