Example #1
0
public class PlayerPanel extends FContainer {
  private static final ForgePreferences prefs = FModel.getPreferences();
  private static final float PADDING = Utils.scale(5);
  private static final FSkinFont LABEL_FONT = FSkinFont.get(14);

  private final LobbyScreen screen;
  private final int index;
  private final boolean allowNetworking;
  private boolean mayEdit = true;
  private boolean isReady, mayControl, mayRemove, useAiSimulation;
  private LobbySlotType type = LobbySlotType.LOCAL;

  private final FLabel nameRandomiser;
  private final FLabel avatarLabel =
      new FLabel.Builder()
          .opaque(true)
          .iconScaleFactor(0.99f)
          .alphaComposite(1)
          .iconInBackground(true)
          .build();
  private int avatarIndex;

  private final FTextField txtPlayerName = new FTextField("Player name");
  private final FToggleSwitch humanAiSwitch;

  private FComboBox<Object> cbTeam = new FComboBox<Object>();
  private FComboBox<Object> cbArchenemyTeam = new FComboBox<Object>();

  private final FLabel btnDeck = new FLabel.ButtonBuilder().text("Loading Deck...").build();
  private final FLabel btnSchemeDeck =
      new FLabel.ButtonBuilder().text("Scheme Deck: Random Generated Deck").build();
  private final FLabel btnCommanderDeck =
      new FLabel.ButtonBuilder().text("Commander Deck: Random Generated Deck").build();
  private final FLabel btnTinyLeadersDeck =
      new FLabel.ButtonBuilder().text("Tiny Leaders Deck: Random Generated Deck").build();
  private final FLabel btnPlanarDeck =
      new FLabel.ButtonBuilder().text("Planar Deck: Random Generated Deck").build();
  private final FLabel btnVanguardAvatar =
      new FLabel.ButtonBuilder().text("Vanguard Avatar: Random").build();

  private final FDeckChooser deckChooser,
      lstSchemeDecks,
      lstCommanderDecks,
      lstTinyLeadersDecks,
      lstPlanarDecks;
  private final FVanguardChooser lstVanguardAvatars;

