diff --git a/code/redeeming.js b/code/redeeming.js index 5a6cc4b9..4b9e6a2b 100644 --- a/code/redeeming.js +++ b/code/redeeming.js @@ -1,101 +1,231 @@ // REDEEMING ///////////////////////////////////////////////////////// -window.REDEEM_RES_LONG = {'RES_SHIELD' : 'Portal Shield', - 'EMITTER_A' : 'Resonator', - 'EMP_BURSTER' : 'XMP Burster', - 'POWER_CUBE' : 'Power Cube'}; +/* Resource type names mapped to actual names and abbreviations. + * Add more here if necessary. + */ +window.REDEEM_RESOURCES = { + RES_SHIELD: {long: 'Portal Shield', short: 'SH'}, + EMITTER_A: {long: 'Resonator', short: 'R'}, + EMP_BURSTER: {long: 'XMP Burster', short: 'X'}, + POWER_CUBE: {long: 'Power Cube', short: 'C'} +}; -window.REDEEM_RES_SHORT = {'RES_SHIELD' : 'S', - 'EMITTER_A' : 'R', - 'EMP_BURSTER' : 'X', - 'POWER_CUBE' : 'C'}; +/* Redemption errors. Very self-explanatory. + */ +window.REDEEM_ERRORS = { + ALREADY_REDEEMED: 'The passcode has already been redeemed.', + ALREADY_REDEEMED_BY_PLAYER : 'You have already redeemed this passcode.', + INVALID_PASSCODE: 'This passcode is invalid.' +}; -window.REDEEM_ERRORS = {'ALREADY_REDEEMED' : 'The passcode has already been redeemed.', - 'ALREADY_REDEEMED_BY_PLAYER' : 'You have already redeemed this passcode.', - 'INVALID_PASSCODE' : 'This passcode is invalid.'}; +/* These are HTTP status codes returned by the redemption API. + * TODO: Move to another file? Use more generally across IITC? + */ +window.REDEEM_STATUSES = { + 429: 'You have been rate-limited by the server. Wait a bit and try again.', + 500: 'Internal server error' +}; -window.REDEEM_STATUSES = {429 : 'You have been rate-limited by the server. Wait a bit and try again.'}; +/* Encouragement for people who got it in. + * Just for fun. + */ +window.REDEEM_ENCOURAGEMENT = [ + "Passcode accepted!", + "Access granted.", + "Asset transfer in progress.", + "Well done, Agent.", + "Make the " + {'RESISTANCE' : 'Resistance', 'ALIENS' : 'Enlightened'}[PLAYER.team] + " proud!" +]; + +/* Redemption "handlers" handle decoding and formatting for rewards. + * + * Redemption "decoders" are used for returning the primary attribute (key) from + * different types of items. Pretty self-explanatory. + * + * Redemption "formatters" are used for formatting specific types of password rewards. + * Right now, Ingress has resourceWithLevels (leveled resources) and modResource (mods). + * Resources with levels have levels, and mods have rarity. Format them appropriately. + */ +window.REDEEM_HANDLERS = { + 'resourceWithLevels' : { + decode: function(type, resource) {return resource.level;}, + format: function(acquired, level) { + var prefix = ''; + var suffix = ''; + return { + table: '' + prefix + 'L' + level + suffix + '' + acquired.name.long + ' [' + acquired.count + ']', + html: acquired.count + '@' + acquired.name.short + prefix + level + suffix, + plain: acquired.count + '@' + acquired.name.short + level + }; + } + }, + 'modResource' : { + decode: function(type, resource) {return resource.rarity;}, + format: function(acquired, rarity) { + var prefix = ''; + var suffix = ''; + var abbreviation = rarity.split('_').map(function (i) {return i[0];}).join(''); + return { + table: '' + prefix + abbreviation + suffix + '' + acquired.name.long + ' [' + acquired.count + ']', + html: acquired.count + '@' + prefix + abbreviation + suffix, + plain: acquired.count + '@' + abbreviation + }; + } + }, + 'default' : { + decode: function(type, resource) {return 'UNKNOWN';}, + format: function(acquired, group) { + return { + table: '+' + acquired.name.long + ' [' + acquired.count + ']', + html: acquired.count + '@' + acquired.name.short, + plain: acquired.count + '@' + acquired.name.short + }; + } + } +}; + +/* Redemption "hints" hint at what an unknown resource might be from its object properties. + */ +window.REDEEM_HINTS = { + level: 'resourceWithLevels', + rarity: 'modResource' +}; window.handleRedeemResponse = function(data, textStatus, jqXHR) { + var passcode = this.passcode, to_alert, to_log; + if(data.error) { - // Errors are now in window.REDEEM_ERRORS. - var error = window.REDEEM_ERRORS[data.error] || 'There was a problem redeeming the passcode. Try again?'; - - // Show an alert and add a console log - alert('' + data.error + '\n' + error); - console.log(this.passcode + ' => [ERROR] ' + data.error); + to_alert = '' + data.error + '
' + (window.REDEEM_ERRORS[data.error] || 'There was a problem redeeming the passcode. Try again?'); + to_log = '[ERROR] ' + data.error; } else if(data.result) { - // Successful redemption - var payload = {}; - var table_result = ['Passcode accepted!'], plain_result = []; - var table = '', plain = ''; - - // Get AP, XM, and other static quantities - var scores = [[parseInt(data.result.apAward), 'AP'], [parseInt(data.result.xmAward), 'XM']]; - for(var i in scores) { - if(scores[i][0] > 0) { - table_result.push('+' + scores[i][0] + ' ' + scores[i][1] + ''); - plain_result.push(scores[i][0] + ' ' + scores[i][1]); - } - } + var payload = {}; + var encouragement = window.REDEEM_ENCOURAGEMENT[Math.floor(Math.random() * window.REDEEM_ENCOURAGEMENT.length)]; + var inferred = []; + var results = { + 'table' : ['' + encouragement + ''], + 'html' : [], + 'plain' : [] + }; // Track frequencies and levels of items - for(var i in data.result.inventoryAward) { - var acquired = data.result.inventoryAward[i][2], primary, secondary, type; - if(acquired.modResource) { - primary = acquired.modResource.resourceType; - secondary = acquired.modResource.rarity; - type = 'mod'; - } else if(acquired.resourceWithLevels) { - primary = acquired.resourceWithLevels.resourceType; - secondary = parseInt(acquired.resourceWithLevels.level); - type = 'leveled'; - } + $.each(data.result.inventoryAward, function (award_idx, award) { + var acquired = award[2], handler, type, key, name; - payload[primary] = payload[primary] || {}; - payload[primary][secondary] = payload[primary][secondary] || {}; - payload[primary][secondary].type = payload[primary][secondary].type || type; - payload[primary][secondary].count = payload[primary][secondary].count || 0; - payload[primary][secondary].count += 1; + // The "what the heck is this item" heuristic + $.each(acquired, function (taxonomy, resource) { + if('resourceType' in resource) { + if(taxonomy in window.REDEEM_HANDLERS) { + // Cool. We know how to directly handle this item. + handler = { + functions: window.REDEEM_HANDLERS[taxonomy], + taxonomy: taxonomy, + processed_as: taxonomy + }; + } else { + // Let's see if we can get a hint for how we should handle this. + $.each(resource, function (resource_key, resource_value) { + if(resource_key in window.REDEEM_HINTS) { + // We're not sure what this item is, but we can process it like another item + handler = { + functions: (window.REDEEM_HANDLERS[window.REDEEM_HINTS[resource_key]] || window.REDEEM_HANDLERS['default']), + taxonomy: taxonomy, + processed_as: window.REDEEM_HINTS[resource_key] + }; + return false; + } + return true; + }); + + // Fall back to the default handler if necessary + handler = handler || { + functions: window.REDEEM_HANDLERS['default'], + taxonomy: taxonomy, + processed_as: 'default' + }; + } + + // Collect the data that we know + type = resource.resourceType; + key = handler.functions.decode(type, resource); + name = window.REDEEM_RESOURCES[type] || {long: type + '*', short: type[0] + '*'}; + + // Decide if we inferred this resource + if(!(type in window.REDEEM_RESOURCES) || handler.taxonomy !== handler.processed_as) { + inferred.push({type: type, key: key, handler: handler}); + } + return false; + } + return true; + }); + + // Update frequencies + payload[type] = payload[type] || {}; + payload[type][key] = payload[type][key] || {}; + payload[type][key].handler = payload[type][key].handler || handler; + payload[type][key].type = payload[type][key].type || type; + payload[type][key].name = payload[type][key].name || name; + payload[type][key].count = payload[type][key].count || 0; + payload[type][key].count += 1; + }); + + // Get AP, XM, and other static quantities + $.each([{label: 'AP', award: parseInt(data.result.apAward)}, {label: 'XM', award: parseInt(data.result.xmAward)}], function(idx, val) { + if(val.award > 0) { + var formatted = val.award + ' ' + val.label; + results.table.push('+' + formatted + ''); + results.html.push(formatted); + results.plain.push(formatted); + } + return true; + }); + + // Build the formatted results alphabetically + $.each(Object.keys(payload).sort(), function(type_idx, type) { + $.each(Object.keys(payload[type]).sort(), function(key_idx, key) { + var acquired = payload[type][key]; + $.each(acquired.handler.functions.format(acquired, key), function(format, string) { + results[format].push(string); + return true; + }); + return true; + }); + return true; + }); + + if (inferred.length > 0) { + results.table.push('**IITC had to guess!'); + results.table.push('**Submit a log including:'); + $.each(inferred, function (idx, val) { + var type = val.type + ':' + val.key, taxonomy = val.handler.taxonomy + ' =~ ' + val.handler.processed_as; + results.table.push('!' + type + ''); + results.table.push('!' + taxonomy + ''); + console.log(passcode + ' => [INFERRED] ' + type + ' :: ' + taxonomy); + }); } - // Build the table and plaintext arrays - var keys = Object.keys(payload).sort(); - for(var k in keys) { - var primary = payload[keys[k]], long_name = window.REDEEM_RES_LONG[keys[k]] || keys[k], short_name = window.REDEEM_RES_SHORT[keys[k]] || '?'; - var table_array = [], plain_array = []; - for(var secondary in primary) { - var acquired = primary[secondary]; - var span_prefix = acquired.type === 'leveled' ? '' : ''; - var span_infix = acquired.type === 'leveled' ? secondary : secondary.split('_').map(function (i) {return i[0];}).join(''); - var span_suffix = '' - table_array.push('' + span_prefix + (acquired.type === 'leveled' ? 'L' : '') + span_infix + span_suffix + '' + long_name + ' [' + primary[secondary].count + ']'); - plain_array.push(primary[secondary].count + '@' + (acquired.type === 'leveled' ? short_name : '') + span_prefix + span_infix + span_suffix); - } - table_result.push(table_array.join('')); - plain_result.push(plain_array.join('/')); - } - - // Add more HTML tags - plain = '' + plain_result.join('/') + ''; - table_result.push('>>[plaintext]'); - table = '' + table_result.map(function(a) {return '' + a + '';}).join("\n") + '
'; + // Add table footers + results.table.push('>>' + encouragement + '
' + results.html.join('/') + '
') + + '\', true);" style="font-family: monospace;">[plaintext]'); // Display formatted versions in a table, plaintext, and the console log - alert(table, true); - console.log(this.passcode + ' => ' + $(plain).text()); + to_alert = '' + results.table.map(function(a) {return '' + a + '';}).join("\n") + '
'; + to_log = results.plain.join('/'); } + + alert(to_alert, true); + console.log(passcode + ' => ' + to_log); } window.setupRedeem = function() { $("#redeem").keypress(function(e) { - if((e.keyCode ? e.keyCode : e.which) != 13) return; + if((e.keyCode ? e.keyCode : e.which) !== 13) return; var data = {passcode: $(this).val()}; window.postAjax('redeemReward', data, window.handleRedeemResponse, function(response) { - var extra = '' + var extra = ''; if(response.status) { extra = (window.REDEEM_STATUSES[response.status] || 'The server indicated an error.') + ' (HTTP ' + response.status + ')'; } else {