Settings

Blockchain
Network
Unit
Language
Theme
Sound New Block

Transaction

ade5ef63ddcf634883de05e8a629c24565aa944d8186704bdcb86d33b1a0b491
Timestamp (utc)
2021-02-16 23:52:28
Fee Paid
0.00016309 BSV
(
0.00993408 BSV
-
0.00977099 BSV
)
Fee Rate
501.8 sat/KB
Version
1
Confirmations
319,033
Size Stats
32,479 B

3 Outputs

Total Output:
0.00977099 BSV
  • jrun cryptofightsMÚ}{"in":0,"ref":["f49c62c6608aa9689484b982ec570692e774c2ca92cef393b6008a3f5bdd7b19_o1","4a4466c7a9318d0558bbcf5d6b6a23fad6df493a37375d12cc5a2d0f93ebfb10_o1","0d8e2a202b9823beab6729eaa47b72d4d2636186f46078ca788c530856d0784a_o1","16202368f2ba724378da3fd34954550ca9a1d8fe5387f0c49e851cfc3c5b38f4_o1","1877fbd129247e2b3c6b233cf3fe964a8c4353482af3274b6414230152af9733_o1","4fb2e7a36125091d4c830381a37bef34e6dc875b690ac6ef31abb1a932d6682d_o1","874bf129456bd6fc54864ce7bac0896035caa89acb15d89cb28d6f6fbdda55f5_o1","507b9cffbeb94c2386e054f3d63617f7b091e0bdb1619a99c3cd04e309be45f3_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1","f3aca3d7a8dc53b88e431a6914904561fdadca20bb8c224def4c075b64775b04_o1","589e107231b17445bc648ace9e1a852d201409a3ed42280c3f5ff4033c1a28ac_o1","b44ee8c523dfbdd1666957ea11b35c653ee7715da5c6a3767a84b35fcf1a6466_o1","f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1","210d039b352db6ea55b74ca5716e68b4b7e88ad3b2cfde40ccd7cbdc8c08fc74_o1","096ba166851d84493712a6b7cda08848a9c624cd2add8777d792e0b396194561_o1","7b60b31ae377843ed1fe262686d1a1c6f8eb00de2a5d8f60ad5cb24d48ac73b4_o1","5e553fdbb66fe95a539e9b14ab962b07692731004217009653a0c32c958ac485_o1","b733e02e820a036327ef0646bd212ea2bf8a03fcb091904107959fbb6f67bb33_o1","9e009aaff442f43e6e5cf5cdd669208f6c71a35d458d4ce6526935712325c44e_o1","28cb79d516414fbe34ac12ad127d7c8e4e265c1f08ca6efbbeb231bf31f38ed1_o1","cf5aa075ac7c2fc80ed387e566acd4c8f441dc5265a4445af1b5677ae6150e54_o1","4d15a94fce2e312fb5eb4db873b98df0216c9e3d29afa25843e88db6bf3a44b9_o1","92a92973f7e734a04016daf6d86170660f4bc2bc8df75b6332ceed95391fb46a_o1","04ccc99c8db035ec117fb7c8d45fc21e79b51764676d93ba939d119fcb0eed67_o1","447ea95c86bc08d24df3e58e6e3ec4ddd45d5b69cbe364bd4443b006fd68f13f_o1","3176c3d2bdca47d9cf07ba3736c5824332984c1344a576ec2cc155de591795cf_o1","62c93766e572e6be2beb73d1c88cfc8569289e6065638f4438cfc602f9513d33_o1","19974bb1d3f87b14e264f1bb37fdccbdd0d7051736b56f0aaa538944d995824a_o1","79ca2220bd8c6d8582b8c0d82486438a2de275f878a4a59452113bc589d34457_o1","8d8a15639e997c29890602c69c2e72f27d163cc5a606e19b7e9d64a935697939_o1","a80929e276440a0b27aa2ad33771382bc92afa397516043391e996b1ce45afc3_o1","d822030b65dd55f8b1a78260045798987faa78bccedc9928a66c45741688ba9c_o1","a205c23f0c4a966f47fd95150f051635d202ee69c221b2d8ccbe40474ed1a9ad_o1","7349f30549cfc5fa8d6f86477046c6dc3c91d7b2a8099078f1a67d348eae11cb_o1","97b20b5d2480ed4f98ff802f18b89cee88b086ce67af15edc16eb5517264d6c2_o1","4fe2bc4a60b95fa075433b88a7dfca4516d9248f594d441c19061f12c81319e8_o1","f956b7ae1a7e638e801574fd29681e90f0ad6c64531ace9e751c512f7dfcdd0e_o1","20d1198384cdc1608e983ca637eadd2b4051e34a2e635b3201a66b06378fa38b_o1","1d9cc801e8a3024c09042fc780980ca6a6f0a31862936511d4dafc7566f89447_o1","52b7d1e701094683ed1afbdc653ecf3cd77346f7fc088ab1b5d956b9253db906_o1","a1362b359611715c8c3f1dc7f60e9b00ef4acd25c3f29a37b50a397088ef9226_o1","ac4e5cc6395df7f2c2773de8d9f0dbc03124ff719b3a1ef1ff70cac72e192f5b_o1","8139ef4520d7ea3470fd628217ba54b58b6758408fba898cf2a9f88fd5625f62_o1","2d1bd8c108e95cd4c8645acb9b7fe64571e5a5c44ff32127bb87259aa2e067c7_o1"],"out":["b8dc832b62ce1be44447cdf42e209e02b382dee9e1a24c5cb21a84ed0b706bf7"],"del":[],"cre":["n2Bd4cWhEQK1aVjb1R7EBGV9mrw3etvSdC"],"exec":[{"op":"DEPLOY","data":["class ValidatorAgent extends Agent {\n async init(params) {\n this.messageHandlers.set('Act', this.onAct);\n this.messageHandlers.set('Forfeit', this.onForfeit);\n this.messageHandlers.set('BattleSigned', this.onBattleSigned);\n // this.messageHandlers.set('CreateBattle', this.onCreateBattle);\n\n const { messages, nonce } = params;\n console.log('Params:', params, JSON.stringify(messages), nonce);\n this.queueMessages = messages;\n \n // if(!message.from === QueueConfig.pubkey) throw new Error('Invalid Request');\n // const { messages, nonce } = message.payloadObj;\n try {\n const players = await this._buildPlayers(messages);\n this.players = players;\n \n console.log('Creating battle');\n const hashchain = this.generateHashchain(128);\n const id = Sha256.hashToHex(Agent.hexToBytes(hashchain[0]));\n console.log(`Battle ID ${id} created by Validator`);\n\n const t = this.wallet.createTransaction();\n console.log(`Invoked: this.wallet.createTransaction(). t = ${t}`);\n t.update(() => {\n new Battle(\n ValidatorConfig.address,\n this.constructor,\n players,\n id,\n this.wallet.now\n );\n });\n console.log(`Invoked: t.update(). t = ${t}`);\n let rawtx = await t.export({ pay: true, sign: true });\n console.log(`Invoked: t.export(). t = ${t} and rawtx = ${rawtx}`);\n t.rollback();\n console.log(`Invoked: t.rollback(). t = ${t}`);\n const tx = this.bsv.Tx.fromHex(rawtx);\n console.log(`Invoked: this.bsv.Tx.fromHex(rawtx). tx = ${tx}`);\n const sigs = tx.txIns.map(txIn => txIn.script.toString());\n console.log('Create Battle Sigs:', JSON.stringify(sigs));\n rawtx = tx.toHex();\n\n await this.storage.pipeline()\n .rpush(`sigs:${id}`, sigs)\n .expire(`sigs:${id}`, 300)\n .hmset(`msgs:${id}`, messages.reduce((acc, m) => {\n acc[m.from] = JSON.stringify(m);\n return acc;\n }, {}))\n .expire(`msgs:${id}`, 300)\n .rpush(`hash:${id}`, hashchain)\n .expire(`hash:${id}`, 1800)\n .set(`rawtx:${id}`, rawtx)\n .expire(`rawtx:${id}`, 1800)\n .exec();\n\n this.emit('subscribe', id);\n const context = [\n nonce, \n ...messages.map(m => {\n const message = new this.lib.SignedMessage(m);\n return message.id;\n })\n ];\n const message = this.wallet.buildMessage({\n to: players.map(player => player.pubkey),\n subject: 'SignBattle',\n context,\n payload: JSON.stringify({ id, rawtx })\n });\n await this.blockchain.sendMessage(message);\n console.log('SignBattle Message:', JSON.stringify(message));\n\n this.wallet.setTimeout(async () => {\n console.log('evalSignTimeout', id);\n if (!(await this.storage.exists(`sigs:${id}`))) return;\n console.log('Sign Timeout Exceeded', id);\n await this.blockchain.sendMessage(this.wallet.buildMessage({\n to: this.players.map(p => p.pubkey),\n subject: 'Requeue',\n }));\n this.emit('close');\n }, 60000);\n } catch (e) {\n console.error('Init Error:', e.message);\n const errMessage = this.wallet.buildMessage({\n to: messages.map(m => m.from),\n subject: 'Requeue',\n payload: e.message\n });\n await this.blockchain.sendMessage(errMessage);\n await Promise.all(messages.map(async message => {\n return this.constructor.sendExitQueueStatus(message, this.wallet, this.blockchain);\n }));\n throw e;\n }\n\n // Commenting out the call to dispose as it was causing issues with \n // sending messages to the blockchain while disposing old jigs - Ashish (1/19/2021)\n\n //this.dispose(); \n\n }\n\n async _buildPlayers(messages) {\n return Promise.all(messages.map((m) => this.constructor.loadPlayer(m, this.wallet)));\n }\n\n async onBattleSigned(message) {\n const {id, sigs} = message.payloadObj;\n\n const queueMessage = JSON.parse(await this.storage.hget(`msgs:${id}`, message.from));\n const { fighterLocation, itemLocations, coinLocations } = JSON.parse(queueMessage.payload);\n let rawtx = await this.storage.get(`rawtx:${id}`);\n\n const tx = this.bsv.Tx.fromHex(rawtx);\n await Promise.all(tx.txIns.map(async (txIn, i) => {\n const txid = this.lib.Buffer.from(txIn.txHashBuf).reverse().toString('hex');\n const loc = `${txid}_o${txIn.txOutNum}`;\n if (loc !== fighterLocation && !itemLocations.includes(loc) && !coinLocations.includes(loc)) return;\n await this.storage.lset(`sigs:${id}`, i, sigs[i]);\n }));\n const allSigs = await this.storage.lrange(`sigs:${id}`, 0, -1);\n\n for (const sig of allSigs) {\n if (!sig || sig === 'OP_0 OP_0') {\n console.log('Not fully signed', JSON.stringify(allSigs));\n await this.storage.expire(`sigs:${id}`, 300);\n return;\n }\n }\n\n allSigs.forEach((sig, i) => tx.txIns[i].setScript(this.bsv.Script.fromString(sig)));\n rawtx = tx.toHex();\n const txid = await this.blockchain.broadcast(rawtx);\n const opponents = await this.storage.hkeys(`msgs:${id}`);\n await this.storage.pipeline()\n .del(`sign:${id}`)\n .del(`msgs:${id}`)\n .del(`rawtx:${id}`)\n .exec();\n console.time('loadBattle');\n const payload = this.wallet.getTxPayload(rawtx);\n const locs = payload.out.map((x, i) => `${txid}_o${i + 1}`);\n while (locs.length) {\n const jig = await this.wallet.loadJig(locs.pop());\n if (jig.constructor.origin === Battle.origin) {\n this.battle = jig;\n break;\n }\n }\n if (!this.battle) throw new Error('Battle Create Failed');\n console.timeEnd('loadBattle');\n\n const battleData = JSON.stringify(this.battle.toObject());\n await this.blockchain.sendMessage(this.wallet.buildMessage({\n to: opponents,\n subject: 'BattleCreated',\n payload: battleData\n }));\n console.log('BattleCreated', battleData);\n\n this.battle.begin(this.wallet.now + this.constructor.initialTimeout);\n await this.battle.sync({ inner: false });\n\n await this.updateBattle();\n await Promise.all(this.queueMessages.map(async message => {\n return this.sendEnteredBattleStatus(message);\n }));\n }\n\n async onAct(message) {\n const timestamp = this.wallet.now;\n const { actionIndex } = message.payloadObj;\n const timeout = this.wallet.now + this.battle.rules.timeout;\n const random = await this.storage.lindex(`hash:${this.battle.id}`, this.battle.turnCount);\n console.time(`Act`);\n this.battle.resolve(\n random,\n timestamp,\n timeout,\n actionIndex,\n message.ts,\n message.sig\n );\n await this.battle.sync({ inner: false });\n console.timeEnd(`Act`);\n console.time(`Update Battle`);\n await this.updateBattle();\n console.timeEnd(`Update Battle`);\n }\n\n async evalActTimeout(location) {\n if(!this.battle || this.battle.location !== location) return;\n const { id, origin, timestamp, turnCount } = this.battle;\n const now = this.wallet.now;\n console.log('Resolving timeout', origin, now, timestamp);\n const random = await this.storage.lindex(`hash:${id}`, turnCount);\n const timeout = this.wallet.now + this.constructor.timeout;\n this.battle.resolve(random, now, timeout);\n await this.battle.sync({ inner: false });\n await this.updateBattle();\n }\n\n async onForfeit(message) {\n this.battle.forfeit({ ...message }, this.wallet.now);\n await this.battle.sync({ inner: false });\n await this.updateBattle();\n }\n\n async updateBattle() {\n console.log('Update Battle');\n const { id, location, origin, status, timeout } = this.battle;\n\n if (status === Constants.Status.Open) {\n const message = this.wallet.buildMessage({\n to: this.battle.battlePlayers\n .map(player => player.pubkey)\n .filter(pubkey => pubkey !== this.pubkey),\n subject: 'BattleUpdated',\n context: [origin],\n payload: JSON.stringify(this.battle.getState())\n });\n console.log('BattleUpdated', message.payload);\n await this.blockchain.sendMessage(message);\n\n const ms = timeout - this.wallet.now;\n console.log('TIMEOUT:', ms);\n this.wallet.setTimeout(() => this.evalActTimeout(location), ms > 0 ? ms : 0);\n } else {\n console.log('Battle complete:', origin);\n\n const message = this.wallet.buildMessage({\n to: this.battle.battlePlayers\n .map(player => player.pubkey)\n .filter(pubkey => pubkey !== this.pubkey),\n subject: 'BattleCompleted',\n context: [origin],\n payload: JSON.stringify({\n location,\n battleId: id\n })\n });\n console.log('BattleCompleted', message.payload);\n await this.blockchain.sendMessage(message);\n \n if (this.battle.owner === this.address) {\n this.battle.destroy();\n await this.battle.sync({ inner: false });\n }\n this.emit('close');\n }\n }\n\n static async loadPlayer(message, wallet) {\n console.log('Payload:', message.payload);\n console.time(`loadPlayer ${message.from}`);\n const { owner, fighterLocation, itemLocations, skills, coinLocations } = JSON.parse(message.payload);\n const [fighter, items] = await Promise.all([\n (async () => {\n const fighter = await wallet.loadJig(fighterLocation);\n if (!fighter) throw new Error(`Validator: Invalid Fighter: ${fighterLocation}`);\n await fighter.sync({inner: false});\n return fighter;\n })(),\n Promise.all(itemLocations.map(async (itemId, i) => {\n if (!itemId) return;\n const item = await wallet.loadJig(itemId);\n if (!item) throw new Error(`Invalid Item: ${i} - ${itemId}`);\n await item.sync({inner: false});\n return item;\n }))\n ]);\n\n const coins = await Promise.all(coinLocations.map(loc => wallet.loadJig(loc)));\n console.timeEnd(`loadPlayer ${message.from}`);\n return {\n pubkey: message.from,\n owner,\n fighter,\n items,\n skills: skills.map(skillType => Skills.library[skillType] || undefined),\n coins,\n tags: []\n };\n }\n\n static validatePlayer({ fighter, items, skills, coins }) {\n const { EquipSlot, ItemProperty } = Constants;\n if (!fighter || ![BotFighter.origin, Fighter.origin].includes(fighter.constructor.origin)) throw new Error('Invalid Fighter');\n if(this.fee) {\n const total = (coins || []).reduce((acc, coin) => acc + coin.amount);\n if(total < this.fee) throw new Error('Insufficient Fee');\n }\n\n expect(fighter).toBeDefined('Undefined fighter or bot');\n\n items.forEach((itemJig, i) => {\n if (!itemJig) return;\n if (!itemJig.item) throw new Error('No item');\n\n // TODO: Validate item is KronoItem and was issued by a valid mint\n const item = KronoClass.deepClone(itemJig.item);\n\n // Encumbrance rules\n if (item.properties[ItemProperty.Heavy]) {\n if (i !== EquipSlot.Mainhand) throw new Error(`Encumbered: Heavy weapon on slot ${i} instead of Mainhand`);\n if (items[EquipSlot.Offhand]) throw new Error(`Encumbered: Can't use Offhand weapon if already using Heavy weapon on Mainhand`);\n }\n if (item.properties[ItemProperty.Strenuous] && i === EquipSlot.Offhand) throw new Error(`Encumbered: Can't use Strenuous weapon on Offhand`);\n\n if (fighter.level < item.levelRequired) {\n throw new Error(`Item '${item.displayName}' equipped in slot slot ${i}, requires level ${item.levelRequired}, yet fighter is level ${fighter.level}`);\n }\n\n item.abilityScoreRequired.forEach((score, i) => {\n if (fighter.abilityScores[i] < score) {\n throw new Error(`Insufficient ability score: ${fighter.displayName} has ${fighter.abilityScores[i]} item ${item.displayName} requires ${score}`);\n }\n });\n if (i === EquipSlot.Offhand && (\n (items[EquipSlot.Mainhand] && items[EquipSlot.Mainhand].item.properties[ItemProperty.Heavy]) ||\n item.properties[ItemProperty.Strenuous]\n )) {\n throw new Error('Invalid Config');\n }\n });\n\n for (let skill of skills) {\n if (typeof skill === 'undefined') continue;\n if (!fighter.skills.includes(skill.skillType)) throw new Error(`Invalid Action: Fighter ${fighter.displayName} doesn't have ${skill.skillType}`);\n }\n }\n\n static joinBattle(validator, player) {\n const { EquipSlot } = Constants;\n const { pubkey, owner, items, fighter, skills, coins, tags } = player;\n this.validatePlayer(player);\n // const fighterData = fighter.toObject();\n fighter.auth();\n\n let fee;\n let coinLock;\n const coin = coins.pop();\n if(coin) coinLock = coin.owner;\n if(coins.length) {\n coin.combine(...coins);\n }\n if (coin) {\n fee = coin.send(validator, this.fee);\n }\n\n let mainhand = items[EquipSlot.Mainhand] && items[EquipSlot.Mainhand].item ? KronoClass.deepClone(items[EquipSlot.Mainhand].item) : null;\n const offhand = items[EquipSlot.Offhand] && items[EquipSlot.Offhand].item ? KronoClass.deepClone(items[EquipSlot.Offhand].item) : null;\n const armor = items[EquipSlot.Body] && items[EquipSlot.Body].item ? KronoClass.deepClone(items[EquipSlot.Body].item) : null;\n\n items.forEach(item => item && item.auth());\n\n return {\n pubkey,\n owner,\n coinLock,\n fighter,\n mainhand,\n offhand,\n armor,\n skills,\n coin: fee,\n tags: tags || []\n };\n }\n\n static async sendEnterQueueStatus(message, wallet, blockchain) {\n console.log('Sending Queue Status');\n const { fighterLocation } = JSON.parse(message.payload);\n const fighter = await wallet.loadJig(fighterLocation);\n const queueMessage = wallet.buildMessage({\n subject: 'QueueStatus',\n payload: `${fighter.displayName} has entered queue`\n });\n await blockchain.sendMessage(queueMessage);\n }\n\n static async sendExitQueueStatus(message, wallet, blockchain) {\n console.log('Sending Queue Status');\n const { fighterLocation } = JSON.parse(message.payload);\n const fighter = await wallet.loadJig(fighterLocation);\n const queueMessage = wallet.buildMessage({\n subject: 'QueueStatus',\n payload: `${fighter.displayName} has exitted queue`\n });\n await blockchain.sendMessage(queueMessage);\n }\n\n async sendEnteredBattleStatus(message) {\n console.log('Sending Queue Status');\n const { fighterLocation } = JSON.parse(message.payload);\n const fighter = await this.wallet.loadJig(fighterLocation);\n const queueMessage = this.wallet.buildMessage({\n subject: 'QueueStatus',\n payload: `${fighter.displayName} has entered battle`\n });\n await this.blockchain.sendMessage(queueMessage);\n }\n\n static issueRewards(dice, tier, recipient) {\n let rewards = [];\n const { Affixes, ItemTypes, RatingRangePerQuality, Tiers } = KronoClass.deepClone(Compendium);\n const { Ability, Bonus, DamageType, ItemTypeNames, ItemQuality, Bonuses } = KronoClass.deepClone(Constants);\n\n // Maximum number of affixes. We want to avoid weapons with too many affixes\n const affix_max = 5;\n\n const permutations = function permutations(array, size) {\n function p(t, i) {\n if (t.length === size) {\n result.push(t);\n return;\n }\n if (i + 1 > array.length) {\n return;\n }\n p(t.concat(array[i]), i + 1);\n p(t, i + 1);\n }\n\n var result = [];\n p([], 0);\n return result;\n };\n\n const isAffixSetValid = function is_affix_set_valid(affix_set) {\n let unique_bonuses = [];\n let affix;\n expect(affix_set).toBeDefined();\n for (affix of affix_set) {\n expect(affix.bonuses).toBeDefined();\n let bonus;\n for (bonus of Object.keys(affix.bonuses)) {\n if (unique_bonuses.includes(bonus)) {\n return false;\n }\n unique_bonuses.push(bonus);\n }\n }\n return true;\n };\n\n /**\n * Puts together a base_item and a set of affixes (named bonuses) to create an item\n */\n const compose = function compose_item(base_item, affix_set) {\n expect(affix_set).toBeDefined('Affix set not initialized');\n\n const qualify = function quality_item(item) {\n expect(RatingRangePerQuality.length).toBe(6);\n for (let i = 0; i < RatingRangePerQuality.length; i++) {\n const range = RatingRangePerQuality[i];\n const [lower, upper] = range;\n if (lower <= item.rating && item.rating <= upper) {\n return i;\n }\n }\n console.error(`failed to find ${item.rating}`);\n return null;\n };\n\n // Initialize the item with the data from the base_item\n let item = { ...base_item };\n let affixBonuses = affix_set.length > 0 ? affix_set.map(affix => affix.bonuses) : [0, 0, 0, 0, 0, 0, 0, 0];\n\n item.bonuses = Object.keys(Bonus).map(() => 0);\n for (const affixBonus of affixBonuses) {\n for (const key of Object.keys(affixBonus)) {\n item.bonuses[Bonuses[key]] = affixBonus[key];\n }\n }\n\n expect(item.bonuses).toBeDefined('Affix set not initialized');\n\n // Name the item\n item.displayName = ItemTypeNames[item.type];\n if (item.displayName == null) throw new Error(`Item has no name`);\n if (item.baseDamageType == null) throw new Error(`Item has no baseDamageType, ${JSON.stringify(item)}`);\n let prefixes = affix_set.filter(affix => affix.prefix);\n for (let prefix of prefixes) {\n item.displayName = `${prefix.name} ` + item.displayName;\n }\n let suffixes = affix_set.filter(affix => !affix.prefix);\n for (let i = 0; i < suffixes.length; i++) {\n item.displayName += (i == 0 ? ' of' : (i < suffixes.length - 1 ? ',' : ' and')) + ` ${suffixes[i].name}`;\n }\n\n // Calculate it's rating\n let affix_stacking_rating = affix_set.filter(affix => affix.rating > 0).length;\n item.rating += affix_set.map(affix => affix.rating).reduce((a, b) => a + b, 0) + affix_stacking_rating;\n\n // Give it a quality\n item.quality = qualify(item);\n\n expect(item).toBeDefined('Failed to generate and issue item reward');\n\n item.abilityScoreRequired = Object.keys(Ability).map(() => 1);\n\n // TODO: Populate with the right level for this quality\n item.levelRequired = 0;\n item.critChanceBonus = 1;\n item.damageBonus = Object.keys(DamageType).map(() => 0);\n item.damageReduction = Object.keys(DamageType).map(() => 0);\n\n return item;\n };\n\n // Build valid sets of affixes.\n // Some affix sets aren't valid, e.g. when 2 affixes give the same type of bonus (Fine and Exceptional for example)\n let affix_sets = [];\n for (let n = 0; n < affix_max; n++) {\n affix_sets = affix_sets.concat(permutations(Affixes, n));\n }\n affix_sets = affix_sets.filter(isAffixSetValid);\n\n\n // figure out how many items we're going to drop\n const n_drop_die = dice.roll(1, 100);\n console.log(`Number of drops die is ${n_drop_die}`);\n\n const n_drops = n_drop_die < 75 ? 1 : (n_drop_die < 99 ? 2 : 1);\n console.log(`Dropping ${n_drops} items`);\n\n // Determine the base items that we're going to drop\n const compendium_size = ItemTypes.length;\n let base_items = [];\n for (let i = 0; i < n_drops; i++) {\n base_items.push(ItemTypes[dice.roll(1, compendium_size) - 1]);\n }\n\n // Combine types with affixes\n console.time('Compose Items');\n let items = [];\n for (let base_item of base_items) {\n for (let affix_set of affix_sets) {\n items.push(compose(base_item, affix_set));\n }\n }\n console.timeEnd('Compose Items');\n\n\n // Sort items per quality : transform the array into a dictionary of lists keyed by Quality\n var groupBy = function (xs, key) {\n return xs.reduce(function (rv, x) {\n (rv[x[key]] = rv[x[key]] || []).push(x);\n return rv;\n }, {});\n };\n const items_per_quality = groupBy(items, 'quality');\n\n expect(tier).toBeLessThanOrEqualTo(Tiers.length);\n expect(Tiers[tier - 1]).toBeDefined('Undefined tier');\n const loot_table = Tiers[tier - 1].loot_table;\n expect(loot_table).toBeDefined('Loot table is null');\n\n // Drop items\n for (let n = 0; n < n_drops; n++) {\n // TODO =======> Grab this rate from the Compendium\n // const rate = [5, 15, 60, 15, 5];\n const rate_accum = loot_table.map((sum => value => sum += value)(0));\n const drop_die = dice.roll(1, 100);\n console.log(`Drop die is ${drop_die}`);\n\n // Find index in rate_accum where drop_die is lower\n let qualityIndex = rate_accum.findIndex(rate => drop_die < rate);\n if (qualityIndex == 5) {\n console.log(`Missing implementation for Legendary items, dropping epic instead`);\n qualityIndex--;\n }\n let quality = Object.keys(ItemQuality).filter(function (key) { return ItemQuality[key] === qualityIndex; })[0];\n\n let items = items_per_quality[qualityIndex];\n\n const base_item_name = ItemTypeNames[base_items[n].type];\n // TODO: Improve this:\n // Some base_items don't have poor variants (crossbow), give out a higher quality item\n if (items == null) {\n items = items_per_quality[qualityIndex + 1];\n const higher_quality = Object.keys(ItemQuality).filter(function (key) { return ItemQuality[key] === qualityIndex + 1; })[0];\n console.warn(`Dropping ${higher_quality} ${base_item_name} instead of ${quality}`);\n quality = higher_quality;\n }\n\n expect(items).toBeDefined(`No '${base_item_name}' of quality '${quality}' to drop, dropdie: ${drop_die}`);\n expect(items.length).toBeGreaterThan(0, `No items '${quality}' to drop`);\n // grab a random item for that quality\n const item_die = dice.roll(1, items.length);\n const item = items[parseInt(item_die) - 1];\n if (item == null) throw new Error('Item is null');\n expect(items).toBeDefined(`Item is null`);\n const reward = new KronoItem(item, recipient);\n rewards.push(reward);\n // console.log(JSON.stringify(item));\n }\n console.log('Rewards Issued');\n return rewards;\n }\n\n async dispose() {\n console.log('Disposing outdated jigs');\n const index = await this.wallet.loadJigIndex({ project: { value: false } });\n console.log('Index:', index.length);\n const deprecated = index\n .filter(data => !this.constructor.whitelist.includes(data.kind))\n .slice(0, 50);\n if (!deprecated.length) {\n console.log('No Deprecated Jigs');\n return;\n } else {\n console.log('Deprecated:', JSON.stringify(deprecated));\n }\n for (const j of deprecated) {\n try {\n const jig = await this.wallet.loadJig(j.location);\n await jig.sync({ inner: false });\n console.log('Disposing:', jig.constructor.name, jig.location);\n jig.destroy();\n await jig.sync({ forward: false });\n } catch (e) {\n console.error('Dispose Error:', e.message, e.stack);\n }\n }\n }\n\n static async preDeploy() {\n this.skills = this.deps.Skills.library;\n this.config = this.deps.ValidatorConfig;\n this.lobbies = [\n this.deps.Tier0Player,\n this.deps.Tier1Player,\n this.deps.Tier2Player,\n this.deps.Tier3Player,\n this.deps.Tier4Player,\n this.deps.Tier5Player,\n this.deps.Tier6Player,\n this.deps.Tier7Player\n ];\n\n this.whitelist = [\n this.deps.Battle.origin,\n this.deps.BotFighter.origin,\n this.deps.KronoItem.origin,\n ];\n }\n}",{"agentId":"validator","config":{"$jig":0},"deps":{"Agent":{"$jig":1},"Battle":{"$jig":2},"BotFighter":{"$jig":3},"Compendium":{"$jig":4},"Constants":{"$jig":5},"Fighter":{"$jig":6},"KronoClass":{"$jig":7},"KronoItem":{"$jig":8},"QueueConfig":{"$jig":9},"Sha256":{"$jig":10},"Skills":{"$jig":11},"ValidatorConfig":{"$dup":["1","config"]},"expect":{"$jig":12}},"fee":0,"hash":"727d25c5b8f7d870e3f07de9812f4ae52b0848e3c1764c3ae3c6aabd7a3ff79e","initialTimeout":60000,"lobbies":[{"$und":1},{"$und":1},{"$und":1},{"$und":1},{"$und":1},{"$und":1},{"$und":1},{"$und":1}],"lobby":0,"lootDropsPerTier":[{"maxQuality":3,"minQuality":1,"percent":75}],"reward":0,"sealed":false,"skills":{"0":{"$jig":13},"1":{"$jig":14},"2":{"$jig":15},"3":{"$jig":16},"4":{"$jig":17},"5":{"$jig":18},"6":{"$jig":19},"7":{"$jig":20},"8":{"$jig":21},"9":{"$jig":22},"10":{"$jig":23},"11":{"$jig":24},"12":{"$jig":25},"13":{"$jig":26},"14":{"$jig":27},"15":{"$jig":28},"16":{"$jig":29},"17":{"$jig":30},"18":{"$jig":31},"19":{"$jig":32},"20":{"$jig":33},"21":{"$jig":34},"22":{"$jig":35},"23":{"$jig":36},"24":{"$jig":37},"25":{"$jig":38},"26":{"$jig":39},"27":{"$jig":40},"31":{"$jig":41},"32":{"$jig":42},"33":{"$jig":43}},"tier":0,"timeout":45000,"whitelist":["0d8e2a202b9823beab6729eaa47b72d4d2636186f46078ca788c530856d0784a_o1","16202368f2ba724378da3fd34954550ca9a1d8fe5387f0c49e851cfc3c5b38f4_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1"]}]}]}
    https://whatsonchain.com/tx/ade5ef63ddcf634883de05e8a629c24565aa944d8186704bdcb86d33b1a0b491