Merge branch 'fix' into 'master'

Simulation: fix for Pack Rat tokens (bad diff because of missing CMC)

See merge request core-developers/forge!5980
This commit is contained in:
Michael Kamensky
2021-12-26 15:33:17 +00:00
19 changed files with 119 additions and 136 deletions

View File

@@ -514,9 +514,8 @@ public class AiAttackController {
if (Iterables.any(oppBattlefield, Predicates.and(CardPredicates.Presets.UNTAPPED, CardPredicates.Presets.LANDS))) { if (Iterables.any(oppBattlefield, Predicates.and(CardPredicates.Presets.UNTAPPED, CardPredicates.Presets.LANDS))) {
maxBlockersAfterCrew = Integer.MAX_VALUE; maxBlockersAfterCrew = Integer.MAX_VALUE;
break; break;
} else {
maxBlockersAfterCrew--;
} }
maxBlockersAfterCrew--;
} else if (cardType.hasSubtype("Vehicle") && !cardType.isCreature()) { } else if (cardType.hasSubtype("Vehicle") && !cardType.isCreature()) {
maxBlockersAfterCrew--; maxBlockersAfterCrew--;
} }

View File

@@ -2436,7 +2436,7 @@ public class ComputerUtil {
CardCollection cardsInPlay = CardLists.getNotType(game.getCardsIn(ZoneType.Battlefield), "Land"); CardCollection cardsInPlay = CardLists.getNotType(game.getCardsIn(ZoneType.Battlefield), "Land");
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents()); CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
CardCollection computerlist = ai.getCreaturesInPlay(); CardCollection computerlist = ai.getCreaturesInPlay();
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage"; return ComputerUtilCard.evaluatePermanentList(computerlist) + 3 < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
case "Judgment": case "Judgment":
if (votes.isEmpty()) { if (votes.isEmpty()) {
CardCollection list = new CardCollection(); CardCollection list = new CardCollection();
@@ -2446,9 +2446,8 @@ public class ComputerUtil {
} }
} }
return ComputerUtilCard.getBestAI(list); return ComputerUtilCard.getBestAI(list);
} else {
return Iterables.getFirst(votes.keySet(), null);
} }
return Iterables.getFirst(votes.keySet(), null);
case "Protection": case "Protection":
if (votes.isEmpty()) { if (votes.isEmpty()) {
List<String> restrictedToColors = Lists.newArrayList(); List<String> restrictedToColors = Lists.newArrayList();
@@ -2459,9 +2458,8 @@ public class ComputerUtil {
} }
CardCollection lists = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents()); CardCollection lists = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
return StringUtils.capitalize(ComputerUtilCard.getMostProminentColor(lists, restrictedToColors)); return StringUtils.capitalize(ComputerUtilCard.getMostProminentColor(lists, restrictedToColors));
} else {
return Iterables.getFirst(votes.keySet(), null);
} }
return Iterables.getFirst(votes.keySet(), null);
case "FeatherOrQuill": case "FeatherOrQuill":
// try to mill opponent with Quill vote // try to mill opponent with Quill vote
if (opponent && !controller.cantLose()) { if (opponent && !controller.cantLose()) {

View File

@@ -2292,9 +2292,8 @@ public class ComputerUtilCombat {
} }
if (!withoutAbilities) { if (!withoutAbilities) {
return canGainKeyword(combatant, Lists.newArrayList(keyword), combat); return canGainKeyword(combatant, Lists.newArrayList(keyword), combat);
} else {
return false;
} }
return false;
} }
public final static boolean canGainKeyword(final Card combatant, final List<String> keywords, final Combat combat) { public final static boolean canGainKeyword(final Card combatant, final List<String> keywords, final Combat combat) {

View File

@@ -306,52 +306,50 @@ public class ComputerUtilMana {
continue; continue;
} }
if (saHost != null) { if (ma.getPayCosts().hasTapCost() && AiCardMemory.isRememberedCard(ai, ma.getHostCard(), MemorySet.PAYS_TAP_COST)) {
if (ma.getPayCosts().hasTapCost() && AiCardMemory.isRememberedCard(ai, ma.getHostCard(), MemorySet.PAYS_TAP_COST)) { continue;
}
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa)) {
continue;
}
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) {
continue;
}
if (sa.getApi() == ApiType.Animate) {
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
&& ma.getHostCard() == saHost.getEnchantingCard()
&& ma.getPayCosts().hasTapCost()) {
continue; continue;
} }
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa)) { // If a manland was previously animated this turn, do not tap it to animate another manland
if (saHost.isLand() && ma.getHostCard().isLand()
&& ai.getController().isAI()
&& AnimateAi.isAnimatedThisTurn(ai, ma.getHostCard())) {
continue; continue;
} }
} else if (sa.getApi() == ApiType.Pump) {
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) { if ((saHost.isInstant() || saHost.isSorcery())
&& ma.getHostCard().isCreature()
&& ai.getController().isAI()
&& ma.getPayCosts().hasTapCost()
&& sa.getTargets().getTargetCards().contains(ma.getHostCard())) {
// do not activate pump instants/sorceries targeting creatures by tapping targeted
// creatures for mana (for example, Servant of the Conduit)
continue; continue;
} }
} else if (sa.getApi() == ApiType.Attach
if (sa.getApi() == ApiType.Animate) { && "AvoidPayingWithAttachTarget".equals(saHost.getSVar("AIPaymentPreference"))) {
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana // For cards like Genju of the Cedars, make sure we're not attaching to the same land that will
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined")) // be tapped to pay its own cost if there's another untapped land like that available
&& ma.getHostCard() == saHost.getEnchantingCard() if (ma.getHostCard().equals(sa.getTargetCard())) {
&& ma.getPayCosts().hasTapCost()) { if (CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.nameEquals(ma.getHostCard().getName()), CardPredicates.Presets.UNTAPPED)).size() > 1) {
continue; continue;
} }
// If a manland was previously animated this turn, do not tap it to animate another manland
if (saHost.isLand() && ma.getHostCard().isLand()
&& ai.getController().isAI()
&& AnimateAi.isAnimatedThisTurn(ai, ma.getHostCard())) {
continue;
}
} else if (sa.getApi() == ApiType.Pump) {
if ((saHost.isInstant() || saHost.isSorcery())
&& ma.getHostCard().isCreature()
&& ai.getController().isAI()
&& ma.getPayCosts().hasTapCost()
&& sa.getTargets().getTargetCards().contains(ma.getHostCard())) {
// do not activate pump instants/sorceries targeting creatures by tapping targeted
// creatures for mana (for example, Servant of the Conduit)
continue;
}
} else if (sa.getApi() == ApiType.Attach
&& "AvoidPayingWithAttachTarget".equals(saHost.getSVar("AIPaymentPreference"))) {
// For cards like Genju of the Cedars, make sure we're not attaching to the same land that will
// be tapped to pay its own cost if there's another untapped land like that available
if (ma.getHostCard().equals(sa.getTargetCard())) {
if (CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.nameEquals(ma.getHostCard().getName()), CardPredicates.Presets.UNTAPPED)).size() > 1) {
continue;
}
}
} }
} }

