MediaWiki:CombatSimulator.js

// CONFIGURATION v3.29 - fixed order of the cards after introduction of the universal card in sets. // CONFIGURATION v3.30 - Light-777-Angel added tasks for events // CONFIGURATION v3.31 - add more info for event tasks, complete status, task name, etc.

// CONFIGURATION v3.33 - new event quests

// CONFIGURATION v3.34 - Light-777-Angel added some effects partialy: Wisdom tome, Enchanting Song // CONFIGURATION v3.35 - new event quests Mountain Madness // CONFIGURATION v3.36 - new event quests Voice of the Deep // CONFIGURATION v3.37 - Light-777-Angel added LuckyHeal and TitanArmor effects // CONFIGURATION v3.38 - Light-777-Angel added Firefly-HealigSpirit and Rocket effects // CONFIGURATION v3.39 - Light-777-Angel added intro for WaterAmp,FireAmp,EarthAmp // CONFIGURATION v3.40 - Light-777-Angel added Poisoned, Poison effects // CONFIGURATION v3.41 - Light-777-Angel added DragonTear, Motivation // CONFIGURATION v3.42 - Light-777-Angel added Nine Ring Sword, MortalStrike, Lightning Sphere // CONFIGURATION v3.43 - correction for effects/artifact changes on august 03 2022 part p1 // CONFIGURATION v3.44  - correction for effects/artifact changes on august 03 2022 part p2 // CONFIGURATION v3.45  - correction for effects/artifact changes on august 03 2022 part p3 // CONFIGURATION v3.46  - correction for backstab, percent of initial base HP not curr HP, disable Allied strike // CONFIGURATION v3.47 - correction for claws and for Allied strike, added w3 quests golden way // CONFIGURATION v3.48 - correction for effects/artifact changes on august 03 2022 part p4 // CONFIGURATION v3.49  - correction for effects/artifact p5 : curse - cannot affect a card twice // CONFIGURATION v3.50 - added rune shield: bugged. // CONFIGURATION v3.51 - added partial WaterAmp,FireAmp,EarthAmp, fixed massrage no stacking. // CONFIGURATION v3.52 -  fixed chilling no stacking, fixed Chian light - run only once. // CONFIGURATION v3.53 -  verifyied Blade Dance. // CONFIGURATION v3.54 -  adjusted rune shield to have only 20% change to give the shield to herself. // CONFIGURATION v3.55 -  added ATT and HP info in bold on card mouse hover, added Thunder effect // CONFIGURATION v3.56 -  added immunity for adamantWill, corrected Motivation (every round) // CONFIGURATION v3.57 -  added effect Confusion, verified Freeze. // CONFIGURATION v3.58 -  fixed Mortal strike - hits afterHit, everyRound // CONFIGURATION v3.59 -  added Effects: Resurrect and Last-Stand // CONFIGURATION v3.60 -  added Effects: JewelShield // CONFIGURATION v3.61 -  fixed Effects: DivineShield, also correction to all other shields. // CONFIGURATION v3.61 -  shields are removed after 'Hit' and need to be re-added by some effect // CONFIGURATION v3.62 -  added Effects: GranitBody and IceShield, verified MassFreeze, // CONFIGURATION v3.63b - added soft version for quests, secondary efect for artifacts.(jewelShield) // CONFIGURATION v3.64 - added Effects: FogOfWar, ThunderHammer // CONFIGURATION v3.65b - added Effects: MithrilArmor, Exorcism, ChainOfJustice // CONFIGURATION v3.66 - Wisdom Tome: Done, selection of 4 random effects; fixed  MithrilArmor // CONFIGURATION v3.67 - added Effects: Temptation, Ignition,Ignited,  corrected Poison,Poisoned // CONFIGURATION v3.68 - added Effects: DragonBreath, BlessedSun , corrected Heal for baseHp. // CONFIGURATION v3.68 - added ability to select artifact for optimize.

