mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Refactor and cleanup targeting arrow code
This commit is contained in:
@@ -22,129 +22,164 @@ import forge.assets.FSkinColor;
|
||||
import forge.assets.FSkinColor.Colors;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.screens.match.views.VCardDisplayArea.CardAreaPanel;
|
||||
import forge.screens.match.views.VPlayerPanel;
|
||||
import forge.screens.match.views.VStack;
|
||||
import forge.screens.match.views.VStack.StackInstanceDisplay;
|
||||
import forge.toolbox.FCardPanel;
|
||||
import forge.toolbox.FDisplayObject;
|
||||
import forge.util.Utils;
|
||||
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TargetingOverlay {
|
||||
private static final float ARROW_THICKNESS = Utils.scaleMax(5);
|
||||
private static final float ARROW_SIZE = 3 * ARROW_THICKNESS;
|
||||
|
||||
private final List<FCardPanel> cardPanels = new ArrayList<FCardPanel>();
|
||||
private final List<Vector2[]> arcsCombat = new ArrayList<Vector2[]>();
|
||||
private final List<Vector2[]> arcsOther = new ArrayList<Vector2[]>();
|
||||
private final List<Arrow> combatArrows = new ArrayList<Arrow>();
|
||||
private final List<Arrow> targetArrows = new ArrayList<Arrow>();
|
||||
private final List<Arrow> pairArrows = new ArrayList<Arrow>();
|
||||
|
||||
public TargetingOverlay() {
|
||||
}
|
||||
|
||||
// TODO - this is called every repaint, regardless if card
|
||||
// positions have changed or not. Could perform better if
|
||||
// it checked for a state change.
|
||||
private void assembleArcs() {
|
||||
//List<VField> fields = VMatchUI.SINGLETON_INSTANCE.getFieldViews();
|
||||
arcsCombat.clear();
|
||||
arcsOther.clear();
|
||||
cardPanels.clear();
|
||||
|
||||
for (VPlayerPanel pnl : FControl.getView().getPlayerPanels().values()) {
|
||||
for (CardAreaPanel cardPanel : pnl.getField().getCardPanels()) {
|
||||
cardPanel.buildCardPanelList(cardPanels);
|
||||
}
|
||||
}
|
||||
|
||||
// Locations of arc endpoint, per card, with ID as primary key.
|
||||
final Map<Integer, Vector2> endpoints = new HashMap<Integer, Vector2>();
|
||||
|
||||
for (FCardPanel c : cardPanels) {
|
||||
endpoints.put(c.getCard().getUniqueNumber(), c.getTargetingArrowOrigin());
|
||||
}
|
||||
private void refreshCombatArrows() {
|
||||
combatArrows.clear();
|
||||
|
||||
final Combat combat = FControl.getGame().getCombat();
|
||||
|
||||
// Work with all card panels currently visible
|
||||
List<Card> visualized = new ArrayList<Card>();
|
||||
for (FCardPanel c : cardPanels) {
|
||||
Card card = c.getCard();
|
||||
if (visualized.contains(card)) { continue; }
|
||||
|
||||
visualized.addAll(addArcsForCard(card, endpoints, combat));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Card> addArcsForCard(final Card c, final Map<Integer, Vector2> endpoints, final Combat combat) {
|
||||
List<Card> cardsVisualized = new ArrayList<Card>();
|
||||
cardsVisualized.add(c);
|
||||
|
||||
Card paired = c.getPairedWith();
|
||||
if (paired != null) {
|
||||
arcsOther.add(new Vector2[] {
|
||||
endpoints.get(paired.getUniqueNumber()),
|
||||
endpoints.get(c.getUniqueNumber())
|
||||
});
|
||||
cardsVisualized.add(paired);
|
||||
}
|
||||
|
||||
if (combat != null) {
|
||||
//connect each attacker with planeswalker it's attacking if applicable
|
||||
for (Card planeswalker : combat.getDefendingPlaneswalkers()) {
|
||||
List<Card> cards = combat.getAttackersOf(planeswalker);
|
||||
for (Card pwAttacker : cards) {
|
||||
if (!planeswalker.equals(c) && !pwAttacker.equals(c)) { continue; }
|
||||
arcsCombat.add(new Vector2[] {
|
||||
endpoints.get(planeswalker.getUniqueNumber()),
|
||||
endpoints.get(pwAttacker.getUniqueNumber())
|
||||
});
|
||||
for (Card attacker : combat.getAttackersOf(planeswalker)) {
|
||||
combatArrows.add(new Arrow(attacker, planeswalker));
|
||||
}
|
||||
}
|
||||
for (Card attackingCard : combat.getAttackers()) {
|
||||
List<Card> cards = combat.getBlockers(attackingCard);
|
||||
for (Card blockingCard : cards) {
|
||||
if (!attackingCard.equals(c) && !blockingCard.equals(c)) { continue; }
|
||||
arcsCombat.add(new Vector2[] {
|
||||
endpoints.get(attackingCard.getUniqueNumber()),
|
||||
endpoints.get(blockingCard.getUniqueNumber())
|
||||
});
|
||||
cardsVisualized.add(blockingCard);
|
||||
|
||||
//connect each blocker with the attacker it's blocking
|
||||
for (Card blocker : combat.getAllBlockers()) {
|
||||
for (Card attacker : combat.getAttackersBlockedBy(blocker)) {
|
||||
combatArrows.add(new Arrow(blocker, attacker));
|
||||
}
|
||||
cardsVisualized.add(attackingCard);
|
||||
}
|
||||
}
|
||||
|
||||
return cardsVisualized;
|
||||
}
|
||||
|
||||
public void drawArcs(Graphics g, FSkinColor color, List<Vector2[]> arcs) {
|
||||
for (Vector2[] p : arcs) {
|
||||
if (p[0] != null && p[1] != null) {
|
||||
g.drawArrow(ARROW_THICKNESS, ARROW_SIZE, color, p[1].x, p[1].y, p[0].x, p[0].y);
|
||||
private void refreshTargetArrows() {
|
||||
targetArrows.clear();
|
||||
|
||||
VStack stack = FControl.getView().getStack();
|
||||
if (stack.isVisible()) {
|
||||
for (FDisplayObject child : stack.getChildren()) {
|
||||
Vector2 arrowOrigin = new Vector2(
|
||||
VStack.CARD_WIDTH * FCardPanel.TARGET_ORIGIN_FACTOR_X + 2 * VStack.PADDING,
|
||||
VStack.CARD_HEIGHT * FCardPanel.TARGET_ORIGIN_FACTOR_Y + 2 * VStack.PADDING);
|
||||
|
||||
float y = child.getTop() + arrowOrigin.y;
|
||||
if (y < 0) {
|
||||
continue; //don't draw arrow scrolled off top
|
||||
}
|
||||
if (y > child.getHeight()) {
|
||||
break; //don't draw arrow scrolled off bottom
|
||||
}
|
||||
|
||||
SpellAbilityStackInstance stackInstance = ((StackInstanceDisplay)child).getStackInstance();
|
||||
TargetChoices targets = stackInstance.getSpellAbility().getTargets();
|
||||
arrowOrigin = arrowOrigin.add(stack.getScreenPosition());
|
||||
|
||||
for (Card c : targets.getTargetCards()) {
|
||||
targetArrows.add(new Arrow(arrowOrigin, c));
|
||||
}
|
||||
for (Player p : targets.getTargetPlayers()) {
|
||||
targetArrows.add(new Arrow(arrowOrigin, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshPairArrows() {
|
||||
pairArrows.clear();
|
||||
|
||||
HashSet<Card> pairedCards = new HashSet<Card>();
|
||||
for (VPlayerPanel playerPanel : FControl.getView().getPlayerPanels().values()) {
|
||||
for (Card card : playerPanel.getField().getRow1().getOrderedCards()) {
|
||||
if (pairedCards.contains(card)) { continue; } //prevent arrows going both ways
|
||||
|
||||
Card paired = card.getPairedWith();
|
||||
if (paired != null) {
|
||||
pairArrows.add(new Arrow(card, paired));
|
||||
pairedCards.add(paired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(final Graphics g) {
|
||||
assembleArcs();
|
||||
|
||||
if (arcsCombat.isEmpty() && arcsOther.isEmpty()) { return; }
|
||||
|
||||
// Get arrow colors from the theme or use default colors if the theme does not have them defined
|
||||
FSkinColor colorOther = FSkinColor.get(Colors.CLR_NORMAL_TARGETING_ARROW);
|
||||
if (colorOther.getAlpha() == 0) {
|
||||
colorOther = FSkinColor.get(Colors.CLR_ACTIVE).alphaColor(153f / 255f);
|
||||
}
|
||||
FSkinColor colorCombat = FSkinColor.get(Colors.CLR_COMBAT_TARGETING_ARROW);
|
||||
if (colorCombat.getAlpha() == 0) {
|
||||
colorCombat = FSkinColor.getStandardColor(new Color(1, 0, 0, 153 / 255f));
|
||||
refreshPairArrows(); //TODO: Optimize so these don't need to be refreshed every render
|
||||
if (!pairArrows.isEmpty()) {
|
||||
FSkinColor color = FSkinColor.get(Colors.CLR_NORMAL_TARGETING_ARROW);
|
||||
if (color.getAlpha() == 0) {
|
||||
color = FSkinColor.get(Colors.CLR_ACTIVE).alphaColor(153f / 255f);
|
||||
}
|
||||
for (Arrow arrow : pairArrows) {
|
||||
arrow.draw(g, color);
|
||||
}
|
||||
}
|
||||
|
||||
drawArcs(g, colorOther, arcsOther);
|
||||
drawArcs(g, colorCombat, arcsCombat);
|
||||
refreshCombatArrows();
|
||||
if (!combatArrows.isEmpty()) {
|
||||
FSkinColor color = FSkinColor.get(Colors.CLR_COMBAT_TARGETING_ARROW);
|
||||
if (color.getAlpha() == 0) {
|
||||
color = FSkinColor.getStandardColor(new Color(1, 0, 0, 153 / 255f));
|
||||
}
|
||||
for (Arrow arrow : combatArrows) {
|
||||
arrow.draw(g, color);
|
||||
}
|
||||
}
|
||||
|
||||
refreshTargetArrows();
|
||||
if (!targetArrows.isEmpty()) {
|
||||
FSkinColor color = FSkinColor.get(Colors.CLR_COMBAT_TARGETING_ARROW); //TODO: Consider a different color for targeting
|
||||
if (color.getAlpha() == 0) {
|
||||
color = FSkinColor.getStandardColor(new Color(1, 0, 0, 153 / 255f));
|
||||
}
|
||||
for (Arrow arrow : targetArrows) {
|
||||
arrow.draw(g, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Arrow {
|
||||
private final Vector2 start, end;
|
||||
|
||||
private Arrow(Card startCard, Card endCard) {
|
||||
this(CardAreaPanel.get(startCard).getTargetingArrowOrigin(), CardAreaPanel.get(endCard).getTargetingArrowOrigin());
|
||||
}
|
||||
private Arrow(Card card, Player player) {
|
||||
this(CardAreaPanel.get(card).getTargetingArrowOrigin(), FControl.getPlayerPanel(player).getAvatar().getTargetingArrowOrigin());
|
||||
}
|
||||
private Arrow(Vector2 start0, Card targetCard) {
|
||||
this(start0, CardAreaPanel.get(targetCard).getTargetingArrowOrigin());
|
||||
}
|
||||
private Arrow(Vector2 start0, Player targetPlayer) {
|
||||
this(start0, FControl.getPlayerPanel(targetPlayer).getAvatar().getTargetingArrowOrigin());
|
||||
}
|
||||
private Arrow(Vector2 start0, Vector2 end0) {
|
||||
start = start0;
|
||||
end = end0;
|
||||
}
|
||||
|
||||
private void draw(Graphics g, FSkinColor color) {
|
||||
if (start == null || end == null) { return; }
|
||||
|
||||
g.drawArrow(ARROW_THICKNESS, ARROW_SIZE, color, start.x, start.y, end.x, end.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.screens.match.views;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
|
||||
import forge.Graphics;
|
||||
import forge.assets.FSkin;
|
||||
@@ -34,6 +35,20 @@ public class VAvatar extends FDisplayObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Vector2 getTargetingArrowOrigin() {
|
||||
Vector2 origin = new Vector2(getScreenPosition());
|
||||
|
||||
origin.x += WIDTH * 0.75f;
|
||||
if (origin.y < FControl.getView().getHeight() / 2) {
|
||||
origin.y += HEIGHT * 0.75f; //target bottom right corner if on top half of screen
|
||||
}
|
||||
else {
|
||||
origin.y += HEIGHT * 0.25f; //target top right corner if on bottom half of screen
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g) {
|
||||
float w = getWidth();
|
||||
|
||||
@@ -19,6 +19,10 @@ public abstract class VCardDisplayArea extends VDisplayArea {
|
||||
protected final List<Card> orderedCards = new ArrayList<Card>();
|
||||
protected final List<CardAreaPanel> cardPanels = new ArrayList<CardAreaPanel>();
|
||||
|
||||
public Iterable<Card> getOrderedCards() {
|
||||
return orderedCards;
|
||||
}
|
||||
|
||||
public Iterable<CardAreaPanel> getCardPanels() {
|
||||
return cardPanels;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import forge.util.Utils;
|
||||
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);
|
||||
private static final float PADDING = Utils.scaleMin(3);
|
||||
public static final float PADDING = Utils.scaleMin(3);
|
||||
private static final FSkinFont FONT = FSkinFont.get(11);
|
||||
private static final float ALPHA_COMPOSITE = 0.5f;
|
||||
private static final TextRenderer textRenderer = new TextRenderer(true);
|
||||
@@ -111,7 +111,7 @@ public class VStack extends FDropDown {
|
||||
super.setScrollPositionsAfterLayout(0, 0); //always scroll to top after layout
|
||||
}
|
||||
|
||||
private class StackInstanceDisplay extends FDisplayObject {
|
||||
public class StackInstanceDisplay extends FDisplayObject {
|
||||
private final SpellAbilityStackInstance stackInstance;
|
||||
private final boolean isTop;
|
||||
private final Color foreColor, backColor;
|
||||
@@ -141,6 +141,10 @@ public class VStack extends FDropDown {
|
||||
return Math.round(height);
|
||||
}
|
||||
|
||||
public SpellAbilityStackInstance getStackInstance() {
|
||||
return stackInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tap(float x, float y, int count) {
|
||||
final Player player = stackInstance.getSpellAbility().getActivatingPlayer();
|
||||
|
||||
@@ -11,6 +11,8 @@ public class FCardPanel extends FDisplayObject {
|
||||
public static final float TAPPED_ANGLE = -90;
|
||||
public static final float ASPECT_RATIO = 3.5f / 2.5f;
|
||||
public static final float PADDING = Utils.scaleMin(2);
|
||||
public static final float TARGET_ORIGIN_FACTOR_X = 0.15f;
|
||||
public static final float TARGET_ORIGIN_FACTOR_Y = 0.5f;
|
||||
|
||||
private Card card;
|
||||
private boolean tapped;
|
||||
@@ -93,8 +95,8 @@ public class FCardPanel extends FDisplayObject {
|
||||
h = temp;
|
||||
}
|
||||
|
||||
origin.x += left + w * 0.15f;
|
||||
origin.y += top + h * 0.5f;
|
||||
origin.x += left + w * TARGET_ORIGIN_FACTOR_X;
|
||||
origin.y += top + h * TARGET_ORIGIN_FACTOR_Y;
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user