  public PlayerPanel(
      final LobbyScreen screen0,
      final boolean allowNetworking0,
      final int index0,
      final LobbySlot slot,
      final boolean mayEdit0,
      final boolean mayControl0) {
    super();
    screen = screen0;
    allowNetworking = allowNetworking0;
    if (allowNetworking) {
      humanAiSwitch = new FToggleSwitch("Not Ready", "Ready");
    } else {
      humanAiSwitch = new FToggleSwitch("Human", "AI");
    }
    index = index0;
    populateTeamsComboBoxes();
    setTeam(slot.getTeam());
    setIsArchenemy(slot.isArchenemy());
    setType(slot.getType());
    setPlayerName(slot.getName());
    setAvatarIndex(slot.getAvatarIndex());

    btnDeck.setEnabled(false); // disable deck button until done loading decks

    boolean isAi = isAi();
    deckChooser =
        new FDeckChooser(
            GameType.Constructed,
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnDeck.setEnabled(true);
                btnDeck.setText(
                    deckChooser.getSelectedDeckType().toString()
                        + ": "
                        + Lang.joinHomogenous(
                            ((DeckManager) e.getSource()).getSelectedItems(),
                            DeckProxy.FN_GET_NAME));
              }
            });
    lstCommanderDecks =
        new FDeckChooser(
            GameType.Commander,
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnCommanderDeck.setText(
                    "Commander Deck: " + ((DeckManager) e.getSource()).getSelectedItem().getName());
              }
            });
    lstTinyLeadersDecks =
        new FDeckChooser(
            GameType.TinyLeaders,
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnTinyLeadersDeck.setText(
                    "Tiny Leaders Deck: "
                        + ((DeckManager) e.getSource()).getSelectedItem().getName());
              }
            });
    lstSchemeDecks =
        new FDeckChooser(
            GameType.Archenemy,
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnSchemeDeck.setText(
                    "Scheme Deck: " + ((DeckManager) e.getSource()).getSelectedItem().getName());
              }
            });
    lstPlanarDecks =
        new FDeckChooser(
            GameType.Planechase,
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnPlanarDeck.setText(
                    "Planar Deck: " + ((DeckManager) e.getSource()).getSelectedItem().getName());
              }
            });
    lstVanguardAvatars =
        new FVanguardChooser(
            isAi,
            new FEventHandler() {
              @Override
              public void handleEvent(FEvent e) {
                btnVanguardAvatar.setText(
                    "Vanguard: " + ((CardManager) e.getSource()).getSelectedItem().getName());
              }
            });

    createAvatar();
    add(avatarLabel);

    createNameEditor();
    add(newLabel("Name:"));
    add(txtPlayerName);

    nameRandomiser = createNameRandomizer();
    add(nameRandomiser);

    humanAiSwitch.setChangedHandler(humanAiSwitched);
    add(humanAiSwitch);

    add(newLabel("Team:"));
    cbTeam.setChangedHandler(teamChangedHandler);
    cbArchenemyTeam.setChangedHandler(teamChangedHandler);
    add(cbTeam);
    add(cbArchenemyTeam);

    add(btnDeck);
    btnDeck.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            deckChooser.setHeaderCaption("Select Deck for " + txtPlayerName.getText());
            Forge.openScreen(deckChooser);
          }
        });
    add(btnCommanderDeck);
    btnCommanderDeck.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            lstCommanderDecks.setHeaderCaption(
                "Select Commander Deck for " + txtPlayerName.getText());
            Forge.openScreen(lstCommanderDecks);
          }
        });
    add(btnTinyLeadersDeck);
    btnTinyLeadersDeck.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            lstTinyLeadersDecks.setHeaderCaption(
                "Select Tiny Leaders Deck for " + txtPlayerName.getText());
            Forge.openScreen(lstTinyLeadersDecks);
          }
        });
    add(btnSchemeDeck);
    btnSchemeDeck.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            lstSchemeDecks.setHeaderCaption("Select Scheme Deck for " + txtPlayerName.getText());
            Forge.openScreen(lstSchemeDecks);
          }
        });
    add(btnPlanarDeck);
    btnPlanarDeck.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            lstPlanarDecks.setHeaderCaption("Select Planar Deck for " + txtPlayerName.getText());
            Forge.openScreen(lstPlanarDecks);
          }
        });
    add(btnVanguardAvatar);
    btnVanguardAvatar.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            lstVanguardAvatars.setHeaderCaption("Select Vanguard for " + txtPlayerName.getText());
            Forge.openScreen(lstVanguardAvatars);
          }
        });

    if (mayEdit == mayEdit0) {
      updateVariantControlsVisibility();
    } else {
      setMayEdit(false);
    }
    setMayControl(mayControl0);

    // disable team combo boxes for now
    cbTeam.setEnabled(false);
  }

  public void initialize(FPref savedStateSetting, DeckType defaultDeckType) {
    deckChooser.initialize(savedStateSetting, defaultDeckType);
    lstCommanderDecks.initialize(null, DeckType.RANDOM_DECK);
    lstTinyLeadersDecks.initialize(null, DeckType.RANDOM_DECK);
    lstPlanarDecks.initialize(null, DeckType.RANDOM_DECK);
    lstSchemeDecks.initialize(null, DeckType.RANDOM_DECK);
  }

  @Override
  protected void doLayout(float width, float height) {
    float x = PADDING;
    float y = PADDING;
    float fieldHeight = txtPlayerName.getHeight();
    float avatarSize = 2 * fieldHeight + PADDING;
    float dy = fieldHeight + PADDING;

    avatarLabel.setBounds(x, y, avatarSize, avatarSize);
    x += avatarSize + PADDING;
    float w = width - x - fieldHeight - 2 * PADDING;
    txtPlayerName.setBounds(x, y, w, fieldHeight);
    x += w + PADDING;
    nameRandomiser.setBounds(x, y, fieldHeight, fieldHeight);

    y += dy;
    humanAiSwitch.setSize(humanAiSwitch.getAutoSizeWidth(fieldHeight), fieldHeight);
    x = width - humanAiSwitch.getWidth() - PADDING;
    humanAiSwitch.setPosition(x, y);
    w = x - avatarSize - 3 * PADDING;
    x = avatarSize + 2 * PADDING;
    if (cbArchenemyTeam.isVisible()) {
      cbArchenemyTeam.setBounds(x, y, w, fieldHeight);
    } else {
      cbTeam.setBounds(x, y, w, fieldHeight);
    }

    y += dy;
    x = PADDING;
    w = width - 2 * PADDING;
    if (btnCommanderDeck.isVisible()) {
      btnCommanderDeck.setBounds(x, y, w, fieldHeight);
      y += dy;
    } else if (btnTinyLeadersDeck.isVisible()) {
      btnTinyLeadersDeck.setBounds(x, y, w, fieldHeight);
      y += dy;
    } else if (btnDeck.isVisible()) {
      btnDeck.setBounds(x, y, w, fieldHeight);
      y += dy;
    }
    if (btnSchemeDeck.isVisible()) {
      btnSchemeDeck.setBounds(x, y, w, fieldHeight);
      y += dy;
    }
    if (btnPlanarDeck.isVisible()) {
      btnPlanarDeck.setBounds(x, y, w, fieldHeight);
      y += dy;
    }
    if (btnVanguardAvatar.isVisible()) {
      btnVanguardAvatar.setBounds(x, y, w, fieldHeight);
    }
  }

  public float getPreferredHeight() {
    int rows = 3;
    if (!btnDeck.isVisible()) {
      rows--;
    }
    if (btnCommanderDeck.isVisible() || btnTinyLeadersDeck.isVisible()) {
      rows++;
    }
    if (btnSchemeDeck.isVisible()) {
      rows++;
    }
    if (btnPlanarDeck.isVisible()) {
      rows++;
    }
    if (btnVanguardAvatar.isVisible()) {
      rows++;
    }
    return rows * (txtPlayerName.getHeight() + PADDING) + PADDING;
  }

  @Override
  protected void drawOverlay(Graphics g) {
    float y = getHeight() - FList.LINE_THICKNESS / 2;
    g.drawLine(FList.LINE_THICKNESS, FList.LINE_COLOR, 0, y, getWidth(), y);
  }

  private final FEventHandler humanAiSwitched =
      new FEventHandler() {
        @Override
        public void handleEvent(FEvent e) {
          boolean toggled = humanAiSwitch.isToggled();
          if (allowNetworking) {
            setIsReady(toggled);
            screen.setReady(index, toggled);
          } else {
            type = toggled ? LobbySlotType.AI : LobbySlotType.LOCAL;
            onIsAiChanged(toggled);

            LobbySlot slot = screen.getLobby().getSlot(index);
            slot.setType(type);

            // update may edit in-case it changed as a result of the AI change
            setMayEdit(screen.getLobby().mayEdit(index));
            setAvatarIndex(slot.getAvatarIndex());
            setPlayerName(slot.getName());
          }
        }
      };

  private void onIsAiChanged(boolean isAi) {
    deckChooser.setIsAi(isAi);
    lstCommanderDecks.setIsAi(isAi);
    lstPlanarDecks.setIsAi(isAi);
    lstSchemeDecks.setIsAi(isAi);
    lstVanguardAvatars.setIsAi(isAi);
  }

  // Listens to name text fields and gives the appropriate player focus.
  // Also saves the name preference when leaving player one's text field. */
  private FEventHandler nameChangedHandler =
      new FEventHandler() {
        @Override
        public void handleEvent(FEvent e) {
          final Object source = e.getSource();
          if (source instanceof FTextField) { // the text box
            FTextField nField = (FTextField) source;
            String newName = nField.getText().trim();
            if (index == 0
                && !StringUtils.isBlank(newName)
                && StringUtils.isAlphanumericSpace(newName)
                && prefs.getPref(FPref.PLAYER_NAME) != newName) {
              prefs.setPref(FPref.PLAYER_NAME, newName);
              prefs.save();
              if (allowNetworking) {
                screen.firePlayerChangeListener(index);
              }
            }
          }
        }
      };

  private FEventHandler avatarCommand =
      new FEventHandler() {
        @Override
        public void handleEvent(FEvent e) {
          AvatarSelector.show(
              getPlayerName(),
              avatarIndex,
              screen.getUsedAvatars(),
              new Callback<Integer>() {
                @Override
                public void run(Integer result) {
                  setAvatarIndex(result);

                  if (index < 2) {
                    screen.updateAvatarPrefs();
                  }
                  if (allowNetworking) {
                    screen.firePlayerChangeListener(index);
                  }
                }
              });
        }
      };

  public void setDeckSelectorButtonText(String text) {
    btnDeck.setText(text);
  }

  public void updateVariantControlsVisibility() {
    boolean isCommanderApplied = false;
    boolean isTinyLeadersApplied = false;
    boolean isPlanechaseApplied = false;
    boolean isVanguardApplied = false;
    boolean isArchenemyApplied = false;
    boolean archenemyVisiblity = false;
    boolean isDeckBuildingAllowed = mayEdit;

    for (GameType variant : screen.getLobby().getAppliedVariants()) {
      switch (variant) {
        case Archenemy:
          isArchenemyApplied = true;
          if (isArchenemy()) {
            archenemyVisiblity = true;
          }
          break;
        case ArchenemyRumble:
          archenemyVisiblity = true;
          break;
        case Commander:
          isCommanderApplied = true;
          isDeckBuildingAllowed = false; // Commander deck replaces basic deck, so hide that
          break;
        case TinyLeaders:
          isTinyLeadersApplied = true;
          isDeckBuildingAllowed = false; // Tiny Leaders deck replaces basic deck, so hide that
          break;
        case Planechase:
          isPlanechaseApplied = true;
          break;
        case Vanguard:
          isVanguardApplied = true;
          break;
        default:
          if (variant.isAutoGenerated()) {
            isDeckBuildingAllowed = false;
          }
          break;
      }
    }

    btnDeck.setVisible(isDeckBuildingAllowed);
    btnCommanderDeck.setVisible(isCommanderApplied && mayEdit);
    btnTinyLeadersDeck.setVisible(isTinyLeadersApplied && mayEdit);

    btnSchemeDeck.setVisible(archenemyVisiblity && mayEdit);

    cbTeam.setVisible(!isArchenemyApplied);
    cbArchenemyTeam.setVisible(isArchenemyApplied);

    btnPlanarDeck.setVisible(isPlanechaseApplied && mayEdit);
    btnVanguardAvatar.setVisible(isVanguardApplied && mayEdit);
  }

  public boolean isAi() {
    return type == LobbySlotType.AI;
  }

  public boolean isArchenemy() {
    return cbArchenemyTeam.getSelectedIndex() == 0;
  }

  public void setIsArchenemy(boolean isArchenemy0) {
    cbArchenemyTeam.setSelectedIndex(isArchenemy0 ? 0 : 1);
  }

  private void populateTeamsComboBoxes() {
    cbArchenemyTeam.addItem("Archenemy");
    cbArchenemyTeam.addItem("Heroes");

    for (int i = 1; i <= LobbyScreen.MAX_PLAYERS; i++) {
      cbTeam.addItem("Team " + i);
    }
    cbTeam.setEnabled(true);
  }

  private FEventHandler teamChangedHandler =
      new FEventHandler() {
        @Override
        public void handleEvent(FEvent e) {
          @SuppressWarnings("unchecked")
          FComboBox<Object> cb = (FComboBox<Object>) e.getSource();
          if (cb.getSelectedIndex() == -1) {
            return;
          }
          if (screen.hasVariant(GameType.Archenemy)) {
            String sel = (String) cb.getSelectedItem();
            if (sel.contains("Archenemy")) {
              screen.lastArchenemy = index;
              for (PlayerPanel pp : screen.getPlayerPanels()) {
                int i = pp.index;
                int team = i == screen.lastArchenemy ? 0 : 1;
                pp.setArchenemyTeam(team);
                pp.toggleIsPlayerArchenemy();
              }
            }
          }
        }
      };

  public void toggleIsPlayerArchenemy() {
    if (screen.hasVariant(GameType.Archenemy)) {
      setIsArchenemy(screen.lastArchenemy == index);
    } else {
      setIsArchenemy(screen.hasVariant(GameType.ArchenemyRumble));
    }
    screen.updateLayoutForVariants();
  }

  private FLabel createNameRandomizer() {
    final FLabel newNameBtn =
        new FLabel.Builder().iconInBackground(false).icon(FSkinImage.EDIT).opaque(false).build();
    newNameBtn.setCommand(
        new FEventHandler() {
          @Override
          public void handleEvent(FEvent e) {
            getNewName(
                new Callback<String>() {
                  @Override
                  public void run(String newName) {
                    if (newName == null) {
                      return;
                    }

                    txtPlayerName.setText(newName);

                    if (index == 0) {
                      prefs.setPref(FPref.PLAYER_NAME, newName);
                      prefs.save();
                    }
                    if (allowNetworking) {
                      screen.firePlayerChangeListener(index);
                    }
                  }
                });
          }
        });
    return newNameBtn;
  }

  private void createNameEditor() {
    String name;
    if (index == 0) {
      name = FModel.getPreferences().getPref(FPref.PLAYER_NAME);
      if (name.isEmpty()) {
        name = "Human";
      }
    } else {
      name = NameGenerator.getRandomName("Any", "Any", screen.getPlayerNames());
    }

    txtPlayerName.setText(name);
    txtPlayerName.setFont(LABEL_FONT);
    txtPlayerName.setChangedHandler(nameChangedHandler);
  }

  private void createAvatar() {
    String[] currentPrefs = prefs.getPref(FPref.UI_AVATARS).split(",");
    if (index < currentPrefs.length) {
      setAvatarIndex(Integer.parseInt(currentPrefs[index]));
    } else {
      setAvatarIndex(AvatarSelector.getRandomAvatar(screen.getUsedAvatars()));
    }
    avatarLabel.setCommand(avatarCommand);
  }

  public void setAvatarIndex(int newAvatarIndex) {
    avatarIndex = newAvatarIndex;
    if (avatarIndex != -1) {
      avatarLabel.setIcon(new FTextureRegionImage(FSkin.getAvatars().get(newAvatarIndex)));
    } else {
      avatarLabel.setIcon(null);
    }
  }

  public int getAvatarIndex() {
    return avatarIndex;
  }

  public void setPlayerName(String string) {
    txtPlayerName.setText(string);
  }

  public String getPlayerName() {
    return txtPlayerName.getText();
  }

  public LobbySlotType getType() {
    return type;
  }

  public void setType(LobbySlotType type0) {
    if (type == type0) {
      return;
    }

    boolean wasAi = isAi();

    type = type0;

    switch (type) {
      case LOCAL:
        humanAiSwitch.setToggled(false);
        break;
      case AI:
        humanAiSwitch.setToggled(true);
        break;
      case OPEN:
        isReady = false;
        humanAiSwitch.setToggled(false);
        break;
      case REMOTE:
        humanAiSwitch.setToggled(isReady);
        break;
    }

    boolean isAi = isAi();
    if (isAi != wasAi && deckChooser != null) {
      onIsAiChanged(isAi);
    }
  }

  public Set<AIOption> getAiOptions() {
    return isSimulatedAi()
        ? ImmutableSet.of(AIOption.USE_SIMULATION)
        : Collections.<AIOption>emptySet();
  }

  private boolean isSimulatedAi() {
    return isAi() && useAiSimulation;
  }

  public void setUseAiSimulation(final boolean useAiSimulation0) {
    useAiSimulation = useAiSimulation0;
  }

  public int getTeam() {
    return cbTeam.getSelectedIndex();
  }

  public void setTeam(int team0) {
    cbTeam.setSelectedIndex(team0);
  }

  public int getArchenemyTeam() {
    return cbTeam.getSelectedIndex();
  }

  public void setArchenemyTeam(int team0) {
    cbTeam.setSelectedIndex(team0);
  }

  public boolean isReady() {
    return isReady;
  }

  public void setIsReady(boolean isReady0) {
    if (isReady == isReady0) {
      return;
    }
    isReady = isReady0;
    if (allowNetworking) {
      humanAiSwitch.setToggled(isReady);
    }
  }

  public void setMayEdit(boolean mayEdit0) {
    if (mayEdit == mayEdit0) {
      return;
    }
    mayEdit = mayEdit0;
    avatarLabel.setEnabled(mayEdit);
    txtPlayerName.setEnabled(mayEdit);
    nameRandomiser.setEnabled(mayEdit);
    humanAiSwitch.setEnabled(mayEdit);
    updateVariantControlsVisibility();

    // if panel has height already, ensure height updated to account for button visibility changes
    if (getHeight() > 0) {
      screen.getPlayersScroll().revalidate();
    }
  }

  public void setMayControl(boolean mayControl0) {
    if (mayControl == mayControl0) {
      return;
    }
    mayControl = mayControl0;
  }

  public void setMayRemove(boolean mayRemove0) {
    if (mayRemove == mayRemove0) {
      return;
    }
    mayRemove = mayRemove0;
  }

  public FDeckChooser getDeckChooser() {
    return deckChooser;
  }

  public Deck getDeck() {
    return deckChooser.getDeck();
  }

  public Deck getCommanderDeck() {
    return lstCommanderDecks.getDeck();
  }

  public Deck getTinyLeadersDeck() {
    return lstTinyLeadersDecks.getDeck();
  }

  public Deck getSchemeDeck() {
    return lstSchemeDecks.getDeck();
  }

  public Deck getPlanarDeck() {
    return lstPlanarDecks.getDeck();
  }

  public PaperCard getVanguardAvatar() {
    return lstVanguardAvatars.getLstVanguards().getSelectedItem();
  }

  /** Adds a pre-styled FLabel component with the specified title. */
  private FLabel newLabel(String title) {
    return new FLabel.Builder().text(title).font(LABEL_FONT).align(HAlignment.RIGHT).build();
  }

  private static final ImmutableList<String> genderOptions =
      ImmutableList.of("Male", "Female", "Any");
  private static final ImmutableList<String> typeOptions =
      ImmutableList.of("Fantasy", "Generic", "Any");

  private final void getNewName(final Callback<String> callback) {
    final String title = "Get new random name";
    final String message = "What type of name do you want to generate?";
    final FSkinImage icon = FOptionPane.QUESTION_ICON;

    FOptionPane.showOptionDialog(
        message,
        title,
        icon,
        genderOptions,
        2,
        new Callback<Integer>() {
          @Override
          public void run(final Integer genderIndex) {
            if (genderIndex == null || genderIndex < 0) {
              callback.run(null);
              return;
            }

            FOptionPane.showOptionDialog(
                message,
                title,
                icon,
                typeOptions,
                2,
                new Callback<Integer>() {
                  @Override
                  public void run(final Integer typeIndex) {
                    if (typeIndex == null || typeIndex < 0) {
                      callback.run(null);
                      return;
                    }

                    generateRandomName(
                        genderOptions.get(genderIndex),
                        typeOptions.get(typeIndex),
                        screen.getPlayerNames(),
                        title,
                        callback);
                  }
                });
          }
        });
  }

  private void generateRandomName(
      final String gender,
      final String type,
      final List<String> usedNames,
      final String title,
      final Callback<String> callback) {
    final String newName = NameGenerator.getRandomName(gender, type, usedNames);
    String confirmMsg = "Would you like to use the name \"" + newName + "\", or try again?";
    FOptionPane.showConfirmDialog(
        confirmMsg,
        title,
        "Use this name",
        "Try again",
        true,
        new Callback<Boolean>() {
          @Override
          public void run(Boolean result) {
            if (result) {
              callback.run(newName);
            } else {
              generateRandomName(gender, type, usedNames, title, callback);
            }
          }
        });
  }
}
Example #2
0
public class VStack extends FDropDown {
  public static final float CARD_WIDTH = Utils.AVG_FINGER_WIDTH;
  public static final float CARD_HEIGHT = Math.round(CARD_WIDTH * FCardPanel.ASPECT_RATIO);
  public static final float BORDER_THICKNESS = Utils.scale(2);
  public static final float PADDING = Utils.scale(3);
  public static final float MARGINS = Utils.scale(4);
  private static final FSkinFont FONT = FSkinFont.get(11);
  private static final float ALPHA_COMPOSITE = 0.5f;
  private static final TextRenderer textRenderer = new TextRenderer(true);

