« get me outta code hell

Basic AI - csb-game - Pixelly spin-off of the Command Synergy Battle system used in Final Fantasy XIII
summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2018-08-19 15:08:27 -0300
committerFlorrie <towerofnix@gmail.com>2018-08-19 15:08:27 -0300
commite1a066460de34b71c5b49d8536fe156194af2672 (patch)
tree72fbaa1807be7de4136fbe5638c46d9459d453cf
parent6b9ae24cbfbc680833d40195bdcaaf7b8bd0d60d (diff)
Basic AI
-rw-r--r--index.js124
1 files 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}
     ]}
   ]
 })