_mapRewardsModels(rewardsModels) {
    // reset rewards data
    this._rewardsData = [];
    if (rewardsModels != null && rewardsModels.length > 0) {
      // Pull apart the season rewards
      // save the currency rewards to insert into the middle
      for (var i = 0; i < rewardsModels.length; i++) {
        const rewardModel = rewardsModels[i];

        if (rewardModel.cosmetic_id || rewardModel.get && rewardModel.get('cosmetic_id')) {
          // cosmetic
          const cosmeticId = rewardModel.cosmetic_id || rewardModel.get && rewardModel.get('cosmetic_id');
          const cosmeticData = SDK.CosmeticsFactory.cosmeticForIdentifier(cosmeticId);
          if (cosmeticData != null) {
            const cosmeticTypeId = cosmeticData.typeId;
            var cosmeticRewardData;
            if (cosmeticTypeId === SDK.CosmeticsTypeLookup.Emote) {
              cosmeticRewardData = { emoteId: cosmeticData.id };
            } else if (cosmeticTypeId === SDK.CosmeticsTypeLookup.ProfileIcon) {
              cosmeticRewardData = { profileIconId: cosmeticData.id };
            } else if (cosmeticTypeId === SDK.CosmeticsTypeLookup.CardSkin) {
              cosmeticRewardData = { cardSkinId: cosmeticData.id };
            } else if (cosmeticTypeId === SDK.CosmeticsTypeLookup.CardBack) {
              cosmeticRewardData = { cardBackId: cosmeticData.id };
            } else if (cosmeticTypeId === SDK.CosmeticsTypeLookup.BattleMap) {
              cosmeticRewardData = { battleMapId: cosmeticData.id };
            } else if (cosmeticTypeId === SDK.CosmeticsTypeLookup.Scene) {
              cosmeticRewardData = { sceneId: cosmeticData.id };
            }

            if (cosmeticRewardData != null) {
              // check for cosmetic converted to spirit
              if (rewardModel.spirit || rewardModel.get && rewardModel.get('spirit')) {
                var spirit = rewardModel.spirit || rewardModel.get && rewardModel.get('spirit');
                cosmeticRewardData.convertedToSpirit = spirit;
              }

              this._rewardsData.push(cosmeticRewardData);
            }
          }
        } else if (rewardModel.gold || rewardModel.get && rewardModel.get('gold')) {
          // Gold
          const gold = rewardModel.gold || rewardModel.get && rewardModel.get('gold');
          this._rewardsData.push({ gold });
        } else if (rewardModel.spirit || rewardModel.get && rewardModel.get('spirit')) {
          // Spirit
          var spirit = rewardModel.spirit || rewardModel.get && rewardModel.get('spirit');
          this._rewardsData.push({ spirit });
        } else if (rewardModel.card_id || rewardModel.get && rewardModel.get('card_id')) {
          // Individual card id
          const card_id = rewardModel.card_id || rewardModel.get && rewardModel.get('card_id');
          this._rewardsData.push({ cardId: card_id });
        } else if (rewardModel.cards || rewardModel.get && rewardModel.get('cards')) {
          // Card ids array
          const cards = rewardModel.cards || rewardModel.get && rewardModel.get('cards');
          for (var j = 0; j < cards.length; j++) {
            const cardId = cards[j];
            this._rewardsData.push({ cardId });
          }
        } else if (rewardModel.spirit_orbs || rewardModel.get && rewardModel.get('spirit_orbs')) {
          // Spirit orbs
          const spirit_orbs = rewardModel.spirit_orbs || rewardModel.get && rewardModel.get('spirit_orbs');
          if (_.isArray(spirit_orbs)) {
            for (var j = 0; j < spirit_orbs.length; j++) {
              this._rewardsData.push({ spirit_orbs: spirit_orbs[j] });
            }
          } else {
            this._rewardsData.push({ spirit_orbs });
          }
        } else if (rewardModel.arena_tickets || rewardModel.get && rewardModel.get('arena_tickets')) {
          // Gauntlet tickets
          const arena_tickets = rewardModel.arena_tickets || rewardModel.get && rewardModel.get('arena_tickets');
          this._rewardsData.push({ gauntletTickets: arena_tickets });
        } else if (rewardModel.gauntlet_tickets || rewardModel.get && rewardModel.get('gauntlet_tickets')) {
          // Gauntlet tickets
          const gauntlet_tickets = rewardModel.gauntlet_tickets || rewardModel.get && rewardModel.get('gauntlet_tickets');
          this._rewardsData.push({ gauntletTickets: gauntlet_tickets });
        } else if (rewardModel.chest_key || rewardModel.get && rewardModel.get('chest_key')) {
          // chest keys
          const chest_key_type = rewardModel.chest_key || rewardModel.get && rewardModel.get('chest_key');
          this._rewardsData.push({ keyType: chest_key_type });
        } else if (rewardModel.cosmetic_keys || rewardModel.get && rewardModel.get('cosmetic_keys')) {
          // chest keys
          _.each(rewardModel.cosmetic_keys || rewardModel.get('cosmetic_keys'), (key) => {
            this._rewardsData.push({ keyType: key });
          });
        } else {
          // Shouldn't reach here
          console.error(`EndOfSeasonLayer:animateReward - Reward model with no recognizable reward type - ${_.keys(rewardModel)}`);
        }
      }
    }

    // randomize reward order
    this._rewardsData = _.shuffle(this._rewardsData);

    // Prepare reward positions
    const centerPosition = this.getCenterPosition();
    if (this._layoutRewardsInCircle) {
      if (this._rewardsData.length == 1) {
        this._rewardTargetPositions = [centerPosition];
      } else if (this._rewardsData.length == 2) {
        this._rewardTargetPositions = [
          cc.p(centerPosition.x - 200, centerPosition.y),
          cc.p(centerPosition.x + 200, centerPosition.y),
        ];
      } else if (this._rewardsData.length > 2) {
        // Radial ditance discribes how far from the center each reward node will be
        const horizontalRadialDistance = 325;
        const verticalRadialDistance = 200;
        const scaleRadiusX = 1.0;
        const scaleRadiusY = 1.0;

        // Position around a circle
        this._rewardTargetPositions = [];
        var numNodes = this._rewardsData.length;
        for (var i = 0; i < numNodes; i++) {
          let progressAroundCircle = i / numNodes;
          if (numNodes % 2 == 0) {
            // If there are an even number of nodes we rotate the circle by a half node to make sure there is no node at the bottom peak
            progressAroundCircle += 0.5 / numNodes;
          }
          const targetPosition = cc.p(
            centerPosition.x + horizontalRadialDistance * Math.cos(Math.PI / 2 + 2 * Math.PI * progressAroundCircle),
            centerPosition.y + verticalRadialDistance * Math.sin(Math.PI / 2 + 2 * Math.PI * progressAroundCircle),
          );
          targetPosition.x *= scaleRadiusX;
          targetPosition.y *= scaleRadiusY;
          if (i == 0 && numNodes % 2 == 1) {
            // If there are an odd number of nodes there will be a node at the highest peak, we want to dampen that nodes radius
            targetPosition.y *= 5 / 6;
          }
          this._rewardTargetPositions.push(targetPosition);
        }
      }
    } else {
      // Layout in rows
      const verticalSpacing = 300;
      const horizontalSpacing = 250;

      this._rewardTargetPositions = [];
      var numNodes = this._rewardsData.length;

      // If greater than 3 rewards, use two rows, else one row
      if (numNodes <= 3) {
        const startingX = horizontalSpacing * (numNodes - 1) * -0.5;
        for (var i = 0; i < numNodes; i++) {
          this._rewardTargetPositions[i] = cc.p(
            centerPosition.x + startingX + i * horizontalSpacing,
            centerPosition.y,
          );
        }
      } else {
        const nodesInFirstRow = Math.round(numNodes / 2);
        const nodesInSecondRow = Math.floor(numNodes / 2);
        const firstRowStartingX = horizontalSpacing * (nodesInFirstRow - 1) * -0.5;
        const secondRowStartingX = horizontalSpacing * (nodesInSecondRow - 1) * -0.5;
        for (var i = 0; i < numNodes; i++) {
          if (i < nodesInFirstRow) {
            this._rewardTargetPositions[i] = cc.p(
              centerPosition.x + firstRowStartingX + i * horizontalSpacing,
              centerPosition.y + verticalSpacing * 0.5,
            );
          } else {
            this._rewardTargetPositions[i] = cc.p(
              centerPosition.x + secondRowStartingX + (i - nodesInFirstRow) * horizontalSpacing,
              centerPosition.y + verticalSpacing * -0.5,
            );
          }
        }
      }
    }

    // region Rewards Nodes
    this._rewardNodes = [];
    this._rewardAnimationPromiseCallbacks = [];
    let currencyRewardsBuilt = 0;
    for (var i = 0, il = this._rewardsData.length; i < il; i++) {
      const rewardData = this._rewardsData[i];
      const rewardTargetPosition = this._rewardTargetPositions[i];
      const sfxIndex = Math.min(i - currencyRewardsBuilt, 4);

      // reward node
      const rewardNode = this._createRewardNode(rewardData);
      rewardNode.setVisible(false);
      rewardNode.setPosition(rewardTargetPosition);
      this.addChild(rewardNode, this._zOrderRewards);
      this._rewardNodes.push(rewardNode);

      // reward animation callback
      let rewardAnimationCallback = this._createRewardAnimationCallback(rewardData, rewardNode, sfxIndex);

      // converted to reward
      const convertedToAnimationCallback = this._createConvertedToRewardAnimationCallback(rewardData, rewardNode, rewardAnimationCallback);
      if (convertedToAnimationCallback != null) {
        rewardAnimationCallback = convertedToAnimationCallback;
      }

      this._rewardAnimationPromiseCallbacks.push(rewardAnimationCallback);

      if (rewardData.gold != null || rewardData.spirit != null) {
        currencyRewardsBuilt++;
      }
    }
  }
(Helper) Processes the reward models into the desired nodes and calculates animation values
@public
@param {Array of backbone models or data objects} rewardsModels - represents the rewards to be displayed