  private StackInstanceDisplay activeItem;
  private StackItemView activeStackInstance;
  private Map<PlayerView, Object> playersWithValidTargets;

  private int stackSize;

  public VStack() {}

  @Override
  protected boolean autoHide() {
    return false;
  }

  // temporarily reveal zones targeted by active stack instance
  private void revealTargetZones() {
    if (activeStackInstance == null) {
      return;
    }

    final Set<ZoneType> zones = new HashSet<ZoneType>();
    playersWithValidTargets = new HashMap<PlayerView, Object>();
    for (final CardView c : activeStackInstance.getTargetCards()) {
      if (c.getZone() != null) {
        zones.add(c.getZone());
        playersWithValidTargets.put(c.getController(), c);
      }
    }
    if (zones.isEmpty() || playersWithValidTargets.isEmpty()) {
      return;
    }
    MatchController.instance.openZones(zones, playersWithValidTargets);
  }

  // restore old zones when active stack instance changes
  private void restoreOldZones() {
    if (playersWithValidTargets == null) {
      return;
    }
    MatchController.instance.restoreOldZones(playersWithValidTargets);
    playersWithValidTargets = null;
  }

  @Override
  public void update() {
    activeItem = null;
    activeStackInstance = null; // reset before updating stack
    restoreOldZones();

    final FCollectionView<StackItemView> stack = MatchController.instance.getGameView().getStack();
    if (stackSize != stack.size()) {
      int oldStackSize = stackSize;
      stackSize = stack.size();
      getMenuTab().setText("Stack (" + stackSize + ")");

      if (stackSize > 0) {
        if (!isVisible()) {
          if (stackSize
              > oldStackSize) { // don't re-show stack if user hid it and then resolved an item on
                                // the stack
            show();
          }
          return; // don't call super.update() either way since show handles this
        }
      } else {
        hide();
        return; // super.update() isn't needed if hidden
      }
    }
    super.update();
  }

