Settings

Blockchain
Network
Unit
Language
Theme
Sound New Block

Transaction

fc49513d0d8707e3ea9d57e11b0a8026f63d32945f446df91164e2d14cd9dfc6
Timestamp (utc)
2021-02-17 03:23:58
Fee Paid
0.00016347 BSV
(
0.00572048 BSV
-
0.00555701 BSV
)
Fee Rate
501.9 sat/KB
Version
1
Confirmations
320,914
Size Stats
32,554 B

3 Outputs

Total Output:
0.00555701 BSV
  • jrun cryptofightsM&~{"in":0,"ref":["4265da657b40362a30db8f71151d1502c97ccfcdb9876f13dcd935e122fa8dbd_o1","4a4466c7a9318d0558bbcf5d6b6a23fad6df493a37375d12cc5a2d0f93ebfb10_o1","e2350b800fc05f0dd0af64fa6798d2f794dc552c5336d989b86445c70b7a92a8_o1","12ca21ed0342d419eb4f6167f7f89f328d24f557ab4e2748a33ed866832dc871_o1","d9cbfb7c64b2fe3f0f10b596980cc9fa5bfe428b804d3a888fcda0463f330a4b_o1","6939b560abd6d2031fc50417abea833a30c09080b5bd0766a19ea1489430a852_o1","f9a7565a637b489502dfe8dbb0745175354a3d47ee03c10dc4ce4acb238f822e_o1","507b9cffbeb94c2386e054f3d63617f7b091e0bdb1619a99c3cd04e309be45f3_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1","0135ed4bc6a365330506c2547d3984945952c8fc9a2cb64226fe443ab7ceba0a_o1","589e107231b17445bc648ace9e1a852d201409a3ed42280c3f5ff4033c1a28ac_o1","9ae303de22074b65803494115db376dba86265f80727723a146981bd8c15b09b_o1","f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1","dd1c7a8f7b345801e14455cf2b2a6cd040d300302e4b101f9ad2b773daee814e_o1","a39b6342948b4f7000d1a2321ccd4405b85e5960b9f10571488d2c3c64e4536a_o1","e933e1b4d74d7cda9b5603aeadefe75353a9038f7ee151dfc3ba32a589d81e37_o1","ddb6dadeba85c3a0061c59891392ff235c5b00b9c7d229fac74da3b9bf262b5c_o1","a83a94fe58f0563ec52ba70161e2f3234f1b0cba7f5a5d5be34a85ff998b298a_o1","02f5c246af1c2ff4fa665b2a8686d62dd83609ea3e39915e695a7a6cd5a15154_o1","b23ca7625f397dffb017808f6fb3c7323b25149153d8f48fbf51fb75a0f49eba_o1","72f88090ecfde3af067736a0cfcf9cd83a16fcb98304c0b5fb7a62ae538f1605_o1","f4a8c056831ad65929b47a90d22bc8b06d9e96345c369b5bd3acc314575a9509_o1","386eab745d5e102616ec5891ca99a08188ef92c31a7ac1290858fb2d58bb979b_o1","cf323bda8c76bdb98855b98ebb5dc68c7f6c370928f83f7d88d526809b5dd7ff_o1","e3ebe13f7c3de5913cf1a0e2cdc9db6f8c7649c408f8b8437cbbf01ca0043da6_o1","343533913a5a3dece83bb1e63b3a6ddd03261d6aa527d019bdff0b718285167d_o1","91922ba41d1471023d03cd21ac433cbc88777d2f503c520fa5e07694625c80ac_o1","500158cd58ecfe7bb885f5d08628699c9f45ee28cc803b7680f0063eb3c2cb0e_o1","b96f331803596d47f81f4f1e35f198020b6fffead6ad884a2c0c8e9928e989d6_o1","c5bb54d01263c6996baedcd2d448ed011f2ed53c585129273c8ae3a9e78407fa_o1","a88eea23ad83761af6c224e92f820408ce27c46fba8dc3fe751f925f0509e530_o1","54286f4e5cce5195e6d865aab96206532da6f07fa8b5fb3ff55e013f573d9a6a_o1","0f55c0a449f977e7cc1d443415b056f8ee667980b1019001398af157ab156a2a_o1","e106dbf5aafec4496fef742c4e66fbf3f5531a26132752289b9b691ebefcd868_o1","aed811ffa94c7d197989ea74b7706453970f3f781a2e057cd26265847dcf2614_o1","cc4cfece89c05d3b234dbcf891d6461d645b372ca767562b919a1f2dc85098a0_o1","89db6896933b3a24cff7290993559bfe8400b69b648591750269b0f1350f4321_o1","656b900aad05280979ba941fe4ed5f098ea3d0adbf0aa16a6837d28fc98e6f51_o1","d4b5c1f4e756b3f80f8018033e4b91f2cfa4ae50dc0387823757272b940cbb21_o1","b93f671bcf3bf7096c7a432af0c47d2e4be34a3074eae31a4bdd0eba0b0858dc_o1","584f989604b5485534becf0b03054c23a911b64f396a37710d5c8ba7f707f18a_o1","0a800440977997f2b038edb5018f649e9193f55c7898732a7939388ce4e348a4_o1","4453fb059849cfdc3d4fd131a73043917f84d286067822f04fa91f6d5c1ddf47_o1","3f1ab8ecb5b3c0a3e70554177f7e906bbd10863a8a4c768607b5dead1421edf3_o1"],"out":["c9752758dedf36d9d5a02b55ad9ea363ecf64f2ef1beff6f91a43e3058423f6a"],"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 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({ 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":"b48a64e76686ed1116ebb9418452b77b48a825391b36963a07cea21ea0e16759","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":["e2350b800fc05f0dd0af64fa6798d2f794dc552c5336d989b86445c70b7a92a8_o1","12ca21ed0342d419eb4f6167f7f89f328d24f557ab4e2748a33ed866832dc871_o1","b2ddb06a8acf4f6e7f594d812982ef6a924390d6e898a83140aa58606b1d2d05_o1"]}]}]}
    https://whatsonchain.com/tx/fc49513d0d8707e3ea9d57e11b0a8026f63d32945f446df91164e2d14cd9dfc6