// ==UserScript==
// @name UD Item Combiner, Organizer and Sorter
// @description Combines, organizes and sorts your inventory and calculates the number of shots you have in your weapons.
// @namespace http://aichon.com
// @include http://urbandead.com/map.cgi*
// @include http://www.urbandead.com/map.cgi*
// @exclude http://urbandead.com/map.cgi?logout
// @exclude http://www.urbandead.com/map.cgi?logout
// @version 0.0.1.20210919202127
// ==/UserScript==
/* Urban Dead Item Combiner, Organizer and Sorter
* v1.2.2
*
* Copyright (C) 2014 Bradley Sattem -- [email protected]
* Copyright (C) 2008 Ville Jokela -- [email protected]
* Copyright (C) 2005 Ryan Forsythe -- [email protected]
*
* Released under the terms of the GNU GPL V2, which can be found at http://www.gnu.org/copyleft/gpl.html
*
* Changes:
* 1.2.2:
* * added a "Special" section for items like the DSS Sat Phone that can't be altered in any way
* 1.2.1:
* * made several items that waste AP when used cease showing up in the inventory
* 1.2:
* * added NecroTech syringe to Science (a syringe is named like this if you lack NT skills)
* * unknown items should work properly now
* 1.1:
* * items matched with regular expressions (most of the decorations) didn't work. now they do
* 1.0:
* * complete rewrite to make compatible with Opera and Google Chrome
* * merged "Medical" category to "Science"
* * all carried radios are now shown entirely separately, so that each of their frequencies can be adjusted individually
* * syringes are no longer dumped to Unknown when the character doesn't know how to revive
* * christmas trees and lights added, though not tested
* * unrecognized items are preserved as they are, and not combined
* * "drop item" list is also sorted and duplicates removed
* 0.5: (skips over that other dude's obfuscated 0.4)
* * new category: decorations
* * shows total shots for pistol clips, similar to pistols
* * alphabetically sorts items inside categories
* * inventory header now looks same as without the script (also in 0.4)
* * shows encumbrance percentage (also in 0.4)
* * added flavour-melee weapons (also in 0.4)
* 0.3:
* * Added "kitchen knife" to weapons
* * Added "radio" to other items
* * Added "radio transmitter" to other items
* * Added "binoculars" to other items
* * Changed namespace
* * Fixed pattern matching bug, non ammo items with brackets(i.e. radio) should be displayed correctly now
* 0.2:
* * Added "portable generator" to other items.
* * Added newline fix (thx Kieren and Joshua).
* * Removed some newlines, fixed the namespace.
*/
var weaponList = [
'baseball bat',
'cricket bat',
'crowbar',
'fencing foil',
'fire axe',
'flare gun',
'golf club',
'hockey stick',
'length of pipe',
'kitchen knife',
'knife',
'pistol',
'pool cue',
'shotgun',
'ski pole',
'tennis racket',
];
var ammoList = [
'pistol clip',
'shotgun shell',
];
var bevList = [
'bottle of beer',
'bottle of wine',
];
var sciList = [
'DNA extractor',
'first-aid kit',
'NecroTech syringe', // without skills
'revivification syringe', // with skills
];
var otherList = [
'binoculars',
'book',
'crucifix',
'flak jacket',
'fuel can',
'GPS unit',
'mobile phone',
'newspaper',
'pair of wirecutters',
'poetry book',
'portable generator',
'radio',
'radio transmitter',
'spray can',
'toolbox',
'stale candy',
];
var specialList = [
'satellite phone'
];
var specialContainer = [];
var decoList = [
'antique mirror',
'blown-glass sculpture',
'terracotta statue',
'clay figurine',
'skull',
];
var wastedClicks = [
'fire axe',
'flak jacket',
'spray can',
'toolbox',
];
var rexList = [
{ rex: 'glass .*', cat: 'Decorations' },
{ rex: '.* painting', cat: 'Decorations' },
{ rex: '.* urn', cat: 'Decorations' },
{ rex: '.* vase', cat: 'Decorations' },
{ rex: '.* sculpture', cat: 'Decorations' },
{ rex: '.* skeleton', cat: 'Decorations' },
{ rex: 'stuffed .*', cat: 'Decorations' },
{ rex: '.* tapestry', cat: 'Decorations' },
{ rex: '.* lights', cat: 'Decorations' },
{ rex: '.* tree', cat: 'Decorations' },
];
var categoryHash = {
'Weapons': weaponList,
'Ammo': ammoList,
'Science': sciList,
'Beverages': bevList,
'Others': otherList,
'Decorations': decoList,
};
// the order in which the categories are printed
var categoryList = [
'Weapons',
'Ammo',
'Science',
'Beverages',
'Others',
'Decorations',
'Unknown',
];
// Unknown items are also uncombined
var uncombineList = {
'radio': true,
};
var calcShotsList = {
'pistol': { fromExtra: true },
'shotgun': { fromExtra: true },
'pistol clip': { multiplier: 6 },
};
function reorganizeInventory() {
var preInventory = findPreInventory();
if (!preInventory) // no inventory
return;
var itemHash = processItems(preInventory.nextSibling);
var categoryTable = categorizeItems(itemHash);
var table = genTable(categoryTable);
preInventory.parentNode.insertBefore(table, preInventory.nextSibling);
}
function findPreInventory() {
var pgraphs = document.evaluate('//td[@class="gp"]/p', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0; i < pgraphs.snapshotLength; i++) {
p = pgraphs.snapshotItem(i);
if (p.firstChild)
if (p.firstChild.textContent == 'Inventory (click to use):')
return p.nextSibling;
}
return null;
}
function processItems(form) {
var iHash = {}; // "itemname" -> { num: "n:o of occurences", command: "?command", extras: [ "<select>"/"<hidden radio stuff>"/"(ammo)" ] } associative array
while (isItem(form)) {
var input = form.firstChild;
var itemName = input.value;
if(!isSpecialItem(itemName)) {
if(!isWastedClick(itemName)) {
var info = iHash[itemName];
if (!info) {
info = {};
info.num = 0;
var matches = form.action.match(/\?(.*)$/);
if (matches) // not sure if this is needed. all items appear to have a use-code
info.command = matches[1];
info.extras = [];
}
info.num++;
info.extras.push(getExtra(form));
iHash[itemName] = info;
}
}
else
specialContainer.push(form);
var oldForm = form;
form = form.nextSibling.nextSibling; // skips over the ' ' between items
form.parentNode.removeChild(oldForm);
}
pruneNulls(iHash);
return iHash;
}
function isSpecialItem(itemName) {
for(var i = 0; i < specialList.length; i++)
if(itemName == specialList[i]) return true;
return false;
}
function isWastedClick(itemName) {
for(var i = 0; i < wastedClicks.length; i++)
if(itemName == wastedClicks[i]) return true;
return false;
}
function isItem(form) {
if (form.nodeName == 'FORM')
return true;
return false;
}
function getExtra(form) {
var frag = document.createDocumentFragment();
for (var i = 1; i < form.childNodes.length; i++)
frag.appendChild(form.childNodes[i].cloneNode(true));
if (frag.childNodes.length)
return frag;
else
return null;
}
function pruneNulls(iHash) {
for (var item in iHash) {
if (iHash[item].extras) {
oldExtras = iHash[item].extras;
newExtras = [];
while (oldExtras.length) {
var val = oldExtras.shift();
if (val)
newExtras.push(val);
}
if (newExtras.length)
iHash[item].extras = newExtras;
else
iHash[item].extras = null;
}
}
}
function categorizeItems(itemHash) {
var cHashHash = createCategoryHashHash();
for (var item in itemHash)
cHashHash[resolveCategory(item)][item] = itemHash[item];
return cHashHash;
}
function createCategoryHashHash() {
cHashHash = {};
for (var i in categoryList)
cHashHash[categoryList[i]] = {};
return cHashHash;
}
function resolveCategory(item) {
var i2cHash = createItemCategoryHash();
var cat = i2cHash[item];
if (cat)
return cat;
else
for (var i = 0; i < rexList.length; i++) {
rexItem = rexList[i];
var matches = item.match(rexItem.rex);
if (matches)
return rexItem.cat;
}
// found neither in hash nor regexes, thus Unknown
return 'Unknown';
}
function createItemCategoryHash() {
var i2cHash = {};
for (var catName in categoryHash) {
itemList = categoryHash[catName];
for (var i in itemList)
i2cHash[itemList[i]] = catName;
}
return i2cHash;
}
function genTable(categoryTable) {
var table = document.createElement('table');
for (var i in categoryList) {
var row = genRow(categoryTable[categoryList[i]], categoryList[i]);
if (row)
table.appendChild(row);
}
row = genSpecialRow("Special");
if (row)
table.appendChild(row);
return table;
}
function genRow(cat, name) {
var keys = getHashKeys(cat);
if (!keys.length)
return null;
var row = document.createElement('tr');
var title = document.createElement('td');
title.style.borderBottom = '3px solid #676';
var titletext = document.createTextNode(name);
title.appendChild(titletext);
row.appendChild(title);
var content = document.createElement('td');
content.style.borderBottom = '3px solid #676';
keys.sort();
for (var i in keys) {
var item = keys[i];
var add;
if (uncombineList[item] || name == 'Unknown')
add = uncombineItems(item, cat[item]);
else
add = genItem(item, cat[item]);
content.appendChild(add);
content.appendChild(document.createTextNode(' '));
for(var j in specialContainer) {
content.appendChild(specialContainer[j]);
content.appendChild(document.createTextNode(' '));
}
}
row.appendChild(content);
return row;
}
function genSpecialRow(name) {
if (!specialContainer.length)
return null;
var row = document.createElement('tr');
var title = document.createElement('td');
title.style.borderBottom = '3px solid #676';
var titletext = document.createTextNode(name);
title.appendChild(titletext);
row.appendChild(title);
var content = document.createElement('td');
content.style.borderBottom = '3px solid #676';
for(var j in specialContainer) {
content.appendChild(specialContainer[j]);
content.appendChild(document.createTextNode(' '));
}
row.appendChild(content);
return row;
}
function genItem(name, info) {
var form = document.createElement('form');
form.action = 'map.cgi?' + info.command;
form.method = 'POST';
form.className = 'a';
if (info.num > 1)
form.appendChild(document.createTextNode(info.num + ' \xD7 ')); // multiplication sign
var button = document.createElement('input');
button.setAttribute('class', 'm');
button.setAttribute('type', 'submit');
button.setAttribute('value', name);
form.appendChild(button);
if (calcShotsList[name])
form.appendChild(calcShots(info, calcShotsList[name]));
else if (info.extras)
form.appendChild(info.extras[0]);
return form;
}
function calcShots(info, def) {
var frag = document.createDocumentFragment();
var shots = 0;
if (def.fromExtra) {
var gunRex = /\((.)\)/;
var str = ' (';
info.extras.sort(sortExtras);
for (var i = 0; i < info.extras.length; i++) {
var matches = info.extras[i].childNodes[0].textContent.match(gunRex);
var n = 1 * matches[1];
shots += n;
str += n;
if (i + 1 != info.extras.length)
str += ', ';
}
str += ')';
frag.appendChild(document.createTextNode(str));
} else
shots = def.multiplier * info.num;
frag.appendChild(document.createTextNode(' \u2192 ' + shots)); // right arrow
return frag;
}
function sortExtras(a, b) {
if (a.textContent < b.textContent)
return -1;
else if (a.textContent == b.textContent)
return 0;
else
return 1;
}
function uncombineItems(name, info) {
var frag = document.createDocumentFragment();
if (info.extras) {
info.num = 1;
for (var i = 0; i < info.extras.length; i++) {
info.extras[0] = info.extras[i];
var item = genItem(name, info);
frag.appendChild(item);
if (i != (info.extras.length - 1))
frag.appendChild(document.createTextNode(' '));
}
} else { // pruneNulls() may remove .extras from unknown items
for (var i = 0; i < info.num; i++) {
var item = genItem(name, info);
frag.appendChild(item);
if (i != (info.num - 1))
frag.appendChild(document.createTextNode(' '));
}
}
return frag;
}
function getHashKeys(hash) {
var keys = [];
for (var key in hash)
keys.push(key);
return keys;
}
function processDropList() {
var dropList = document.evaluate('//select[@name="drop"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
var dl = [];
if(dropList != null) {
for (var i = 0; i < dropList.childNodes.length; i++)
dl.push(dropList.childNodes[i].cloneNode(true));
while (dropList.hasChildNodes())
dropList.removeChild(dropList.firstChild);
var pruned = pruneDuplicates(dl.sort(sortOptions));
for (var i in pruned)
dropList.appendChild(pruned[i]);
}
}
function sortOptions(a, b) {
var r;
var astr = a.firstChild.textContent;
var bstr = b.firstChild.textContent;
if (astr < bstr)
r = -1;
else if (astr == bstr)
r = 0;
else
r = 1;
return r;
}
function pruneDuplicates(dl) {
var pruned = [ dl[0] ];
for (var i = 1; i < dl.length; i++) {
if (dl[i].value != pruned[pruned.length-1].value)
pruned.push(dl[i]);
}
return pruned;
}
reorganizeInventory();
processDropList();