  @Override
  protected ScrollBounds updateAndGetPaneSize(float maxWidth, float maxVisibleHeight) {
    clear();

    float x = MARGINS;
    float y = MARGINS;
    float totalWidth;
    if (Forge.isLandscapeMode()) {
      totalWidth = Forge.getScreenWidth() * 0.35f;
    } else {
      totalWidth =
          maxWidth
              - MatchController.getView()
                  .getTopPlayerPanel()
                  .getTabs()
                  .iterator()
                  .next()
                  .getRight(); // keep avatar, life total, and hand tab visible to left of stack
    }
    float width = totalWidth - 2 * MARGINS;

    final FCollectionView<StackItemView> stack = MatchController.instance.getGameView().getStack();
    if (stack.isEmpty()) { // show label if stack empty
      FLabel label =
          add(new FLabel.Builder().text("[Empty]").font(FONT).align(HAlignment.CENTER).build());

      float height = Math.round(label.getAutoSizeBounds().height) + 2 * PADDING;
      label.setBounds(x, y, width, height);
      return new ScrollBounds(totalWidth, y + height + MARGINS);
    } else {
      // iterate stack in reverse so most recent items appear on bottom
      StackItemView stackInstance = null;
      StackInstanceDisplay display = null;
      float overlap = Math.round(CARD_HEIGHT / 2 + PADDING + BORDER_THICKNESS);
      for (int i = stack.size() - 1; i >= 0; i--) {
        stackInstance = stack.get(i);
        display = new StackInstanceDisplay(stackInstance, width);
        if (activeStackInstance == stackInstance) {
          activeItem = display;
        } else { // only add non-active items here
          add(display);
        }
        // use full preferred height of display for topmost item on stack, overlap amount for other
        // items
        display.setBounds(x, y, width, i > 0 ? overlap : display.preferredHeight);
        y += display.getHeight();
      }
      if (activeStackInstance == null) {
        activeStackInstance = stackInstance; // use topmost item on stack as default active item
        activeItem = display;
      } else {
        activeItem.setHeight(
            display.preferredHeight); // increase active item height to preferred height if needed
        if (activeItem.getBottom() > y) {
          y = activeItem.getBottom(); // ensure stack height increases if needed
        }
        add(activeItem);
      }
      scrollIntoView(activeItem); // scroll active display into view
      revealTargetZones();
    }
    return new ScrollBounds(totalWidth, y + MARGINS);
  }