View File

@@ -1023,9 +1023,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
if (blink) { if (blink) {
return c.isToken(); return c.isToken();
} else {
return c.isToken() || c.getCMC() > 0;
} }
return c.isToken() || c.getCMC() > 0;
} }
}); });
} }

View File

@@ -315,7 +315,6 @@ public class ControlGainAi extends SpellAbilityAi {
} else { } else {
return this.canPlayAI(ai, sa); return this.canPlayAI(ai, sa);
} }
} // pumpDrawbackAI() } // pumpDrawbackAI()
@Override @Override

View File

@@ -153,10 +153,9 @@ public abstract class DamageAiBase extends SpellAbilityAi {
} }
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
return false; return false;
} else {
final float chance = MyRandom.getRandom().nextFloat();
return chance < value;
} }
final float chance = MyRandom.getRandom().nextFloat();
return chance < value;
} }
} }

View File

@@ -811,9 +811,8 @@ public class DamageDealAi extends DamageAiBase {
if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && ComputerUtilCombat.getDamageToKill(c, false) <= restDamage) { if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && ComputerUtilCombat.getDamageToKill(c, false) <= restDamage) {
if (c.getController().equals(ai)) { if (c.getController().equals(ai)) {
return false; return false;
} else {
urgent = true;
} }
urgent = true;
} }
if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) { if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) {
positive = true; positive = true;

View File

@@ -198,7 +198,7 @@ public class EffectAi extends SpellAbilityAi {
final Card host = saTop.getHostCard(); final Card host = saTop.getHostCard();
if (saTop.getActivatingPlayer() != ai // from opponent if (saTop.getActivatingPlayer() != ai // from opponent
&& host.canDamagePrevented(false) // no prevent damage && host.canDamagePrevented(false) // no prevent damage
&& host != null && (host.isInstant() || host.isSorcery()) && (host.isInstant() || host.isSorcery())
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target && !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
final ApiType type = saTop.getApi(); final ApiType type = saTop.getApi();
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell

View File

@@ -286,12 +286,11 @@ public class FightAi extends SpellAbilityAi {
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) { private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
if (canKill(fighter, opponent, pumpAttack)) { if (canKill(fighter, opponent, pumpAttack)) {
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
return true; return true;
} else { }
if (MyRandom.getRandom().nextInt(20) < (opponent.getCMC() - fighter.getCMC())) { // trade if (MyRandom.getRandom().nextInt(20) < (opponent.getCMC() - fighter.getCMC())) { // trade
return true; return true;
} }
}
} }
return false; return false;
} }

