diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
index accd75afab7..28f5cc117e2 100644
--- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
@@ -69,7 +69,6 @@ public enum SpellApiToAi {
.put(ApiType.DayTime, DayTimeAi.class)
.put(ApiType.DealDamage, DamageDealAi.class)
.put(ApiType.Debuff, DebuffAi.class)
- .put(ApiType.DeclareCombatants, CannotPlayAi.class)
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
.put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class)
diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
deleted file mode 100644
index aa68c75f978..00000000000
--- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Forge: Play Magic: the Gathering.
- * Copyright (C) 2011 Forge Team
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package forge.game;
-
-/**
- * The Enum GlobalRuleChange.
- */
-public enum GlobalRuleChange {
-
- attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat.");
-
- private final String ruleText;
-
- GlobalRuleChange(String text) {
- ruleText = text;
- }
-
- public static GlobalRuleChange fromString(String text) {
- for (final GlobalRuleChange v : GlobalRuleChange.values()) {
- if (v.ruleText.compareToIgnoreCase(text) == 0) {
- return v;
- }
- }
-
- throw new RuntimeException("Element " + text + " not found in GlobalRuleChange enum");
- }
-}
diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java
index 831fda76d9d..d5890e5ff6c 100644
--- a/forge-game/src/main/java/forge/game/StaticEffect.java
+++ b/forge-game/src/main/java/forge/game/StaticEffect.java
@@ -198,6 +198,9 @@ public class StaticEffect {
p.removeAdditionalVote(getTimestamp());
p.removeAdditionalOptionalVote(getTimestamp());
p.removeAdditionalVillainousChoices(getTimestamp());
+
+ p.removeDeclaresAttackers(getTimestamp());
+ p.removeDeclaresBlockers(getTimestamp());
}
// modify the affected card
diff --git a/forge-game/src/main/java/forge/game/StaticEffects.java b/forge-game/src/main/java/forge/game/StaticEffects.java
index af677db1a3f..0b56db9cfbe 100644
--- a/forge-game/src/main/java/forge/game/StaticEffects.java
+++ b/forge-game/src/main/java/forge/game/StaticEffects.java
@@ -17,7 +17,6 @@
*/
package forge.game;
-import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
@@ -39,12 +38,8 @@ public class StaticEffects {
// **************** StaticAbility system **************************
private final Map staticEffects = Maps.newHashMap();
- //Global rule changes
- private final Set ruleChanges = EnumSet.noneOf(GlobalRuleChange.class);
public final void clearStaticEffects(final Set affectedCards) {
- ruleChanges.clear();
-
// remove all static effects
for (final StaticEffect se : staticEffects.values()) {
Iterables.addAll(affectedCards, se.remove());
@@ -52,14 +47,6 @@ public class StaticEffects {
this.staticEffects.clear();
}
- public void setGlobalRuleChange(final GlobalRuleChange change) {
- this.ruleChanges.add(change);
- }
-
- public boolean getGlobalRuleChange(final GlobalRuleChange change) {
- return this.ruleChanges.contains(change);
- }
-
/**
* Add a static effect to the list of static effects.
*
diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java
index 4f0c3a25efc..8294bb551f0 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -67,7 +67,6 @@ public enum ApiType {
DealDamage (DamageDealEffect.class),
DayTime (DayTimeEffect.class),
Debuff (DebuffEffect.class),
- DeclareCombatants (DeclareCombatantsEffect.class),
DelayedTrigger (DelayedTriggerEffect.class),
Destroy (DestroyEffect.class),
DestroyAll (DestroyAllEffect.class),
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java
deleted file mode 100644
index f7d5b928985..00000000000
--- a/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package forge.game.ability.effects;
-
-import java.util.List;
-
-import forge.GameCommand;
-import forge.game.ability.SpellAbilityEffect;
-import forge.game.phase.PhaseHandler;
-import forge.game.player.Player;
-import forge.game.spellability.SpellAbility;
-import forge.util.Lang;
-import forge.util.TextUtil;
-
-public class DeclareCombatantsEffect extends SpellAbilityEffect {
-
- @Override
- protected String getStackDescription(SpellAbility sa) {
- List tgtPlayers = getDefinedPlayersOrTargeted(sa);
- boolean attackers = sa.hasParam("DeclareAttackers");
- boolean blockers = sa.hasParam("DeclareBlockers");
- String what = Lang.joinHomogenous(
- attackers
- ? "which creatures attack"
- : null,
- blockers
- ? "which creatures block this turn and how those creatures block"
- : null
- );
- String duration = "EndOfTurn".equals(sa.getParam("Until")) ? "turn" : "combat";
- return TextUtil.concatWithSpace(Lang.joinHomogenous(tgtPlayers),Lang.joinVerb(tgtPlayers, "choose"),what,"this",TextUtil.addSuffix(duration,"."));
- }
-
- @Override
- public void resolve(SpellAbility sa) {
- List tgtPlayers = getDefinedPlayersOrTargeted(sa);
-
- final boolean attackers = sa.hasParam("DeclareAttackers");
- final boolean blockers = sa.hasParam("DeclareBlockers");
-
- String until = sa.getParam("Until");
- boolean untilEoT = "EndOfTurn".equals(until);
-
- for (Player p : tgtPlayers) { // Obviously the last player will be applied
- final PhaseHandler ph = p.getGame().getPhaseHandler();
- if (attackers) ph.setPlayerDeclaresAttackers(p);
- if (blockers) ph.setPlayerDeclaresBlockers(p);
-
- GameCommand removeOverrides = new GameCommand() {
- private static final long serialVersionUID = -8064627517852651016L;
-
- @Override
- public void run() {
- if (attackers) ph.setPlayerDeclaresAttackers(null);
- if (blockers) ph.setPlayerDeclaresBlockers(null);
- }
- };
-
- if (untilEoT)
- p.getGame().getEndOfTurn().addUntil(removeOverrides);
- else
- p.getGame().getEndOfCombat().addUntil(removeOverrides);
- }
-
- }
-
-}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
index 4d6efae1e97..67bfc46627a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
@@ -44,8 +44,6 @@ public class RestartGameEffect extends SpellAbilityEffect {
trigHandler.suppressMode(TriggerType.Shuffled);
game.getPhaseHandler().restart();
- game.getPhaseHandler().setPlayerDeclaresAttackers(null);
- game.getPhaseHandler().setPlayerDeclaresBlockers(null);
game.getUntap().clearCommands();
game.getUpkeep().clearCommands();
game.getEndOfCombat().clearCommands();
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index e7405e1c4f9..7e51cac2c97 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -42,6 +42,8 @@ import forge.util.CollectionSuppliers;
import forge.util.TextUtil;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
+
+import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.StopWatch;
import java.util.*;
@@ -81,9 +83,6 @@ public class PhaseHandler implements java.io.Serializable {
private transient Combat combat = null;
private boolean bRepeatCleanup = false;
- private transient Player playerDeclaresBlockers = null;
- private transient Player playerDeclaresAttackers = null;
-
/** The need to next phase. */
private boolean givePriorityToPlayer = false;
@@ -524,9 +523,7 @@ public class PhaseHandler implements java.io.Serializable {
}
private void declareAttackersTurnBasedAction() {
- final Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost()
- ? playerTurn
- : playerDeclaresAttackers;
+ final Player whoDeclares = ObjectUtils.firstNonNull(playerTurn.getDeclaresAttackers(), playerTurn);
if (CombatUtil.canAttack(playerTurn)) {
boolean success = false;
@@ -654,10 +651,7 @@ public class PhaseHandler implements java.io.Serializable {
do {
p = game.getNextPlayerAfter(p);
// Apply Odric's effect here
- Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers;
- if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.attackerChoosesBlockers)) {
- whoDeclaresBlockers = combat.getAttackingPlayer();
- }
+ Player whoDeclaresBlockers = ObjectUtils.firstNonNull(p.getDeclaresBlockers(), p);
if (combat.isPlayerAttacked(p)) {
if (CombatUtil.canBlock(p, combat)) {
// Replacement effects (for Camouflage)
@@ -1236,14 +1230,6 @@ public class PhaseHandler implements java.io.Serializable {
givePriorityToPlayer = true;
}
- public final void setPlayerDeclaresAttackers(Player player) {
- playerDeclaresAttackers = player;
- }
-
- public final void setPlayerDeclaresBlockers(Player player) {
- playerDeclaresBlockers = player;
- }
-
public void endCombat() {
game.getEndOfCombat().executeUntil();
game.getEndOfCombat().executeUntilEndOfPhase(playerTurn);
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index c3c0a18919f..19992f4c9ca 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -196,6 +196,9 @@ public class Player extends GameEntity implements Comparable {
private SortedSet controlVotes = Sets.newTreeSet();
private Map additionalVillainousChoices = Maps.newHashMap();
+ private NavigableMap declaresAttackers = Maps.newTreeMap();
+ private NavigableMap declaresBlockers = Maps.newTreeMap();
+
private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view;
@@ -906,7 +909,7 @@ public class Player extends GameEntity implements Comparable {
setCounters(counterName, newValue, null, true);
getGame().addCounterRemovedThisTurn(counterName, this, delta);
-
+
/* TODO Run triggers when something cares
int curCounters = oldValue;
for (int i = 0; i < delta && curCounters != 0; i++) {
@@ -2303,7 +2306,7 @@ public class Player extends GameEntity implements Comparable {
public final void addSpellCastSinceBegOfYourLastTurn(List spells) {
spellsCastSinceBeginningOfLastTurn.addAll(spells);
}
-
+
public final int getSpellsCastThisTurn() {
return spellsCastThisTurn;
}
@@ -3894,4 +3897,30 @@ public class Player extends GameEntity implements Comparable {
this.visitAttractions(total);
}
+
+ public void addDeclaresAttackers(long ts, Player p) {
+ this.declaresAttackers.put(ts, p);
+ }
+
+ public void removeDeclaresAttackers(long ts) {
+ this.declaresAttackers.remove(ts);
+ }
+
+ public Player getDeclaresAttackers() {
+ Map.Entry e = declaresAttackers.lastEntry();
+ return e == null ? null : e.getValue();
+ }
+
+ public void addDeclaresBlockers(long ts, Player p) {
+ this.declaresBlockers.put(ts, p);
+ }
+
+ public void removeDeclaresBlockers(long ts) {
+ this.declaresBlockers.remove(ts);
+ }
+
+ public Player getDeclaresBlockers() {
+ Map.Entry e = declaresBlockers.lastEntry();
+ return e == null ? null : e.getValue();
+ }
}
diff --git a/forge-game/src/main/java/forge/game/player/PlayerProperty.java b/forge-game/src/main/java/forge/game/player/PlayerProperty.java
index 0cb78e63007..8560ff530be 100644
--- a/forge-game/src/main/java/forge/game/player/PlayerProperty.java
+++ b/forge-game/src/main/java/forge/game/player/PlayerProperty.java
@@ -175,6 +175,10 @@ public class PlayerProperty {
if (!source.getDamageHistory().hasAttackedThisTurn(player)) {
return false;
}
+ } else if (property.equals("Attacking")) {
+ if (game.getCombat() == null || !player.equals(game.getCombat().getAttackingPlayer())) {
+ return false;
+ }
} else if (property.equals("Defending")) {
if (game.getCombat() == null || !game.getCombat().getAttackersAndDefenders().values().contains(player)) {
return false;
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
index 2eb046c689d..f3abfe6607b 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
@@ -165,7 +165,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
if (hasParam("AddHiddenKeyword") || hasParam("MayPlay")
|| hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")
- || hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) {
+ || hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")
+ || hasParam("DeclaresAttackers") || hasParam("DeclaresBlockers")) {
layers.add(StaticAbilityLayer.RULES);
}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
index a6fbdf79a1e..ed25ebd56f0 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
@@ -26,7 +26,6 @@ import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.*;
import forge.game.Game;
-import forge.game.GlobalRuleChange;
import forge.game.StaticEffect;
import forge.game.StaticEffects;
import forge.game.ability.AbilityUtils;
@@ -142,11 +141,6 @@ public final class StaticAbilityContinuous {
boolean mayPlayGrantZonePermissions = true;
Integer mayPlayLimit = null;
- //Global rules changes
- if (layer == StaticAbilityLayer.RULES && params.containsKey("GlobalRule")) {
- effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule")));
- }
-
if (layer == StaticAbilityLayer.SETPT || layer == StaticAbilityLayer.CHARACTERISTIC) {
if (params.containsKey("SetPower")) {
setP = params.get("SetPower");
@@ -599,6 +593,17 @@ public final class StaticAbilityContinuous {
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalVillainousChoices(se.getTimestamp(), add);
}
+
+ if (params.containsKey("DeclaresAttackers")) {
+ PlayerCollection players = AbilityUtils.getDefinedPlayers(hostCard, params.get("DeclaresAttackers"), stAb);
+ if (!players.isEmpty())
+ p.addDeclaresAttackers(se.getTimestamp(), players.getFirst());
+ }
+ if (params.containsKey("DeclaresBlockers")) {
+ PlayerCollection players = AbilityUtils.getDefinedPlayers(hostCard, params.get("DeclaresBlockers"), stAb);
+ if (!players.isEmpty())
+ p.addDeclaresBlockers(se.getTimestamp(), players.getFirst());
+ }
}
}
diff --git a/forge-gui/res/adventure/common/maps/map/cave/cave_kobold_floor2.tmx b/forge-gui/res/adventure/common/maps/map/cave/cave_kobold_floor2.tmx
index 99f3fa81f44..150da89a122 100644
--- a/forge-gui/res/adventure/common/maps/map/cave/cave_kobold_floor2.tmx
+++ b/forge-gui/res/adventure/common/maps/map/cave/cave_kobold_floor2.tmx
@@ -32,7 +32,7 @@
diff --git a/forge-gui/res/adventure/common/maps/map/fort/fort_colorless_7_multilevel3.tmx b/forge-gui/res/adventure/common/maps/map/fort/fort_colorless_7_multilevel3.tmx
index 4af1cd7edf7..06685db5322 100644
--- a/forge-gui/res/adventure/common/maps/map/fort/fort_colorless_7_multilevel3.tmx
+++ b/forge-gui/res/adventure/common/maps/map/fort/fort_colorless_7_multilevel3.tmx
@@ -36,6 +36,7 @@