mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Deleted 2 steps that never existed in original game rules: COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY and COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY
This commit is contained in:
@@ -60,7 +60,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// don't use instant speed animate abilities outside humans
|
// don't use instant speed animate abilities outside humans
|
||||||
// Combat_Declare_Attackers_InstantAbility step
|
// Combat_Declare_Attackers_InstantAbility step
|
||||||
if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| (game.getCombat().getAttackers().isEmpty()))
|
|| (game.getCombat().getAttackers().isEmpty()))
|
||||||
&& game.getPhaseHandler().isPlayerTurn(opponent)) {
|
&& game.getPhaseHandler().isPlayerTurn(opponent)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
source.setSVar("PayX", Integer.toString(xPay));
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class BecomesBlockedAi extends SpellAbilityAi {
|
|||||||
final Target tgt = sa.getTarget();
|
final Target tgt = sa.getTarget();
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
|
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -743,7 +743,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Save combatants
|
// Save combatants
|
||||||
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
final List<Card> combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
CardLists.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
@@ -799,7 +799,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (origin.equals(ZoneType.Battlefield)
|
if (origin.equals(ZoneType.Battlefield)
|
||||||
&& destination.equals(ZoneType.Exile)
|
&& destination.equals(ZoneType.Exile)
|
||||||
&& (subApi == ApiType.DelayedTrigger || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))
|
&& (subApi == ApiType.DelayedTrigger || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))
|
||||||
&& !(ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY) || sa
|
&& !(ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS) || sa
|
||||||
.isAbility())) {
|
.isAbility())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
} else if (sa.getParam("AILogic").equals("Never")) {
|
} else if (sa.getParam("AILogic").equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
} else if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||||
if (!game.getPhaseHandler().getPhase() .equals(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (!game.getPhaseHandler().getPhase() .equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!game.getPhaseHandler().getPhase()
|
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
.equals(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
|
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// don't use instant speed clone abilities outside humans
|
// don't use instant speed clone abilities outside humans
|
||||||
// Combat_Declare_Attackers_InstantAbility step
|
// Combat_Declare_Attackers_InstantAbility step
|
||||||
if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getCombat().getAttackers().isEmpty())
|
|| game.getCombat().getAttackers().isEmpty())
|
||||||
&& !phase.isPlayerTurn(ai)) {
|
&& !phase.isPlayerTurn(ai)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||||
&& sa instanceof AbilitySub
|
&& sa instanceof AbilitySub
|
||||||
&& (!phase.getNextTurn().equals(ai)
|
&& (!phase.getNextTurn().equals(ai)
|
||||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY))) {
|
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||||
boolean combatants = false;
|
boolean combatants = false;
|
||||||
for (Card c : hList) {
|
for (Card c : hList) {
|
||||||
if (!c.equals(source) && c.isUntapped()) {
|
if (!c.equals(source) && c.isUntapped()) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PhaseHandler handler = game.getPhaseHandler();
|
PhaseHandler handler = game.getPhaseHandler();
|
||||||
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
@@ -119,7 +119,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // Protect combatants
|
} // Protect combatants
|
||||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, game.getCombat())
|
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, game.getCombat())
|
||||||
&& (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || sa.isAbility())
|
&& (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || sa.isAbility())
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai.getOpponent())) {
|
&& game.getPhaseHandler().isPlayerTurn(ai.getOpponent())) {
|
||||||
@@ -198,7 +198,7 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
if (compTargetables.size() > 0) {
|
if (compTargetables.size() > 0) {
|
||||||
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
CardLists.sortByEvaluateCreature(combatants);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) {
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) {
|
||||||
tgt.addTarget(c);
|
tgt.addTarget(c);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
|||||||
// control
|
// control
|
||||||
|
|
||||||
} // Protect combatants
|
} // Protect combatants
|
||||||
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
// Phase Restrictions
|
// Phase Restrictions
|
||||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !ai.getGame().getStack().isEmpty()) {
|
|| !ai.getGame().getStack().isEmpty()) {
|
||||||
// Instant-speed pumps should not be cast outside of combat when the
|
// Instant-speed pumps should not be cast outside of combat when the
|
||||||
// stack is empty
|
// stack is empty
|
||||||
@@ -124,7 +124,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
||||||
// this would be for evasive things like Flying, Unblockable, etc
|
// this would be for evasive things like Flying, Unblockable, etc
|
||||||
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
if (!mandatory
|
if (!mandatory
|
||||||
&& !sa.isTrigger()
|
&& !sa.isTrigger()
|
||||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !(sa.isCurse() && (defense < 0))
|
&& !(sa.isCurse() && (defense < 0))
|
||||||
&& !this.containsNonCombatKeyword(keywords)
|
&& !this.containsNonCombatKeyword(keywords)
|
||||||
&& !sa.hasParam("UntilYourNextTurn")) {
|
&& !sa.hasParam("UntilYourNextTurn")) {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
||||||
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || card.isBlocking())
|
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || card.isBlocking())
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| ph.getPhase().isBefore(PhaseType.MAIN1)
|
|| ph.getPhase().isBefore(PhaseType.MAIN1)
|
||||||
|| CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) {
|
|| CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) {
|
||||||
return false;
|
return false;
|
||||||
@@ -172,14 +172,14 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
// give evasive keywords to creatures that can or do attack
|
// give evasive keywords to creatures that can or do attack
|
||||||
if (evasive) {
|
if (evasive) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (keyword.endsWith("Flying")) {
|
} else if (keyword.endsWith("Flying")) {
|
||||||
if (ph.isPlayerTurn(opp)
|
if (ph.isPlayerTurn(opp)
|
||||||
&& ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
&& ph.getPhase() == PhaseType.COMBAT_DECLARE_ATTACKERS
|
||||||
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|
||||||
&& !card.hasKeyword("Reach")
|
&& !card.hasKeyword("Reach")
|
||||||
&& CombatUtil.canBlock(card)
|
&& CombatUtil.canBlock(card)
|
||||||
@@ -188,7 +188,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
Predicate<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
|
Predicate<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
|| !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||||
Predicates.not(flyingOrReach))) {
|
Predicates.not(flyingOrReach))) {
|
||||||
@@ -196,14 +196,14 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.endsWith("Horsemanship")) {
|
} else if (keyword.endsWith("Horsemanship")) {
|
||||||
if (ph.isPlayerTurn(opp)
|
if (ph.isPlayerTurn(opp)
|
||||||
&& ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
&& ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Horsemanship").isEmpty()
|
&& !CardLists.getKeyword(game.getCombat().getAttackers(), "Horsemanship").isEmpty()
|
||||||
&& CombatUtil.canBlock(card)
|
&& CombatUtil.canBlock(card)
|
||||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||||
"Horsemanship").isEmpty()) {
|
"Horsemanship").isEmpty()) {
|
||||||
@@ -221,7 +221,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else if (keyword.endsWith("Deathtouch")) {
|
} else if (keyword.endsWith("Deathtouch")) {
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)) {
|
if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
List<Card> attackers = combat.getAttackers();
|
List<Card> attackers = combat.getAttackers();
|
||||||
for (Card attacker : attackers) {
|
for (Card attacker : attackers) {
|
||||||
if (CombatUtil.canBlock(attacker, card, combat)
|
if (CombatUtil.canBlock(attacker, card, combat)
|
||||||
@@ -242,7 +242,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} else if (combatRelevant) {
|
} else if (combatRelevant) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| (opp.getCreaturesInPlay().size() < 1)
|
|| (opp.getCreaturesInPlay().size() < 1)
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -250,18 +250,18 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
} else if (keyword.equals("Double Strike")) {
|
} else if (keyword.equals("Double Strike")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (keyword.startsWith("Rampage")) {
|
} else if (keyword.startsWith("Rampage")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (keyword.startsWith("Flanking")) {
|
} else if (keyword.startsWith("Flanking")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
|
||||||
"Flanking").isEmpty()) {
|
"Flanking").isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -269,7 +269,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
} else if (keyword.startsWith("Trample")) {
|
} else if (keyword.startsWith("Trample")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| !CombatUtil.canBeBlocked(card)
|
|| !CombatUtil.canBeBlocked(card)
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() + attack <= 1
|
|| card.getNetCombatDamage() + attack <= 1
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -283,7 +283,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ((ph.isPlayerTurn(opp))
|
if ((ph.isPlayerTurn(opp))
|
||||||
|| !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
|| !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (keyword.endsWith("Wither")) {
|
} else if (keyword.endsWith("Wither")) {
|
||||||
@@ -312,7 +312,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.equals("Reach")) {
|
} else if (keyword.equals("Reach")) {
|
||||||
if (ph.isPlayerTurn(ai)
|
if (ph.isPlayerTurn(ai)
|
||||||
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|
|| CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|
||||||
|| card.hasKeyword("Flying")
|
|| card.hasKeyword("Flying")
|
||||||
|| !CombatUtil.canBlock(card)) {
|
|| !CombatUtil.canBlock(card)) {
|
||||||
@@ -320,7 +320,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.endsWith("CARDNAME can block an additional creature.")) {
|
} else if (keyword.endsWith("CARDNAME can block an additional creature.")) {
|
||||||
if (ph.isPlayerTurn(ai)
|
if (ph.isPlayerTurn(ai)
|
||||||
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)) {
|
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int canBlockNum = 1 + card.getKeywordAmount("CARDNAME can block an additional creature.");
|
int canBlockNum = 1 + card.getKeywordAmount("CARDNAME can block an additional creature.");
|
||||||
@@ -342,7 +342,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.equals("Islandwalk")) {
|
} else if (keyword.equals("Islandwalk")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
|
|| CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty()
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
@@ -350,7 +350,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.equals("Swampwalk")) {
|
} else if (keyword.equals("Swampwalk")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
|
|| CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty()
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
@@ -358,7 +358,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.equals("Mountainwalk")) {
|
} else if (keyword.equals("Mountainwalk")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
|
|| CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty()
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
@@ -366,7 +366,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if (keyword.equals("Forestwalk")) {
|
} else if (keyword.equals("Forestwalk")) {
|
||||||
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking())
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| card.getNetCombatDamage() <= 0
|
|| card.getNetCombatDamage() <= 0
|
||||||
|| CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
|
|| CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
|
||||||
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
|| CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) {
|
||||||
@@ -422,7 +422,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
|
|
||||||
// is the creature blocking and unable to destroy the attacker
|
// is the creature blocking and unable to destroy the attacker
|
||||||
// or would be destroyed itself?
|
// or would be destroyed itself?
|
||||||
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY) && c.isBlocking()) {
|
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && c.isBlocking()) {
|
||||||
if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c)) {
|
if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -435,13 +435,13 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// is the creature unblocked and the spell will pump its power?
|
// is the creature unblocked and the spell will pump its power?
|
||||||
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& game.getCombat().isAttacking(c) && game.getCombat().isUnblocked(c) && attack > 0) {
|
&& game.getCombat().isAttacking(c) && game.getCombat().isUnblocked(c) && attack > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is the creature blocked and the blocker would survive
|
// is the creature blocked and the blocker would survive
|
||||||
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY) && attack > 0
|
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && attack > 0
|
||||||
&& game.getCombat().isAttacking(c)
|
&& game.getCombat().isAttacking(c)
|
||||||
&& game.getCombat().isBlocked(c)
|
&& game.getCombat().isBlocked(c)
|
||||||
&& game.getCombat().getBlockers(c) != null
|
&& game.getCombat().getBlockers(c) != null
|
||||||
@@ -457,7 +457,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
attackerHasTrample |= b.hasKeyword("Trample");
|
attackerHasTrample |= b.hasKeyword("Trample");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phase.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (phase.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& phase.isPlayerTurn(ai.getOpponent())
|
&& phase.isPlayerTurn(ai.getOpponent())
|
||||||
&& c.isBlocking()
|
&& c.isBlocking()
|
||||||
&& defense > 0
|
&& defense > 0
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}); // leaves all creatures that will be destroyed
|
}); // leaves all creatures that will be destroyed
|
||||||
} // -X/-X end
|
} // -X/-X end
|
||||||
else if (power < 0) { // -X/-0
|
else if (power < 0) { // -X/-0
|
||||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
||||||
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -90,7 +90,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
totalPower += Math.min(c.getNetAttack(), power * -1);
|
totalPower += Math.min(c.getNetAttack(), power * -1);
|
||||||
if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY
|
if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS
|
||||||
&& game.getCombat().getUnblockedAttackers().contains(c)) {
|
&& game.getCombat().getUnblockedAttackers().contains(c)) {
|
||||||
if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), game.getCombat())) {
|
if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), game.getCombat())) {
|
||||||
return true;
|
return true;
|
||||||
@@ -113,7 +113,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
} // end Curse
|
} // end Curse
|
||||||
|
|
||||||
// don't use non curse PumpAll after Combat_Begin until AI is improved
|
// don't use non curse PumpAll after Combat_Begin until AI is improved
|
||||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY) || phase.isBefore(PhaseType.MAIN1)) {
|
if (phase.isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isBefore(PhaseType.MAIN1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) {
|
if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (phase.equals(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY) && c.isAttacking()) {
|
if (phase.equals(PhaseType.COMBAT_DECLARE_ATTACKERS) && c.isAttacking()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) {
|
if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
|
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
@@ -136,7 +136,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
CardLists.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
if (compTargetables.size() > 0) {
|
if (compTargetables.size() > 0) {
|
||||||
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||||
CardLists.sortByEvaluateCreature(combatants);
|
CardLists.sortByEvaluateCreature(combatants);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) {
|
if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) {
|
||||||
tgt.addTarget(c);
|
tgt.addTarget(c);
|
||||||
@@ -207,7 +207,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
// can target
|
// can target
|
||||||
|
|
||||||
// choose my best X without regen
|
// choose my best X without regen
|
||||||
if (CardLists.getNotType(compTargetables, "Creature").size() == 0) {
|
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (c.getShield() == 0) {
|
if (c.getShield() == 0) {
|
||||||
tgt.addTarget(c);
|
tgt.addTarget(c);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class RegenerateAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
final List<Card> combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES);
|
final List<Card> combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES);
|
||||||
|
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ((ph.isPlayerTurn(ai)
|
if ((ph.isPlayerTurn(ai)
|
||||||
|| ph.getPhase().isBefore(
|
|| ph.getPhase().isBefore(
|
||||||
PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY))
|
PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn")
|
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("PlayerTurn")
|
||||||
&& !SpellAbilityAi.isSorcerySpeed(sa) && !haste) {
|
&& !SpellAbilityAi.isSorcerySpeed(sa) && !haste) {
|
||||||
return false;
|
return false;
|
||||||
@@ -170,7 +170,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (sa.isAbility()) {
|
if (sa.isAbility()) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
source.setSVar("PayX", Integer.toString(xPay));
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ public class SpellPermanent extends Spell {
|
|||||||
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize())
|
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize())
|
||||||
&& ai.getManaPool().totalMana() <= 0
|
&& ai.getManaPool().totalMana() <= 0
|
||||||
&& (game.getPhaseHandler().isPlayerTurn(ai)
|
&& (game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY)
|
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !card.hasETBTrigger())
|
&& !card.hasETBTrigger())
|
||||||
&& !ComputerUtil.castPermanentInMain1(ai, this)) {
|
&& !ComputerUtil.castPermanentInMain1(ai, this)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ public class FControlGamePlayback extends IGameEventVisitor.Base<Void> {
|
|||||||
|
|
||||||
switch(ev.phase) {
|
switch(ev.phase) {
|
||||||
case COMBAT_END:
|
case COMBAT_END:
|
||||||
case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY:
|
case COMBAT_DECLARE_ATTACKERS:
|
||||||
case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY:
|
case COMBAT_DECLARE_BLOCKERS:
|
||||||
if( fc.getObservedGame().getPhaseHandler().inCombat() )
|
if( fc.getObservedGame().getPhaseHandler().inCombat() )
|
||||||
pauseForEvent(combatDelay);
|
pauseForEvent(combatDelay);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -787,16 +787,6 @@ public class AiController {
|
|||||||
public void onPriorityRecieved() {
|
public void onPriorityRecieved() {
|
||||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||||
switch(phase) {
|
switch(phase) {
|
||||||
case COMBAT_DECLARE_ATTACKERS:
|
|
||||||
declareAttackers();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
|
||||||
final List<Card> blockers = player.getCreaturesInPlay();
|
|
||||||
game.setCombat(ComputerUtilBlock.getBlockers(player, game.getCombat(), blockers));
|
|
||||||
CombatUtil.orderMultipleCombatants(game.getCombat());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MAIN1:
|
case MAIN1:
|
||||||
case MAIN2:
|
case MAIN2:
|
||||||
Log.debug("Computer " + phase.nameForUi);
|
Log.debug("Computer " + phase.nameForUi);
|
||||||
@@ -810,8 +800,14 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void declateBlockers() {
|
||||||
|
final List<Card> blockers = player.getCreaturesInPlay();
|
||||||
|
game.setCombat(ComputerUtilBlock.getBlockers(player, game.getCombat(), blockers));
|
||||||
|
CombatUtil.orderMultipleCombatants(game.getCombat());
|
||||||
|
}
|
||||||
|
|
||||||
private void declareAttackers() {
|
|
||||||
|
public void declareAttackers() {
|
||||||
// 12/2/10(sol) the decision making here has moved to getAttackers()
|
// 12/2/10(sol) the decision making here has moved to getAttackers()
|
||||||
game.setCombat(new AiAttackController(player, player.getOpponent()).getAttackers());
|
game.setCombat(new AiAttackController(player, player.getOpponent()).getAttackers());
|
||||||
|
|
||||||
|
|||||||
@@ -1017,7 +1017,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
return (sa.getSourceCard().isCreature()
|
return (sa.getSourceCard().isCreature()
|
||||||
&& sa.getPayCosts().hasTapCost()
|
&& sa.getPayCosts().hasTapCost()
|
||||||
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY)
|
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !ph.getNextTurn().equals(sa.getActivatingPlayer())));
|
|| !ph.getNextTurn().equals(sa.getActivatingPlayer())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,17 +264,30 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case COMBAT_DECLARE_ATTACKERS:
|
case COMBAT_DECLARE_ATTACKERS:
|
||||||
break;
|
game.getStack().freezeStack();
|
||||||
|
|
||||||
|
playerTurn.getController().declareAttackers();
|
||||||
|
|
||||||
case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY:
|
|
||||||
PhaseUtil.handleDeclareAttackers(game);
|
PhaseUtil.handleDeclareAttackers(game);
|
||||||
|
|
||||||
|
this.bCombat = !game.getCombat().getAttackers().isEmpty();
|
||||||
|
game.getStack().unfreezeStack();
|
||||||
|
this.nCombatsThisTurn++;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
case COMBAT_DECLARE_BLOCKERS:
|
||||||
game.getCombat().verifyCreaturesInPlay();
|
game.getCombat().verifyCreaturesInPlay();
|
||||||
break;
|
game.getStack().freezeStack();
|
||||||
|
|
||||||
case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY:
|
Player p = playerTurn;
|
||||||
|
do {
|
||||||
|
p = game.getNextPlayerAfter(p);
|
||||||
|
if ( game.getCombat().isPlayerAttacked(p) )
|
||||||
|
p.getController().declareBlockers();
|
||||||
|
} while(p != playerTurn);
|
||||||
|
|
||||||
|
game.getStack().unfreezeStack();
|
||||||
PhaseUtil.handleDeclareBlockers(game);
|
PhaseUtil.handleDeclareBlockers(game);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -403,16 +416,6 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
this.nCombatsThisTurn = 0;
|
this.nCombatsThisTurn = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COMBAT_DECLARE_ATTACKERS:
|
|
||||||
this.bCombat = !game.getCombat().getAttackers().isEmpty();
|
|
||||||
game.getStack().unfreezeStack();
|
|
||||||
this.nCombatsThisTurn++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
|
||||||
game.getStack().unfreezeStack();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMBAT_END:
|
case COMBAT_END:
|
||||||
game.getCombat().reset(playerTurn);
|
game.getCombat().reset(playerTurn);
|
||||||
this.getPlayerTurn().resetAttackedThisCombat();
|
this.getPlayerTurn().resetAttackedThisCombat();
|
||||||
@@ -445,9 +448,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
case COMBAT_DECLARE_ATTACKERS:
|
case COMBAT_DECLARE_ATTACKERS:
|
||||||
return playerTurn.isSkippingCombat();
|
return playerTurn.isSkippingCombat();
|
||||||
|
|
||||||
case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY:
|
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
case COMBAT_DECLARE_BLOCKERS:
|
||||||
case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY:
|
|
||||||
case COMBAT_FIRST_STRIKE_DAMAGE:
|
case COMBAT_FIRST_STRIKE_DAMAGE:
|
||||||
case COMBAT_DAMAGE:
|
case COMBAT_DAMAGE:
|
||||||
return !this.inCombat();
|
return !this.inCombat();
|
||||||
@@ -684,20 +685,13 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
while (!game.isGameOver()) { // loop only while is playing
|
while (!game.isGameOver()) { // loop only while is playing
|
||||||
|
|
||||||
boolean givePriority = givePriorityToPlayer ;
|
|
||||||
if ( phase == PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
givePriority = game.getCombat().isPlayerAttacked(pPlayerPriority);
|
|
||||||
|
|
||||||
if ( phase == PhaseType.COMBAT_DECLARE_ATTACKERS && playerTurn != pPlayerPriority )
|
|
||||||
givePriority = false;
|
|
||||||
|
|
||||||
if( DEBUG_PHASES ) {
|
if( DEBUG_PHASES ) {
|
||||||
System.out.println("\t\tStack: " + game.getStack());
|
System.out.println("\t\tStack: " + game.getStack());
|
||||||
System.out.print(FThreads.prependThreadId(debugPrintState(givePriority)));
|
System.out.print(FThreads.prependThreadId(debugPrintState(givePriorityToPlayer)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if( givePriority ) {
|
if( givePriorityToPlayer ) {
|
||||||
if( DEBUG_PHASES )
|
if( DEBUG_PHASES )
|
||||||
sw.start();
|
sw.start();
|
||||||
|
|
||||||
@@ -747,13 +741,6 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Time to handle priority to next player.
|
|
||||||
if ( phase == PhaseType.COMBAT_DECLARE_ATTACKERS || phase == PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
game.getStack().freezeStack();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ public enum PhaseType {
|
|||||||
MAIN1("Main, precombat", "Main1"),
|
MAIN1("Main, precombat", "Main1"),
|
||||||
COMBAT_BEGIN("Begin Combat", "BeginCombat"),
|
COMBAT_BEGIN("Begin Combat", "BeginCombat"),
|
||||||
COMBAT_DECLARE_ATTACKERS("Declare Attackers"),
|
COMBAT_DECLARE_ATTACKERS("Declare Attackers"),
|
||||||
COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY("Declare Attackers - Play Instants and Abilities"),
|
//COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY("Declare Attackers - Play Instants and Abilities"),
|
||||||
COMBAT_DECLARE_BLOCKERS("Declare Blockers"),
|
COMBAT_DECLARE_BLOCKERS("Declare Blockers"),
|
||||||
COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY("Declare Blockers - Play Instants and Abilities"),
|
//COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY("Declare Blockers - Play Instants and Abilities"),
|
||||||
COMBAT_FIRST_STRIKE_DAMAGE("First Strike Damage"),
|
COMBAT_FIRST_STRIKE_DAMAGE("First Strike Damage"),
|
||||||
COMBAT_DAMAGE("Combat Damage"),
|
COMBAT_DAMAGE("Combat Damage"),
|
||||||
COMBAT_END("End Combat", "EndCombat"),
|
COMBAT_END("End Combat", "EndCombat"),
|
||||||
@@ -28,8 +28,8 @@ public enum PhaseType {
|
|||||||
public static final List<PhaseType> ALL_PHASES = Collections.unmodifiableList(
|
public static final List<PhaseType> ALL_PHASES = Collections.unmodifiableList(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
UNTAP, UPKEEP, DRAW, MAIN1,
|
UNTAP, UPKEEP, DRAW, MAIN1,
|
||||||
COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY,
|
COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, //COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY,
|
||||||
COMBAT_DECLARE_BLOCKERS, COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY,
|
COMBAT_DECLARE_BLOCKERS, // COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY,
|
||||||
COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END,
|
COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END,
|
||||||
MAIN2, END_OF_TURN, CLEANUP
|
MAIN2, END_OF_TURN, CLEANUP
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -135,7 +135,10 @@ public abstract class PlayerController {
|
|||||||
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
|
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
|
||||||
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
|
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
|
||||||
|
|
||||||
|
public abstract void declareAttackers();
|
||||||
|
public abstract void declareBlockers();
|
||||||
public abstract void takePriority();
|
public abstract void takePriority();
|
||||||
|
|
||||||
public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
||||||
public abstract boolean payManaOptional(Card card, Cost cost, String prompt, ManaPaymentPurpose purpose);
|
public abstract boolean payManaOptional(Card card, Cost cost, String prompt, ManaPaymentPurpose purpose);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,6 +286,17 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return ComputerUtil.getPartialParisCandidates(player);
|
return ComputerUtil.getPartialParisCandidates(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declareAttackers() {
|
||||||
|
brains.declareAttackers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void declareBlockers() {
|
||||||
|
brains.declateBlockers();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void takePriority() {
|
public void takePriority() {
|
||||||
if ( !game.isGameOver() )
|
if ( !game.isGameOver() )
|
||||||
|
|||||||
@@ -469,17 +469,19 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand);
|
return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
private void showDefaultInput() {
|
public void declareAttackers() {
|
||||||
SpellAbility chosenSa = null;
|
game.getCombat().initiatePossibleDefenders(player.getOpponents());
|
||||||
do {
|
// This input should not modify combat object itself, but should return user choice
|
||||||
if (chosenSa != null) {
|
InputSynchronized inpAttack = new InputAttack(player, game.getCombat());
|
||||||
HumanPlay.playSpellAbility(player, chosenSa);
|
Singletons.getControl().getInputQueue().setInputAndWait(inpAttack);
|
||||||
}
|
}
|
||||||
InputPassPriority defaultInput = new InputPassPriority(player);
|
|
||||||
Singletons.getControl().getInputQueue().setInputAndWait(defaultInput);
|
@Override
|
||||||
chosenSa = defaultInput.getChosenSa();
|
public void declareBlockers() {
|
||||||
} while( chosenSa != null );
|
// This input should not modify combat object itself, but should return user choice
|
||||||
|
InputSynchronized inpBlock = new InputBlock(player, game.getCombat());
|
||||||
|
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -492,22 +494,15 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
} else
|
} else
|
||||||
autoPassCancel(); // probably cancel, since something has happened
|
autoPassCancel(); // probably cancel, since something has happened
|
||||||
|
|
||||||
switch(phase) {
|
SpellAbility chosenSa = null;
|
||||||
|
do {
|
||||||
case COMBAT_DECLARE_ATTACKERS:
|
if (chosenSa != null) {
|
||||||
game.getCombat().initiatePossibleDefenders(player.getOpponents());
|
HumanPlay.playSpellAbility(player, chosenSa);
|
||||||
InputSynchronized inpAttack = new InputAttack(player, game.getCombat());
|
|
||||||
Singletons.getControl().getInputQueue().setInputAndWait(inpAttack);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
|
||||||
InputSynchronized inpBlock = new InputBlock(player, game.getCombat());
|
|
||||||
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
showDefaultInput();
|
|
||||||
}
|
}
|
||||||
|
InputPassPriority defaultInput = new InputPassPriority(player);
|
||||||
|
Singletons.getControl().getInputQueue().setInputAndWait(defaultInput);
|
||||||
|
chosenSa = defaultInput.getChosenSa();
|
||||||
|
} while( chosenSa != null );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -532,10 +532,8 @@ public class VField implements IVDoc<CField> {
|
|||||||
case COMBAT_BEGIN:
|
case COMBAT_BEGIN:
|
||||||
return this.getLblBeginCombat();
|
return this.getLblBeginCombat();
|
||||||
case COMBAT_DECLARE_ATTACKERS:
|
case COMBAT_DECLARE_ATTACKERS:
|
||||||
case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY:
|
|
||||||
return this.getLblDeclareAttackers();
|
return this.getLblDeclareAttackers();
|
||||||
case COMBAT_DECLARE_BLOCKERS:
|
case COMBAT_DECLARE_BLOCKERS:
|
||||||
case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY:
|
|
||||||
return this.getLblDeclareBlockers();
|
return this.getLblDeclareBlockers();
|
||||||
case COMBAT_DAMAGE:
|
case COMBAT_DAMAGE:
|
||||||
return this.getLblCombatDamage();
|
return this.getLblCombatDamage();
|
||||||
|
|||||||
Reference in New Issue
Block a user