CanBlockAny: rewrite for 'can block an additional' and 'can block any number'

This commit is contained in:
Hanmac
2019-09-01 10:41:32 +02:00
parent d3faa740f1
commit 357f146742
11 changed files with 87 additions and 156 deletions

View File

@@ -547,8 +547,7 @@ public class AiAttackController {
remainingAttackers.removeAll(unblockedAttackers); remainingAttackers.removeAll(unblockedAttackers);
for (Card blocker : this.blockers) { for (Card blocker : this.blockers) {
if (blocker.hasKeyword("CARDNAME can block any number of creatures.") if (blocker.canBlockAny()) {
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (CombatUtil.canBlock(attacker, blocker)) { if (CombatUtil.canBlock(attacker, blocker)) {
remainingAttackers.remove(attacker); remainingAttackers.remove(attacker);
@@ -564,6 +563,7 @@ public class AiAttackController {
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) { if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
break; break;
} }
// TODO replace with better amount
if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) { if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
blockedAttackers.add(remainingAttackers.get(0)); blockedAttackers.add(remainingAttackers.get(0));
remainingAttackers.remove(0); remainingAttackers.remove(0);
@@ -884,7 +884,7 @@ public class AiAttackController {
final int outNumber = computerForces - humanForces; final int outNumber = computerForces - humanForces;
for (Card blocker : this.blockers) { for (Card blocker : this.blockers) {
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) { if (blocker.canBlockAny()) {
aiLifeToPlayerDamageRatio--; aiLifeToPlayerDamageRatio--;
} }
} }

View File

@@ -1581,9 +1581,6 @@ public class AttachAi extends SpellAbilityAi {
&& CombatUtil.canBlock(card, true); && CombatUtil.canBlock(card, true);
} else if (keyword.equals("Reach")) { } else if (keyword.equals("Reach")) {
return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true); return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME can block an additional creature each combat.")) {
return CombatUtil.canBlock(card, true) && !card.hasKeyword("CARDNAME can block any number of creatures.")
&& !card.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.");
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) { } else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0; return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0;
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) { } else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {

View File

@@ -256,7 +256,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} }
} }
return false; return false;
} else if (keyword.equals("Bushido")) { } else if (keyword.startsWith("Bushido")) {
return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card))) return !ph.isPlayerTurn(opp) && (CombatUtil.canAttack(card, opp) || (combat != null && combat.isAttacking(card)))
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& !opp.getCreaturesInPlay().isEmpty() && !opp.getCreaturesInPlay().isEmpty()
@@ -335,22 +335,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
&& !CardLists.getKeyword(game.getCombat().getAttackers(), Keyword.FLYING).isEmpty() && !CardLists.getKeyword(game.getCombat().getAttackers(), Keyword.FLYING).isEmpty()
&& !card.hasKeyword(Keyword.FLYING) && !card.hasKeyword(Keyword.FLYING)
&& CombatUtil.canBlock(card); && CombatUtil.canBlock(card);
} else if (keyword.endsWith("CARDNAME can block an additional creature each combat.")) {
if (ph.isPlayerTurn(ai)
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
int canBlockNum = 1 + CombatUtil.numberOfAdditionalCreaturesCanBlock(card);
int possibleBlockNum = 0;
for (Card attacker : game.getCombat().getAttackers()) {
if (CombatUtil.canBlock(attacker, card)) {
possibleBlockNum++;
if (possibleBlockNum > canBlockNum) {
break;
}
}
}
return possibleBlockNum > canBlockNum;
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) { } else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
return ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(card); return ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(card);
} else if (keyword.equals("Persist")) { } else if (keyword.equals("Persist")) {

View File

@@ -47,11 +47,8 @@ public class StaticEffect {
private final Card source; private final Card source;
private StaticAbility ability; private StaticAbility ability;
private int keywordNumber = 0;
private CardCollectionView affectedCards = new CardCollection(); private CardCollectionView affectedCards = new CardCollection();
private List<Player> affectedPlayers = Lists.newArrayList(); private List<Player> affectedPlayers = Lists.newArrayList();
private int xValue = 0;
private int yValue = 0;
private long timestamp = -1; private long timestamp = -1;
private String chosenType; private String chosenType;
@@ -88,11 +85,8 @@ public class StaticEffect {
private StaticEffect makeMappedCopy(GameObjectMap map) { private StaticEffect makeMappedCopy(GameObjectMap map) {
StaticEffect copy = new StaticEffect(map.map(this.source)); StaticEffect copy = new StaticEffect(map.map(this.source));
copy.ability = this.ability; copy.ability = this.ability;
copy.keywordNumber = this.keywordNumber;
copy.affectedCards = map.mapCollection(this.affectedCards); copy.affectedCards = map.mapCollection(this.affectedCards);
copy.affectedPlayers = map.mapList(this.affectedPlayers); copy.affectedPlayers = map.mapList(this.affectedPlayers);
copy.xValue = this.xValue;
copy.yValue = this.yValue;
copy.timestamp = this.timestamp; copy.timestamp = this.timestamp;
copy.chosenType = this.chosenType; copy.chosenType = this.chosenType;
copy.mapParams = this.mapParams; copy.mapParams = this.mapParams;
@@ -627,29 +621,6 @@ public class StaticEffect {
return this.source; return this.source;
} }
/**
* <p>
* Setter for the field <code>keywordNumber</code>.
* </p>
*
* @param i
* a int.
*/
public final void setKeywordNumber(final int i) {
this.keywordNumber = i;
}
/**
* <p>
* Getter for the field <code>keywordNumber</code>.
* </p>
*
* @return a int.
*/
public final int getKeywordNumber() {
return this.keywordNumber;
}
/** /**
* <p> * <p>
* Getter for the field <code>affectedCards</code>. * Getter for the field <code>affectedCards</code>.
@@ -692,52 +663,6 @@ public class StaticEffect {
this.affectedPlayers = list; this.affectedPlayers = list;
} }
/**
* <p>
* Setter for the field <code>xValue</code>.
* </p>
*
* @param x
* a int.
*/
public final void setXValue(final int x) {
this.xValue = x;
}
/**
* <p>
* Getter for the field <code>xValue</code>.
* </p>
*
* @return a int.
*/
public final int getXValue() {
return this.xValue;
}
/**
* <p>
* Setter for the field <code>yValue</code>.
* </p>
*
* @param y
* a int.
*/
public final void setYValue(final int y) {
this.yValue = y;
}
/**
* <p>
* Getter for the field <code>yValue</code>.
* </p>
*
* @return a int.
*/
public final int getYValue() {
return this.yValue;
}
/** /**
* setParams. TODO Write javadoc for this method. * setParams. TODO Write javadoc for this method.
* *
@@ -788,7 +713,6 @@ public class StaticEffect {
String changeColorWordsTo = null; String changeColorWordsTo = null;
int keywordMultiplier = 1;
boolean setPT = false; boolean setPT = false;
String[] addHiddenKeywords = null; String[] addHiddenKeywords = null;
String addColors = null; String addColors = null;
@@ -805,15 +729,6 @@ public class StaticEffect {
setPT = true; setPT = true;
} }
if (params.containsKey("KeywordMultiplier")) {
String multiplier = params.get("KeywordMultiplier");
if (multiplier.equals("X")) {
keywordMultiplier = getXValue();
} else {
keywordMultiplier = Integer.valueOf(multiplier);
}
}
if (params.containsKey("AddHiddenKeyword")) { if (params.containsKey("AddHiddenKeyword")) {
addHiddenKeywords = params.get("AddHiddenKeyword").split(" & "); addHiddenKeywords = params.get("AddHiddenKeyword").split(" & ");
} }
@@ -906,9 +821,7 @@ public class StaticEffect {
if (addHiddenKeywords != null) { if (addHiddenKeywords != null) {
for (final String k : addHiddenKeywords) { for (final String k : addHiddenKeywords) {
for (int j = 0; j < keywordMultiplier; j++) { affectedCard.removeHiddenExtrinsicKeyword(k);
affectedCard.removeHiddenExtrinsicKeyword(k);
}
} }
} }
@@ -949,6 +862,13 @@ public class StaticEffect {
affectedCard.removeGoad(getTimestamp()); affectedCard.removeGoad(getTimestamp());
} }
if (params.containsKey("CanBlockAny")) {
affectedCard.removeCanBlockAny(getTimestamp());
}
if (params.containsKey("CanBlockAmount")) {
affectedCard.removeCanBlockAdditional(getTimestamp());
}
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
} }
return affectedCards; return affectedCards;

View File

@@ -64,7 +64,15 @@ public class PumpEffect extends SpellAbilityEffect {
if (redrawPT) { if (redrawPT) {
gameCard.updatePowerToughnessForView(); gameCard.updatePowerToughnessForView();
} }
if (sa.hasParam("CanBlockAny")) {
gameCard.addCanBlockAny(timestamp);
}
if (sa.hasParam("CanBlockAmount")) {
int v = AbilityUtils.calculateAmount(host, sa.getParam("CanBlockAmount"), sa, true);
gameCard.addCanBlockAdditional(v, timestamp);
}
if (sa.hasParam("LeaveBattlefield")) { if (sa.hasParam("LeaveBattlefield")) {
addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield")); addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
} }
@@ -77,6 +85,9 @@ public class PumpEffect extends SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
gameCard.removePTBoost(timestamp); gameCard.removePTBoost(timestamp);
boolean updateText = false;
updateText = gameCard.removeCanBlockAny(timestamp) || updateText;
updateText = gameCard.removeCanBlockAdditional(timestamp) || updateText;
if (keywords.size() > 0) { if (keywords.size() > 0) {
@@ -88,6 +99,9 @@ public class PumpEffect extends SpellAbilityEffect {
gameCard.removeChangedCardKeywords(timestamp); gameCard.removeChangedCardKeywords(timestamp);
} }
gameCard.updatePowerToughnessForView(); gameCard.updatePowerToughnessForView();
if (updateText) {
gameCard.updateAbilityTextForView();
}
game.fireEvent(new GameEventCardStatsChanged(gameCard)); game.fireEvent(new GameEventCardStatsChanged(gameCard));
} }

View File

@@ -123,6 +123,8 @@ public class Card extends GameEntity implements Comparable<Card> {
private final NavigableMap<Long, CardCloneStates> clonedStates = Maps.newTreeMap(); private final NavigableMap<Long, CardCloneStates> clonedStates = Maps.newTreeMap();
private final NavigableMap<Long, CardCloneStates> textChangeStates = Maps.newTreeMap(); private final NavigableMap<Long, CardCloneStates> textChangeStates = Maps.newTreeMap();
private final Map<Long, Integer> canBlockAdditional = Maps.newTreeMap();
private final Set<Long> canBlockAny = Sets.newHashSet();
// changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps // changes that say "replace each instance of one [color,type] by another - timestamp is the key of maps
private final CardChangedWords changedTextColors = new CardChangedWords(); private final CardChangedWords changedTextColors = new CardChangedWords();
@@ -2045,6 +2047,17 @@ public class Card extends GameEntity implements Comparable<Card> {
sb.append("is goaded by: ").append(Lang.joinHomogenous(getGoaded())); sb.append("is goaded by: ").append(Lang.joinHomogenous(getGoaded()));
sb.append("\r\n"); sb.append("\r\n");
} }
// maybe move to CardView for better output
if (canBlockAny()) {
sb.append("CARDNAME can block any number of creatures.");
sb.append("\r\n");
} else if (!canBlockAdditional.isEmpty()){
sb.append("CARDNAME can block an additional ");
sb.append(Lang.nounWithNumeral(canBlockAdditional(), "creature"));
sb.append(" creatures each combat.");
sb.append("\r\n");
}
// replace triple line feeds with double line feeds // replace triple line feeds with double line feeds
int start; int start;
final String s = "\r\n\r\n\r\n"; final String s = "\r\n\r\n\r\n";
@@ -6224,4 +6237,35 @@ public class Card extends GameEntity implements Comparable<Card> {
numberTurnActivations.clear(); numberTurnActivations.clear();
numberTurnActivationsStatic.clear(); numberTurnActivationsStatic.clear();
} }
public void addCanBlockAdditional(int n, long timestamp) {
if (n <= 0) {
return;
}
canBlockAdditional.put(timestamp, n);
}
public boolean removeCanBlockAdditional(long timestamp) {
return canBlockAdditional.remove(timestamp) != null;
}
public int canBlockAdditional() {
int result = 0;
for (Integer v : canBlockAdditional.values()) {
result += v;
}
return result;
}
public void addCanBlockAny(long timestamp) {
canBlockAny.add(timestamp);
}
public boolean removeCanBlockAny(long timestamp) {
return canBlockAny.remove(timestamp) != null;
}
public boolean canBlockAny() {
return !canBlockAny.isEmpty();
}
} }

View File

@@ -581,16 +581,6 @@ public class CardView extends GameEntityView {
} }
String nonAbilityText = get(TrackableProperty.NonAbilityText); String nonAbilityText = get(TrackableProperty.NonAbilityText);
int blockAdditional = state.getBlockAdditional();
if (blockAdditional > 1) {
final StringBuilder ab = new StringBuilder();
ab.append("CARDNAME can block an additional ");
ab.append(blockAdditional);
ab.append(" creatures each combat.");
nonAbilityText = nonAbilityText.replaceFirst("CARDNAME can block an additional creature each combat.", ab.toString());
nonAbilityText = nonAbilityText.replaceAll("CARDNAME can block an additional creature each combat.", "");
nonAbilityText = nonAbilityText.replaceAll("\r\n\r\n\r\n", "");
}
if (!nonAbilityText.isEmpty()) { if (!nonAbilityText.isEmpty()) {
sb.append("\r\n \r\nNon ability features: \r\n"); sb.append("\r\n \r\nNon ability features: \r\n");
sb.append(nonAbilityText.replaceAll("CARDNAME", getName())); sb.append(nonAbilityText.replaceAll("CARDNAME", getName()));
@@ -989,9 +979,6 @@ public class CardView extends GameEntityView {
return get(TrackableProperty.HasTrample); return get(TrackableProperty.HasTrample);
} }
public int getBlockAdditional() {
return get(TrackableProperty.BlockAdditional);
}
public String getAbilityText() { public String getAbilityText() {
return get(TrackableProperty.AbilityText); return get(TrackableProperty.AbilityText);
} }
@@ -1005,7 +992,6 @@ public class CardView extends GameEntityView {
set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state)); set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state));
set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state)); set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state));
set(TrackableProperty.HasTrample, c.hasKeyword(Keyword.TRAMPLE, state)); set(TrackableProperty.HasTrample, c.hasKeyword(Keyword.TRAMPLE, state));
set(TrackableProperty.BlockAdditional, c.getAmountOfKeyword("CARDNAME can block an additional creature each combat.", state));
updateAbilityText(c, state); updateAbilityText(c, state);
} }

View File

@@ -430,20 +430,13 @@ public class CombatUtil {
} }
public static boolean canBlockMoreCreatures(final Card blocker, final CardCollectionView blockedBy) { public static boolean canBlockMoreCreatures(final Card blocker, final CardCollectionView blockedBy) {
if (blockedBy.isEmpty() || blocker.hasKeyword("CARDNAME can block any number of creatures.")) { if (blockedBy.isEmpty() || blocker.canBlockAny()) {
return true; return true;
} }
int canBlockMore = numberOfAdditionalCreaturesCanBlock(blocker); int canBlockMore = blocker.canBlockAdditional();
return canBlockMore >= blockedBy.size(); return canBlockMore >= blockedBy.size();
} }
public static int numberOfAdditionalCreaturesCanBlock(final Card blocker) {
// If Wizards makes a few more of these, we should really just make a generic version
return blocker.getAmountOfKeyword("CARDNAME can block an additional creature each combat.") +
blocker.getAmountOfKeyword("CARDNAME can block an additional ninety-nine creatures.") * 99 +
blocker.getAmountOfKeyword("CARDNAME can block an additional seven creatures each combat.") * 7;
}
// can the attacker be blocked at all? // can the attacker be blocked at all?
/** /**
* <p> * <p>

View File

@@ -166,7 +166,7 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
layers.add(StaticAbilityLayer.RULES); layers.add(StaticAbilityLayer.RULES);
} }
if (hasParam("IgnoreEffectCost") || hasParam("Goad")) { if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) {
layers.add(StaticAbilityLayer.RULES); layers.add(StaticAbilityLayer.RULES);
} }

View File

@@ -110,7 +110,6 @@ public final class StaticAbilityContinuous {
int setPower = Integer.MAX_VALUE; int setPower = Integer.MAX_VALUE;
String setT = ""; String setT = "";
int setToughness = Integer.MAX_VALUE; int setToughness = Integer.MAX_VALUE;
int keywordMultiplier = 1;
String[] addKeywords = null; String[] addKeywords = null;
List<String> addHiddenKeywords = Lists.newArrayList(); List<String> addHiddenKeywords = Lists.newArrayList();
@@ -186,16 +185,6 @@ public final class StaticAbilityContinuous {
toughnessBonus = AbilityUtils.calculateAmount(hostCard, addT, stAb, true); toughnessBonus = AbilityUtils.calculateAmount(hostCard, addT, stAb, true);
} }
if (params.containsKey("KeywordMultiplier")) {
final String multiplier = params.get("KeywordMultiplier");
if (multiplier.equals("X")) {
keywordMultiplier = AbilityUtils.calculateAmount(hostCard, "X", stAb);
se.setXValue(keywordMultiplier);
} else {
keywordMultiplier = Integer.valueOf(multiplier);
}
}
if (layer == StaticAbilityLayer.ABILITIES2 && params.containsKey("AddKeyword")) { if (layer == StaticAbilityLayer.ABILITIES2 && params.containsKey("AddKeyword")) {
addKeywords = params.get("AddKeyword").split(" & "); addKeywords = params.get("AddKeyword").split(" & ");
final Iterable<String> chosencolors = hostCard.getChosenColors(); final Iterable<String> chosencolors = hostCard.getChosenColors();
@@ -448,9 +437,7 @@ public final class StaticAbilityContinuous {
// add keywords // add keywords
if (addKeywords != null) { if (addKeywords != null) {
for (int i = 0; i < keywordMultiplier; i++) { p.addChangedKeywords(addKeywords, removeKeywords == null ? new String[0] : removeKeywords, se.getTimestamp());
p.addChangedKeywords(addKeywords, removeKeywords == null ? new String[0] : removeKeywords, se.getTimestamp());
}
} }
// add static abilities // add static abilities
@@ -577,9 +564,7 @@ public final class StaticAbilityContinuous {
// add HIDDEN keywords // add HIDDEN keywords
if (!addHiddenKeywords.isEmpty()) { if (!addHiddenKeywords.isEmpty()) {
for (final String k : addHiddenKeywords) { for (final String k : addHiddenKeywords) {
for (int j = 0; j < keywordMultiplier; j++) { affectedCard.addHiddenExtrinsicKeyword(k);
affectedCard.addHiddenExtrinsicKeyword(k);
}
} }
} }
@@ -718,8 +703,17 @@ public final class StaticAbilityContinuous {
} }
} }
if (layer == StaticAbilityLayer.RULES && params.containsKey("Goad")) { if (layer == StaticAbilityLayer.RULES) {
affectedCard.addGoad(se.getTimestamp(), hostCard.getController()); if (params.containsKey("Goad")) {
affectedCard.addGoad(se.getTimestamp(), hostCard.getController());
}
if (params.containsKey("CanBlockAny")) {
affectedCard.addCanBlockAny(se.getTimestamp());
}
if (params.containsKey("CanBlockAmount")) {
int v = AbilityUtils.calculateAmount(hostCard, params.get("CanBlockAmount"), stAb, true);
affectedCard.addCanBlockAdditional(v, se.getTimestamp());
}
} }
if (mayLookAt != null) { if (mayLookAt != null) {

View File

@@ -88,7 +88,6 @@ public enum TrackableProperty {
HasTrample(TrackableTypes.BooleanType), HasTrample(TrackableTypes.BooleanType),
YouMayLook(TrackableTypes.BooleanType), YouMayLook(TrackableTypes.BooleanType),
OpponentMayLook(TrackableTypes.BooleanType), OpponentMayLook(TrackableTypes.BooleanType),
BlockAdditional(TrackableTypes.IntegerType),
AbilityText(TrackableTypes.StringType), AbilityText(TrackableTypes.StringType),
NonAbilityText(TrackableTypes.StringType), NonAbilityText(TrackableTypes.StringType),
FoilIndex(TrackableTypes.IntegerType), FoilIndex(TrackableTypes.IntegerType),