View File

@@ -233,6 +233,10 @@ public class GameCopier {
if (card.isPaired()) { if (card.isPaired()) {
otherCard.setPairedWith(cardMap.get(card.getPairedWith())); otherCard.setPairedWith(cardMap.get(card.getPairedWith()));
} }
if (card.getCopiedPermanent() != null) {
// TODO would it be safe to simply reuse the prototype?
otherCard.setCopiedPermanent(CardFactory.copyCard(card.getCopiedPermanent(), false));
}
// TODO: Verify that the above relationships are preserved bi-directionally or not. // TODO: Verify that the above relationships are preserved bi-directionally or not.
} }
} }

View File

@@ -547,9 +547,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
public String getSVar(final String name) { public String getSVar(final String name) {
if (sVars.containsKey(name)) { if (sVars.containsKey(name)) {
return sVars.get(name); return sVars.get(name);
} else {
return getSVarFallback().getSVar(name);
} }
return getSVarFallback().getSVar(name);
} }
@Override @Override

View File

@@ -1422,7 +1422,7 @@ public class AbilityUtils {
// do blessing there before condition checks // do blessing there before condition checks
if (sa.isSpell() && sa.isBlessing() && !sa.getHostCard().isPermanent()) { if (sa.isSpell() && sa.isBlessing() && !sa.getHostCard().isPermanent()) {
if (pl != null && pl.getZone(ZoneType.Battlefield).size() >= 10) { if (pl.getZone(ZoneType.Battlefield).size() >= 10) {
pl.setBlessing(true); pl.setBlessing(true);
} }
} }
@@ -1871,9 +1871,8 @@ public class AbilityUtils {
list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa);
if (k[0].contains("TotalToughness")) { if (k[0].contains("TotalToughness")) {
return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb); return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb);
} else {
return doXMath(list.size(), expr, c, ctb);
} }
return doXMath(list.size(), expr, c, ctb);
} }
if (sq[0].startsWith("LastStateGraveyard")) { if (sq[0].startsWith("LastStateGraveyard")) {
@@ -3208,9 +3207,8 @@ public class AbilityUtils {
} else if (s[0].contains("DivideEvenlyDown")) { } else if (s[0].contains("DivideEvenlyDown")) {
if (secondaryNum == 0) { if (secondaryNum == 0) {
return 0; return 0;
} else {
return num / secondaryNum;
} }
return num / secondaryNum;
} else if (s[0].contains("Mod")) { } else if (s[0].contains("Mod")) {
return num % secondaryNum; return num % secondaryNum;
} else if (s[0].contains("Abs")) { } else if (s[0].contains("Abs")) {
@@ -3218,15 +3216,13 @@ public class AbilityUtils {
} else if (s[0].contains("LimitMax")) { } else if (s[0].contains("LimitMax")) {
if (num < secondaryNum) { if (num < secondaryNum) {
return num; return num;
} else {
return secondaryNum;
} }
return secondaryNum;
} else if (s[0].contains("LimitMin")) { } else if (s[0].contains("LimitMin")) {
if (num > secondaryNum) { if (num > secondaryNum) {
return num; return num;
} else {
return secondaryNum;
} }
return secondaryNum;
} else { } else {
return num; return num;

View File

@@ -371,8 +371,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false; return false;
} }
public long getTransformedTimestamp() { return transformedTimestamp; } public long getTransformedTimestamp() { return transformedTimestamp; }
public void incrementTransformedTimestamp() { this.transformedTimestamp++; } public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
public CardState getCurrentState() { public CardState getCurrentState() {
return currentState; return currentState;
@@ -3037,7 +3037,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return tokenCard; return tokenCard;
} }
public final void setTokenCard(boolean tokenC) { public final void setTokenCard(boolean tokenC) {
if (tokenCard = tokenC) { return; } if (tokenCard == tokenC) { return; }
tokenCard = tokenC; tokenCard = tokenC;
view.updateTokenCard(this); view.updateTokenCard(this);
} }
@@ -5232,9 +5232,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// this is the amount of damage a creature needs to receive before it dies // this is the amount of damage a creature needs to receive before it dies
public final int getLethal() { public final int getLethal() {
if (hasKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.")) { if (hasKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.")) {
return getNetPower(); } return getNetPower();
else { }
return getNetToughness(); } return getNetToughness();
} }
// this is the minimal damage a trampling creature has to assign to a blocker // this is the minimal damage a trampling creature has to assign to a blocker

