Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public void initialize() {
lstControls.add(Pair.of(view.getCbOpenPacksIndiv(), FPref.UI_OPEN_PACKS_INDIV));
lstControls.add(Pair.of(view.getCbTokensInSeparateRow(), FPref.UI_TOKENS_IN_SEPARATE_ROW));
lstControls.add(Pair.of(view.getCbStackCreatures(), FPref.UI_STACK_CREATURES));
lstControls.add(Pair.of(view.getCbAnimateFlying(), FPref.UI_ANIMATE_FLYING));
lstControls.add(Pair.of(view.getCbManaLostPrompt(), FPref.UI_MANA_LOST_PROMPT));
lstControls.add(Pair.of(view.getCbEscapeEndsTurn(), FPref.UI_ALLOW_ESC_TO_END_TURN));
lstControls.add(Pair.of(view.getCbDetailedPaymentDesc(), FPref.UI_DETAILED_SPELLDESC_IN_PROMPT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final JCheckBox cbOpenPacksIndiv = new OptionsCheckBox(localizer.getMessage("cbOpenPacksIndiv"));
private final JCheckBox cbTokensInSeparateRow = new OptionsCheckBox(localizer.getMessage("cbTokensInSeparateRow"));
private final JCheckBox cbStackCreatures = new OptionsCheckBox(localizer.getMessage("cbStackCreatures"));
private final JCheckBox cbAnimateFlying = new OptionsCheckBox(localizer.getMessage("cbAnimateFlying"));
private final JCheckBox cbFilterLandsByColorId = new OptionsCheckBox(localizer.getMessage("cbFilterLandsByColorId"));
private final JCheckBox cbShowStormCount = new OptionsCheckBox(localizer.getMessage("cbShowStormCount"));
private final JCheckBox cbRemindOnPriority = new OptionsCheckBox(localizer.getMessage("cbRemindOnPriority"));
Expand Down Expand Up @@ -423,6 +424,9 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbStackCreatures, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlStackCreatures")), descriptionConstraints);

pnlPrefs.add(cbAnimateFlying, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAnimateFlying")), descriptionConstraints);

pnlPrefs.add(cbTimedTargOverlay, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlTimedTargOverlay")), descriptionConstraints);

Expand Down Expand Up @@ -973,6 +977,10 @@ public final JCheckBox getCbStackCreatures() {
return cbStackCreatures;
}

public final JCheckBox getCbAnimateFlying() {
return cbAnimateFlying;
}

public final JCheckBox getCbManaLostPrompt() {
return cbManaLostPrompt;
}
Expand Down
33 changes: 32 additions & 1 deletion forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
private boolean displayEnabled = true;
private boolean isAnimationPanel;
private int cardXOffset, cardYOffset, cardWidth, cardHeight;
int baseY = 0;
float flyingPhase = 0f;
static final float FLYING_AMPLITUDE = 3f;
static final float FLYING_SPEED = 0.12f;
static final float FLYING_PHASE_MAX = (float)(Math.PI * 2);
private boolean isSelected;
private boolean hasFlash;
private CachedCardImage cachedImage;
Expand Down Expand Up @@ -969,7 +974,17 @@ public final void setCardBounds(final int x, final int y, int width, int height)
cardYOffset = -yOffset;
width = -xOffset + rotCenterX + rotCenterToTopCorner;
height = -yOffset + rotCenterY + rotCenterToBottomCorner;
setBounds(x + xOffset, y + yOffset, width, height);

// Store baseY when not floating or when position changes significantly
boolean shouldFloat = shouldFloat();
if (!shouldFloat || Math.abs(y - baseY) > 5) {
baseY = y;
}

// Calculate floating offset
int floatOffset = shouldFloat ? (int)Math.round(Math.sin(flyingPhase) * FLYING_AMPLITUDE) : 0;

setBounds(x + xOffset, y + yOffset + floatOffset, width, height);
}

@Override
Expand Down Expand Up @@ -1157,6 +1172,22 @@ private boolean showAbilityIcons() {
return isShowingOverlays() && isPreferenceEnabled(FPref.UI_OVERLAY_ABILITY_ICONS);
}

boolean shouldFloat() {
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ANIMATE_FLYING)) {
return false;
}
if (card == null || !card.getCurrentState().hasFlying()) {
return false;
}
if (isAnimationPanel || !displayEnabled) {
return false;
}
if (!ZoneType.Battlefield.equals(card.getZone())) {
return false;
}
return true;
}

public void repaintOverlays() {
repaint();
doLayout();
Expand Down
74 changes: 73 additions & 1 deletion forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.WeakHashMap;
import javax.swing.Timer;

import com.google.common.collect.Lists;

Expand Down Expand Up @@ -49,6 +51,10 @@
public class PlayArea extends CardPanelContainer implements CardPanelMouseListener {
private static final long serialVersionUID = 8333013579724492513L;

private static final WeakHashMap<PlayArea, Void> registeredPlayAreas = new WeakHashMap<>();
private static Timer animationTimer;
private static final int ANIMATION_DELAY_MS = 50; // 20 FPS

private static final int GUTTER_Y = 5;
private static final int GUTTER_X = 5;
static final float EXTRA_CARD_SPACING_X = 0.04f;
Expand Down Expand Up @@ -85,6 +91,14 @@ public PlayArea(final CMatchUI matchUI, final FScrollPane scrollPane, final bool
this.zone = zone;
this.makeTokenRow = FModel.getPreferences().getPrefBoolean(FPref.UI_TOKENS_IN_SEPARATE_ROW);
this.stackCreatures = FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_CREATURES);

// Register Battlefield PlayArea for flying animation
if (FModel.getPreferences().getPrefBoolean(FPref.UI_ANIMATE_FLYING) && ZoneType.Battlefield.equals(zone)) {
synchronized (registeredPlayAreas) {
registeredPlayAreas.put(this, null);
startAnimationTimerIfNeeded();
}
}
}

private CardStackRow collectAllLands(List<CardPanel> remainingPanels) {
Expand Down Expand Up @@ -950,4 +964,62 @@ private void setCardWidth(int cardWidth0) {
this.stackSpacingX = Math.round(this.cardWidth * PlayArea.STACK_SPACING_X);
this.stackSpacingY = Math.round(this.cardHeight * PlayArea.STACK_SPACING_Y);
}
}

private static synchronized void startAnimationTimerIfNeeded() {
if (animationTimer != null || !FModel.getPreferences().getPrefBoolean(FPref.UI_ANIMATE_FLYING)) {
return;
}
animationTimer = new Timer(ANIMATION_DELAY_MS, e -> updateFlyingAnimationForAllPlayAreas());
animationTimer.start();
}

private static synchronized void stopAnimationTimerIfNeeded() {
if (animationTimer != null) {
animationTimer.stop();
animationTimer = null;
}
}

static void updateFlyingAnimationForAllPlayAreas() {
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ANIMATE_FLYING)) {
synchronized (registeredPlayAreas) {
registeredPlayAreas.clear();
stopAnimationTimerIfNeeded();
}
return;
}

List<PlayArea> playAreasToUpdate;
synchronized (registeredPlayAreas) {
playAreasToUpdate = new ArrayList<>(registeredPlayAreas.keySet());
}

for (PlayArea playArea : playAreasToUpdate) {
if (playArea == null || !ZoneType.Battlefield.equals(playArea.zone)) {
continue;
}

for (CardPanel panel : playArea.getCardPanels()) {
if (!panel.shouldFloat()) {
continue;
}

// Update flying phase
panel.flyingPhase += CardPanel.FLYING_SPEED;
if (panel.flyingPhase >= CardPanel.FLYING_PHASE_MAX) {
panel.flyingPhase -= CardPanel.FLYING_PHASE_MAX;
}

// Update position using baseY
panel.setCardBounds(panel.getCardX(), panel.baseY, panel.getCardWidth(), panel.getCardHeight());
}
}

// Check if we should stop timer (no more registered areas)
synchronized (registeredPlayAreas) {
if (registeredPlayAreas.isEmpty()) {
stopAnimationTimerIfNeeded();
}
}
}
}
2 changes: 2 additions & 0 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ cbCardTextHideReminder=Hide Reminder Text for Card Text Renderer
cbOpenPacksIndiv=Open Packs Individually
cbTokensInSeparateRow=Display Tokens in a Separate Row
cbStackCreatures=Stack Creatures
cbAnimateFlying=Animate Flying Creatures
cbFilterLandsByColorId=Filter Lands by Color in Activated Abilities
cbShowStormCount=Show Storm Count in Prompt Pane
cbRemindOnPriority=Visually Alert on Receipt of Priority
Expand Down Expand Up @@ -240,6 +241,7 @@ nlCardTextHideReminder=When render card images, skip rendering reminder text.
nlOpenPacksIndiv=When opening Fat Packs and Booster Boxes, booster packs will be opened and displayed one at a time.
nlTokensInSeparateRow=Displays tokens in a separate row on the battlefield below the non-token creatures.
nlStackCreatures=Stacks identical creatures on the battlefield like lands, artifacts, and enchantments.
nlAnimateFlying=Gives flying creatures a floating animation. (Requires starting a new match)
nlTimedTargOverlay=Enables throttling-based optimization of targeting overlay to reduce CPU use (only disable if you experience choppiness on older hardware, requires starting a new match).
nlCounterDisplayType=Selects the style of the in-game counter display for cards. Text-based is a new tab-like display on the cards. Image-based is the old counter image. Hybrid displays both at once.
nlCounterDisplayLocation=Determines where to position the text-based counters on the card: close to the top or close to the bottom.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public enum FPref implements PreferencesStore.IPref {
UI_SR_OPTIMIZE ("false"),
UI_OPEN_PACKS_INDIV ("false"),
UI_STACK_CREATURES ("false"),
UI_ANIMATE_FLYING ("false"),
UI_TOKENS_IN_SEPARATE_ROW("false"),
UI_UPLOAD_DRAFT ("false"),
UI_SCALE_LARGER ("true"),
Expand Down