2015-03-11 16:00:14 -04:00
//GrimoireJS v0.2
//by Russell Champoux
//Grimoire magic item generator for the Pathfinder roleplaying game
/*global $:false, _:false, Chance:false, console:true */
'use strict' ;
var TYPES = {
ARMOR_AND_SHIELDS : 'Armor and Shields' ,
POTIONS : 'Potions' ,
RINGS : 'Rings' ,
RODS : 'Rods' ,
SCROLLS : 'Scrolls' ,
STAVES : 'Staves' ,
WANDS : 'Wands' ,
WEAPONS : 'Weapons' ,
WONDROUS_ITEMS : 'Wondrous Items' ,
SPELLS : 'Spells'
};
var SPELL_TYPES = {
ARCANE : 'Arcane' ,
DIVINE : 'Divine'
};
2015-03-13 16:59:49 -04:00
var ENERGY_TYPES = {
ACID : 'Acid' ,
COLD : 'Cold' ,
ELEC : 'Electricity' ,
FIRE : 'Fire' ,
SONIC : 'Sonic'
};
2015-03-11 16:00:14 -04:00
var QUALITIES = {
MINOR : 'minor' ,
MEDIUM : 'medium' ,
MAJOR : 'major'
};
var WEIGHTS = {};
WEIGHTS [ QUALITIES . MINOR ] =
{
ARMOR_AND_SHIELDS : 4 ,
WEAPONS : 5 ,
POTIONS : 35 ,
RINGS : 2 ,
RODS : 0 ,
SCROLLS : 35 ,
STAVES : 0 ,
WANDS : 10 ,
WONDROUS_ITEMS : 9 ,
};
WEIGHTS [ QUALITIES . MEDIUM ] =
{
ARMOR_AND_SHIELDS : 10 ,
WEAPONS : 10 ,
POTIONS : 10 ,
RINGS : 10 ,
RODS : 10 ,
SCROLLS : 15 ,
STAVES : 3 ,
WANDS : 15 ,
WONDROUS_ITEMS : 17 ,
};
WEIGHTS [ QUALITIES . MAJOR ] = {
ARMOR_AND_SHIELDS : 10 ,
WEAPONS : 10 ,
POTIONS : 5 ,
RINGS : 10 ,
RODS : 10 ,
SCROLLS : 10 ,
STAVES : 20 ,
WANDS : 5 ,
WONDROUS_ITEMS : 20 ,
};
2015-03-13 16:59:49 -04:00
2015-03-11 16:00:14 -04:00
//Contains all the source info
2015-03-13 16:59:49 -04:00
var Grimoire = function ( config ) { // jshint ignore:line
2015-03-11 17:51:43 -04:00
var self = this ;
this . chance = ( config && config . chance ? config . chance : config && config . seed ? new Chance ( config . seed ) : new Chance ());
this . SOURCES = [];
2015-03-17 17:49:35 -04:00
$ . ajaxSetup ({
async : false //A project that works now is better than one that doesn't exist.
//I MUST come back and revisit this with promises later
});
2015-03-11 17:51:43 -04:00
$ . getJSON ( 'sources/sources.json' , function ( sources ) {
sources . sources . forEach ( function ( url ) {
2015-03-13 16:59:49 -04:00
$ . getJSON ( url , function ( source ) {
console . log ( 'loaded ' + url );
2015-03-11 17:51:43 -04:00
source . data = {};
//construct the data containers we will insert later data into
source . types . forEach ( function ( type ) {
source . data [ type . name ] = {};
source . data [ type . name ]. enabled = true ;
if ( type . source ) {
2015-03-13 16:59:49 -04:00
$ . getJSON ( type . source , function ( typeData ) {
source . data [ type . name ]. data = typeData ;
console . log ( 'loaded ' + type . source );
2015-03-11 17:51:43 -04:00
})
. fail ( function ( d , textStatus , error ) {
2015-03-13 16:59:49 -04:00
console . error ( 'getJSON failed to load ' + type . source + ', status: ' + textStatus + ', error: ' + error );
2015-03-11 17:51:43 -04:00
});
}
2015-03-11 16:00:14 -04:00
});
2015-03-11 17:51:43 -04:00
self . SOURCES . push ( source );
})
. fail ( function ( d , textStatus , error ) {
2015-03-13 16:59:49 -04:00
console . error ( 'getJSON failed to load ' + url + ', status: ' + textStatus + ', error: ' + error );
2015-03-11 16:00:14 -04:00
});
});
2015-03-13 16:59:49 -04:00
sources . other . forEach ( function ( url ) {
$ . getJSON ( url , function ( data ) {
var dataName = url . replace ( /.*\/(.*)\.json/ , '$1' );
self [ dataName ] = data ;
console . log ( 'loaded ' + url + ' as ' + dataName );
})
. fail ( function ( d , textStatus , error ) {
console . error ( 'getJSON failed to load ' + url + ', status: ' + textStatus + ', error: ' + error );
});
});
2015-03-17 17:49:35 -04:00
//LOADED ALL SOURCES AND DATA
2015-03-11 17:51:43 -04:00
})
. fail ( function ( err ) {
2015-03-13 16:59:49 -04:00
console . error ( 'Failed to load sources list:' , err );
2015-03-11 16:00:14 -04:00
});
2015-03-17 17:49:35 -04:00
$ . ajaxSetup ({
async : true
});
2015-03-11 16:00:14 -04:00
2015-03-17 17:49:35 -04:00
//options.sources manually select which sources you want to use
// will not use sources that are marked as disabled
//options.types manually specify what types you want to get
2015-03-13 16:59:49 -04:00
this . getItems = function ( quality , count , options ) {
options = options ? options : {};
var sources = options . sources ? options . sources : self . SOURCES ;
var types = options . types ? options . types : _ . values ( TYPES );
2015-03-11 16:00:14 -04:00
2015-03-11 17:51:43 -04:00
//Error checking block
2015-03-13 16:59:49 -04:00
if ( ! sources || sources . length === 0 ) { console . error ( 'No sources given!' ); return items ; }
2015-03-11 17:51:43 -04:00
if ( ! types || types == 'any' || types . length === 0 ) {
types = _ . values ( TYPES ); //set to all types
2015-03-11 16:00:14 -04:00
}
2015-03-11 17:51:43 -04:00
for ( var i in types ) {
2015-03-13 16:59:49 -04:00
if ( _ . values ( TYPES ). indexOf ( types [ i ]) == - 1 ) { console . error ( 'Invalid type ' + types [ i ]); return items ; }
2015-03-11 16:00:14 -04:00
}
2015-03-13 16:59:49 -04:00
if ( _ . values ( QUALITIES ). indexOf ( quality ) == - 1 ) { console . error ( 'Invalid quality ' + quality ); return items ; }
2015-03-11 17:51:43 -04:00
if ( ! count || count < 1 ) { count = 1 ; }
//initialize the chance engine, using a seed if provided (for testing purposes)
//we do this now instead of reinitializing the Chance engine each time we generate an item
self . chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
2015-03-13 16:59:49 -04:00
var items = [];
2015-03-11 17:51:43 -04:00
_ . times ( count , function () {
2015-03-13 16:59:49 -04:00
var item = self . getItem ( quality , options );
2015-03-11 17:51:43 -04:00
if ( item ) {
items . push ( item );
2015-03-17 17:49:35 -04:00
//console.log(item);
2015-03-11 17:51:43 -04:00
}
});
return items ;
};
var selectSource = function ( sources , quality , type , options ) {
options = options ? options : {};
//this is a concern when "getPotions/getWands" calls "getSpell" which calls "selectSource"
type = options . typeOverride ? options . typeOverride : type ;
quality = quality ? quality : options . quality ;
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var source = {};
var sourceWeights = {};
2015-03-13 16:59:49 -04:00
//***UNIQUES FOR WEAPONS AND ARMORS***
//************************************
if ( type == 'Uniques' ) {
if ( ! options . itemType || ! options . uniqueType ) {
console . error ( 'itemType or uniqueType not provided' , options );
}
sourceWeights = _ . map ( sources , function ( source ) {
2015-03-18 16:59:24 -04:00
var items = source . data [ options . itemType ];
if ( items && items . enabled && items . data [ options . uniqueType ]) {
return _ . filter ( items . data [ options . uniqueType ],
2015-03-13 16:59:49 -04:00
function ( special ) {
return special . weight [ quality ] > 0 ;
}). length ; //return a count of all specials that have a quality weight above 0
}
else {
return 0 ; //either itemType not found or not enabled or uniqueType not found
}
});
}
//***SPECIALS FOR WEAPONS AND ARMORS***
//*************************************
else if ( type == 'Specials' ) {
if ( ! options . itemType || ! options . specialType ) {
console . error ( 'itemType or specialType not provided' , options );
}
sourceWeights = _ . map ( sources , function ( source ) {
if ( source . data [ options . itemType ] && source . data [ options . itemType ]. enabled &&
source . data [ options . itemType ]. data [ options . specialType ]) {
return _ . filter ( source . data [ options . itemType ]. data [ options . specialType ],
function ( special ) {
return special . weight [ quality ] > 0 ;
}). length ; //return a count of all specials that have a quality weight above 0
}
else {
return 0 ; //either itemType not found or not enabled or specialType not found
}
});
}
2015-03-11 17:51:43 -04:00
//***SPELLS AND SCROLLS AND WANDS***
2015-03-13 16:59:49 -04:00
//**********************************
else if ( type == TYPES . SCROLLS || type == TYPES . SPELLS || type == TYPES . WANDS ) {
2015-03-17 17:49:35 -04:00
if ( ! options . spellType || options . spellLevel > 9 || options . spellLevel < 0 ) {
console . error ( 'Error selecting source for Spells. Invalid options' , type , options );
2015-03-11 17:51:43 -04:00
}
sourceWeights = _ . map ( sources , function ( source ) {
//Check that we have a spells section
//AND that the specific item type we are looking for is enabled
if ( source . data [ TYPES . SPELLS ] && source . data [ type ] && source . data [ type ]. enabled ) {
if ( source . data [ TYPES . SPELLS ]. data [ options . spellType ]) {
return source . data [ TYPES . SPELLS ]. data [ options . spellType ][ options . spellLevel ]. length ;
}
else { //source did not have my desired spell type
return 0 ;
}
2015-03-11 16:00:14 -04:00
}
2015-03-11 17:51:43 -04:00
else { //source has no spells
2015-03-11 16:00:14 -04:00
return 0 ;
}
2015-03-11 17:51:43 -04:00
});
}
//***POTIONS***
//*************
else if ( type == TYPES . POTIONS ) {
sourceWeights = _ . map ( sources ,
function ( source ) {
if ( source . data [ type ] && source . data [ type ]. enabled && source . data [ type ]. data [ options . potionLevel ]) {
return source . data [ type ]. data [ options . potionLevel ]. length ;
}
else {
return 0 ; //either no data, disabled, or not items of that quality
}
});
}
//***WONDROUS ITEMS***
//********************
else if ( type == TYPES . WONDROUS_ITEMS ) {
sourceWeights = _ . map ( sources ,
function ( source ) {
if ( source . data [ type ] && source . data [ type ]. enabled && source . data [ type ]. data [ quality ]) {
return source . data [ type ]. data [ quality ]. length ;
}
else {
return 0 ; //either no data, disabled, or not items of that quality
}
});
}
//***EVERYTHING ELSE***
//*********************
else {
2015-03-11 18:07:35 -04:00
if (( type == TYPES . RODS || type == TYPES . STAVES ) && quality == QUALITIES . MINOR ) {
2015-03-13 16:59:49 -04:00
console . error ( 'Error! There are no ' + QUALITIES . MINOR + ' ' + type );
2015-03-11 17:51:43 -04:00
return ;
2015-03-11 16:00:14 -04:00
}
2015-03-11 17:51:43 -04:00
//Determine which source to use
//get a count from each source of what we're looking for
sourceWeights = _ . map ( sources ,
function ( source ) {
//only choose from sources that HAVE that type of item
//AND are enabled
//AND have at least one item of that quality with a probability weight
if ( source . data [ type ] && source . data [ type ]. enabled ) {
return _ . filter ( source . data [ type ]. data ,
2015-03-17 17:49:35 -04:00
function ( item ) {
2015-03-11 17:51:43 -04:00
return item . weight [ quality ] > 0 ;
}). length ;
}
else {
return 0 ;
}
});
}
if ( _ . sum ( sourceWeights ) <= 0 ) {
2015-03-13 16:59:49 -04:00
console . error ( 'Unable to find valid ' + quality + ' ' + type + ' within sources!' , sources );
2015-03-11 16:00:14 -04:00
return ;
}
2015-03-11 17:51:43 -04:00
//chance.weighted will DESTRUCTIVELY modify paramaters that have a weight of zero
//in this case, that means sources that do not contain the selected type
//or sources that have types we want but with no weight
//because of that, I create a temp array that I don't care if chance mangles
source = sources [ chance . weighted ( _ . range ( sources . length ), sourceWeights )];
return source ;
2015-03-11 16:00:14 -04:00
};
2015-03-13 16:59:49 -04:00
this . getItem = function ( quality , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
var sources = options . sources ? options . sources : self . SOURCES ;
var types = options . types ? options . types : _ . values ( TYPES );
2015-03-11 16:00:14 -04:00
2015-03-11 17:51:43 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
2015-03-11 16:00:14 -04:00
2015-03-11 17:51:43 -04:00
var itemType = '' ;
if ( types . length == 1 ) {
itemType = types [ 0 ];
2015-03-11 16:00:14 -04:00
}
2015-03-11 17:51:43 -04:00
else {
//Create a weighted-average map based on the desired types
var localWeights = _ . clone ( WEIGHTS [ quality ]); //copy the default weighted-average for this quality
for ( var key in localWeights ) {
if ( _ . values ( types ). indexOf ( TYPES [ key ]) == - 1 ) { //remove any items that were not selected
delete localWeights [ key ];
}
}
//Determine the type of item to generate
itemType = TYPES [ chance . weighted ( _ . keys ( localWeights ), _ . values ( localWeights ))];
2015-03-11 16:00:14 -04:00
}
2015-03-11 17:51:43 -04:00
var item = {};
var source = {};
//GENERATE ITEM
//SCROLLS
if ( itemType == TYPES . SCROLLS ) {
2015-03-13 16:59:49 -04:00
item = self . getScroll ( quality , options );
2015-03-11 16:00:14 -04:00
}
2015-03-16 16:18:46 -04:00
//POTIONS
2015-03-11 17:51:43 -04:00
else if ( itemType == TYPES . POTIONS ) {
2015-03-13 16:59:49 -04:00
item = self . getPotion ( quality , options );
2015-03-11 17:51:43 -04:00
}
2015-03-16 16:18:46 -04:00
//WANDS
2015-03-11 17:51:43 -04:00
else if ( itemType == TYPES . WANDS ) {
2015-03-13 16:59:49 -04:00
item = self . getWand ( quality , options );
2015-03-11 17:51:43 -04:00
}
//WONDROUS ITEMS
else if ( itemType == TYPES . WONDROUS_ITEMS ) {
2015-03-13 16:59:49 -04:00
item = self . getWondrousItem ( quality , options );
2015-03-11 17:51:43 -04:00
}
2015-03-17 17:49:35 -04:00
//WEAPONS
2015-03-16 16:18:46 -04:00
else if ( itemType == TYPES . WEAPONS ) {
item = self . getWeapon ( quality , options );
}
2015-03-17 17:49:35 -04:00
//ARMOR AND SHIELDS
else if ( itemType == TYPES . ARMOR_AND_SHIELDS ) {
item = self . getArmorOrShield ( quality , options );
}
//RINGS
else if ( itemType == TYPES . RINGS ) {
item = self . getRing ( quality , options );
}
//RODS
else if ( itemType == TYPES . RODS ) {
item = self . getRod ( quality , options );
}
//STAVES
else if ( itemType == TYPES . STAVES ) {
item = self . getStaff ( quality , options );
}
//EVERYTHING ELSE NOT LISTED.
//All TYPES should have their own 'get' method but this allows for additional types to be added
// as long as they follow the data structure standard of source.data.type and contain weights
2015-03-11 17:51:43 -04:00
else {
//Select the item source based on the quality and item type
source = selectSource ( sources , quality , itemType , options );
2015-03-11 16:00:14 -04:00
2015-03-13 16:59:49 -04:00
item = self . getWeightedItem ( source . data [ itemType ]. data , quality );
item . source = source . shortName ;
2015-03-17 17:49:35 -04:00
item . itemType = itemType ;
2015-03-11 17:51:43 -04:00
}
2015-03-13 16:59:49 -04:00
if ( ! options . disableClue && chance . d100 () <= ( options . clueChance || 20 )) {
2015-03-11 17:51:43 -04:00
item . name += ' with a clue to it\'s purpose!' ;
}
item . quality = quality ;
return item ;
};
2015-03-16 16:18:46 -04:00
//options.maxRoll - override the maximul value that can be rolled for these items
// default is the sum of the quality weights
2015-03-13 16:59:49 -04:00
this . getWeightedItem = function ( itemData , quality , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
2015-03-17 17:49:35 -04:00
var maxRoll = options . maxRoll || _ . sum ( _ . map ( itemData , function ( item ) {
return item . weight [ quality ]; }));
2015-03-13 16:59:49 -04:00
var roll = chance . integer ({ min : 1 , max : maxRoll });
var item = {};
for ( var i in itemData ) { //check all 'itemType' items in the source
if ( roll <= itemData [ i ]. weight [ quality ]) { //if we roll under their weighted probability
item = _ . clone ( itemData [ i ]);
delete item . weight ;
return item ;
}
else { //decrease the roll value by the current item's weighted probability
roll -= itemData [ i ]. weight [ quality ];
}
}
console . error ( 'Could not find valid ' + quality + 'item in' , itemData );
2015-03-16 16:18:46 -04:00
};
2015-03-13 16:59:49 -04:00
2015-03-17 17:49:35 -04:00
//options.type can be 'Arcane' or 'Divine'
2015-03-13 16:59:49 -04:00
this . getSpells = function ( level , count , options ) {
options = options ? options : {};
2015-03-11 17:51:43 -04:00
var spells = [];
self . chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
2015-03-17 17:49:35 -04:00
_ . times ( count , function () {
spells . push ( self . getSpell ( level , options ));
2015-03-11 17:51:43 -04:00
});
return spells ;
};
2015-03-17 17:49:35 -04:00
//options.type can be 'Arcane' or 'Divine'
2015-03-13 16:59:49 -04:00
this . getSpell = function ( level , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
var sources = options . sources ? options . sources : self . SOURCES ;
var type = options . type ;
2015-03-11 17:51:43 -04:00
//initialize the chance engine, using a seed if provided (for testing purposes)
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var spellType = type ? type : chance . pick ( _ . values ( SPELL_TYPES ));
options . spellType = spellType ;
options . spellLevel = level ;
var source = selectSource ( sources , options . quality , TYPES . SPELLS , options );
//console.log('Generating a level ' + level + ' ' + spellType + ' spell from ' + source.shortName);
var spell = {
2015-03-13 16:59:49 -04:00
name : chance . pick ( source . data [ TYPES . SPELLS ]. data [ spellType ][ level ]),
2015-03-11 17:51:43 -04:00
level : level ,
type : spellType ,
source : source . shortName
};
return spell ;
};
2015-03-17 17:49:35 -04:00
//options.level manually override the scroll spell level
2015-03-13 16:59:49 -04:00
this . getScroll = function ( quality , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
2015-03-11 17:51:43 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var cost = [ 12.5 , 25 , 150 , 375 , 700 , 1125 , 1650 , 2275 , 3000 , 3825 ];
2015-03-17 17:49:35 -04:00
var spellLevel = options . scrollLevel ;
2015-03-11 17:51:43 -04:00
2015-03-17 17:49:35 -04:00
if ( ! spellLevel || spellLevel < 0 || spellLevel > 9 ) {
var weights = {};
weights [ QUALITIES . MINOR ] = [ 5 , 45 , 45 , 5 , 0 , 0 , 0 , 0 , 0 , 0 ];
weights [ QUALITIES . MEDIUM ] = [ 0 , 0 , 5 , 60 , 30 , 5 , 0 , 0 , 0 , 0 ];
weights [ QUALITIES . MAJOR ] = [ 0 , 0 , 0 , 0 , 5 , 45 , 20 , 15 , 10 , 5 ];
spellLevel = chance . weighted ([ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ], weights [ quality ]);
2015-03-11 17:51:43 -04:00
}
options . quality = quality ;
2015-03-13 16:59:49 -04:00
var spell = self . getSpell ( spellLevel , options );
2015-03-11 17:51:43 -04:00
return {
'name' : 'Scroll of ' + spell . name ,
'description' : '(' + spell . type + ', spell level ' + spellLevel + ', caster level ' + _ . max ([ 1 , ( spellLevel * 2 - 1 )]) + ')' ,
'cost' : cost [ spellLevel ],
2015-03-17 17:49:35 -04:00
'source' : spell . source ,
'itemType' : TYPES . SCROLLS
2015-03-11 17:51:43 -04:00
};
};
2015-03-13 16:59:49 -04:00
this . getPotion = function ( quality , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
var sources = options . sources ? options . sources : self . SOURCES ;
var level = options . potionLevel ;
2015-03-11 17:51:43 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var cost = [ 25 , 50 , 300 , 750 ];
2015-03-16 16:18:46 -04:00
if ( ! level || level < 0 || level > 3 ) {
var weights = {};
weights [ QUALITIES . MINOR ] = [ 20 , 40 , 40 , 0 ];
weights [ QUALITIES . MEDIUM ] = [ 0 , 20 , 40 , 40 ];
weights [ QUALITIES . MAJOR ] = [ 0 , 0 , 20 , 80 ];
level = chance . weighted ([ 0 , 1 , 2 , 3 ], weights [ quality ]);
2015-03-11 17:51:43 -04:00
}
options . typeOverride = TYPES . POTIONS ;
options . potionLevel = level ;
var source = selectSource ( sources , quality , TYPES . POTIONS , options );
return {
'name' : 'Potion/Oil of ' + chance . pick ( source . data [ TYPES . POTIONS ]. data [ level ]),
'description' : '(spell level ' + level + ', caster level ' + _ . max ([ 1 , ( level * 2 - 1 )]) + ')' ,
'cost' : cost [ level ],
2015-03-17 17:49:35 -04:00
'source' : source . shortName ,
'itemType' : TYPES . POTIONS
2015-03-11 17:51:43 -04:00
};
};
2015-03-13 16:59:49 -04:00
this . getWand = function ( quality , options ) {
2015-03-11 17:51:43 -04:00
options = options ? options : {};
2015-03-13 16:59:49 -04:00
var level = options . wandLevel ;
2015-03-11 17:51:43 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var cost = [ 375 , 750 , 4500 , 11250 , 21000 ];
if ( ! level || level < 0 || level > 4 ) {
2015-03-16 16:18:46 -04:00
var weights = {};
weights [ QUALITIES . MINOR ] = [ 5 , 55 , 40 , 0 , 0 ];
weights [ QUALITIES . MEDIUM ] = [ 0 , 0 , 60 , 40 , 0 ];
weights [ QUALITIES . MAJOR ] = [ 0 , 0 , 0 , 60 , 40 ];
level = chance . weighted ([ 0 , 1 , 2 , 3 , 4 ], weights [ quality ]);
2015-03-11 17:51:43 -04:00
}
2015-03-13 16:59:49 -04:00
options . wandLevel = level ;
2015-03-11 17:51:43 -04:00
options . typeOverride = TYPES . WANDS ;
2015-03-13 16:59:49 -04:00
options . spellType = SPELL_TYPES . ARCANE ;
var spell = self . getSpell ( level , options );
2015-03-11 17:51:43 -04:00
return { 'name' : 'Wand of ' + spell . name ,
'description' : '(spell level ' + level + ', caster level ' + _ . max ([ 1 , ( level * 2 - 1 )]) + ', 50 charges)' ,
'cost' : cost [ level ],
2015-03-17 17:49:35 -04:00
'source' : spell . source ,
'itemType' : TYPES . WANDS
2015-03-11 17:51:43 -04:00
};
2015-03-11 16:00:14 -04:00
};
2015-03-13 16:59:49 -04:00
this . getWondrousItem = function ( quality , options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
//Select the item source based on the quality and item type
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , TYPES . WONDROUS_ITEMS , options );
//wondrous items of the same quality have equal probability weight
var item = _ . clone ( chance . pick ( source . data [ TYPES . WONDROUS_ITEMS ]. data [ quality ]));
item . source = source . shortName ;
2015-03-17 17:49:35 -04:00
item . itemType = TYPES . WONDROUS_ITEMS ;
2015-03-13 16:59:49 -04:00
2015-03-17 17:49:35 -04:00
//consumable items should NEVER be intelligent. I don't have a perfect filter, but here's a start
//I may instead add an attribute for 'consumable'
if ( ! /\b(dust|token|tome|elixir|salve|ointment|polish)\b/i . test ( item . name )) {
//make an intelligent item if we either want ALL intelligent items
//OR intelligent items are NOT disabled AND we roll under the chance threshold
if ( options . allIntelligent || ( ! options . disableIntelligent && chance . d100 () <= ( options . intelligenceChance || 1 ))) {
item . intelligence = self . getIntelligence ( item . cost , options ); //'WOOHOO I AM INTELLIGENT!';
item . cost += item . intelligence . cost ;
}
2015-03-13 16:59:49 -04:00
}
return item ;
};
2015-03-17 17:49:35 -04:00
this . getRing = function ( quality , options ) {
options = options ? options : {};
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , TYPES . RINGS , options );
var ring = self . getWeightedItem ( source . data [ TYPES . RINGS ]. data , quality );
ring . source = source . shortName ;
ring . itemType = TYPES . RINGS ;
return ring ;
};
this . getRod = function ( quality , options ) {
options = options ? options : {};
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , TYPES . RODS , options );
var rod = self . getWeightedItem ( source . data [ TYPES . RODS ]. data , quality );
rod . source = source . shortName ;
rod . itemType = TYPES . RODS ;
return rod ;
};
this . getStaff = function ( quality , options ) {
options = options ? options : {};
if ( quality == QUALITIES . MINOR ) {
console . error ( 'Error! There are no minor staves!' );
return ;
}
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , TYPES . STAVES , options );
var staff = self . getWeightedItem ( source . data [ TYPES . STAVES ]. data , quality );
staff . source = source . shortName ;
staff . itemType = TYPES . STAVES ;
return staff ;
};
2015-03-13 16:59:49 -04:00
this . getHumanoid = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
return chance . weighted (
_ . map ( self . Humanoids , function ( h ) { return h . name ; }),
_ . map ( self . Humanoids , function ( h ) { return h . weight ; }));
};
this . getOutsider = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
return chance . weighted (
_ . map ( self . Outsiders , function ( o ) { return o . name ; }),
_ . map ( self . Outsiders , function ( o ) { return o . weight ; }));
};
this . getBane = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var bane = chance . weighted (
_ . map ( self . Banes , function ( b ) { return b . name ; }),
_ . map ( self . Banes , function ( b ) { return b . weight ; }));
bane = bane . replace ( /GET_HUMANOID_TYPE/ , self . getHumanoid ( options ));
bane = bane . replace ( /GET_OUTSIDER_TYPE/ , self . getOutsider ( options ));
return bane ;
};
2015-03-16 16:18:46 -04:00
this . getEnergyType = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
return chance . pick ( _ . values ( ENERGY_TYPES ));
};
2015-03-13 16:59:49 -04:00
//type is 'armor', 'shield', 'melee', or 'ranged'
//options.excludeSpecials can contain a list of specials you do NOT want to get
//this is useful when creating a weapon that you do not want to have duplicate specials
2015-03-18 16:59:24 -04:00
//options.disableRecursiveSpecials
2015-03-13 16:59:49 -04:00
this . getSpecials = function ( quality , type , options ) {
options = options ? options : {};
options . excludeSpecials = options . excludeSpecials ? options . excludeSpecials : [];
options . itemType = options . itemType ? options . itemType :
( type == 'melee' || type == 'ranged' ) ? TYPES . WEAPONS :
( type == 'armor' || type == 'shield' ) ? TYPES . ARMOR_AND_SHIELDS :
undefined ;
options . specialType = type + 'Specials' ;
2015-03-18 16:59:24 -04:00
options . disableRecursiveSpecials = options . disableRecursiveSpecials || false ;
2015-03-13 16:59:49 -04:00
if ( ! options . itemType ) {
console . error ( 'Invalid special type' , type );
return ;
}
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var sources = options . sources ? options . sources : self . SOURCES ;
2015-03-18 16:59:24 -04:00
var specials = [];
2015-03-13 16:59:49 -04:00
var special = {};
do {
2015-03-18 16:59:24 -04:00
var source = selectSource ( sources , quality , 'Specials' , options );
//data source will look like source.data.Weapons.meleeSpecials
2015-03-13 16:59:49 -04:00
2015-03-18 16:59:24 -04:00
special = self . getWeightedItem ( source . data [ options . itemType ]. data [ options . specialType ], quality );
special . source = source . shortName ;
2015-03-13 16:59:49 -04:00
2015-03-18 16:59:24 -04:00
//EXCEPTION for Bane special
if ( /GET_BANE_TYPE/ . test ( special . name )) {
special . name = special . name . replace ( /GET_BANE_TYPE/ , self . getBane ( options ));
} else if ( /GET_ENERGY_RESISTANCE_TYPE/ . test ( special . name )) {
special . name = special . name . replace ( /GET_ENERGY_RESISTANCE_TYPE/ , self . getEnergyType ( options ));
}
2015-03-18 17:19:31 -04:00
} while ( _ . chain ( options . excludeSpecials ). map ( function ( s ) { return s . name ; }). includes ( special . name ). value ()); //if we get something from our exclusions, try again
2015-03-18 16:59:24 -04:00
specials . push ( special );
var roll = chance . d100 ();
//chance for a second special
if (( quality == QUALITIES . MINOR && roll === 100 ) ||
( quality == QUALITIES . MEDIUM && roll > 95 ) ||
( quality == QUALITIES . MAJOR && roll > 90 )) {
options . excludeSpecials = options . excludeSpecials . concat ( specials ); //exclude the special(s) just rolled
var bonusSpecials = self . getSpecials ( quality , type , options );
if ( options . disableRecursiveSpecials ) {
specials . push ( bonusSpecials [ 0 ]); //if we're not allowed to return recursive specials, just take the first one
}
else {
specials = specials . concat ( bonusSpecials );
}
2015-03-13 16:59:49 -04:00
}
2015-03-18 16:59:24 -04:00
return specials ; //return special as array in case we need to concat it with another special
2015-03-13 16:59:49 -04:00
};
this . getUniqueWeapon = function ( quality , options ) {
options = options ? options : {};
options . itemType = TYPES . WEAPONS ;
options . uniqueType = 'uniqueWeapons' ;
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , 'Uniques' , options );
var weapon = self . getWeightedItem ( source . data [ TYPES . WEAPONS ]. data . uniqueWeapons , quality );
weapon . source = source . shortName ;
2015-03-17 17:49:35 -04:00
weapon . itemType = TYPES . WEAPONS ;
2015-03-13 16:59:49 -04:00
return weapon ;
};
//options.type can be 'melee' or 'ranged'
2015-03-17 17:49:35 -04:00
//note that this option is NOT alwawys honored when a unique item is rolled
//I'm not sure if unique weapons should be allowed to have modifiers.
//I'm allowing it because it is more awesome. This can cause problems when the unique rolled is, for example, an arrow
2015-03-18 16:59:24 -04:00
//config.disableRecursiveSpecials If you roll 'get a special and roll again' twice, it will only
// generate one specials
2015-03-13 16:59:49 -04:00
this . getWeapon = function ( quality , options ) {
options = options ? options : {};
options . excludeSpecials = options . excludeSpecials || [];
2015-03-18 16:59:24 -04:00
options . disableRecursiveSpecials = options . disableRecursiveSpecials || false ;
2015-03-13 16:59:49 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var weapon = {
name : '' ,
type : '' ,
bonus : 0 ,
baseCost : 0 ,
2015-03-17 17:49:35 -04:00
cost : 0 ,
2015-03-13 16:59:49 -04:00
specials : [],
intelligence : {},
glowing : false
};
weapon . type = ( options . type == 'melee' || options . type == 'ranged' ) ? options . type
: chance . weighted ([ 'melee' , 'ranged' ], [ 7 , 3 ]);
var roll = chance . d100 ();
var action = '' ;
if ( quality == QUALITIES . MINOR ) {
if ( roll <= 70 ) {
//generic +1 weapon
weapon . bonus = 1 ;
}
else if ( roll <= 85 ) {
//generic +2 weapon
weapon . bonus = 2 ;
}
else if ( roll <= 90 ) {
action = 'getUnique' ;
}
else {
//10% special ability and roll again
action = 'getSpecial' ;
}
}
else if ( quality == QUALITIES . MEDIUM ) {
if ( roll <= 10 ) {
weapon . bonus = 1 ;
}
else if ( roll <= 29 ) {
weapon . bonus = 2 ;
}
else if ( roll <= 58 ) {
weapon . bonus = 3 ;
}
else if ( roll <= 62 ) {
weapon . bonus = 4 ;
}
else if ( roll <= 68 ) {
action = 'getUnique' ;
}
else {
//32% special ability and roll again
action = 'getSpecial' ;
}
}
else if ( quality == QUALITIES . MAJOR ) {
if ( roll <= 20 ) {
weapon . bonus = 3 ;
}
else if ( roll <= 38 ) {
weapon . bonus = 4 ;
}
else if ( roll <= 49 ) {
weapon . bonus = 5 ;
}
else if ( roll <= 63 ) {
action = 'getUnique' ;
}
else {
//37% special ability and roll again
action = 'getSpecial' ;
}
}
else {
console . error ( 'invalid quality' , quality );
return ;
}
if ( action == 'getUnique' ) {
var unique = self . getUniqueWeapon ( quality , options );
weapon . name = unique . name ;
weapon . baseCost = unique . cost ;
weapon . source = unique . source ;
weapon . url = unique . url ;
}
else if ( action == 'getSpecial' ) {
2015-03-18 16:59:24 -04:00
//if we either do not already have any specials OR we allow recursive specials
if ( weapon . specials . length === 0 || options . disableRecursiveSpecials === false ) {
2015-03-17 17:49:35 -04:00
weapon = self . getWeapon ( quality , options );
var specialsBonus = _ . chain ( weapon . specials ). map ( function ( s ) { return s . bonus ; }). sum (). value ();
var specials = [];
var newSpecialsBonus = 0 ;
options . excludeSpecials = options . excludeSpecials . concat ( weapon . specials );
if ( weapon . bonus + specialsBonus < 10 ) {
var tries = 5 ;
//this has the opportunity to loop forever either if no match can be found
do {
specials = self . getSpecials ( quality , weapon . type , options );
newSpecialsBonus = _ . chain ( specials ). map ( function ( s ) { return s . bonus ; }). sum (). value ();
2015-03-18 16:59:24 -04:00
} while ( weapon . bonus + specialsBonus + newSpecialsBonus > 10 && -- tries > 0 );
2015-03-17 17:49:35 -04:00
if ( tries > 0 ) { //we found a match without running out of tries!
weapon . specials = weapon . specials . concat ( specials );
}
2015-03-13 16:59:49 -04:00
}
}
}
//ALL
//1% chance to be intelligent (NOT usable items - eg Arrows)
//30% chance to be glowing
if ( options . allIntelligent ||
( ! options . disableIntelligent && chance . d100 () <= ( options . intelligenceChance || 1 ))) {
2015-03-16 16:18:46 -04:00
var cost = weapon . baseCost + 2000 * Math . pow ( weapon . bonus , 2 );
weapon . intelligence = self . getIntelligence ( cost , options ); //'WOOHOO I AM INTELLIGENT!';
weapon . cost += weapon . intelligence . cost ;
2015-03-13 16:59:49 -04:00
}
if ( ! options . disableGlowing && chance . d100 () <= ( options . glowingChance || 30 )) {
weapon . glowing = true ;
}
2015-03-17 17:49:35 -04:00
var totalBonus = weapon . bonus + _ . sum ( _ . map ( weapon . specials , function ( s ) { return s . bonsu ; }));
weapon . cost = weapon . baseCost + 2000 * Math . pow ( totalBonus , 2 ) + ( weapon . intelligence . cost || 0 );
2015-03-16 16:18:46 -04:00
weapon . print = function () {
var ret = 'Name: ' ;
ret += this . glowing ? 'Glowing ' : '' ;
ret += this . bonus > 0 ? ( '+' + this . bonus + ' ' ) : '' ;
ret += this . specials . length > 0 ? _ . map ( this . specials , function ( s ) { return s . name ;}). join ( ', ' ) + ' ' : '' ;
ret += this . name ? this . name : ( this . type + ' weapon' );
ret += ';' ;
ret += this . intelligence . print ? 'Intelligence: [' + this . intelligence . print () + '];' : '' ;
2015-03-17 17:49:35 -04:00
ret += 'Cost: ' + this . cost + 'gp' ;
2015-03-16 16:18:46 -04:00
return ret ;
};
2015-03-13 16:59:49 -04:00
2015-03-17 17:49:35 -04:00
weapon . itemType = TYPES . WEAPONS ;
2015-03-13 16:59:49 -04:00
return weapon ;
};
2015-03-17 17:49:35 -04:00
this . getUniqueArmorOrShield = function ( quality , options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
options . itemType = TYPES . ARMOR_AND_SHIELDS ;
options . uniqueType = ( options . type == 'armor' ? 'uniqueArmors' :
options . type == 'shield' ? 'uniqueShields' :
chance . pick ([ 'uniqueArmors' , 'uniqueShields' ])); //50-50 chance if type is not selected
2015-03-18 17:19:31 -04:00
options . disableRecursiveSpecials = options . disableRecursiveSpecials || false ;
2015-03-17 17:49:35 -04:00
var sources = options . sources ? options . sources : self . SOURCES ;
var source = selectSource ( sources , quality , 'Uniques' , options );
var item = self . getWeightedItem ( source . data [ TYPES . ARMOR_AND_SHIELDS ]. data [ options . uniqueType ], quality );
item . source = source . shortName ;
item . itemType = TYPES . ARMOR_AND_SHIELDS ;
return item ;
};
2015-03-13 16:59:49 -04:00
//options.type can be 'armor' or 'shield'
this . getArmorOrShield = function ( quality , options ) {
options = options ? options : {};
2015-03-17 17:49:35 -04:00
options . excludeSpecials = options . excludeSpecials || [];
options . maxSpecials = options . maxSpecials || 5 ;
2015-03-13 16:59:49 -04:00
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
2015-03-17 17:49:35 -04:00
var item = {
name : '' ,
type : '' ,
bonus : 0 ,
baseCost : 0 ,
cost : 0 ,
specials : [],
};
var roll = chance . d100 ();
var action = '' ;
if ( quality == QUALITIES . MINOR ) {
if ( roll <= 60 ) {
item . type = 'shield' ;
item . bonus = 1 ;
}
else if ( roll <= 80 ) {
item . type = 'armor' ;
item . bonus = 1 ;
}
else if ( roll <= 85 ) {
item . type = 'shield' ;
item . bonus = 2 ;
}
else if ( roll <= 87 ) {
item . type = 'armor' ;
item . bonus = 2 ;
}
else if ( roll <= 89 ) {
item . type = 'armor' ;
action = 'getUnique' ;
}
else if ( roll <= 91 ) {
item . type = 'shield' ;
action = 'getUnique' ;
}
else if ( roll <= 100 ) {
//9% special ability and roll again
action = 'getSpecial' ;
}
}
else if ( quality == QUALITIES . MEDIUM ) {
if ( roll <= 5 ) {
item . type = 'shield' ;
item . bonus = 1 ;
}
else if ( roll <= 10 ) {
item . type = 'armor' ;
item . bonus = 1 ;
}
else if ( roll <= 20 ) {
item . type = 'shield' ;
item . bonus = 2 ;
}
else if ( roll <= 30 ) {
item . type = 'armor' ;
item . bonus = 2 ;
}
else if ( roll <= 40 ) {
item . type = 'shield' ;
item . bonus = 3 ;
}
else if ( roll <= 50 ) {
item . type = 'armor' ;
item . bonus = 3 ;
}
else if ( roll <= 55 ) {
item . type = 'shield' ;
item . bonus = 4 ;
}
else if ( roll <= 57 ) {
item . type = 'armor' ;
item . bonus = 2 ;
}
else if ( roll <= 60 ) {
item . type = 'armor' ;
action = 'getUnique' ;
}
else if ( roll <= 63 ) {
item . type = 'shield' ;
action = 'getUnique' ;
}
else if ( roll <= 100 ) {
//37% special ability and roll again
action = 'getSpecial' ;
}
}
else if ( quality == QUALITIES . MAJOR ) {
if ( roll <= 8 ) {
item . type = 'shield' ;
item . bonus = 3 ;
}
else if ( roll <= 16 ) {
item . type = 'armor' ;
item . bonus = 3 ;
}
else if ( roll <= 27 ) {
item . type = 'shield' ;
item . bonus = 4 ;
}
else if ( roll <= 38 ) {
item . type = 'armor' ;
item . bonus = 4 ;
}
else if ( roll <= 49 ) {
item . type = 'shield' ;
item . bonus = 5 ;
}
else if ( roll <= 57 ) {
item . type = 'armor' ;
item . bonus = 5 ;
}
else if ( roll <= 60 ) {
item . type = 'armor' ;
action = 'getUnique' ;
}
else if ( roll <= 63 ) {
item . type = 'shield' ;
action = 'getUnique' ;
}
else if ( roll <= 100 ) {
//37% special ability and roll again
action = 'getSpecial' ;
}
}
if ( action == 'getUnique' ) {
options . type = item . type ;
var unique = self . getUniqueArmorOrShield ( quality , options );
item . name = unique . name ;
item . baseCost = unique . cost ;
item . url = unique . url ;
}
else if ( action == 'getSpecial' ) {
2015-03-18 17:19:31 -04:00
if ( item . specials . length === 0 || options . disableRecursiveSpecials === false ) {
item = self . getArmorOrShield ( quality , options );
var specialsBonus = _ . chain ( item . specials ). map ( function ( s ) { return s . bonus ; }). sum (). value ();
2015-03-17 17:49:35 -04:00
2015-03-18 17:19:31 -04:00
var specials = [];
var newSpecialsBonus = 0 ;
options . excludeSpecials = options . excludeSpecials . concat ( item . specials );
2015-03-18 16:59:24 -04:00
2015-03-18 17:19:31 -04:00
if ( item . bonus + specialsBonus < 10 ) {
var tries = 5 ;
//this has the opportunity to loop forever either if no match can be found
do {
specials = self . getSpecials ( quality , item . type , options );
newSpecialsBonus = _ . chain ( specials ). map ( function ( s ) { return s . bonus ; }). sum (). value ();
2015-03-17 17:49:35 -04:00
2015-03-18 17:19:31 -04:00
} while ( item . bonus + specialsBonus + newSpecialsBonus > 10 && -- tries > 0 );
if ( tries > 0 ) { //we found a match without running out of tries!
item . specials = item . specials . concat ( specials );
}
2015-03-17 17:49:35 -04:00
}
}
}
var totalBonus = item . bonus + _ . sum ( _ . map ( item . specials , function ( s ) { return s . bonsu ; }));
2015-03-18 17:21:56 -04:00
item . cost = item . baseCost + 1000 * Math . pow ( totalBonus , 2 );
2015-03-17 17:49:35 -04:00
item . print = function () {
var ret = 'Name: ' ;
ret += this . bonus > 0 ? ( '+' + this . bonus + ' ' ) : '' ;
ret += this . specials . length > 0 ? _ . map ( this . specials , function ( s ) { return s . name ;}). join ( ', ' ) + ' ' : '' ;
ret += this . name ? this . name : this . type ;
ret += ';' ;
ret += 'Cost: ' + this . cost + 'gp' ;
return ret ;
};
item . itemType = TYPES . ARMOR_AND_SHIELDS ;
2015-03-13 16:59:49 -04:00
return item ;
};
//can take clWeights and geWeights to weight the alignment probabilities
//clWeights are for [chaotic, neutral, lawful]
//geWeights are for [good, neutral, evil]
this . getAlignment = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var clWeights = options . clWeights ? options . clWeights : [ 1 , 1 , 1 ];
var geWeights = options . geWeights ? options . geWeights : [ 1 , 1 , 1 ];
var alignment = chance . weighted ([ 'Chaotic' , 'Neutral' , 'Lawful' ], clWeights ) +
' ' +
chance . weighted ([ 'Good' , 'Neutral' , 'Evil' ], geWeights );
if ( alignment == 'Neutral Neutral' ) {
alignment = 'True Neutral' ;
}
return alignment ;
};
2015-03-16 16:18:46 -04:00
//options.cl can be Chaotic, Neutral, or Lawful
//options.ge can be Good, Neutral, or Evil
this . getDeity = function ( options ) {
options = options ? options : {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var cl = options . cl || chance . pick ([ 'Chaotic' , 'Neutral' , 'Lawful' ]);
var ge = options . ge || chance . pick ([ 'Good' , 'Neutral' , 'Evil' ]);
var deity = {};
deity . name = chance . pick ( self . Deities . data [ cl ][ ge ]);
deity . alignment = ( cl == 'Neutral' && ge == 'Neutral' ) ? 'True Neutral' : ( cl + ' ' + ge );
return deity ;
};
//baseCost is the base cost of the item the intelligence is being made for.
//This directly affects the intelligence's ego score!
//baseCost also directly affects the probability of the number of special abilities an intelligence will have
//options.numPowers
//options.purposeChance
//options.numDedicatedPowers
this . getIntelligence = function ( baseCost , options ) {
baseCost = baseCost || 0 ;
options = options || {};
var chance = ( self . chance ? self . chance : options . seed ? new Chance ( options . seed ) : new Chance ());
var i = {}; //intelligence object. we'll be referencing this a LOT
//***BASE VALUES***
i . cost = 500 ; //base starting cost
i . ego = 0 ;
//Base cost ego modifier
switch ( true ) {
case ( baseCost <= 1000 ) : i . ego += 0 ; break ;
case ( baseCost <= 5000 ) : i . ego += 1 ; break ;
case ( baseCost <= 10000 ) : i . ego += 2 ; break ;
case ( baseCost <= 20000 ) : i . ego += 3 ; break ;
case ( baseCost <= 50000 ) : i . ego += 4 ; break ;
case ( baseCost <= 100000 ) : i . ego += 6 ; break ;
case ( baseCost <= 200000 ) : i . ego += 8 ; break ;
case ( baseCost > 200000 ) : i . ego += 12 ; break ;
}
//***ITEM ABILITY STATS***
var statScores = [
{ 'score' : 10 , 'costMod' : 0 , 'egoMod' : 0 },
{ 'score' : 11 , 'costMod' : 200 , 'egoMod' : 0 },
{ 'score' : 12 , 'costMod' : 500 , 'egoMod' : 1 },
{ 'score' : 13 , 'costMod' : 700 , 'egoMod' : 1 },
{ 'score' : 14 , 'costMod' : 1000 , 'egoMod' : 2 },
{ 'score' : 15 , 'costMod' : 1400 , 'egoMod' : 2 },
{ 'score' : 16 , 'costMod' : 2000 , 'egoMod' : 3 },
{ 'score' : 17 , 'costMod' : 2800 , 'egoMod' : 3 },
{ 'score' : 18 , 'costMod' : 4000 , 'egoMod' : 4 },
{ 'score' : 19 , 'costMod' : 5200 , 'egoMod' : 4 },
{ 'score' : 20 , 'costMod' : 8000 , 'egoMod' : 5 },
];
var statWeights = [ 10 , 10 , 20 , 10 , 20 , 5 , 5 , 5 , 5 , 5 , 5 ];
i . stats = {};
[ 'intelligence' , 'wisdom' , 'charisma' ]. forEach ( function ( stat ) {
var score = chance . weighted ( statScores , statWeights );
i . stats [ stat ] = score . score ;
i . cost += score . costMod ;
i . ego += score . egoMod ;
});
i . alignment = self . getAlignment ( options );
//***ITEM COMMUNICATION AND SENSES***
//get communication ability for the item.
var communicationTypes = [
{ 'description' : 'Empathy' , 'costMod' : 0 , 'egoMod' : 0 },
{ 'description' : 'Speech' , 'costMod' : 500 , 'egoMod' : 0 },
{ 'description' : 'Telepathy' , 'costMod' : 1000 , 'egoMod' : 1 },
];
var communicationWeights = [ 3 , 4 , 3 ]; //There was no roll table so I made this up
var communication = chance . weighted ( communicationTypes , communicationWeights );
//get senses for the item. there was no roll table so I made this up
var basicSenses = [
{ 'description' : 'Senses (30 ft.)' , 'costMod' : 0 , 'egoMod' : 0 },
{ 'description' : 'Senses (60 ft.)' , 'costMod' : 500 , 'egoMod' : 0 },
{ 'description' : 'Senses (120 ft.)' , 'costMod' : 1000 , 'egoMod' : 0 },
];
var basicSensesWeights = [ 5 , 3 , 2 ]; //There was no roll table so I made this up
var basicSense = chance . weighted ( basicSenses , basicSensesWeights );
//get special senses for the item.
var specialSenses = [
{ 'description' : '' , 'costMod' : 0 , 'egoMod' : 0 },
{ 'description' : 'Darkvision' , 'costMod' : 500 , 'egoMod' : 0 },
{ 'description' : 'Blindsense' , 'costMod' : 5000 , 'egoMod' : 1 },
{ 'description' : 'Read languages' , 'costMod' : 1000 , 'egoMod' : 1 },
{ 'description' : 'Read magic' , 'costMod' : 2000 , 'egoMod' : 1 },
];
//There was no roll table so I made this up and limited it to only 1 special per item
var specialSensesWeights = [ 50 , 20 , 5 , 15 , 10 ];
var specialSense = chance . weighted ( specialSenses , specialSensesWeights );
i . communication = communication . description ;
i . senses = basicSense . description + ( specialSense . description ? ', ' + specialSense . description : '' );
i . cost += communication . costMod + basicSense . costMod + specialSense . costMod ;
i . ego += communication . egoMod + basicSense . egoMod + specialSense . egoMod ;
//***ITEM POWERS***
//Get item powers!
var powerCountWeights = [ 1 , 1 , 1 , 1 , 1 ];
//The probability for more powers is directly related to the base cost of the item
//I made up these roll weights. references the same basecost as the basic ego modifier
switch ( true ) {
2015-03-17 17:49:35 -04:00
case ( baseCost <= 1000 ) : powerCountWeights = [ 6 , 4 , 0 , 0 , 0 ]; break ;
case ( baseCost <= 5000 ) : powerCountWeights = [ 3 , 4 , 3 , 0 , 0 ]; break ;
case ( baseCost <= 10000 ) : powerCountWeights = [ 2 , 3 , 4 , 1 , 0 ]; break ;
case ( baseCost <= 20000 ) : powerCountWeights = [ 1 , 3 , 4 , 2 , 0 ]; break ;
case ( baseCost <= 50000 ) : powerCountWeights = [ 0 , 2 , 4 , 3 , 1 ]; break ;
case ( baseCost <= 100000 ) : powerCountWeights = [ 0 , 1 , 3 , 4 , 2 ]; break ;
case ( baseCost <= 200000 ) : powerCountWeights = [ 0 , 0 , 2 , 5 , 3 ]; break ;
case ( baseCost > 200000 ) : powerCountWeights = [ 0 , 0 , 0 , 6 , 4 ]; break ;
2015-03-16 16:18:46 -04:00
}
var powerWeights = _ . map ( self . ItemPowers , function ( p ) { return p . weight ; });
i . powers = [];
var reSkill = /GET_ITEM_SKILL/ ;
var reSpell = /GET_SPELL_LEVEL_(.)/ ; //for spell levels 0-9
//Another option would be to do this with _.sample but it does not support weights
_ . times (( options . numPowers || chance . weighted ( _ . range ( 1 , 6 ), powerCountWeights )), function () {
var power = {};
do {
power = _ . clone ( chance . weighted ( _ . clone ( self . ItemPowers ), powerWeights ));
if ( reSkill . test ( power . description )) {
power . description = power . description . replace ( reSkill , chance . pick ( self . ItemSkills ). name );
}
else if ( reSpell . test ( power . description )) {
var spellLevel = reSpell . exec ( power . description )[ 1 ];
power . spell = self . getSpell ( spellLevel , options ); //add the spell onto the power for later reference
power . description = power . description . replace ( reSpell , power . spell . name );
}
} while ( _ . findWhere ( i . powers , { 'description' : power . description }) && ! power . repeatable ); //power exists in item powers and is not repeatable
if ( _ . findWhere ( i . powers , { 'description' : power . description })) { //if we selected an existing but repeatable power
//add the new power values to the old power
var oldPower = _ . findWhere ( i . powers , { 'description' : power . description });
oldPower . costMod += power . costMod ;
oldPower . egoMod += power . egoMod ;
oldPower . value += power . value ;
}
else {
i . powers . push ( power );
}
});
//Set for the next section
var bonusLanguages = 0 ;
//Finalize powers
i . powers . forEach ( function ( p ) {
p . description = p . description . replace ( /#/ , p . value );
i . ego += p . egoMod ;
i . cost += p . costMod ;
2015-03-17 17:49:35 -04:00
if ( /Linguistics/i . test ( p . description )) { //check if we got any ranks in linguistics
2015-03-16 16:18:46 -04:00
bonusLanguages += p . value ;
}
//clean up the power
delete p . value ;
delete p . egoMod ;
delete p . costMod ;
delete p . weight ;
delete p . repeatable ;
//final power should only have description and spell
});
//***ITEM BONUS LANGUAGES***
i . languages = [ 'Common' ]; //ALL intelligent items know Common
//extra languages are based on intelligence and ranks in Linguistics
bonusLanguages += Math . floor (( i . stats . intelligence - 10 ) / 2 );
//pick is safe even if we have more bonusLanguages than exist
i . languages . concat ( chance . pick ( self . Languages . data , bonusLanguages ));
//***ITEM PURPOSE***
//there was NO roll chart for determining if an item has a purpose so I made this up
//I figure 30% of the time sounds good
i . purpose = '' ;
i . dedicatedPowers = [];
if ( chance . d100 () <= ( options . purposeChance || 30 )) {
var reBane = /GET_BANE/ ;
var reHumOrOut = /GET_HUMANOID_OR_OUTSIDER_TYPE/ ;
var reDeity = /GET_DEITY/ ;
//get purpose
var purpose = chance . weighted ( _ . clone ( self . ItemPurposes ),
_ . map ( self . ItemPurposes , function ( p ) { return p . weight ; }));
i . purpose = purpose . description ;
i . ego += purpose . egoMod ;
if ( reBane . test ( i . purpose )) {
i . purpose = i . purpose . replace ( reBane , self . getBane ( options ));
}
else if ( reHumOrOut . test ( i . purpose )) {
i . purpose = i . purpose . replace ( reHumOrOut ,
chance . pick ([ 'Humanoids (' + self . getHumanoid ( options ) + ')' ,
'Outsiders (' + self . getOutsider ( options ) + ')' ]));
}
else if ( reDeity . test ( i . purpose )) {
var deity = self . getDeity ( options );
i . purpose = i . purpose . replace ( reDeity , deity . name + ' (' + deity . alignment + ')' );
}
//console.log(i.purpose.description);
2015-03-17 17:49:35 -04:00
i . dedicatedPowers = [];
2015-03-16 16:18:46 -04:00
//I decided that purposed items get between 1-3 dedicated powers, heavily leaning on 1
var numDedicatedPowers = options . numDedicatedPowers || chance . weighted ([ 1 , 2 , 3 ], [ 7 , 2 , 1 ]);
_ . times ( numDedicatedPowers , function () {
var power = {};
do {
power = _ . clone ( chance . weighted ( self . ItemDedicatedPowers ,
_ . map ( self . ItemDedicatedPowers , function ( p ) { return p . weight ; })));
if ( reSpell . test ( power . description )) { //as in the normal powers
var spellLevel = reSpell . exec ( power . description )[ 1 ];
power . spell = self . getSpell ( spellLevel , options ); //add the spell onto the power for later reference
power . description = power . description . replace ( reSpell , power . spell . name );
}
//don't allow for duplicate dedicated powers
} while ( _ . findWhere ( i . dedicatedPowers , { 'description' : power . description }));
//finalize dedicated power
i . cost += power . costMod ;
i . ego += power . egoMod ;
delete power . costMod ;
delete power . egoMod ;
delete power . weight ;
i . dedicatedPowers . push ( power );
});
}
//***PRINT FUNCTION***
i . print = function () {
var ret = '' ;
ret += 'Alignment: ' + this . alignment + ';' ;
ret += 'Stats: Ego ' + this . ego + ', Int ' + this . stats . intelligence + ', ' +
'Wis ' + this . stats . wisdom + ', ' +
'Cha ' + this . stats . charisma + ';' ;
ret += 'Communication: ' + this . communication + ' (' + this . languages . join ( ',' ) + ');' ;
ret += 'Senses: ' + this . senses + ';' ;
ret += 'Powers: ' + _ . map ( this . powers , function ( p ) { return p . description ; }). join ( ', ' ) + ';' ;
if ( this . purpose ) {
ret += 'Purpose: ' + this . purpose + ';' ;
ret += 'Dedicated Powers: ' + _ . map ( this . dedicatedPowers , function ( p ) { return p . description ; }). join ( ', ' ) + ';' ;
}
ret += 'Cost: ' + i . cost + 'gp' ;
return ret ;
};
return i ; //object intelligence
};
2015-03-11 16:00:14 -04:00
};