View File

@@ -185,7 +185,6 @@ public class TokenInfo {
} }
} }
if (!typeMap.isEmpty()) { if (!typeMap.isEmpty()) {
CardType type = new CardType(result.getType()); CardType type = new CardType(result.getType());
final boolean nameGenerated = result.getName().endsWith(" Token"); final boolean nameGenerated = result.getName().endsWith(" Token");
boolean typeChanged = false; boolean typeChanged = false;

View File

@@ -135,56 +135,54 @@ public class CostDiscard extends CostPartWithList {
if (this.payCostFromSource()) { if (this.payCostFromSource()) {
return source.canBeDiscardedBy(ability, effect); return source.canBeDiscardedBy(ability, effect);
} }
else if (type.equals("Hand")) {
// trying to discard an empty hand always work even with Tamiyo
if (payer.getZone(ZoneType.Hand).isEmpty()) {
return true;
}
return payer.canDiscardBy(ability, effect);
// this will always work
}
else if (type.equals("LastDrawn")) {
final Card c = payer.getLastDrawnCard();
return handList.contains(c);
}
else if (type.equals("DifferentNames")) {
Set<String> cardNames = Sets.newHashSet();
for (Card c : handList) {
cardNames.add(c.getName());
}
return cardNames.size() >= amount;
}
else { else {
if (type.equals("Hand")) { boolean sameName = false;
// trying to discard an empty hand always work even with Tamiyo if (type.contains("+WithSameName")) {
if (payer.getZone(ZoneType.Hand).isEmpty()) { sameName = true;
return true; type = TextUtil.fastReplace(type, "+WithSameName", "");
}
return payer.canDiscardBy(ability, effect);
// this will always work
} }
else if (type.equals("LastDrawn")) { if (!type.equals("Random") && !type.contains("X")) {
final Card c = payer.getLastDrawnCard(); // Knollspine Invocation fails to activate without the above conditional
return handList.contains(c); handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
} }
else if (type.equals("DifferentNames")) { if (sameName) {
Set<String> cardNames = Sets.newHashSet(); for (Card c : handList) {
for (Card c : handList) { if (CardLists.filter(handList, CardPredicates.nameEquals(c.getName())).size() > 1) {
cardNames.add(c.getName()); return true;
}
return cardNames.size() >= amount;
}
else {
boolean sameName = false;
if (type.contains("+WithSameName")) {
sameName = true;
type = TextUtil.fastReplace(type, "+WithSameName", "");
}
if (!type.equals("Random") && !type.contains("X")) {
// Knollspine Invocation fails to activate without the above conditional
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
}
if (sameName) {
for (Card c : handList) {
if (CardLists.filter(handList, CardPredicates.nameEquals(c.getName())).size() > 1) {
return true;
}
}
return false;
}
int adjustment = 0;
if (source.isInZone(ZoneType.Hand) && payer.equals(source.getOwner())) {
// If this card is in my hand, I can't use it to pay for it's own cost
if (handList.contains(source)) {
adjustment = 1;
} }
} }
return false;
}
int adjustment = 0;
if (source.isInZone(ZoneType.Hand) && payer.equals(source.getOwner())) {
// If this card is in my hand, I can't use it to pay for it's own cost
if (handList.contains(source)) {
adjustment = 1;
}
}
if (amount > handList.size() - adjustment) { if (amount > handList.size() - adjustment) {
// not enough cards in hand to pay // not enough cards in hand to pay
return false; return false;
}
} }
} }
return true; return true;

View File

@@ -148,13 +148,12 @@ public class CostPartMana extends CostPart {
int timesToPay = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getSVar("NumTimes"), sa); int timesToPay = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getSVar("NumTimes"), sa);
if (timesToPay == 0) { if (timesToPay == 0) {
return null; return null;
} else {
ManaCostBeingPaid totalMana = new ManaCostBeingPaid(getManaToPay());
for (int i = 1; i < timesToPay; i++) {
totalMana.addManaCost(getManaToPay());
}
return totalMana.toManaCost();
} }
ManaCostBeingPaid totalMana = new ManaCostBeingPaid(getManaToPay());
for (int i = 1; i < timesToPay; i++) {
totalMana.addManaCost(getManaToPay());
}
return totalMana.toManaCost();
} }
return getManaToPay(); return getManaToPay();
} }

