Transaction

007070e69a3655887edb3eed06dc195223e449407b64dab309069d2508451155
( - )
208,153
2021-01-13 05:41:42
1
31,879 B

3 Outputs

Total Output:
  • jrun cryptofightsMƒ{{"in":0,"ref":["6137cc92426478e6aab4036389d43942774e2b4b707ffb3e5bd6edfeb5f759fb_o1","b3878396e872bf4dcffc6df0f4f1469a63a970182fe40c9020630f83c2c21211_o1","419f1a052198cc8464d9816e33c6ea9ec9025877b5a521df626fdab97a15fc2a_o1","ed84a4072ce62b8b017bb1e853ee740341b6b2a7ed9229fbd2c153555b31f294_o1","c46b83119a3d16e729c4a4b94737435f347f98b8a45dd321bf9e3506beaecd48_o1","0e660dbbccc19c47e388dbd07832821da0083f020eced33c3f49d195df5427bc_o1","bc3574b008e372b4409ae7307bb8f0b34e1733cc1e78125276e2d17f89fef532_o1","507b9cffbeb94c2386e054f3d63617f7b091e0bdb1619a99c3cd04e309be45f3_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1","589e107231b17445bc648ace9e1a852d201409a3ed42280c3f5ff4033c1a28ac_o1","71a1b97fbc18e9c85f5325f19fc319495c4cd66de02e0c3298cecb9ed18fbb07_o1","f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1","fe51df7a52d15b7ca973d26e152c99a15ac0999c564cc02069e911c2de172645_o1","61b1ebdc64669eddf554b96ce8806218af993187260d2bf928b053c44823f018_o1","61cc626a506579b06b242b294e653b27a239e4d763a4da1b1cb6f79634707e5e_o1","0c6e6cfc9aa441f7dd501c57dff599bb0cc385b09fa14fd6d1819a6192f17180_o1","b34d8d3b8987a6fe1489b691d9f81b454b891032174e26a83437b20dc927aa1f_o1","2e303b169bbd2f0ebea751f3a903d1788aab5feeaf87401619323871158922e9_o1","cd82da27c54227a4318d230052be280fb4715bdec4a8aea4718076152e5f2eb3_o1","70b45f5a6cc076f90f5f9dff0e6b1000c26ecd2e757a357c55045dacf0f67df7_o1","af1f84d35a72741971d343bc1185148556b91b10a6528e0f91fa5c774995ca60_o1","d606d5d3f7686315049f2378d3a67cd2a35067ba29b60c6a81a86cacd64062de_o1","e53d25b33a3dba42879dd59af491a1f490d4e2ef1d2d0bec336301cee8222e7f_o1","8e321620b39e13850f18126987a70024e8a5f3e10f3845dbe80d71e592722814_o1","391e6a6f65f4e8076cab01f502a19b698593a059c127a8b33b108b2c66ea5efb_o1","40ee60ffb0dd6c69a2c05bdb97faa535ee3c64c93082993073bd547f6d30a42d_o1","7e104b838bb7ddf4526d2f2ece2f9f9ad3ab70feeb14b7e3ddfba3bb4923d0f6_o1","1e892ea06f4440e4325041c775af60bf757916b005651e8e4e9885a8391ee5fb_o1","2250f4609006fa7cb078a8e324f2c15da1c595160c208c05a2dc2d5cb10edaf0_o1","965ee816a4c968af58324f9ec2c1619598ae66f2d2d9c2cd2f7126ca73cf94b2_o1","76dd393049394a78f4bf5161b1fa3ae707a458bbe794284bb5a8ed0ce0b279c6_o1","63a95922d64757b101dec0bda9cf1a94ae57e4db11758cdcf2055fd70d37796b_o1","3874753ee14fd8a634e493ecb99740129bf4a45a00696e1a66073be40ce58e97_o1","945e5b651d4bd5a26dd66ede72c1ee1bb5d13e5878d1f60f27675bb9a7860114_o1","3224062f168bf1b346eeaf3c151867bf8848ecfa8424fcb3b2b869e805bcdaa3_o1","880e1b9f652ef4477db9b8add5718512a77ff2faa87e6f77c7a65fc8c51653ee_o1","a88cf531ee597862a8cfb8d8838761b933a91762a3998b71a8a5c1810b5c193b_o1","1033da3a240b99c0125a6a81305b86a01553d0d66221d7338a19b7932d9021ef_o1","80b0651684aece2b49ad9dc4226142b333ae6966b0ed23b76c934dfe27c0ebb3_o1","1d514ed783b16684162f1efbbd7ef7ccb5a605a751015f5ecb879dc46b8358a9_o1","c8eab96af901f2eac6e346aa5680d081f8df75380df11cb831cccf2089e7b302_o1","856c25e3fed20be88a3f732df582d7b82f5c00888538d5c8cf5d6bd3f47f5031_o1","054fdad6723c04da2fab33113b4f37a3e46ad0c927a0c0d549ef1f9ccbbb17e4_o1"],"out":["cb010734ea9807f350b2de07a567df556d3d2e55d1589e291ce3d6c8b0f1bf5f"],"del":[],"cre":["n2Bd4cWhEQK1aVjb1R7EBGV9mrw3etvSdC"],"exec":[{"op":"DEPLOY","data":["class ValidatorAgent extends Agent {\n async init(params) {\n console.log('Initializing', this.constructor.name);\n const { messages, nonce } = params;\n console.log('Params:', params, JSON.stringify(messages), nonce);\n this.queueMessages = messages;\n this.messageHandlers.set('Act', this.onAct);\n this.messageHandlers.set('Forfeit', this.onForfeit);\n this.messageHandlers.set('BattleSigned', this.onBattleSigned);\n let id;\n try {\n console.time('_buildPlayers');\n const players = await this._buildPlayers(messages);\n console.timeEnd('_buildPlayers');\n console.log('Players:', players);\n this.players = players;\n\n console.log('Creating battle');\n this.hashchain = this.generateHashchain(128);\n id = this.battleId = Sha256.hashToHex(Agent.hexToBytes(this.hashchain[0]));\n\n const t = this.wallet.createTransaction();\n let battle;\n t.update(() => {\n const b = new Battle(\n ValidatorConfig.address,\n this.constructor,\n players,\n id,\n this.wallet.now\n );\n\n battle = {\n id,\n random: b.random,\n timestamp: b.timestamp,\n rules: this.constructor.toObject(['rewardTiers']),\n status: b.status,\n battlePlayers: KronoClass.deepClone(b.battlePlayers.map(p => ({\n pubkey: p.pubkey,\n owner: p.owner,\n fighter: {\n origin: p.fighter.origin,\n displayName: p.fighter.displayName,\n race: p.fighter.race,\n appearance: p.fighter.appearance,\n abilityScores: p.fighter.abilityScores,\n level: p.fighter.level,\n xp: p.fighter.xp,\n hpBonus: p.fighter.hpBonus,\n skills: p.fighter.skills,\n owner: p.fighter.owner,\n },\n mainhand: p.mainhand,\n offhand: p.offhand,\n armor: p.armor,\n skills: p.skills,\n })))\n };\n });\n console.log('BattleCreated', JSON.stringify(battle));\n let rawtx = await t.export({ pay: true, sign: true });\n t.rollback();\n const tx = this.bsv.Tx.fromHex(rawtx);\n this.sigs = tx.txIns.map(txIn => {\n return txIn.script.toString();\n });\n console.log('Create Battle Sigs:', this.sigs);\n rawtx = this.rawtx = tx.toHex();\n\n this.emit('subscribe', id);\n console.log('Build Message');\n const message = this.wallet.buildMessage({\n to: players.map(player => player.pubkey),\n subject: 'SignBattle',\n context: [nonce],\n payload: JSON.stringify({ id, rawtx, battle })\n });\n await this.blockchain.sendMessage(message);\n await this.storage.pipeline()\n .set(`sign:${id}`, this.wallet.now)\n .expire(`sign:${id}`, 300)\n .exec();\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: 'ExitQueue',\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 this.wallet.setTimeout(async () => {\n console.log('evalSignTimeout', id);\n if(!(await this.storage.exists(`sign:${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: 'ExitQueue',\n context: [id]\n }));\n this.emit('close');\n }, 120000);\n\n\n this.dispose();\n }\n\n async onBattleSigned(message) {\n const sigs = message.payloadObj;\n\n const queueMessage = this.queueMessages.find(m => m.from === message.from);\n const { fighterLocation, itemLocations, coinId } = JSON.parse(queueMessage.payload);\n\n const tx = this.bsv.Tx.fromHex(this.rawtx);\n tx.txIns.forEach((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) && loc !== coinId) return;\n this.sigs[i] = sigs[i];\n });\n\n for (const sig of this.sigs) {\n if (!sig || sig === 'OP_0 OP_0') {\n console.log('Not fully signed', this.sigs);\n return;\n }\n }\n\n this.sigs.forEach((sig, i) => tx.txIns[i].setScript(this.bsv.Script.fromString(sig)));\n const rawtx = tx.toHex();\n const txid = await this.blockchain.broadcast(rawtx);\n await this.storage.del(`sign:${this.battleId}`);\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) continue;\n this.battle = jig;\n }\n if (!this.battle) throw new Error('Battle Create Failed');\n console.timeEnd('loadBattle');\n\n this.battle.begin(this.wallet.now + this.constructor.initialTimeout);\n await this.battle.sync();\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 = this.hashchain[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();\n console.timeEnd(`Act`);\n console.time(`Update Battle`);\n await this.updateBattle();\n console.timeEnd(`Update Battle`);\n }\n\n async evalActTimeout(id, turnCount) {\n if(!this.battle || this.battle.id !== id || this.battle.turnCount !== turnCount) return;\n const now = this.wallet.now;\n console.log('Resolving timeout', this.battle.origin, now, this.battle.timestamp);\n const random = this.hashchain[this.battle.turnCount];\n const timeout = this.wallet.now + this.constructor.timeout;\n this.battle.resolve(random, now, timeout);\n await this.battle.sync();\n await this.updateBattle();\n }\n\n async onForfeit(message) {\n this.battle.forfeit({...message}, this.wallet.now);\n await this.battle.sync();\n await this.updateBattle();\n }\n\n async updateBattle() {\n console.log('Update Battle');\n const { id, location, origin, status, timeout, turnCount } = 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 await this.blockchain.sendMessage(message);\n\n const ms = timeout - this.wallet.now;\n console.log('TIMEOUT:', ms);\n this.wallet.setTimeout(() => this.evalActTimeout(id, turnCount), 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 await this.blockchain.sendMessage(message);\n if (this.battle.owner === this.address) {\n this.battle.destroy();\n await this.battle.sync();\n }\n this.battle = null;\n this.emit('close');\n }\n }\n\n static async processQueue(msg1, agent) {\n console.log('Processing log', msg1.subject);\n const queue = await agent.storage.hgetall(this.origin);\n console.log('Queue:', queue);\n const player = await this.loadPlayer(msg1, agent.wallet);\n for (let msgId2 of Object.values(queue)) {\n const msg2 = await agent.storage.hgetall(msgId2);\n console.log('Message Loaded:', msg2);\n const opponent = await this.loadPlayer(msg2, agent.wallet);\n console.log('Opponent Loaded:', opponent);\n if (!this.matchOpponent(player, opponent)) {\n continue;\n }\n console.log('Matched:', msg2);\n return msg2;\n }\n }\n\n static matchOpponent(player, opponent) {\n return player.pubkey !== opponent.pubkey;\n }\n\n async _loadPlayer(message) {\n return this.constructor.loadPlayer(message, this.wallet);\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, coinId } = 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 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 return item;\n }))\n ]);\n\n let coin;\n if (this.fee) {\n coin = await wallet.loadJig(coinId);\n }\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 coin,\n tags: []\n };\n }\n\n static _validatePlayer({ fighter, items, skills, coin }) {\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 && (!coin || coin.amount < this.fee)) throw new Error('Insufficient Fee');\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, coin, tags } = player;\n this._validatePlayer(player);\n // const fighterData = fighter.toObject();\n fighter.auth();\n\n let fee;\n if (this.fee) {\n fee = coin.send(validator, this.fee);\n }\n\n let mainhand = items[EquipSlot.Mainhand] && items[EquipSlot.Mainhand].item;\n const offhand = items[EquipSlot.Offhand] && items[EquipSlot.Offhand].item;\n const armor = items[EquipSlot.Body] && items[EquipSlot.Body].item;\n\n items.forEach(item => item && item.auth());\n\n return {\n pubkey,\n owner,\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 async _buildPlayers(messages) {\n return Promise.all(messages.map((m) => this._loadPlayer(m)));\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:', 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},"Sha256":{"$jig":9},"Skills":{"$jig":10},"ValidatorConfig":{"$dup":["1","config"]},"expect":{"$jig":11}},"fee":0,"hash":"be1e90401b0442b837a1d72cc50e4940a65d11f9069fc58d65b812e208a5e7c5","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":12},"1":{"$jig":13},"2":{"$jig":14},"3":{"$jig":15},"4":{"$jig":16},"5":{"$jig":17},"6":{"$jig":18},"7":{"$jig":19},"8":{"$jig":20},"9":{"$jig":21},"10":{"$jig":22},"11":{"$jig":23},"12":{"$jig":24},"13":{"$jig":25},"14":{"$jig":26},"15":{"$jig":27},"16":{"$jig":28},"17":{"$jig":29},"18":{"$jig":30},"19":{"$jig":31},"20":{"$jig":32},"21":{"$jig":33},"22":{"$jig":34},"23":{"$jig":35},"24":{"$jig":36},"25":{"$jig":37},"26":{"$jig":38},"27":{"$jig":39},"31":{"$jig":40},"32":{"$jig":41},"33":{"$jig":42}},"tier":0,"timeout":45000,"whitelist":["419f1a052198cc8464d9816e33c6ea9ec9025877b5a521df626fdab97a15fc2a_o1","ed84a4072ce62b8b017bb1e853ee740341b6b2a7ed9229fbd2c153555b31f294_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1"]}]}]}
    https://whatsonchain.com/tx/007070e69a3655887edb3eed06dc195223e449407b64dab309069d2508451155