  protected void updateSizeAndPosition() {
    if (!getRotate90()) {
      super.updateSizeAndPosition();
      return;
    }

    FMenuTab menuTab = getMenuTab();
    float screenHeight = Forge.getScreenHeight();
    float width = (screenHeight - 2 * VPrompt.HEIGHT - 2 * VAvatar.HEIGHT) * 4f / 6f;
    float maxVisibleHeight = menuTab.screenPos.x;
    paneSize =
        updateAndGetPaneSize(
            width
                + MatchController.getView()
                    .getTopPlayerPanel()
                    .getTabs()
                    .iterator()
                    .next()
                    .getRight(),
            maxVisibleHeight);

    // round width and height so borders appear properly
    paneSize = new ScrollBounds(Math.round(paneSize.getWidth()), Math.round(paneSize.getHeight()));

    setBounds(
        Math.round(menuTab.screenPos.x - width),
        Math.round((screenHeight + width) / 2),
        paneSize.getWidth(),
        Math.min(paneSize.getHeight(), maxVisibleHeight));
  }

  @Override
  protected void setScrollPositionsAfterLayout(float scrollLeft0, float scrollTop0) {
    super.setScrollPositionsAfterLayout(0, 0); // always scroll to top after layout
  }

  @Override
  protected void drawOnContainer(Graphics g) {
    // draw target arrows immediately above stack for active item only
    if (activeItem != null) {
      Vector2 arrowOrigin =
          new Vector2(
              activeItem.getLeft()
                  + VStack.CARD_WIDTH * FCardPanel.TARGET_ORIGIN_FACTOR_X
                  + VStack.PADDING
                  + VStack.BORDER_THICKNESS,
              activeItem.getTop()
                  + VStack.CARD_HEIGHT * FCardPanel.TARGET_ORIGIN_FACTOR_Y
                  + VStack.PADDING
                  + VStack.BORDER_THICKNESS);

      PlayerView activator = activeStackInstance.getActivatingPlayer();
      arrowOrigin = arrowOrigin.add(screenPos.x, screenPos.y);

      StackItemView instance = activeStackInstance;
      while (instance != null) {
        for (CardView c : instance.getTargetCards()) {
          TargetingOverlay.drawArrow(g, arrowOrigin, c, activator.isOpponentOf(c.getController()));
        }
        for (PlayerView p : instance.getTargetPlayers()) {
          TargetingOverlay.drawArrow(g, arrowOrigin, p, activator.isOpponentOf(p));
        }
        instance = instance.getSubInstance();
      }
    }
  }

