Settings

Blockchain
Network
Unit
Language
Theme
Sound New Block

Transaction

f5dd2552116ab048b04de161e17de4813d5c6d71829f103095bc496d801b69bc
Timestamp (utc)
2021-02-20 00:37:42
Fee Paid
0.00032436 BSV
(
0.00946812 BSV
-
0.00914376 BSV
)
Fee Rate
996.7 sat/KB
Version
1
Confirmations
321,500
Size Stats
32,537 B

3 Outputs

Total Output:
0.00914376 BSV
  • jrun cryptofightsM~{"in":0,"ref":["1c1accbca985304f5b5d3f8058d640d6a2b47edb8c9119ec1adbcacd0defe53c_o1","601edbeb03cc17a04d7ef75d78deddf3c51cd45c84d0850effc475cc2f6ca0b5_o1","7be0faf9d5c2cc12981cc3c04117aa90b82d7928e66e7ccc941431517906b4bc_o1","162058b016ec64fff953ada13768b32a77c23b76abb7b72abebd8f0fe642948a_o1","d5823b255f14a57f63f94490fc4db742ac91574c98dc34f2b0762d41fde13f15_o1","20af650ed493f124361af75c0d2131238266d3cb9098e1b819ca9ef176ed2c1c_o1","363e1420840195239965db31907d9d1635eaf6993a07f963749f6cdd201bf0ec_o1","e3a71f1844f597ad4f8af1485f385b518c239d11c338503870d459306024df81_o1","438bfb5abc25e9bd04217610b78df75d748f3a63153084f9b33bd871172cb492_o1","80cf361259fa8fb944e3a524cf31c5f485c217f7b4a4fb14bafc278b5a938975_o1","e9c568244b6cf874b22072812cdfd51d272b4e831f5cdf3912d48745bc7c701e_o1","e90ea5246b5a95dc111933d60f590939f045af227caeb3e0822ba8882beb88a3_o1","f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1","c343df9e2f24586b4042d682f56f917e14020933b707569d2aa999ed8184b459_o1","1d7197f48ef9b30880aa4bfa0f17fb7c6a1cf27ff1e7c951d3d13e500891a61f_o1","94828ac616ddf2a1ef04f9292521ded11787c4815c43ad9bdf50704f609b0704_o1","a4a45a03e31560c6c8e154fe459225ba7b94281cbfec062bcee419733e34e604_o1","c5812b35aa74d26b7063ffceaf4da53145440927e35925f032e9d7e789bf8e5e_o1","c35ac01b5b29adb7a756d32dc3cdc9fbe16c6193d2a20f5ff33d2ef7c432b5b3_o1","ffa6359d77f445a84a9893ebbfab27489bf771dfda37f599b4686c2f6a54550b_o1","ad880a89c7b10fcddfdf2ee93f5d341f621e549d8441e2b1578175fc4c54f92b_o1","58d325923ae103130f3d62d4d13196b438a38e901c0cc534dc5175325fb30a25_o1","d4eae0ee824176837156b7f55404f3e9a72b154178e590501819b67acbc15031_o1","5758567e65500056633ad6f7ad46c59d772a722de35a2aa0ec6e921ab58ee6cd_o1","8f44e44adacd08264014647f8738211cac73913996b43ccd9219b1378a1dd36b_o1","9987c1585a2d6becf267844c347cf42d467c82ab553641df582c600e508b9645_o1","1f6d3c59361704e81e261d7622edaca3f2a39b6dbfe1504024b433b2d6f93985_o1","50e19daadb740a76af21443ff2b1c7fbd5bd130781f94f227249a825a41425ce_o1","d5a96e0858961d62004b2ca42b2bc5dae13316f9fd5d0076884ef3423fc1d51c_o1","6fa0f0be28ac0debd7d7e1f3707739c00b7aa1bb244eb55bb0496c920e3e2098_o1","04010808bcbd600fb9be8b5b30b53ce87c0795b3f8ae2234324ba7e70f1ec0d5_o1","0c391c61ac247e5acd90e452939620ff11251d8ae6e10551a5f7c163f3fc4267_o1","7b092894768cfec92a51bfed3f14c2706780f24f32682129649cda95f99e7294_o1","f6225cec354adacf0347a16e41a282c781b8a33558f6db9985a50ef64ad6e3bf_o1","b2ecfe203cd0e553ea19fd0465ce74f66f0080a859ef2181dca99b50af7e1705_o1","b4280616ee88160b2d864608801048230a99b3bf80250ab5151cbb58691f15f3_o1","e8d29c1d5cb0aa8f76571dfccce0ec075993a8a7d1fa9ecb16177b91f79021d9_o1","105268726068388692c9212a23389b94bda8520de8acdea5b7bfc653170229c1_o1","f7643ecd5e6312caeecda0b5b5415ca67401ffa7c6279b3df0c34a10d144c908_o1","7cad53625dfb00a06ff1044d73e3051c918ecabbf88df9e023dc5eddb36c64fa_o1","d0aab2960a9dc269cdf9efe76247f46b5f463218a21497d87b39695ceeea7f92_o1","cdfe90a898bd9737e1e92ef42f764b01920454fa1ceabc0f904fdb469a281d15_o1","6c304cf1ff0e3b9a665ff53dfa382281e8a3062be5999da0f0a4f9c67083ff1e_o1","2bdba6221f06290add3d7f70e44ae8b8be89038705a2bebca3bda62daaad6e63_o1"],"out":["07ce878775d910116bc89e278d8c2298f0b588e34491cb278b355e240cdea2a7"],"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(`sigs:${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();\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();\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) return;\n await this.battle.sync();\n if(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();\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 } = 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 // this.battle.finalize();\n // await this.battle.sync();\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();\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({ projection: { 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":"e623cace8198d5d408c1c3d6d01c50fc8f449929943ff2b16a72d35150a49fec","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":["7be0faf9d5c2cc12981cc3c04117aa90b82d7928e66e7ccc941431517906b4bc_o1","162058b016ec64fff953ada13768b32a77c23b76abb7b72abebd8f0fe642948a_o1","438bfb5abc25e9bd04217610b78df75d748f3a63153084f9b33bd871172cb492_o1"]}]}]}
    https://whatsonchain.com/tx/f5dd2552116ab048b04de161e17de4813d5c6d71829f103095bc496d801b69bc