From e1a066460de34b71c5b49d8536fe156194af2672 Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 19 Aug 2018 15:08:27 -0300 Subject: Basic AI --- index.js | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index a0cda18..929a9e6 100644 --- a/index.js +++ b/index.js @@ -35,6 +35,16 @@ const actionDatabase = { manasvinRecover: {id: 'manasvinRecover', label: 'Recover', size: 6, target: 'ally', color: '#AFA', heal: 175, cooldown: 2} } +const isHealingAction = action => { + if (action.heal) { + return true + } else if (action.healRelativeToDamageTaken) { + return true + } else { + return false + } +} + class Sprite { constructor() { this.x = 0 @@ -206,6 +216,9 @@ class ATBBar { return } + this.battleCharacter.determineChain() + + /* // Determine a target // TODO: AI logic for when to change target // TODO: Maybe need to bump the Math.random() chances. @@ -220,23 +233,9 @@ class ATBBar { // ATB gauge not full? Try to queue actions if (!this.battleCharacter.isExecutingChain && this.battleCharacter.targetCharacter && this.getRemainingSpace() >= 0) { // TODO: Actual AI logic here - enqueueActions: while (this.getRemainingSpace()) { - const actions = this.battleCharacter.knownActions - .map(actionID => actionDatabase[actionID]) - .filter(a => a.target === this.battleCharacter.targetType) - while (actions.length) { - const index = randomIndex(actions) - const action = actions[index] - actions.splice(index, 1) - if (this.enqueue(action)) { - continue enqueueActions - } - } - break - } - this.battleCharacter.willExecuteChain = true } + */ } getRemainingSpace() { @@ -564,12 +563,10 @@ class TargetMenu extends BaseBattleMenu { this.options = this.battle.playerCharacter.getValidTargets(targetType) .map(char => ({label: char.name, battleCharacter: char})) - // If the top action is a 'cure' action, pick the character with the least health by default. + // If the top action is a "healing" action, pick the character with the least health by default. const lastAction = last(this.battle.playerCharacter.atbBar.queuedActions) - if (lastAction && (lastAction.heal || lastAction.healRelativeToDamageTaken)) { - const allies = this.battle.playerCharacter.team.characters - const aliveCharacter = allies.find(c => !c.dead) - const mostHurtCharacter = allies.reduce((a, b) => (b.hp < a.hp && !b.dead ? b : a), aliveCharacter) + if (lastAction && isHealingAction(lastAction)) { + const mostHurtCharacter = this.battle.playerCharacter.team.getMostHurtCharacter() this.currentOptionIndex = this.options.findIndex(opt => opt.battleCharacter === mostHurtCharacter) } } @@ -771,6 +768,25 @@ class BattleCharacter extends Sprite { } } + enqueueActions(callback) { + enqueueActions: while (this.atbBar.getRemainingSpace()) { + const validActions = this.getValidActions(this.targetType) + while (validActions.length) { + if (callback(validActions)) { + continue enqueueActions + } + } + break + } + } + + getValidActions(targetType) { + return this.knownActions + .map(actionID => actionDatabase[actionID]) + .filter(a => a.target === targetType) + .filter(a => !this.cooldownTimers[a.id]) + } + getValidTargets(targetType) { return battle.getAllBattleCharacters() .filter(char => @@ -828,6 +844,10 @@ class BattleCharacter extends Sprite { this.stunIsSignificant = false } } + + determineChain() { + // Should be overriden in subclasses/instances. + } } class Battle { @@ -1133,6 +1153,11 @@ class Team { character.update(dt) } } + + getMostHurtCharacter() { + const aliveCharacter = this.characters.find(c => !c.dead) + return this.characters.reduce((a, b) => (b.hp < a.hp && !b.dead ? b : a), aliveCharacter) + } } class Camera extends Sprite { @@ -1274,14 +1299,67 @@ class SlideacrossMessage { } } +const basicAI = function() { + const mostHurtAlly = this.team.getMostHurtCharacter() + + // Not currently executing a chain or in the process of doing so. Now is an + // okay time to change target or queue actions. + if (!this.isExecutingChain && !this.willExecuteChain) { + determineTarget: { + // If a party member is low on HP, target them. + if (mostHurtAlly.hp / mostHurtAlly.maxHP <= 0.5) { + // We can only heal them if we have healing actions available! + if (this.getValidActions('ally').find(a => isHealingAction(a))) { + this.targetType = 'ally' + this.targetCharacter = mostHurtAlly + break determineTarget + } + } + + // Otherwise, pick an enemy to attack. + this.determineNewTarget('enemy') + } + + queueActions: { + // If targetting an ally, heal them. + if (this.targetType === 'ally') { + this.enqueueActions(validActions => { + const index = randomIndex(validActions) + const action = validActions[index] + validActions.splice(index, 1) + return this.atbBar.enqueue(action) + }) + break queueActions + } + + // If targetting an enemy, attack them. + if (this.targetType === 'enemy') { + // Just use random offensive actions. + this.enqueueActions(validActions => { + const index = randomIndex(validActions) + const action = validActions[index] + validActions.splice(index, 1) + return this.atbBar.enqueue(action) + }) + break queueActions + } + } + + // If we've actually enqueued anything, start executing: + if (this.atbBar.queuedActions.length) { + this.willExecuteChain = true + } + } +} + const battle = new Battle({ teamData: [ {characterData: [ - {x: -55, y: 190, name: 'Ren', hp: 600, maxHP: 600, knownActions: ['fire', 'fira', 'firaga', 'blizz', 'blizzara', 'cure']}, - {x: -75, y: 225, name: 'Fie', hp: 475, maxHP: 475, knownActions: ['fire', 'blizz', 'blizzara', 'blizzaga', 'aqua', 'aquara', 'curasa']} + {x: -55, y: 190, name: 'Ren', hp: 600, maxHP: 600, knownActions: ['fire', 'fira', 'firaga', 'blizz', 'blizzara', 'cure'], determineChain: basicAI}, + {x: -75, y: 225, name: 'Fie', hp: 475, maxHP: 475, knownActions: ['fire', 'blizz', 'blizzara', 'blizzaga', 'aqua', 'aquara', 'curasa'], determineChain: basicAI} ]}, {characterData: [ - {x: 80, y: 200, aiID: 'manasvinWarmech', name: 'Manasvin Warmech', hp: 1600, maxHP: 1600, knownActions: ['aqua', 'aquaga', 'manasvinSwipe', 'manasvinRecover']} + {x: 80, y: 200, aiID: 'manasvinWarmech', name: 'Manasvin Warmech', hp: 1600, maxHP: 1600, knownActions: ['aqua', 'aquaga', 'manasvinSwipe', 'manasvinRecover'], determineChain: basicAI} ]} ] }) -- cgit 1.3.0-6-gf8a5