Settings

Blockchain
Network
Unit
Language
Theme
Sound New Block

Transaction

17c94bb3bb315ce176bc2b2272d37e3fb8111f294d4fc64ba6a675d06e3f9eed
Timestamp (utc)
2022-03-25 16:24:52
Fee Paid
0.00009307 BSV
(
0.00095718 BSV
-
0.00086411 BSV
)
Fee Rate
250 sat/KB
Version
1
Confirmations
243,556
Size Stats
37,224 B

3 Outputs

Total Output:
0.00086411 BSV
  • jrun cryptofightsMd{"in":0,"ref":["743a27f9c3fb50e35bfdc4f09bb7b1dd98f19d347c2ab858776aa28241c80c16_o1","e5602b14f529aa66499a6dc2b93f7fdf2f091edbbc2b0efd8b2e2161d59b5ca5_o1","5334f688050535806987424ec00ba75b74a2087c8c3945765dd9107ac81af1a7_o1","9f0e192f744258c3484a1c19fa88ef68e46ee6cf75acc78cfe2ad5a0d2340d0e_o1","5dc606e8c78f47a10ecb5f8645f2dbe09a3b3a2da87e7e614dcc150203adba1a_o1","f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1"],"out":["0560ec1bc54ce4f13688572baf124d43951dfd4a386eee4525040840ce96482d"],"del":[],"cre":["n2Bd4cWhEQK1aVjb1R7EBGV9mrw3etvSdC"],"exec":[{"op":"DEPLOY","data":["class Attack extends FyxClass {\n\n static getWeapons(battlePlayer)\n {\n const {Ability, DamageType, ItemProperty, ItemType, SkillType, Bonus} = Constants;\n var weapons = [];\n if ((battlePlayer.mainhand == null || battlePlayer.mainhand.type == null) && battlePlayer.offhand == null) {\n for (let index = 0; index < 2; index++) {\n var element = {};\n element.properties = Object.keys(ItemProperty).map(() => false);\n\n if(battlePlayer.fighter.skills.includes(SkillType.Unarmed)){\n element.damageOutputs = [{\n type: DamageType.Bludgeoning,\n diceCount: 2,\n diceFaces: 6\n }];\n element.properties[ItemProperty.Heavy] = true;\n }else{\n element.damageOutputs = [{\n type: DamageType.Bludgeoning,\n diceCount: 1,\n diceFaces: 4\n }];\n element.properties[ItemProperty.Light] = true;\n }\n element.properties[ItemProperty.Brutal] = true;\n element.properties[ItemProperty.Assault] = true;\n\n expect(ItemType).toBeDefined('ItemType is null');\n expect(Bonus).toBeDefined('ItemBonus is null');\n expect(DamageType).toBeDefined('DamageType is null');\n\n element.displayName= 'Barefists';\n element.description= 'Pair of hands.';\n element.type= ItemType.Club;\n element.levelRequired= 1;\n element.abilityRequirements= Object.keys(Ability).map(()=> 1 );\n element.bonuses= Object.keys(Bonus).map(()=> 0 );\n element.damageBonus= Object.keys(DamageType).map(()=> 0 );\n element.damageReduction= Object.keys(DamageType).map(()=> 0 );\n element.quality= 1;\n weapons.push(element);\n }\n }\n else{\n if (battlePlayer.mainhand) {\n weapons.push(battlePlayer.mainhand);\n }\n if (battlePlayer.offhand) {\n weapons.push(battlePlayer.offhand);\n }\n }\n\n return weapons;\n }\n \n //Calculate weapon damage output\n static getWeaponDamageOutput( weapon, attacker )\n {\n const {DamageType, SkillType, Bonus} = Constants;\n \n expect(attacker && attacker !== null).toBeDefined('Attacker is null');\n\n let weaponDamageOutputs = [];\n if (weapon.damageOutputs && weapon.damageOutputs !== null) {\n weapon.damageOutputs.forEach(element => {\n weaponDamageOutputs.push(element);\n });\n }\n \n var baseDamageType = weaponDamageOutputs[0].type;\n //Add damage bonus to flat damage bonus\n //0 index is the base damage type\n weaponDamageOutputs[0].flatDamage += weapon.bonuses[Bonus.BaseDamage];\n\n if (weapon.imbuedItems && weapon.imbuedItems !== null) {\n weapon.imbuedItems.forEach(imbuedItem => {\n if (imbuedItem.damageOutput && imbuedItem.damageOutput !== null) {\n imbuedItem.damageOutput.forEach(damageOutput => {\n weaponDamageOutputs.push(damageOutput);\n });\n }\n });\n }\n\n //Idealy we only need DamageOutputs but some weapons still use weapon.bonuses[DamageType]\n let oldFlatDamages = [weapon.bonuses[Bonus.Piercing], weapon.bonuses[Bonus.Slashing], weapon.bonuses[Bonus.Bludgeoning],\n weapon.bonuses[Bonus.Fire], weapon.bonuses[Bonus.Cold], weapon.bonuses[Bonus.Necrotic], weapon.bonuses[Bonus.Lightning], weapon.bonuses[Bonus.Poison],\n weapon.bonuses[Bonus.Mystic], weapon.bonuses[Bonus.Warfare]];\n\n //Add old damage bonuses as damageOutputs\n if (oldFlatDamages) {\n for (let index = 0; index < oldFlatDamages.length; index++) {\n if (oldFlatDamages[index] != 0) {\n weaponDamageOutputs.push({\n type: index,\n flatBonus: oldFlatDamages[index]\n });\n }\n }\n }\n\n var includesArmoring = attacker.fighter.skills.includes(SkillType.Warmongering);\n if (includesArmoring) {\n weaponDamageOutputs.forEach(element => {\n //Update damage/damage Type here\n if(element.type == baseDamageType){\n element.type = DamageType.Warfare;\n }\n });\n }\n return weaponDamageOutputs;\n }\n \n //Get all periodic damages\n static getWeaponPeriodicDamage( weapon )\n {\n var periodicDamages = [];\n\n if (weapon.periodicDamage && weapon.periodicDamage !== null) {\n weapon.periodicDamage.forEach( damage => {\n periodicDamages.push( damage );\n });\n }\n if (weapon.ImbuedItems && weapon.ImbuedItems !== null) {\n console.log(`----weapon.ImbuedItems && weapon.ImbuedItems !== null ${weapon.ImbuedItems && weapon.ImbuedItems !== null}`);\n weapon.ImbuedItems.forEach( ImbuedItem => {\n if (ImbuedItem.periodicDamage && ImbuedItem.periodicDamage !== null) {\n console.log(`--------ImbuedItem.periodicDamage`);\n ImbuedItem.periodicDamage.forEach( damage => {\n periodicDamages.push( damage );\n });\n }\n });\n }\n\n return periodicDamages;\n }\n\n static run(battle, state, dice, timestamp, actionOptions, toHitBonus = 0, toDmgBonus = 0, attackType = Constants.SkillType.Attack, cooldown = -1) {\n const {Ability, DamageType, EquipSlot, ItemProperty, SkillType, StatusEffect} = Constants;\n\n const skillIndex = battle.battlePlayers[state.playerToAct].skills.findIndex( x=> x.skillType == attackType);\n const cooldownTurns = state.fighterStates[state.playerToAct].actionCooldownsRound[skillIndex];\n if(cooldownTurns > state.fighterStates[state.playerToAct].turnCounter){\n return BattleUtils.skipTurn(battle, state, dice);\n }\n\n let playerName = BattleUtils.getPrettifiedPlayerName(battle, state);\n let opponentName = BattleUtils.getOpponentName(battle, state);\n let log = `${playerName} uses <sprite name=\"${BattleUtils.parseSkillName(attackType)}\">${BattleUtils.parseSkillName(attackType)}\\n`;\n\n const attacker = battle.battlePlayers[state.playerToAct];\n const attackerItems = [battle.battlePlayers[state.playerToAct].mainhand, battle.battlePlayers[state.playerToAct].offhand, battle.battlePlayers[state.playerToAct].armor, battle.battlePlayers[state.playerToAct].hat];\n\n var attackItems = Attack.getWeapons(attacker);\n attackerItems[0] = attackItems[0];\n attackerItems[1] = attackItems[1];\n\n let attackerState = state.fighterStates[state.playerToAct];\n let defenderState = state.fighterStates[state.playerToAct === 0 ? 1 : 0];\n const defenderPlayer = battle.battlePlayers[state.playerToAct ? 0 : 1];\n\n // Bob attacks Tim\n let resultMessage = `${playerName} attacks ${opponentName} : `; // goes up\n let attackActionLog = {\n actionLogMessage: log,\n playerIndex: state.playerToAct,\n skillType: attackType,\n results: []\n };\n \n // Process pre-attack saving throws actionLogs\n if (BattleUtils.isUnderStatusEffect(defenderState, StatusEffect.Hidden, defenderState.turnCounter)) { // Spot\n state.actionLogs.push(this.makeSpotActionLog(battle, state, dice, attackerState, defenderState, state.playerToAct));\n }\n \n if (!BattleUtils.isUnderStatusEffect(defenderState, StatusEffect.Hidden, defenderState.turnCounter)) {\n let totalHitBonus = 0;\n const attackerStr = attacker.fighter.abilityScores[Ability.Strength];\n const attackerDex = attacker.fighter.abilityScores[Ability.Dexterity];\n const derivedStats = BattleUtils.getDerivedStats(attacker.fighter, attackerItems);\n\n for (var i = 0; i < 2; i++) {\n let weapon = attackerItems[i];\n let multiattackImmune = BattleUtils.isUnderStatusEffect(defenderState, StatusEffect.Meditative, defenderState.turnCounter);\n\n if (weapon && weapon.type !== null) {\n if (!(multiattackImmune && i > 0)) {\n let weaponDamageOutputs = this.getWeaponDamageOutput( weapon, attacker );\n let weaponPeriodicDamage = this.getWeaponPeriodicDamage( weapon );\n \n // Find out 'to hit' ability\n var toHitAbility = Ability.Intelligence;\n // Weapon is brutal or ambivalent + player has str > dex\n if (weapon.properties[ItemProperty.Brutal] || (weapon.properties[ItemProperty.Ambivalent] && attackerStr >= attackerDex)) {\n toHitAbility = Ability.Strength;\n } else {\n // Weapon is dextrous or ambivalent + player has dex > str\n if (weapon.properties[ItemProperty.Dextrous] || (weapon.properties[ItemProperty.Ambivalent] && attackerDex >= attackerStr)) {\n toHitAbility = Ability.Dexterity;\n }\n }\n\n // Find out 'to damage' ability\n let toDmgAbility = Ability.Intelligence;\n // Weapon is Assault\n if (weapon.properties[ItemProperty.Assault]) {\n toDmgAbility = Ability.Strength;\n } else {\n // Weapon is Precision\n if (weapon.properties[ItemProperty.Precision]) {\n toDmgAbility = Ability.Dexterity;\n }\n }\n \n if (weapon.damageOutputs.length > 0) {\n expect(Constants.DamageTypeNames[weapon.damageOutputs[0].type]).toBeDefined(`Weapon with unknown damage type while processing attack, ${weapon.damageOutputs[0].type} not in ${JSON.stringify(Constants.DamageTypeNames)}`);\n \n // We also need to grab any crit chance bonus from weapons\n var critChanceBonus = derivedStats.critChance; //moving this to BattleUtils.getDerivedStats (we might need change if we add offhand precising weapons) + (attacker.fighter.skills.includes(SkillType.Precise) && weapon.properties[ItemProperty.Precision]) ? 1 : 0;\n\n // Keen\n if(weapon.properties[ItemProperty.Keen]){\n critChanceBonus++;\n }\n\n totalHitBonus = toHitBonus;\n\n if(i === 0)\n {\n totalHitBonus += 2;\n }\n else if( i === 1 && attackerItems[EquipSlot.Offhand].properties[ItemProperty.Light])\n {\n totalHitBonus += 2;\n }\n\n if (attacker.fighter.skills.includes(SkillType.Ambidextrous)) {\n totalHitBonus += 2;\n }\n\n if (attackActionLog.results.length > 0) {\n if (attackActionLog.results.slice(-1).pop().damageOutput[0].defenderHp <= 0) {\n break;\n }\n }\n\n if (weapon.properties[ItemProperty.Loading] && !attackerState.areLoaded[ i ]) {\n attackerState.areLoaded[ i ] = true;\n let result = this.makeLoadingResult(state, weapon.type);\n attackActionLog.results.push(result);\n }\n else\n {\n if (attackType == SkillType.Stab && !(weapon.damageOutputs && weapon.damageOutputs !== null && weapon.damageOutputs.length > 0 \n && (weapon.damageOutputs[0].type == DamageType.Piercing || weapon.damageOutputs[0].type == DamageType.Slashing)\n && weapon.properties[ItemProperty.Assault])) {\n continue;\n }\n let result = this.makeResult(battle, state, dice, toHitAbility, toDmgAbility, totalHitBonus, toDmgBonus,\n critChanceBonus, weaponDamageOutputs, defenderPlayer, attackActionLog, resultMessage, attackType, i, weaponPeriodicDamage);\n attackActionLog.results.push(result);\n attackerState.areLoaded[ i ] = false;\n }\n\n if ((weapon.properties[ItemProperty.Swift] || (weapon.properties[ItemProperty.Light] && attackType == SkillType.Strike)\n || BattleUtils.isUnderStatusEffect(attackerState, StatusEffect.Swifted, attackerState.turnCounter)) && !multiattackImmune) {\n if (attackActionLog.results.length > 0) {\n if (attackActionLog.results.slice(-1).pop().damageOutput[0].defenderHp <= 0) {\n break;\n }\n }\n if (weapon.properties[ItemProperty.Loading] && !attackerState.areLoaded[ i ]) {\n attackerState.areLoaded[ i ] = true;\n let result = this.makeLoadingResult(state, weapon.type);\n attackActionLog.results.push(result);\n }\n else\n {\n let result = this.makeResult(battle, state, dice, toHitAbility, toDmgAbility, totalHitBonus, toDmgBonus,\n critChanceBonus, weaponDamageOutputs, defenderPlayer, attackActionLog, resultMessage, attackType, i, weaponPeriodicDamage);\n attackActionLog.results.push(result);\n attackerState.areLoaded[ i ] = false;\n }\n }\n }\n }\n }\n if (attackType === SkillType.Stun) {\n break;\n }\n }\n }\n else\n {\n for (var i = 0; i < 2; i++) {\n let weapon = attackerItems[i];\n if (weapon && weapon.type !== null) {\n if (weapon.properties[ItemProperty.Loading] && !attackerState.areLoaded[ i ]) {\n attackerState.areLoaded[ i ] = true;\n let result = this.makeLoadingResult(state, weapon.type);\n attackActionLog.results.push(result);\n }\n }\n }\n }\n\n state.actionLogs.push(attackActionLog);\n this.processPostAttackStatus(state, battle, attackType);\n BattleUtils.applySkillCooldown(battle, state, attackType, cooldown);\n \n return BattleUtils.endTurn(battle, state, dice);\n }\n\n static processPostAttackStatus(state, battle, attackType) {\n const {Outcome, RollType, SkillType, StatusEffect, DamageType, ItemProperty} = Constants;\n let wasHit = false;\n let wasCrit = false;\n let attackerState = state.fighterStates[state.playerToAct];\n let defenderState = state.fighterStates[state.playerToAct === 0 ? 1 : 0];\n let attackerPlayer = battle.battlePlayers[state.playerToAct];\n let defenderPlayer = battle.battlePlayers[state.playerToAct ? 0 : 1];\n\n let weapons = [];\n let mainhand = attackerPlayer.mainhand;\n let mainHand = false;\n if (mainhand && mainhand !== null) {\n if (mainhand.properties && mainhand.properties !== null) {\n mainHand = true;\n weapons.push(mainhand);\n }\n }\n let offhand = attackerPlayer.offhand;\n let offHand = false;\n if (offhand && offhand !== null) {\n if (offhand.properties && offhand.properties !== null) {\n offHand = true;\n weapons.push(offhand);\n }\n }\n \n state.actionLogs.forEach(actionLog => {\n if (actionLog.skillType === attackType) {\n actionLog.results.forEach(result => {\n result.rolls.forEach(roll => {\n if (roll.type == RollType.ToHit) {\n if (result.outcome === Outcome.Success || result.outcome === Outcome.Critical) {\n wasHit = true;\n }\n if (result.outcome === Outcome.Critical) {\n wasCrit = true;\n }\n }\n });\n });\n }\n });\n \n // On Hit\n if (wasHit) {\n if(attackType === SkillType.SneakAttack && attackerPlayer.fighter.skills.includes(SkillType.ImprovedSneakAttack))\n {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Poisoned, 1, false);\n }\n \n if (attackType === SkillType.Fireball) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Burning, 3, false);\n }\n\n if (attackType === SkillType.Stun &&\n ((attackerPlayer.fighter.skills.includes(SkillType.Unarmed) && weapons.length == 0) ||\n (mainHand && attackerPlayer.mainhand.properties !== null \n && attackerPlayer.mainhand.properties[ItemProperty.Brutal] && attackerPlayer.mainhand.properties[ItemProperty.Heavy]))) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Stunned, 2, false);\n }\n \n if (attackType === SkillType.Freeze\n || (attackType === SkillType.Stab && ((mainHand && mainhand.properties[ItemProperty.Assault] && \n mainhand.damageOutputs && mainhand.damageOutputs !== null && mainhand.damageOutputs.length > 0 \n && (mainhand.damageOutputs[0].type == DamageType.Piercing || mainhand.damageOutputs[0].type == DamageType.Slashing))\n || (offHand && offhand.properties[ItemProperty.Assault] \n && offhand.damageOutputs && offhand.damageOutputs !== null && offhand.damageOutputs.length > 0 \n && (offhand.damageOutputs[0].type == DamageType.Piercing || offhand.damageOutputs[0].type == DamageType.Slashing))))) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Stunned, 1, false);\n }\n\n if (attackType === SkillType.Chill) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Demoralized, 2, false);\n }\n if (attackType === SkillType.PinningStrike && wasCrit) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Stunned, 2, false);\n }\n }\n if (attackType === SkillType.PinningStrike) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Marked, wasCrit ? 4 : 2, false);\n }\n\n // On Attack\n if (attackType === SkillType.Gore) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Demoralized, 1, true);\n }\n\n if (attackType === SkillType.LockAndLoad) {\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Demoralized, 3, false);\n BattleUtils.applyStatusEffect(state.actionLogs.slice(-1).pop(), battle, state, StatusEffect.Inspired, 3, true);\n }\n if (defenderState.hp > 0) {\n if (defenderPlayer.fighter.skills.includes(SkillType.Opportunistic) && !wasHit) {\n state.actionLogs.push(BattleUtils.makeStatusEffectActionLog(battle, state, SkillType.Opportunistic, StatusEffect.Inspired, 1, '', false));\n }\n\n if (defenderPlayer.fighter.skills.includes(SkillType.Vengeful) && wasHit) {\n state.actionLogs.push( { actionLogMessage: `${BattleUtils.getPrettifiedPlayerName(battle, state, true)} is vengeful\\n`, skillType: Constants.SkillType.Vengeful } );\n }\n }\n }\n\n static makeLoadingResult(state, weaponType)\n { \n const {Outcome, ItemTypeNames, DamageType, StatusEffect} = Constants;\n const defenderState = state.fighterStates[state.playerToAct ? 0 : 1];\n const attackerState = state.fighterStates[state.playerToAct];\n const damageMap = Object.keys(DamageType).map(() => 0);\n return {\n rolls: [],\n outcome: Outcome.Success,\n damageOutput: [{\n damage: damageMap,\n defenderHp: defenderState.hp,\n defenderReduction: damageMap\n }],\n hpIncrement: 0,\n attackerHp: attackerState.hp,\n resultMessage: `${ItemTypeNames[weaponType]} is loading\\n`,\n target: state.playerToAct\n };\n }\n\n static makeResult(battle, state, dice, toHitAbility, toDmgAbility, toHitBonus, toDmgBonus, critChanceBonus, damageOutputs, defenderPlayer, attackActionLog, log, attackType = Constants.SkillType.Attack, weaponIndex, periodicDamage = null) {\n const {Outcome, RollType, SkillType, StatusEffect, ItemProperty, DamageType} = Constants;\n \n const attackerIndex = state.playerToAct;\n const defenderIndex = state.playerToAct ? 0 : 1;\n const attackerState = state.fighterStates[attackerIndex];\n const defenderState = state.fighterStates[defenderIndex];\n const attacker = battle.battlePlayers[attackerIndex];\n const defender = battle.battlePlayers[defenderIndex];\n\n /* #region attack_roll */\n let toHitRoll = {\n type: RollType.ToHit,\n ability: toHitAbility,\n size: 20,\n value: dice.roll(1, 20)\n };\n var rolls = [toHitRoll];\n\n // Advantage\n if (BattleUtils.isUnderStatusEffect(attackerState, StatusEffect.Hidden, attackerState.turnCounter) ||\n BattleUtils.isUnderStatusEffect(attackerState, StatusEffect.Inspired, attackerState.turnCounter) ||\n BattleUtils.isUnderStatusEffect(defenderState, StatusEffect.Marked, attackerState.turnCounter)) {\n rolls.push({\n type: RollType.ToHit,\n ability: toHitAbility,\n size: 20,\n value: dice.roll(1, 20)\n });\n\n if (rolls.slice(-1).pop().value > toHitRoll.value) {\n toHitRoll.discarded = true;\n toHitRoll = rolls.slice(-1).pop();\n } else {\n rolls.slice(-1).pop().discarded = true;\n }\n }\n\n if (BattleUtils.isUnderStatusEffect(attackerState, StatusEffect.Demoralized, attackerState.turnCounter)) {\n rolls.push({\n type: RollType.ToHit,\n ability: toHitAbility,\n size: 20,\n value: dice.roll(1, 20)\n });\n\n if (rolls.slice(-1).pop().value < toHitRoll.value) {\n toHitRoll.discarded = true;\n toHitRoll = rolls.slice(-1).pop();\n } else {\n rolls.slice(-1).pop().discarded = true;\n }\n }\n /* #endregion */\n\n // Process bonuses for selected dice\n toHitRoll.bonusFromAbility = attackerState.modifiers[toHitAbility];\n let skill = attacker.skills.find( x => x.skillType == attackType);\n if ((!attacker.mainhand || attacker.mainhand.properties[ItemProperty.Innocuous]) \n && (!attacker.offhand|| attacker.offhand.properties[ItemProperty.Innocuous]) && skill && skill.isSpellAction) {\n toHitBonus += 2;\n }\n toHitRoll.bonusFromSkill = toHitBonus;\n toHitRoll.totalBonus = toHitRoll.bonusFromAbility + toHitRoll.bonusFromSkill;\n\n // Process hit\n // Determine hit crit miss\n // Sometimes the attack hits because it naturally critted but is not a solid hit in the sense that\n // it failed to break the opponent's evasion. On those cases critting doesn't deal double dmg\n let outcome = Outcome.Fail;\n const critImmune = BattleUtils.isUnderStatusEffect(defenderState, StatusEffect.Meditative, defenderState.turnCounter);\n const wasCrit = attackType !== SkillType.DirtyFighting && toHitRoll.value >= 20 - critChanceBonus && !critImmune;\n const solidHit = toHitRoll.value + toHitRoll.totalBonus >= defenderState.evasion;\n if ( solidHit || wasCrit) {\n outcome = Outcome.Success;\n if (wasCrit) {\n toHitRoll.totalBonus += critChanceBonus;\n outcome = Outcome.Critical;\n }\n \n if(periodicDamage && periodicDamage !== null)\n {\n //Apply Damage Over Time\n for (let index = 0; index < periodicDamage.length; index++) {\n var statusEffect = periodicDamage[index];\n if (BattleUtils.validStatusEffect(attackerState, statusEffect, attackerState.turnCounter)) {\n BattleUtils.applyStatusEffect(attackActionLog, battle, state, statusEffect, 2, false );\n BattleUtils.applyPeriodicDamageCooldown(state, statusEffect );\n }\n };\n }\n \n //Apply Poisonous\n if (BattleUtils.isUnderStatusEffect(attackerState, StatusEffect.Poisonous, attackerState.turnCounter)) {\n BattleUtils.applyStatusEffect(attackActionLog, battle, state, StatusEffect.Poisoned, 2, false);\n }\n\n //Apply Vengeful/Curse\n if (defender.fighter.skills.includes(SkillType.Vengeful)) {\n BattleUtils.applyStatusEffect(attackActionLog, battle, state, StatusEffect.Cursed, 0, true);\n }\n }\n\n //(roll+bonusFromAbility+bonusFromSkill) = total\n // *Hit* (10 + 1 + 1) = 12\n log += `*${BattleUtils.parseAttackOutcome(outcome)}* (${toHitRoll.value} + ${toHitRoll.bonusFromAbility} + ${toHitRoll.bonusFromSkill}${outcome == Outcome.Critical ?` +${critChanceBonus}` : ``} = ${toHitRoll.value + toHitRoll.totalBonus})\\n`;\n\n let preResultDescription = `You need at least ${defenderState.evasion} <sprite name=Dexterity-d20> to hit`;\n let rollDamageMap = Object.keys(DamageType).map(() => 0);\n let flatDamageMap = Object.keys(DamageType).map(() => 0);\n\n if (outcome !== Outcome.Fail) {\n if (attackType !== SkillType.Stun && attackType !== SkillType.PinningStrike) {\n for (let i = 0; i < damageOutputs.length; i++) {\n var damageRoll = BattleUtils.rollDmgDice(toDmgAbility, damageOutputs[i].diceCount, damageOutputs[i].diceFaces, dice, rolls, RollType.ToDamage);\n rollDamageMap[damageOutputs[i].type] += damageRoll;\n if (damageOutputs[i].flatBonus) {\n flatDamageMap[damageOutputs[i].type] += damageOutputs[i].flatBonus;\n }\n if (attackType === SkillType.Gore) {\n toDmgBonus += damageRoll;\n }\n }\n } else {\n rolls.push({\n type: RollType.ToDamage,\n ability: toDmgAbility,\n size: 0,\n value: 0\n });\n }\n\n // Process to dmg bonuses\n toHitRoll = rolls.slice(-1).pop();\n toHitRoll.bonusFromSkill = toDmgBonus;\n toHitRoll.totalBonus = toHitRoll.bonusFromSkill;\n if (!attacker.fighter.skills.includes(SkillType.Cirurgical)) {\n toHitRoll.bonusFromAbility = attackerState.modifiers[toDmgAbility];\n toHitRoll.totalBonus += toHitRoll.bonusFromAbility;\n }\n rollDamageMap[damageOutputs[0].type] += toHitRoll.totalBonus;\n if (attackType === SkillType.DirtyFighting) {\n let dirtyRoll = dice.roll(1, 4);\n rolls.push({\n type: RollType.ToDamage,\n ability: toHitAbility,\n size: 4,\n value: dirtyRoll\n });\n rollDamageMap[damageOutputs[0].type] += dirtyRoll;\n }\n if (attackType === SkillType.SneakAttack) {\n let isImproved = attacker.fighter.skills.includes(SkillType.ImprovedSneakAttack);\n let sneakRoll = dice.roll(1, isImproved ? 8 : 6);\n rolls.push({\n type: RollType.ToDamage,\n ability: toHitAbility,\n size: isImproved ? 8 : 6,\n value: sneakRoll\n });\n rollDamageMap[damageOutputs[0].type] += sneakRoll;\n }\n\n let weapons = [];\n let validWeapons = [ false, false ];\n let mainhand = attacker.mainhand;\n if (mainhand && mainhand !== null) {\n if (mainhand.properties && mainhand.properties !== null) {\n validWeapons[ 0 ] = true;\n weapons.push(mainhand);\n }\n }\n let offhand = attacker.offhand;\n if (offhand && offhand !== null) {\n if (offhand.properties && offhand.properties !== null) {\n validWeapons[ 1 ] = true;\n weapons.push(offhand);\n }\n }\n\n if (outcome === Outcome.Critical && solidHit) {\n let extraRolls = 1;\n\n // Loading weapons do one extra crit roll \n if( validWeapons[weaponIndex] && weapons[weaponIndex].properties[ItemProperty.Loading] ){\n extraRolls++;\n }\n\n for(var i = 0; i< extraRolls; i++)\n {\n let critDamage = BattleUtils.rollDmgDice(toDmgAbility, damageOutputs[0].diceCount, damageOutputs[0].diceFaces, dice, rolls, RollType.WeaponCrit);\n rollDamageMap[damageOutputs[0].type] += critDamage;\n }\n }\n\n for (let i = 0; i < rollDamageMap.length; i++) { \n if (rollDamageMap[i] < 0) {\n rollDamageMap[i] = 0;\n } \n }\n \n let result = BattleUtils.makeDamageResult(battle, state, defenderState, defenderPlayer, rollDamageMap, flatDamageMap, rolls, outcome, preResultDescription);\n result.weaponIndex = weaponIndex;\n log += result.resultMessage;\n result.resultMessage = log;\n return result;\n } else {\n return {\n rolls: rolls,\n outcome: outcome,\n damageOutput: [{\n damage: Object.keys(DamageType).map(() => 0),\n defenderHp: defenderState.hp,\n defenderReduction: Object.keys(DamageType).map(() => 0)\n }],\n hpIncrement: 0,\n attackerHp: attackerState.hp,\n preResultDescription: preResultDescription,\n resultMessage: log,\n weaponIndex: weaponIndex,\n target: defenderIndex\n };\n }\n }\n\n // Abstract this out to a saving throw\n static makeSpotActionLog(battle, state, dice, spotterState, spoteeState, spotterIndex) {\n const {Ability, Outcome, RollType, SkillType, StatusEffect, DamageType} = Constants;\n let savingThrows = [];\n\n if (BattleUtils.isUnderStatusEffect(spoteeState, StatusEffect.Hidden, spoteeState.turnCounter)) {\n\n var spoteeName = BattleUtils.getPrettifiedName(battle, spotterIndex);\n const attackerIndex = state.playerToAct;\n const defenderIndex = state.playerToAct ? 0 : 1;\n const attackerState = state.fighterStates[attackerIndex];\n const defenderState = state.fighterStates[defenderIndex];\n\n let attackerIntMod = spotterState.modifiers[Ability.Intelligence];\n let savingThrow = dice.roll(1, 20);\n let rollResult = attackerIntMod + savingThrow;\n\n let rollType = defenderState.perception < defenderState.evasion;\n let dc = rollType ? defenderState.perception : defenderState.evasion;\n\n let outcome = savingThrow == 20? Outcome.Critical : rollResult >= dc ? Outcome.Success : Outcome.Fail;\n\n savingThrows.push({\n type: RollType.Intelligence,\n ability: Ability.Intelligence,\n size: 20,\n value: savingThrow\n });\n\n if (outcome === Outcome.Critical || outcome === Outcome.Success) { // found opponent\n for (let index = 0; index < spoteeState.statusEffectsRound.length; index++) {\n spoteeState.statusEffectsRound[index].value[StatusEffect.Hidden] = 0;\n }\n }\n const damageMap = Object.keys(DamageType).map(() => 0);\n return {\n playerIndex: spotterIndex,\n skillType: SkillType.Attack,\n results: [{\n rolls: savingThrows,\n outcome: outcome,\n damageOutput: [{\n damage: damageMap,\n defenderHp: spoteeState.hp,\n defenderReduction: damageMap,\n }],\n hpIncrement: 0,\n attackerHp: attackerState.hp\n }],\n actionLogMessage: `${spoteeName} <gradient=!log-color>Intelligence vs. ${rollType ? 'Perception' : 'Evasion'}: *${BattleUtils.parseSkillOutcome(outcome)}* (${savingThrow} + ${attackerIntMod} = ${rollResult} vs. DC ${dc})`\n };\n }\n }\n}",{"affectsOpponent":true,"cooldown":0,"deps":{"BattleUtils":{"$jig":0},"Constants":{"$jig":1},"Dice":{"$jig":2},"FyxClass":{"$jig":3},"KronoMath":{"$jig":4},"expect":{"$jig":5}},"description":"Attack Action","displayName":"Attack","handle":"attack","hash":"22c8d66a1ce633073f4b96a1cfc9e95a9054b8893033e87453163fae937f257f","isAttackAction":true,"requiredLevel":0,"skillType":0}]}]}
    https://whatsonchain.com/tx/17c94bb3bb315ce176bc2b2272d37e3fb8111f294d4fc64ba6a675d06e3f9eed