mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Merge branch 'cantblock' into 'master'
CantBlockBy as Static Ability See merge request core-developers/forge!218
This commit is contained in:
@@ -229,6 +229,10 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
eff.copyChangedTextFrom(hostCard);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT")) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), Lists.newArrayList(hostCard));
|
||||
}
|
||||
|
||||
// Duration
|
||||
final String duration = sa.getParam("Duration");
|
||||
if ((duration == null) || !duration.equals("Permanent")) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package forge.game.card;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.*;
|
||||
import forge.GameCommand;
|
||||
import forge.ImageKeys;
|
||||
@@ -1537,6 +1536,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
} else if (keyword.startsWith("Strive") || keyword.startsWith("Escalate")
|
||||
|| keyword.startsWith("ETBReplacement")
|
||||
|| keyword.startsWith("CantBeBlockedBy ")
|
||||
|| keyword.equals("CARDNAME enters the battlefield tapped.")
|
||||
|| keyword.startsWith("UpkeepCost")) {
|
||||
} else if (keyword.startsWith("Provoke") || keyword.startsWith("Ingest") || keyword.equals("Unleash")
|
||||
@@ -1546,7 +1546,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|| (keyword.startsWith("Split second") && !sb.toString().contains("Split second"))
|
||||
|| keyword.equals("Suspend") // for the ones without amounnt
|
||||
|| keyword.equals("Hideaway") || keyword.equals("Ascend")
|
||||
|| keyword.equals("Totem armor")
|
||||
|| keyword.equals("Totem armor") || keyword.equals("Battle cry")
|
||||
|| keyword.equals("Devoid")){
|
||||
sbLong.append(keyword + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst")
|
||||
@@ -1595,12 +1595,9 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu")
|
||||
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.startsWith("CantBeBlockedBy")) {
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
if (keyword.startsWith("CantBeBlockedByAmount"))
|
||||
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
|
||||
else
|
||||
sbLong.append(getTextForKwCantBeBlockedByType(keyword));
|
||||
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
|
||||
} else if (keyword.startsWith("CantBlock")) {
|
||||
sbLong.append(getName()).append(" can't block ");
|
||||
if (keyword.contains("CardUID")) {
|
||||
@@ -1657,112 +1654,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature");
|
||||
}
|
||||
|
||||
private static String getTextForKwCantBeBlockedByType(final String keyword) {
|
||||
boolean negative = true;
|
||||
final List<String> subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ','));
|
||||
final List<List<String>> subsAnd = Lists.newArrayList();
|
||||
final List<String> orClauses = Lists.newArrayList();
|
||||
for (final String expession : subs) {
|
||||
final List<String> parts = Lists.newArrayList(expession.split("[.+]"));
|
||||
for (int p = 0; p < parts.size(); p++) {
|
||||
final String part = parts.get(p);
|
||||
if (part.equalsIgnoreCase("creature")) {
|
||||
parts.remove(p--);
|
||||
continue;
|
||||
}
|
||||
// based on suppossition that each expression has at least 1 predicate except 'creature'
|
||||
negative &= part.contains("non") || part.contains("without");
|
||||
}
|
||||
subsAnd.add(parts);
|
||||
}
|
||||
|
||||
final boolean allNegative = negative;
|
||||
final String byClause = allNegative ? "except by " : "by ";
|
||||
|
||||
final Function<Pair<Boolean, String>, String> withToString = new Function<Pair<Boolean, String>, String>() {
|
||||
@Override
|
||||
public String apply(Pair<Boolean, String> inp) {
|
||||
boolean useNon = inp.getKey() == allNegative;
|
||||
return (useNon ? "*NO* " : "") + inp.getRight();
|
||||
}
|
||||
};
|
||||
|
||||
for (final List<String> andOperands : subsAnd) {
|
||||
final List<Pair<Boolean, String>> prependedAdjectives = Lists.newArrayList();
|
||||
final List<Pair<Boolean, String>> postponedAdjectives = Lists.newArrayList();
|
||||
String creatures = null;
|
||||
|
||||
for (String part : andOperands) {
|
||||
boolean positive = true;
|
||||
if (part.startsWith("non")) {
|
||||
part = part.substring(3);
|
||||
positive = false;
|
||||
}
|
||||
if (part.startsWith("with")) {
|
||||
positive = !part.startsWith("without");
|
||||
postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7)));
|
||||
} else if (part.startsWith("powerLEX")) {// Kraken of the Straits
|
||||
postponedAdjectives.add(Pair.of(true, "power less than the number of islands you control"));
|
||||
} else if (part.startsWith("power")) {
|
||||
int kwLength = 5;
|
||||
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
|
||||
String operand = part.substring(kwLength + 2);
|
||||
postponedAdjectives.add(Pair.of(true, "power" + opName + operand));
|
||||
} else if (CardType.isACreatureType(part)) {
|
||||
if (creatures != null && CardType.isACreatureType(creatures)) { // e.g. Kor Castigator
|
||||
creatures = StringUtils.capitalize(Lang.getPlural(part)) + creatures;
|
||||
} else {
|
||||
creatures = StringUtils.capitalize(Lang.getPlural(part)) + (creatures == null ? "" : " or " + creatures);
|
||||
}
|
||||
// Kor Castigator and other similar creatures with composite subtype Eldrazi Scion in their text
|
||||
creatures = TextUtil.fastReplace(creatures, "Scions or Eldrazis", "Eldrazi Scions");
|
||||
} else {
|
||||
prependedAdjectives.add(Pair.of(positive, part.toLowerCase()));
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sbShort = new StringBuilder();
|
||||
if (allNegative) {
|
||||
boolean isFirst = true;
|
||||
for (Pair<Boolean, String> pre : prependedAdjectives) {
|
||||
if (isFirst) isFirst = false;
|
||||
else sbShort.append(" and/or ");
|
||||
|
||||
boolean useNon = pre.getKey() == allNegative;
|
||||
if (useNon) sbShort.append("non-");
|
||||
sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures);
|
||||
}
|
||||
if (prependedAdjectives.isEmpty())
|
||||
sbShort.append(creatures == null ? "creatures" : creatures);
|
||||
|
||||
if (!postponedAdjectives.isEmpty()) {
|
||||
if (!prependedAdjectives.isEmpty()) {
|
||||
sbShort.append(" and/or creatures");
|
||||
}
|
||||
|
||||
sbShort.append(" with ");
|
||||
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
|
||||
}
|
||||
|
||||
} else {
|
||||
for (Pair<Boolean, String> pre : prependedAdjectives) {
|
||||
boolean useNon = pre.getKey() == allNegative;
|
||||
if (useNon) sbShort.append("non-");
|
||||
sbShort.append(pre.getValue()).append(" ");
|
||||
}
|
||||
sbShort.append(creatures == null ? "creatures" : creatures);
|
||||
|
||||
if (!postponedAdjectives.isEmpty()) {
|
||||
sbShort.append(" with ");
|
||||
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
|
||||
}
|
||||
|
||||
}
|
||||
orClauses.add(sbShort.toString());
|
||||
}
|
||||
return byClause + StringUtils.join(orClauses, " or ") + ".";
|
||||
}
|
||||
|
||||
// get the text of the abilities of a card
|
||||
public String getAbilityText() {
|
||||
return getAbilityText(currentState);
|
||||
@@ -1910,6 +1801,33 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
}
|
||||
|
||||
// CantBlockBy static abilities
|
||||
if (game != null && isCreature() && isInZone(ZoneType.Battlefield)) {
|
||||
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
|
||||
if (equals(ca)) {
|
||||
continue;
|
||||
}
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.isSecondary() ||
|
||||
!stAb.getParam("Mode").equals("CantBlockBy") ||
|
||||
stAb.isSuppressed() || !stAb.checkConditions() ||
|
||||
!stAb.hasParam("ValidAttacker")) {
|
||||
continue;
|
||||
}
|
||||
final Card host = stAb.getHostCard();
|
||||
if (isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, null)) {
|
||||
String desc = stAb.toString();
|
||||
desc = TextUtil.fastReplace(desc, "CARDNAME", host.getName());
|
||||
if (host.getEffectSource() != null) {
|
||||
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", host.getEffectSource().getName());
|
||||
}
|
||||
sb.append(desc);
|
||||
sb.append("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
if (sb.toString().contains(" (NOTE: ")) {
|
||||
sb.insert(sb.indexOf("(NOTE: "), "\r\n");
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game.card;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -51,10 +52,12 @@ import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
@@ -2114,6 +2117,18 @@ public class CardFactoryUtil {
|
||||
sa.setBlessing(true);
|
||||
}
|
||||
}
|
||||
} else if (keyword.equals("Battle cry")) {
|
||||
final String trig = "Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Secondary$ True "
|
||||
+ " | TriggerDescription$ " + keyword + " (" + inst.getReminderText() + ")";
|
||||
String pumpStr = "DB$ PumpAll | ValidCards$ Creature.attacking+Other | NumAtt$ 1";
|
||||
SpellAbility sa = AbilityFactory.getAbility(pumpStr, card);
|
||||
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
final Trigger trigger = TriggerHandler.parseTrigger(trig, card, intrinsic);
|
||||
trigger.setOverridingAbility(sa);
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Bushido")) {
|
||||
final String[] k = keyword.split(" ", 2);
|
||||
final String n = k[1];
|
||||
@@ -4214,6 +4229,11 @@ public class CardFactoryUtil {
|
||||
} else if (keyword.equals("Undaunted")) {
|
||||
effect = "Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
+ "| Amount$ Undaunted | EffectZone$ All | Description$ Undaunted (" + inst.getReminderText() + ")";
|
||||
} else if (keyword.startsWith("CantBeBlockedBy ")) {
|
||||
final String[] k = keyword.split(" ", 2);
|
||||
|
||||
effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ " + k[1]
|
||||
+ " | Description$ CARDNAME can't be blocked " + getTextForKwCantBeBlockedByType(keyword);
|
||||
}
|
||||
|
||||
if (effect != null) {
|
||||
@@ -4265,4 +4285,110 @@ public class CardFactoryUtil {
|
||||
return as;
|
||||
// ETBReplacementMove(sa.getHostCard(), null));
|
||||
}
|
||||
|
||||
private static String getTextForKwCantBeBlockedByType(final String keyword) {
|
||||
boolean negative = true;
|
||||
final List<String> subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ','));
|
||||
final List<List<String>> subsAnd = Lists.newArrayList();
|
||||
final List<String> orClauses = Lists.newArrayList();
|
||||
for (final String expession : subs) {
|
||||
final List<String> parts = Lists.newArrayList(expession.split("[.+]"));
|
||||
for (int p = 0; p < parts.size(); p++) {
|
||||
final String part = parts.get(p);
|
||||
if (part.equalsIgnoreCase("creature")) {
|
||||
parts.remove(p--);
|
||||
continue;
|
||||
}
|
||||
// based on suppossition that each expression has at least 1 predicate except 'creature'
|
||||
negative &= part.contains("non") || part.contains("without");
|
||||
}
|
||||
subsAnd.add(parts);
|
||||
}
|
||||
|
||||
final boolean allNegative = negative;
|
||||
final String byClause = allNegative ? "except by " : "by ";
|
||||
|
||||
final Function<Pair<Boolean, String>, String> withToString = new Function<Pair<Boolean, String>, String>() {
|
||||
@Override
|
||||
public String apply(Pair<Boolean, String> inp) {
|
||||
boolean useNon = inp.getKey() == allNegative;
|
||||
return (useNon ? "*NO* " : "") + inp.getRight();
|
||||
}
|
||||
};
|
||||
|
||||
for (final List<String> andOperands : subsAnd) {
|
||||
final List<Pair<Boolean, String>> prependedAdjectives = Lists.newArrayList();
|
||||
final List<Pair<Boolean, String>> postponedAdjectives = Lists.newArrayList();
|
||||
String creatures = null;
|
||||
|
||||
for (String part : andOperands) {
|
||||
boolean positive = true;
|
||||
if (part.startsWith("non")) {
|
||||
part = part.substring(3);
|
||||
positive = false;
|
||||
}
|
||||
if (part.startsWith("with")) {
|
||||
positive = !part.startsWith("without");
|
||||
postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7)));
|
||||
} else if (part.startsWith("powerLEX")) {// Kraken of the Straits
|
||||
postponedAdjectives.add(Pair.of(true, "power less than the number of islands you control"));
|
||||
} else if (part.startsWith("power")) {
|
||||
int kwLength = 5;
|
||||
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
|
||||
String operand = part.substring(kwLength + 2);
|
||||
postponedAdjectives.add(Pair.of(true, "power" + opName + operand));
|
||||
} else if (CardType.isACreatureType(part)) {
|
||||
if (creatures != null && CardType.isACreatureType(creatures)) { // e.g. Kor Castigator
|
||||
creatures = StringUtils.capitalize(Lang.getPlural(part)) + creatures;
|
||||
} else {
|
||||
creatures = StringUtils.capitalize(Lang.getPlural(part)) + (creatures == null ? "" : " or " + creatures);
|
||||
}
|
||||
// Kor Castigator and other similar creatures with composite subtype Eldrazi Scion in their text
|
||||
creatures = TextUtil.fastReplace(creatures, "Scions or Eldrazis", "Eldrazi Scions");
|
||||
} else {
|
||||
prependedAdjectives.add(Pair.of(positive, part.toLowerCase()));
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sbShort = new StringBuilder();
|
||||
if (allNegative) {
|
||||
boolean isFirst = true;
|
||||
for (Pair<Boolean, String> pre : prependedAdjectives) {
|
||||
if (isFirst) isFirst = false;
|
||||
else sbShort.append(" and/or ");
|
||||
|
||||
boolean useNon = pre.getKey() == allNegative;
|
||||
if (useNon) sbShort.append("non-");
|
||||
sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures);
|
||||
}
|
||||
if (prependedAdjectives.isEmpty())
|
||||
sbShort.append(creatures == null ? "creatures" : creatures);
|
||||
|
||||
if (!postponedAdjectives.isEmpty()) {
|
||||
if (!prependedAdjectives.isEmpty()) {
|
||||
sbShort.append(" and/or creatures");
|
||||
}
|
||||
|
||||
sbShort.append(" with ");
|
||||
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
|
||||
}
|
||||
|
||||
} else {
|
||||
for (Pair<Boolean, String> pre : prependedAdjectives) {
|
||||
boolean useNon = pre.getKey() == allNegative;
|
||||
if (useNon) sbShort.append("non-");
|
||||
sbShort.append(pre.getValue()).append(" ");
|
||||
}
|
||||
sbShort.append(creatures == null ? "creatures" : creatures);
|
||||
|
||||
if (!postponedAdjectives.isEmpty()) {
|
||||
sbShort.append(" with ");
|
||||
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
|
||||
}
|
||||
|
||||
}
|
||||
orClauses.add(sbShort.toString());
|
||||
}
|
||||
return byClause + StringUtils.join(orClauses, " or ") + ".";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,6 +955,7 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Game game = attacker.getGame();
|
||||
if (!CombatUtil.canBlock(blocker, nextTurn)) {
|
||||
return false;
|
||||
}
|
||||
@@ -995,32 +996,15 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (KeywordInterface inst1 : attacker.getKeywords()) {
|
||||
String k = inst1.getOriginal();
|
||||
if (k.startsWith("CantBeBlockedBy ")) {
|
||||
final String[] n = k.split(" ", 2);
|
||||
final String[] restrictions = n[1].split(",");
|
||||
if (blocker.isValid(restrictions, attacker.getController(), attacker, null)) {
|
||||
boolean stillblock = false;
|
||||
//Dragon Hunter check
|
||||
if (n[1].contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
|
||||
for (KeywordInterface inst2 : blocker.getKeywords()) {
|
||||
String k2 = inst2.getOriginal();
|
||||
if (k2.startsWith("IfReach")) {
|
||||
String n2[] = k2.split(":");
|
||||
if (attacker.getType().hasCreatureType(n2[1])) {
|
||||
stillblock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stillblock) {
|
||||
return false;
|
||||
}
|
||||
// CantBlockBy static abilities
|
||||
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (stAb.applyAbility("CantBlockBy", attacker, blocker)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KeywordInterface inst : blocker.getKeywords()) {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("CantBlockCardUID")) {
|
||||
|
||||
@@ -453,6 +453,8 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
|
||||
|
||||
if (mode.equals("CantAttack")) {
|
||||
return StaticAbilityCantAttackBlock.applyCantAttackAbility(this, card, target);
|
||||
} else if (mode.equals("CantBlockBy") && target instanceof Card) {
|
||||
return StaticAbilityCantAttackBlock.applyCantBlockByAbility(this, card, (Card)target);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -23,6 +23,7 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -89,6 +90,46 @@ public class StaticAbilityCantAttackBlock {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if attacker can be blocked by blocker
|
||||
* @param stAb
|
||||
* @param attacker
|
||||
* @param blocker
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean applyCantBlockByAbility(final StaticAbility stAb, final Card attacker, final Card blocker) {
|
||||
final Card host = stAb.getHostCard();
|
||||
if (stAb.hasParam("ValidAttacker")) {
|
||||
if (!attacker.isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (stAb.hasParam("ValidBlocker")) {
|
||||
for (final String v : stAb.getParam("ValidBlocker").split(",")) {
|
||||
if (blocker.isValid(v, host.getController(), host, null)) {
|
||||
boolean stillblock = false;
|
||||
//Dragon Hunter check
|
||||
if (v.contains("withoutReach") && blocker.hasStartOfKeyword("IfReach")) {
|
||||
for (KeywordInterface inst : blocker.getKeywords()) {
|
||||
String k = inst.getOriginal();
|
||||
if (k.startsWith("IfReach")) {
|
||||
String n[] = k.split(":");
|
||||
if (attacker.getType().hasCreatureType(n[1])) {
|
||||
stillblock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stillblock) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Write javadoc for this method.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user