« get me outta code hell

Action cooldowns - csb-game - Pixelly spin-off of the Command Synergy Battle system used in Final Fantasy XIII
summary refs log tree commit diff
path: root/index.js
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2018-08-17 09:06:13 -0300
committerFlorrie <towerofnix@gmail.com>2018-08-17 09:06:14 -0300
commit0cc49bacfab18e8e146fec890674b489e8a192e4 (patch)
tree74b39de3f5061c0e25e8355d151f8875c0fad062 /index.js
parent4d51316c23687f658a4676c6ba2bb18b653caeff (diff)
Action cooldowns
Also tweak player character ATB gauges to be 4 segments instead of 6
Diffstat (limited to 'index.js')
-rw-r--r--index.js69
1 files changed, 65 insertions, 4 deletions
diff --git a/index.js b/index.js
index b0e57b9..c927385 100644
--- a/index.js
+++ b/index.js
@@ -27,8 +27,8 @@ const actionDatabase = {
   aquara: {id: 'aquara', chain: 'aqua', label: 'Aquara', size: 2, target: 'enemy', color: '#33F', damage: 60, stun: 0},
   aquaga: {id: 'aquaga', chain: 'aqua', label: 'Aquaga', size: 3, target: 'enemy', color: '#33F', damage: 90, stun: 0},
 
-  manasvinSwipe: {id: 'manasvinSwipe', label: 'Swipe', size: 3, target: 'enemy', color: '#CCC', damage: 125, stun: 1.25},
-  manasvinRecover: {id: 'manasvinRecover', label: 'Recover', size: 6, target: 'ally', color: '#AFA', heal: 300}
+  manasvinSwipe: {id: 'manasvinSwipe', label: 'Swipe', size: 3, target: 'enemy', color: '#CCC', damage: 125, stun: 1.25, cooldown: 1},
+  manasvinRecover: {id: 'manasvinRecover', label: 'Recover', size: 6, target: 'ally', color: '#AFA', heal: 175, cooldown: 2}
 }
 
 class Sprite {
@@ -193,7 +193,7 @@ class ATBBar {
     // TODO: AI logic for when to change target
     // TODO: Maybe need to bump the Math.random() chances.
     if (!this.battleCharacter.isExecutingChain && !this.battleCharacter.willExecuteChain && (this.battleCharacter.aiID === 'manasvinWarmech' || !this.battleCharacter.targetCharacter)) {
-      if (this.battleCharacter.aiID === 'manasvinWarmech' && Math.random() < 1 - this.battleCharacter.hp / this.battleCharacter.maxHP) {
+      if (this.battleCharacter.aiID === 'manasvinWarmech' && Math.random() < (1 - this.battleCharacter.hp / this.battleCharacter.maxHP) / 2) {
         this.battleCharacter.determineNewTarget('ally')
       } else {
         this.battleCharacter.determineNewTarget('enemy')
@@ -227,7 +227,13 @@ class ATBBar {
   }
 
   dequeue() {
-    this.queuedActions.pop()
+    if (this.queuedActions.length) {
+      const action = this.queuedActions.pop()
+
+      if (action.cooldown) {
+        delete this.battleCharacter.cooldownTimers[action.id] // Or set to 0, either works
+      }
+    }
   }
 
   enqueue(action) {
@@ -235,7 +241,14 @@ class ATBBar {
       return false
     } else if (this.getRemainingSpace() < action.size) {
       return false
+    } else if (action.cooldown && this.battleCharacter.cooldownTimers[action.id]) {
+      return false
     } else {
+      if (action.cooldown) {
+        this.battleCharacter.cooldownTimers[action.id] = action.cooldown * this.segmentCount
+        this.battleCharacter.cooldownTimers[action.id] -= this.segmentCount - this.getRemainingSpace()
+        console.log('Enqueued cooldown:', action.label, action.cooldown * this.segmentCount, this.battleCharacter.cooldownTimers[action.id])
+      }
       this.queuedActions.push(action)
       return true
     }
@@ -612,6 +625,44 @@ class BattleCharacter extends Sprite {
 
     // Array of action IDs.
     this.knownActions = []
+
+    // Cooldown timers for each action with a cooldown. If an action's cooldown
+    // is greater than zero, it can't be queued. A unit of "1" on the cooldown
+    // means one full ATB gauge of executed actions. So if the ATB gauge length
+    // is 3 and a size-2 action is executed, all timers go down by 2/3.
+    // Note, however, that on this particular object a unit of 1 refers to one
+    // individual ATB segment; however, the cooldown property on an action
+    // object behaves as described above. The reason 1 refers to a single ATB
+    // segment here is to avoid decimal rounding issues; 3 - 2 is 1 but that's
+    // not so certain with math similar to 1 - 2/3.
+    // If an action can successfully be enqueued (i.e. its cooldown timer is
+    // already zero), its cooldown timer is immediately set according to its
+    // cooldown property. This is so that it cannot be enqueued twice within
+    // a single queue. If it is dequeued, its cooldown timer is reset to 0.
+    // A cooldown timer of 1 means "can't be executed more than once in a
+    // single queue", but will be able to be enqueued and executed during the
+    // next queue (there is no number of queues that must be waited).
+    // Technically this behavior is surprising since, if a move with cooldown
+    // is executed at the end of a queue, it can enqueued immediately again
+    // in the next queue - after less than its cooldown timer has passed.
+    // To implement this, the cooldown timer is initially offset by the number
+    // of ATB segments before where its action is enqueued to. So in our 2-size
+    // action with a cooldown of 1 being enqueued to the end of a 3-ATB gauge
+    // queue, its cooldown timer is initially offset by -1/3, so that only 2/3,
+    // or 2 ATB segments, must pass before it can be enqueued again. Since the
+    // move with cooldown itself is a 2-size move, it will account for the
+    // remaining ATB space.
+    // As of this writing there is no designed special behavior for what should
+    // happen if an action with cooldown is executed as part of a queue that is
+    // not entirely filled. My understanding is that its cooldown timer would
+    // not be fully cleared since, even taking into account the initial offset,
+    // not enough actions would have been executed after the cooldown action.
+    // Thus another ATB gauge would have to be waited until the timer reaches
+    // zero. This is not an issue for AI-controlled characters (i.e. enemies),
+    // though, since they usually fill their queue as full as possible; and AI-
+    // controlled characters are the ones which the cooldown mechanic was
+    // designed for in the first place.
+    this.cooldownTimers = {}
   }
 
   update(dt) {
@@ -680,6 +731,14 @@ class BattleCharacter extends Sprite {
     this.actionExecuteTime = 0.4 + 0.2 * action.size
     this.actionBeingExecuted = action
 
+    for (const [ actionID, remainingSegments ] of Object.entries(this.cooldownTimers)) {
+      if (remainingSegments > action.size) {
+        this.cooldownTimers[actionID] -= action.size
+      } else {
+        delete this.cooldownTimers[actionID] // Or set to 0, either works
+      }
+    }
+
     if (this.targetCharacter) {
       this.battle.animationEntities.push(new MagicProjectile(this, this.targetCharacter, action))
     }
@@ -1195,6 +1254,8 @@ const battle = new Battle({
   ]
 })
 
+battle.teams[0].characters[0].atbBar.segmentCount = 4
+battle.teams[0].characters[1].atbBar.segmentCount = 4
 battle.teams[1].characters[0].image.src = 'img/warmech.png'
 
 const camera = battle.camera