showStatChanges(atkValue, hpValue, hpChangeType) {
    let showDuration = 0.0;

    if (atkValue != null || hpValue != null) {
      // we're queuing stat change to be shown
      // but we know how long it will show for
      showDuration += CONFIG.ENTITY_STATS_CHANGE_DELAY * 0.5;

      // attempt to squash changes into last stat change
      let squashed = false;
      if (this._statChangeQueue.length > 0) {
        const lastStatChangeData = this._statChangeQueue[this._statChangeQueue.length - 1];
        if (atkValue != null && hpValue == null) {
          // squash attack change if signs match
          var lastAtk = lastStatChangeData.atk;
          var lastAtkSign = lastAtk && lastAtk.match(/([+-])/);
          var atkSign = atkValue.match(/([+-])/);
          if (lastAtkSign == null && atkSign == null) {
            squashed = true;
            lastStatChangeData.atk = `${(parseInt(lastAtk) || 0) + parseInt(atkValue)}`;
          } else if (lastAtkSign != null && atkSign != null && lastAtkSign[1] === atkSign[1]) {
            squashed = true;
            lastStatChangeData.atk = lastAtkSign[1] + ((parseInt(lastAtk && lastAtk.slice(1)) || 0) + parseInt(atkValue.slice(1)));
          }
        } else if (lastStatChangeData.hpChangeType == null || lastStatChangeData.hpChangeType == hpChangeType) {
          // squash change if signs match
          const lastHP = lastStatChangeData.hp;
          const lastHPSign = lastHP && lastHP.match(/([+-])/);
          const hpSign = hpValue.match(/([+-])/);
          const signsMatchNull = lastHPSign == null && hpSign == null;
          const signsMatchNotNull = lastHPSign != null && hpSign != null && lastHPSign[1] === hpSign[1];
          let signsMatch = signsMatchNull || signsMatchNotNull;
          if (signsMatch) {
            // squash atk
            if (atkValue != null) {
              var lastAtk = lastStatChangeData.atk;
              var lastAtkSign = lastAtk && lastAtk.match(/([+-])/);
              var atkSign = atkValue.match(/([+-])/);
              if (lastAtkSign == null && atkSign == null) {
                signsMatch = true;
                lastStatChangeData.atk = `${(parseInt(lastAtk) || 0) + parseInt(atkValue)}`;
              } else if (lastAtkSign != null && atkSign != null && lastAtkSign[1] === atkSign[1]) {
                signsMatch = true;
                lastStatChangeData.atk = lastAtkSign[1] + ((parseInt(lastAtk && lastAtk.slice(1)) || 0) + parseInt(atkValue.slice(1)));
              } else {
                signsMatch = false;
              }
            }

            // squash hp
            if (signsMatch) {
              squashed = true;
              if (signsMatchNull) {
                lastStatChangeData.hp = `${(parseInt(lastHP) || 0) + parseInt(hpValue)}`;
              } else if (signsMatchNotNull) {
                lastStatChangeData.hp = lastHPSign[1] + ((parseInt(lastHP && lastHP.slice(1)) || 0) + parseInt(hpValue.slice(1)));
              }
            }
          }
        }
      }

      if (!squashed) {
        // push to queue
        this._statChangeQueue.push({ atk: atkValue, hp: hpValue, hpChangeType });
        if (!this._statChangeShowing && this._statChangeQueue.length === 1) {
          // delay slightly to allow multiple rapid stat changes to be squashed
          this._statChangeShowing = true;
          this.runAction(cc.sequence(
            cc.delayTime(showDuration * 0.25),
            cc.callFunc(this._showNextStatChange, this),
          ));
        }
      }
    }

    return showDuration;
  }
Shows stat changes for atk and/or hp. Either parameter is optional, but at least one must be defined.
@param {Number} [atkValue=null]
@param {Number} [hpValue=null]
@param {String} [hpChangeType=StatsChangeNode.HP_CHANGE_TYPE_MODIFIER]
@returns {Number} show duration