* UI: improved and fixed possible targets highlighting:

* added blockers highlighting on declare blockers step;
   * fixed that blocker targets highlights all attackers instead real;
   * fixed wrong attackers draw in images render mode;
This commit is contained in:
Oleg Agafonov 2019-03-28 09:29:15 +04:00
parent c58b28f94f
commit 0e6dbb4eed
7 changed files with 167 additions and 132 deletions

View file

@ -696,6 +696,13 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
List<UUID> possibleBlockers = new ArrayList<>();
if (options != null && options.containsKey(Constants.Option.POSSIBLE_BLOCKERS)) {
if (options.get(Constants.Option.POSSIBLE_BLOCKERS) instanceof List) {
possibleBlockers.addAll((List) options.get(Constants.Option.POSSIBLE_BLOCKERS));
}
}
for (PlayerView player : game.getPlayers()) {
if (players.containsKey(player.getPlayerId())) {
if (!possibleAttackers.isEmpty()) {
@ -705,6 +712,13 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
}
if (!possibleBlockers.isEmpty()) {
for (UUID permanentId : possibleBlockers) {
if (player.getBattlefield().containsKey(permanentId)) {
player.getBattlefield().get(permanentId).setCanBlock(true);
}
}
}
players.get(player.getPlayerId()).update(player);
if (player.getPlayerId().equals(playerId)) {
skipButtons.updateFromPlayer(player);

View file

@ -1,17 +1,5 @@
package org.mage.card.arcane;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.StringTokenizer;
import java.util.UUID;
import javax.swing.*;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import mage.cards.action.ActionCallback;
import mage.client.constants.Constants;
import mage.client.dialog.PreferencesDialog;
@ -25,6 +13,16 @@ import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.StringTokenizer;
import java.util.UUID;
/**
* Class for drawing the mage card object by using a form based JComponent
@ -117,8 +115,9 @@ public class CardPanelComponentImpl extends CardPanel {
final boolean isChoosable;
final boolean isPlayable;
final boolean canAttack;
final boolean canBlock;
public Key(int width, int height, int cardWidth, int cardHeight, int cardXOffset, int cardYOffset, boolean hasImage, boolean isSelected, boolean isChoosable, boolean isPlayable, boolean canAttack) {
public Key(int width, int height, int cardWidth, int cardHeight, int cardXOffset, int cardYOffset, boolean hasImage, boolean isSelected, boolean isChoosable, boolean isPlayable, boolean canAttack, boolean canBlock) {
this.width = width;
this.height = height;
this.cardWidth = cardWidth;
@ -130,6 +129,7 @@ public class CardPanelComponentImpl extends CardPanel {
this.isChoosable = isChoosable;
this.isPlayable = isPlayable;
this.canAttack = canAttack;
this.canBlock = canBlock;
}
@Override
@ -146,6 +146,7 @@ public class CardPanelComponentImpl extends CardPanel {
hash = 19 * hash + (this.isChoosable ? 1 : 0);
hash = 19 * hash + (this.isPlayable ? 1 : 0);
hash = 19 * hash + (this.canAttack ? 1 : 0);
hash = 19 * hash + (this.canBlock ? 1 : 0);
return hash;
}
@ -194,7 +195,7 @@ public class CardPanelComponentImpl extends CardPanel {
if (this.canAttack != other.canAttack) {
return false;
}
return true;
return this.canBlock == other.canBlock;
}
}
@ -202,20 +203,20 @@ public class CardPanelComponentImpl extends CardPanel {
IMAGE_CACHE = ImageCaches.register(SoftValuesLoadingCache.from(CardPanelComponentImpl::createImage));
}
static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage){
static private boolean canShowCardIcons(int cardFullWidth, boolean cardHasImage) {
// cards without images show icons and text always
// TODO: apply "card names on card" setting to icon too?
// TODO: fix card min-max size to hide (compare to settings size, not direct 60 and 200)
return ((cardFullWidth > 60) && (cardFullWidth < 200)) || (!cardHasImage);
}
private static class CardSizes{
private static class CardSizes {
Rectangle rectFull;
Rectangle rectSelection;
Rectangle rectBorder;
Rectangle rectCard;
CardSizes(int offsetX, int offsetY, int fullWidth, int fullHeight){
CardSizes(int offsetX, int offsetY, int fullWidth, int fullHeight) {
int realBorderSizeX = Math.round(fullWidth * BLACK_BORDER_SIZE);
int realBorderSizeY = Math.round(fullWidth * BLACK_BORDER_SIZE);
@ -406,7 +407,8 @@ public class CardPanelComponentImpl extends CardPanel {
g2d.drawImage(
IMAGE_CACHE.getOrThrow(
new Key(getWidth(), getHeight(), getCardWidth(), getCardHeight(), getCardXOffset(), getCardYOffset(),
hasImage, isSelected(), isChoosable(), getGameCard().isPlayable(), getGameCard().isCanAttack())),
hasImage, isSelected(), isChoosable(), getGameCard().isPlayable(), getGameCard().isCanAttack(),
getGameCard().isCanBlock())),
0, 0, null);
g2d.dispose();
}
@ -442,6 +444,12 @@ public class CardPanelComponentImpl extends CardPanel {
g2d.fillRoundRect(sizes.rectSelection.x, sizes.rectSelection.y, sizes.rectSelection.width, sizes.rectSelection.height, cornerSizeSelection, cornerSizeSelection);
}
// draw attack or block border (?inner part of selection?)
if (key.canAttack || key.canBlock) {
g2d.setColor(new Color(255, 50, 50, 230));
g2d.fillRoundRect(sizes.rectSelection.x + 1, sizes.rectSelection.y + 1, sizes.rectSelection.width - 2, sizes.rectSelection.height - 2, cornerSizeSelection, cornerSizeSelection);
}
// draw empty card with border
if (!key.hasImage) {
// gray 1 px border
@ -452,12 +460,6 @@ public class CardPanelComponentImpl extends CardPanel {
g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder);
}
// draw attack border (inner part of selection)
if (key.canAttack) {
g2d.setColor(new Color(0, 0, 255, 230));
g2d.fillRoundRect(sizes.rectBorder.x + 1, sizes.rectBorder.y + 1, sizes.rectBorder.width - 2, sizes.rectBorder.height - 2, cornerSizeBorder, cornerSizeBorder);
}
// draw real card by component (see imagePanel and other layout's items)
//TODO:uncomment
@ -521,7 +523,7 @@ public class CardPanelComponentImpl extends CardPanel {
StringTokenizer tok = new StringTokenizer(manaCost, " ");
while (tok.hasMoreTokens()) {
tok.nextToken();
if(width != 0) {
if (width != 0) {
width += symbolMarginX;
}
width += getSymbolWidth();

View file

@ -1,17 +1,7 @@
package org.mage.card.arcane;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import mage.cards.action.ActionCallback;
import mage.client.constants.Constants;
import mage.constants.CardType;
@ -21,6 +11,14 @@ import mage.view.CardView;
import mage.view.CounterView;
import mage.view.PermanentView;
import mage.view.StackAbilityView;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.graphics.GraphicsUtilities;
import org.mage.plugins.card.images.ImageCache;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
public class CardPanelRenderImpl extends CardPanel {
@ -107,9 +105,7 @@ public class CardPanelRenderImpl extends CardPanel {
// are the same for a and b
return false;
}
if (aa.getDamage() != bb.getDamage()) {
return false;
}
return aa.getDamage() == bb.getDamage();
}
return true;
}
@ -143,6 +139,7 @@ public class CardPanelRenderImpl extends CardPanel {
sb.append((char) (isChoosable ? 1 : 0));
sb.append((char) (this.view.isPlayable() ? 1 : 0));
sb.append((char) (this.view.isCanAttack() ? 1 : 0));
sb.append((char) (this.view.isCanBlock() ? 1 : 0));
sb.append((char) (this.view.isFaceDown() ? 1 : 0));
sb.append((char) this.view.getFrameStyle().ordinal());
if (this.view instanceof PermanentView) {
@ -266,8 +263,8 @@ public class CardPanelRenderImpl extends CardPanel {
// Try to get card image from cache based on our card characteristics
ImageKey key
= new ImageKey(getGameCard(), artImage,
getCardWidth(), getCardHeight(),
isChoosable(), isSelected());
getCardWidth(), getCardHeight(),
isChoosable(), isSelected());
try {
cardImage = IMAGE_CACHE.get(key, this::renderCard);
} catch (ExecutionException e) {

View file

@ -5,24 +5,6 @@
*/
package org.mage.card.arcane;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.*;
import mage.ObjectColor;
import mage.cards.ArtRect;
import mage.cards.FrameStyle;
@ -34,6 +16,22 @@ import mage.util.SubTypeList;
import mage.view.CardView;
import mage.view.PermanentView;
import org.apache.log4j.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.mage.card.arcane.ManaSymbols.getSizedManaSymbol;
@ -56,9 +54,10 @@ import static org.mage.card.arcane.ManaSymbols.getSizedManaSymbol;
render.draw(g, cardWidth, cardHeight);
}
*/
/**
* @author stravant@gmail.com
*
* <p>
* Base rendering class for new border cards
*/
public class ModernCardRenderer extends CardRenderer {
@ -98,6 +97,7 @@ public class ModernCardRenderer extends CardRenderer {
}
return new Font("Arial", Font.PLAIN, 1);
}
public static final Font BASE_BELEREN_FONT = loadFont("beleren-bold");
public static final Paint BG_TEXTURE_WHITE = loadBackgroundTexture("white");
@ -275,7 +275,9 @@ public class ModernCardRenderer extends CardRenderer {
} else if (cardView.isPlayable()) {
borderColor = new Color(153, 102, 204, 200);
} else if (cardView.isCanAttack()) {
borderColor = new Color(0, 0, 255, 230);
borderColor = new Color(255, 50, 50, 230);
} else if (cardView.isCanBlock()) {
borderColor = new Color(255, 50, 50, 230);
} else {
borderColor = Color.BLACK;
}
@ -659,7 +661,7 @@ public class ModernCardRenderer extends CardRenderer {
}
public void drawZendikarCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
Color boxColor, Paint paint) {
Color boxColor, Paint paint) {
BufferedImage artToUse = faceArtImage;
boolean hadToUseFullArt = false;
@ -707,8 +709,8 @@ public class ModernCardRenderer extends CardRenderer {
}
public void drawBFZCurvedFace(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
int topxdelta, int endydelta,
Color boxColor, Paint paint) {
int topxdelta, int endydelta,
Color boxColor, Paint paint) {
BufferedImage artToUse = faceArtImage;
boolean hadToUseFullArt = false;
if (faceArtImage == null) {
@ -764,10 +766,10 @@ public class ModernCardRenderer extends CardRenderer {
}
public void drawUSTCurves(Graphics2D g2, BufferedImage image, int x, int y, int x2, int y2,
int topxdelta, int endydelta,
Color boxColor, Paint paint) {
int topxdelta, int endydelta,
Color boxColor, Paint paint) {
BufferedImage artToUse = artImage;
int srcW = x2;
int srcH = y2;
if (artToUse != null) {
@ -999,23 +1001,23 @@ public class ModernCardRenderer extends CardRenderer {
Polygon symbol = new Polygon(
new int[]{
x + w / 2,
(int) (x + w * 0.9),
x + w,
(int) (x + w * 0.6),
x + w / 2,
(int) (x + w * 0.4),
x,
(int) (x + w * 0.1),},
x + w / 2,
(int) (x + w * 0.9),
x + w,
(int) (x + w * 0.6),
x + w / 2,
(int) (x + w * 0.4),
x,
(int) (x + w * 0.1),},
new int[]{
y + h,
(int) (y + 0.8 * h),
y,
(int) (y - 0.2 * h),
y,
(int) (y - 0.2 * h),
y,
(int) (y + 0.8 * h),},
y + h,
(int) (y + 0.8 * h),
y,
(int) (y - 0.2 * h),
y,
(int) (y - 0.2 * h),
y,
(int) (y + 0.8 * h),},
8);
// Draw + stroke
@ -1124,7 +1126,7 @@ public class ModernCardRenderer extends CardRenderer {
drawBasicManaTextbox(g, x, y, w, h, ((TextboxBasicManaRule) allRules.get(0)).getBasicManaSymbol());
return;
} else // Big circle in the middle for Zendikar lands
if (allRules.size() == 1) {
if (allRules.size() == 1) {
// Size of mana symbol = 9/4 * h, 3/4h above line
if (allRules.get(0) instanceof TextboxBasicManaRule) {
drawBasicManaSymbol(g, x + w / 2 - 9 * h / 8 + 1, y - 3 * h / 4, 9 * h / 4, 9 * h / 4, ((TextboxBasicManaRule) allRules.get(0)).getBasicManaSymbol());
@ -1278,45 +1280,45 @@ public class ModernCardRenderer extends CardRenderer {
if (loyaltyRule.loyaltyChange < 0 || loyaltyRule.loyaltyChange == TextboxLoyaltyRule.MINUS_X) {
symbol = new Polygon(
new int[]{
symbolX,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX + symbolWidth / 2,
symbolX,},
symbolX,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX + symbolWidth / 2,
symbolX,},
new int[]{
symbolY,
symbolY,
symbolY + symbolHeight - 3,
symbolY + symbolHeight + 3,
symbolY + symbolHeight - 3,},
symbolY,
symbolY,
symbolY + symbolHeight - 3,
symbolY + symbolHeight + 3,
symbolY + symbolHeight - 3,},
5);
} else if (loyaltyRule.loyaltyChange > 0) {
symbol = new Polygon(
new int[]{
symbolX,
symbolX + symbolWidth / 2,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX,},
symbolX,
symbolX + symbolWidth / 2,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX,},
new int[]{
symbolY + 3,
symbolY - 3,
symbolY + 3,
symbolY + symbolHeight,
symbolY + symbolHeight,},
symbolY + 3,
symbolY - 3,
symbolY + 3,
symbolY + symbolHeight,
symbolY + symbolHeight,},
5);
} else {
symbol = new Polygon(
new int[]{
symbolX,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX,},
symbolX,
symbolX + symbolWidth,
symbolX + symbolWidth,
symbolX,},
new int[]{
symbolY,
symbolY,
symbolY + symbolHeight,
symbolY + symbolHeight,},
symbolY,
symbolY,
symbolY + symbolHeight,
symbolY + symbolHeight,},
4);
}
g.setColor(new Color(0, 0, 0, 128));
@ -1610,13 +1612,13 @@ public class ModernCardRenderer extends CardRenderer {
Color[] translatedColors;
if (types.contains(CardType.LAND)) {
translatedColors = new Color[]{
getLandTextboxColor(twoColors.get(0)),
getLandTextboxColor(twoColors.get(1))
getLandTextboxColor(twoColors.get(0)),
getLandTextboxColor(twoColors.get(1))
};
} else {
translatedColors = new Color[]{
getTextboxColor(twoColors.get(0)),
getTextboxColor(twoColors.get(1))
getTextboxColor(twoColors.get(0)),
getTextboxColor(twoColors.get(1))
};
}

View file

@ -1,8 +1,6 @@
package mage.constants;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class Constants {
@ -50,19 +48,14 @@ public final class Constants {
*/
public static final int PRIORITY_TIME_SEC = 1200;
public enum Option {
;
public static final String POSSIBLE_ATTACKERS = "possibleAttackers";
public static final String POSSIBLE_BLOCKERS = "possibleBlockers";
public static final String SPECIAL_BUTTON = "specialButton";
// used to control automatic answers of optional effects
public static final String ORIGINAL_ID = "originalId";
public static final String SECOND_MESSAGE = "secondMessage";
public static final String HINT_TEXT = "hintText";
}
}

View file

@ -1,4 +1,3 @@
package mage.view;
import com.google.gson.annotations.Expose;
@ -109,6 +108,7 @@ public class CardView extends SimpleCardView {
protected boolean isChoosable;
protected boolean selected;
protected boolean canAttack;
protected boolean canBlock;
protected boolean inViewerOnly;
protected Card originalCard = null;
@ -202,6 +202,7 @@ public class CardView extends SimpleCardView {
this.isChoosable = cardView.isChoosable;
this.selected = cardView.selected;
this.canAttack = cardView.canAttack;
this.canBlock = cardView.canBlock;
this.inViewerOnly = cardView.inViewerOnly;
this.originalCard = cardView.originalCard.copy();
}
@ -286,7 +287,7 @@ public class CardView extends SimpleCardView {
this.power = Integer.toString(card.getPower().getValue());
this.toughness = Integer.toString(card.getToughness().getValue());
this.cardTypes = card.getCardType();
this.faceDown = ((Permanent) card).isFaceDown(game);
this.faceDown = card.isFaceDown(game);
} else {
// this.hideInfo = true;
return;
@ -296,9 +297,9 @@ public class CardView extends SimpleCardView {
SplitCard splitCard = null;
if (card.isSplitCard()) {
splitCard = (SplitCard) card;
rotate = (((SplitCard) card).getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH;
rotate = (card.getSpellAbility().getSpellAbilityType()) != SpellAbilityType.SPLIT_AFTERMATH;
} else if (card instanceof Spell) {
switch (((Spell) card).getSpellAbility().getSpellAbilityType()) {
switch (card.getSpellAbility().getSpellAbilityType()) {
case SPLIT_FUSED:
splitCard = (SplitCard) ((Spell) card).getCard();
rotate = true;
@ -390,12 +391,12 @@ public class CardView extends SimpleCardView {
this.cardNumber = ((PermanentToken) card).getToken().getOriginalCardNumber();
} else {
// a created token
this.expansionSetCode = ((PermanentToken) card).getExpansionSetCode();
this.tokenDescriptor = ((PermanentToken) card).getTokenDescriptor();
this.expansionSetCode = card.getExpansionSetCode();
this.tokenDescriptor = card.getTokenDescriptor();
}
//
// set code und card number for token copies to get the image
this.rules = ((PermanentToken) card).getRules(game);
this.rules = card.getRules(game);
this.type = ((PermanentToken) card).getToken().getTokenType();
} else {
this.rarity = card.getRarity();
@ -995,6 +996,14 @@ public class CardView extends SimpleCardView {
this.canAttack = canAttack;
}
public boolean isCanBlock() {
return canBlock;
}
public void setCanBlock(boolean canBlock) {
this.canBlock = canBlock;
}
public boolean isCreature() {
return cardTypes.contains(CardType.CREATURE);
}

View file

@ -51,6 +51,7 @@ import java.io.Serializable;
import java.util.List;
import java.util.Queue;
import java.util.*;
import java.util.stream.Collectors;
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
@ -1432,7 +1433,12 @@ public class HumanPlayer extends PlayerImpl {
while (!abort) {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireSelectEvent(playerId, "Select blockers");
Map<String, Serializable> options = new HashMap<>();
List<UUID> possibleBlockers = game.getBattlefield().getActivePermanents(filter, playerId, game).stream()
.map(p -> p.getId())
.collect(Collectors.toList());
options.put(Constants.Option.POSSIBLE_BLOCKERS, (Serializable) possibleBlockers);
game.fireSelectEvent(playerId, "Select blockers", options);
}
waitForResponse(game);
if (response.getBoolean() != null) {
@ -1504,9 +1510,21 @@ public class HumanPlayer extends PlayerImpl {
TargetAttackingCreature target = new TargetAttackingCreature();
prepareForResponse(game);
if (!isExecutingMacro()) {
// possible attackers to block
Set<UUID> attackers = target.possibleTargets(null, playerId, game);
Permanent blocker = game.getPermanent(blockerId);
Set<UUID> possibleTargets = new HashSet<>();
for (UUID attackerId : attackers) {
CombatGroup group = game.getCombat().findGroup(attackerId);
if (group != null && blocker != null && group.canBlock(blocker, game)) {
possibleTargets.add(attackerId);
}
}
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)),
target.possibleTargets(null, playerId, game), false, getOptions(target, null));
possibleTargets, false, getOptions(target, null));
}
waitForResponse(game);
if (response.getBoolean() != null) {
// do nothing