  private class StackInstanceDisplay extends FDisplayObject {
    private final StackItemView stackInstance;
    private final Color foreColor, backColor;
    private String text;
    private float preferredHeight;

    private StackInstanceDisplay(StackItemView stackInstance0, float width) {
      stackInstance = stackInstance0;
      CardView card = stackInstance.getSourceCard();

      text = stackInstance.getText();
      if (stackInstance.isOptionalTrigger()
          && stackInstance0
              .getActivatingPlayer()
              .equals(MatchController.instance.getCurrentPlayer())) {
        text = "(OPTIONAL) " + text;
      }

      // TODO: A hacky workaround is currently used to make the game not leak the color information
      // for Morph cards.
      final CardStateView curState = card.getCurrentState();
      final boolean isFaceDown = curState.getState() == CardStateName.FaceDown;
      final DetailColors color =
          isFaceDown
              ? CardDetailUtil.DetailColors.FACE_DOWN
              : CardDetailUtil.getBorderColor(
                  card.getCurrentState(),
                  true); // otherwise doesn't work correctly for face down Morphs
      backColor = FSkinColor.fromRGB(color.r, color.g, color.b);
      foreColor = FSkinColor.getHighContrastColor(backColor);

      width -= CARD_WIDTH; // account for card picture
      width -=
          3 * PADDING
              + 2
                  * BORDER_THICKNESS; // account for left and right insets and gap between picture
                                      // and text
      float height = Math.max(CARD_HEIGHT, textRenderer.getWrappedBounds(text, FONT, width).height);
      height += 2 * (PADDING + BORDER_THICKNESS);
      preferredHeight = Math.round(height);
    }

