Skip combat phases if player has no creatures that can attack

This commit is contained in:
drdev
2013-11-30 20:57:30 +00:00
parent cfeeda3c93
commit 2ffc18cc89
3 changed files with 105 additions and 64 deletions

View File

@@ -20,6 +20,7 @@ package forge.game.combat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
@@ -157,7 +158,7 @@ public class CombatUtil {
if (blockedBy.isEmpty() || blocker.hasKeyword("CARDNAME can block any number of creatures.")) {
return true;
}
int canBlockMore = blocker.getKeywordAmount("CARDNAME can block an additional creature.")
int canBlockMore = blocker.getKeywordAmount("CARDNAME can block an additional creature.")
+ blocker.getKeywordAmount("CARDNAME can block an additional ninety-nine creatures.") * 99;
return canBlockMore >= blockedBy.size();
}
@@ -184,7 +185,7 @@ public class CombatUtil {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) {
return false;
}
// Rule 802.4a: A player can block only creatures attacking him or a planeswalker he controls
Player attacked = combat.getDefendingPlayerRelatedTo(attacker);
if (attacked != null && attacked != defendingPlayer) {
@@ -327,7 +328,7 @@ public class CombatUtil {
blocks++;
}
}
return canAttackerBeBlockedWithAmount(attacker, blocks);
}
@@ -368,7 +369,7 @@ public class CombatUtil {
final List<Card> defendersArmy = defending.getCreaturesInPlay();
final List<Card> attackers = combat.getAttackers();
final List<Card> blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending);
final List<Card> blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending);
// if a creature does not block but should, return false
for (final Card blocker : defendersArmy) {
@@ -448,7 +449,7 @@ public class CombatUtil {
if (attacker.hasStartOfKeyword("All creatures able to block CARDNAME do so.")
|| (attacker.hasStartOfKeyword("All Walls able to block CARDNAME do so.") && blocker.isType("Wall"))
|| (attacker.hasStartOfKeyword("All creatures with flying able to block CARDNAME do so.") && blocker.hasKeyword("Flying"))
|| (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.")
|| (attacker.hasStartOfKeyword("CARDNAME must be blocked if able.")
&& combat.getBlockers(attacker).isEmpty())) {
attackersWithLure.add(attacker);
}
@@ -669,6 +670,45 @@ public class CombatUtil {
}
}
// can a player attack with one or more creatures at the moment?
/**
* <p>
* canAttack.
* </p>
*
* @param p
* a {@link forge.game.player} object.
* @return a boolean.
*/
public static boolean canAttack(Player p) {
List<Card> creatures = p.getCreaturesInPlay();
if (creatures.isEmpty()) { return false; }
List<Player> defenders = p.getOpponents();
if (defenders.isEmpty()) { return false; }
boolean foundCreatureThatCantAttackAlone = false;
for (Card c : creatures) {
if (CombatUtil.canAttack(c)) {
for (Player def : defenders) {
if (CombatUtil.canAttackNextTurn(c, def)) {
if (c.hasKeyword("CARDNAME can't attack or block alone.")) {
//ensure another possible attacker is found
//if the first one found can't attack alone
if (!foundCreatureThatCantAttackAlone) {
foundCreatureThatCantAttackAlone = true;
break;
}
}
return true;
}
}
}
}
return false;
}
// can a creature attack given the combat state
/**
* <p>
@@ -685,37 +725,40 @@ public class CombatUtil {
int cntAttackers = combat.getAttackers().size();
final Game game = c.getGame();
for (final Card card : game.getCardsIn(ZoneType.Battlefield)) {
for (final String keyword : card.getKeyword()) {
if (keyword.equals("No more than two creatures can attack each combat.") && cntAttackers > 1) {
return false;
}
if (keyword.equals("No more than two creatures can attack you each combat.") && cntAttackers > 1
&& card.getController().getOpponent().equals(c.getController())) {
return false;
}
if (keyword.equals("CARDNAME can only attack alone.") && combat.isAttacking(card)) {
return false;
if (cntAttackers > 0) {
for (final Card card : game.getCardsIn(ZoneType.Battlefield)) {
for (final String keyword : card.getKeyword()) {
if (cntAttackers > 1) {
if (keyword.equals("No more than two creatures can attack each combat.")) {
return false;
}
if (keyword.equals("No more than two creatures can attack you each combat.") &&
card.getController().getOpponent().equals(c.getController())) {
return false;
}
}
if (keyword.equals("CARDNAME can only attack alone.") && combat.isAttacking(card)) {
return false;
}
}
}
if (c.hasKeyword("CARDNAME can only attack alone.")) {
return false;
}
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerACombat)) {
return false;
}
}
final List<Card> list = c.getController().getCreaturesInPlay();
if (list.size() < 2 && c.hasKeyword("CARDNAME can't attack or block alone.")) {
if (c.hasKeyword("CARDNAME can't attack or block alone.") &&
c.getController().getCreaturesInPlay().size() < 2) {
return false;
}
if (cntAttackers > 0 && c.hasKeyword("CARDNAME can only attack alone.")) {
return false;
}
if (cntAttackers > 0
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerACombat)) {
return false;
}
if ((cntAttackers > 0 || c.getController().getAttackedWithCreatureThisTurn())
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) {
if ((cntAttackers > 0 || c.getController().getAttackedWithCreatureThisTurn()) &&
game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) {
return false;
}
@@ -735,7 +778,7 @@ public class CombatUtil {
public static boolean canAttack(final Card c, final GameEntity defender) {
return canAttack(c) && canAttackNextTurn(c, defender);
}
public static boolean canAttack(final Card c) {
final Game game = c.getGame();
if (c.isTapped() || c.isPhasedOut()
@@ -745,12 +788,12 @@ public class CombatUtil {
}
return true;
}
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) {
if( amount == 0 )
return false; // no block
List<String> restrictions = Lists.newArrayList();
List<String> restrictions = Lists.newArrayList();
for ( String kw : attacker.getKeyword() ) {
if ( kw.startsWith("CantBeBlockedByAmount") )
restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
@@ -806,7 +849,6 @@ public class CombatUtil {
final int[] powerLimit = { 0 };
String cantAttackKw = null;
for( String kw : c.getKeyword()) {
if (kw.startsWith("CARDNAME can't attack if defending player controls an untapped creature with power")) {
cantAttackKw = kw;
@@ -828,7 +870,7 @@ public class CombatUtil {
@Override
public boolean apply(final Card ct) {
return (ct.isUntapped()
&& ((ct.getNetAttack() >= powerLimit[0] && asSeparateWords[14].contains("greater"))
&& ((ct.getNetAttack() >= powerLimit[0] && asSeparateWords[14].contains("greater"))
|| (ct.getNetAttack() <= powerLimit[0] && asSeparateWords[14].contains("less"))));
}
});
@@ -926,7 +968,7 @@ public class CombatUtil {
attackCost.add(additionalCost);
}
}
boolean isFree = attackCost.getTotalMana().isZero() && attackCost.isOnlyManaCost(); // true if needless to pay
return isFree || c.getController().getController().payManaOptional(c, attackCost, null, "Pay additional cost to declare " + c + " an attacker", ManaPaymentPurpose.DeclareAttacker);
}
@@ -936,7 +978,7 @@ public class CombatUtil {
* This method checks triggered effects of attacking creatures, right before
* defending player declares blockers.
* </p>
* @param game
* @param game
*
* @param c
* a {@link forge.game.card.Card} object.
@@ -969,7 +1011,7 @@ public class CombatUtil {
game.getStack().addSimultaneousStackEntry(ability);
}
}
}
c.getDamageHistory().setCreatureAttackedThisCombat(true);
c.getDamageHistory().clearNotAttackedSinceLastUpkeepOf();
@@ -983,7 +1025,7 @@ public class CombatUtil {
int idx = keyword.indexOf("Rampage ");
if ( idx < 0)
continue;
final int numBlockers = blockers.size();
if (numBlockers > 1) {
final int magnitude = Integer.valueOf(keyword.substring(idx + "Rampage ".length()));
@@ -996,7 +1038,7 @@ public class CombatUtil {
for (Card blocker : blockers) {
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
int flankingMagnitude = 0;
for (String kw : attacker.getKeyword()) {
if (kw.equals("Flanking")) {
flankingMagnitude++;
@@ -1007,7 +1049,7 @@ public class CombatUtil {
for( int i = 0; i < flankingMagnitude; i++ ) {
String effect = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ -1 | NumDef$ -1 | ", blocker.getUniqueNumber());
String desc = String.format("StackDescription$ Flanking (The blocking %s gets -1/-1 until end of turn)", blocker.getName());
SpellAbility ability = AbilityFactory.getAbility(effect + desc, attacker);
ability.setActivatingPlayer(attacker.getController());
ability.setDescription(ability.getStackDescription());
@@ -1024,7 +1066,7 @@ public class CombatUtil {
/**
* executes Rampage abilities for a given card.
* @param game
* @param game
*
* @param c
* the card to add rampage bonus to
@@ -1038,7 +1080,7 @@ public class CombatUtil {
// numBlockers starts with 1 since it is for every creature beyond the first
for (int i = 1; i < numBlockers; i++) {
String effect = "AB$ Pump | Cost$ 0 | " + c.getUniqueNumber() + " | NumAtt$ " + magnitude + " | NumDef$ " + magnitude + " | ";
String desc = "StackDescription$ Rampage " + magnitude + " (Whenever CARDNAME becomes blocked, it gets +" + magnitude + "/+"
String desc = "StackDescription$ Rampage " + magnitude + " (Whenever CARDNAME becomes blocked, it gets +" + magnitude + "/+"
+ magnitude + " until end of turn for each creature blocking it beyond the first.)";
SpellAbility ability = AbilityFactory.getAbility(effect + desc, c);

View File

@@ -51,6 +51,7 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.CombatUtil;
import forge.game.event.GameEventLandPlayed;
import forge.game.event.GameEventMulligan;
import forge.game.event.GameEventPlayerControl;
@@ -2947,7 +2948,6 @@ public class Player extends GameEntity implements Comparable<Player> {
* @return a boolean.
*/
public boolean isSkippingCombat() {
if (hasKeyword("Skip your next combat phase.")) {
return true;
}
@@ -2963,7 +2963,8 @@ public class Player extends GameEntity implements Comparable<Player> {
return true;
}
return false;
//skip combat unless player has a creature that can attack
return !CombatUtil.canAttack(this);
}
/**

View File

@@ -47,22 +47,20 @@ public class InputAttack extends InputSyncronizedBase {
/** Constant <code>serialVersionUID=7849903731842214245L</code>. */
private static final long serialVersionUID = 7849903731842214245L;
private final Combat combat;
private final List<GameEntity> defenders;
private GameEntity currentDefender;
private final Player playerAttacks;
private final Player playerDeclares;
private AttackingBand activeBand = null;
public InputAttack(Player attacks, Player declares, Combat combat) {
this.playerAttacks = attacks;
this.playerDeclares = declares;
this.combat = combat;
this.defenders = combat.getDefenders();
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
@@ -96,12 +94,12 @@ public class InputAttack extends InputSyncronizedBase {
}
}
}
private void showCombat() {
// redraw sword icons
CMatchUI.SINGLETON_INSTANCE.showCombat(combat);
}
/** {@inheritDoc} */
@Override
protected final void onOk() {
@@ -119,7 +117,7 @@ public class InputAttack extends InputSyncronizedBase {
else
flashIncorrectAction(); // cannot attack that player
}
/** {@inheritDoc} */
@Override
protected final void onCardSelected(final Card card, final MouseEvent triggerEvent) {
@@ -132,7 +130,7 @@ public class InputAttack extends InputSyncronizedBase {
showCombat();
// When removing an attacker clear the attacking band
this.activateBand(null);
CMatchUI.SINGLETON_INSTANCE.fireEvent(new UiEventAttackerDeclared(card, null));
return;
}
@@ -143,7 +141,7 @@ public class InputAttack extends InputSyncronizedBase {
this.activateBand(combat.getBandOfAttacker(card));
} else if (this.activeBand.getAttackers().contains(card)) {
this.activateBand(null);
} else { // Join a band by selecting a non-active band member after activating a band
} else { // Join a band by selecting a non-active band member after activating a band
if (this.activeBand.canJoinBand(card)) {
combat.removeFromCombat(card);
declareAttacker(card);
@@ -155,7 +153,7 @@ public class InputAttack extends InputSyncronizedBase {
updateMessage();
return;
}
if ( card.getController().isOpponentOf(playerAttacks) ) {
if ( defenders.contains(card) ) { // planeswalker?
setCurrentDefender(card);
@@ -170,11 +168,11 @@ public class InputAttack extends InputSyncronizedBase {
flashIncorrectAction();
return;
}
if(combat.isAttacking(card)) {
combat.removeFromCombat(card);
}
}
declareAttacker(card);
showCombat();
}
@@ -192,12 +190,12 @@ public class InputAttack extends InputSyncronizedBase {
combat.addAttacker(card, currentDefender, this.activeBand);
this.activateBand(this.activeBand);
updateMessage();
CMatchUI.SINGLETON_INSTANCE.fireEvent(new UiEventAttackerDeclared(card, currentDefender));
}
private final void setCurrentDefender(GameEntity def) {
currentDefender = def;
currentDefender = def;
for( GameEntity ge: defenders ) {
if ( ge instanceof Card) {
CMatchUI.SINGLETON_INSTANCE.setUsedToPay((Card)ge, ge == def);
@@ -211,7 +209,7 @@ public class InputAttack extends InputSyncronizedBase {
// update UI
}
private final void activateBand(AttackingBand band) {
if (this.activeBand != null) {
for(Card card : this.activeBand.getAttackers()) {
@@ -219,16 +217,16 @@ public class InputAttack extends InputSyncronizedBase {
}
}
this.activeBand = band;
if (this.activeBand != null) {
for(Card card : this.activeBand.getAttackers()) {
CMatchUI.SINGLETON_INSTANCE.setUsedToPay(card, true);
}
}
// update UI
}
private void updateMessage() {
StringBuilder sb = new StringBuilder();
sb.append(playerDeclares.getName()).append(", ");