View File

@@ -434,8 +434,8 @@ public class ReplacementHandler {
private void getPossibleReplaceDamageList(PlayerCollection players, final boolean isCombat, final CardDamageMap damageMap, final SpellAbility cause) { private void getPossibleReplaceDamageList(PlayerCollection players, final boolean isCombat, final CardDamageMap damageMap, final SpellAbility cause) {
for (Map.Entry<GameEntity, Map<Card, Integer>> et : damageMap.columnMap().entrySet()) { for (Map.Entry<GameEntity, Map<Card, Integer>> et : damageMap.columnMap().entrySet()) {
final GameEntity target = et.getKey(); final GameEntity target = et.getKey();
int playerIndex = (target instanceof Player ? players.indexOf(((Player) target)) : int playerIndex = target instanceof Player ? players.indexOf(((Player) target)) :
players.indexOf(((Card) target).getController())); players.indexOf(((Card) target).getController());
if (playerIndex == -1) continue; if (playerIndex == -1) continue;
Map<ReplacementEffect, List<Map<AbilityKey, Object>>> replaceCandidateMap = replaceDamageList.get(playerIndex); Map<ReplacementEffect, List<Map<AbilityKey, Object>>> replaceCandidateMap = replaceDamageList.get(playerIndex);
for (Map.Entry<Card, Integer> e : et.getValue().entrySet()) { for (Map.Entry<Card, Integer> e : et.getValue().entrySet()) {
@@ -501,8 +501,8 @@ public class ReplacementHandler {
Map<ReplacementEffect, List<Map<AbilityKey, Object>>> newReplaceCandidateMap = replaceCandidateMap; Map<ReplacementEffect, List<Map<AbilityKey, Object>>> newReplaceCandidateMap = replaceCandidateMap;
if (!target.equals(newTarget)) { if (!target.equals(newTarget)) {
PlayerCollection players = game.getPlayersInTurnOrder(); PlayerCollection players = game.getPlayersInTurnOrder();
int playerIndex = (newTarget instanceof Player ? players.indexOf(((Player) newTarget)) : int playerIndex = newTarget instanceof Player ? players.indexOf(((Player) newTarget)) :
players.indexOf(((Card) newTarget).getController())); players.indexOf(((Card) newTarget).getController());
newReplaceCandidateMap = replaceDamageList.get(playerIndex); newReplaceCandidateMap = replaceDamageList.get(playerIndex);
} }

View File

@@ -386,9 +386,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { .hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
return InputConfirm.confirm(this, CardView.get(attacker), return InputConfirm.confirm(this, CardView.get(attacker),
localizer.getMessage("lblAssignCombatDamageWerentBlocked")); localizer.getMessage("lblAssignCombatDamageWerentBlocked"));
} else {
return false;
} }
return false;
} }
@Override @Override