Refactor and cleanup targeting arrow code

This commit is contained in:
drdev
2014-06-26 04:06:10 +00:00
parent f0a2809638
commit 53b2d87e78
5 changed files with 151 additions and 91 deletions

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}