    @Override
    public boolean tap(float x, float y, int count) {
      if (activeStackInstance
          != stackInstance) { // set as active stack instance if not already such
        activeStackInstance = stackInstance;
        restoreOldZones(); // restore old zones before changing active stack instance
        VStack.this.updateSizeAndPosition();
        return true;
      }
      final GameView gameView = MatchController.instance.getGameView();
      final IGuiGame gui = MatchController.instance;
      final IGameController controller = MatchController.instance.getGameController();
      final PlayerView player = MatchController.instance.getCurrentPlayer();
      if (player != null) { // don't show menu if tapping on art
        if (stackInstance.isAbility()) {
          FPopupMenu menu =
              new FPopupMenu() {
                @Override
                protected void buildMenu() {
                  final String key = stackInstance.getKey();
                  final boolean autoYield = gui.shouldAutoYield(key);
                  addItem(
                      new FCheckBoxMenuItem(
                          "Auto-Yield",
                          autoYield,
                          new FEventHandler() {
                            @Override
                            public void handleEvent(FEvent e) {
                              gui.setShouldAutoYield(key, !autoYield);
                              if (!autoYield && stackInstance.equals(gameView.peekStack())) {
                                // auto-pass priority if ability is on top of stack
                                controller.passPriority();
                              }
                            }
                          }));
                  if (stackInstance.isOptionalTrigger()
                      && stackInstance.getActivatingPlayer().equals(player)) {
                    final int triggerID = stackInstance.getSourceTrigger();
                    addItem(
                        new FCheckBoxMenuItem(
                            "Always Yes",
                            gui.shouldAlwaysAcceptTrigger(triggerID),
                            new FEventHandler() {
                              @Override
                              public void handleEvent(FEvent e) {
                                if (gui.shouldAlwaysAcceptTrigger(triggerID)) {
                                  gui.setShouldAlwaysAskTrigger(triggerID);
                                } else {
                                  gui.setShouldAlwaysAcceptTrigger(triggerID);
                                  if (stackInstance.equals(gameView.peekStack())) {
                                    // auto-yes if ability is on top of stack
                                    controller.selectButtonOk();
                                  }
                                }
                              }
                            }));
                    addItem(
                        new FCheckBoxMenuItem(
                            "Always No",
                            gui.shouldAlwaysDeclineTrigger(triggerID),
                            new FEventHandler() {
                              @Override
                              public void handleEvent(FEvent e) {
                                if (gui.shouldAlwaysDeclineTrigger(triggerID)) {
                                  gui.setShouldAlwaysAskTrigger(triggerID);
                                } else {
                                  gui.setShouldAlwaysDeclineTrigger(triggerID);
                                  if (stackInstance.equals(gameView.peekStack())) {
                                    // auto-no if ability is on top of stack
                                    controller.selectButtonOk();
                                  }
                                }
                              }
                            }));
                  }
                  addItem(
                      new FMenuItem(
                          "Zoom/Details",
                          new FEventHandler() {
                            @Override
                            public void handleEvent(FEvent e) {
                              CardZoom.show(stackInstance.getSourceCard());
                            }
                          }));
                };
              };

          menu.show(this, x, y);
          return true;
        }
      }
      CardZoom.show(stackInstance.getSourceCard());
      return true;
    }

