summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-08-15 13:18:57 +0200
committerDaniel Weipert <code@drogueronin.de>2023-08-15 13:18:57 +0200
commit8ab8988d01199f64151c532c59ff6c08735d4e37 (patch)
tree17d5bbdf36aa744119f37e0fffc6ede78d13fa70
initial commit
-rw-r--r--.gitignore0
-rw-r--r--.gitmodules3
-rw-r--r--Readme.md1
-rw-r--r--all-monsters.php10
-rw-r--r--db/all-monsters.json1
-rw-r--r--index.html81
m---------modules/tuxemon0
-rw-r--r--script.js583
-rw-r--r--style.css190
9 files changed, 869 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.gitignore
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c3786a0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "modules/tuxemon"]
+ path = modules/tuxemon
+ url = https://github.com/Tuxemon/Tuxemon
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..7de2ce4
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1 @@
+- https://wiki.tuxemon.org/Category:Monster
diff --git a/all-monsters.php b/all-monsters.php
new file mode 100644
index 0000000..cf687c7
--- /dev/null
+++ b/all-monsters.php
@@ -0,0 +1,10 @@
+<?php
+
+$allMonsters = [];
+foreach (scandir(__DIR__ . '/modules/tuxemon/mods/tuxemon/db/monster') as $file) {
+ if (in_array($file, ['.', '..'])) continue;
+
+ $allMonsters[] = pathinfo($file, PATHINFO_FILENAME);
+}
+
+file_put_contents(__DIR__ . '/db/all-monsters.json', json_encode($allMonsters));
diff --git a/db/all-monsters.json b/db/all-monsters.json
new file mode 100644
index 0000000..4011363
--- /dev/null
+++ b/db/all-monsters.json
@@ -0,0 +1 @@
+["aardart","aardorn","abesnaki","agnidon","agnigon","agnite","agnsher","allagon","altie","ambuwl","angesnow","angrito","anoleaf","anu","apeoro","araignee","arthrobolt","av8r","axylightl","b_ver_1","bamboon","banling","baobaraffe","baoby","bearloch","beenstalker","bigfin","birdling","blasdoor","bolt","boltnu","botbot","boxali","brewdin","bricgard","brickhemoth","budaye","bugnin","bumbulus","bursa","cairfrey","capinyah","capiti","cardiling","cardinale","cardiwing","cataspike","cateye","chenipode","cherubat","chibiro","chillimp","chloragon","chrome_robo","chromeye","cochini","coleorus","conglolem","conifrost","conileaf","coproblight","corvix","cowpignon","criniotherme","d0llf1n","dandicub","dandylion","dankush","dark_robo","demosnow","dinoflop","djinnbo","dollfin","dracune","dragarbor","drashimi","drokoro","dune_pincher","eaglace","elofly","elostorm","elowind","embazook","embra","enduros","eruptibus","exapode","exclawvate","eyenemy","eyesore","f7u1t3ra","fancair","ferricran","firomenis","flacono","flambear","flisces","fluoresfin","fluttaflap","foofle","fordin","forturtle","foxfire","foxko","fribbit","frondly","fruitera","furnursus","fuzzina","fuzzlet","galnec","gectile","ghosteeth","glombroc","graffiki","grimachin","grinflare","grintot","grintrock","grumpi","gryfix","hampotamos","happito","hatchling","hectapod","heronquak","hotline","howl","hydrone","ignibus","imbrickcile","incandesfin","jelillow","jemuar","joulraton","k9","katacoon","katapill","kernel","komodraw","l3gk0","lambert","lapinou","legko","lendos","lightmare","loliferno","lucifice","lunight","manosting","masknake","mauai","medushock","memnomnom","merlicun","metesaur","miaownolith","mingdyn","mk01_alpha","mk01_beta","mk01_delta","mk01_gamma","mk01_omega","mk01_proto","moloch","mrmoswitch","mystikapi","narcileaf","neutrito","nimbulex","noctalo","noctula","nostray","nudiflot_female","nudiflot_male","nudikill","nudimind","nuenflu","nut","octabode","ouroboutlet","pairagrim","pairagrin","pantherafira","pharfan","picc","pigabyte","pilthropus","pipis","poinchin","polyrock","possessun","potturmeist","potturney","propellercat","prophetoise","puparmor","pyraminx","pythock","pythwire","qetzlrokilus","r0ck1tt3n","rabbitosaur","rhincus","rhinocarpe","rinocereed","rockat","rockitten","rocktot","rosarin","ruption","sadito","sampsack","sampsage","sapragon","sapsnap","saurchin","sclairus","seirein","selket","selmatek","seraphice","shammer","sharpfin","shelagu","shnark","shybulb","skwib","slichen","sludgehog","snaki","snarlon","snock","snokari","snowrilla","sockeserp","solight","spighter","spoilurm","spycozeus","squabbit","squink","statursus","strella","sumchon","tadcool","tarpeur","taupypus","teddisun","tetrchimp","tigrock","tikoal","tikorch","tobishimi","toufigel","tourbidi","trapsnap","tsushimi","tumblebee","tumbledillo","tumblequill","tumbleworm","turnipper","tux","tweesher","uf0","uglip","uneye","urcheedle","urcine","vamporm","velocitile","vigueur","vivicinder","vividactil","vivipere","viviphyta","vivisource","viviteel","vivitrans","vivitron","volcoli","waysprite","weavifly","windeye","woodoor","wrougon","xeon","xeon_2","yiinaang","ziggurat","zunna"] \ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5b1c653
--- /dev/null
+++ b/index.html
@@ -0,0 +1,81 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="style.css" />
+ </head>
+ <body>
+ <div class="wrap">
+ <div>Money: <div id="money"></div></div>
+ <div id="party"></div>
+
+ <div id="battle">
+ <div id="battle__enemy"></div>
+
+ <div id="battle__player"></div>
+ </div>
+ </div>
+
+ <template id="tpl___party__monster">
+ <div class="party__monster">
+ <div><div class="monster__level"></div></div>
+ <img src="" />
+ <div class="monster__exp">
+ <div class="monster__exp-bar"></div>
+ </div>
+ <div class="monster__exp-text"></div>
+ <div class="monster__active-technique"></div>
+ </div>
+ </template>
+
+ <template id="tpl___battle__monster">
+ <div class="battle__monster">
+ <div class="battle__monster-info">
+ <div class="battle__monster-info-box">
+ <div>
+ <span class="battle__monster-info__name">{NAME}</span> <span class="battle__monster-info__gender">{GENDER}</span> Lv. <span class="battle__monster-info__level">{LEVEL}</span>
+ </div>
+ <div class="hp">
+ <div class="hp-bar-wrap">
+ <div class="hp-bar"></div>
+ </div>
+ <div class="hp-text"></div>
+ </div>
+ </div>
+ <div class="battle__monster-info-exp">
+ <div class="exp-label">XP</div>
+ <div class="exp">
+ <div class="exp-bar-wrap">
+ <div class="exp-bar"></div>
+ </div>
+ <div class="exp-text"></div>
+ </div>
+ </div>
+ </div>
+ <div class="battle__monster-visual">
+ <div class="battle__monster-sprite">
+ <img src="" draggable="false" />
+ </div>
+ <div class="battle__monster-technique">
+
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <template id="tpl___popup">
+ <div class="popup__overlay">
+ <div class="popup"></div>
+ </div>
+ </template>
+
+ <template id="tpl___moveset__list">
+ <div class="moveset__list"></div>
+ </template>
+ <template id="tpl___moveset__item">
+ <div class="moveset__item">
+ name, lvlrequired, types, power
+ </div>
+ </template>
+
+ <script type="text/javascript" src="script.js"></script>
+ </body>
+</html>
diff --git a/modules/tuxemon b/modules/tuxemon
new file mode 160000
+Subproject 136c50bff1fb5b04a1cb0032030fb868c0bca9e
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..c5c2028
--- /dev/null
+++ b/script.js
@@ -0,0 +1,583 @@
+const DB = {
+ allMonsters: [],
+ monsters: {},
+ shapes: {},
+ elements: {},
+ techniques: {},
+};
+
+const ElementType = {
+ aether: 'aether',
+ wood: 'wood',
+ fire: 'fire',
+ earth: 'earth',
+ metal: 'metal',
+ water: 'water',
+};
+const ElementTypeColor = {
+ [ElementType.aether]: 'rgba(255, 255, 255, 1)',
+ [ElementType.wood]: 'rgb(0, 255, 0, 1)',
+ [ElementType.fire]: 'red',
+ [ElementType.earth]: 'brown',
+ [ElementType.metal]: 'silver',
+ [ElementType.water]: 'blue',
+};
+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',
+};
+
+class State {
+ money = 0;
+ monsters = [];
+
+ partyMonsters = [];
+ activeMonster = null;
+ activeTechnique = null;
+
+ enemy = {
+ monster: null,
+ };
+};
+
+class Monster {
+ #level = 1;
+ exp = 1;
+ hp = 0;
+
+ tasteWarm = TasteWarm.tasteless;
+ tasteCold = TasteCold.tasteless;
+
+ name = '';
+ gender = '';
+
+ statusModifiers = {
+ 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 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
+ );
+ }
+
+ canEvolve () {
+ if (this.evolutions.length === 0) {
+ return;
+ }
+
+ return this.level >= this.evolutions[0].at_level;
+ }
+
+ evolve () {
+ const evolution = this.evolutions[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.statusModifiers.hp;
+ let melee = (this.shape.melee * multiplier) + this.statusModifiers.melee;
+ let armour = (this.shape.armour * multiplier) + this.statusModifiers.armour;
+ let ranged = (this.shape.ranged * multiplier) + this.statusModifiers.ranged;
+ let dodge = (this.shape.dodge * multiplier) + this.statusModifiers.dodge;
+ let speed = (this.shape.speed * multiplier) + this.statusModifiers.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,
+ };
+ }
+};
+
+class Technique {
+ constructor (slug) {
+ this.slug = slug;
+ }
+
+ get types () {
+ return DB.techniques[this.slug].types;
+ }
+
+ get power () {
+ return DB.techniques[this.slug].power;
+ }
+
+ get range () {
+ return DB.techniques[this.slug].range;
+ }
+}
+
+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) {
+ 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 damage;
+}
+
+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);
+}
+
+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.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('drashimi');
+
+ state.partyMonsters = [
+ await fetchMonster('corvix'),
+ await fetchMonster('lunight'),
+ await fetchMonster('prophetoise'),
+ await fetchMonster('drashimi'),
+ ];
+ 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,
+
+ 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;
+ },
+
+ 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 + 'rem';
+
+ damageNode.style.left = event.pageX - battleEnemy.offsetLeft;
+ damageNode.style.top = event.pageY - battleEnemy.offsetTop;
+
+ 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);
+
+ const enemyImg = battleEnemy.querySelector('img');
+ const imgClickDuration = 0.1;
+ enemyImg.style.transitionDuration = imgClickDuration + 's';
+ enemyImg.classList.add('damaged');
+ clearTimeout(clickTimeout);
+ clickTimeout = setTimeout(() => enemyImg.classList.remove('damaged'), imgClickDuration * 1000);
+ },
+
+ addPartyMonster (slug) {
+ let partyMonster = document.createElement('div');
+ partyMonster.innerHTML = templatePartyMonster.trim();
+ partyMonster = partyMonster.firstChild;
+
+ partyMonster.dataset.slug = slug;
+ partyMonster.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${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)];
+ state.activeTechnique = await fetchTechnique(state.activeMonster.moveset[0].technique);
+
+ UI.setBattleMonster(state.activeMonster, 'player');
+ });
+ partyMonster.style.cursor = 'pointer';
+
+ party.appendChild(partyMonster);
+ },
+
+ 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;
+ UI.setHp(monster, battleMonster.querySelector('.hp'));
+ battleMonster.querySelector('img').src = `/modules/tuxemon/mods/tuxemon/gfx/sprites/battle/${monster.slug}-front.png`;
+
+ if (where === 'player') {
+ UI.setExp(monster, battleMonster.querySelector('.exp'));
+ battleMonster.querySelector('.battle__monster-technique').textContent =
+ state.activeTechnique.slug + ' - ' + state.activeTechnique.types + ' - ' + state.activeTechnique.power;
+ battleMonster.querySelector('.battle__monster-technique').addEventListener('click', UI.openMovesetSelection);
+
+ battleMonster.classList.add('battle__monster--player');
+ battlePlayer.replaceChildren(battleMonster);
+ } else {
+ battleMonster.classList.add('battle__monster--enemy');
+ // battleEnemy.replaceChildren(battleMonster);
+ battleEnemy.querySelector('.battle__monster') && battleEnemy.removeChild(battleEnemy.querySelector('.battle__monster'));
+ battleEnemy.appendChild(battleMonster);
+ }
+ },
+
+ async openMovesetSelection () {
+ const popup = UI.getTemplate(templatePopup);
+ popup.addEventListener('click', ({ target }) => target === popup && popup.remove());
+
+ const movesetList = UI.getTemplate(templateMovesetList);
+ for (const move of state.activeMonster.moveset) {
+ const technique = await fetchTechnique(move.technique);
+ const movesetItem = UI.getTemplate(templateMovesetItem);
+
+ movesetItem.textContent = slugToName(technique.slug) + ' - ' + technique.types + ' - ' + technique.power;
+ movesetItem.addEventListener('click', () => {
+ state.activeTechnique = technique;
+ UI.setBattleMonster(state.activeMonster, 'player');
+
+ popup.remove();
+ });
+
+ movesetList.appendChild(movesetItem);
+ }
+
+ popup.querySelector('.popup').appendChild(movesetList);
+ document.body.appendChild(popup);
+ },
+ };
+
+ for (const monster of state.partyMonsters) {
+ UI.addPartyMonster(monster.slug);
+ }
+
+ UI.setBattleMonster(state.activeMonster, 'player');
+ UI.setBattleMonster(state.enemy.monster, 'enemy');
+
+ var clickTimeout;
+ document.querySelector('#battle__enemy').addEventListener('click', async (event) => {
+ 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;
+
+ 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.moneyModifier = state.enemy.monster.level;
+ UI.setBattleMonster(state.enemy.monster, 'enemy');
+
+ 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();
+ state.activeTechnique = await fetchTechnique(state.activeMonster.moveset[0].technique);
+ }
+
+ UI.setBattleMonster(state.activeMonster, 'player');
+ }
+ UI.setHp(state.enemy.monster, battleEnemy);
+ money.textContent = state.money;
+ });
+})();
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..8ffcee1
--- /dev/null
+++ b/style.css
@@ -0,0 +1,190 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+img {
+ display: inline-block;
+ max-width: 100%;
+}
+
+.wrap {
+ margin: 0 auto;
+ width: 1200px;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.popup__overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.8);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.popup {
+ background-color: #fff;
+ padding: 1rem;
+}
+
+#battle {
+ user-select: none;
+ min-width: 750px;
+ min-height: 300px;
+ padding: 1rem;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ background-image: url('/modules/tuxemon/mods/tuxemon/gfx/backgrounds/test/back02.png');
+ background-image: url('https://wiki.tuxemon.org/images/9/9f/Sea_background.png');
+ background-size: cover;
+}
+
+#battle__enemy {
+ position: relative;
+ cursor: pointer;
+ flex-grow: 1;
+}
+
+.battle__monster {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.battle__monster-info {
+ border: 2px solid #000;
+ background-color: beige;
+
+ flex-grow: 1;
+ flex-basis: 50%;
+}
+.battle__monster-info-box {
+ display: flex;
+ flex-direction: column;
+
+ padding: 0.25rem;
+}
+.battle__monster-info__gender {
+ line-height: 1em;
+}
+.battle__monster-info-exp {
+ display: flex;
+ align-items: center;
+
+ padding: 0.25rem;
+
+ background-color: #000;
+ color: yellow;
+}
+.exp-label {
+ margin-right: 0.5rem;
+}
+.battle__monster-visual {
+ flex-grow: 1;
+ flex-basis: 50%;
+ text-align: center;
+}
+
+.battle__monster-sprite {
+ margin-bottom: 0.25rem;
+}
+.battle__monster-sprite img {
+ transition-property: filter;
+}
+.battle__monster-sprite img.damaged {
+ filter: brightness(2);
+}
+
+.battle__monster-technique {
+ background-color: beige;
+ border: 2px solid #000;
+ display: inline;
+ padding: 0.25rem;
+}
+
+.battle__monster--player {
+ flex-direction: row-reverse;
+}
+.battle__monster--enemy .battle__monster-info-exp,
+.battle__monster--enemy .battle__monster-technique {
+ display: none;
+}
+
+.exp {
+ width: 100%;
+}
+.exp-bar-wrap {
+ width: 100%;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+ background-color: lightgray;
+}
+.exp-bar {
+ background-color: blue;
+ height: 7px;
+ transition: background-color;
+ width: 0%;
+}
+.hp-bar-wrap {
+ width: 100%;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+}
+.hp-bar {
+ background-color: green;
+ height: 10px;
+ transition: background-color;
+}
+
+#enemy {
+ position: relative;
+ user-select: none;
+ cursor: pointer;
+ min-width: 750px;
+ min-height: 300px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid #000;
+ flex-direction: column;
+
+ background-image: url('/modules/tuxemon/mods/tuxemon/gfx/backgrounds/test/back02.png');
+ background-image: url('https://wiki.tuxemon.org/images/9/9f/Sea_background.png');
+ background-size: cover;
+}
+
+#enemy img {
+ transition-property: filter;
+}
+#enemy img.clicked {
+ filter: brightness(2);
+}
+
+
+.damage {
+ position: absolute;
+ color: red;
+
+ animation: float-up-and-disappear;
+}
+
+@keyframes float-up-and-disappear {
+ from {
+ opacity: 1;
+ }
+
+ to {
+ top: 0;
+ opacity: 0;
+ font-size: 0;
+ }
+}