summaryrefslogtreecommitdiff
path: root/resources/js/game.js
diff options
context:
space:
mode:
authorDaniel Weipert <code@drogueronin.de>2023-08-17 02:53:14 +0200
committerDaniel Weipert <code@drogueronin.de>2023-08-17 17:42:15 +0200
commitcc685bfe02b42b592987117fa008a4461785f53c (patch)
tree625c1c9573b178e574bb70cac042c35da4036cf1 /resources/js/game.js
parent717fde1c48c7221da986ac02d2b806b2fee6f2d5 (diff)
refactorrefactor
Diffstat (limited to 'resources/js/game.js')
-rw-r--r--resources/js/game.js459
1 files changed, 459 insertions, 0 deletions
diff --git a/resources/js/game.js b/resources/js/game.js
new file mode 100644
index 0000000..1f1f92b
--- /dev/null
+++ b/resources/js/game.js
@@ -0,0 +1,459 @@
+const state = new State();
+
+
+const Game = {
+ phases: {
+ preAction: [],
+ action: [],
+ postAction: [],
+ },
+
+ didTechniqueHit: false,
+
+
+ async progressTurn () {
+ await Game.applyStatusEffect(state.enemy.monster);
+ await Game.applyStatusEffect(state.activeMonster);
+
+ for (const event of Game.phases.preAction) {
+ event();
+ }
+ Game.phases.preAction = [];
+ for (const event of Game.phases.action) {
+ event();
+ }
+ Game.phases.action = [];
+ for (const event of Game.phases.postAction) {
+ event();
+ }
+ Game.phases.postAction = [];
+
+ UI.drawEnemyMonster();
+ UI.drawActiveMonster();
+ UI.drawActiveTechniques();
+
+ UI.elements.money.textContent = state.money;
+ },
+
+ /**
+ * @param {Technique} technique
+ * @param {Monster} user
+ * @param {Monster} target
+ */
+ async useTechnique (technique, user, target) {
+ if (!Game.didTechniqueHit) {
+ UI.drawDamageMiss(UI.createDamageMiss());
+ return;
+ }
+
+ if (state.activeMonster.hp === state.activeMonster.stats.hp) {
+ state.activeMonster.hp = 1;
+ }
+
+ for (const techniqueEffect of technique.effects) {
+
+ // damage
+ if (['damage', 'splash', 'area'].includes(techniqueEffect)) {
+ Game.phases.action.push(() => {
+ const damage = simpleDamageCalculation(state.activeTechnique, state.activeMonster, state.enemy.monster);
+
+ state.enemy.monster.hp -= damage;
+ const damageNode = UI.createDamage(damage);
+ UI.applyMultiplierToDamage(damageNode, simpleDamageMultiplier(state.activeTechnique.types, state.enemy.monster.types));
+ UI.applyTechniqueToDamage(damageNode, state.activeTechnique);
+ UI.drawDamage(damageNode);
+ });
+ }
+
+ else if (techniqueEffect === 'money') {
+ state.money += Math.floor(Math.random() * target.level);
+ }
+
+ else if (techniqueEffect === 'enhance') {
+ UI.drawDamage(UI.createDamage('!!ENHANCE!!'));
+ }
+
+ // status effect
+ else if (techniqueEffect.includes('status_')) {
+ const statusEffect_recipient = techniqueEffect.split(',')[1];
+ const statusEffect_application = techniqueEffect.split(' ')[0];
+ const statusEffect_type = techniqueEffect.split(',')[0].split(' ')[1].split('_')[0];
+ const statusEffect_effect = techniqueEffect.split(',')[0].split(' ')[1].split('_')[1];
+
+ const statusEffect = await fetchStatusEffect(statusEffect_effect);
+ statusEffect.issuer = user;
+
+ let recipient;
+ if (statusEffect_recipient === 'user') {
+ recipient = user;
+ } else {
+ recipient = target;
+ }
+
+ Game.phases.postAction.push(() => {
+ // add status effect
+ const potency = Math.random();
+ const success = technique.potency >= potency;
+
+ if (success) {
+ // TODO: check replace
+ if (recipient.statusEffect) return;
+
+ recipient.statusEffect = statusEffect;
+ }
+ });
+
+ UI.drawTechniqueAnimation();
+ }
+ }
+ },
+
+ /**
+ * @param {Monster} monster
+ */
+ async applyStatusEffect (monster) {
+ if (!monster.statusEffect) {
+ return;
+ }
+
+ if (monster.statusEffect.turnsLeft === 0) {
+ Game.phases.preAction.push(() => {
+ monster.statusEffect.onRemove && monster.statusEffect.onRemove();
+
+ // if still 0 turns left after remove action
+ if (monster.statusEffect.turnsLeft === 0) {
+ monster.statusEffect = null;
+ } else {
+ Game.applyStatusEffect(monster);
+ }
+ });
+
+ return;
+ }
+
+ // poison / burn
+ if (monster.statusEffect.slug === 'poison' || monster.statusEffect.slug === 'burn') {
+ const statusEffectDamage = Math.floor(monster.stats.hp / 8);
+
+ Game.phases.postAction.push(() => {
+ monster.hp -= statusEffectDamage;
+
+ const damageNode = UI.createDamage(statusEffectDamage);
+ UI.applyStatusEffectToDamage(damageNode, monster.statusEffect);
+ UI.drawDamage(damageNode);
+ });
+ }
+
+ // lifeleech
+ else if (monster.statusEffect.slug === 'lifeleech') {
+ const statusEffectLeech = Math.floor(monster.stats.hp / 16);
+
+ Game.phases.postAction.push(() => {
+ monster.hp -= statusEffectLeech;
+ monster.statusEffect.issuer.hp += statusEffectLeech;
+
+ const damageNode = UI.createDamage(statusEffectLeech);
+ UI.applyStatusEffectToDamage(damageNode, monster.statusEffect);
+ UI.drawDamage(damageNode);
+ });
+ }
+
+ // recover
+ else if (monster.statusEffect.slug === 'recover') {
+ const statusEffectHeal = Math.floor(monster.stats.hp / 16);
+
+ Game.phases.postAction.push(() => {
+ monster.hp += statusEffectHeal;
+
+ const damageNode = UI.createDamage(statusEffectHeal);
+ UI.applyStatusEffectToDamage(damageNode, monster.statusEffect);
+ UI.drawDamage(damageNode);
+ });
+ }
+
+ // stuck
+ else if (monster.statusEffect.slug === 'stuck') {
+ for (const technique of monster.activeTechniques) {
+ if ([TechniqueRange.melee, TechniqueRange.touch].includes(technique.range)) {
+ Game.phases.preAction.push(() => {
+ technique.potency = technique.stats.potency * 0.5;
+ technique.power = technique.stats.power * 0.5;
+ });
+
+ monster.statusEffect.onRemove = () => {
+ technique.resetStats();
+ };
+ }
+ }
+ }
+
+ // grabbed
+ else if (monster.statusEffect.slug === 'grabbed') {
+ for (const technique of monster.activeTechniques) {
+ if ([TechniqueRange.ranged, TechniqueRange.reach].includes(technique.range)) {
+ Game.phases.preAction.push(() => {
+ technique.potency = technique.stats.potency * 0.5;
+ technique.power = technique.stats.power * 0.5;
+ });
+
+ monster.statusEffect.onRemove = () => {
+ technique.resetStats();
+ };
+ }
+ }
+ }
+
+ // charging
+ else if (monster.statusEffect.slug === 'charging') {
+ const nextStatusEffect = await fetchStatusEffect('chargedup');
+
+ monster.statusEffect.onRemove = () => {
+ monster.statusEffect = nextStatusEffect;
+ };
+ }
+
+ // statchange
+ else if (monster.statusEffect.effects.includes('statchange')) {
+ monster.resetStatModifiers();
+
+ for (const statType in monster.statusEffect.stats) {
+ const statChange = monster.statusEffect.stats[statType];
+ const modifiedValue = Math.floor(eval(`${monster.stats[statType]} ${statChange.operation} ${statChange.value}`));
+
+ Game.phases.preAction.push(() => {
+ monster.setStatModifier(statType, modifiedValue);
+ });
+ }
+
+ monster.statusEffect.onRemove = () => {
+ monster.resetStatModifiers();
+ };
+ }
+
+ Game.phases.postAction.push(() => {
+ monster.statusEffect.turnsLeft--;
+ });
+ },
+
+ async spawnEnemyMonster () {
+ 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.drawEnemyMonster();
+ },
+
+ /**
+ * @param {MouseEvent} event
+ */
+ async battleClick (event) {
+ UI.battleClickEvent = event;
+
+ // hit?
+ const accuracy = Math.random();
+ Game.didTechniqueHit = state.activeTechnique.accuracy >= accuracy;
+
+ await Game.useTechnique(state.activeTechnique, state.activeMonster, state.enemy.monster);
+
+ Game.phases.postAction.push(async () => {
+ // enemy defeated
+ if (state.enemy.monster.hp <= 0) {
+ // money
+ state.money += state.enemy.monster.level * state.enemy.monster.moneyModifier;
+
+ // exp
+ state.activeMonster.exp += calculateAwardedExperience(state.enemy.monster, [state.activeMonster])[0];
+
+ if (state.activeMonster.canLevelUp()) {
+ state.activeMonster.levelUp();
+ }
+ if (state.activeMonster.canEvolve()) {
+ await fetchMonster(state.activeMonster.evolutions[0].monster_slug);
+ state.activeMonster.evolve();
+ }
+
+ await Game.spawnEnemyMonster();
+ }
+ });
+
+ Game.progressTurn();
+ },
+
+ /**
+ * @param {MouseEvent} event
+ */
+ techniqueClick (event) {
+ if (event.target === UI.elements.techniques) {
+ return;
+ }
+
+ let target = event.target;
+ while (!target.classList.contains('techniques__technique')) {
+ target = target.parentNode;
+ }
+
+ const idx = [...UI.elements.techniques.children].indexOf(target);
+ state.activeTechnique = state.activeMonster.activeTechniques[idx];
+
+ // trigger battle click
+ const rect = UI.elements.battleEnemy.getBoundingClientRect();
+ const xMin = rect.left + 64;
+ const xMax = rect.right - 64;
+ const yMin = rect.top + 32;
+ const yMax = rect.bottom - 32;
+ UI.elements.battleEnemy.dispatchEvent(new MouseEvent('click', {
+ clientX: Math.random() * (xMax - xMin) + xMin,
+ clientY: Math.random() * (yMax - yMin) + yMin,
+ }));
+ },
+
+ catchMonster () {
+ const caughtMonster = new Monster(state.enemy.monster.slug);
+ caughtMonster.initialize();
+ caughtMonster.level = state.enemy.monster.level;
+
+ state.partyMonsters.push(caughtMonster);
+
+ Game.spawnEnemyMonster();
+ },
+
+ /**
+ * @returns {string}
+ */
+ save () {
+ const saveMonster = (monsterData, monsterState) => {
+ monsterData.level = monsterState.level;
+ monsterData.hp = monsterState.hp;
+
+ return monsterData;
+ };
+
+ const saveData = JSON.parse(JSON.stringify(state));
+
+ for (const idx in saveData.monsters) {
+ saveData.monsters[idx] = saveMonster(saveData.monsters[idx], state.monsters[idx]);
+ }
+
+ for (const idx in saveData.partyMonsters) {
+ saveData.partyMonsters[idx] = saveMonster(saveData.partyMonsters[idx], state.partyMonsters[idx]);
+ }
+
+ saveData.activeMonsterIdx = state.partyMonsters.indexOf(state.activeMonster);
+
+ saveData.enemy.monster = saveMonster(saveData.enemy.monster, state.enemy.monster);
+
+ return btoa(JSON.stringify(saveData));
+ },
+
+ /**
+ * @param {string} saveData
+ */
+ async load (saveData) {
+ /**
+ * @param {Monster} monsterData
+ */
+ const loadMonster = async (monsterData) => {
+ const monster = await fetchMonster(monsterData.slug);
+
+ monster.level = monsterData.level;
+ monster.hp = monsterData.hp;
+ monster.exp = monsterData.exp;
+ monster.tasteWarm = monsterData.tasteWarm;
+ monster.tasteCold = monsterData.tasteCold;
+ monster.gender = monsterData.gender;
+ monster.heldItem = await loadItem(monsterData.heldItem);
+ monster.statusEffect = await loadStatusEffect(monsterData.statusEffect);
+ monster.statModifiers = monsterData.statModifiers;
+ monster.experienceModifier = monsterData.experienceModifier;
+ monster.moneyModifier = monsterData.moneyModifier;
+ monster.activeTechniques = await Promise.all(monsterData.activeTechniques.map(async (technique) => {
+ return loadTechnique(technique);
+ }));
+
+ return monster;
+ };
+
+ /**
+ * @param {Item} itemData
+ */
+ const loadItem = async (itemData) => {};
+
+ /**
+ * @param {StatusEffect} statusEffectData
+ */
+ const loadStatusEffect = async (statusEffectData) => {
+ if (!statusEffectData) {
+ return null;
+ }
+
+ const statusEffect = await fetchStatusEffect(statusEffectData.slug);
+
+ statusEffect.turnsLeft = statusEffectData.turnsLeft;
+
+ return statusEffect;
+ };
+
+ /**
+ * @param {Technique} techniqueData
+ */
+ const loadTechnique = async (techniqueData) => {
+ const technique = await fetchTechnique(techniqueData.slug);
+
+ return technique;
+ };
+
+ /**
+ * @type {State}
+ */
+ const loadedState = JSON.parse(atob(saveData));
+
+ state.money = loadedState.money;
+ state.monsters = await Promise.all(loadedState.monsters.map(async (monsterData) => await loadMonster(monsterData)));
+ state.inventory = await Promise.all(loadedState.inventory.map(async (itemData) => await loadItem(itemData)));
+ state.partyMonsters = await Promise.all(loadedState.partyMonsters.map(async (monsterData) => await loadMonster(monsterData)));
+ state.activeMonster = state.partyMonsters[loadedState.activeMonsterIdx];
+ state.activeTechnique = await loadTechnique(loadedState.activeTechnique);
+ state.enemy.monster = await loadMonster(loadedState.enemy.monster);
+
+ UI.drawEnemyMonster();
+ UI.drawActiveMonster();
+ UI.drawActiveTechniques();
+ },
+};
+
+// Game click bindings
+UI.elements.battleEnemy.addEventListener('click', Game.battleClick);
+UI.elements.techniques.addEventListener('click', Game.techniqueClick);
+UI.elements.menuCatch.addEventListener('click', Game.catchMonster);
+
+
+(async function () {
+ await initializeDB();
+
+ const possibleStarterMonsters = ['budaye', 'dollfin', 'grintot', 'ignibus', 'memnomnom'];
+
+ // state.enemy.monster = await fetchMonster('grintot');
+ state.enemy.monster = await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]);
+
+ state.partyMonsters = [
+ await fetchMonster(possibleStarterMonsters[Math.round(Math.random() * (possibleStarterMonsters.length - 1))]),
+ await fetchMonster('corvix'),
+ await fetchMonster('lunight'),
+ await fetchMonster('prophetoise'),
+ await fetchMonster('drashimi'),
+ await fetchMonster('glombroc'),
+ await fetchMonster('uneye'),
+ await fetchMonster('nostray'),
+ await fetchMonster('dragarbor'),
+ await fetchMonster('mk01_omega'),
+ ];
+
+ state.activeMonster = state.partyMonsters[0];
+ state.activeTechnique = state.activeMonster.activeTechniques[0];
+
+ UI.drawEnemyMonster();
+ UI.drawActiveMonster();
+ UI.drawActiveTechniques();
+})();