Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement "perpetually" mechanic #13016

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ public void update(Map<UUID, PermanentView> battlefield) {
MagePermanent oldMagePermanent = oldFound == null ? null : (MagePermanent) oldFound.getMainPanel();

// Check if there was a change in the power or toughness of the permanent
if(permanent.isPowerPerpetuallyAffected() || permanent.isToughnessPerpetuallyAffected()) {
changed = true;
}
int permanentPower = 0;
int permanentToughness = 0;
int oldMagePermanentPower = 0;
Expand Down Expand Up @@ -224,6 +227,11 @@ public void update(Map<UUID, PermanentView> battlefield) {
changed = true;
}
}

// Check for power or toughness being perpetually affected
if(permanent.isPowerPerpetuallyAffected() || permanent.isToughnessPerpetuallyAffected()) {
changed = true;
}
}
oldMagePermanent.update(permanent);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ public class CardPanelAttributes {
public final boolean isSelected;
public final boolean isChoosable;
public final boolean isTransformed;
public final boolean isPowerPerpetuallyAffected;
public final boolean isToughnessPerpetuallyAffected;

public CardPanelAttributes(int cardWidth, int cardHeight, boolean isChoosable, boolean isSelected, boolean isTransformed) {
public CardPanelAttributes(int cardWidth, int cardHeight, boolean isChoosable, boolean isSelected, boolean isTransformed,
boolean isPowerPerpetuallyAffected, boolean isToughnessPerpetuallyAffected) {
this.cardWidth = cardWidth;
this.cardHeight = cardHeight;
this.isChoosable = isChoosable;
this.isSelected = isSelected;
this.isTransformed = isTransformed;
this.isPowerPerpetuallyAffected = isPowerPerpetuallyAffected;
this.isToughnessPerpetuallyAffected = isToughnessPerpetuallyAffected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,9 @@ public void doLayout() {
MageInt currentPower = cardView.getOriginalPower();
MageInt currentToughness = cardView.getOriginalToughness();

prepareGlowFont(ptText1, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), currentPower, false);
prepareGlowFont(ptText2, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), null, false);
prepareGlowFont(ptText3, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), currentToughness, CardRendererUtils.isCardWithDamage(cardView));
prepareGlowFont(ptText1, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), currentPower, false, getCard().isPowerPerpetuallyAffected());
prepareGlowFont(ptText2, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), null, false, false);
prepareGlowFont(ptText3, Math.max(CARD_PT_FONT_MIN_SIZE, mainFontSize), currentToughness, CardRendererUtils.isCardWithDamage(cardView), getCard().isToughnessPerpetuallyAffected());

// right bottom corner with margin (sizes from any sample card)
int ptMarginRight = Math.round(64f / 672f * cardWidth);
Expand Down Expand Up @@ -677,9 +677,9 @@ private int getManaWidth(String manaCost, int symbolMarginX) {
return width;
}

