Merge pull request #5580 from magefree/5497-dynamic-hints-for-cards

UI: Add dynamic hints for cards
This commit is contained in:
Oleg Agafonov 2019-02-09 14:44:42 +04:00 committed by GitHub
commit c52a3c8a48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
215 changed files with 1840 additions and 1137 deletions

View file

@ -8,6 +8,7 @@ import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.hint.Hint;
import mage.constants.AbilityType;
import mage.constants.AbilityWord;
import mage.constants.EffectType;
@ -525,4 +526,8 @@ public interface Ability extends Controllable, Serializable {
CostAdjuster getCostAdjuster();
void adjustCosts(Game game);
List<Hint> getHints();
Ability addHint(Hint hint);
}

View file

@ -12,6 +12,7 @@ import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.effects.mana.DynamicManaEffect;
import mage.abilities.hint.Hint;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
import mage.cards.SplitCard;
@ -73,6 +74,7 @@ public abstract class AbilityImpl implements Ability {
protected boolean canFizzle = true;
protected TargetAdjuster targetAdjuster = null;
protected CostAdjuster costAdjuster = null;
protected List<Hint> hints = new ArrayList<>();
public AbilityImpl(AbilityType abilityType, Zone zone) {
this.id = UUID.randomUUID();
@ -120,6 +122,9 @@ public abstract class AbilityImpl implements Ability {
this.canFizzle = ability.canFizzle;
this.targetAdjuster = ability.targetAdjuster;
this.costAdjuster = ability.costAdjuster;
for (Hint hint : ability.getHints()) {
this.hints.add(hint.copy());
}
}
@Override
@ -256,7 +261,7 @@ public abstract class AbilityImpl implements Ability {
}
}
if (modes.getAdditionalCost() != null) {
((OptionalAdditionalModeSourceCosts) modes.getAdditionalCost()).addOptionalAdditionalModeCosts(this, game);
modes.getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
}
// 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such
@ -952,9 +957,7 @@ public abstract class AbilityImpl implements Ability {
} else if (!object.getAbilities().contains(this)) {
// check if it's an ability that is temporary gained to a card
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(this.getSourceId());
if (otherAbilities == null || !otherAbilities.contains(this)) {
return false;
}
return otherAbilities != null && otherAbilities.contains(this);
}
}
return true;
@ -1243,4 +1246,15 @@ public abstract class AbilityImpl implements Ability {
costAdjuster.adjustCosts(this, game);
}
}
@Override
public List<Hint> getHints() {
return this.hints;
}
@Override
public Ability addHint(Hint hint) {
this.hints.add(hint);
return this;
}
}

View file

@ -1,17 +1,11 @@
package mage.abilities.condition.common;
import java.util.EnumSet;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.cards.Card;
import mage.constants.CardType;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.game.Game;
import mage.players.Player;
/**
*
*
* @author fireshoes
*/
public enum DeliriumCondition implements Condition {
@ -20,15 +14,7 @@ public enum DeliriumCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
EnumSet<CardType> foundCardTypes = EnumSet.noneOf(CardType.class);
for (Card card : controller.getGraveyard().getCards(game)) {
foundCardTypes.addAll(card.getCardType());
}
return foundCardTypes.size() >= 4;
}
return false;
return CardTypesInGraveyardCount.instance.calculate(game, source, null) >= 4;
}
@Override

View file

@ -0,0 +1,24 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.dynamicvalue.common.OpponentsLostLifeCount;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum OpponentsLostLifeCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return OpponentsLostLifeCount.instance.calculate(game, source.getControllerId()) > 0;
}
@Override
public String toString() {
return "opponents lost life";
}
}

View file

@ -0,0 +1,47 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.constants.CardType;
import mage.game.Game;
import mage.players.Player;
import java.util.EnumSet;
/**
* @author JayDi85
*/
public enum CardTypesInGraveyardCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player controller = game.getPlayer(sourceAbility.getControllerId());
if (controller != null) {
EnumSet<CardType> foundCardTypes = EnumSet.noneOf(CardType.class);
for (Card card : controller.getGraveyard().getCards(game)) {
foundCardTypes.addAll(card.getCardType());
}
return foundCardTypes.size();
}
return 0;
}
@Override
public CardTypesInGraveyardCount copy() {
return instance;
}
@Override
public String toString() {
return "1";
}
@Override
public String getMessage() {
return "the number of opponents you attacked this turn";
}
}

