const DB = { allMonsters: [], allAnimations: {}, monsters: {}, shapes: {}, elements: {}, techniques: {}, statusEffects: {}, }; const ElementType = { aether: 'aether', wood: 'wood', fire: 'fire', earth: 'earth', metal: 'metal', water: 'water', /* lightning: 'lightning', frost: 'frost', venom: 'venom', //vermin: 'vermin', cosmic: 'cosmic', battle: 'battle', psionic: 'psionic', darkness: 'darkness', heaven: 'heaven', combineTypes(typeA, typeB) { if (typeA === ElementType.earth & typeB === ElementType.fire) { return ElementType.lightning; } if (typeA === ElementType.earth & typeB === ElementType.water) { return ElementType.frost; } if (typeA === ElementType.earth & typeB === ElementType.wood) { return ElementType.venom; } // if (typeA === ElementType.earth & typeB === ElementType.metal) { // return ElementType.vermin; // } if (typeA === ElementType.fire && typeB === ElementType.water) { return ElementType.cosmic; } } */ }; const ElementTypeColor = { [ElementType.aether]: 'rgba(255, 255, 255, 1)', [ElementType.wood]: '#3ca6a6', [ElementType.fire]: '#ca3c3c', [ElementType.earth]: '#eac93c', [ElementType.metal]: '#e4e4e4', [ElementType.water]: '#3c3c3c', }; const TasteWarm = { tasteless: 'tasteless', peppy: 'peppy', salty: 'salty', hearty: 'hearty', zesty: 'zesty', refined: 'refined', }; const TasteCold = { tasteless: 'tasteless', mild: 'mild', sweet: 'sweet', soft: 'soft', flakey: 'flakey', dry: 'dry', }; const TechniqueRange = { melee: 'melee', touch: 'touch', ranged: 'ranged', reach: 'reach', reliable: 'reliable', }; const StatusType = { melee: 'melee', armour: 'armour', ranged: 'ranged', dodge: 'dodge', speed: 'speed', }; const StatusEffectType = { blinded: 'blinded', burn: 'burn', chargedup: 'chargedup', charging: 'charging', confused: 'confused', diehard: 'diehard', dozing: 'dozing', elementalshield: 'elementalshield', eliminated: 'eliminated', enraged: 'enraged', exhausted: 'exhausted', faint: 'faint', feedback: 'feedback', festering: 'festering', flinching: 'flinching', focused: 'focused', grabbed: 'grabbed', hardshell: 'hardshell', harpooned: 'harpooned', lifeleech: 'lifeleech', lockdown: 'lockdown', noddingoff: 'noddingoff', poison: 'poison', prickly: 'prickly', recover: 'recover', slow: 'slow', sniping: 'sniping', softened: 'softened', stuck: 'stuck', tired: 'tired', wasting: 'wasting', wild: 'wild', }; class State { money = 0; monsters = []; inventory = []; partyMonsters = []; activeMonster = null; activeTechnique = null; turn = 0; currentArea = null; enemy = { monster: null, }; }; class Monster { #level = 1; #hp = 0; exp = 1; tasteWarm = TasteWarm.tasteless; tasteCold = TasteCold.tasteless; gender = ''; heldItem = null; statusEffect = ''; statusModifier = { hp: 0, melee: 0, armour: 0, ranged: 0, dodge: 0, speed: 0, }; experienceModifier = 1; moneyModifier = 1; constructor (slug) { this.slug = slug; const tasteWarm = Object.keys(TasteWarm).slice(1); this.tasteWarm = tasteWarm[Math.floor(Math.random() * tasteWarm.length)]; const tasteCold = Object.keys(TasteCold).slice(1); this.tasteCold = tasteCold[Math.floor(Math.random() * tasteCold.length)]; this.hp = this.status.hp; const possibleGenders = DB.monsters[this.slug].possible_genders; this.gender = possibleGenders[Math.floor(Math.random() * possibleGenders.length)] } get shape () { for (const shapeData of DB.shapes) { if (shapeData.slug === DB.monsters[this.slug].shape) { return shapeData; } } } get types () { return DB.monsters[this.slug].types; } get moveset () { return DB.monsters[this.slug].moveset; } get evolutions () { return DB.monsters[this.slug].evolutions; } get level () { return this.#level; } set level (level) { const statusPreLevelUp = this.status; const hpPreLevelUp = this.hp; this.#level = level; const statusPostLevelUp = this.status; this.hp = statusPostLevelUp.hp - (statusPreLevelUp.hp - hpPreLevelUp); if (this.exp < this.getExperienceRequired(-1)) { this.exp = this.getExperienceRequired(-1); } } get hp () { return this.#hp; } set hp (hp) { this.#hp = Math.max(0, Math.min(hp, this.status.hp)); } get name () { return slugToName(this.slug); } canLevelUp () { return this.exp >= this.getExperienceRequired(); } levelUp () { while (this.canLevelUp()) { this.level++; } } getExperienceRequired (levelOffset = 0) { return Math.max( Math.pow(this.level + levelOffset, 3), 1 ); } getPossibleEvolutions () { // return this.evolutions.filter((evolution) => this.level >= evolution.at_level && (!evolution.item || this.heldItem === evolution.item)); return this.evolutions.filter((evolution) => evolution.path === 'standard' && this.level >= evolution.at_level); } canEvolve () { return this.getPossibleEvolutions().length > 0; } evolve () { const evolution = this.getPossibleEvolutions()[0]; const statusPreEvolve = this.status; const hpPreEvolve = this.hp; this.slug = evolution.monster_slug; const statusPostEvolve = this.status; this.hp = statusPostEvolve.hp - (statusPreEvolve.hp - hpPreEvolve); } getTasteStatusModifier (statusName, baseStatus) { let positive = 0; let negative = 0; let isPositive = false; let isNegative = false; if (statusName === 'melee') { isPositive = this.tasteWarm === TasteWarm.salty; isNegative = this.tasteCold === TasteCold.sweet; } else if (statusName === 'armour') { isPositive = this.tasteWarm === TasteWarm.hearty; isNegative = this.tasteCold === TasteCold.soft; } else if (statusName === 'ranged') { isPositive = this.tasteWarm === TasteWarm.zesty; isNegative = this.tasteCold === TasteCold.flakey; } else if (statusName === 'dodge') { isPositive = this.tasteWarm === TasteWarm.refined; isNegative = this.tasteCold === TasteCold.dry; } else if (statusName === 'speed') { isPositive = this.tasteWarm === TasteWarm.peppy; isNegative = this.tasteCold === TasteCold.mild; } if (isPositive) { positive = baseStatus * 10 / 100; } if (isNegative) { negative = baseStatus * 10 / 100; } return Math.floor(positive) - Math.floor(negative); } get status () { const multiplier = this.level + 7; let hp = (this.shape.hp * multiplier) + this.statusModifier.hp; let melee = (this.shape.melee * multiplier) + this.statusModifier.melee; let armour = (this.shape.armour * multiplier) + this.statusModifier.armour; let ranged = (this.shape.ranged * multiplier) + this.statusModifier.ranged; let dodge = (this.shape.dodge * multiplier) + this.statusModifier.dodge; let speed = (this.shape.speed * multiplier) + this.statusModifier.speed; // Tastes melee += this.getTasteStatusModifier('melee', melee); armour += this.getTasteStatusModifier('armour', melee); ranged += this.getTasteStatusModifier('ranged', melee); dodge += this.getTasteStatusModifier('dodge', melee); speed += this.getTasteStatusModifier('speed', melee); return { hp, melee, armour, ranged, dodge, speed, }; } setStatusModifier (statusType, newAbsoluteValue) { this.statusModifier[statusType] = newAbsoluteValue - this.status[statusType]; } resetStatusModifier () { for (const m in this.statusModifier) { this.statusModifier[m] = 0; } } }; class Technique { #accuracy = 0; #potency = 0; #power = 0; combatEffects = []; constructor (slug) { this.slug = slug; this.resetToBase(); } get types () { return DB.techniques[this.slug].types; } get range () { return DB.techniques[this.slug].range; } get animation () { return DB.techniques[this.slug].animation; } get sfx () { return DB.techniques[this.slug].sfx; } get effects () { return DB.techniques[this.slug].effects; } get accuracy () { return this.#accuracy; } set accuracy (accuracy) { this.#accuracy = accuracy; } get potency () { return this.#potency; } set potency (potency) { this.#potency = potency; } get power () { return this.#power; } set power (power) { this.#power = power; } get base () { const accuracy = DB.techniques[this.slug].accuracy; const potency = DB.techniques[this.slug].potency; const power = DB.techniques[this.slug].power; return { accuracy, potency, power, }; } resetToBase () { this.accuracy = this.base.accuracy; this.potency = this.base.potency; this.power = this.base.power; } } class Item {} class TechniqueEffect { constructor (str) { this.recipient = str.split(',')[1]; this.application = str.split(' ')[0]; this.type = str.split(',')[0].split(' ')[1].split('_')[0]; this.effect = str.split(',')[0].split(' ')[1].split('_')[1]; } } class StatusEffect { turnsLeft = 0; constructor (slug) { this.slug = slug; if (['recover', 'lifeleech'].includes(this.slug)) { this.turnsLeft = 1; } else if (['charging'].includes(this.slug)) { this.turnsLeft = 2; } else if (this.category === 'positive') { this.turnsLeft = Math.ceil(Math.random() * 6) + 4; } else if (this.category === 'negative') { this.turnsLeft = Math.ceil(Math.random() * 3) + 2; } else { this.turnsLeft = Math.ceil(Math.random() * 3) + 2; } } get effects () { return DB.statusEffects[this.slug].effects; } get category () { return DB.statusEffects[this.slug].category; } get name () { return slugToName(this.slug); } get status () { const status = {}; const statusChangeKeys = Object.keys(DB.statusEffects[this.slug]).filter((key) => key.startsWith('stat')); for (const key of statusChangeKeys) { status[key.replace('stat', '')] = DB.statusEffects[this.slug][key]; } return status; } } function simpleDamageMultiplier (techniqueTypes, targetTypes) { let multiplier = 1; for (const techniqueType of techniqueTypes) { if (techniqueType === ElementType.aether) { continue; } for (const targetType of targetTypes) { if (targetType === ElementType.aether) { continue; } multiplier *= DB.elements[techniqueType].types.find((type) => type.against === targetType).multiplier; } } return Math.max(0.25, Math.min(multiplier, 4)); } function simpleDamageCalculation (technique, user, target) { if (technique.power === 0) { return 0; } let userBaseStrength = user.level + 7; let userStrength = 1; let targetResist = 1; if (technique.range === TechniqueRange.melee) { userStrength = userBaseStrength * user.status.melee; targetResist = target.status.armour; } else if (technique.range === TechniqueRange.touch) { userStrength = userBaseStrength * user.status.melee; targetResist = target.status.dodge; } else if (technique.range === TechniqueRange.ranged) { userStrength = userBaseStrength * user.status.ranged; targetResist = target.status.dodge; } else if (technique.range === TechniqueRange.reach) { userStrength = userBaseStrength * user.status.ranged; targetResist = target.status.armour; } else if (technique.range === TechniqueRange.reliable) { userStrength = userBaseStrength; targetResist = 1; } const multiplier = simpleDamageMultiplier(technique.types, target.types); const moveStrength = technique.power * multiplier; const damage = Math.floor((userStrength * moveStrength) / targetResist); return Math.max(damage, 1); } function calculateAwardedExperience (playerMonster, enemyMonster) { return Math.max( Math.floor( enemyMonster.getExperienceRequired(-1) / enemyMonster.level * enemyMonster.experienceModifier / playerMonster.level ), 1 ); } async function fetchMonster (slug) { if (! DB.monsters[slug]) { DB.monsters[slug] = await fetch(`/modules/tuxemon/mods/tuxemon/db/monster/${slug}.json`).then((response) => response.json()); } return new Monster(slug); } async function fetchTechnique (slug) { if (! DB.techniques[slug]) { DB.techniques[slug] = await fetch(`/modules/tuxemon/mods/tuxemon/db/technique/${slug}.json`).then((response) => response.json()); } return new Technique(slug); } async function fetchStatusEffect (slug) { if (! DB.statusEffects[slug]) { DB.statusEffects[slug] = await fetch(`/modules/tuxemon/mods/tuxemon/db/technique/status_${slug}.json`).then((response) => response.json()); } return new StatusEffect(slug); } function standardizeColor (color) { var ctx = document.createElement('canvas').getContext('2d'); ctx.fillStyle = color; return ctx.fillStyle; } function mixColors(...colors) { let r = 0; let g = 0; let b = 0; for (const color of colors) { const [cr, cg, cb] = color.match(/\w\w/g).map((c) => parseInt(c, 16)); r += cr; g += cg; b += cb; } r = r / colors.length; g = g / colors.length; b = b / colors.length; return `rgb(${r}, ${g}, ${b})`; } function slugToName (slug) { return slug.split('_').map((item) => item.charAt(0).toUpperCase() + item.slice(1)).join(' '); } (async function () { DB.allMonsters = await fetch('/db/all-monsters.json').then((response) => response.json()); DB.allAnimations = await fetch('/db/animations.json').then((response) => response.json()); DB.shapes = await fetch('/modules/tuxemon/mods/tuxemon/db/shape/shapes.json').then((response) => response.json()); for (const element of Object.keys(ElementType)) { DB.elements[element] = await fetch(`/modules/tuxemon/mods/tuxemon/db/element/${element}.json`).then((response) => response.json()); } const state = new State(); state.enemy.monster = await fetchMonster('grintot'); state.partyMonsters = [ await fetchMonster('corvix'), await fetchMonster('lunight'), await fetchMonster('prophetoise'), await fetchMonster('drashimi'), await fetchMonster('glombroc'), await fetchMonster('uneye'), await fetchMonster('nostray'), await fetchMonster('dragarbor'), ]; state.activeMonster = state.partyMonsters[0]; state.activeTechnique = await fetchTechnique(state.activeMonster.moveset[0].technique); const templatePartyMonster = document.querySelector('#tpl___party__monster').innerHTML; const templateBattleMonster = document.querySelector('#tpl___battle__monster').innerHTML; const templatePopup = document.querySelector('#tpl___popup').innerHTML; const templateMovesetList = document.querySelector('#tpl___moveset__list').innerHTML; const templateMovesetItem = document.querySelector('#tpl___moveset__item').innerHTML; const party = document.querySelector('#party'); const money = document.querySelector('#money'); const battle = document.querySelector('#battle'); const battleEnemy = document.querySelector('#battle__enemy'); const battlePlayer = document.querySelector('#battle__player'); const UI = { activeMonster: null, enemyImg: null, damageHighlightClickDuration: 0.1, damageHighlightClickTimeout: null, imgClickDuration: 0.1, damageClickTimeout: null, damageAnimationIsRunning: false, damageAnimationNumber: 0, getTemplate (template) { var tpl = document.createElement('div'); tpl.innerHTML = template.trim(); return tpl.firstChild; }, setExp (monster, parentNode) { const expBar = parentNode.querySelector('.exp-bar'); const expText = parentNode.querySelector('.exp-text'); const expToNextLevel = monster.getExperienceRequired() - monster.getExperienceRequired(-1); const currentExp = monster.exp - monster.getExperienceRequired(-1); expBar.style.width = (currentExp / expToNextLevel) * 100 + '%'; expText.textContent = monster.exp + ' / ' + monster.getExperienceRequired(); }, setHp (monster, parentNode) { const hpBar = parentNode.querySelector('.hp-bar'); const hpText = parentNode.querySelector('.hp-text'); const percentHp = (monster.hp / monster.status.hp) * 100; if (percentHp > 60) { hpBar.style.backgroundColor = 'green'; } else if (percentHp > 15) { hpBar.style.backgroundColor = 'rgb(240, 240, 100)'; } else { hpBar.style.backgroundColor = 'red'; } hpBar.style.width = percentHp + '%'; hpText.textContent = monster.hp + ' / ' + monster.status.hp; }, async createDamage (event, damage) { const damageNode = document.createElement('div'); damageNode.classList.add('damage'); damageNode.textContent = damage; const damageMultiplier = simpleDamageMultiplier(state.activeTechnique.types, state.enemy.monster.types); damageNode.style.fontSize = damageMultiplier*2 + 'rem'; damageNode.style.top = event.pageY - battleEnemy.offsetTop + (Math.random() * 40 - 20); damageNode.style.left = event.pageX - battleEnemy.offsetLeft + (Math.random() * 40 - 20); damageNode.style.color = mixColors(...state.activeTechnique.types.map((type) => standardizeColor(ElementTypeColor[type]))); const damageNodeDuration = 2; damageNode.style.animationDuration = damageNodeDuration + 's'; battleEnemy.appendChild(damageNode); setTimeout(() => damageNode.remove(), (damageNodeDuration * 1000) - 500); this.enemyImg.classList.add('damaged'); clearTimeout(this.damageHighlightClickTimeout); this.damageHighlightClickTimeout = setTimeout(() => this.enemyImg.classList.remove('damaged'), this.damageHighlightClickDuration * 1000); var enemyAnimation = battleEnemy.querySelector('.battle__monster-sprite__animation'); if (!this.damageAnimationIsRunning && state.activeTechnique.animation && DB.allAnimations[state.activeTechnique.animation]) { this.damageAnimationIsRunning = true; const damageAnimationLoop = () => { enemyAnimation.src = `/modules/tuxemon/mods/tuxemon/animations/technique/${state.activeTechnique.animation}_${("00" + this.damageAnimationNumber).slice(-2)}.png`; enemyAnimation.style.top = event.clientY - (enemyAnimation.clientHeight / 2); enemyAnimation.style.left = event.clientX - (enemyAnimation.clientWidth / 2); // console.log(enemyAnimation.src); this.damageAnimationNumber++; if (this.damageAnimationNumber >= DB.allAnimations[state.activeTechnique.animation].length) { this.damageAnimationIsRunning = false; this.damageAnimationNumber = 0; enemyAnimation.src = ''; return; } setTimeout(() => requestAnimationFrame(damageAnimationLoop), 50); }; requestAnimationFrame(damageAnimationLoop); // sfx /* let sfx = state.activeTechnique.sfx.substr(4); const audio = new Audio(`/modules/tuxemon/mods/tuxemon/sounds/technique/${sfx}.ogg`); audio.play(); */ } }, setBattleMonster (monster, where) { let battleMonster = document.createElement('div'); battleMonster.innerHTML = templateBattleMonster.trim(); battleMonster = battleMonster.firstChild; battleMonster.querySelector('.battle__monster-info__name').textContent = monster.name; battleMonster.querySelector('.battle__monster-info__gender').textContent = monster.gender === 'male' ? '♂' : monster.gender === 'female' ? '♀' : '⚲'; battleMonster.querySelector('.battle__monster-info__level').textContent = monster.level; battleMonster.querySelector('.battle__monster-info__status').innerHTML = UI.createStatusEffectIcon(monster.statusEffect); battleMonster.querySelector('.battle__monster-img').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`; UI.setHp(monster, battleMonster.querySelector('.hp')); if (where === 'player') { UI.setExp(monster, battleMonster.querySelector('.exp')); battleMonster.querySelector('.battle__monster-technique').addEventListener('click', UI.openMovesetSelection); battleMonster.classList.add('battle__monster--player'); battlePlayer.querySelector('.battle__monster') && battlePlayer.removeChild(battlePlayer.querySelector('.battle__monster')); battlePlayer.appendChild(battleMonster); UI.setActiveTechnique(); } else { battleMonster.classList.add('battle__monster--enemy'); this.enemyImg = battleMonster.querySelector('.battle__monster-img'); this.enemyImg.style.transitionDuration = this.damageHighlightClickDuration + 's'; const previousBattleMonster = battleEnemy.querySelector('.battle__monster'); if (previousBattleMonster) { this.enemyImg.classList = previousBattleMonster.querySelector('.battle__monster-img').classList; battleEnemy.removeChild(previousBattleMonster); } battleEnemy.appendChild(battleMonster); } }, setEnemyMonster () { UI.setBattleMonster(state.enemy.monster, 'enemy'); }, setActiveMonster () { UI.setBattleMonster(state.activeMonster, 'player'); }, setActiveTechnique () { battlePlayer.querySelector('.battle__monster-technique').innerHTML = slugToName(state.activeTechnique.slug) + '   ' + state.activeTechnique.types.map((type) => UI.createElementTypeIcon(type)).join(''); }, async chooseActiveTechnique () { let activeMoveIndex = 0; while ((await fetchTechnique(state.activeMonster.moveset[activeMoveIndex].technique)).power === 0) { activeMoveIndex++; } state.activeTechnique = await fetchTechnique(state.activeMonster.moveset[activeMoveIndex].technique); UI.setActiveTechnique(); }, async openMovesetSelection () { const popup = UI.createPopup(); const movesetList = UI.getTemplate(templateMovesetList); for (const move of state.activeMonster.moveset) { const technique = await fetchTechnique(move.technique); const movesetItem = UI.getTemplate(templateMovesetItem); movesetItem.querySelector('.moveset__item__name').textContent = slugToName(technique.slug); movesetItem.querySelector('.moveset__item__type').innerHTML = technique.types.map((type) => UI.createElementTypeIcon(type)).join(''); movesetItem.querySelector('.moveset__item__power').innerHTML = technique.power; movesetItem.querySelector('.moveset__item__level').innerHTML = move.level_learned; movesetItem.addEventListener('click', () => { if (movesetItem.getAttribute('disabled')) { return false; } state.activeTechnique = technique; UI.setActiveTechnique(); popup.remove(); }); if (state.activeMonster.level < move.level_learned) { movesetItem.setAttribute('disabled', true); } movesetList.appendChild(movesetItem); } popup.querySelector('.popup').appendChild(movesetList); document.body.appendChild(popup); }, async createNewEnemyMonster () { state.enemy.monster = await fetchMonster(DB.allMonsters[Math.floor(Math.random() * DB.allMonsters.length)]); state.enemy.monster.level = Math.ceil(Math.random() * state.activeMonster.level); // state.enemy.monster.experienceModifier = state.enemy.monster.level; state.enemy.monster.moneyModifier = state.enemy.monster.level; UI.setEnemyMonster(); }, createElementTypeIcon (type) { var img = document.createElement('img'); img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/element/${type}_type.png`; img.title = slugToName(type); return img.outerHTML; }, createStatusEffectIcon (statusEffect) { if (!statusEffect) return ''; var img = document.createElement('img'); img.src = `/modules/tuxemon/mods/tuxemon/gfx/ui/icons/status/icon_${statusEffect.slug}.png`; img.title = statusEffect.name; return img.outerHTML; }, createPopup () { const popup = UI.getTemplate(templatePopup); popup.addEventListener('click', ({ target }) => target === popup && popup.remove()); return popup; }, openPartyMenu () { const popup = UI.createPopup(); const party = document.createElement('div'); party.id = 'party'; for (const monster of state.partyMonsters) { const partyMonster = UI.getTemplate(templatePartyMonster); partyMonster.dataset.slug = monster.slug; partyMonster.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`; partyMonster.addEventListener('click', async (event) => { let target = event.target; while (target.parentNode.id !== 'party') { target = target.parentNode; } state.activeMonster = state.partyMonsters[Array.prototype.indexOf.call(document.querySelector('#party').children, target)]; await UI.chooseActiveTechnique(); UI.setActiveMonster(); popup.remove(); }); party.appendChild(partyMonster); } popup.querySelector('.popup').appendChild(party); document.body.appendChild(popup); }, openInventoryMenu () { const popup = UI.createPopup(); const inventory = document.createElement('div'); inventory.id = 'inventory'; for (const item of state.inventory) { } popup.querySelector('.popup').appendChild(inventory); document.body.appendChild(popup); }, }; const Game = { async useTechnique(technique, user, target) { let statusEffects = []; let combatEffects = []; for (const effect of technique.effects) { if (effect.includes('status_')) { statusEffects.push(new TechniqueEffect(effect)); } else if (!['damage', 'splash', 'area'].includes(effect)) { combatEffects.push(effect); } } for (const effect of statusEffects) { let recipient; if (effect.recipient === 'user') { recipient = user; } else { recipient = target; } if (effect.type === 'status') { if (effect.application === 'give') { if (recipient.statusEffect) { continue; } const potency = Math.random(); const success = technique.potency >= potency; if (success) { recipient.statusEffect = await fetchStatusEffect(effect.effect); } } else if (effect.application === 'remove') { if (recipient.statusEffect.slug === effect.effect) { recipient.statusEffect = ''; } } } } technique.combatEffects = combatEffects; }, async applyStatusEffect (affectedMonster, opposingMonster) { if (!affectedMonster.statusEffect) { return; } if (! (affectedMonster.statusEffect instanceof StatusEffect)) { return; } if (affectedMonster.statusEffect.slug === 'poison' || affectedMonster.statusEffect.slug === 'burn') { const statusEffectDamage = Math.floor(affectedMonster.status.hp / 8); affectedMonster.hp -= statusEffectDamage; UI.createDamage(clickEvent, statusEffectDamage); } else if (affectedMonster.statusEffect.slug === 'recover') { const statusEffectHeal = Math.floor(affectedMonster.status.hp / 16); affectedMonster.hp += statusEffectHeal; } else if (affectedMonster.statusEffect.slug === 'lifeleech') { const statusEffectLeech = Math.floor(affectedMonster.status.hp / 16); affectedMonster.hp -= statusEffectLeech; opposingMonster.hp += statusEffectLeech; UI.createDamage(clickEvent, statusEffectLeech); } else if (affectedMonster.statusEffect.slug === 'charging') { turnEndPhaseEvents.push(async () => { if (affectedMonster.statusEffect.turnsLeft === 0) { affectedMonster.statusEffect = await fetchStatusEffect('chargedup'); } }); } else if (affectedMonster.statusEffect.effects.includes('statchange')) { affectedMonster.resetStatusModifier(); for (const statusType in affectedMonster.statusEffect.status) { const statusChange = affectedMonster.statusEffect.status[statusType]; const modifiedValue = Math.floor(eval(`${affectedMonster.status[statusType]} ${statusChange.operation} ${statusChange.value}`)); affectedMonster.setStatusModifier(statusType, modifiedValue); } } affectedMonster.statusEffect.turnsLeft--; turnEndPhaseEvents.push(() => { if (affectedMonster.statusEffect.turnsLeft === 0) { affectedMonster.statusEffect = null; affectedMonster.resetStatusModifier(); } }); }, applyTechniqueEffect (technique, user, target) { for (const effect of technique.combatEffects) { if (effect === 'money') { state.money += Math.floor(Math.random() * target.level); } } // modify technique stats if (user.statusEffect) { if (user.statusEffect.slug === 'grabbed') { if ([TechniqueRange.ranged, TechniqueRange.reach].includes(technique.range)) { technique.potency = technique.base.potency * 0.5; technique.power = technique.base.power * 0.5; } } else if (user.statusEffect.slug === 'stuck') { if ([TechniqueRange.melee, TechniqueRange.touch].includes(technique.range)) { technique.potency = technique.base.potency * 0.5; technique.power = technique.base.power * 0.5; } } // remove effect if (user.statusEffect.turnsLeft === 0) { turnEndPhaseEvents.push(() => { technique.resetToBase(); }); } } }, }; UI.setActiveMonster(); UI.setEnemyMonster(); let clickEvent; let turnEndPhaseEvents = []; document.querySelector('#battle__enemy').addEventListener('click', async (event) => { clickEvent = event; const accuracy = Math.random(); const hit = state.activeTechnique.accuracy >= accuracy; if (hit) { await Game.useTechnique(state.activeTechnique, state.activeMonster, state.enemy.monster); } await Game.applyStatusEffect(state.activeMonster, state.enemy.monster); await Game.applyStatusEffect(state.enemy.monster, state.activeMonster); if (hit) { Game.applyTechniqueEffect(state.activeTechnique, state.activeMonster, state.enemy.monster); const damage = simpleDamageCalculation(state.activeTechnique, state.activeMonster, state.enemy.monster); UI.createDamage(event, damage); state.enemy.monster.hp -= damage; if (state.enemy.monster.hp <= 0) { const faintedMonster = state.enemy.monster; await UI.createNewEnemyMonster(); state.money += faintedMonster.level * faintedMonster.moneyModifier; state.activeMonster.exp += calculateAwardedExperience(state.activeMonster, faintedMonster); state.activeMonster.levelUp(); if (state.activeMonster.canEvolve()) { await fetchMonster(state.activeMonster.evolutions[0].monster_slug); state.activeMonster.evolve(); } } } else { UI.createDamage(event, 'MISS!'); } UI.setActiveMonster(); UI.setEnemyMonster(); UI.setHp(state.enemy.monster, battleEnemy); money.textContent = state.money; for (const turnEndPhaseEvent of turnEndPhaseEvents) { const returnValue = turnEndPhaseEvent(); if (returnValue instanceof Promise) { await returnValue; } } turnEndPhaseEvents = []; }); document.querySelector('#menu__party').addEventListener('click', UI.openPartyMenu); document.querySelector('#menu__catch').addEventListener('click', async () => { const caughtMonster = new Monster(state.enemy.monster.slug); caughtMonster.level = state.enemy.monster.level; state.partyMonsters.push(caughtMonster); UI.createNewEnemyMonster(); }); document.querySelector('#menu__inventory').addEventListener('click', UI.openInventoryMenu); })();