_showAction(sdkActionInterface) {
    if (sdkActionInterface) {
      this._stopShowActionSequences();

      const action = sdkActionInterface.getSdkAction();
      const resolveParentAction = action.getResolveParentAction();
      if (!this._hasShownPlayCardActionSetup && resolveParentAction instanceof SDK.ApplyCardToBoardAction && !resolveParentAction.getIsImplicit() && action.getIsImplicit()) {
        // when the first sub-action of an explicit apply card to board action attempts to show
        // first show explicit apply card to board setup then shown this action
        this._hasShownPlayCardActionSetup = true;

        // show instructional arrow as needed
        const instructionalArrowDelay = this._showApplyCardInstructionalArrow(resolveParentAction);
        this._setupActionForShowSequence = this.runAction(cc.sequence(
          cc.delayTime(instructionalArrowDelay),
          cc.callFunc(() => {
            this._setupActionForShowSequence = null;
            // show automatic fx
            const autoFXDelay = this._showActionAutoFX(resolveParentAction);
            const applyCardTargetFXDelay = this._showApplyCardTargetFX(resolveParentAction);
            const maxFXDelay = Math.max(autoFXDelay, applyCardTargetFXDelay);
            const maxFXDelayCorrection = Math.min(maxFXDelay * 0.25, 1.0);
            const delayBeforeShowingActions = maxFXDelay - maxFXDelayCorrection;
            this._setupActionForShowSequence = this.runAction(cc.sequence(
              cc.delayTime(delayBeforeShowingActions),
              cc.callFunc(() => {
                this._setupActionForShowSequence = null;
                // show action
                this._showAction(sdkActionInterface);
              }),
            ));
          }),
        ));
      } else if (action instanceof SDK.RevealHiddenCardAction) {
        if (action.getIsValidReveal()) {
          // reset the card if it is showing in the battle log
          const revealedCard = action.getCard();
          const battleLogNodes = this._battleLog.getBattleLogNodesInUse();
          for (let i = 0, il = battleLogNodes.length; i < il; i++) {
            const battleLogNode = battleLogNodes[i];
            const battleLogSdkCard = battleLogNode.getSdkCard();
            if (battleLogSdkCard != null
              && battleLogSdkCard.getIndex() === revealedCard.getIndex()
              && battleLogSdkCard.getId() !== revealedCard.getId()) {
              battleLogNode.setSdkCard(revealedCard);
              break;
            }
          }
        }

        // reveal hidden card by showing it as a played card
        this.showPlayCard(action, CONFIG.REVEAL_HIDDEN_CARD_TRANSITION_DURATION, CONFIG.REVEAL_HIDDEN_CARD_SHOW_DURATION, false);

        // setup sequence to show next action after reveal hidden card delay
        // this allows a user to skip the reveal if they wish
        const onShown = function () {
          this._showActionCardSequence = null;
          this.showNextAction();
        }.bind(this);
        this._showActionCardSequenceCompletedCallback = onShown;
        this._showActionCardSequence = this.runAction(cc.sequence(
          cc.delayTime(CONFIG.REVEAL_HIDDEN_CARD_DELAY),
          cc.callFunc(onShown),
        ));
      } else {
        const myAction = action.getOwnerId() === this._player.getPlayerId();
        let actionDelay = 0.0;
        let nextActionDelay = 0.0;
        let showDelay = 0.0;
        let impactDelay = 0.0;
        const sourceBoardPosition = action.getSourcePosition();
        const targetBoardPosition = action.getTargetPosition();
        const sequenceRootSdkActionInterface = this._currentSequenceRootSdkActionInterface;
        const isSequencedAsOne = sdkActionInterface === sequenceRootSdkActionInterface ? sdkActionInterface.isSequencedAsOne : sequenceRootSdkActionInterface.isSequenceSequencedAsOne;
        let isSequenced = !isSequencedAsOne;
        this._lastActionTime = this._actionTime || Date.now();
        this._actionTime = Date.now();
        // Logger.module("ENGINE").log("GameLayer._showAction -> delta time", (this._actionTime - this._lastActionTime) / 1000.0, " is ", action.getLogName(), "root?", sdkActionInterface.isSequenceRoot);
        // show action for game
        nextActionDelay = Math.max(nextActionDelay, this._showActionForGame(action));

        /*
         // show modifier emphasis
         if (action instanceof ModifierAction || action instanceof SDK.ApplyModifierAction || action instanceof SDK.RemoveModifierAction) {
         nextActionDelay = Math.max(nextActionDelay, this._showModifierEmphasis(action));
         }
         */

        // show action for modifier
        nextActionDelay = Math.max(nextActionDelay, this._showActionForModifier(action));

        // wait to trigger before show action until just after action starts to show
        // this way, if the action created new nodes, they can be included in the event
        const _currentActionSourceSdkCard = this._currentActionSourceSdkCard = action.getSource();
        const _currentActionSourceNode = this._currentActionSourceNode = this.getNodeForSdkCard(_currentActionSourceSdkCard);
        const _currentActionTargetSdkCard = this._currentActionTargetSdkCard = action.getTarget();
        const _currentActionTargetNode = this._currentActionTargetNode = this.getNodeForSdkCard(_currentActionTargetSdkCard);
        this.getEventBus().trigger(EVENTS.before_show_action, {
          type: EVENTS.before_show_action,
          action,
          sourceSdkCard: _currentActionSourceSdkCard,
          sourceNode: _currentActionSourceNode,
          targetSdkCard: _currentActionTargetSdkCard,
          targetNode: _currentActionTargetNode,
        });

        // get game fx board positions
        let gameFXSourceBoardPosition;
        let gameFXTargetBoardPosition;
        if (action instanceof SDK.ApplyCardToBoardAction && action.getIsValidApplication()) {
          const card = action.getCard();
          if (card instanceof SDK.Spell) {
            gameFXSourceBoardPosition = gameFXTargetBoardPosition = card.getCenterPositionOfAppliedEffects();
          } else if (card instanceof SDK.Artifact) {
            const general = SDK.GameSession.getInstance().getGeneralForPlayerId(card.getOwnerId());
            if (general != null) {
              gameFXSourceBoardPosition = gameFXTargetBoardPosition = general.getPosition();
            }
          }
        }

        if (gameFXSourceBoardPosition == null) {
          gameFXSourceBoardPosition = sourceBoardPosition;
        }
        if (gameFXTargetBoardPosition == null) {
          gameFXTargetBoardPosition = targetBoardPosition;
        }

        // show special apply card fx
        actionDelay = Math.max(actionDelay, this._showActionForApplyCard(action, gameFXTargetBoardPosition));

        // source reaction and fx when there is a source entity
        if (_currentActionSourceNode) {
          const sourceFX = this._getActionFXData(action, SDK.FXType.SourceFX);
          const sourceFXSprites = NodeFactory.createFX(sourceFX);
          const sourceFXDelays = UtilsEngine.getDelaysFromFXSprites(sourceFXSprites);
          showDelay = Math.max(showDelay, sourceFXDelays.showDelay);
          this._showCurrentActionForSourceSequence = this.runAction(cc.sequence(
            cc.delayTime(actionDelay),
            cc.callFunc(() => {
              this._showCurrentActionForSourceSequence = null;
              this._showActionForSource(action, _currentActionSourceSdkCard, _currentActionSourceNode, sourceFXSprites);
            }),
          ));

          // add attack delay from source
          if ((action instanceof SDK.AttackAction || action instanceof SDK.DamageAsAttackAction)
            && _currentActionSourceNode != null && _currentActionSourceNode.getAnimResource() != null) {
            const attackDelay = (_currentActionSourceNode.getAnimResource().attackDelay || 0.0) * CONFIG.ENTITY_ATTACK_DURATION_MODIFIER;
            actionDelay += attackDelay;
            nextActionDelay = Math.max(nextActionDelay, attackDelay);
          }
        }

        // game fx
        // shown at the center of the application pattern when possible
        const gameFX = this._getActionFXData(action, SDK.FXType.GameFX);
        const gameFXEvent = {
          sourceBoardPosition: gameFXSourceBoardPosition,
          targetBoardPosition: gameFXTargetBoardPosition,
          offset: { x: 0.0, y: CONFIG.TILESIZE * 0.5 },
          forAutoFX: true,
        };
        const gameFXSprites = NodeFactory.createFX(gameFX, gameFXEvent);
        const gameFXDelays = UtilsEngine.getDelaysFromFXSprites(gameFXSprites);
        showDelay = Math.max(showDelay, gameFXDelays.showDelay);
        impactDelay = Math.max(impactDelay, gameFXDelays.impactDelay);
        this._showCurrentActionForGameSequence = this.runAction(cc.sequence(
          cc.delayTime(actionDelay),
          cc.callFunc(function () {
            this._showCurrentActionForGameSequence = null;
            this.addNodes(gameFXSprites, gameFXEvent);
          }, this),
        ));

        // target reaction and fx when there is a target entity
        const targetReactionDelay = actionDelay + impactDelay;
        this._currentSequenceMaxTargetReactionDelay = Math.max(this._currentSequenceMaxTargetReactionDelay, targetReactionDelay);
        let targetDelays = 0.0;
        if (_currentActionTargetNode) {
          const targetFX = this._getActionFXData(action, SDK.FXType.TargetFX);
          const targetFXSprites = NodeFactory.createFX(targetFX);
          const targetFXDelays = UtilsEngine.getDelaysFromFXSprites(targetFXSprites);
          targetDelays = targetFXDelays.showDelay;
          this._showCurrentActionForTargetSequence = this.runAction(cc.sequence(
            cc.delayTime(isSequencedAsOne ? this._currentSequenceMaxTargetReactionDelay : targetReactionDelay),
            cc.callFunc(function () {
              this._showCurrentActionForTargetSequence = null;
              this._showActionForTarget(action, _currentActionTargetSdkCard, _currentActionTargetNode, targetFXSprites);
            }, this),
          ));
        }

        // show action for artifact
        nextActionDelay = Math.max(nextActionDelay, this.player1Layer.showActionForArtifact(action, this._currentSequenceMaxTargetReactionDelay));
        nextActionDelay = Math.max(nextActionDelay, this.player2Layer.showActionForArtifact(action, this._currentSequenceMaxTargetReactionDelay));

        // special cases show delay
        if (action instanceof SDK.DieAction && _currentActionTargetNode != null && _currentActionTargetNode.getAnimResource() != null) {
          // special case for die actions
          showDelay = this.getDelayFromAnim(_currentActionTargetNode.getAnimResource().death);
        } else if (this.getIsActionSpawn(action) || action instanceof SDK.RemoveAction) {
          // special case for apply entity actions
          showDelay = CONFIG.ACTION_DELAY;
        } else {
          // simple show delay
          showDelay = Math.max(showDelay, targetDelays);
        }

        // check sequencing
        if (!isSequenced) {
          const nextActionInterface = this.getNextActionInterfaceInActionSequence(true);
          // sequence the last action in the sequence and use the max delay
          if (this._nextActionIndex === this._currentActionSequence.length || (nextActionInterface && nextActionInterface.isSequenceRoot)) {
            isSequenced = true;
            showDelay = Math.max(showDelay, this._currentSequenceMaxDelay);
            this._currentSequenceMaxDelay = 0.0;
          }
        }

        // apply show delay
        nextActionDelay = Math.max(nextActionDelay, showDelay);
        // nextActionDelay += showDelay;
        // Logger.module("ENGINE").log( " > _showAction finish", action.getLogName(), "isSequenced", isSequenced, "nextActionDelay", nextActionDelay, "showDelay", showDelay, "targetDelays", targetDelays, "actionDelay", actionDelay)

        // record max target delays of each action
        this._currentSequenceMaxDelay = Math.max(nextActionDelay, this._currentSequenceMaxDelay);

        // set delays to skip
        if (!isSequenced) {
          nextActionDelay = 0;
        }

        // reset max delays after root when root is not sequenced as one
        if (sdkActionInterface === sequenceRootSdkActionInterface && !sdkActionInterface.isSequencedAsOne) {
          this._currentSequenceMaxDelay = this._currentSequenceMaxTargetReactionDelay = 0.0;
        }

        // special fx for non-implicit actions
        this._showActionSpecialFX(action, nextActionDelay, targetReactionDelay);

        // next action
        if (nextActionDelay > 0) {
          this._nextActionSequence = cc.sequence(
            cc.delayTime(nextActionDelay),
            cc.callFunc(() => {
              this._nextActionSequence = null;
              this.showNextAction();
            }),
          );
          this.runAction(this._nextActionSequence);
        } else {
          this.showNextAction();
        }
      }
    }
  }
Shows a visual representation of an action and when done shows the next action in the list of current actions.
@param {SdkActionInterface} sdkActionInterface
@private