$(document).ready(function {    console.log('Check if page is the CombatSimulator');    if (mw.config.values.wgPageName != 'Combat_Simulator') { return 0; }    console.log('Page is CombatSimulator');

'use strict';

/**    * URL location of the game webpack content. */   const confJsUrl = 'https://smutstone.miraheze.org/w/index.php?title=MediaWiki:Smutstone.conf.js&action=raw&ctype=text/javascript'; //const confJsUrl = 'https://cdn.smutstone.com/s2/33ca20ff.conf.js' // const confJsUrl = $('#smutstoneConfJs a').prop('href'); //const confJsUrl = 'Summer Avalanche w3-cs.conf.js'

/**    * URL location of the smutstone image URLs. * Images are loaded from the game site if `smutstoneImagesUrl` is not defined. */   // const smutstoneImagesUrl = 'https://smutstone.miraheze.org/w/index.php?title=MediaWiki:SmutstoneImageUrls.js&action=raw&ctype=text/javascript'; // const smutstoneImagesUrl = 'smutstoneImageUrls.js';

// GLOBALS

/** The webpack content of the game. */   var webpack;

/** Adapter to provide image URL's for the target platform. */   var imageManager;

/** The userData with possible temporary changes. */   var userCurrent;

/** Whether the event bonus is applied. */   var applyEventBonus;

/** Whether all girls and artifacts are included. */   var allGirlsAndArtifacts;

/** The display style for the girls */ var displayStyle;

// HTML

function hereDoc(f) { return f.toString. replace(/^[^\/]+\/\*!\r?\n?/, ''). replace(/\r?\n?\*\/[^\/]+$/, ''); }

document.getElementById('combatSimulator').innerHTML = hereDoc(function {/*!        userData from the game:         Copy userData from the game here (Ctrl+U, select all, copy, paste).

For instance: var userData = JSON.parse('{"hero":{"exp":1222890,"cardGame":{"decks":[{},{"cardIds":[209744677,209672394,209842509,209695022,212557683,210765560,210847135]}],"cards":[{"_name":"Amazon","id":209842509,"cardId":52,"level":15,"dupes":82,"stones":[24,24],"sbl":0,"evolution":{"currentStage":6},"aid":29,"initialHp":2570},{"_name":"Bone Walker","id":210847135,"cardId":143,"level":17,"dupes":0,"stones":[33,33],"sbl":0,"evolution":{"currentStage":6},"initialHp":2339},{"_name":"Dark Twins","id":210765560,"cardId":151,"level":18,"dupes":65,"stones":[43,43],"sbl":1,"evolution":{"currentStage":4},"aid":34,"initialHp":1521},{"_name":"Fairy","id":209744677,"cardId":42,"level":13,"dupes":0,"stones":[24,24],"sbl":3,"evolution":{"currentStage":6},"aid":32,"initialHp":2231},{"_name":"Geomancer","id":209695022,"cardId":147,"level":14,"dupes":339,"stones":[24],"sbl":1,"evolution":{"currentStage":9},"aid":30,"initialHp":1685},{"_name":"Oglodi","id":212557683,"cardId":152,"level":15,"dupes":58,"stones":[24,24],"sbl":1,"evolution":{"currentStage":5},"initialHp":1683},{"_name":"Snow Queen","id":209672394,"cardId":26,"level":10,"dupes":52,"stones":[24],"sbl":3,"evolution":{"currentStage":9},"aid":31,"initialHp":2086}]},"artifacts":{"items":[{"id":28,"stages":{"currentStage":0},"dupes":2,"cardId":447},{"id":29,"stages":{"currentStage":0},"dupes":1,"cardId":52},{"id":30,"stages":{"currentStage":0},"dupes":2,"cardId":147},{"id":31,"stages":{"currentStage":1},"dupes":0,"cardId":26},{"id":32,"stages":{"currentStage":1},"dupes":0,"cardId":42},{"id":34,"stages":{"currentStage":2},"dupes":1,"cardId":151},{"id":36,"stages":{"currentStage":0},"dupes":3,"cardId":0}]}}}'); Debug Combat Guild Event-Tasks  Choose deck  Choose location  Choose mission Combat  Hero: Max All Reset All ☼ More properties ☼ Artifacts   All to: 8*                    9*                     10*                     LV25 LV50 REG PUR RAD FLA ⛓3                    ⛓6                     ⛓9                     ⛓12                     A5★ Artifacts:  Enemy: <button id="showEnemy" class="button_style3">☼ Show <button id="bestArtifactDistribution">Optimize artifacts <button id="findBestIncrease">Find best increase 0%                Cancel Output: <textarea readonly id="output" rows="26" cols="80"> <label for="supportedEffects">List of artifacts and effects: <select id="supportedEffects"> Supported <select id="unsupportedEffects"> Unsupported <input id="applyEventBonus" type="checkbox" checked>Event bonus <input id="allGirlsAndArtifacts" type="checkbox">All girls and artifacts Random:<select id="worstCase"> Expected <option value="Random" selected="true">Random <option value="Worst">Worst case <option value="Best">Best case <label for="selectDisplayStyle">Display:<select id="selectDisplayStyle"> <option value="0" selected="true">Icon 1 star 2 star Guild troops <input id="allGuildGirls" type="checkbox">All girls <input id="guildSummary" type="checkbox">Summary <input id="guildTime" type="text" size="25" readonly> <div id="Tasks_List"> */});   enableDialog(false);

/** Loads the script at `url` asynchronously and calls `resolve` afterwards. */   function loadScript(url, resolve) { console.log('CombatSimulator: load script from ' + url); const script = document.createElement('script'); document.body.appendChild(script); script.onload = function { console.log('CombatSimulator: loaded ' + url); resolve; };       script.onerror = function  { console.error('CombatSimulator: failed to load ' + url); $('#output').text('CombatSimulator: failed to load ' + url); };       script.async = true; script.src = url; };

/** Finds and parses the `userData` in the `text` parameter as provided in the html source of Smutstone */ function parseUserData(text) { console.log('CombatSimulator: parseUserData', text); const line = text.split('\n').find(function (s) { return s.includes('var userData ='); }); if (!line) { console.warn('CombatSimulator: could not find line with userData. Skipping'); return undefined }       const userDataStr = line.replace(/^ *var *userData *= *JSON.parse\('/, ).replace(/'\);? *$/, ) .replace(/\\'/g, "'").replace(/\\\\/g, '\\') var userData = JSON.parse(userDataStr) console.log('CombatSimulator: parsed userData', userData); return new User(userData); }

/** Finds and parses the `duelData` in the `text` parameter as provided in the response of a duel of Smutstone */ function parseDuelData(text) { console.log('CombatSimulator: parseDuelData', text); var duelData = JSON.parse(text) console.log('CombatSimulator: duelData', duelData); const sides = duelData.response.battle.sides const options = { verbose: true } const hero = new BattlePlayer('hero', sides[0].hp, sides[0].cards, options) const enemy = new BattlePlayer('enemy', sides[1].hp, sides[1].cards, options) console.log(`hero`, hero) console.log(`enemy`, enemy) const battle = new Battle(hero, enemy, options) console.log(`Result`, battle.result) }

/** Populate text box with userData with useful decks that can be copy-and-pasted offline, edited, and copied back */ function populateUserData { const user = { "guildMember": { "blockedCards": [], "bonuses": { "11": 0.05, "12": 0.05, "13": 0.05, "14": 0.05, "15": 0.05,                   "20": 0.1,                    "31": 0.5, "32": 0.5, "33": 0.5, "34": 0.5, "35": 0.5,                    "51": 0.05, "52": 0.05, "53": 0.05, "54": 0.05, "55": 0.05,                }            },            "hero": { "exp": 16904420, // Level 60, 2300 hp               "cardGame": { "decks": [], "cards": webpack.cardSets.flatMap(cardSet =>                       webpack.cards.filter(c => !['bot', 'tutorial', 'boss'].includes(c.tag))                            .filter(c => c.cset == cardSet.id)                            .sort((a, b) => a.name.localeCompare(b.name))                            .map(card => { const stoneId = card.color * 10 + 7 return { "_name": card.name, "id": card.cardId, "cardId": card.cardId, "level": 50, "stones": card.sockets >= 2 ? [stoneId, stoneId] : [stoneId], "sbl": 12, "evolution": { "currentStage": 9 }, }                           }))                },                "artifacts": { "items": Object.values(webpack.artifacts).filter(artifact => artifact.color) .sort((a, b) => a.effect.id.localeCompare(b.effect.id)) .sort((a, b) => a.color - b.color) .map(artifact => {                           return {                                "_effectId": artifact.effect.id,                                "_color": Webpack.colorText[artifact.color],                                "id": artifact.id,                                "stages": { "currentStage": 4 },                            }                        }) }           }        }        const text = `Copy userData from the game here (Ctrl+U, select all, copy, paste).\n` + `For instance:\n` + `var userData = JSON.parse('${JSON.stringify(user)}')` $('#userData').val(text) }

function populateDecks(user, options) { const dropdown = $('#selectDeck'); dropdown.empty; dropdown.append('<option selected="true" disabled>Choose deck '); dropdown.prop('selectedIndex', 0); const battleCards = user.hero.cardGame.cards.map(function (cardInstance) {           const noCsetCounts = {};            console.log('cardInstance ' + JSON.stringify(cardInstance));            return new BattleCard(cardInstance, noCsetCounts, options);        }); if (!allGirlsAndArtifacts) { // Deck #1 to #3 user.hero.cardGame.decks.forEach(function (deckWrapper, deckIndex) {               if (deckWrapper.cardIds) {                    const deck = battleCards                        .filter(function (c) { return deckWrapper.cardIds.includes(c.id) })                        .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp })                        .map(function (c) { return c.id });                    const name = 'Deck #' + (deckIndex + 1);                    dropdown.append($('  ').attr('value', JSON.stringify(deck)).text(name));                }            }); }       // PNF decks and other cardSets webpack.cardSets.map(function (s) { return s.id }).forEach(function (cset) {           const deck = battleCards                .filter(function (c) { return c.cset == cset })                .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp })                .map(function (c) { return c.id });            if (deck.length > 0) {                const name = webpack.cardSets.find(function (cardSet) { return cardSet.id == cset }).name;                console.log('deck ' + name, deck);                dropdown.append($('  ').attr('value', JSON.stringify(deck)).text(name));            }        }); // Event cards if it is not a cardSet if (user.currentEventTag) { const deck = battleCards .filter(function (c) { return c.tag === user.currentEventTag && !c.cset }) .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp }) .map(function (c) { return c.id }); if (deck.length > 0) { const name = user.currentEvent.name; console.log('deck ' + name, deck); dropdown.append($(' ').attr('value', JSON.stringify(deck)).text(name)); }       }        // Strongest single color decks [1, 2, 3, 4, 5].forEach(function (color) {           const deck = battleCards                .filter(function (c) { return c.color == color })                .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp })                .slice(0, 7)                .map(function (c) { return c.id });            if (deck.length > 0) {                const name = 'Strongest ' + Webpack.elementText[color];                console.log('deck ' + name, deck);                dropdown.append($('  ').attr('value', JSON.stringify(deck)).text(name));            }        }); // Currently strongest {           const deck = battleCards .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp }) .slice(0, 7) .map(function (c) { return c.id }); const name = 'Current strongest'; console.log('deck ' + name, deck); dropdown.append($(' ').attr('value', JSON.stringify(deck)).text(name)); }       // Strongest when maxed {           const maxedBattleCards = battleCards.map(function (battleCard) {                const stoneId = (battleCard.color - 1) * 10 + 10                const highestStar = battleCard.getHighestSeduction.highestStar                const maxStage = ['player', 'pvp', 'rating'].includes(battleCard.tag) || highestStar >= 5 ? 9 : highestStar - 1                const maxedCardInstance = {                    id: battleCard.id,                    cardId: battleCard.cardId,                    evolution: { currentStage: maxStage },                    level: 50,                    stones: battleCard.sockets >= 2 ? [stoneId, stoneId] : [stoneId],                    sbl: 12,                }                const noCsetCounts = {};                return new BattleCard(maxedCardInstance, noCsetCounts, options);            }); const deck = maxedBattleCards .sort(function (a, b) { return b.attack * b.hp - a.attack * a.hp }) .slice(0, 7) .map(function (c) { return c.id }); const name = 'Strongest maxed'; console.log('deck ' + name, deck); dropdown.append($(' ').attr('value', JSON.stringify(deck)).text(name)); }   }

function populateLocations { const dropdown = $('#selectLocation'); webpack.allLocations.forEach(function (location, locationIndex) {           dropdown.append($('  ').attr('value', locationIndex).text(location.name));        }); }

function populateMissions(locationIndex) { const dropdown = $('#selectMission'); dropdown.empty; dropdown.append('<option selected="true" disabled>Choose mission '); dropdown.prop('selectedIndex', 0); const location = webpack.allLocations[locationIndex]; console.log('CombatSimulator: location', location); console.log('CombatSimulator: missions', location.missions); for (var missionIndex in location.missions) { const mission = location.missions[missionIndex]; const value = JSON.stringify({ locationIndex: locationIndex, missionIndex: missionIndex }); dropdown.append($(' ').attr('value', value).text(mission.displayName)); }   }

function populateArtifacts(user) { function addArtifact(parent, artifact) { const items = []; items.push(artifact.name + ' ' + artifact.stars); const costToRemove = artifact.removeCost[artifact.stages.currentStage].gold items.push(artifact.effect.name + ' (' + artifact.effect.id + ')') var info = artifact.effect.info for (const [key, value] of Object.entries(artifact.effect)) { if (info.includes(key + 'PC')) { info = info.replace(new RegExp('\{' + key + 'PC\}', 'g'), Math.floor(value * 100)) }           }            items.push(info) if (artifact.effect.events) { items.push('Events: ' + JSON.stringify(artifact.effect.events)) }           if (costToRemove) { items.push('Cost to remove: ' + costToRemove.toLocaleString('en-US') + ' gold') }           const paramsDiv = $(' ').addClass('params') .append($(' ').addClass('stars').addClass('level').addClass(`${artifact.artifactInstance.modified_stars || ''}`)                   .text(`${artifact.stars}/${artifact.maxStars}★`)                    .append(createChangeDiv(function  {                        return artifact.changeStars(artifact.stars - 1)                    }, function  {                        return artifact.changeStars(artifact.stars + 1)                    }, function  {                        artifact.artifactInstance.modified_stars = 'modified'                    }))) const artifactDiv = $(' ').addClass('artifact').addClass(['', 'fire', 'earth', 'ice', 'light', 'dark'][artifact.color || 0]) .append(paramsDiv) .append($(' ', { src: imageManager.getImageUrl(`artifact:${artifact.name}`, artifact.effect.icons._1x), name: items.join('\n') })) if (artifact.cardId) { const card = webpack.cards.find(c => c.cardId === artifact.cardId) artifactDiv.append($(' ').addClass('minicard')                   .append($(' ', { src: imageManager.getImageUrl(`cardIcon:${card.name}`, card.icons._1x) }))) }           artifactDiv.append(' ' + items.join(' ') + ' ')

const optimize_selected = $(' ').addClass('params') .append($(' ').addClass('stars').addClass('level').addClass(`${artifact.artifactInstance.modified_stars || ''}`)                   .text(`${artifact.stars}/${artifact.maxStars}★`)                    .append(createChangeDiv(function  {                        return artifact.changeStars(artifact.stars - 1)                    }, function  {                        return artifact.changeStars(artifact.stars + 1)                    }, function  {                        artifact.artifactInstance.modified_stars = 'modified'                    })))

parent.append(artifactDiv).append($(' ').addClass('artifact-name').addClass(`${artifact.artifactInstance.optimize_selected || ''}`) .text(artifact.name).click(function { if(artifact.artifactInstance.optimize_selected == undefined){artifact.artifactInstance.optimize_selected = 'selected';} else if (artifact.artifactInstance.optimize_selected == 'selected'){artifact.artifactInstance.optimize_selected = undefined} update; }));

artifactDiv.attr('draggable', true).get(0).addEventListener('dragstart', ev => {               console.log(`dragstart aid=${artifact.id}`, artifact)                ev.dataTransfer.setData("text", artifact.id)            })

return parent; }

const selectedColor = $('#selectArtifactColors').val

$('#artifacts div').empty; const ul = $('<ul>'); $('#artifacts div').append(ul); Object.values(user.hero.artifacts.items).map(artifactInstance => new BattleArtifact(artifactInstance, { user: user })) .filter(battleArtifact => battleArtifact.effect) .filter(battleArtifact => selectedColor == -1 || (battleArtifact.color || 0) == selectedColor) .sort((a, b) => a.effect.id.localeCompare(b.effect.id)) .sort((a, b) => (a.color || 0) - (b.color || 0)) .forEach(artifact => {               const li = $('<li>');                ul.append(li);                addArtifact(li, artifact)            }) }

function populateSupportedEffects { const appendDivider = function (dropdown) { dropdown.append($(' ').text('---')); };       const appendTitle = function (dropdown, title) { appendDivider(dropdown); dropdown.append($(' ').text(title)); appendDivider(dropdown); };       appendTitle($('#supportedEffects'), 'Artifacts'); Object.values(webpack.artifacts) .filter(function (a) { return a.effect.id in cardEffects }) .map(function (a) { return a.name }) .sort .forEach(function (name) { $('#supportedEffects').append($(' ').text(name)) }); appendTitle($('#supportedEffects'), 'Effects'); Object.values(webpack.cardEffects) .filter(function (e) { return e.id in cardEffects }) .map(function (e) { return e.name }) .sort .forEach(function (name) { $('#supportedEffects').append($(' ').text(name)) });

appendTitle($('#unsupportedEffects'), 'Artifacts'); Object.values(webpack.artifacts) .filter(function (a) { return !(a.effect.id in cardEffects) }) .map(function (a) { return a.name }) .sort .forEach(function (name) { $('#unsupportedEffects').append($(' ').text(name)) }); appendTitle($('#unsupportedEffects'), 'Effects'); Object.values(webpack.cardEffects) .filter(function (e) { return !(e.id in cardEffects) }) .map(function (e) { return e.name }) .sort .forEach(function (name) { $('#unsupportedEffects').append($(' ').text(name)) }); }

function createChangeDiv(decrease, increase, modify) { return $(' ').addClass('adjust') .append($(' - ').click(function { if (decrease) { modify update }           }))            .append($(' + ').click(function  { if (increase) { modify update }           }))    }

function showPlayerShort(player, id) { $(id).empty; if (!player) return;

function addGirl(parent, c) { const items = [c.name, Webpack.rarityText[c.rarity], Webpack.colorText[c.color]]; var imageUrl = imageManager.getImageUrl(`card1:${c.name}`, c.getImageHashOfGirl) parent.append($(' ').addClass('card').addClass(['', 'fire', 'earth', 'ice', 'light', 'dark'][c.color])               .addClass(!c.effect ? 'full_card' : '')               .append($(' ').addClass('params') .append($(' ').addClass('stars').addClass(`s${c.stars}`).addClass(`${c.cardInstance.modified_stars || ''}`)                       .text(`${c.stars}★`)                        .append(createChangeDiv(function  {                            if (!c.cardInstance.evolution) {                                c.cardInstance.evolution = { currentStage: 0 }                            }                            if (c.cardInstance.evolution.currentStage > 0) {                                c.cardInstance.evolution.currentStage--                                return true                            }                            return false                        }, function  {                            if (!c.cardInstance.evolution) {                                c.cardInstance.evolution = { currentStage: 0 }                            }                            if (c.cardInstance.evolution.currentStage < 9) { c.cardInstance.evolution.currentStage++ return true }                           return false }, function { c.cardInstance.modified_stars = 'modified' })))                   .append($(' ').addClass('level').addClass(`${c.cardInstance.modified_level || ''}`) .text(`lv${c.level}`) .append(createChangeDiv(function { if ((c.cardInstance.level || 0) > 1) { c.cardInstance.level = (c.cardInstance.level || 0) - 1 return true }                           return false }, function { c.cardInstance.level = (c.cardInstance.level || 0) + 1 return true }, function { c.cardInstance.modified_level = 'modified' })))                   .append($(' ').addClass('stones').addClass(`${c.cardInstance.modified_stones || ''}`) .text(`◊${c.stoneLevels.join('+')}`) .append(createChangeDiv(function { if (c.cardInstance.stones.length == 1 && (c.cardInstance.stones[0] - 1) % 10 > 0) { c.cardInstance.stones[0]-- } else if (c.cardInstance.stones.length == 2 && c.cardInstance.stones[0] > c.cardInstance.stones[1] && (c.cardInstance.stones[0] - 1) % 10 > 0) { c.cardInstance.stones[0]-- } else if (c.cardInstance.stones.length == 2 && (c.cardInstance.stones[1] - 1) % 10 > 0) { c.cardInstance.stones[1]-- } else { return false }                           return true }, function { if (!c.cardInstance.stones || c.cardInstance.stones.length == 0) { c.cardInstance.stones = [c.color * 10 + 1] } else if (c.cardInstance.stones.length == 1 && c.sockets >= 2) { c.cardInstance.stones.push(c.color * 10 + 1) } else if (c.cardInstance.stones.length == 1 && (c.cardInstance.stones[0] - 1) % 10 < 9) { c.cardInstance.stones[0]++ } else if (c.cardInstance.stones.length == 2 && c.cardInstance.stones[0] <= c.cardInstance.stones[1]                               && (c.cardInstance.stones[0] - 1) % 10 < 9) { c.cardInstance.stones[0]++ } else if (c.cardInstance.stones.length == 2 && (c.cardInstance.stones[1] - 1) % 10 < 9) { c.cardInstance.stones[1]++ } else { return false }                           return true }, function { c.cardInstance.modified_stones = 'modified' })))                   .append($(' ').addClass('soulbind').addClass(`${c.cardInstance.modified_soulbind || ''}`) .text(`⛓${c.cardInstance.sbl || 0}`) .append(createChangeDiv(function { if (!c.cardInstance.sbl) c.cardInstance.sbl = 0 if (c.cardInstance.sbl > 0) { c.cardInstance.sbl-- return true }                           return false }, function { if (!c.cardInstance.sbl) c.cardInstance.sbl = 0 if (c.cardInstance.sbl < 12) { c.cardInstance.sbl++ return true }                           return false }, function { c.cardInstance.modified_soulbind = 'modified' }))))               .append($(' ').addClass('card_image')                    .append($(' ', { src: imageUrl, name: items.join('\n') }))) .append(' ' + items.join(' ') + ' ' + '<span style="float:left !important;color:red;">' + c.attack + ' <span style="float:right !important;color:red;">' + c.hp + ' ' + ' ') );           return parent;        }        function addEffect(parent, c) {            if (!c.effect) {                parent.append($(' ').addClass('no_art_eff').addClass([, 'fire', 'earth', 'ice', 'light', 'dark'][c.color]));                return parent;            }            const items = [];            var type = 'effect';            var color = 0;            var costToRemove = undefined;            if (c.artifact) {                items.push(c.artifact.name + ' ' + c.artifact.stars);                type = 'artifact'                color = (c.artifact.color || 0);                costToRemove = c.artifact.removeCost[c.artifact.stages.currentStage].gold            }            items.push(c.effect.name + ' (' + c.effect.id + ')' + (c.effect.id in cardEffects ?  : ' UNSUPPORTED'))            var info = c.effect.info            for (const key of Object.keys(c.effect)) { if (info.includes(key + 'PC')) { info = info.replace(new RegExp('\{' + key + 'PC\}', 'g'), Math.floor(c.effect[key] * 100)) }           }            items.push(info) if (c.effect.events) { items.push('Events: ' + JSON.stringify(c.effect.events)) }           if (costToRemove) { items.push('Cost to remove: ' + costToRemove.toLocaleString('en-US') + ' gold') }           var imageUrl if (c.artifact) { imageUrl = imageManager.getImageUrl(`artifact:${c.artifact.name}`, c.effect.icons._1x) } else { imageUrl = imageManager.getImageUrl(`effect:${c.effect.name}`, c.effect.icons?._1x) }           const effect = $(' ').addClass(type).addClass(['', 'fire', 'earth', 'ice', 'light', 'dark'][color]) .append($(' ', { src: imageUrl, name: items.join('\n') })) .append(' ' + items.join(' ') + ' ')

if (c.artifact) { effect.prepend($(' ').addClass('params')                   .append($(' ').addClass('stars').addClass('level').addClass(`s${c.artifact.stars}`).addClass(`${c.artifact.artifactInstance.modified_stars || ''}`) .text(`${c.artifact.stars}/${c.artifact.maxStars}★`) .append(createChangeDiv(function { return c.artifact.changeStars(c.artifact.stars - 1) }, function { return c.artifact.changeStars(c.artifact.stars + 1) }, function { c.artifact.artifactInstance.modified_stars = 'modified' }))))               effect.attr('draggable', true).get(0).addEventListener('dragstart', ev => {                    console.log(`dragstart aid=${c.artifact.id}`, c.artifact)                    ev.dataTransfer.setData("text", c.artifact.id)                }) }

parent.append(effect); return parent; }

const ul = $('<ul/>') $(id).append(ul); player.library.forEach(function (c) {           const li = $('<li/>');            addGirl(li, c);            addEffect(li, c);            ul.append(li);

li.get(0).addEventListener('dragover', ev => {               // Only allow drop if girl has no native effect                if (c.effect && !c.artifact) return                // Only allow drop if artifact color matches                const aid = Number(ev.dataTransfer.getData("text"))                const artifact = webpack.artifacts[aid]                if (artifact.color && artifact.color != c.color) return                // Allow drop                ev.preventDefault            }) li.get(0).addEventListener('drop', ev => {               ev.preventDefault;                const aid = Number(ev.dataTransfer.getData("text"))                console.log(`drop aid=${aid} on ${c.name}`, c)                console.log(`player`, player)                // Remove previous artifact from target girl                if (c.aid) {                    player.options.user.hero.artifacts.items.find(a => a.id == c.aid).cardId = 0                }                // Remove artifact from the girl who previously had the new artifact                player.options.user.hero.cardGame.cards.forEach(cardInstance => { if (cardInstance.aid === aid) { cardInstance.aid = 0 }               })                // Assign artifact to girl                c.cardInstance.aid = aid                player.options.user.hero.artifacts.items.find(a => a.id == aid).cardId = c.cardId                update            }) })   }

function showPlayerColors(player, id) { $(id).empty; if (!player) return; const ul = $('<ul/>') $(id).append(ul); player.library.map(function (card) { return card.color }).sort.forEach(function (color) {           ul.append($('<li/>').addClass(['', 'fire', 'earth', 'ice', 'light', 'dark'][color]));        }); }

function showMissionDescription(mission) { $('#enemy .desc').empty; if (!mission) return;

const items = []; items.push(mission.displayName); items.push('Cost: ' + Object.keys(mission.cost || {}).map(function (k) { return mission.cost[k] + ' ' + k;       }).join(', ')) const winRewards = mission.rewards_data.win[0] items.push('Awarded resources: ' + Object.keys(winRewards.r || {}).map(function (k) { return winRewards.r[k] + ' ' + k;       }).join(', ')) const fullWinResources = Object.assign({}, winRewards.r || {}); mission.rewards_data.stars.forEach(function (star) {           Object.keys(star.r || {}).map(function (k) { fullWinResources[k] = (fullWinResources[k] || 0) + star.r[k] });       });        items.push('Awarded resources for first 3 star win: ' + Object.keys(fullWinResources).map(function (k) { return fullWinResources[k] + ' ' + k;       }).join(', ')) if (Object.keys(mission.cost || {}).length === 1) { const [costKey, cost] = Object.entries(mission.cost)[0] items.push('Farm rate: ' + Object.keys(winRewards.r || {}).map(function (k) { return `${(winRewards.r[k] / cost).toFixed(1)} ${k}/${costKey}` }).join(', ')) }

$('#enemy .desc').html(items.join(' \n')); }

function setGirlStars(cardInstance, stars) { if (!cardInstance.evolution) cardInstance.evolution = {} cardInstance.evolution.currentStage = stars - 1 cardInstance.modified_stars = 'modified' }

function giveGirlStones(cardInstance, stoneLevel) { const stats = webpack.cards.find(function (stats) { return stats.cardId === cardInstance.cardId; }) const stoneId = (stats.color - 1) * 10 + stoneLevel if (stats.sockets >= 2) cardInstance.stones = [stoneId, stoneId] else cardInstance.stones = [stoneId] cardInstance.modified_stones = 'modified' }

const propertiesChange = { 'Max All': function (user) { for (const c of user.hero.cardGame.cards) { const stats = webpack.cards.find(function (stats) { return stats.cardId === c.cardId; }) if (!c.evolution) c.evolution = {} c.evolution.currentStage = 9 c.level = 50 const stoneId = (stats.color - 1) * 10 + 10 if (stats.sockets >= 2) c.stones = [stoneId, stoneId] else c.stones = [stoneId] c.sbl = 12 c.modified_stars = c.modified_level = c.modified_stones = c.modified_soulbind = 'modified' }           for (const a of user.hero.artifacts.items) { if (!a.stages) a.stages = {} const artifact = new BattleArtifact(a) if (artifact.changeStars(artifact.maxStars)) { a.modified_stars = 'modified' }           }        },        'Reset All': function (user) { userCurrent = parseUserData($('#userData').val); },       '8*': function (user) { user.hero.cardGame.cards.forEach(function (c) { setGirlStars(c, 8) }) },       '9*': function (user) { user.hero.cardGame.cards.forEach(function (c) { setGirlStars(c, 9) }) },       '10*': function (user) { user.hero.cardGame.cards.forEach(function (c) { setGirlStars(c, 10) }) },       'LV25': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.level = 25; c.modified_level = 'modified' }) },       'LV50': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.level = 50; c.modified_level = 'modified' }) },       'REG': function (user) { user.hero.cardGame.cards.forEach(function (c) { giveGirlStones(c, 4) }) },       'PUR': function (user) { user.hero.cardGame.cards.forEach(function (c) { giveGirlStones(c, 5) }) },       'RAD': function (user) { user.hero.cardGame.cards.forEach(function (c) { giveGirlStones(c, 6) }) },       'FLA': function (user) { user.hero.cardGame.cards.forEach(function (c) { giveGirlStones(c, 7) }) },       '⛓3': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.sbl = 3; c.modified_soulbind = 'modified' }) },       '⛓6': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.sbl = 6; c.modified_soulbind = 'modified' }) },       '⛓9': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.sbl = 9; c.modified_soulbind = 'modified' }) },       '⛓12': function (user) { user.hero.cardGame.cards.forEach(function (c) { c.sbl = 12; c.modified_soulbind = 'modified' }) },       'A5★': function (user) { user.hero.artifacts.items.forEach(function (a) {               const artifact = new BattleArtifact(a)                if (artifact.changeStars(artifact.maxStars)) {                    a.modified_stars = 'modified'                }            }) },       '☼ More properties': function (user) { // Not a function, but it gets triggered anyway because it's a button within the Properties div. }   }

function update { // Get selections const deckName = $('#selectDeck option:selected').text; const selectDeckVal = $('#selectDeck').val; console.log('selectDeckVal', selectDeckVal); const cardInstanceIds = JSON.parse($('#selectDeck').val); const missionId = JSON.parse($('#selectMission').val); const options = {}; options.currentEventTag = userCurrent.currentEventTag options.test = $("#test input").is(":checked"); options.verbose = options.test;

console.log('cardInstanceIds', cardInstanceIds); console.log('missionId', missionId);

var outputItems = [] if (userCurrent.currentEventTag) { console.log('CombatSimulator: eventTag=' + userCurrent.currentEventTag, userCurrent.currentEvent); outputItems.push("Event '" + userCurrent.currentEvent.name + "' "               + formatDurationMs(userCurrent.currentEventEndDate - Date.now) + " left, "                + userCurrent.eventTournamentsLeft + " tournaments left in stage") }

var hero = BattlePlayer.createHero(userCurrent, deckName, cardInstanceIds || [], options); console.log('CombatSimulator: hero', hero); outputItems.push('Hero ' + hero.toString);

$('#heroProperties').text(`Lv ${BattlePlayer.getLevelFromExp(userCurrent.hero.exp)} ${BattlePlayer.getHpFromExp(userCurrent.hero.exp).toLocaleString('en-US')} hp`) $('#combat_status').text('') showPlayerShort(hero, '#deckHero'); populateArtifacts(userCurrent); var mission if (missionId) { mission = webpack.allLocations[missionId.locationIndex].missions[missionId.missionIndex]; console.log('mission', mission); const enemy = BattlePlayer.createMissionEnemy(mission, options); console.log('CombatSimulator: enemy', enemy); outputItems.push('Enemy ' + enemy.toString) showPlayerShort(enemy, '#deckEnemy'); showPlayerColors(enemy, '#enemyColors') }       showMissionDescription(mission); $('#output').text(outputItems.join('\n\n')); listGuildCardSets(userCurrent, {           user: userCurrent,            currentEventTag: userCurrent.currentEventTag,            allGuildGirls: $('#allGuildGirls').is(':checked'),            guildSummary: $('#guildSummary').is(':checked'),        }) //added by light-angel showTasks; }

// GAME RESOURCES

function ImageManager { }

ImageManager.prototype.load = function (url, resolve) { loadScript(url, resolve) };

ImageManager.prototype.getImageUrl = function (imageRef, hashLocation) { if (typeof smutstoneImageUrls !== 'undefined') { return smutstoneImageUrls[imageRef.trim] }       return 'https://cdn.smutstone.com/s2/' + hashLocation }

function Webpack { this.index = { artifacts: undefined, battleEffects: undefined, cardEffects: undefined, cardSets: undefined, cards: undefined, dungeon: undefined, items: undefined, journey: undefined, quests: undefined, specialEvent: undefined, stones: undefined, story: undefined, };   }

Webpack.colorText = ['Colorless', 'Red', 'Green', 'Blue', 'White', 'Black']; Webpack.elementText = ['Colorless', 'Fire', 'Earth', 'Water', 'Light', 'Dark']; Webpack.rarityText = ['No rarity', 'Common', 'Rare', 'Epic', 'Legendary', 'Mythic'];

Webpack.prototype.load = function (url, resolve) { console.log('CombatSimulator: load webpackJsonp from ' + url); var resourceContent; window.webpackJsonp = function (r, resource, a) { console.log('CombatSimulator: webpackJsonp', resource); resourceContent = resource; };       const script = document.createElement('script'); document.body.appendChild(script); const thisWebpack = this; script.onload = function { console.log('CombatSimulator: loaded ' + url); thisWebpack.unpack(resourceContent); resolve; };       script.onerror = function  { console.error('CombatSimulator: failed to load ' + url); $('#output').text('CombatSimulator: failed to load ' + url); };       script.async = true; script.src = url; };

Webpack.prototype.init = function (resolve, executeConfJs) { console.log('CombatSimulator: load webpackJsonp from local copy'); var resourceContent; executeConfJs(function (r, resource, a) {           console.log('CombatSimulator: webpackJsonp', resource);            resourceContent = resource;               }); console.log('CombatSimulator: loaded local copy of webpackJsonp'); this.unpack(resourceContent); resolve; };

Webpack.prototype.unpack = function (resourceContent) { const unpackExport = function (packFunction) { var e = {}; packFunction(e, {}, function (i) {               if (i == 2) {                    return { _tr: function (s) { return s } };                }                return unpackExport(resourceContent[i]);            }); return e.exports; };       const webpackIndex = unpackExport(resourceContent[15]); for (const key in this.index) { const value = webpackIndex.get(key); this[key] = value; }       console.log('this webpack', this); this.allLocations = this.getAllLocations; }

Webpack.prototype.getAllLocations = function { function decorateMission(location, mission) { mission.displayName = location.name.toString.trim + ' ' + mission.name.toString.trim; if (location.name === 'Journey') { mission.displayName = mission.name.toString.trim }           if (!mission.enemy) { // The 'enemy' was renamed to 'enemy_data'. // However, this was not done everywhere, so map it back to 'enemy' for the time being. mission.enemy = mission.enemy_data mission.enemy.cardGame = { cards: mission.enemy_data.cardGame.cards.map(function (val) {                       return { id: val[0], cardId: val[1], level: val[2], evolution: { currentStage: val[3] }, stones: val[4] }                    }) }           }            if (mission.enemy._hp) { mission.displayName += ' boss' }       }

console.log('CombatSimulator: locations', this.story.locations); console.log('CombatSimulator: dungeons', this.dungeon); const allLocations = this.story.locations.map(function (location) {           location.missions.forEach(function (mission) { decorateMission(location, mission); });           return location;        }); this.dungeon.forEach(function (dungeon) {           allLocations.push(Webpack.convertDungeon(dungeon));        }); Object.values(this.journey).forEach(function (location, locationIndex) {           location.missions.forEach(function (mission) { decorateMission(location, mission); });           location.name += ' ' + String.fromCharCode(65 + locationIndex);            allLocations.push(location);        }); console.log('CombatSimulator: allLocations', allLocations); return allLocations; }

Webpack.convertDungeon = function (dungeon) { const location = Object.assign({}, dungeon) location.missions = dungeon.missions.map(function (dungeonMission, missionNr) {           const name = dungeon.name + ' ' + (missionNr + 1)            const exp = dungeonMission[0]            return {                displayName: name,                name: name,                enemy: {                    exp: exp,                    cardGame: {                        cards: dungeonMission[2].map(function (val) { return { id: val[0], cardId: val[1], level: val[2], evolution: { currentStage: val[3] }, stones: val[4] } })                   }                },                enemy_cards: dungeonMission[2].map(function (val) { return val[1] }),                rewards_data: dungeonMission[1]            }        }) return location }

function User(userData) { Object.assign(this, userData) const now = Date.now this.currentEvent = this.getCurrentEvent(now) this.currentEventTag = this.getEventTag(this.currentEvent) this.currentEventEndDate = this.getEventEndDate(this.currentEvent) this.currentEventStage = this.getEventStage(this.currentEvent, now) this.eventTournamentsLeft = this.getEventTournamentsLeft(this.currentEvent, this.currentEventStage, now) if (allGirlsAndArtifacts) { this.hero.cardGame.cards = webpack.cards .filter(card => card.tag !== 'bot' && card.tag !== 'tutor' && card.tag !== 'boss') .map(card => { return { id: card.cardId, cardId: card.cardId } }) this.hero.artifacts.items = Object.keys(webpack.artifacts).map(aid => {               return { id: aid, stages: { currentStage: 0 }, dupes: Number.MAX_SAFE_INTEGER }            }) }       // Add missing girls of the current event to the user // webpack.cards.filter(card => this.currentEventTag && card.tag === this.currentEventTag).forEach(card => {       //     if (!this.hero.cardGame.cards.find(cardInstance => cardInstance.cardId === card.cardId)) {        //         this.hero.cardGame.cards.push({ id: card.cardId, cardId: card.cardId })        //     }        // }) // Add missing artifacts of the current event to the user // this.getEventArtifacts(this.currentEvent).forEach(artifact => {       //     if (!this.hero.artifacts.items.find(item => item.id === artifact.id)) {        //         this.hero.artifacts.items.push({ id: artifact.id, stages: { currentStage: 0 } })        //     }        // }) }

User.prototype.getEventTag = function (event) { return event ? Object.keys(event.buffCards)[0] : undefined }

User.prototype.getEventArtifacts = function (event) { if (!event) return [] const hank_coffer = webpack.items.find(item => item.props && item.props.arts) if (!hank_coffer) return [] return hank_coffer.props.arts.map(aid => webpack.artifacts[aid]) }

User.prototype.getCurrentEvent = function (now) { const seData = this.vars?.__SEDATA || this.vars?.store?.__SEDATA if (!seData) { return undefined }       const gameDate = now for (const eventId in seData) { const event = webpack.specialEvent.find(function (e) { return e.id === eventId }) if (!event) { continue }           if (gameDate < this.getEventEndDate(event)) { return event }       }        return undefined }

User.prototype.getEventEndDate = function (event) { if (!event) { return undefined }       if (event.fixedEndTime) { return new Date(event.fixedEndTime * 1000) }       const seData = this.vars?.__SEDATA || this.vars?.store?.__SEDATA if (event.ttl && this.vars && seData && seData[event.id]) { return convertGameSecondsToDate(seData[event.id].started + event.ttl) }       return undefined }

User.prototype.getEventStage = function (event, now) { if (!event) return undefined const gameDate = now const stage = event.stages.find(stage => !stage.fixedEndStageTime || gameDate < new Date(stage.fixedEndStageTime * 1000)) if (!stage && event.stages.length === 1) { // Wild Week rerun is running after its stage.fixedEndStageTime return event.stages[0] }       return stage }

User.prototype.getEventTournamentsLeft = function (event, stage, now) { if (!event) return undefined // Wild Week has stage.fixedEndStageTime in the past -> it does not count const endStageDate = stage?.fixedEndStageTime && new Date(stage.fixedEndStageTime * 1000) > new Date ? new Date(stage.fixedEndStageTime * 1000) : this.getEventEndDate(event) const nextResetDate = new Date.setUTCHours(24, 0, 0, 0) return Math.ceil((endStageDate - nextResetDate) / (24 * 60 * 60 * 1000))

}

function convertGameSecondsToDate(gameSeconds) { if (!userCurrent) { return undefined }       const date = new Date(userCurrent.addedTime) date.setUTCSeconds(date.getUTCSeconds + gameSeconds) return date }

function formatDurationMs(milliseconds) { const date = new Date(Math.abs(milliseconds)) const sign = milliseconds < 0 ? '-' : ''       let days = date.getUTCDate - 1 let hours = date.getUTCHours if (days <= 1) { hours += days * 24 days = '' } else { days = `${days}T` }       const minutes = `0${date.getUTCMinutes}`.slice(-2) return `${sign}${days}${hours}:${minutes}` }

// COMBAT SIMULATOR

function BattleArtifact(artifactInstance) { const bareArtifact = webpack.artifacts[artifactInstance.id] if (!bareArtifact) { return }       var artifactEffect = bareArtifact.effect.effects[0] var artifactEffect_secondary = bareArtifact.effect.effects[1] if (artifactInstance.stages.currentStage > 0) { artifactEffect = bareArtifact.stages.stages[artifactInstance.stages.currentStage - 1].changes.effects[0] artifactEffect_secondary = bareArtifact.stages.stages[artifactInstance.stages.currentStage - 1].changes.effects[1] }       Object.assign(this, bareArtifact, artifactInstance); this.artifactInstance = artifactInstance this.stars = artifactInstance.stages.currentStage + 1; this.dupes = (this.dupes || 0) this.maxStars = this.getMaxStars this.effect = Object.assign({}, artifactEffect, bareArtifact.effect); this.effect.effect_secondary = Object.assign({}, artifactEffect_secondary); if (bareArtifact.effect.events && artifactEffect.events) { this.effect.events = Object.assign({}, bareArtifact.effect.events, artifactEffect.events) }   }

BattleArtifact.prototype.getMaxStars = function { var maxStars = this.stars var dupes = this.dupes while (maxStars < 5 && dupes >= this.cost[maxStars - 1].dupes) { dupes -= this.cost[maxStars - 1].dupes maxStars++ }       while (maxStars > 1 && dupes < 0) { maxStars-- dupes += this.cost[maxStars - 1].dupes }       return maxStars }

BattleArtifact.prototype.changeStars = function (newStars) { const origStars = this.stars while (this.stars < newStars && this.stars < 5) { const dupesCost = this.cost[this.artifactInstance.stages.currentStage].dupes this.artifactInstance.dupes = (this.artifactInstance.dupes || 0) - dupesCost this.dupes -= dupesCost this.artifactInstance.stages.currentStage++ this.stars++ }       while (this.stars > newStars && this.stars > 1) { this.artifactInstance.stages.currentStage-- this.stars-- const dupesCost = this.cost[this.artifactInstance.stages.currentStage].dupes this.artifactInstance.dupes = (this.artifactInstance.dupes || 0) + dupesCost this.dupes += dupesCost }       return this.stars !== origStars }

function BattleCard(cardInstance, csetCounts, options) { Object.assign(this, cardInstance, webpack.cards.find(function (stats) { return stats.cardId === cardInstance.cardId; })); this.cardInstance = cardInstance this.evolution.currentStage = cardInstance?.evolution?.currentStage || 0 this.stars = this.evolution.currentStage + 1 this.level = this.level || 1; this.stoneLevels = (this.stones || []).map(stoneId => (stoneId - 1) % 10 + 1) if (this.stoneLevels.length === 0) { this.stoneLevels.push(0) } if (this.stoneLevels.length === 1 && this.sockets >= 2) { this.stoneLevels.push(0) } const stoneBonuses = (this.stones || []).map(function (stoneId) {           return [10, 25, 40, 70, 100, 130, 180, 220, 270, 350][(stoneId - 1) % 10];        }); this.stoneBonus = stoneBonuses.reduce(function (acc, cur) { return acc + cur; }, 0); this.soulBindBonus = [0, 25, 50, 75, 105, 135, 165, 200, 235, 270, 310, 355, 405][this.sbl || 0]; if (applyEventBonus) { this.eventMultiplier = (options.currentEventTag && this.tag == options.currentEventTag) ? 3 : 1       } else { this.eventMultiplier = 1 }

//edited by light-angel const universalCset = webpack.cardSets.find(cset => cset.universal).id       const universalCount = csetCounts[universalCset] || 0 //      const csetCount = this.cset //          ? this.cset == universalCset //              ? Math.max(...Object.values(csetCounts)) + universalCount //              : csetCounts[this.cset] + universalCount //          : 0 //       this.csetBonus = [0, 0, 0, 0, 0.05, 0.10, 0.25, 0.75][csetCount];

if (this.cset == universalCset) { var total_cards_in_set = Object.values(csetCounts) || 0 //console.log('total_cards_in_set: ' + total_cards_in_set); - return 6,1 var total_cards_in_set_int = Object.values(csetCounts)[0] || 0 //console.log('total_cards_in_set_int: ' + total_cards_in_set_int); - return 6 this.csetBonus = [0, 0, 0, 0, 0.05, 0.10, 0.25, 0.75][(total_cards_in_set_int + universalCount) || 0] } else {

this.csetBonus = [0, 0, 0, 0, 0.05, 0.10, 0.25, 0.75][(csetCounts[this.cset] + universalCount) || 0];

} //console.log('csetCounts[this.cset]: ' + csetCounts[this.cset]); //console.log('Object.values(csetCounts) ' + Object.values(csetCounts)) //console.log('this.csetBonus: ' + this.csetBonus); //the end of editing by light-angel

const guildBonuses = options.user?.guildMember?.bonuses || [] this.guildWeapon = guildBonuses[10 + this.color] || 0 this.guildRune = guildBonuses[30 + this.color] || 0 this.guildElixir = guildBonuses[50 + this.color] || 0

const bareAttack = Math.ceil(this.baseAttack * (this.stars + 0.15 * this.level)) * this.eventMultiplier; const attack = Math.floor((bareAttack + this.stoneBonus + this.soulBindBonus) * (1 + this.guildElixir)); this.attack = attack + Math.ceil(attack * this.csetBonus);

const bareHp = 3 * Math.ceil(this.baseHp * (this.stars + 0.15 * this.level)) * this.eventMultiplier; const hp = Math.floor((bareHp + this.stoneBonus + this.soulBindBonus) * (1 + this.csetBonus )); this.baseHp = hp; this.hp = this.baseHp + Math.ceil(hp * this.guildElixir); this.initialHp = this.hp

this.power = Math.floor((this.attack + this.hp / 3) / 2) const rarityMultiplierTP = [, 100, 125, 150, 175, 225, 300][this.rarity] const bareTP = rarityMultiplierTP * (this.stars + 0.15 * this.level) + this.stoneBonus this.listTP = Math.floor((bareTP * this.eventMultiplier + this.soulBindBonus) * (1 + this.guildWeapon + this.guildElixir) / 5) * 5 this.csetTP = Math.floor((bareTP * (this.eventMultiplier + this.csetBonus) + this.soulBindBonus) * (1 + this.guildWeapon + this.guildElixir) / 5) * 5 this.buildingTP = Math.floor((bareTP * (this.eventMultiplier + this.csetBonus) + this.soulBindBonus) * (1.5 + this.guildWeapon + this.guildElixir + this.guildRune) / 5) * 5

if (this.effect) { const be = webpack.battleEffects[this.effect] const ce = webpack.cardEffects[this.effect] this.effect = Object.assign({}, be, ce); if (be && be.events && ce && ce.events) { this.effect.events = Object.assign({}, ce.events, be.events) }           if (options.verbose) console.log('this battlecard', this) }       this.equipArtifact(this.aid, options) }

BattleCard.prototype.clone = function { const newBattleCard = Object.assign({}, this) Object.setPrototypeOf(newBattleCard, BattleCard.prototype) return newBattleCard }

BattleCard.prototype.canEquipArtifact = function (aid) { return !this.effect || this.artifact }

BattleCard.prototype.equipArtifact = function (aid, options) { if (!options.user) { return }       const artifactInstance = options.user.hero.artifacts.items.find(function (a) { return a.id == aid; }) this.equipArtifactInstance(artifactInstance) if (options.verbose) console.log('this battlecard', this) }

BattleCard.prototype.equipArtifactInstance = function (artifactInstance) { if (this.artifact) { this.aid = undefined this.artifact = undefined this.effect = undefined }       if (!artifactInstance) { return }       this.aid = artifactInstance.id        this.artifact = new BattleArtifact(artifactInstance) this.effect = this.artifact.effect this.artifact.effect = undefined; // Make sure we don't accidentally refer to this one }

BattleCard.prototype.toString = function { function formatWidth(value, width) { return ('               ' + value).slice(-Math.max(value.toString.length, width)) }

const name = (this.name + '              ').slice(0, 15) var bonus = formatWidth(this.stars, 2) + '* lv' + formatWidth(this.level, 2) if (this.eventMultiplier > 1) { bonus += ' x' + this.eventMultiplier } else { bonus += '  ' }       bonus += ' ◊' + formatWidth(this.stoneBonus, 3) + ' ⛓' + formatWidth(this.soulBindBonus, 2) bonus += ' ⚗️' + (this.guildElixir * 100).toFixed(0) + '%' if (this.csetBonus) { bonus += ' ' + (this.csetBonus * 100).toFixed(0) + '%' }       bonus = (bonus + '               ').slice(0, Math.max(bonus.length, 25)) var str = name + ' ' + bonus + ' ' + this.attack + '/' + this.hp           + ' ' + (this.attack * this.hp / 1e6).toFixed(1) + 'M'; if (this.artifact) { str += ' ' + this.artifact.name + ' ' + this.artifact.stars } else if (this.effect) { str += ' ' + this.effect.name; }       return str; };

BattleCard.prototype.getImageHashOfGirl = function { switch (displayStyle) { case 0: return this.icons._1x case 1: return this.images._1x case 2: return this.evolution.stages[0].changes.images._1x }   }

BattleCard.prototype.getSeduceDupesForStage = function (currentStage) { if (this.evolveDupes) { return currentStage < this.evolveDupes.length ? this.evolveDupes[currentStage] : Infinity }       return [ [2, 4, 3, 2, 1, 1],           [3, 8, 6, 4, 2, 1],            [4, 64, 48, 32, 16, 1],            [5, 256, 192, 128, 64, 1],            [6, 280, 210, 140, 70, 2],            [7, 310, 230, 155, 75, 2],            [8, 340, 255, 170, 85, 2],            [9, 375, 280, 185, 95, 2],            [10, 415, 310, 205, 105, 3],            [11, Infinity, Infinity, Infinity, Infinity, Infinity] ][currentStage][this.rarity] }

BattleCard.prototype.getTotalDupes = function { let totalDupes = (this.dupes || 0) + 1 for (let star = 1; star < this.stars; ++star) { totalDupes += this.getSeduceDupesForStage(star - 1) }       return totalDupes }

BattleCard.prototype.getHighestSeduction = function { let remainingDupes = this.getTotalDupes - 1 let highestStar = 1 while (true) { const dupesToNextSeduction = this.getSeduceDupesForStage(highestStar - 1) if (remainingDupes < dupesToNextSeduction) { break }           highestStar++ remainingDupes -= dupesToNextSeduction }       return { highestStar: highestStar, remainingDupes: remainingDupes } }

function BattlePlayer(name, hp, deck, options) { this.name = name; this.hp = hp; this.deck = deck; this.options = options; this.librarySize = options.librarySize || 7; this.fixedEnemy = options.fixedEnemy this.unlimitedLibrary = options.unlimitedLibrary; const csetCounts = {} for (const cardIndex in deck) { const cardInstance = deck[cardIndex]; if (options.verbose) console.log('cardInstance', cardInstance) const cardStats = webpack.cards.find(function (c) { return c.cardId == cardInstance.cardId; }) if (cardStats.cset) { csetCounts[cardStats.cset] = (csetCounts[cardStats.cset] || 0) + 1; }       }        this.csetCounts = csetCounts; // Status this.table = [null, null, null]; this.library = deck.slice(0, this.librarySize).map(function (card) {           return new BattleCard(card, csetCounts, options);        }); if (this.verbose) console.log('library', this.library); this.power = this.library.reduce((acc, cur) => acc + cur.power, 0) this.sumProduct = this.library.reduce((acc, cur) => acc + cur.attack * cur.hp, 0) this.damageDealt = 0; }

BattlePlayer.getLevelFromExp = function (exp) { return -1 * Math.floor(Math.log(300 / (360 + exp)) / Math.log(1.2)) - 1; };   BattlePlayer.getHpFromLevel = function (level) { return Math.floor(level * level / 2 + 5 * level + 200); };   BattlePlayer.getHpFromExp = function (exp) { return BattlePlayer.getHpFromLevel(BattlePlayer.getLevelFromExp(exp)); };

BattlePlayer.createMissionEnemy = function (mission, options) { mission.hp = BattlePlayer.getHpFromExp(mission.enemy.exp) if (mission.enemy._hp) { // Boss event console.log('mission ' + mission.displayName + ' is boss event') mission.hp = mission.enemy._hp mission.unlimitedLibrary = true }       const extendedOptions = Object.assign({}, options) extendedOptions.librarySize = mission.enemy_cards ? mission.enemy_cards.length : 7; extendedOptions.fixedEnemy = (mission.enemy_cards !== undefined) extendedOptions.unlimitedLibrary = mission.unlimitedLibrary return new BattlePlayer(mission.displayName, mission.hp, mission.enemy.cardGame.cards, extendedOptions); }

BattlePlayer.createHero = function (user, deckName, cardInstanceIds, options) { const deck = cardInstanceIds.map(function (instanceId) {           return user.hero.cardGame.cards.find(function (cardInstance) { return cardInstance.id === instanceId; });        }) const extendedOptions = Object.assign({}, options) extendedOptions.user = user return new BattlePlayer(deckName, BattlePlayer.getHpFromExp(user.hero.exp), deck, extendedOptions) }

BattlePlayer.prototype.clone = function { const newBattlePlayer = Object.assign({}, this) newBattlePlayer.table = this.table.map(function (c) { return c ? c.clone : null; }) newBattlePlayer.library = this.library.map(function (c) { return c.clone; }) Object.setPrototypeOf(newBattlePlayer, BattlePlayer.prototype) return newBattlePlayer }

BattlePlayer.prototype.liveCards = function { return this.table.filter(function (card) { return card && card.hp > 0 }).length + this.library.length }

BattlePlayer.prototype.fillTable = function (emitter) { for (var i = 0; i < this.table.length; ++i) { if ((!this.table[i] || (this.table[i].hp <= 0)) && (this.library.length > 0)) { const card = this.library.shift if (this.unlimitedLibrary) { this.library.push(card.clone) }                   card.seqNr = emitter.sequenceGenerator.getSeqNr if (card.effect) {

if (!(card.effect.id in cardEffects)) { if (this.options.warnedUnsupportedEffects && !(card.effect.id in this.options.warnedUnsupportedEffects)) { console.warn('CombatSimulator: ' + card.effect.id + ' not supported') this.options.warnedUnsupportedEffects[card.effect.id] = true }                   } else { for (const event in cardEffects[card.effect.id]) { const cardEvent = Battle.getCardEvent(event, card) if (event === 'afterCardsAppeared') { emitter.once(cardEvent, cardEffects[card.effect.id][event]) } else { emitter.on(cardEvent, cardEffects[card.effect.id][event]) }                       }                    }                }                this.table[i] = card }       }    }

BattlePlayer.prototype.toString = function { var tableStr if (this.table[0] !== null) { tableStr = this.table.map(function (c) { return c.toString; }).join('\n'); }       const libraryStr = this.library.map(function (c) { return c.toString; }).join('\n'); var str = "'" + this.name + "'" + ' hp=' + this.hp.toLocaleString('en-US') + ' power=' + this.power.toLocaleString('en-US') + ' sumProduct=' + (this.sumProduct / 2e6).toFixed(1) + 'M' + '\n'; if (tableStr) { str += 'Table:\n' + tableStr + '\nLibrary:\n' }       str += libraryStr; return str; }

function SequenceGenerator { this.seqNr = 0 }

SequenceGenerator.prototype.getSeqNr = function { return ++this.seqNr }

function Emitter(options) { this.options = options // Status this.sequenceGenerator = new SequenceGenerator this.handlers = {} this.onceEvents = {} this.unsupported = {} }

Emitter.prototype.on = function (event, callback) { if (this.options.verbose) console.log('on event=' + event, callback) this.handlers[event] = callback }

Emitter.prototype.once = function (event, callback) { if (this.options.verbose) console.log('once event=' + event, callback) this.handlers[event] = callback this.onceEvents[event] = true }

Emitter.prototype.emit = function (event, obj) { if (event in this.handlers) { if (this.options.verbose) console.log('emit event=' + event, obj) this.handlers[event](obj) if (event in this.onceEvents) { delete this.handlers[event] delete this.onceEvents[event] }       }    }

Emitter.prototype.hasHandler = function (event) { return event in this.handlers }

function BattleEvent(attackingPlayer, defendingPlayer, tableIndex, battle) { this.attackingPlayer = attackingPlayer this.defendingPlayer = defendingPlayer this.tableIndex = tableIndex this.battle = battle this.attacker = attackingPlayer.table[tableIndex] this.defender = defendingPlayer.table[tableIndex] this.effect = attackingPlayer.table[tableIndex].effect this.attackingTable = attackingPlayer.table this.defendingTable = defendingPlayer.table this.verbose = this.battle.verbose this.chance = this.effect?.chance if (this.chance && battle.worstCase) { switch (battle.worstCase) { case "": break case "Worst": this.chance = attackingPlayer === battle.hero ? 0 : 1; break case "Best": this.chance = attackingPlayer === battle.hero ? 1 : 0; break case "Random": this.chance = Math.random < this.chance ? 1 : 0; break default: throw `Unrecognized random option ${battle.worstCase}` }       }    }

BattleEvent.prototype.dealHitDamage = function (damage) { if (this.verbose) console.log(`${this.attacker.name} attacks ${this.defender.name} for ${damage} damage`) const actualDamage = Math.min(this.defender.hp, damage) this.defender.hp -= damage

this.attackingPlayer.damageDealt += damage if (this.defendingPlayer.unlimitedLibrary) { if (this.verbose) console.log('unlimitedLibrary: card to card damage ' + damage + ' to defendingPlayer.hp=' + this.defendingPlayer.hp) this.defendingPlayer.hp -= damage }

const cardEvent = Battle.getCardEvent('hit', this.defender) const hitBattleEvent = new BattleEvent(this.attackingPlayer, this.defendingPlayer, this.tableIndex, this.battle) hitBattleEvent.effect = undefined hitBattleEvent.damage = actualDamage this.battle.emitter.emit(cardEvent, hitBattleEvent)

if (this.defender.hp <= 0) { const cardEvent = Battle.getCardEvent('cardDead', this.defender) this.battle.emitter.emit(cardEvent, new BattleEvent(this.attackingPlayer, this.defendingPlayer, this.tableIndex, this.battle)) }   }

BattleEvent.prototype.dealEffectHpDamage = function (damage, target) { //if card has immunity from adamant will if(target.has_immunity == true){ damage = 0; console.log ( target.name + ' has immunity, exit now. Could not manifest damage effect on ' + target.name); }

//if damage is not a number if( isNaN(damage) ){ var old_damage = damage; damage = 0; console.log ( 'Damage was: ' + old_damage + '. Reset to zero damage: ' + damage); }

if (this.verbose) console.log(`${this.effect.id} inflicts ${damage} damage to ${target.name}`) target.hp -= damage

if (target.hp <= 0) { const cardEvent = Battle.getCardEvent('cardDead', target) this.battle.emitter.emit(cardEvent, new BattleEvent(this.attackingPlayer, this.defendingPlayer, this.tableIndex, this.battle)) }   }

function Battle(hero, enemy, options) { this.hero = hero this.enemy = enemy this.verbose = options.verbose this.worstCase = options.worstCase this.emitter = new Emitter(options) this.cardSequenceGenerator = new SequenceGenerator

this.fillTable while ((hero.hp > 0) && (enemy.hp > 0) && (hero.liveCards > 0) && (enemy.liveCards > 0)) { this.exchangeRound this.fillTable //console.log("End of a Round.") }         console.log("End of a Battle.") if (this.verbose) console.log('Battle result hero.hp=' + hero.hp + ' live=' + hero.liveCards + ' dmgDealt=' + hero.damageDealt) this.result = { win: (hero.hp > 0) && (hero.liveCards > 0) ? 1 : 0,           stars: Math.min(3, (hero.hp > 0) ? hero.liveCards : 0), damage: hero.damageDealt }   }

Battle.prototype.getCardSeqNr = function { this.cardSeqNr = (this.cardSeqNr || 0) + 1 return this.cardSeqNr }

Battle.prototype.fillTable = function { if (this.verbose) console.log('fillTable hero=' + this.hero.toString + '\n' + 'enemy=' + this.enemy.toString) this.hero.fillTable(this.emitter) this.enemy.fillTable(this.emitter) for (var tableIndex = 0; tableIndex < this.hero.table.length; ++tableIndex) { this.applySlotEffect('afterCardsAppeared', tableIndex) }   }

Battle.prototype.exchangeRound = function { if (this.verbose) console.log('exchangeRound') this.applyAfterRoundStart; for (var tableIndex = 0; tableIndex < this.hero.table.length; ++tableIndex) { if (this.hero.hp > 0 && this.enemy.hp > 0) { this.applySlotEffect('beforeHit', tableIndex) this.exchangeSlotHit(tableIndex) this.applySlotEffect('afterHit', tableIndex) }       }        this.applyRoundEnd; }

Battle.prototype.applyAfterRoundStart = function { for (var tableIndex = 0; tableIndex < this.hero.table.length; ++tableIndex) { this.applySlotEffect('afterRoundStart', tableIndex) }   }

Battle.prototype.applySlotEffect = function (event, tableIndex) { this.applyCardEffect(event, this.hero, this.enemy, tableIndex) this.applyCardEffect(event, this.enemy, this.hero, tableIndex) }

Battle.prototype.applyRoundEnd = function { for (var tableIndex = 0; tableIndex < this.hero.table.length; ++tableIndex) { delete this.hero.table[tableIndex].frozenChance; delete this.enemy.table[tableIndex].frozenChance; delete this.hero.table[tableIndex].missChance; delete this.enemy.table[tableIndex].missChance; delete this.hero.table[tableIndex].dodge; delete this.enemy.table[tableIndex].dodge;

//if the card has been icefrozen then convert to frozenchance to freeze it for next round. if(this.hero.table[tableIndex].icefrozenChance) { this.hero.table[tableIndex].frozenChance = this.hero.table[tableIndex].icefrozenChance; delete this.hero.table[tableIndex].icefrozenChance;

// console.log( this.hero.table[tableIndex].name + ' is-frozen for next round ' + ' with chance ' + this.hero.table[tableIndex].frozenChance) }           if(this.enemy.table[tableIndex].icefrozenChance) { this.enemy.table[tableIndex].frozenChance = this.enemy.table[tableIndex].icefrozenChance; delete this.enemy.table[tableIndex].icefrozenChance;

// console.log( this.enemy.table[tableIndex].name + ' is-frozen for next round ' + ' with chance ' + this.enemy.table[tableIndex].frozenChance) }       }    }

Battle.getCardEvent = function (event, card) { return event + '-' + card.name + '-' + card.seqNr }

Battle.prototype.applyCardEffect = function (event, attackingPlayer, defendingPlayer, tableIndex) { const attackingCard = attackingPlayer.table[tableIndex] const defendingCard = defendingPlayer.table[tableIndex] if ((attackingCard.hp > 0) || (attackingCard.has_last_stand)) { const cardEvent = Battle.getCardEvent(event, attackingCard)

if (defendingPlayer.hasSilence && defendingPlayer.hasSilence.hp > 0) { if (this.verbose && this.emitter.hasHandler(cardEvent)) { console.log(`Skip '${cardEvent}' due to silence`) }               return }

if (event === 'afterHit' && attackingCard.dreaming && attackingCard.hp <= attackingCard.initialHp / 2) { //remove dreaming if the card has less than half hp. attackingCard.dreaming = undefined if (this.verbose) console.log('Dreaming on ' + attackingCard.name + ' expires') }

this.emitter.emit(cardEvent, new BattleEvent(attackingPlayer, defendingPlayer, tableIndex, this)) }   }

Battle.prototype.exchangeSlotHit = function (tableIndex) { const heroCard = this.hero.table[tableIndex] const enemyCard = this.enemy.table[tableIndex]

if (heroCard.hp > 0 && enemyCard.hp > 0) { this.doCardToCardHit(this.hero, heroCard, this.enemy, enemyCard, tableIndex) this.doCardToCardHit(this.enemy, enemyCard, this.hero, heroCard, tableIndex) } else if (heroCard.hp > 0) { this.doCardToPlayerHit(this.hero, heroCard, this.enemy) } else if (enemyCard.hp > 0) { this.doCardToPlayerHit(this.enemy, enemyCard, this.hero) }           //console.log ('End of Hit actions'); }

Battle.prototype.doCardToCardHit = function (attackingPlayer, attacker, defendingPlayer, defender, tableIndex) { var damage = Math.floor(attacker.attack * Battle.getColorAttackMultiplier(attacker, defender)) if (attacker.dreaming && attacker.dreaming.hp > 0) { damage = 0 if (this.verbose) console.log('Dreaming ' + attacker.name + ' reduces damage to ' + damage) }       if (attacker.frozenChance) { damage -= Math.floor(damage * attacker.frozenChance) if (this.verbose) console.log('Frozen ' + attacker.name + ' with chance ' + attacker.frozenChance + ' reduces damage to ' + damage) }       if (attacker.missChance) { damage -= Math.floor(damage * attacker.missChance) if (this.verbose) console.log('Miss chance for ' + attacker.name + ' with chance ' + attacker.missChance + ' reduces damage to ' + damage) }       if (defender.dodge) { damage -= Math.floor(damage * defender.dodge) if (this.verbose) console.log('dodge ' + defender.dodge + ' reduces damage to ' + defender.name + ' to ' + damage + ' damage') }       if (defender.damageReduction) { damage -= Math.floor(damage * defender.damageReduction) if (this.verbose) console.log('damage reduction ' + defender.damageReduction + ' reduces damage to ' + defender.name + ' to ' + damage + ' damage')

//delete Shield after each hit, the card needs to get another shield in next round. defender.damageReduction = 0; }       if (defender.RSdamageReduction) { damage -= Math.floor(damage * defender.RSdamageReduction) if (this.verbose) console.log('damage reduction ' + defender.RSdamageReduction + ' reduces damage to ' + defender.name + ' to ' + damage + ' damage')

//delete Shield after each hit, the card needs to get another shield in next round. //defender.damageReduction = 0; }       // if (defender.jewelShield) { //    damage -= Math.floor(damage * defender.jewelShield) //    if (this.verbose) console.log('jewelShield reduces damage to ' + defender.name + ' to ' + damage + ' damage') // }       if (damage > 0) { const battleEvent = new BattleEvent(attackingPlayer, defendingPlayer, tableIndex, this) battleEvent.dealHitDamage(damage) }   }

Battle.prototype.doCardToPlayerHit = function (attackingPlayer, attacker, defendingPlayer) { var damage = attacker.attack if (this.verbose) console.log(attacker.name + ' attacks opponent for ' + damage + ' damage') if (attacker.frozenChance) { damage -= Math.floor(damage * attacker.frozenChance) if (this.verbose) console.log('Frozen ' + attacker.name + ' with chance ' + attacker.frozenChance + ' reduces damage to ' + damage) }       defendingPlayer.hp -= damage attackingPlayer.damageDealt += damage

if (attacker.damageReduction) { //delete Shield after each hit even if the opponent card is dead, the card needs to get another shield in next round. attacker.damageReduction = 0; }

}

Battle.getColorAttackMultiplier = function (attacker, defender) { if ((attacker.color == 1 && defender.color == 2)           || (attacker.color == 2 && defender.color == 3)            || (attacker.color == 3 && defender.color == 1)            || (attacker.color == 4 && defender.color == 5)            || (attacker.color == 5 && defender.color == 4)) { var CAM = 1.5 + (attacker.guildRune || 0) + (attacker.AmplifyColorAttackMultiplier || 0); if(attacker.AmplifyColorAttackMultiplier > 0) console.log(attacker.name + ' has CAM of: ' + CAM); return CAM; }       return 1 }

const cardEffects = { "AbyssalWrath": { // Verified //Once, hits the cards near the opposite one with {powerPC}% of attack. //Once, hits the cards near the opposite one with 55% of attack. //Once, hits the cards near the opposite one with 20/30/40/50/60% of attack.

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

var damage = Math.floor(attacker.attack * effect.power) battleEvent.defendingTable .filter(function (card, index) { return Math.abs(index - battleEvent.tableIndex) === 1 }) .forEach(function (defender) {                       battleEvent.dealEffectHpDamage(damage, defender)                    })

attacker.effectStruck = true }

}       }, "AlliedStrike": { // Verified //Every round increases card's attack by {powerPC}% for each alive friendly card. //Every round increases card's attack by 40% for each alive friendly card. //no artifact with this effect. //events:{clear:{afterHit:true}}

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender var Attackers_Alive = 0;

battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (attacker) {

Attackers_Alive += 1 })

attacker.allied_attack = attacker.attack; attacker.attack += Math.floor(attacker.attack * effect.power * (Attackers_Alive-1)) // if (battleEvent) console.log(effect.id + ' increased attack of ' + attacker.name + ' to ' + attacker.attack + ' using ' + (Attackers_Alive - 1) + ' friendly cards alive.' + ' and bonus of: ' +  effect.power * (Attackers_Alive-1))

},             'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

attacker.attack = attacker.allied_attack; // if (battleEvent) console.log(attacker.name + ' is going to original attack of ' + attacker.allied_attack + ' end of round.' )

}       }, "Backstab": { // Verified // "Before each hit does additional damage to all opposite cards: {powerPC}% HP" //Before each hit does additional damage to all opposite cards: 20% HP //Before each hit does additional damage to all opposite cards: 3%, 6%, 10%, 15%, 20% HP //it works on base HP not current HP           'beforeHit': function (battleEvent) { const effect = battleEvent.effect battleEvent.defendingTable .filter(function (defender) { return defender.hp > 0 }) .forEach(function (defender) {                       const damage = Math.floor(defender.initialHp * effect.power)                        battleEvent.dealEffectHpDamage(damage, defender)                        //if (battleEvent) console.log(effect.id + ' reduces hp of ' + defender.name + ' to ' + defender.hp + ' with damage of: ' + damage)                    }) //if (battleEvent) console.log('End of strike'); }       }, "Exorcism": { // Verified //"Every odd round attacks enemy cards with {powerPC}% of their base HP." //Effect: "Every odd round attacks enemy cards with 30% of their base HP." //power:.3;events:{afterRoundStart:!0} //it works on base HP not current HP, like backstab but only on odd rounds. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect ; const attacker = battleEvent.attacker ;

//initialize attacker.battle_round first time if( typeof attacker.battle_round == 'undefined') {attacker.battle_round = true ;}

if(attacker.battle_round == true){ battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                       const damage = Math.floor(card.initialHp * effect.power)                        battleEvent.dealEffectHpDamage(damage, card)                       // if (battleEvent) console.log(effect.id + ' reduces hp of ' + card.name + ' to ' + card.hp + ' with damage of: ' + damage)                    }) }                       attacker.battle_round = !attacker.battle_round ; //if (battleEvent) console.log('attacker.battle_round is: ' + attacker.battle_round); // if (battleEvent) console.log('End of strike'); }       }, "BladeDance": { //"Each turn the card does additional damage of {powerPC}% from it's attack to each opposite card with {chancePC}% chance." //Each turn the card does additional damage of 50% from it's attack to each opposite card with 50% chance. //No artifact //Run: Every Round. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {                       const damage = Math.floor(attacker.attack * effect.power * battleEvent.chance)                        battleEvent.dealEffectHpDamage(damage, defender)                    }) }       },        "Bleeding": { // Secondary effect of Claws//discarded, not needed anymore. 'afterHit': function (battleEvent) { const attacker = battleEvent.attacker const damage = attacker.bleeding battleEvent.effect = webpack.cardEffects['Bleeding'] battleEvent.dealEffectHpDamage(damage, attacker) },       }, "ChainLight": { // Verified // "A bolt deals {powerPC}% AT dmg at the first strike, then jumps to a neighbor. Each jump deals twice less dmg." //Effect: A bolt deals 60% AT dmg at the first strike, then jumps to a neighbor. Each jump deals twice less dmg. //Artifact: A bolt deals 15/25/40/55/65% AT dmg at the first strike, then jumps to a neighbor. Each jump deals twice less dmg. //One time only, like fireball. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the effect is not applied yet. if (!attacker.effectStruck){

//if defender is dead then do not execute the effect. if (defender.hp <= 0) { return; }

// calculate full chain damage inflicted on opponent card. var damage = Math.floor(attacker.attack * effect.power) battleEvent.dealEffectHpDamage(damage, defender)

//if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did full chainlight damage of ' + damage + ' to opponent ' + defender.name)

// calculate damage done to neighbors enemy cards, if they are alive. switch (battleEvent.tableIndex) { case 0: case 2: //attacker is the last card on the right or first on the left... const first_neighbor = battleEvent.defendingTable[1]; if (first_neighbor.hp > 0) { const chainedDamage = Math.floor(damage / 2) battleEvent.dealEffectHpDamage(chainedDamage, first_neighbor)

const last_neighbor = battleEvent.defendingTable[2 - battleEvent.tableIndex]; if (last_neighbor.hp > 0) { const nextChainedDamage = Math.floor(damage / 3) battleEvent.dealEffectHpDamage(nextChainedDamage, last_neighbor)

//if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did 1/2 chain damage of ' + chainedDamage + ' to neighbor1 ' + first_neighbor.name) //if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did 1/3 chain damage of ' + nextChainedDamage + ' to neighbor2 ' + last_neighbor.name) }                   }                    break; case 1: //attacker is on the middle const neighbors = [battleEvent.defendingTable[0], battleEvent.defendingTable[2]] damage = Math.floor(damage / 2); neighbors.forEach(function (neighbor) {                       if (neighbor.hp > 0) {                            battleEvent.dealEffectHpDamage(damage, neighbor) //if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did chain damage of ' + damage + ' to side neighbor ' + neighbor.name)                           damage = Math.floor((damage *2)/ 3);                         }                    }) break; }

//if (battleEvent) console.log('end of strike'); attacker.effectStruck = true }            }        }, "WisdomTome": { // Done //Every round casts one of the most powerfull spells {afterRoundStart:!0} //effects:["ChainLight","Thunder","EnchantingSong","Heal"] // "When appears, gives a random effect to arcane card: ChainLight, Thunder, EnchantingSong, Heal"

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender //cancel effect done and redo the effect attacker.effectStruck = false;

const cardEvent = Battle.getCardEvent('beforeHit', attacker)

//select an effect random_effect = Math.floor(Math.random*5);

switch (random_effect) { case 0: effect.power = 0.6; battleEvent.battle.emitter.on(cardEvent, cardEffects['ChainLight']['beforeHit']) break; case 1: effect.power = 0.5; battleEvent.battle.emitter.on(cardEvent, cardEffects['Heal']['beforeHit']) break; case 2: effect.power = 0.5;  battleEvent.effect.chance = 0.5; battleEvent.battle.emitter.on(cardEvent, cardEffects['Thunder']['beforeHit']) break; case 3: effect.power = 0.3;  battleEvent.effect.chance = 0.5; battleEvent.battle.emitter.on(cardEvent, cardEffects['EnchantingSong']['beforeHit']) break; case 4: effect.power = 0.3;  battleEvent.effect.chance = 0.5; battleEvent.battle.emitter.on(cardEvent, cardEffects['EnchantingSong']['beforeHit']) break; }

},   }, "Thunder": { // Done, //"With a {chancePC}% chance an electric strike hits a random card with {powerPC}% of attack at the first strike, then affects its neighbor with twice less damage." //Effect: "With a 50% chance an electric strike hits a random card with 50% of attack at the first strike, then affects its neighbor with twice less damage." //power:0.5,change:0.5,chance:0.5 //affecting all cards, the gap has no importance.

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//calculate damage to first card hit by the thunder. var damage = Math.floor(attacker.attack * effect.power * battleEvent.chance)

//do the damage to each of the defending cards that are alive, from left to right battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender,index) {

//set damage as 1, 1/2, 1/3 ; from left to right. damage = Math.floor(damage/(index+1));

//if defender has immunity it is handeled in the dealEffectHpDamage function. battleEvent.dealEffectHpDamage(damage, defender)

//if (battleEvent) console.log(effect.id + ' did damage to ' + defender.name + ' in value of ' + damage) })

// if (battleEvent) console.log('end of thunder strike');

}       }, "Chilling": { // Verified // "Decrease the attack of all rival cards by {powerPC}%. Can affect each card only once." //Decrease the attack of all rival cards by 20%. Can affect each card only once. //Decrease the attack of all rival cards by 5/10/15/20/25%. Can affect each card only once. //Run: Every round. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {

// if defender is not chilled then set to false. if(defender.is_chilled != true) defender.is_chilled = false;

if(defender.is_chilled != true){ //check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {var immunity = 1}

defender.attack -= Math.floor(defender.attack * effect.power * immunity) // set defender as chilled (already affected by the effect). defender.is_chilled = true; //   if (battleEvent) console.log(effect.id + ' reduces attack of ' + defender.name + ' to ' + defender.attack + '. Immunity is: ' + !immunity) } else { //   if (battleEvent) console.log(effect.id + ' cannot reduces attack of ' + defender.name + '. The card is already affected.') }                   })

//if (battleEvent) console.log('end of strike'); }       }, "EnchantingSong": { //Done // "Each turn the card with 50% chance decreases the attack of all the opponent cards by {powerPC}%. Can`t decrease the attack of the same card twice." //Each turn the card with 50% chance decreases the attack of all the opponent cards by 30%. Can`t decrease the attack of the same card twice. //for artifact: power: 5%, 10%, 15%, 20%, 25% chance: 50%

'beforeHit': function (battleEvent) { const effect = battleEvent.effect

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {

//check for immunity, if true multiple by zero. if(card.has_immunity == true) {var immunity = 0} else {var immunity = 1}

//if the card has not been EnchantingSong effect applied yet. if(!card.has_EnchantingSong){ card.attack -= Math.floor(card.attack * effect.power * battleEvent.chance * immunity) card.has_EnchantingSong = true;

// if (battleEvent) console.log(effect.id + ' reduces attack of ' + card.name + ' to ' + card.attack) } else { //if (battleEvent) console.log(effect.id + ' already applied on ' + card.name ) }                   })            }        }, "Enchanting Song": { //Done // "Each turn the card with 50% chance decreases the attack of all the opponent cards by {powerPC}%. Can`t decrease the attack of the same card twice." //Each turn the card with 50% chance decreases the attack of all the opponent cards by 30%. Can`t decrease the attack of the same card twice. //for artifact: power: 5%, 10%, 15%, 20%, 25%  chance: 50%

'beforeHit': function (battleEvent) { const effect = battleEvent.effect

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {

//check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {var immunity = 1}

//if the card has not been EnchantingSong effect applied yet. if(!defender.has_EnchantingSong){ defender.attack -= Math.floor(defender.attack * effect.power * battleEvent.chance * immunity) defender.has_EnchantingSong = true;

//if (battleEvent) console.log(effect.id + ' reduces attack of ' + defender.name + ' to ' + defender.attack) } else { //if (battleEvent) console.log(effect.id + ' already applied on ' + defender.name ) }                   })            }        }, "DragonTear": { //Done // "Once, increases friendly card's HP by XX% of their base HP" //Effect:Once, increases friendly card's HP by 40% of their base HP //Artifact:Once, increases friendly card's HP by 15/20/25/30/40% of their base HP

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the effect is not applied yet. if (!attacker.effectStruck){

battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {

card.hp += Math.floor(card.initialHp * (effect.power || 0));

// if (battleEvent) console.log(effect.id + ' increased HP of ' + card.name + ' from ' + card.initialHp + ' to ' + card.hp + ' using ' + effect.power*100 +'%') })                   //if (battleEvent) console.log('end of round');                    attacker.effectStruck = true                                                  }             }        }, "Motivation": { //Done // "Increases the friendly card's AT and HP by {powerPC}%" //Increases the friendly card's AT and HP by 20% //Run: Every Round, like mass-rage, a card cannot be affected twice.

'beforeHit': function (battleEvent) { const effect = battleEvent.effect //const attacker = battleEvent.attacker

battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (attacker) {

//if the card is not already motivated by Motivation effect. if (attacker.motivated != true) { attacker.motivated = false;}

if (attacker.motivated != true) {

attacker.hp += Math.floor(attacker.hp * effect.power) attacker.attack += Math.floor(attacker.attack * effect.power)

//set attacker as motivated so that it will not receive the increase more than once attacker.motivated = true;

// if (battleEvent) console.log(effect.id + ' increased HP and attack of ' + attacker.name + ' to hp ' + attacker.hp + ' and attack: ' + attacker.attack) } else { //console.log(attacker.name + 'is already motivated'); }                   })

},            'cardDead': function (battleEvent) { const effect = battleEvent.effect battleEvent.attackingTable .filter(function (card) { return card.hp > 0; }) .forEach(function (attacker) {                       //if the card is not already motivated                        if (attacker.motivated == true) {

//disable motivation on each card. attacker.motivated = false; }                    })            }        }, "NineRingSword": { //Nine Ring Sword:Done, Verified. // "Increases the card's AT by {powerPC}% and has a {chancePC}% chance to strike again" //Effect:Increases the card's AT by 40% and has a 40% chance to strike again //Artifact:Increases the card's AT by 15, 20, 25, 30, 40% and has a 40% chance to strike again // Once, one time.

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the effect is not applied yet. if (!attacker.effectStruck){

//increase card attack by effect/artifact power var original_attack = attacker.attack attacker.attack += Math.floor(attacker.attack * effect.power)

//if (battleEvent) console.log(effect.id + ' increased Attack of ' + attacker.name + ' form: ' + original_attack + ' to value of: ' + attacker.attack)

attacker.effectStruck = true }            },

'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender var strike_again_damage = 0; var defender_hp = defender.hp;

//if the effect is not applied yet. if (!attacker.NineRingHit){

//if defender is alive, it has hp > 0, then strike again with %% chance: if (defender.hp > 0) { strike_again_damage = Math.floor(attacker.attack * battleEvent.chance) battleEvent.dealEffectHpDamage(strike_again_damage, defender) }

// if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did a second strike with: ' + strike_again_damage + ' to the ' + defender.name + ' that had hp of ' + defender_hp )

attacker.NineRingHit = true }            }        }, "LightningSphere": { //Lightning Sphere:Done // "Makes a Lightning Sphere which hits all the enemy cards by {powerPC}% of the card's attack" //Makes a Lightning Sphere which hits all the enemy cards by 30% of the card's attack //Every round.

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the effect is not applied yet. // if (!attacker.effectStruck){

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                     //calculate damage : attacker.attack by effect power                          const damage = Math.floor(attacker.attack * effect.power)                        battleEvent.dealEffectHpDamage(damage, card)

// if (battleEvent) console.log(effect.id + ' did damage to ' + card.name + ' with value of: ' + damage) })                   //if (battleEvent) console.log('end of strike');                   // attacker.effectStruck = true                     //                             }             }        }, "MortalStrike": { //Mortal Strike:Done // "Has a {chancePC}% chance to instantly destroy an opposite card" //Has a 50% chance to instantly destroy an opposite card // Every Round, afterHit

'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if defender is alive, it has hp > 0, then strike with its hp value with chance %%: if (defender.hp > 0) { const damage = Math.floor(defender.hp * battleEvent.chance) battleEvent.dealEffectHpDamage(damage, defender)

//Final_result is holding the result of the card strike. var Final_result = 'not destroyed' if (damage >= defender.hp) {Final_result = 'destroyed';}

// if (battleEvent) console.log(effect.id + ' on the ' + attacker.name + ' card did damage of: ' + damage + ' to the ' + defender.name + ' and did ' + Final_result + ' the defender.') }           }        }, "AdamantWill": { //Robe of Master:AdamantWill:Done // "Once, increases HP and AT by {powerPC}%. Enemy effects don't affect the card." //Efect: Once, increases HP and AT by 40%. Enemy effects don't affect the card. //Artifact: Once, increases HP and AT by 10, 20, 30, 40, 55%. Enemy effects don't affect the card. // Once, one time.

'afterCardsAppeared': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the effect is not applied yet. if (!attacker.effectStruck){

//if defender is alive, it has hp > 0, then apply effect: if (attacker.hp > 0) { const startHp = attacker.hp                   const startattack = attacker.attack attacker.hp += Math.floor(attacker.hp * effect.power) attacker.attack += Math.floor(attacker.attack * effect.power)

//if (battleEvent) console.log(effect.id + ' on the ' + attacker.name + ' card increased HP from: ' + startHp + ' to ' + attacker.hp + ' and attack from ' + startattack + ' to ' + attacker.attack)

//if (battleEvent) console.log('end of strike'); attacker.effectStruck = true }                                                 }                //immunity for effects and artifacts. attacker.has_immunity = true;

}       }, "Claws": { // Once, scratches the cards near the opposite one with {powerPC}% of their base HP. // Once, scratches the cards near the opposite one with 50% of their base HP. // Events: {"afterRoundStart":true}" //battleEvent.tableIndex, position in the battle screen. //battleEvent.defendingTable[x], x = 0,1 or 2 //battleEvent.attackingTable[x], x = 0,1 or 2

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

// if the effect is not done. if (!attacker.effectStruck) { battleEvent.defendingTable .forEach(function (card, index) {                          //if card is not direct opposite and is alive.                            if((index != battleEvent.tableIndex) && (card.hp > 0)){                            damage = Math.floor(card.initialHp * effect.power)                            battleEvent.dealEffectHpDamage(damage, card)

if (battleEvent) { //console.log(effect.id + ' on ' + attacker.name + ' scratched ' + card.name + ' for ' + damage + '.') //console.log(card.name + 'has position : ' + index + ' and ' +  ' attacker is on position: ' + battleEvent.tableIndex)

}                                                             } else { //if (battleEvent) console.log('same position: ' + battleEvent.tableIndex) }

})                   //do this effect only once                    attacker.effectStruck = true                   // if (battleEvent) console.log("end of strike");                }            }        }, "Poison": { // "Poisons the opposite card. Poisoned card loses {powerPC}% of its HP at the start of each turn." //Effect:"Poisons the opposite card. Poisoned card loses 40% of its HP at the start of each turn." //Artifact:"Poisons the opposite card. Poisoned card loses 10,20,30,40,45% of its HP at the start of each turn." //poison-action-run:{afterRoundStart:!0}, card is poisoned in beforeHit.

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

// if the effect is not done. if (!attacker.effectStruck) {

if ((defender.hp > 0) && (defender.has_immunity !== true)) { // defender.poison = Math.floor(effect.power * defender.hp) defender.poisoned_with_effectpower = effect.power;

// if (battleEvent) console.log(effect.id + ' poisoned ' + defender.name + ' first time for ' + defender.poison + ' damage. Card poisoned. Damage not applied this round.')

const cardEvent = Battle.getCardEvent('afterRoundStart', defender) battleEvent.battle.emitter.on(cardEvent, cardEffects['Poisoned']['afterRoundStart'])

}

//do this effect only once attacker.effectStruck = true // if (battleEvent) console.log("end of strike"); }                              }        }, "Poisoned": { // effect of Poison artifact. The damage is done in afterRoundStart, at the start of each round. // it appears that the pioson percent is taken from the base HP (initial HP minus GW bonus) 'afterRoundStart': function (battleEvent) { battleEvent.effect = webpack.cardEffects['Poisoned'] const attacker = battleEvent.attacker

//console.log(attacker.poisoned_with_effectpower);

const damage = Math.floor(attacker.baseHp * attacker.poisoned_with_effectpower);

//console.log(attacker.name + ' poisoned in this round for: ' + damage) //console.log(attacker.name + ' had initialHP of value: ' + attacker.initialHp) //console.log(attacker.name + ' had baseHP of value: ' + attacker.baseHp) //console.log(attacker.name + ' had HP of value: ' + attacker.hp)

battleEvent.dealEffectHpDamage(damage, attacker) // console.log(attacker.name + ' has after poison HP of value: ' + attacker.hp) },       }, "Curse": { // Verified //Decreases the attack of the rival card by {powerPC}% //Decreases the attack of the rival card by 35% //no artifact with this effect // it affect a card only once, run: every round. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const defender = battleEvent.defender if (defender.cursed != true) defender.cursed = false; //if (battleEvent) console.log('defender.cursed is: ' + defender.cursed); if ((defender.hp > 0) && (!defender.cursed) ) {

//check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {var immunity = 1} defender.attack -= Math.floor(defender.attack * effect.power * immunity) defender.cursed = true;

// if (battleEvent) console.log(effect.id + ' reduces attack of ' + defender.name + ' to ' + defender.attack) }                   //if (battleEvent) console.log('end of round'); }       }, "DiscovererStrike": { // "As soon as the card appears on a battlefield, it strikes the opposite one with {powerPC}% of attack." //As soon as the card appears on a battlefield, it strikes the opposite one with 100% of attack. 'afterCardsAppeared': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (defender.hp > 0) { const damage = Math.floor(attacker.attack * effect.power) battleEvent.dealEffectHpDamage(damage, defender) }           }        }, "Rocket": { // "Every turn attacks one of the enemy cards with xx% of its base HP."(defender initial card hp) // xx% is effect.power, up to 60% at 5 stars. // it has 'afterCardsAppeared' as well,when the target is aquired, partial work, only attacks the card that is on top, the opposing card, for now //Effect:Every turn attacks one of the enemy cards with 60% of its base HP. //Artifact:Every turn attacks one of the enemy cards with 20, 30, 40, 50, 60% of its base HP. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (defender.hp > 0) { const damage = Math.floor(defender.initialHp * effect.power) battleEvent.dealEffectHpDamage(damage, defender)

// if (battleEvent){ console.log(' damage inflicted by the rocket of ' + attacker.name + ' on enemy ' + defender.name + ' is: ' + damage)} }           }        }, "DivineShield": { //"Makes a shield, which protects friendly cards from {powerPC}% AT" //Effect: Makes a shield, which protects friendly cards from 60% AT //One time //events:{clear:{cardDead:!0,hit:!0}}}

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

battleEvent.attackingTable .filter(function (card) {return card.hp > 0 }) .forEach(function (card) {

card.damageReduction = Math.max(effect.power, card.damageReduction || 0) //if (battleEvent) console.log(effect.id + ' gives ' + card.name + ' total damage reduction of: ' + card.damageReduction) })

// if (battleEvent) console.log('end of round'); attacker.effectStruck = true }           }

}, "DragonBreath": { // Done; Card:Dragon Blade //Once, makes all the opponent cards burn and loose {powerPC}% HP each round. // "Once, makes all the opponent cards burn and loose 25% HP each round." //dragon-breath= 0.25, burning = 0.15, percent of baseHp not full HP. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {

if(!defender.has_immunity) { //if (battleEvent) console.log(effect.id + ' starts burning ' + defender.name + ' with ' + effect.power*100 + '% HP for ' + defender.burning + ' damage/round')

const cardEvent = Battle.getCardEvent('beforeHit', defender) battleEvent.battle.emitter.on(cardEvent, cardEffects['Burning']['beforeHit']) }

})                   attacker.effectStruck = true                                        }

}       }, "Burning": { // Secondary effect of DragonBreath //the burning is beforeHit action of each card. 'beforeHit': function (battleEvent) { battleEvent.effect = webpack.cardEffects['Burning'] const attacker = battleEvent.attacker

//burning each round beforeHit with 0.15 of baseHp attacker.burning = Math.floor(attacker.baseHp * 0.15); damage = attacker.burning; battleEvent.dealEffectHpDamage(damage, attacker);

//if (battleEvent) console.log('Burning ' + attacker.name + ' with ' + attacker.burning + ' damage/round') },       }, "Fearblow": { // Verified //effect //Before each blow does additional damage: {powerPC}% of opponent card's base HP //Before each blow does additional damage: 50% of opponent card's base HP //artifact //Before each blow does additional damage: {powerPC}% of opponent card's HP (should be base HP) //Before each blow does additional damage: 15/20/35/45/55% of opponent card's base HP            'beforeHit': function (battleEvent) { const effect = battleEvent.effect const defender = battleEvent.defender if (defender.hp > 0) { const damage = Math.floor(defender.initialHp * effect.power) battleEvent.dealEffectHpDamage(damage, defender)

//if (battleEvent) console.log(effect.id + ' did damage to ' + defender.name + ' in value of ' + damage + ' of opponent HP.') }           }        }, "Fireball": { // Verified //Once, one time only. //Throws a fireball that hits all the enemy cards by {powerPC}% of the card's attack //Throws a fireball that hits all the enemy cards by 35% of the card's attack //Throws a fireball that hits all the enemy cards by 15/20/25/30/40% of the card's attack

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){ battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {                           const damage = Math.floor(attacker.attack * effect.power)                            battleEvent.dealEffectHpDamage(damage, defender)                        }) // if (battleEvent) console.log('end of round'); attacker.effectStruck = true }            }        }, "Ignition": { // partial //Once, one time only. //"Deals {powerPC}% AT fire damage. Ignites enemy cards for 2 rounds. Deals {power2PC}% AT damage to the ignited cards each round." //power:.3,power2:.15,events:{run:{afterRoundStart:!0}}, first part is simillar to fireball.

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {                       const damage = Math.floor(attacker.attack * effect.power)                        battleEvent.dealEffectHpDamage(damage, defender)

//if (battleEvent) console.log(effect.id + ' did damage to ' + defender.name + ' in value of ' + damage )

//ignite for 2 rounds every defender defender.is_ignited = 2; defender.is_ignited_with_power = effect.power2; const cardEvent = Battle.getCardEvent('afterHit', defender) battleEvent.battle.emitter.on(cardEvent, cardEffects['Ignited']['afterHit']) })

//if (battleEvent) console.log('end of round'); attacker.effectStruck = true }            }        }, "Ignited": { // effect of Ignition artifact. The damage is done in afterHit, for 2 rounds. 'afterHit': function (battleEvent) { battleEvent.effect = webpack.cardEffects['Ignited'] const attacker = battleEvent.attacker

//do this for 2 rounds if(attacker.is_ignited > 0){

//console.log('is_ignited ' + attacker.is_ignited); //console.log('is_ignited_with_power ' + attacker.is_ignited_with_power);

const damage = Math.floor(attacker.initialHp * attacker.is_ignited_with_power);

//console.log(attacker.name + ' ignited in this round for: ' + damage) //console.log(attacker.name + ' had initialHp of value: ' + attacker.initialHp) //console.log(attacker.name + ' had hp of value: ' + attacker.hp)

battleEvent.dealEffectHpDamage(damage, attacker) //console.log(attacker.name + ' has after ignited, HP of value: ' + attacker.hp) attacker.is_ignited -= 1; }           },        }, "Resurrect": { // //"Revive a fallen card" // Once, one time only. //events:{beforeHit:!0,afterRoundStart:!0}

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){ battleEvent.attackingTable .filter(function (card) { return card.hp <= 0 }) .forEach(function (card) {

// if (battleEvent) console.log(effect.id + ' detected card ' + card.name + ' as dead with hp of ' + card.hp + ' and attack of ' + card.attack )

//if this is the first resurrectd card if (!attacker.effectStruck){ card.hp = 1 attacker.effectStruck = true // if (battleEvent) console.log(effect.id + ' ressurected ' + card.name + ' with hp of ' + card.hp + ' and attack of ' + card.attack ) }

})                  // if (battleEvent) console.log('end of round');                                                  }             }        }, "LastStand": { // //"Revives itself after death with {powerPC}% HP" //Effect: "Revives itself after death with 20% HP" // Once, one time only. //events:{cardDead:!0} has_last_stand

'afterCardsAppeared': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//set the card as revivable. attacker.has_last_stand = true; },

'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck && (attacker.hp <= 0)){

// if (battleEvent) console.log( attacker.name + ' is dead with hp of ' + attacker.hp ) attacker.hp = attacker.initialHp * effect.power attacker.effectStruck = true

// if (battleEvent) console.log(effect.id + ' revived ' + attacker.name + ' with hp of ' + attacker.hp + ' and attack of ' + attacker.attack )

// if (battleEvent) console.log('end of round'); }            }        }, "MithrilArmor": { //"Once, gives a {powerPC}% defence. Every turn has a {chancePC}% chance to avoid enemy's strike" //"Once, gives a 5%, 10%, 15%, 20%, 25% defence. Every turn has a 50% chance to avoid enemy's strike" // events:clear:{cardDead:!0}, //power:1.2, artifact: 5%, 10%, 15%, 20%, 25%, chance 50%

'beforeHit': function (battleEvent) {

const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//Give defence aka shield. attacker.damageReduction = Math.max(effect.power, attacker.damageReduction || 0)

// if (battleEvent) console.log(effect.id + ' gives shield to ' + attacker.name + ' with a value of: ' + attacker.damageReduction*100 + '%')

//Avoid damage from the strike with 50% chance. attacker.dodge = battleEvent.chance //if (battleEvent) console.log(effect.id + ' gives dodge to ' + attacker.name + ' with a value of: ' + attacker.dodge)

//if (battleEvent) console.log('end of round'); }        }, "FogOfWar": { //"Gives the opportunity to avoid enemy's hit with 30% chance to all friendly cards." // Events: events:{run:{afterRoundStart:!0},clear:{roundEnd:!0}} 'afterRoundStart': function (battleEvent) {

const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {

card.dodge = 0.3;

// if (battleEvent) console.log(effect.id + ' makes ' + card.name + ' avoid strike with 30% chance')

})            }         }, "ThunderHammer": { //"With {chancePC}% chance hits all the enemy cards by {powerPC}% of its own attack" //"With 70% chance hits all the enemy cards by 40% of its own attack" // power:.4,chance:.7; beforeHit            'beforeHit': function (battleEvent) {

const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {

damage = Math.floor(attacker.attack * effect.power * battleEvent.chance); battleEvent.dealEffectHpDamage(damage, card); //if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' did damage to: ' + card.name + ' with value ' + damage )

})            }         }, "BlessedSun": { // Verified, one time action, it waits for next turn if cards HP is above baseHp. //Every round heals {powerPC}% of HP to itself and the friendly cards. //Effect: Every round heals 30% of HP to itself and the friendly cards. //Lady of Eclipse //events:{afterRoundStart:!0}, Every Round.            'afterRoundStart': function (battleEvent) {                const effect = battleEvent.effect                const attacker = battleEvent.attacker                var total_healed = 0;

battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                           const healHp = Math.min(card.initialHp - card.hp, Math.floor(card.initialHp * effect.power))

if (healHp > 0) { card.hp += healHp total_healed +=1 //if (battleEvent) console.log('Heal healed ' + card.name + ' by ' + healHp + '. Effect struck.') }                       })

//if (battleEvent) console.log('End of strike this round. BlessedSun healed ' + total_healed + ' cards.'); }       }, "Heal": { // Verified, one time action, it waits for next turn if cards HP is above baseHp. //Heal all own cards by {powerPC}% of their total HP (no more than maximal) //Heal all own cards by 50% of their total HP (no more than maximal) //Heal all own cards by 10,20,30,40,55% of their total HP (no more than maximal) 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker var total_healed = 0;

if (!attacker.effectStruck) { battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                           const healHp = Math.min(card.baseHp - card.hp, Math.floor(card.baseHp * effect.power))

if (healHp > 0) { card.hp += healHp total_healed +=1 attacker.effectStruck = true //if (battleEvent) console.log('Heal healed ' + card.name + ' by ' + healHp + '. Effect struck.') }

})               }                          //if (battleEvent) console.log('End of strike this round. Healed ' + total_healed + ' cards.');           }        }, "LuckyHeal": { // partial, only works after its own hit, not for friendly hits. //After each friendly card's hit has {chancePC}% chance to heal self by {powerPC}% of total HP. //After each friendly card's hit has 30% chance to heal self by 50% of total HP. //After each friendly card's hit has 50% chance to heal self by 10/20/30/40/55% of total HP. //no more than maximal HP

'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const healHp = Math.min(attacker.initialHp - attacker.hp, Math.floor(attacker.initialHp * effect.power));

//50% chance to apply the hp increase. //battleEvent.chance values: expected is 0.5, best case is 1, worst is 0, random is 0 or 1 with 50% probability. const healHp2 = healHp * battleEvent.chance;

if (healHp2 > 0) { const FinalHp = attacker.hp + healHp2;

if (battleEvent) { //console.log('Lucky_Heal on card ' + attacker.name + ' healed the card by ' + healHp2);

//console.log("attacker.initialHp is: " + attacker.initialHp + " " + "attacker.hp is: " + attacker.hp + " " + "effect.power is: " + effect.power + " " + "battleEvent.chance is: " + battleEvent.chance + " " + "healHP2 is: " + healHp2 + " " +   "final HP is: " + FinalHp + " " )  ; }

attacker.hp = FinalHp ;

}                                                              }        }, "JewelShield": { // Done //"Once, decreases any damage to self by {powerPC}% and turns back this damage to enemy's cards" //Effect:"Once, decreases any damage to self by 60% and turns back this damage to enemy's cards" //Artifact: "Once, decreases any damage to self by 60% and turns back 15,20,25,30,40% damage to enemy's cards" (per card) // events:{afterRoundStart:!0} ; clear:{cardDead:!0,hit:!0}; One time only.

// 60% damage reduction afterRoundStart, the cards gets only 40% damage from the attacker, afterHit this damage absorbed by the shield (60%) is split and directed to the enemy cards. Each card 20%.

//ex: attacker attack 3032, 3032 *0.4 = 1213 dmg to the defender, 3032 * 0.6 = 1819 is reduced ; //3032 is split in 3 and damge each card with an actual 20% of the attacker.attack

// the attacker is the card attacking the jewel shield (so actually the defender in afterHit) //if the attacker is dead, then the value of attack is taken from the dead card... //if the jewelshield card is dead the opponents still receive the attack.

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if the shield has not been applied at all. if (!attacker.shield_Struck) { attacker.jewelShield = effect.power

//set the card so that it will return damage even if it is dead. attacker.has_last_stand = true;

//attacker.damagereduction = attacker.jewelShield attacker.damageReduction = Math.max(effect.power, attacker.damageReduction || 0) attacker.shield_Struck = true //if (battleEvent) console.log(attacker.name + ' gained jewelShield of ' + attacker.damageReduction) }                                                      },

'afterHit': function (battleEvent) { const effect = battleEvent.effect const effect_secondary = battleEvent.effect.effect_secondary const attacker = battleEvent.attacker const defender = battleEvent.defender

//JewelLightning (second effect of jewel shield) //if the effect is not applied yet. if (!attacker.effectStruck){

//delete defender.jewelShield, redundant, already done after 'Hit' action. attacker.damageReduction = 0;

// calculate damage to do back per card. //for effect is 0.6 split in 3 so 20% or 0.2 per card always var damage_to_do = Math.floor(defender.attack * attacker.jewelShield / 3);

//for artifact, value is taken from effect_secondary.power if (typeof effect_secondary != "undefined") { damage_to_do = Math.floor(defender.attack * effect_secondary.power); }

//count number of defenders// not relevant anymore /*var Total_Defenders = 0; battleEvent.defendingTable .filter(function (card) { return card.hp > 0; }) .forEach(function (card) {                    Total_Defenders += 1

})

//calculate split_damage var split_damage = Math.floor(damage_to_do/Total_Defenders);*/

//do the damage to defenders battleEvent.defendingTable .filter(function (card) { return card.hp > 0; }) .forEach(function (card) {                    battleEvent.dealEffectHpDamage(damage_to_do, card)

//if (battleEvent) console.log('Defender ' + card.name + ' got damage from jewelLightning of: ' + damage_to_do + '. Defender ' + defender.name + ' has attack of ' + defender.attack) })

//if (battleEvent) console.log('end of round'); attacker.effectStruck = true;

//const cardEvent = Battle.getCardEvent('afterHit', attacker) // battleEvent.battle.emitter.on(cardEvent, cardEffects['JewelLightning']['afterHit']) }                                              }

}, /*"JewelLightning": { // just for testing

'afterHit': function (battleEvent) { const effect = battleEvent.effect const effect_secondary = battleEvent.effect.effect_secondary const attacker = battleEvent.attacker const defender = battleEvent.defender

//set the card so that it will return damage even if it is dead. attacker.has_last_stand = true;

battleEvent.effect = webpack.cardEffects['JewelLightning'] ; if (battleEvent) console.log(effect.name + ' has power of ' + effect.power) ;

if (battleEvent && (typeof effect_secondary != "undefined") ) console.log(effect_secondary.name + ' has power of ' + effect_secondary.power) ; } },*/ "LoveDream": { // Verified // "Make the opposite card dreaming and not attacking until HP is at 50%." // Events: {"clear":{"cardDead":true,"afterHit":true}} 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (!attacker.eventStruck && defender.hp >= defender.initialHp / 2) {

//check for immunity, if true multiple by zero. if(!defender.has_immunity) { defender.dreaming = attacker if (battleEvent.verbose) console.log(effect.id + ' prevents attack of ' + defender.name) attacker.eventStruck = true }               }            }            // "cardDead": Handled by checking if card is still alive when applying dreaming. }, "LuckyDodge": { // Verified //Avoid damage from the strike with {chancePC}% chance. //Avoid damage from the strike with 50% chance. //Avoid damage from the strike with 10/20/35/50/55% chance. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker attacker.dodge = battleEvent.chance if (battleEvent.verbose) console.log(effect.id + ' gives dodge ' + attacker.dodge) }       }, "LuckyStrike": { // Verified // After the main strike, attempts another strike with {chancePC}% chance. //After the main strike, attempts another strike with 60% chance. //After the main strike, attempts another strike with 20/30/40/50/65% chance. 'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (defender.hp > 0) { const damage = Math.floor(attacker.attack * battleEvent.chance) battleEvent.dealEffectHpDamage(damage, defender) }           }        }, "IceShield": { // Verified // "Makes a shield, which protects friendly cards from {powerPC}% AT and freezes each enemy's card with {chancePC}% chance." // Effect: "Makes a shield, which protects friendly cards from 30% AT and freezes each enemy's card with 50% chance." //events:{afterRoundStart:!0}; One time. ////the freeze is afterHit and last for next round. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

// give shield to own cards. battleEvent.attackingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                    card.oldDR = card.damageReduction;                     card.damageReduction = Math.max(effect.power, card.damageReduction || 0 );                  // if (battleEvent) console.log(effect.id + ' gives ' + card.name + ' total damage reduction ' + card.damageReduction + 'from old value ' + card.oldDR) })

//give frozen/freeze chance to enemy cards/defender. battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {

//check for immunity, if true > skip. if(defender.has_immunity == true) {var immunity = 0} else {

//if (battleEvent) console.log(effect.id + ' ice-freezes for next round ' + defender.name + ' with chance ' + battleEvent.chance) defender.icefrozenChance = battleEvent.chance }                   })

//if (battleEvent) console.log('end of round'); attacker.effectStruck = true; }            }        }, "ChainOfJustice": { // Verified // "Chains every enemy card with {chancePC}% chance. Chained card skips its next turn." // Effect: "Chains every enemy card with 40% chance. Chained card skips its next turn." // Artifact: "Chains every enemy card with 15,20,25,30,40% chance. Chained card skips its next turn." //events:{run:{hit:true},clear:{hit:true}}}; every round //action: every time after a hit action happen, so before beforeHit. //the freeze/chain is after the Hit and is acting in next round, so icefrozenChance can be used. 'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//give frozen/freeze/chain chance to enemy cards/defender. battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {

//check for immunity, if true > skip. if(card.has_immunity == true) {var immunity = 0} else {

//convert a random number under 1, to probability to chain a card var random_chain = Math.random; if(random_chain < effect.chance ){ // if (battleEvent) console.log(effect.id + ' chained for next round ' + card.name + ' with chance ' + battleEvent.chance) card.icefrozenChance = battleEvent.chance }                                                                           }                    })

//if (battleEvent) console.log('end of round');

}       }, "Freeze": { // "Prevent the attack of the rival card for one turn" //clear: RoundEnd //the freeze is beforeHit 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (!attacker.effectStruck && defender.hp > 0) {

//check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {var immunity = 1}

if (battleEvent.verbose) console.log(effect.id + ' freezes ' + defender.name) defender.frozenChance = 1.0 * immunity attacker.effectStruck = true }           }        }, "Frozen": { // "Freezes for a round." //events:{afterHit:!0} // Nothing to do. Handled with 'Freeze', 'MassFreeze', and hard coded handling of 'frozenChance' }, "MassFreeze": { // Verified // "Prevents the attack of all the enemy's cards for one turn with {chancePC}% chance" // "Prevents the attack of all the enemy's cards for one turn with 30% chance" //does it acts once or not? ////the freeze is beforeHit 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (defender) {

//check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {

//give to the defending card a freeze chance defender.frozenChance = battleEvent.chance if (battleEvent.verbose) console.log(effect.id + ' freezes ' + defender.name + ' with chance ' + battleEvent.chance) }                   })

// if (battleEvent) console.log('end of round'); //if the effect managed to freeze enemy cards then disable effect for next rounds if(battleEvent.chance > 0 ) attacker.effectStruck = true ; }

}       }, "MassRage": { // Verified //"Increase the attack of all own cards by {powerPC}%. Can affect each card only once." //Increase the attack of all own cards by 30%. Can affect each card only once. //(Vengeance Song); events:{clear:{cardDead:true}} //no artifact, does not stack. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect battleEvent.attackingTable .filter(function (card) { return card.hp > 0; }) .forEach(function (attacker) {                       //if the card is not already massraged                        if (attacker.massraged != true) { attacker.massraged = false;}                        if (attacker.massraged != true) {                        attacker.attack += Math.floor(attacker.attack * effect.power)

//set attacker as massraged so that it will not receive the increase more than once attacker.massraged = true;

//         if (battleEvent) console.log(effect.id + ' increases attack of ' + attacker.name + ' to ' + attacker.attack) } else { //          console.log(attacker.name + 'is already massraged'); }                   })            },            'cardDead': function (battleEvent) {                const effect = battleEvent.effect                battleEvent.attackingTable                    .filter(function (card) { return card.hp > 0; })                    .forEach(function (attacker) { //if the card is not already massraged if (attacker.massraged == true) {

//disable massrage attacker.massraged = false; }                    })            }        }, "MassShield": { // Verified, Run: Every round. // Decrease any damage to own cards by {powerPC}% //Decrease any damage to own cards by 20% //(Aegis{n}) :Decrease any damage to own cards by 5,10,15,20,25%            'beforeHit': function (battleEvent) {                const effect = battleEvent.effect                battleEvent.attackingTable                    .filter(function (card) { return card.hp > 0; })                    .forEach(function (attacker) { attacker.oldDR = attacker.damageReduction; attacker.damageReduction = Math.max(effect.power, attacker.damageReduction || 0) //if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' total damage reduction ' + attacker.damageReduction + 'from old value ' + attacker.oldDR) })

//if (battleEvent) console.log('end of round'); }       }, "HealingSpirit": { // Verified // "Inflicts additional {powerPC}% damage and heals friendly cards on the same amount." // aditional damage 15% to 35% of defender HP like fearblow, return the damage done as HP to own cards on top of the current HP, can go over maximum initialHp. //Effect:Inflicts additional 30% damage and heals friendly cards on the same amount. //Artifact:Inflicts additional 15/20/25/30/35% damage and heals friendly cards on the same amount. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//check for immunity, if true skip. if(defender.has_immunity == true) {var immunity = 0;} else {

//calculate and inflict % damage of and to opposing card if defender card is alive. if (defender.hp > 0) { const addit_damage = Math.floor(defender.hp * effect.power); battleEvent.dealEffectHpDamage(addit_damage, defender)

//calculate healing HP for each own cards const heal_value = Math.floor(addit_damage);

//heal own cards by same amount as the damage done, can go over maximum initial Hp. battleEvent.attackingTable .filter(function (card) { return card.hp > 0; }) .forEach(function (card) {                       card.hp = card.hp + heal_value;               //if (battleEvent) console.log(effect.id + ' gives ' + card.name + ' HP in value of ' + heal_value + ' from total damage of ' + addit_damage + ' done to ' + defender.name)                    }) //if (battleEvent) console.log('end of round'); }

}           }        }, "OlympicShield": { // Verified // "Makes the opposite card to miss its hits with {chancePC}% chance." //Makes the opposite card to miss its hits with 50% chance. //Makes the opposite card to miss its hits with 20,30,40,50,60% chance. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender if (defender.hp > 0) { //check for immunity, if true multiple by zero. if(defender.has_immunity == true) {var immunity = 0} else {

if (battleEvent.verbose) console.log(effect.id + ' makes ' + defender.name + ' miss with ' + battleEvent.chance + ' chance') defender.missChance = battleEvent.chance }               }            }        }, "TitanArmor": { // Verified // "Once, increases the card's HP by {powerPC}%" 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//apply effect only first time when the card appear. if (!attacker.effectStruck) { const IncHp = Math.floor(attacker.hp * effect.power); attacker.hp = attacker.hp + IncHp; //apply effect only once, effect struck is completed, value = true. attacker.effectStruck = true

//  if (battleEvent) {console.log('TitanArmor increased HP of ' + attacker.name + ' by ' + IncHp + ' with effect power of: ' + effect.power) } }           }        }, /* "Plague": { //LOVID-XXX ; Events: {"afterRoundStart":true}, Every Round //"Infects the opposite card with {chancePC}% chance. Before each hit an infected card with {chancePC}% chance losts half of HP and AT and infects a card nearby." // "Infects the opposite card with 50% chance. Before each hit an infected card with 50% chance loose half of HP and AT and infects a card nearby." //Illness:events:{beforeHit:!0};"Infects the card nearby and losts 50% of HP and AT with {chancePC}% chance."

'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//if opponent card is alive and does not have immunity from adamantwill. if ((defender.hp > 0) && (defender.has_immunity != true)) { if (battleEvent) console.log(effect.id + ' on ' + attacker.name + ' infected ' + defender.name + ' with lovid-xxx at ' + battleEvent.chance*100 + '% chance') defender.plague_infected = battleEvent.chance }

//attach Illness effect to the defender. }       },*/ "Illness": { // effect of Plague effect. The damage is done in beforeHit. // the ilnness act only once. 'beforeHit': function (battleEvent) { battleEvent.effect = webpack.cardEffects['Illness'] const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

//console.log(attacker.plague_infected);

const HPdamage = Math.floor(attacker.hp  * attacker.plague_infected); const ATTdamage = Math.floor(attacker.attack  * attacker.plague_infected);

attacker.attack -= ATTdamage; battleEvent.dealEffectHpDamage(damage, attacker)

//console.log(attacker.name + ' lost 50% HP in this round : ' + HPdamage) //console.log(attacker.name + ' lost 50% ATT in this round : ' + ATTdamage) // if (battleEvent) console.log('end of round'); attacker.effectStruck = true }                },        }, "Rage": { // Verified // "Once, increases the card's attack by {powerPC}%" //Effect: Once, increases the card's attack by 70% //Artifact: Once, increases the card's attack by 30/40/50/60/75% 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//if the effect is not applied yet. if (!attacker.effectStruck){

attacker.attack += Math.floor(attacker.attack * effect.power) //if (battleEvent) console.log(effect.id + ' increases attack of ' + attacker.name + ' to ' + attacker.attack)

// if (battleEvent) console.log('end of round'); attacker.effectStruck = true }

}       }, "Retribution": { // Verified // "When hit, return {powerPC}% of the opposite card's attack to all the enemy cards." //Effect: When hit, return 20% of the opposite card's attack to all the enemy cards. //Artifact: When hit, return 5%, 10%, 15%, 20%, 25% of the opposite card's attack to all the enemy cards. 'afterHit': function (battleEvent) { const effect = battleEvent.effect const defender = battleEvent.defender // Defender is the one who dealt the damage to us               battleEvent.defendingTable .filter(function (card) { return card.hp > 0 }) .forEach(function (card) {                       const damage = Math.floor(defender.attack * effect.power)                        battleEvent.dealEffectHpDamage(damage, card)                    }) }       }, "Shield": { // Verified // "Decrease any damage to self by {powerPC}%" // Note: shields only once as opposed to MassShield (aegis). It is one time only but it is permanent until cards die so you can say it acts every round. //Effect:Decrease any damage to self by 35% //Artifact: Decrease any damage to self by 15%, 20%, 25%, 30%, 40%

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker //if (!attacker.effectStruck) { attacker.damageReduction = Math.max(effect.power, attacker.damageReduction || 0) // if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' total damage reduction ' + attacker.damageReduction) //attacker.effectStruck = true //}           }        }, "GranitBody": { // Verified // "Before every hit increases its defence by {powerPC}% but no more than {maxPowerPC}%" //Effect:Before every hit increases its defence by 20% but no more than 60%" // Run: every round. //power:.2,maxPower:.6

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker

//count rounds or steps if (attacker.granit_step) {attacker.granit_step += 1 ;} else {attacker.granit_step = 1;} //calculate effect total damage reduction var granit_damageReduction = effect.power * attacker.granit_step //if granit damge reduction bigger than max then set it to max. if (granit_damageReduction > effect.maxPower) {granit_damageReduction = effect.maxPower ;  }

attacker.damageReduction = Math.max(granit_damageReduction, attacker.damageReduction || 0) // if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' total damage reduction ' + attacker.damageReduction)

}       }, "RuneShield": { // Testing area51 // "Creates a shield which makes one of own cards invincible for one blow"

'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const rune_shield = 1; var old_shield

//select a random card from the attacking cards, lower priority for attacker. const random_number_start = Math.floor(Math.random *10); var random_number; switch (battleEvent.tableIndex) { case 0: if(random_number_start <= 3) { random_number = 1} else if ( (random_number_start >= 4) && (random_number_start <= 7)) {random_number = 2} else {random_number = battleEvent.tableIndex } break; case 1: if(random_number_start <= 3) { random_number = 0} else if ( (random_number_start >= 4) && (random_number_start <= 7)) {random_number = 2} else {random_number = battleEvent.tableIndex } break; case 2: if(random_number_start <= 3) { random_number = 0} else if ( (random_number_start >= 4) && (random_number_start <= 7)) {random_number = 1} else {random_number = battleEvent.tableIndex } break; }

//give this attacking card a shield of ... old_shield = battleEvent.attackingTable[random_number].RSdamageReduction battleEvent.attackingTable[random_number].RSdamageReduction = rune_shield;

//give rune priest shield as well, first. //attacker.RSdamageReduction = 1;

// if (battleEvent) console.log(effect.id + ' increase ' + battleEvent.attackingTable[random_number].name + ' r-shield from ' + old_shield*100 + '%' + ' to a total damage reduction of ' + rune_shield*100 +'%.')

}       }, "Confusion": { // Done // "Once, turns the opposite card against its neighbours. All the cards involved get {powerPC}% of normal damage. The opposite card misses its hit." //power:0.3; events:clear:{hit:!0}; One time only effect. // Note: Defender is frozen for 1 round. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender //cards and positions card_pos0 = battleEvent.defendingTable[0] card_pos1 = battleEvent.defendingTable[1] card_pos2 = battleEvent.defendingTable[2] //damage for each card as percent of attack. damage0 = Math.floor(card_pos0.attack * effect.power) damage1 = Math.floor(card_pos1.attack * effect.power) damage2 = Math.floor(card_pos2.attack * effect.power)

//if the effect is not applied yet, apply only one time. if (!attacker.effectStruck){

switch (battleEvent.tableIndex) { case 0: //if both cards from position 0 and 1 are alive. if ((card_pos0.hp > 0 ) && (card_pos1.hp > 0)) {

battleEvent.dealEffectHpDamage(damage0, card_pos1) battleEvent.dealEffectHpDamage(damage1, card_pos0)

// if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor ' + damage0 + ' and neighbor(pos1) ' + card_pos1.name + ' did damage to defender ' + damage1 +'.') }                 break;

case 1: //if both cards from position 1 and 0 are alive. if ((card_pos1.hp > 0 ) && (card_pos0.hp > 0)) {

battleEvent.dealEffectHpDamage(damage1, card_pos0) battleEvent.dealEffectHpDamage(damage0, card_pos1)

// if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor ' + damage1 + ' and neighbor(pos0) ' + card_pos0.name + ' did damage to defender ' +  damage0 +'.') }                 //if both cards from position 1 and 2 are alive. if ((card_pos1.hp > 0 ) && (card_pos2.hp > 0)) {

battleEvent.dealEffectHpDamage(damage1, card_pos2) battleEvent.dealEffectHpDamage(damage2, card_pos1)

// if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor ' + damage1 + ' and neighbor(pos2) ' + card_pos2.name + ' did damage to defender ' + damage2 +'.') }                 break;

case 2: //if both cards from position 2 and 1 are alive. if ((card_pos2.hp > 0 ) && (card_pos1.hp > 0)) {

battleEvent.dealEffectHpDamage(damage2, card_pos1) battleEvent.dealEffectHpDamage(damage1, card_pos2)

//if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor ' + damage2 + ' and neighbor(pos1) ' + card_pos1.name + ' did damage to defender ' + damage1 +'.') }                 break;

}                   //freeze defender for one round defender.frozenChance = 1.0 // if (battleEvent) console.log(effect.id + ' frozen defender for 1 round: ' + defender.name)

//if (battleEvent) console.log('end of strike'); attacker.effectStruck = true }

},       }, "Temptation": { // Done // "With {chancePC}% of chance, the opposite card can attack its neibour instead of a common strike" //Effect: "With 40% of chance, the opposite card can attack its neibour instead of a common strike" //chance:.4; beforeHit, Every Round // Note: Defender is frozen for 1 round. 'beforeHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender

//measure hit_success var hit_success = 0;

//if battleEvent.chance > 0 do the effect else do normal hit. if(battleEvent.chance > 0){

//cards and positions card_pos0 = battleEvent.defendingTable[0] card_pos1 = battleEvent.defendingTable[1] card_pos2 = battleEvent.defendingTable[2] //damage for each card as percent of attack. damage0 = Math.floor(card_pos0.attack * battleEvent.chance) damage1 = Math.floor(card_pos1.attack * battleEvent.chance) damage2 = Math.floor(card_pos2.attack * battleEvent.chance)

switch (battleEvent.tableIndex) { case 0: //if both cards from position 0 and 1 are alive. if ((card_pos0.hp > 0 ) && (card_pos1.hp > 0)) {

battleEvent.dealEffectHpDamage(damage0, card_pos1); hit_success = 1; //if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor (middle) ' + damage0) }                 break;

case 1: //if both cards from position 1 and 0 are alive. if ((card_pos1.hp > 0 ) && (card_pos0.hp > 0)) {

battleEvent.dealEffectHpDamage(damage1, card_pos0) hit_success = 1; //if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor(0) ' + damage1 ) }                 //if both cards from position 1 and 2 are alive. if ((card_pos1.hp > 0 ) && (card_pos2.hp > 0)) {

battleEvent.dealEffectHpDamage(damage1, card_pos2) hit_success = 1; // if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor(2) ' + damage1 ) }                 break;

case 2: //if both cards from position 2 and 1 are alive. if ((card_pos2.hp > 0 ) && (card_pos1.hp > 0)) {

battleEvent.dealEffectHpDamage(damage2, card_pos1) hit_success = 1; // if (battleEvent) console.log(effect.id + ' on attacker ' + attacker.name + '. Defender ' + defender.name + ' did damage to neighbor (middle) ' + damage2 ) }                 break; }//end switch

}//end if check for battlevent.chance

//freeze defender for one round if effect is success. if ((battleEvent.chance > 0) && (hit_success > 0)) {

defender.frozenChance = battleEvent.chance; // if (battleEvent) console.log(effect.id + ' frozen defender for 1 round: ' + defender.name) }

},       }, "WaterAmp": { // Partial // "Amplifies all the blue card`s attack 2 times if the opposite card is a red one." //{run:{afterRoundStart:!0},clear:{afterHit:!0} // Note: partial work, amplify only the attacker attack multiplier to 2x. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' has color: ' + attacker.color + ' and ' + defender.name + ' has color: ' + defender.color);

if (attacker.color == 3 && defender.color == 1) { attacker.AmplifyColorAttackMultiplier = 0.5; //if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' additional attack multiplier of ' + attacker.AmplifyColorAttackMultiplier) }           },           'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' Effect: AmplifyColorAttackMultiplier is set to 0.' ); },       }, "FireAmp": { // Partial // "Amplifies all the red card`s attack 2 times if the opposite card is a green one." // Note: partial work, amplify only the attacker attack multiplier to 2x. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' has color: ' + attacker.color + ' and ' + defender.name + ' has color: ' + defender.color);

if (attacker.color == 1 && defender.color == 2) { attacker.AmplifyColorAttackMultiplier = 0.5; //if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' additional attack multiplier of ' + attacker.AmplifyColorAttackMultiplier) }           },           'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' Effect: AmplifyColorAttackMultiplier is set to 0.' ); },       }, "EarthAmp": { // Partial // "Amplifies all the green card`s attack 2 times if the opposite card is a blue one." // Note: partial work, amplify only the attacker attack multiplier to 2x. 'afterRoundStart': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' has color: ' + attacker.color + ' and ' + defender.name + ' has color: ' + defender.color);

if (attacker.color == 2 && defender.color == 3) { attacker.AmplifyColorAttackMultiplier = 0.5; //if (battleEvent) console.log(effect.id + ' gives ' + attacker.name + ' additional attack multiplier of ' + attacker.AmplifyColorAttackMultiplier) }           },           'afterHit': function (battleEvent) { const effect = battleEvent.effect const attacker = battleEvent.attacker const defender = battleEvent.defender attacker.AmplifyColorAttackMultiplier = 0;

//if (battleEvent) console.log(attacker.name + ' Effect: AmplifyColorAttackMultiplier is set to 0.' ); },       }, "Silence": { // maybe done !! problem for expected at 0.5. //Doesn't allow enemy cards to cast spells with {chancePC}% of chance. //Doesn't allow enemy cards to cast spells with 50% of chance. 'beforeHit': function (battleEvent) { const attacker = battleEvent.attacker

//generate a random binar number.[0 or 1] var random_number = Math.floor(Math.random *2);

if (battleEvent.chance == 1) random_number = 1; if(battleEvent.chance >= 0.5 && random_number == 1) { battleEvent.attackingPlayer.hasSilence = battleEvent.attacker //if (battleEvent) console.log(battleEvent.effect.id + ' on ' + attacker.name +' silences all enemies') }           },

'afterRoundStart': function (battleEvent) { const attacker = battleEvent.attacker

//generate a random binar number.[0 or 1] var random_number = Math.floor(Math.random *2);

if (battleEvent.chance == 1) random_number = 1; if(battleEvent.chance >= 0.5 && random_number == 1) { battleEvent.attackingPlayer.hasSilence = battleEvent.attacker // if (battleEvent) console.log(battleEvent.effect.id + ' on ' + attacker.name +' silences all enemies') }           },            // "cardDead": Handled by checking if card is still alive when applying silence. }, "Supernova": { // Verified // "On defeat the card explodes distributing equally all the damage absorbed throughout the battle            // on each opposite card." // Events: {"hit":true} 'hit': function (battleEvent) { const defender = battleEvent.defender const damage = battleEvent.damage defender.blowPower = (defender.blowPower || 0) + damage if (battleEvent.verbose) console.log(`Supernova collects ${damage} blowPower`) },           'cardDead': function (battleEvent) { const supernovaBlow = new BattleEvent(battleEvent.defendingPlayer, battleEvent.attackingPlayer, battleEvent.tableIndex, battleEvent.battle) supernovaBlow.effect = webpack.cardEffects['SupernovaBlow'] supernovaBlow.attacker = battleEvent.defender // Card that is dead now supernovaBlow.defender = undefined cardEffects[supernovaBlow.effect.id]['cardDead'](supernovaBlow) },       }, "SupernovaBlow": { // Verified // Secondary effect of Supernova 'cardDead': function (battleEvent) { const attacker = battleEvent.attacker const defenders = battleEvent.defendingTable .filter(function (card) { return card.hp > 0 })

if (attacker.blowPower && defenders.length > 0) { const damage = Math.floor(attacker.blowPower / defenders.length) defenders.forEach(function (defender) {                       battleEvent.dealEffectHpDamage(damage, defender)                    }) }           },        },        // "Vengeance": { //    // "When any other own card dies, the card's attack increased by 100%" //    // Events: {"cardDead":true,"clear":{"cardDead":true}} //    'afterRoundStart': function (battleEvent) { //        const [effect, attacker, defender] = [battleEvent.effect, battleEvent.attacker, battleEvent.defender] //        if (defender.hp > 0) { //            if (battleEvent.verbose) console.log(effect.id + ' makes ' + defender.name + ' miss with ' + battleEvent.chance + ' chance') //            defender.missChance = battleEvent.chance //        }        //     }        // },    }

// SIMULATION STATISTICS

function recurseCombinationFromDeckVsFixed(available, selected, hero, enemy, results, options) { if (available.length === 0 || selected.length === (options.librarySize || 7)) { const heroClone = hero.clone heroClone.library = selected.map(function (c) {               return c.clone;            }) heroClone.options = Object.assign({}, heroClone.options, options) const enemyClone = enemy.clone enemyClone.options = Object.assign({}, enemyClone.options, options) if (options.verbose) console.log('recurseCombinationFromDeckVsFixed: Battle') const battle = new Battle(heroClone, enemyClone, options) results.push(battle.result) return }       for (var i = available.length; i-- > 0;) { const card = available.shift selected.push(card) recurseCombinationFromDeckVsFixed(available, selected, hero, enemy, results, options) selected.pop available.push(card) }   }

function recurseCombinationFromDeckVsRandom(havailable, hselected, eavailable, eselected, hero, enemy, results, options) { if (hselected.length === 3) { if (eselected.length === 3) { const heroClone = hero.clone heroClone.library = hselected.map(function (c) {                   return c.clone;                }) heroClone.options = Object.assign({}, heroClone.options, options)

const enemyClone = enemy.clone enemyClone.library = eselected.map(function (c) {                   return c.clone;                }) enemyClone.options = Object.assign({}, enemyClone.options, options)

if (options.verbose) console.log('recurseCombinationFromDeckVsRandom: Battle') const battle = new Battle(heroClone, enemyClone, options) results.push(battle.result)

return }           for (var i = eavailable.length; i-- > 0;) { const card = eavailable.shift eselected.push(card) recurseCombinationFromDeckVsRandom(havailable, hselected, eavailable, eselected, hero, enemy, results, options) eselected.pop eavailable.push(card) }

}       for (var i = havailable.length; i-- > 0;) { const card = havailable.shift hselected.push(card) recurseCombinationFromDeckVsRandom(havailable, hselected, eavailable, eselected, hero, enemy, results, options) hselected.pop havailable.push(card) }   }

function measureStatistics(hero, enemy, options) { if (options.verbose) console.log('measureStatistics') options.warnedUnsupportedEffects = {} var results = [] if (options.verbose) console.log(`fixedEnemy=${options.fixedEnemy} librarySize=${options.librarySize} unlimitedLibrary=${options.unlimitedLibrary}`) if (options.test) { const battle = new Battle(hero.clone, enemy.clone, options) results.push(battle.result) } else { if (options.fixedEnemy) { recurseCombinationFromDeckVsFixed(hero.library, [], hero, enemy, results, options) } else { recurseCombinationFromDeckVsRandom(hero.library, [], enemy.library, [], hero, enemy, results, options) }       }        const averageWin = results.reduce(function (acc, cur) {            return acc + cur.win;        }, 0) / results.length const averageDamage = results.reduce(function (acc, cur) {           return acc + cur.damage;        }, 0) / results.length return { count: results.length, win: averageWin, numBossFights: enemy.hp / averageDamage, stars: [0, 1, 2, 3].map(function (s) {               return results.reduce(function (acc, cur) { return acc + (cur.stars === s); }, 0) / results.length;           }), damage: averageDamage, damageStdev: Math.sqrt(results.reduce(function (acc, cur) { return acc + (cur.damage - averageDamage) * (cur.damage - averageDamage); }, 0) / results.length), damageMin: Math.min.apply(null, results.map(function (cur) { return cur.damage })),           damageMax: Math.max.apply(null, results.map(function (cur) { return cur.damage })),           losses: results.filter(result => result.win === 0).length, }   }

function recurseArtifactDistribution(availableArtifacts, availableCards, hero, enemy, heroes, options) { if ((availableArtifacts.length === 0) || (availableCards.length === 0)) { heroes.push(hero.clone) return }       var bestResult const card = availableCards.shift var possibleArtifacts = availableArtifacts.filter(function (artifact) {           return !artifact.color || (artifact.color == card.color)        }) possibleArtifacts.unshift({ id: 0 }) // Include the option to not use any artifact possibleArtifacts.forEach(function (artifact) {           card.equipArtifact(artifact.id, options)            recurseArtifactDistribution(availableArtifacts.filter(function (a) { return a !== artifact }), availableCards, hero, enemy, heroes, options)           card.equipArtifact(undefined, options)        }) availableCards.unshift(card) }

function enableDialog(enabled) { $('.combatSimulator button, .combatSimulator select, .combatSimulator input, .combatSimulator textarea') .not('#output').prop('disabled', !enabled).css('opacity', enabled ? 1 : 0.1) $('.combatSimulator #progress, .combatSimulator #cancel') .prop('disabled', enabled).css('opacity', enabled ? 0.1 : 1) }

function findBestArtifactDistribution(hero, enemy, options) { var availableArtifacts = options.user.hero.artifacts.items.map(function (artifactInstance) {           return Object.assign({}, artifactInstance, webpack.artifacts[artifactInstance.id])        }).filter(artifact => options.artifactColor === undefined || options.artifactColor == -1 || artifact.color == options.artifactColor)

//filter more for better control and less astifacts availableArtifacts = availableArtifacts.filter(function (artifact) {return artifact.optimize_selected == 'selected'});

const availableCards = hero.library.filter(function (c) {           return c.canEquipArtifact                && (options.artifactColor === undefined || options.artifactColor == -1 || options.artifactColor == 0 || c.color == options.artifactColor)       }) console.log('findBestArtifactDistribution availableArtifacts', availableArtifacts) console.log('findBestArtifactDistribution availableCards', availableCards) const heroClone = hero.clone const heroes = [] var bestBattleResult recurseArtifactDistribution(availableArtifacts, availableCards,           hero, enemy, heroes, options)

const findCostToRemoveArtifact = function (c) { return c.artifact ? c.artifact.removeCost[c.artifact.stages.currentStage].gold : 0 }       const calcCostToRemoveArtifacts = function (newHero) { var cost = 0 heroClone.library.forEach(function (c, cardIndex) {               if (c.aid && newHero.library[cardIndex].aid != c.aid) {                    cost += findCostToRemoveArtifact(c)                }            }) return cost }       heroes.forEach(function (h) {            h.costToRemoveArtifacts = calcCostToRemoveArtifacts(h)        }) heroes.sort(function (a, b) {           return a.costToRemoveArtifacts - b.costToRemoveArtifacts        }) var indexInHeroes = 0 console.log('findBestArtifactDistribution sortedHeroes', heroes) enableDialog(false) var intervalTimer = setInterval(function {            if (indexInHeroes === heroes.length) {                clearInterval(intervalTimer)                console.log('findBestArtifactDistribution finished')                $('#output').append('\n\nDone')                $("#cancel").off('click')                var textarea = document.getElementById('output');                textarea.scrollTop = textarea.scrollHeight;                enableDialog(true)                return            }            const hero = heroes[indexInHeroes]            ++indexInHeroes            console.log('findBestArtifactDistribution ' + indexInHeroes + '/' + heroes.length)            $('#progress').text(indexInHeroes + '/' + heroes.length)            const result = measureStatistics(hero, enemy, options)            const isBetterResult = function  {                if (enemy.unlimitedLibrary) {                    return result.numBossFights < bestBattleResult.numBossFights } else { return result.win > bestBattleResult.win }           }            if (!bestBattleResult || isBetterResult) { bestBattleResult = result bestBattleResult.mission = options.mission const outputItems = [] outputItems.push('Redistribution cost=' + hero.costToRemoveArtifacts.toLocaleString('en-US')) outputItems.push('Hero ' + hero.toString) outputItems.push(formatBattleResult(bestBattleResult)) console.log('Better artifact distribution:\n' + outputItems.join('\n')) $('#output').append('\n\n' + outputItems.join('\n\n')) var textarea = document.getElementById('output'); textarea.scrollTop = textarea.scrollHeight; }       }, 1)        $('#cancel').click(function  { clearInterval(intervalTimer) console.log('findBestArtifactDistribution canceled') $('#output').append('\n\nCanceled') $("#cancel").off('click') var textarea = document.getElementById('output'); textarea.scrollTop = textarea.scrollHeight; enableDialog(true) })   }

function findBestIncrease(hero, enemy, options) { const increaseStat = (hero, cardIndex, increase) => { const heroClone = hero.clone const card = heroClone.library[cardIndex] const newCardInstance = JSON.parse(JSON.stringify(card.cardInstance)) const newArtifactInstance = card.artifact ? JSON.parse(JSON.stringify(card.artifact.artifactInstance)) : undefined const increaseLabel = increase(newCardInstance, newArtifactInstance) if (!increaseLabel) { return undefined }           const newCard = new BattleCard(newCardInstance, heroClone.csetCounts, options) newCard.equipArtifactInstance(newArtifactInstance) heroClone.library[cardIndex] = newCard return { increase: `${increaseLabel} ${newCard.name}`, hero: heroClone } }

let heroes = [] for (let cardIndex = 0; cardIndex < hero.library.length; ++cardIndex) { heroes.push(increaseStat(hero, cardIndex, cardInstance => { if (!cardInstance.evolution) { cardInstance.evolution = { currentStage: 0 } }               if (cardInstance.evolution.currentStage >= 9) { return undefined }               cardInstance.evolution.currentStage++ return `Stars+1` }))           heroes.push(increaseStat(hero, cardIndex, cardInstance => { const increase = Math.min(50 - cardInstance.level, 5) if (increase <= 1) { return undefined }               cardInstance.level += increase return `Level+${increase}` }))           heroes.push(increaseStat(hero, cardIndex, cardInstance => { if (cardInstance.level >= 50) { return undefined }               cardInstance.level++ return `Level+1` }))           heroes.push(increaseStat(hero, cardIndex, cardInstance => { if (cardInstance.sbl >= 12) { return undefined }               cardInstance.sbl++ return `Soulbind+1` }))           heroes.push(increaseStat(hero, cardIndex, cardInstance => { const card = hero.library[cardIndex] if (!cardInstance.stones) { cardInstance.stones = [] }               if (cardInstance.stones.length === 0                    || card.sockets >= 2 && cardInstance.stones.length < card.sockets) { cardInstance.stones.push((card.color - 1) * 10 + 1) } else if ((cardInstance.stones.length === 1 || cardInstance.stones[0] <= cardInstance.stones[1])                   && (cardInstance.stones[0] - 1) % 10 < 9) { cardInstance.stones[0]++ } else if (cardInstance.stones.length === 2 && (cardInstance.stones[1] - 1) % 10 < 9) { cardInstance.stones[1]++ } else { return undefined }               return `Stones+1` }))           heroes.push(increaseStat(hero, cardIndex, (cardInstance, artifactInstance) => { if (!artifactInstance || artifactInstance.stages.currentStage >= 4) { return undefined }               artifactInstance.stages.currentStage++ return `Artifact+1` }))       }        heroes = heroes.filter(heroEntry => heroEntry !== undefined) var indexInHeroes = 0 console.log('findBestIncrease sortedHeroes', heroes) enableDialog(false) const referenceResult = measureStatistics(hero, enemy, options) var intervalTimer = setInterval(function {            const getSortedResults =  => {                const mission = options.mission                if (mission.unlimitedLibrary) {                    return heroes.filter(heroEntry => heroEntry.result.damage > referenceResult.damage)                        .sort((a, b) => a.result.expectedBattles - b.result.expectedBattles).map(heroEntry => `n=${heroEntry.result.expectedBattles.toFixed(2)} ${heroEntry.increase}`).join('\n')               } else {                    const items = [                        heroes.filter(heroEntry => heroEntry.result.win > referenceResult.win)                            .sort((a, b) => b.result.win - a.result.win).map(heroEntry => `win=${(100 * heroEntry.result.win).toFixed(2)}% ${heroEntry.increase}`).join('\n'),                       heroes.filter(heroEntry => heroEntry.result.stars[3] > referenceResult.stars[3])                            .sort((a, b) => b.result.stars[3] - a.result.stars[3]).map(heroEntry => `s3=${(100 * heroEntry.result.stars[3]).toFixed(2)}% ${heroEntry.increase}`).join('\n')                   ]                    return items.join('\n')                }            }

if (indexInHeroes === heroes.length) { clearInterval(intervalTimer) console.log('findBestIncrease finished') $("#cancel").off('click') $('#output').append('\n\n' + getSortedResults + '\n\nDone') var textarea = document.getElementById('output'); textarea.scrollTop = textarea.scrollHeight; enableDialog(true) return }           const heroEntry = heroes[indexInHeroes] ++indexInHeroes console.log('findBestIncrease ' + indexInHeroes + '/' + heroes.length) $('#progress').text(indexInHeroes + '/' + heroes.length) heroEntry.result = measureStatistics(heroEntry.hero, enemy, options) if (options.mission.unlimitedLibrary) { const result = calcMultiFightStatistics(options.mission.hp, heroEntry.result.damage, heroEntry.result.damageStdev, heroEntry.result.damageMin) heroEntry.result.expectedBattles = result.expectedBattles }       }, 1)        $('#cancel').click(function  { clearInterval(intervalTimer) console.log('findBestIncrease canceled') $('#output').append('\n\nCanceled') $("#cancel").off('click') var textarea = document.getElementById('output'); textarea.scrollTop = textarea.scrollHeight; enableDialog(true) })   }

function normalcdf(mean, sigma, to) { var z = (to - mean) / Math.sqrt(2 * sigma * sigma); var t = 1 / (1 + 0.3275911 * Math.abs(z)); var a1 = 0.254829592; var a2 = -0.284496736; var a3 = 1.421413741; var a4 = -1.453152027; var a5 = 1.061405429; var erf = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-z * z); var sign = 1; if (z < 0) { sign = -1; }       return (1 / 2) * (1 + sign * erf); }

function formatWinAsPercentage(value) { if (value >= .99999) { // Just show 100%. return (100 * value).toFixed(0) + '%' }       if (value >= .9999) { // Show extra decimals to 'explain' a loss when otherwise 100% would be reported. return (100 * value).toFixed(3) + '%' }       if (value >= .999) { // Show extra decimal to 'explain' a loss when otherwise 100% would be reported. return (100 * value).toFixed(2) + '%' }       return (100 * value).toFixed(1) + '%' }

function calcMultiFightStatistics(enemyHp, meanDamage, stdevDamage, minDamage) { const result = {} result.maxBattles = Math.ceil(enemyHp / minDamage) result.cumulativeProbabilities = [0] for (let n = 1; n <= result.maxBattles; ++n) { result.cumulativeProbabilities.push(1 - normalcdf(n * meanDamage, stdevDamage * Math.sqrt(n), enemyHp)) }       // Cannot have more than max battles since minimum damage is guaranteed: consider more as part of maxBattles. const cdfGreaterMax = normalcdf(result.maxBattles * meanDamage, stdevDamage * Math.sqrt(result.maxBattles), enemyHp) result.cumulativeProbabilities[result.maxBattles] += cdfGreaterMax result.probabilities = result.cumulativeProbabilities.map((cdf, i) => i === 0 ? cdf : cdf - result.cumulativeProbabilities[i - 1]) result.expectedBattles = result.probabilities.reduce((acc, cur, i) => acc + cur * i, 0) if (result.maxBattles > 1) { result.stdevBattles = Math.sqrt(result.probabilities.reduce((acc, cur, i) => acc + cur * (i - result.expectedBattles) ** 2, 0) / (result.maxBattles - 1)) } else { result.stdevBattles = 0 }       const zValue99_99 = 3.719016485 result.worstBattles = result.expectedBattles + zValue99_99 * result.stdevBattles return result }

function reportNumFights(enemyHp, meanDamage, stdevDamage, minDamage) { const statistics = calcMultiFightStatistics(enemyHp, meanDamage, stdevDamage, minDamage) const numExpectedBattles = Math.ceil(enemyHp / meanDamage) const probabilityLessNum = statistics.cumulativeProbabilities[numExpectedBattles - 1] const probabilityEqualNum = statistics.probabilities[numExpectedBattles] const probabilityEqualNumPlusOne = statistics.probabilities[numExpectedBattles + 1] || 0 const probabilityGreaterNumPlusOne = 1 - (statistics.cumulativeProbabilities[numExpectedBattles + 1] || 0) return 'P(N < ' + numExpectedBattles + ') = ' + formatWinAsPercentage(probabilityLessNum) + ' P(N = ' + numExpectedBattles + ') = ' + formatWinAsPercentage(probabilityEqualNum) + ' P(N = ' + (numExpectedBattles + 1) + ') = ' + formatWinAsPercentage(probabilityEqualNumPlusOne) + ' P(N > ' + (numExpectedBattles + 1) + ') = ' + formatWinAsPercentage(probabilityGreaterNumPlusOne) }

function getFarmResult(averageResult) { const mission = averageResult.mission const winRewards = mission.rewards_data.win && mission.rewards_data.win[0] || {} const loseRewards = mission.rewards_data.lose && mission.rewards_data.lose[0] || {} if (mission.unlimitedLibrary) { const multiFightStatistics = calcMultiFightStatistics(mission.hp, averageResult.damage, averageResult.damageStdev, averageResult.damageMin) if (Object.keys(mission.cost || {}).length === 1) { const [costKey, cost] = Object.entries(mission.cost)[0] return Object.keys(winRewards.r || {}).map(function (k) {                   const expectedCost = multiFightStatistics.probabilities.reduce((acc, p, i) => p <= 0 ? acc : acc + p * winRewards.r[k] / (i * cost))                   return `${expectedCost.toFixed(1)} ${k}/${costKey}`                }).join(', ') }       } else { if (Object.keys(mission.cost || {}).length === 1) { const [costKey, cost] = Object.entries(mission.cost)[0] return Object.keys(winRewards.r || {}).map(function (k) {                   let expectedResources = winRewards.r[k] * averageResult.win                    if (loseRewards.r && loseRewards.r[k]) {                        expectedResources += loseRewards.r[k] * (1 - averageResult.win)                    }                    return `${(expectedResources / cost).toFixed(1)} ${k}/${costKey}`                }).join(', ') }       }        return undefined }

function formatBattleResult(averageResult) { const mission = averageResult.mission if (mission.unlimitedLibrary) { return 'damage=' + averageResult.damage.toFixed(0) + '±' + averageResult.damageStdev.toFixed(0) + ' min=' + averageResult.damageMin.toFixed(0) + ' max=' + averageResult.damageMax.toFixed(0) + ' n=' + averageResult.count + '\n' + reportNumFights(mission.hp, averageResult.damage, averageResult.damageStdev, averageResult.damageMin) } else { const starsText = averageResult.stars.slice(1, 4).map(function (s, i) { return 'stars' + (i + 1) + '=' + formatWinAsPercentage(s) }).join(' ') const farm = getFarmResult(averageResult) return 'win=' + formatWinAsPercentage(averageResult.win) + ' ' + starsText + ' n=' + averageResult.count + ' losses=' + averageResult.losses + (farm ? ` farm=${farm}` : '') }   }

function updateCombatStatus(averageResult, hasUnsupportedEffect) { const mission = averageResult.mission let shortStatus let combatStatus if (mission.unlimitedLibrary) { const multiFightStatistics = calcMultiFightStatistics(mission.hp, averageResult.damage, averageResult.damageStdev, averageResult.damageMin) console.log('calcMultiFightStatistics', multiFightStatistics) const numExpectedBattles = multiFightStatistics.expectedBattles shortStatus = `Num: ${numExpectedBattles.toFixed(1)} ` combatStatus = 'num=' + numExpectedBattles.toFixed(1) + ` (worst=${multiFightStatistics.worstBattles.toFixed(1)})` + ' n=' + averageResult.count } else { shortStatus = `Win: ${formatWinAsPercentage(averageResult.win)} ` combatStatus = 'win=' + formatWinAsPercentage(averageResult.win) + ' n=' + averageResult.count + ' losses=' + averageResult.losses + ' s3=' + formatWinAsPercentage(averageResult.stars[3]) }       const farm = getFarmResult(averageResult) if (farm) { combatStatus += ` farm=${farm}` }       if (hasUnsupportedEffect) { combatStatus += ' UNSUPPORTED' }       $('#combat_status').text(shortStatus).append($(' ').text(combatStatus)) }

function simulateCombat(user, mission, options) { console.log('CombatSimulator: simulateCombat user', user); console.log('CombatSimulator: mission', mission); const enemy = BattlePlayer.createMissionEnemy(mission, options); console.log('CombatSimulator: enemy', enemy); const hero = BattlePlayer.createHero(user, options.deckName, options.cardInstanceIds, options); console.log('CombatSimulator: hero', hero); options.librarySize = enemy.librarySize; options.fixedEnemy = enemy.fixedEnemy; options.warnedUnsupportedEffects = {}; options.user = user; options.mission = mission;

options.test = $("#test input").is(":checked"); options.verbose = options.test; options.worstCase = $("#worstCase").val;

var resultText try { var averageResult if (options.recurseArtifacts) { findBestArtifactDistribution(hero, enemy, options) return } if (options.findBestIncrease) { findBestIncrease(hero, enemy, options) return } else { averageResult = measureStatistics(hero, enemy, options) }           console.log('averageResult', averageResult) averageResult.mission = mission resultText = formatBattleResult(averageResult) const hasUnsupportedEffect = Object.keys(options.warnedUnsupportedEffects).length const unsupportedEffects = hasUnsupportedEffect ? Object.keys(options.warnedUnsupportedEffects).join(',') : 'all effects are supported' resultText = '\n' + '\n' + 'Unsupported effects: ' + unsupportedEffects + '\n' + '\n' + resultText updateCombatStatus(averageResult, hasUnsupportedEffect) } catch (e) { resultText = 'Failed to run simulation: ' + e       } console.log('resultText', resultText) $('#output').text($('#output').val + resultText) }

// GUILD

class GuildPhase { constructor(dateTime) { const phaseMinutes = [0, 7 * 60, 7 * 60 + 45, 8 * 60, Infinity] const minutesSinceGuildWars = Math.floor((dateTime - new Date('2021-12-24T09:00:00Z')) / (60 * 1000)) this.season = Math.floor(minutesSinceGuildWars / (28 * 24 * 60)) const minutesInSeason = minutesSinceGuildWars - (28 * 24 * 60) * this.season this.war = Math.floor(minutesInSeason / (4 * 24 * 60)) const minutesInWar = minutesInSeason - (4 * 24 * 60) * this.war const round = Math.floor(minutesInWar / (8 * 60)) this.round = Math.min(round, 9) const minutesInRound = minutesInWar - (8 * 60) * this.round this.phase = round < 9 ? phaseMinutes.findIndex(phaseStart => minutesInRound < phaseStart) - 1 : undefined this.minutesInPhase = this.phase ? minutesInRound - phaseMinutes[this.phase] : minutesInRound }       toString { const phaseNames = ['Main', 'Prep', 'Battle'] let text = `${this.season + 1}.${this.war + 1}` if (this.round < 9) { text += `.${this.round + 1} ${phaseNames[this.phase]}` } else { text += ` Peace` }           const hours = Math.floor(this.minutesInPhase / 60) const minutes = `0${this.minutesInPhase % 60}`.slice(-2) text += ` ${hours}:${minutes}` return text }   }

function listGuildCardSets(user, options) { const noCsetCounts = {}; const availableCards = user.hero.cardGame.cards .filter(cardInstance => options.allGuildGirls || !user.guildMember.blockedCards.includes(cardInstance.cardId)) .map(function (cardInstance) { return new BattleCard(cardInstance, noCsetCounts, options) }) const availableSets = {} availableCards.forEach(battleCard => {           let csetIndex = battleCard.cset || 10002 + 10 * battleCard.color            if (availableSets[csetIndex]) {                return // continue            }            const cards = availableCards.filter(card => battleCard.cset ? card.cset === battleCard.cset : !card.cset && card.color === battleCard.color)           const csetCounts = {}            if (battleCard.cset) {                csetCounts[battleCard.cset] = cards.length            } else {                const x = 1            }            const csetCards = cards.map(battleCard => new BattleCard(battleCard.cardInstance, csetCounts, options))            const colors = csetCards.map(card => card.color)            const csetColor = colors.sort((a, b) => colors.filter(v => v === a).length - colors.filter(v => v === b).length ).pop           availableSets[csetIndex] = {                cset: battleCard.cset,                csetCards: csetCards,                color: csetColor,                listTP: csetCards.reduce((sum, cur) => sum + cur.listTP, 0),                csetTP: csetCards.reduce((sum, cur) => sum + cur.csetTP, 0),                buildingTP: csetCards.reduce((sum, cur) => cur.color = csetColor ? sum + cur.buildingTP : sum, 0),           }        }) const summarySets = {} const allColors = [1, 2, 3, 4, 5] allColors.forEach(color => {           const csetIndex = 10001 + 10 * color            const csets = Object.values(availableSets).filter(availableSet => availableSet.cset && availableSet.color == color)                .flatMap(availableSet => availableSet.csetCards)            summarySets[csetIndex] = {                cset: csetIndex,                csetCards: csets.flatMap(availableSet => availableSet.csetCards),                color: color,                listTP: csets.reduce((sum, cur) => sum + cur.listTP, 0),                csetTP: csets.reduce((sum, cur) => sum + cur.csetTP, 0),                buildingTP: csets.reduce((sum, cur) => sum + cur.buildingTP, 0),            }            summarySets[csetIndex + 1] = availableSets[csetIndex + 1] || {                cset: undefined,                csetCards: [],                color: color,                listTP: 0,                csetTP: 0, buildingTP: 0, }       })        const setsToDisplay = options.guildSummary ? summarySets : availableSets

const updateTotals = function { $('#totalAvailableListTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.sent ? sum : sum + cur.listTP, 0)) $('#totalSelectedListTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.selected ? sum + cur.listTP : sum, 0)) $('#totalAvailableCsetTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.sent ? sum : sum + cur.csetTP, 0)) $('#totalSelectedCsetTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.selected ? sum + cur.csetTP : sum, 0)) $('#totalAvailableBuildingTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.sent ? sum : sum + cur.buildingTP, 0)) $('#totalSelectedBuildingTP').text(Object.values(setsToDisplay).reduce((sum, cur) => cur.selected ? sum + cur.buildingTP : sum, 0)) }

$('#guildTime').val(user.gameTime ? 'Round ' + new GuildPhase(new Date(user.addedTime + user.gameTime)).toString : '')

const headers = ['cset', 'color', 'listTP', 'csetTP', 'buildingTP', 'sent', 'selected'] const table = $('.guildTroops table') table.empty table.append($(' ').html(headers.map(header => ` ${header} `).join(''))) Object.keys(setsToDisplay) .sort((a, b) => a - b)           .map(key => setsToDisplay[key]) .forEach(battleSet => {               const tr = $(' ').addClass(['', 'fire', 'earth', 'ice', 'light', 'dark'][battleSet.color]).appendTo(table)                const csetText = options.guildSummary                    ? (battleSet.cset ? 'sets' : )                   : battleSet.cset ? (webpack.cardSets.find(cardSet => cardSet.id == battleSet.cset).short || '★') :                 headers.forEach(key => { const value = { 'color': Webpack.colorText[battleSet.color], 'cset': csetText + ' (' + battleSet.csetCards.length + ')', }[key] || battleSet[key] || '' const td = $(' ').html(value).appendTo(tr) if (key === 'sent') { td.append($(' ').attr('type', 'checkbox').change(function { battleSet.sent = $(this).is(":checked") if (battleSet.sent) { tr.addClass('strike') } else { tr.removeClass('strike') }                           updateTotals }))                   } else if (key === 'selected') { td.append($(' ').attr('type', 'checkbox').change(function { battleSet.selected = $(this).is(":checked") updateTotals }))                   }                })            })        table.append($(' ')            .append($(' ').attr('colspan', 2).text('Available'))            .append($(' ').attr('id', 'totalAvailableListTP'))            .append($(' ').attr('id', 'totalAvailableCsetTP'))            .append($(' ').attr('id', 'totalAvailableBuildingTP'))            // .append($(' ').append($(' ').attr('type', 'checkbox')))            // .append($(' ').append($(' ').attr('type', 'checkbox')))        ) table.append($(' ')           .append($(' ').attr('colspan', 2).text('Selected'))            .append($(' ').attr('id', 'totalSelectedListTP'))            .append($(' ').attr('id', 'totalSelectedCsetTP'))            .append($(' ').attr('id', 'totalSelectedBuildingTP'))            // .append($(' ').append($(' ').attr('type', 'checkbox')))            // .append($(' ').append($(' ').attr('type', 'checkbox')))        ) updateTotals }

function showTasks {

/** quests content*/ const seQuests = webpack.quests?.filter(q => q.se && q.se == userCurrent.currentEvent?.id) const quest_start = seQuests?.[0]?.id   console.log(quest_start); const questsCNT = JSON.stringify(seQuests) console.log(questsCNT);

var task_result = "";

console.log("Open Tasks for userCurrent userData['quests']"); //if there are quests if (userCurrent["quests"]) { //find total number of quests in the userData snippet. const Total_quests = userCurrent["quests"].length; console.log(Total_quests);

//iterate quests, find id for current event for (Qsts = 0; Qsts < Total_quests; Qsts++){ var id = userCurrent["quests"][Qsts]["id"];

//search tasks for current event only >= 4216 ( var--> quest_start) if (id >= quest_start) { //output tasks that are not finished aka if finished is undefined console.log(id);

//search for hidden tasks to discard them. string_to_search_1 = '"id":' + id; var quest_index_1 = questsCNT.search(string_to_search_1);

if (quest_index_1 != -1){ var hidden = questsCNT.substring(quest_index_1,quest_index_1 + 50 ); //search for hidden tasks to discard them. var hidden_search = hidden.search('"hidden"'); // console.log(hidden + ' ' + hidden_search); if (hidden_search >= 0) {continue;} } else {continue;}

if(userCurrent["quests"][Qsts]["finished"] == undefined ) { //search task description string_to_search_1 = '"id":' + id; string_to_search_2 = '"title":'; string_to_search_3 = ','; var quest_index_1 = questsCNT.search(string_to_search_1); //if quest found search title if (quest_index_1 != -1){ quest_index_2 = questsCNT.indexOf(string_to_search_2, quest_index_1); quest_index_3 = questsCNT.indexOf(string_to_search_3, quest_index_2); quest_title = questsCNT.substring(quest_index_2 + 9,quest_index_3 -1); } else {quest_title = "not found";}

//find start parameter of task var quest_start_at = userCurrent["quests"][Qsts]["obj_state"]; console.log(quest_start_at);

//if obj_state is null then skip if(quest_start_at == null){quest_start_at='NA'}

qsa1 = quest_start_at.search("counters"); if (qsa1 != -1) { qsa1 = quest_start_at.indexOf("{",qsa1); qsa2 = quest_start_at.search("}"); qsa3 = quest_start_at.indexOf(": ",qsa1); quest_counter_parameter = quest_start_at.substring(qsa1 + 2,qsa3 - 1); quest_start_at = quest_start_at.substring(qsa3+1,qsa2);

quest_finish_at = userCurrent["counters"][quest_counter_parameter]; if (quest_finish_at == undefined){quest_finish_at = userCurrent["counters2"][quest_counter_parameter];}

quest_completed = quest_finish_at - quest_start_at ;

} else { quest_start_at = ""; quest_finish_at = ; quest_completed = 'Not yet'; quest_counter_parameter = ;}

task_result = task_result + " task id: " + id + "     " + " " + quest_title + "    " + " info: " + quest_counter_parameter + " : " + quest_start_at + " : " + quest_finish_at + "  Done: " + quest_completed + "  "; }                   }                                                }

}

const handle = document.getElementById('Tasks_List'); handle.innerHTML = " List of open tasks " + " " + task_result +  "  " ;

}

// MAIN

function initialize { populateUserData; enableDialog(true);

userCurrent = parseUserData($('#userData').val);

$('#selectArtifactColors').append($(' ').val(-1).text('All elements')) $.each([0, 1, 2, 3, 4, 5], (index, color) => {           $('#selectArtifactColors').append($(' ').val(color).text(Webpack.elementText[color]))        }) $('#selectArtifactColors').change( => {           console.log(`change selectArtifactColors`, $(this))            update        })

populateDecks(userCurrent, { user: userCurrent, currentEventTag: userCurrent.currentEventTag }); populateLocations; populateSupportedEffects; update;

$('#userData').on('change keyup paste', function {            console.log('CombatSimulator: change keyup paste');            try {                userCurrent = parseUserData(this.value);                populateDecks(userCurrent, { user: userCurrent, currentEventTag: userCurrent.currentEventTag });                update;            } catch (e) {                console.warn('CombatSimulator: caught exception', e)                // parseDuelData(this.value);            }        }); $('#selectDeck').change(function {            update;        }); $('#selectLocation').change(function {            populateMissions($(this).val);            update;        }); $('#selectMission').change(function {            update;        }); $('#combat').click(function {            const deckName = $('#selectDeck option:selected').text;            const cardInstanceIds = JSON.parse($('#selectDeck').val);            const mission = JSON.parse($('#selectMission').val);            simulateCombat(userCurrent, webpack.allLocations[mission.locationIndex].missions[mission.missionIndex] , { deckName: deckName, cardInstanceIds: cardInstanceIds, currentEventTag: userCurrent.currentEventTag });       }); $('.propertyButtons button, .propertyButtons_more button').click(function {            propertiesChange[$(this).text](userCurrent)            update        }) $('.propertyButtons_more button:contains("REG")') .append($(' ').attr('src', imageManager.getImageUrl(`stones:Regular Topaz`, webpack.stones.find(stone => stone.stoneId == 34).img._1x))) $('.propertyButtons_more button:contains("PUR")') .append($(' ').attr('src', imageManager.getImageUrl(`stones:Pure Topaz`, webpack.stones.find(stone => stone.stoneId == 35).img._1x))) $('.propertyButtons_more button:contains("RAD")') .append($(' ').attr('src', imageManager.getImageUrl(`stones:Radiant Topaz`, webpack.stones.find(stone => stone.stoneId == 36).img._1x))) $('.propertyButtons_more button:contains("FLA")') .append($(' ').attr('src', imageManager.getImageUrl(`stones:Flawless Topaz`, webpack.stones.find(stone => stone.stoneId == 37).img._1x))) $('.propertyButtons_more').css('display', localStorage.getItem('smutstone_propertyButtons') || 'none') $('#showMoreProperties').click(function {            $('.propertyButtons_more').toggle;            localStorage.setItem('smutstone_propertyButtons', $('.propertyButtons_more').css('display'))        }) $('#artifacts').css('display', localStorage.getItem('smutstone_showArtifacts') || 'none') $('#showArtifacts').click(function {            $('#artifacts').toggle;            localStorage.setItem('smutstone_showArtifacts', $('#artifacts').css('display'))        }) $('#deckEnemy').css('display', localStorage.getItem('smutstone_showEnemy') || 'none') $('#showEnemy').click(function {            $('#deckEnemy').toggle;            localStorage.setItem('smutstone_showEnemy', $('#deckEnemy').css('display'))        }) displayStyle = Number(localStorage.getItem('smutstone_displayStyle') || 0) $('#selectDisplayStyle').val(displayStyle).change(function {            localStorage.setItem('smutstone_displayStyle', $(this).val)            displayStyle = Number($(this).val)            update;        }); $('#bestArtifactDistribution').click(function {            const deckName = $('#selectDeck option:selected').text;            const cardInstanceIds = JSON.parse($('#selectDeck').val);            const mission = JSON.parse($('#selectMission').val);            simulateCombat(userCurrent, webpack.allLocations[mission.locationIndex].missions[mission.missionIndex], { deckName: deckName, cardInstanceIds: cardInstanceIds, currentEventTag: userCurrent.currentEventTag, recurseArtifacts: true, artifactColor: $('#selectArtifactColors').val, });       });        $('#findBestIncrease').click(function  {            const deckName = $('#selectDeck option:selected').text;            const cardInstanceIds = JSON.parse($('#selectDeck').val);            const mission = JSON.parse($('#selectMission').val);            simulateCombat(userCurrent, webpack.allLocations[mission.locationIndex].missions[mission.missionIndex], { deckName: deckName, cardInstanceIds: cardInstanceIds, currentEventTag: userCurrent.currentEventTag, findBestIncrease: true, });       });        applyEventBonus = $('#applyEventBonus').is(":checked") $('#applyEventBonus').change(function {            applyEventBonus = $(this).is(":checked")            update        }) allGirlsAndArtifacts = $('#allGirlsAndArtifacts').is(":checked") $('#allGirlsAndArtifacts').change(function {            allGirlsAndArtifacts = $(this).is(":checked")            userCurrent = parseUserData($('#userData').val);            populateDecks(userCurrent, { user: userCurrent, currentEventTag: userCurrent.currentEventTag });            update        })

$('#allGuildGirls').change(function {            update        }) $('#guildSummary').change(function {            update        })

$('.tabbuttons button').click(function (event) {           // For some reason $('.tabcontent').hide does not work. Specify each tab explicitly for now.            $('#tabCombat, #tabGuild, #tabEvent-Tasks').hide            $('.tabbuttons button').removeClass('active')            const buttonText = $(this).text            $(this).addClass('active')            $(`#tab${buttonText}`).show        })

if (window.location.protocol === 'file:') { $('#test').show }   }

webpack = new Webpack; imageManager = new ImageManager; webpack.load(confJsUrl, function {        if (typeof (smutstoneImagesUrl) !== 'undefined') {            imageManager.load(smutstoneImagesUrl, initialize)        } else {            initialize        }    }) console.log('CombatSimulator: Webpack load called'); });