nextActionForActiveGame() {
    let nextAction;

    if (this.getGameSession().isActive() && this.getGameSession().getCurrentPlayerId() === this.getMyPlayerId()) {
      // Logger.module("AI").debug("[G:" + this.getGameSession().gameId + "] finding next actions!");
      // attempt to find next actions
      this._findNextActions();

      // use next action in queue
      nextAction = this._nextActions.shift();
      if (nextAction != null
        && !(nextAction instanceof SDK.EndTurnAction
          || nextAction instanceof SDK.EndFollowupAction
          || nextAction instanceof SDK.ResignAction)) {
        // validate the current action and reset the queue if invalid
        // resetting queue is safest as we don't want to execute queued actions that expected a specific sequence
        // do not emit event when invalid as we don't want anything reacting to this validation
        // it is strictly for internal stability checking and anti-roping
        const emitEventWhenInvalid = false;
        this.getGameSession().validateAction(nextAction, emitEventWhenInvalid);
        if (!nextAction.getIsValid()) {
          // reset the queue
          this._nextActions = [];

          // check whether action is same as last invalid action
          if (this._lastInvalidAction != null
            && this._lastInvalidAction.getType() === nextAction.getType()
            && (this._lastInvalidAction.getSourceIndex() == null || this._lastInvalidAction.getSourceIndex() === nextAction.getSourceIndex())
            && (this._lastInvalidAction.getSourcePosition() == null || this._lastInvalidAction.getSourcePosition().x === nextAction.getSourcePosition().x && this._lastInvalidAction.getSourcePosition().y === nextAction.getSourcePosition().y)
            && (this._lastInvalidAction.getTargetIndex() == null || this._lastInvalidAction.getTargetIndex() === nextAction.getTargetIndex())
            && (this._lastInvalidAction.getTargetPosition() == null || this._lastInvalidAction.getTargetPosition().x === nextAction.getTargetPosition().x && this._lastInvalidAction.getTargetPosition().y === nextAction.getTargetPosition().y)
          ) {
            // end turn immediately
            nextAction = this.getGameSession().actionEndTurn();
          } else {
            // remember action to prevent roping
            this._lastInvalidAction = nextAction;
            if (nextAction instanceof SDK.ApplyCardToBoardAction) {
              const card = nextAction.getCard();
              if (card != null) {
                UtilsJavascript.arrayCautiousAdd(this._invalidPlayedCards, card);
              }
            } else if (nextAction instanceof SDK.AttackAction) {
              this._invalidAttackActions.push(nextAction);
            } else if (nextAction instanceof SDK.MoveAction) {
              this._invalidMoveActions.push(nextAction);
            }

            // recursively find next action
            nextAction = this.nextActionForActiveGame();
          }
        }
      }

      // reset when turn is complete
      if (nextAction instanceof SDK.EndTurnAction) {
        this._resetForNextTurn();
      }
    }

    return nextAction;
  }
Returns next action for active game, or null if no action.
@returns {Action|null}
@see nextAction