private void prepareGlowFont(GlowText label, int fontSize, MageInt value, boolean drawAsDamaged) {
private void prepareGlowFont(GlowText label, int fontSize, MageInt value, boolean drawAsDamaged, boolean isPerpetuallyAffected) {
label.setFont(getFont().deriveFont(Font.BOLD, fontSize));
label.setForeground(CardRendererUtils.getCardTextColor(value, drawAsDamaged, titleText.getForeground(), true));
label.setForeground(CardRendererUtils.getCardTextColor(value, drawAsDamaged, titleText.getForeground(), true, isPerpetuallyAffected));
Dimension ptSize = label.getPreferredSize();
label.setSize(ptSize.width, ptSize.height);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public void updateArtImage() {
}

private CardPanelAttributes getAttributes() {
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed());
return new CardPanelAttributes(getCardWidth(), getCardHeight(), isChoosable(), isSelected(), isTransformed(), getCard().isPowerPerpetuallyAffected(), getCard().isToughnessPerpetuallyAffected());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public final class CardRendererUtils {
private static final Color CARD_TEXT_COLOR_GOOD_DARK = new Color(52, 135, 88);
private static final Color CARD_TEXT_COLOR_BAD_LIGHT = new Color(234, 153, 153);
private static final Color CARD_TEXT_COLOR_BAD_DARK = new Color(200, 33, 33);
private static final Color CARD_TEXT_COLOR_PERPETUAL = new Color(204, 79, 254);

/**
* Convert an abstract image, whose underlying implementation may or may not
Expand Down Expand Up @@ -239,15 +240,18 @@ public static boolean isCardWithDamage(CardView cardView) {
return haveDamage;
}

public static Color getCardTextColor(MageInt value, boolean drawAsDamaged, Color defaultColor, boolean textLight) {
public static Color getCardTextColor(MageInt value, boolean drawAsDamaged, Color defaultColor, boolean textLight, boolean isPerpetuallyAffected) {
if (drawAsDamaged) {
return textLight ? CARD_TEXT_COLOR_BAD_LIGHT : CARD_TEXT_COLOR_BAD_DARK;
}

// boost colorizing
if (value != null) {
int currentValue = value.getValue();
int baseValue = value.getModifiedBaseValue();
// perpetual boost colorizing
if(isPerpetuallyAffected) {
return CARD_TEXT_COLOR_PERPETUAL;
}
// boost colorizing
if (currentValue < baseValue) {
return textLight ? CARD_TEXT_COLOR_BAD_LIGHT : CARD_TEXT_COLOR_BAD_DARK;
} else if (currentValue > baseValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1043,13 +1043,13 @@ protected void drawBottomRight(Graphics2D g, CardPanelAttributes attribs, Paint
int ptPosStart2 = ptPosStart1 + ptTextWidth1 + ptDeviderSpace;
int ptPosStart3 = ptPosStart2 + ptTextWidth2 + ptDeviderSpace;
// p
g.setColor(CardRendererUtils.getCardTextColor(currentPower, false, defaultTextColor, defaultTextLight));
g.setColor(CardRendererUtils.getCardTextColor(currentPower, false, defaultTextColor, defaultTextLight, attribs.isPowerPerpetuallyAffected));
g.drawString(ptText1, ptPosStart1, curY - ptTextOffset - 1); // left
// /
g.setColor(defaultTextColor);
g.drawString(ptText2, ptPosStart2, curY - ptTextOffset - 1); // center
// t
g.setColor(CardRendererUtils.getCardTextColor(currentToughness, CardRendererUtils.isCardWithDamage(cardView), defaultTextColor, defaultTextLight));
g.setColor(CardRendererUtils.getCardTextColor(currentToughness, CardRendererUtils.isCardWithDamage(cardView), defaultTextColor, defaultTextLight, attribs.isToughnessPerpetuallyAffected));
g.drawString(ptText3, ptPosStart3, curY - ptTextOffset - 1); // right
//
g.setColor(defaultTextColor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImag

CardPanelAttributes adventureAttribs = new CardPanelAttributes(
attribs.cardWidth, attribs.cardHeight, attribs.isChoosable,
attribs.isSelected, true);
attribs.isSelected, true, attribs.isPowerPerpetuallyAffected, attribs.isToughnessPerpetuallyAffected);

// Draw the adventure name line box
g.setPaint(getBoxColor(rightHalf.color, cardView.getCardTypes(), true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@ public class ScryfallImageSupportTokens {
put("AFC/Servo", "https://api.scryfall.com/cards/tafc/11/en?format=image");
put("AFC/Thopter", "https://api.scryfall.com/cards/tafc/12/en?format=image");

// J21
put("J21/Emblem Davriel", "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg");
put("J21/Emblem Teyo", "https://upload.wikimedia.org/wikipedia/en/a/aa/Magic_the_gathering-card_back.jpg");

// MIC
put("MIC/Beast", "https://api.scryfall.com/cards/tmic/7/en?format=image");
put("MIC/Centaur", "https://api.scryfall.com/cards/tmic/8/en?format=image");
Expand Down
44 changes: 43 additions & 1 deletion Mage.Common/src/main/java/mage/view/CardView.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
import mage.abilities.Mode;
import mage.abilities.SpellAbility;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.common.continuous.BoostTargetPerpetuallyEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetPerpetuallyEffect;
import mage.abilities.hint.HintUtils;
import mage.abilities.icon.CardIcon;
import mage.abilities.icon.CardIconImpl;
Expand Down Expand Up @@ -71,6 +74,8 @@ public class CardView extends SimpleCardView {
protected String loyalty = "";
@Expose
protected String defense = "";
protected boolean isPowerPerpetuallyAffected = false;
protected boolean isToughnessPerpetuallyAffected = false;
protected String startingLoyalty;
protected String startingDefense;
protected List<CardType> cardTypes;
Expand Down Expand Up @@ -346,6 +351,37 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st
// TODO: research, why it used here?
this.zone = cardZone;
}
List<ContinuousEffect> perpetualEffects = game.getContinuousEffects().getPerpetuallyEffectsByCard(card, game);
if(perpetualEffects.stream()
.anyMatch(eff -> eff instanceof BoostTargetPerpetuallyEffect
|| eff instanceof SetBasePowerToughnessTargetPerpetuallyEffect)) {

if (perpetualEffects.stream()
.anyMatch(eff -> eff instanceof BoostTargetPerpetuallyEffect
&& ((BoostTargetPerpetuallyEffect) eff).affectsPower(game))) {
this.isPowerPerpetuallyAffected = true;
}


if (perpetualEffects.stream()
.anyMatch(eff -> eff instanceof BoostTargetPerpetuallyEffect
&& ((BoostTargetPerpetuallyEffect) eff).affectsToughness(game))) {
this.isToughnessPerpetuallyAffected = true;
}

if (perpetualEffects.stream()
.anyMatch(eff -> eff instanceof SetBasePowerToughnessTargetPerpetuallyEffect
&& ((SetBasePowerToughnessTargetPerpetuallyEffect) eff).affectsPower())) {
this.isPowerPerpetuallyAffected = true;
}


if (perpetualEffects.stream()
.anyMatch(eff -> eff instanceof SetBasePowerToughnessTargetPerpetuallyEffect
&& ((SetBasePowerToughnessTargetPerpetuallyEffect) eff).affectsToughness())) {
this.isToughnessPerpetuallyAffected = true;
}
}
}

// FACE DOWN
Expand All @@ -372,7 +408,7 @@ public CardView(Card sourceCard, Game game, boolean showAsControlled, boolean st
this.color = card.getColor(null).copy();
this.superTypes = new ArrayList<>(card.getSuperType());
this.subTypes = card.getSubtype().copy();
this.rules = new ArrayList<>(card.getRules());
this.rules = new ArrayList<>(card.getRules(game));
}

// GUI: enable day/night button to view original face up card
Expand Down Expand Up @@ -1219,10 +1255,16 @@ public String getPower() {
return power;
}

public boolean isPowerPerpetuallyAffected() {
return isPowerPerpetuallyAffected;
}
public String getToughness() {
return toughness;
}

public boolean isToughnessPerpetuallyAffected() {
return isToughnessPerpetuallyAffected;
}
public String getLoyalty() {
return loyalty;
}
Expand Down
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/a/ArdenAngel.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public ArdenAngel(UUID ownerId, CardSetInfo setInfo) {

// At the beginning of your upkeep, if Arden Angel is in your graveyard, roll a four-sided die. If the result is 1, return Arden Angel from your graveyard to the battlefield.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new BeginningOfUpkeepTriggeredAbility(new ArdenAngelEffect(), TargetController.YOU, false),
new BeginningOfUpkeepTriggeredAbility(Zone.GRAVEYARD, new ArdenAngelEffect(), TargetController.YOU, false),
SourceInGraveyardCondition.instance, "At the beginning of your upkeep, if {this} is in your graveyard, " +
"roll a four-sided die. If the result is 1, return {this} from your graveyard to the battlefield."
));
Expand Down
42 changes: 42 additions & 0 deletions Mage.Sets/src/mage/cards/b/BafflingDefenses.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package mage.cards.b;

import java.util.UUID;

import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetPerpetuallyEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.game.permanent.token.FrogToken;
import mage.target.common.TargetCreaturePermanent;

/**
*
* @author karapuzz14
*/
public final class BafflingDefenses extends CardImpl {

public BafflingDefenses(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}");


// Target creature's base power perpetually becomes 0.
Effect effect = new SetBasePowerToughnessTargetPerpetuallyEffect(StaticValue.get(0), null);
effect.setText("Target creature's base power perpetually becomes 0");
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addTarget(new TargetCreaturePermanent());

}

private BafflingDefenses(final BafflingDefenses card) {
super(card);
}

@Override
public BafflingDefenses copy() {
return new BafflingDefenses(this);
}
}
56 changes: 56 additions & 0 deletions Mage.Sets/src/mage/cards/b/BenalishPartisan.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package mage.cards.b;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.CycleControllerTriggeredAbility;
import mage.abilities.effects.common.BoostSourcePerpetuallyEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.constants.SubType;
import mage.abilities.keyword.LifelinkAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.keyword.CyclingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;

/**
*
* @author karapuzz14
*/
public final class BenalishPartisan extends CardImpl {

public BenalishPartisan(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}");

this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SOLDIER);
this.power = new MageInt(1);
this.toughness = new MageInt(2);

// Lifelink
this.addAbility(LifelinkAbility.getInstance());

// Whenever you cycle another card, you may pay {1}{W}. If you do, return Benalish Partisan from your graveyard to the battlefield tapped and it perpetually gets +1/+0.
DoIfCostPaid effect = new DoIfCostPaid(
new ReturnSourceFromGraveyardToBattlefieldEffect(true),
new ManaCostsImpl<>("{1}{W}"));
effect.addEffect(new BoostSourcePerpetuallyEffect(1, 0).setText(" and it perpetually gets +1/0"));

this.addAbility(new CycleControllerTriggeredAbility(Zone.GRAVEYARD, effect, false, true));

// Cycling {1}{W}
this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{1}{W}")));

}

private BenalishPartisan(final BenalishPartisan card) {
super(card);
}

@Override
public BenalishPartisan copy() {
return new BenalishPartisan(this);
}
}
44 changes: 44 additions & 0 deletions Mage.Sets/src/mage/cards/b/BloodsproutTalisman.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package mage.cards.b;

import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.ChooseACardInYourHandItPerpetuallyGainsEffect;
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.filter.StaticFilters;

import java.util.UUID;

public class BloodsproutTalisman extends CardImpl {

public BloodsproutTalisman(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{B}{G}");

// Bloodsprout Talisman enters tapped.
this.addAbility(new EntersBattlefieldTappedAbility());

// {T}, Pay 1 life: Choose a nonland card in your hand. It perpetually gains “This spell costs {1} less to cast.”
Ability spellCostReductionAbility = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(1));
Ability ability = new SimpleActivatedAbility(
new ChooseACardInYourHandItPerpetuallyGainsEffect(spellCostReductionAbility, StaticFilters.FILTER_CARD_A_NON_LAND),
new TapSourceCost());
ability.addCost(new PayLifeCost(1));
this.addAbility(ability);
}

private BloodsproutTalisman(final BloodsproutTalisman card) {
super(card);
}

@Override
public BloodsproutTalisman copy() {
return new BloodsproutTalisman(this);
}
}
Loading