mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Don't prompt to declare blockers if no creatures can block
This commit is contained in:
@@ -35,21 +35,18 @@ import forge.gui.input.InputSynchronized;
|
||||
* @version $Id$
|
||||
*/
|
||||
public class InputQueue extends Observable {
|
||||
|
||||
private final BlockingDeque<InputSynchronized> inputStack = new LinkedBlockingDeque<InputSynchronized>();
|
||||
private final InputLockUI inputLock;
|
||||
|
||||
|
||||
public InputQueue() {
|
||||
inputLock = new InputLockUI(this);
|
||||
}
|
||||
|
||||
|
||||
public final void updateObservers() {
|
||||
this.setChanged();
|
||||
this.notifyObservers();
|
||||
}
|
||||
|
||||
|
||||
public final Input getInput() {
|
||||
return inputStack.isEmpty() ? null : this.inputStack.peek();
|
||||
}
|
||||
@@ -81,25 +78,24 @@ public class InputQueue extends Observable {
|
||||
public String printInputStack() {
|
||||
return inputStack.toString();
|
||||
}
|
||||
|
||||
|
||||
public void setInputAndWait(InputSynchronized input) {
|
||||
this.inputStack.push(input);
|
||||
syncPoint();
|
||||
this.updateObservers();
|
||||
|
||||
|
||||
input.awaitLatchRelease();
|
||||
}
|
||||
|
||||
|
||||
void setInput(InputSynchronized input) {
|
||||
this.inputStack.push(input);
|
||||
syncPoint();
|
||||
this.updateObservers();
|
||||
}
|
||||
|
||||
|
||||
public void syncPoint() {
|
||||
public void syncPoint() {
|
||||
synchronized (inputLock) {
|
||||
// acquire and release lock, so that actions from Game thread happen before EDT reads their results
|
||||
// acquire and release lock, so that actions from Game thread happen before EDT reads their results
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,5 +109,4 @@ public class InputQueue extends Observable {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // InputControl
|
||||
|
||||
@@ -76,7 +76,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBlock(final Card blocker, final Combat combat) {
|
||||
|
||||
if (blocker == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +130,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBlock(final Card blocker, final boolean nextTurn) {
|
||||
|
||||
if (blocker == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -176,7 +174,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBeBlocked(final Card attacker, final Combat combat, Player defendingPlayer) {
|
||||
|
||||
if (attacker == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -206,7 +203,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBeBlocked(final Card attacker, Player defender) {
|
||||
|
||||
if (attacker == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -366,7 +362,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static String validateBlocks(final Combat combat, final Player defending) {
|
||||
|
||||
final List<Card> defendersArmy = defending.getCreaturesInPlay();
|
||||
final List<Card> attackers = combat.getAttackers();
|
||||
final List<Card> blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending);
|
||||
@@ -494,6 +489,35 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can a player block with one or more creatures at the moment?
|
||||
/**
|
||||
* <p>
|
||||
* canAttack.
|
||||
* </p>
|
||||
*
|
||||
* @param p
|
||||
* a {@link forge.game.player} object.
|
||||
* @param combat
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBlock(Player p, Combat combat) {
|
||||
List<Card> creatures = p.getCreaturesInPlay();
|
||||
if (creatures.isEmpty()) { return false; }
|
||||
|
||||
List<Card> attackers = combat.getAttackers();
|
||||
if (attackers.isEmpty()) { return false; }
|
||||
|
||||
for (Card c : creatures) {
|
||||
for (Card a : attackers) {
|
||||
if (CombatUtil.canBlock(a, c, combat)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// can the blocker block the attacker given the combat state?
|
||||
/**
|
||||
* <p>
|
||||
@@ -509,7 +533,6 @@ public class CombatUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canBlock(final Card attacker, final Card blocker, final Combat combat) {
|
||||
|
||||
if (attacker == null || blocker == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -614,7 +637,6 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedBy ")) {
|
||||
final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy ");
|
||||
final String parse = attacker.getKeyword().get(keywordPosition).toString();
|
||||
|
||||
@@ -555,13 +555,16 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.attackerChoosesBlockers)) {
|
||||
whoDeclaresBlockers = combat.getAttackingPlayer();
|
||||
}
|
||||
if ( combat.isPlayerAttacked(p) ) {
|
||||
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
||||
} else
|
||||
continue;
|
||||
if (combat.isPlayerAttacked(p)) {
|
||||
if (CombatUtil.canBlock(p, combat)) {
|
||||
whoDeclaresBlockers.getController().declareBlockers(p, combat);
|
||||
}
|
||||
}
|
||||
else { continue; }
|
||||
|
||||
if ( game.isGameOver() ) // they just like to close window at any moment
|
||||
if (game.isGameOver()) { // they just like to close window at any moment
|
||||
return;
|
||||
}
|
||||
|
||||
// Handles removing cards like Mogg Flunkies from combat if group block
|
||||
// didn't occur
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
public boolean isUiSetToSkipPhase(final Player turn, final PhaseType phase) {
|
||||
return !CMatchUI.SINGLETON_INSTANCE.stopAtPhase(turn, phase);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses GUI to learn which spell the player (human in our case) would like to play
|
||||
*/
|
||||
@@ -145,7 +145,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
});
|
||||
menu.show(triggerEvent.getComponent(), triggerEvent.getX(), triggerEvent.getY());
|
||||
}
|
||||
|
||||
|
||||
return null; //delay ability until choice made
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
|
||||
CardPool newSb = new CardPool();
|
||||
List<PaperCard> newMain = null;
|
||||
|
||||
|
||||
if (sbSize == 0 && mainSize == deckMinSize) {
|
||||
// Skip sideboard loop if there are no sideboarding opportunities
|
||||
newMain = main.toFlatList();
|
||||
@@ -223,7 +223,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
for(PaperCard c : newMain) {
|
||||
newSb.remove(c);
|
||||
}
|
||||
|
||||
|
||||
Deck res = (Deck)deck.copyTo(deck.getName());
|
||||
res.getMain().clear();
|
||||
res.getMain().add(newMain);
|
||||
@@ -239,7 +239,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
@Override
|
||||
public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder) {
|
||||
// Attacker is a poor name here, since the creature assigning damage
|
||||
// could just as easily be the blocker.
|
||||
// could just as easily be the blocker.
|
||||
Map<Card, Integer> map;
|
||||
if (defender != null && assignDamageAsIfNotBlocked(attacker)) {
|
||||
map = new HashMap<Card, Integer>();
|
||||
@@ -254,7 +254,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private final boolean assignDamageAsIfNotBlocked(Card attacker) {
|
||||
return attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")
|
||||
|| (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||
@@ -270,8 +270,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
for(int i = canChooseZero ? 0 : 1; i < 10; i++)
|
||||
options.add(Integer.valueOf(i));
|
||||
options.add("Other amount");
|
||||
|
||||
|
||||
|
||||
Object chosen = GuiChoose.oneOrNone("Choose " + announce + " for " + ability.getSourceCard().getName(), options);
|
||||
if (chosen instanceof Integer || chosen == null)
|
||||
return (Integer)chosen;
|
||||
@@ -280,27 +279,26 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
while(true){
|
||||
String str = JOptionPane.showInputDialog(JOptionPane.getRootFrame(), message, ability.getSourceCard().getName(), JOptionPane.QUESTION_MESSAGE);
|
||||
if (null == str) return null; // that is 'cancel'
|
||||
|
||||
|
||||
if(StringUtils.isNumeric(str)) {
|
||||
Integer val = Integer.valueOf(str);
|
||||
if (val == 0 && canChooseZero || val > 0)
|
||||
if (val == 0 && canChooseZero || val > 0)
|
||||
return val;
|
||||
}
|
||||
GuiDialog.message("You have to enter a valid number", "Announce value");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> valid, String message) {
|
||||
String outerMessage = "Select %d " + message + "(s) to sacrifice";
|
||||
return choosePermanentsTo(min, max, valid, outerMessage);
|
||||
return choosePermanentsTo(min, max, valid, outerMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> valid, String message) {
|
||||
String outerMessage = "Select %d " + message + "(s) to be destroyed";
|
||||
return choosePermanentsTo(min, max, valid, outerMessage);
|
||||
return choosePermanentsTo(min, max, valid, outerMessage);
|
||||
}
|
||||
|
||||
private List<Card> choosePermanentsTo(int min, int max, List<Card> valid, String outerMessage) {
|
||||
@@ -322,17 +320,17 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
@Override
|
||||
public List<Card> chooseCardsForEffect(List<Card> sourceList, SpellAbility sa, String title, int amount,
|
||||
boolean isOptional) {
|
||||
// If only one card to choose, use a dialog box.
|
||||
// Otherwise, use the order dialog to be able to grab multiple cards in one shot
|
||||
// If only one card to choose, use a dialog box.
|
||||
// Otherwise, use the order dialog to be able to grab multiple cards in one shot
|
||||
if (amount == 1) {
|
||||
return Lists.newArrayList(chooseSingleCardForEffect(sourceList, sa, title, isOptional));
|
||||
}
|
||||
|
||||
|
||||
GuiUtils.setPanelSelection(sa.getSourceCard());
|
||||
int remaining = isOptional ? -1 : Math.max(sourceList.size() - amount, 0);
|
||||
return GuiChoose.order(title, "Chosen Cards", remaining, sourceList, null, sa.getSourceCard());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCardForEffect(Collection<Card> options, SpellAbility sa, String title, boolean isOptional) {
|
||||
// Human is supposed to read the message and understand from it what to choose
|
||||
@@ -340,7 +338,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
return null;
|
||||
if ( !isOptional && options.size() == 1 )
|
||||
return Iterables.getFirst(options, null);
|
||||
|
||||
|
||||
boolean canUseSelectCardsInput = true;
|
||||
for(Card c : options) {
|
||||
Zone cz = c.getZone();
|
||||
@@ -359,7 +357,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
Singletons.getControl().getInputQueue().setInputAndWait(input);
|
||||
return Iterables.getFirst(input.getSelected(), null);
|
||||
}
|
||||
|
||||
|
||||
return isOptional ? GuiChoose.oneOrNone(title, options) : GuiChoose.one(title, options);
|
||||
}
|
||||
|
||||
@@ -422,7 +420,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
@Override
|
||||
public void reveal(String string, Collection<Card> cards, ZoneType zone, Player owner) {
|
||||
String message = string;
|
||||
if ( StringUtils.isBlank(message) )
|
||||
if ( StringUtils.isBlank(message) )
|
||||
message = String.format("Looking at %s's %s", owner, zone);
|
||||
GuiChoose.oneOrNone(message, cards);
|
||||
}
|
||||
@@ -431,13 +429,13 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
|
||||
List<Card> toBottom = null;
|
||||
List<Card> toTop = null;
|
||||
|
||||
|
||||
if (topN.size() == 1) {
|
||||
if (willPutCardOnTop(topN.get(0)))
|
||||
toTop = topN;
|
||||
else
|
||||
else
|
||||
toBottom = topN;
|
||||
} else {
|
||||
} else {
|
||||
toBottom = GuiChoose.order("Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null, null);
|
||||
topN.removeAll(toBottom);
|
||||
if ( topN.isEmpty() )
|
||||
@@ -633,7 +631,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
return;
|
||||
} else
|
||||
autoPassCancel(); // probably cancel, since something has happened
|
||||
|
||||
|
||||
SpellAbility chosenSa = null;
|
||||
do {
|
||||
if (chosenSa != null) {
|
||||
@@ -709,8 +707,8 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
}
|
||||
|
||||
// end of not related candidates for move.
|
||||
|
||||
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseBinary(java.lang.String, boolean)
|
||||
*/
|
||||
@@ -760,21 +758,21 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility saSpellskite, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
|
||||
if( allTargets.size() < 2)
|
||||
return Iterables.getFirst(allTargets, null);
|
||||
|
||||
|
||||
final Function<Pair<SpellAbilityStackInstance, GameObject>, String> fnToString = new Function<Pair<SpellAbilityStackInstance, GameObject>, String>() {
|
||||
@Override
|
||||
public String apply(Pair<SpellAbilityStackInstance, GameObject> targ) {
|
||||
return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
List<Pair<SpellAbilityStackInstance, GameObject>> chosen = GuiChoose.getChoices(saSpellskite.getSourceCard().getName(), 1, 1, allTargets, null, fnToString);
|
||||
return Iterables.getFirst(chosen, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyOfValue(SpellAbility sa, GameObject realtedTarget, String value) {
|
||||
String message = formatNotificationMessage(sa, realtedTarget, value);
|
||||
String message = formatNotificationMessage(sa, realtedTarget, value);
|
||||
GuiDialog.message(message, sa.getSourceCard().getName());
|
||||
}
|
||||
|
||||
@@ -790,8 +788,8 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
return String.format(random ? "Randomly chosen number for %s is %s" : "%s choses number: %s", mayBeYou(target, player), value);
|
||||
case FlipACoin:
|
||||
String flipper = StringUtils.capitalize(mayBeYou(target, player));
|
||||
return sa.hasParam("NoCall")
|
||||
? String.format("%s flip comes up %s", Lang.getPossesive(flipper), value)
|
||||
return sa.hasParam("NoCall")
|
||||
? String.format("%s flip comes up %s", Lang.getPossesive(flipper), value)
|
||||
: String.format("%s %s the flip", flipper, Lang.joinVerb(flipper, value));
|
||||
case Protection:
|
||||
String choser = StringUtils.capitalize(mayBeYou(target, player));
|
||||
@@ -800,7 +798,7 @@ public class PlayerControllerHuman extends PlayerController {
|
||||
return String.format("%s effect's value for %s is %s", sa.getSourceCard().getName(), mayBeYou(target, player), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String mayBeYou(GameObject what, Player you) {
|
||||
return what == you ? "you" : what.toString();
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ public abstract class InputBase implements java.io.Serializable, Input {
|
||||
private boolean finished = false;
|
||||
protected final boolean isFinished() { return finished; }
|
||||
protected final void setFinished() { finished = true; }
|
||||
|
||||
|
||||
// showMessage() is always the first method called
|
||||
@Override
|
||||
public final void showMessageInitial() {
|
||||
finished = false;
|
||||
showMessage();
|
||||
}
|
||||
|
||||
|
||||
protected abstract void showMessage();
|
||||
|
||||
@Override
|
||||
@@ -58,7 +58,6 @@ public abstract class InputBase implements java.io.Serializable, Input {
|
||||
@Override
|
||||
public void selectAbility(SpellAbility ab) { }
|
||||
|
||||
|
||||
@Override
|
||||
public final void selectButtonCancel() {
|
||||
if( isFinished() ) return;
|
||||
@@ -82,11 +81,10 @@ public abstract class InputBase implements java.io.Serializable, Input {
|
||||
protected void onOk() {}
|
||||
|
||||
// to remove need for CMatchUI dependence
|
||||
protected final void showMessage(String message) {
|
||||
protected final void showMessage(String message) {
|
||||
CMatchUI.SINGLETON_INSTANCE.showMessage(message);
|
||||
}
|
||||
|
||||
|
||||
protected final void flashIncorrectAction() {
|
||||
SDisplayUtil.remind(VPrompt.SINGLETON_INSTANCE);
|
||||
}
|
||||
@@ -94,7 +92,7 @@ public abstract class InputBase implements java.io.Serializable, Input {
|
||||
protected String getTurnPhasePriorityMessage(Game game) {
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
sb.append("Priority: ").append(ph.getPriorityPlayer()).append("\n");
|
||||
sb.append("Turn ").append(ph.getTurn()).append(" (").append(ph.getPlayerTurn()).append(")\n");
|
||||
sb.append("Phase: ").append(ph.getPhase().nameForUi).append("\n");
|
||||
|
||||
@@ -46,7 +46,7 @@ public class InputBlock extends InputSyncronizedBase {
|
||||
private final Combat combat;
|
||||
private final Player defender;
|
||||
private final Player declarer;
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for Constructor.
|
||||
* @param priority
|
||||
@@ -62,24 +62,24 @@ public class InputBlock extends InputSyncronizedBase {
|
||||
protected final void showMessage() {
|
||||
// could add "Reset Blockers" button
|
||||
ButtonUtil.enableOnlyOk();
|
||||
|
||||
String prompt = declarer == defender ? "declare blockers." : "declare blockers for " + defender.getName();
|
||||
|
||||
|
||||
String prompt = declarer == defender ? "declare blockers." : "declare blockers for " + defender.getName();
|
||||
|
||||
final StringBuilder sb = new StringBuilder(declarer.getName());
|
||||
sb.append(", ").append(prompt).append("\n\n");
|
||||
|
||||
if (this.currentAttacker == null) {
|
||||
sb.append("To Block, click on your opponent's attacker first, then your blocker(s).\n");
|
||||
sb.append("To cancel a block right-click on your blocker");
|
||||
showMessage(sb.toString());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
final String attackerName = this.currentAttacker.isFaceDown() ? "Morph" : this.currentAttacker.getName();
|
||||
sb.append("Select a creature to block ").append(attackerName).append(" (");
|
||||
sb.append(this.currentAttacker.getUniqueNumber()).append("). ");
|
||||
sb.append("To cancel a block right-click on your blocker");
|
||||
showMessage(sb.toString());
|
||||
}
|
||||
|
||||
showMessage(sb.toString());
|
||||
CMatchUI.SINGLETON_INSTANCE.showCombat(combat);
|
||||
}
|
||||
|
||||
@@ -100,14 +100,13 @@ public class InputBlock extends InputSyncronizedBase {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void onCardSelected(final Card card, final MouseEvent triggerEvent) {
|
||||
|
||||
if (triggerEvent.getButton() == 3 && card.getController() == defender) {
|
||||
combat.removeFromCombat(card);
|
||||
CMatchUI.SINGLETON_INSTANCE.fireEvent(new UiEventBlockerAssigned(card, (Card)null));
|
||||
} else {
|
||||
} else {
|
||||
// is attacking?
|
||||
boolean isCorrectAction = false;
|
||||
|
||||
|
||||
if (combat.isAttacking(card)) {
|
||||
setCurrentAttacker(card);
|
||||
isCorrectAction = true;
|
||||
@@ -121,7 +120,7 @@ public class InputBlock extends InputSyncronizedBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!isCorrectAction) {
|
||||
flashIncorrectAction();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user