    @Override
    public boolean longPress(float x, float y) {
      CardZoom.show(stackInstance.getSourceCard());
      return true;
    }

    @Override
    public void draw(Graphics g) {
      float x = 0;
      float y = 0;
      float w = getWidth();
      float h = preferredHeight;

      boolean needAlpha = (activeStackInstance != stackInstance);
      if (needAlpha) { // use alpha for non-active items on stack
        g.setAlphaComposite(ALPHA_COMPOSITE);
      }

      g.startClip(0, 0, w, getHeight()); // clip based on actual height

      g.fillRect(Color.BLACK, x, y, w, h); // draw rectangle for border

      x += BORDER_THICKNESS;
      y += BORDER_THICKNESS;
      w -= 2 * BORDER_THICKNESS;
      h -= 2 * BORDER_THICKNESS;
      g.fillRect(backColor, x, y, w, h);

      x += PADDING;
      y += PADDING;
      CardRenderer.drawCardWithOverlays(
          g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top);

      x += CARD_WIDTH + PADDING;
      w -= x + PADDING - BORDER_THICKNESS;
      h -= y + PADDING - BORDER_THICKNESS;
      textRenderer.drawText(
          g, text, FONT, foreColor, x, y, w, h, y, h, true, HAlignment.LEFT, true);

      g.endClip();

      if (needAlpha) {
        g.resetAlphaComposite();
      }
    }
  }
}
Example #3
0
 public void setFontSize(int fontSize0) {
   font = FSkinFont.get(fontSize0);
   revalidate();
 }
Example #4
0
 public float getPreferredHeight(float width) {
   return renderer.getWrappedBounds(text, font, width - 2 * insets.x).height
       + font.getLineHeight()
       - font.getCapHeight() // need to account for difference in line and cap height
       + 2 * insets.y;
 }
Example #5
0
 public FTextArea(String text0) {
   text = text0;
   font = FSkinFont.get(14);
   alignment = HAlignment.LEFT;
   insets = new Vector2(1, 1); // prevent text getting cut off by clip
 }