View file

@ -0,0 +1,35 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.filter.StaticFilters;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum CreaturesYouControlCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getBattlefield().count(StaticFilters.FILTER_CONTROLLED_CREATURES, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game);
}
@Override
public CreaturesYouControlCount copy() {
return instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "creatures you control";
}
}

View file

@ -0,0 +1,43 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum GateYouControlCount implements DynamicValue {
instance;
private static final FilterPermanent filter = new FilterControlledPermanent("Gate you control");
static {
filter.add(new SubtypePredicate(SubType.GATE));
}
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game.getBattlefield().count(filter, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game);
}
@Override
public GateYouControlCount copy() {
return instance;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "gate you control";
}
}

View file

@ -1,18 +1,19 @@
package mage.abilities.dynamicvalue.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.watchers.common.PlayerLostLifeWatcher;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class OpponentsLostLifeCount implements DynamicValue {
public enum OpponentsLostLifeCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
@ -29,7 +30,7 @@ public class OpponentsLostLifeCount implements DynamicValue {
@Override
public OpponentsLostLifeCount copy() {
return new OpponentsLostLifeCount();
return instance;
}
@Override

View file

@ -1,7 +1,5 @@
package mage.abilities.effects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.constants.Duration;
import mage.constants.EffectType;
@ -9,8 +7,9 @@ import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public abstract class RestrictionEffect extends ContinuousEffectImpl {
@ -39,6 +38,13 @@ public abstract class RestrictionEffect extends ContinuousEffectImpl {
return true;
}
/**
* @param attacker
* @param defenderId id of planeswalker or player to attack, can be empty for general checks
* @param source
* @param game
* @return
*/
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
return true;
}
@ -47,6 +53,13 @@ public abstract class RestrictionEffect extends ContinuousEffectImpl {
return true;
}
/**
* @param attacker can be empty for general checks
* @param blocker
* @param source
* @param game
* @return
*/
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
return true;
}

View file

@ -1,5 +1,3 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
@ -32,6 +30,9 @@ public class CanBlockOnlyFlyingAttachedEffect extends RestrictionEffect {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return attacker.getAbilities().contains(FlyingAbility.getInstance());
}

View file

@ -1,5 +1,3 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
@ -10,7 +8,6 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
@ -33,6 +30,9 @@ public class CanBlockOnlyFlyingEffect extends RestrictionEffect {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return attacker.getAbilities().contains(FlyingAbility.getInstance());
}

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.AttachmentType;
@ -10,9 +7,10 @@ import mage.constants.Duration;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
@ -34,6 +32,10 @@ public class CantAttackControllerAttachedEffect extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
if (defenderId.equals(source.getControllerId())) {
return false;
}

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Duration;
@ -11,8 +8,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author BursegSardaukar
*/
@ -38,6 +36,10 @@ public class CantAttackIfDefenderControlsPermanent extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
UUID defendingPlayerId;
Player player = game.getPlayer(defenderId);
if (player == null) {

View file

@ -1,8 +1,5 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Duration;
@ -11,8 +8,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author LevelX2
*/
@ -38,6 +36,10 @@ public class CantAttackUnlessDefenderControllsPermanent extends RestrictionEffec
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
UUID defendingPlayerId;
Player player = game.getPlayer(defenderId);
if (player == null) {
@ -50,10 +52,7 @@ public class CantAttackUnlessDefenderControllsPermanent extends RestrictionEffec
} else {
defendingPlayerId = defenderId;
}
if (defendingPlayerId != null && game.getBattlefield().countAll(filter, defendingPlayerId, game) == 0) {
return false;
}
return true;
return defendingPlayerId == null || game.getBattlefield().countAll(filter, defendingPlayerId, game) != 0;
}
@Override

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
@ -13,7 +12,6 @@ import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class CantAttackYouAllEffect extends RestrictionEffect {
@ -51,6 +49,9 @@ public class CantAttackYouAllEffect extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
if (alsoPlaneswalker) {
Permanent planeswalker = game.getPermanent(defenderId);
if (planeswalker != null) {

View file

@ -1,15 +1,14 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author TheElk801
*/
public class CantAttackYouEffect extends RestrictionEffect {
@ -34,6 +33,9 @@ public class CantAttackYouEffect extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
return !defenderId.equals(source.getControllerId());
}
}

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Duration;
@ -10,8 +8,9 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public class CantAttackYouOrPlaneswalkerAllEffect extends RestrictionEffect {
@ -40,6 +39,10 @@ public class CantAttackYouOrPlaneswalkerAllEffect extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
if (defenderId.equals(source.getControllerId())) {
return false;
}

View file

@ -1,18 +1,17 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.AttachmentType;
import mage.constants.Duration;
import static mage.constants.Duration.EndOfTurn;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import static mage.constants.Duration.EndOfTurn;
/**
*
* @author North
*/
public class CantBlockAttachedEffect extends RestrictionEffect {
@ -70,6 +69,9 @@ public class CantBlockAttachedEffect extends RestrictionEffect {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return !filter.match(attacker, source.getSourceId(), source.getControllerId(), game);
}

View file

@ -37,6 +37,9 @@ public class CantBlockCreaturesSourceEffect extends RestrictionEffect {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return !filter.match(attacker, source.getSourceId(), source.getControllerId(), game);
}

View file

@ -0,0 +1,59 @@
package mage.abilities.hint;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.game.Game;
import java.awt.*;
/**
* @author JayDi85
*/
public class ConditionHint implements Hint {
private Condition condition;
private String trueText;
private Color trueColor;
private String falseText;
private Color falseColor;
private Boolean useIcons;
public ConditionHint(Condition condition, String textWithIcons) {
this(condition, textWithIcons, null, textWithIcons, null, true);
}
public ConditionHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) {
this.condition = condition;
this.trueText = trueText;
this.trueColor = trueColor;
this.falseText = falseText;
this.falseColor = falseColor;
this.useIcons = useIcons;
}
private ConditionHint(final ConditionHint hint) {
this.condition = hint.condition;
this.trueText = hint.trueText;
this.trueColor = hint.trueColor;
this.falseText = hint.falseText;
this.falseColor = hint.falseColor;
this.useIcons = hint.useIcons;
}
@Override
public String getText(Game game, Ability ability) {
String icon;
if (condition.apply(game, ability)) {
icon = this.useIcons ? HintUtils.HINT_ICON_GOOD : null;
return HintUtils.prepareText(this.trueText, this.trueColor, icon);
} else {
icon = this.useIcons ? HintUtils.HINT_ICON_BAD : null;
return HintUtils.prepareText(this.falseText, this.falseColor, icon);
}
}
@Override
public Hint copy() {
return new ConditionHint(this);
}
}

View file

@ -0,0 +1,16 @@
package mage.abilities.hint;
import mage.abilities.Ability;
import mage.game.Game;
import java.io.Serializable;
/**
* @author JayDi85
*/
public interface Hint extends Serializable {
String getText(Game game, Ability ability);
Hint copy();
}

View file

@ -0,0 +1,56 @@
package mage.abilities.hint;
import java.awt.*;
import java.util.HashSet;
import java.util.List;
/**
* @author JayDi85
*/
public class HintUtils {
public static final boolean ABILITY_HINTS_ENABLE = true;
public static final boolean RESTRICT_HINTS_ENABLE = true;
// icons changes to real files on client side (see mana icons replacement)
public static final String HINT_ICON_GOOD = "ICON_GOOD";
public static final String HINT_ICON_BAD = "ICON_BAD";
public static final String HINT_ICON_RESTRICT = "ICON_RESTRICT";
//
public static final String HINT_START_MARK = "<br/><hintstart/>"; // workaround to find hint text in rules list and shows it in html
public static String prepareText(String text, Color color) {
return prepareText(text, color, null);
}
public static String prepareText(String text, Color color, String icon) {
String res;
// text
if (text != null && color != null) {
String hex = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getGreen());
res = String.format("<font color=%s>%s</font>", hex, text);
} else {
res = text;
}
// icon
if (res != null && icon != null) {
res = icon + res;
}
return res;
}
public static void appendHints(List<String> destList, List<String> newHints) {
// append only unique hints
HashSet<String> used = new HashSet<>();
for (String s : newHints) {
if (!used.contains(s)) {
destList.add(s);
used.add(s);
}
}
}
}

View file

@ -0,0 +1,36 @@
package mage.abilities.hint;
import mage.abilities.Ability;
import mage.game.Game;
import java.awt.*;
/**
* @author JayDi85
*/
public class StaticHint implements Hint {
private String text;
public StaticHint(String text) {
this(text, null);
}
public StaticHint(String text, Color color) {
this.text = HintUtils.prepareText(text, color);
}
private StaticHint(final StaticHint hint) {
this.text = hint.text;
}
@Override
public String getText(Game game, Ability ability) {
return text;
}
@Override
public Hint copy() {
return new StaticHint(this);
}
}

View file

@ -0,0 +1,34 @@
package mage.abilities.hint;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.game.Game;
/**
* @author JayDi85
*/
public class ValueHint implements Hint {
private String name;
private DynamicValue value;
public ValueHint(String name, DynamicValue value) {
this.name = name;
this.value = value;
}
private ValueHint(final ValueHint hint) {
this.name = hint.name;
this.value = hint.value.copy();
}
@Override
public String getText(Game game, Ability ability) {
return name + ": " + value.calculate(game, ability, null);
}
@Override
public Hint copy() {
return new ValueHint(this);
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.CitysBlessingCondition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum CitysBlessingHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(CitysBlessingCondition.instance, "You have city's blessing");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum CreaturesYouControlHint implements Hint {
instance;
private static final Hint hint = new ValueHint("Creatures you control", CreaturesYouControlCount.instance);
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,27 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.DeliriumCondition;
import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum DeliriumHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(DeliriumCondition.instance, "4+ card types in your graveyard");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability) + " (current: " + CardTypesInGraveyardCount.instance.calculate(game, ability, null) + ")";
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.FerociousCondition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum FerociousHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(FerociousCondition.instance, "You control a creature with power 4+");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,27 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.GateYouControlCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum GateYouControlHint implements Hint {
instance;
private static final Hint hint = new ValueHint("Gate you control", GateYouControlCount.instance);
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.OpponentsLostLifeCondition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum SpectacleHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(OpponentsLostLifeCondition.instance, "Opponents lost life this turn");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -1,6 +1,5 @@
package mage.abilities.keyword;
import java.io.ObjectStreamException;
import mage.abilities.Ability;
import mage.abilities.EvasionAbility;
import mage.abilities.MageSingleton;
@ -10,6 +9,8 @@ import mage.constants.Duration;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.io.ObjectStreamException;
/**
* "Shadow" keyword
*
@ -60,6 +61,9 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return attacker.getAbilities().containsKey(ShadowAbility.getInstance().getId());
}

View file

@ -1,7 +1,5 @@
package mage.abilities.keyword;
import java.io.ObjectStreamException;
import mage.abilities.Ability;
import mage.abilities.EvasionAbility;
import mage.abilities.MageSingleton;
@ -10,8 +8,9 @@ import mage.constants.Duration;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.io.ObjectStreamException;
/**
*
* @author Styxo
*/
public class SpaceflightAbility extends EvasionAbility implements MageSingleton {
@ -59,6 +58,9 @@ class SpaceFlightEffect extends RestrictionEffect implements MageSingleton {
@Override
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (attacker == null) {
return true;
}
return attacker.getAbilities().containsKey(SpaceflightAbility.getInstance().getId());
}

View file

@ -1,13 +1,13 @@
package mage.abilities.keyword;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.dynamicvalue.common.OpponentsLostLifeCount;
import mage.abilities.hint.common.SpectacleHint;
import mage.cards.Card;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.watchers.common.PlayerLostLifeWatcher;
import java.util.ArrayList;
import java.util.UUID;
@ -31,6 +31,7 @@ public class SpectacleAbility extends SpellAbility {
this.setRuleAtTheTop(true);
this.rule = "Spectacle " + spectacleCosts.getText()
+ " <i>(You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn.)</i>";
this.addHint(SpectacleHint.instance);
}
public SpectacleAbility(final SpectacleAbility ability) {
@ -40,8 +41,7 @@ public class SpectacleAbility extends SpellAbility {
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
PlayerLostLifeWatcher watcher = game.getState().getWatcher(PlayerLostLifeWatcher.class);
if (watcher != null && watcher.getAllOppLifeLost(playerId, game) > 0) {
if (OpponentsLostLifeCount.instance.calculate(game, playerId) > 0) {
return super.canActivate(playerId, game);
}
return ActivationStatus.getFalse();

View file

@ -5,6 +5,8 @@ import mage.MageObjectImpl;
import mage.Mana;
import mage.ObjectColor;
import mage.abilities.*;
import mage.abilities.hint.Hint;
import mage.abilities.hint.HintUtils;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.repository.PluginClassloaderRegistery;
import mage.constants.*;
@ -243,6 +245,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
try {
List<String> rules = getRules();
if (game != null) {
// debug state
CardState cardState = game.getState().getCardState(objectId);
if (cardState != null) {
for (String data : cardState.getInfo().values()) {
@ -252,6 +255,27 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
rules.add(ability.getRule());
}
}
// ability hints
List<String> abilityHints = new ArrayList<>();
if (HintUtils.ABILITY_HINTS_ENABLE) {
for (Ability ability : abilities) {
for (Hint hint : ability.getHints()) {
String s = hint.getText(game, ability);
if (s != null && !s.isEmpty()) {
abilityHints.add(s);
}
}
}
}
// restrict hints only for permanents, not cards
// total hints
if (!abilityHints.isEmpty()) {
rules.add(HintUtils.HINT_START_MARK);
HintUtils.appendHints(rules, abilityHints);
}
}
return rules;
} catch (Exception e) {
@ -486,7 +510,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
}
}
if (lkiObject != null) {
removed = game.getState().getCommand().remove((CommandObject) lkiObject);
removed = game.getState().getCommand().remove(lkiObject);
}
break;
case OUTSIDE:

View file

@ -1,9 +1,5 @@
package mage.game.command.planes;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
@ -33,8 +29,11 @@ import mage.target.Target;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.PlanarRollWatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author spjspj
*/
public class AgyremPlane extends Plane {
@ -152,6 +151,10 @@ class AgyremRestrictionEffect extends RestrictionEffect {
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) {
if (defenderId == null) {
return true;
}
Plane cPlane = game.getState().getCurrentPlane();
if (cPlane != null && cPlane.getName().equalsIgnoreCase("Plane - Agyrem")) {
return !defenderId.equals(source.getControllerId());

View file

@ -8,6 +8,8 @@ import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.HintUtils;
import mage.abilities.keyword.*;
import mage.abilities.text.TextPart;
import mage.cards.Card;
@ -236,17 +238,83 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
public List<String> getRules(Game game) {
try {
List<String> rules = getRules();
// info
if (info != null) {
for (String data : info.values()) {
rules.add(data);
}
}
// ability hints
List<String> abilityHints = new ArrayList<>();
if (HintUtils.ABILITY_HINTS_ENABLE) {
for (Ability ability : abilities) {
for (Hint hint : ability.getHints()) {
String s = hint.getText(game, ability);
if (s != null && !s.isEmpty()) {
abilityHints.add(s);
}
}
}
}
// restrict hints
List<String> restrictHints = new ArrayList<>();
if (HintUtils.RESTRICT_HINTS_ENABLE) {
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) {
for (Ability ability : entry.getValue()) {
if (!entry.getKey().applies(this, ability, game)) {
continue;
}
if (!entry.getKey().canAttack(game) || !entry.getKey().canAttack(this, null, ability, game)) {
restrictHints.add(HintUtils.prepareText("Can't attack" + addSourceObjectName(game, ability), null, HintUtils.HINT_ICON_RESTRICT));
}
if (!entry.getKey().canBlock(null, this, ability, game)) {
restrictHints.add(HintUtils.prepareText("Can't block" + addSourceObjectName(game, ability), null, HintUtils.HINT_ICON_RESTRICT));
}
if (!entry.getKey().canBeUntapped(this, ability, game)) {
restrictHints.add(HintUtils.prepareText("Can't untapped" + addSourceObjectName(game, ability), null, HintUtils.HINT_ICON_RESTRICT));
}
if (!entry.getKey().canUseActivatedAbilities(this, ability, game)) {
restrictHints.add(HintUtils.prepareText("Can't use activated abilities" + addSourceObjectName(game, ability), null, HintUtils.HINT_ICON_RESTRICT));
}
if (!entry.getKey().canTransform(this, ability, game)) {
restrictHints.add(HintUtils.prepareText("Can't transform" + addSourceObjectName(game, ability), null, HintUtils.HINT_ICON_RESTRICT));
}
}
}
restrictHints.sort(String::compareTo);
}
// total hints
if (!abilityHints.isEmpty() || !restrictHints.isEmpty()) {
rules.add(HintUtils.HINT_START_MARK);
HintUtils.appendHints(rules, abilityHints);
HintUtils.appendHints(rules, restrictHints);
}
return rules;
} catch (Exception e) {
return rulesError;
}
}
private String addSourceObjectName(Game game, Ability ability) {
if (ability != null) {
MageObject object = game.getObject(ability.getSourceId());
if (object != null) {
return " (" + object.getIdName() + ")";
}
}
return "";
}
@Override
public Abilities<Ability> getAbilities() {
return abilities;
@ -477,7 +545,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
public boolean phaseIn(Game game, boolean onlyDirect) {
if (!phasedIn) {
if (!replaceEvent(EventType.PHASE_IN, game)
&& ((onlyDirect && !indirectPhase) || (!onlyDirect))) {
&& (!onlyDirect || !indirectPhase)) {
this.phasedIn = true;
this.indirectPhase = false;
if (!game.isSimulation()) {
@ -768,7 +836,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
sourceControllerId = ((Card) source).getOwnerId();
} else if (source instanceof CommandObject) {
sourceControllerId = ((CommandObject) source).getControllerId();
sourceAbilities = ((CommandObject) source).getAbilities();
sourceAbilities = source.getAbilities();
} else {
source = null;
}
@ -969,9 +1037,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
// needed to get the correct possible targets if target rule modification effects are active
// e.g. Fiendslayer Paladin tried to target with Ultimate Price
if (game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(EventType.TARGET, this.getId(), source.getId(), sourceControllerId), null, game, true)) {
return false;
}
return !game.getContinuousEffects().preventedByRuleModification(GameEvent.getEvent(EventType.TARGET, this.getId(), source.getId(), sourceControllerId), null, game, true);
}
return true;

View file

@ -1,18 +1,15 @@
package mage.game.permanent.token;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
/**
*
* @author spjspj
*/
public final class VoiceOfResurgenceToken extends TokenImpl {
@ -28,8 +25,9 @@ public final class VoiceOfResurgenceToken extends TokenImpl {
power = new MageInt(0);
toughness = new MageInt(0);
// This creature's power and toughness are each equal to the number of creatures you control.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetPowerToughnessSourceEffect(
new PermanentsOnBattlefieldCount(new FilterControlledCreaturePermanent()), Duration.EndOfGame)));
CreaturesYouControlCount.instance, Duration.EndOfGame)));
}
public VoiceOfResurgenceToken(final VoiceOfResurgenceToken token) {

View file

@ -13,6 +13,7 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.hint.Hint;
import mage.abilities.text.TextPart;
import mage.cards.Card;
import mage.cards.FrameStyle;
@ -647,4 +648,15 @@ public class StackAbility extends StackObjImpl implements Ability {
costAdjuster.adjustCosts(this, game);
}
}
@Override
public List<Hint> getHints() {
return this.ability.getHints();
}
@Override
public Ability addHint(Hint hint) {
// only abilities supports addhint
return null;
}
}

View file

@ -1,9 +1,5 @@
package mage.players;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import mage.ConditionalMana;
import mage.MageObject;
import mage.MageObjectReference;
@ -69,6 +65,11 @@ import mage.util.GameLog;
import mage.util.RandomUtil;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
public abstract class PlayerImpl implements Player, Serializable {
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
@ -1422,10 +1423,10 @@ public abstract class PlayerImpl implements Player, Serializable {
!= null
// if anyone sees an issue with this code, please report it. Worked in my testing.
|| game.getContinuousEffects().asThough(object.getId(),
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE,
ability,
this.getId(),
game)
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE,
ability,
this.getId(),
game)
!= null) {
if (canUse
|| ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
@ -2527,7 +2528,7 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Card card : library.getCards(game)) {
for (Ability ability : card.getAbilities()) {
if (ability.getClass() == WhileSearchingPlayFromLibraryAbility.class) {
libraryCastableCardTracker.put(card.getId(), card.getName() + " [" + card.getId().toString().substring(0, 3) + "]");
libraryCastableCardTracker.put(card.getId(), card.getIdName());
}
}
}
@ -2636,7 +2637,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param game
* @param appliedEffects
* @param numSides Number of sides the dice has
* @param numSides Number of sides the dice has
* @return the number that the player rolled
*/
@Override
@ -2670,10 +2671,10 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param game
* @param appliedEffects
* @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5)
* @param numberChaosSides The number of chaos sides the planar die
* currently has (normally 1 but can be 5)
* @param numberPlanarSides The number of chaos sides the planar die
* currently has (normally 1)
* currently has (normally 1)
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
* or NilRoll
*/
@ -2830,7 +2831,7 @@ public abstract class PlayerImpl implements Player, Serializable {
/**
* @param ability
* @param available if null, it won't be checked if enough mana is available
* @param available if null, it won't be checked if enough mana is available
* @param sourceObject
* @param game
* @return
@ -3380,7 +3381,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
UUID controllerId, Game game
UUID controllerId, Game game
) {
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
}
@ -3528,8 +3529,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCards(Card card, Zone toZone,
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) {
Set<Card> cardList = new HashSet<>();
if (card != null) {
@ -3540,22 +3541,22 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCards(Cards cards, Zone toZone,
Ability source, Game game
Ability source, Game game
) {
return moveCards(cards.getCards(game), toZone, source, game);
}
@Override
public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game
Ability source, Game game
) {
return moveCards(cards, toZone, source, game, false, false, false, null);
}
@Override
public boolean moveCards(Set<Card> cards, Zone toZone,
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
Ability source, Game game,
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
) {
if (cards.isEmpty()) {
return true;
@ -3641,8 +3642,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardsToExile(Card card, Ability source,
Game game, boolean withName, UUID exileId,
String exileZoneName
Game game, boolean withName, UUID exileId,
String exileZoneName
) {
Set<Card> cards = new HashSet<>();
cards.add(card);
@ -3651,8 +3652,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardsToExile(Set<Card> cards, Ability source,
Game game, boolean withName, UUID exileId,
String exileZoneName
Game game, boolean withName, UUID exileId,
String exileZoneName
) {
if (cards.isEmpty()) {
return true;
@ -3667,14 +3668,14 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game
Game game
) {
return this.moveCardToHandWithInfo(card, sourceId, game, true);
}
@Override
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
Game game, boolean withName
Game game, boolean withName
) {
boolean result = false;
Zone fromZone = game.getState().getZone(card.getId());
@ -3699,7 +3700,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
Game game, Zone fromZone
Game game, Zone fromZone
) {
UUID sourceId = source == null ? null : source.getSourceId();
Set<Card> movedCards = new LinkedHashSet<>();
@ -3707,7 +3708,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// identify cards from one owner
Cards cards = new CardsImpl();
UUID ownerId = null;
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
Card card = it.next();
if (cards.isEmpty()) {
ownerId = card.getOwnerId();
@ -3768,7 +3769,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone
Game game, Zone fromZone
) {
if (card == null) {
return false;
@ -3797,8 +3798,8 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
Game game, Zone fromZone,
boolean toTop, boolean withName
Game game, Zone fromZone,
boolean toTop, boolean withName
) {
if (card == null) {
return false;
@ -3832,7 +3833,7 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
Game game, Zone fromZone, boolean withName) {
Game game, Zone fromZone, boolean withName) {
if (card == null) {
return false;
}

View file

@ -1,11 +1,9 @@
package mage.util;
import mage.MageObject;
import mage.ObjectColor;
/**
*
* @author LevelX2
*/
public final class GameLog {
@ -40,11 +38,11 @@ public final class GameLog {
}
public static String getColoredObjectIdName(MageObject mageObject) {
return "<font color=\'" + getColorName(mageObject.getColor(null)) + "\'>" + mageObject.getName() + " [" + mageObject.getId().toString().substring(0, 3) + "]</font>";
return "<font color=\'" + getColorName(mageObject.getColor(null)) + "\'>" + mageObject.getIdName() + "</font>";
}
public static String getColoredObjectIdNameForTooltip(MageObject mageObject) {
return "<font color=\'" + getTooltipColorName(mageObject.getColor(null)) + "\'>" + mageObject.getName() + " [" + mageObject.getId().toString().substring(0, 3) + "]</font>";
return "<font color=\'" + getTooltipColorName(mageObject.getColor(null)) + "\'>" + mageObject.getIdName() + "</font>";
}
public static String getNeutralColoredText(String text) {