diff --git a/.gitattributes b/.gitattributes index a30e915ca2a..abfd68943bb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -29,6 +29,7 @@ forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java -text forge-ai/src/main/java/forge/ai/PlayerControllerAi.java -text forge-ai/src/main/java/forge/ai/SpellAbilityAi.java -text forge-ai/src/main/java/forge/ai/SpellApiToAi.java -text +forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java -text forge-ai/src/main/java/forge/ai/ability/AddPhaseAi.java -text forge-ai/src/main/java/forge/ai/ability/AddTurnAi.java svneol=native#text/plain forge-ai/src/main/java/forge/ai/ability/AlwaysPlayAi.java -text @@ -292,6 +293,7 @@ forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java -text forge-game/src/main/java/forge/game/ability/SpellApiBased.java -text forge-game/src/main/java/forge/game/ability/StaticAbilityApiBased.java -text forge-game/src/main/java/forge/game/ability/effects/AbandonEffect.java -text +forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java -text forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java -text forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java -text forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java -text @@ -4424,6 +4426,7 @@ forge-gui/res/cardsfolder/d/dragonspeaker_shaman.txt svneol=native#text/plain forge-gui/res/cardsfolder/d/dragonstalker.txt svneol=native#text/plain forge-gui/res/cardsfolder/d/dragonstorm.txt svneol=native#text/plain forge-gui/res/cardsfolder/d/drain_life.txt -text +forge-gui/res/cardsfolder/d/drain_power.txt -text forge-gui/res/cardsfolder/d/drain_the_well.txt svneol=native#text/plain forge-gui/res/cardsfolder/d/draining_whelk.txt -text forge-gui/res/cardsfolder/d/drainpipe_vermin.txt -text diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 75af5ac1ee0..832561e7e94 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -14,6 +14,7 @@ public enum SpellApiToAi { static { apiToClass.put(ApiType.Abandon, AlwaysPlayAi.class); + apiToClass.put(ApiType.ActivateAbility, ActivateAbilityAi.class); apiToClass.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class); apiToClass.put(ApiType.AddPhase, AddPhaseAi.class); apiToClass.put(ApiType.AddTurn, AddTurnAi.class); diff --git a/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java new file mode 100644 index 00000000000..8d32835d5b2 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java @@ -0,0 +1,100 @@ +package forge.ai.ability; + +import forge.ai.SpellAbilityAi; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; +import forge.util.MyRandom; + +import java.util.List; +import java.util.Random; + +public class ActivateAbilityAi extends SpellAbilityAi { + + @Override + protected boolean canPlayAI(Player ai, SpellAbility sa) { + // AI cannot use this properly until he can use SAs during Humans turn + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Card source = sa.getHostCard(); + final Player opp = ai.getOpponent(); + final Random r = MyRandom.getRandom(); + boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); + + List list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); + if (list.isEmpty()) { + return false; + } + + if (tgt == null) { + final List defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); + + if (!defined.contains(opp)) { + return false; + } + } else { + sa.resetTargets(); + sa.getTargets().add(opp); + } + + return randomReturn; + } + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + final Player opp = ai.getOpponent(); + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Card source = sa.getHostCard(); + + if (null == tgt) { + if (mandatory) { + return true; + } else { + final List defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); + + if (!defined.contains(opp)) { + return false; + } + } + + return true; + } else { + sa.resetTargets(); + sa.getTargets().add(opp); + } + + return true; + } + + @Override + public boolean chkAIDrawback(SpellAbility sa, Player ai) { + // AI cannot use this properly until he can use SAs during Humans turn + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Card source = sa.getHostCard(); + + boolean randomReturn = true; + + if (tgt == null) { + final List defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); + + if (defined.contains(ai)) { + return false; + } + } else { + sa.resetTargets(); + sa.getTargets().add(ai.getOpponent()); + } + + return randomReturn; + } + + @Override + public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells) { + return spells.get(0); + } +} diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java index 001e725ac54..029f9e426e3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -30,8 +30,9 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return canPlayAI(aiPlayer, sa); - } - + } + + @Override public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells) { final String logic = sa.getParam("AILogic"); if ("Random".equals(logic)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java index 241ed739733..4ded619a314 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java @@ -60,5 +60,34 @@ public class LifeExchangeAi extends SpellAbilityAi { return ((r.nextFloat() < .6667) && chance); } + /** + *

+ * exchangeLifeDoTriggerAINoCost. + *

+ * @param sa + * a {@link forge.game.spellability.SpellAbility} object. + * @param mandatory + * a boolean. + * @param af + * a {@link forge.game.ability.AbilityFactory} object. + * + * @return a boolean. + */ + @Override + protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, + final boolean mandatory) { + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + Player opp = ai.getOpponent(); + if (tgt != null) { + sa.resetTargets(); + if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { + sa.getTargets().add(opp); + } else { + return false; + } + } + return true; + } } 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 7fb41ee6364..dc21c12e0c4 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -13,6 +13,7 @@ import java.util.TreeMap; */ public enum ApiType { Abandon (AbandonEffect.class), + ActivateAbility (ActivateAbilityEffect.class), AddOrRemoveCounter (CountersPutOrRemoveEffect.class), AddPhase (AddPhaseEffect.class), AddTurn (AddTurnEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java new file mode 100644 index 00000000000..c328b9b8948 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java @@ -0,0 +1,58 @@ +package forge.game.ability.effects; + +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.ZoneType; +import forge.util.Lang; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; + +import java.util.List; + +public class ActivateAbilityEffect extends SpellAbilityEffect { + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + + final List tgtPlayers = getTargetPlayers(sa); + + sb.append(StringUtils.join(tgtPlayers, ", ")); + sb.append(" activates "); + sb.append(Lang.nounWithAmount(1, sa.hasParam("ManaAbility") ? "mana ability" : "ability")); + sb.append(" of each ").append(sa.getParamOrDefault("Type", "Card")); + sb.append(" he or she controls."); + + return sb.toString(); + } + + @Override + public void resolve(SpellAbility sa) { + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final boolean isManaAb = sa.hasParam("ManaAbility"); + // TODO: improve ai and fix corner cases + + for (final Player p : getTargetPlayers(sa)) { + if ((tgt == null) || p.canBeTargetedBy(sa)) { + List list = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); + for (Card c : list) { + List possibleAb = Lists.newArrayList(c.getAllPossibleAbilities(p, true)); + if (isManaAb) { + possibleAb.retainAll(c.getManaAbility()); + } + if (possibleAb.isEmpty()) { + continue; + } + SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(possibleAb, sa, "Choose a mana ability:"); + p.getController().playChosenSpellAbility(manaAb); + } + } + } + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java index 5dd9f7a42cd..a021e6f848b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DrainManaEffect.java @@ -1,9 +1,11 @@ package forge.game.ability.effects; import forge.game.ability.SpellAbilityEffect; +import forge.game.mana.Mana; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; + import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -27,7 +29,12 @@ public class DrainManaEffect extends SpellAbilityEffect { for (final Player p : getTargetPlayers(sa)) { if ((tgt == null) || p.canBeTargetedBy(sa)) { - p.getManaPool().clearPool(false); + List drained = p.getManaPool().clearPool(false); + if (sa.hasParam("DrainMana")) { + for (Mana mana : drained) { + sa.getActivatingPlayer().getManaPool().addMana(mana); + } + } } } } diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index 741229ed886..65307a895b1 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -125,15 +125,15 @@ public class ManaPool implements Iterable { * @return - the amount of mana removed this way *

*/ - public final int clearPool(boolean isEndOfPhase) { + public final List clearPool(boolean isEndOfPhase) { // isEndOfPhase parameter: true = end of phase, false = mana drain effect - if (floatingMana.isEmpty()) { return 0; } + List cleared = new ArrayList(); + if (floatingMana.isEmpty()) { return cleared; } if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) { - return 0; + return cleared; } - int numRemoved = 0; boolean keepGreenMana = isEndOfPhase && owner.hasKeyword("Green mana doesn't empty from your mana pool as steps and phases end."); boolean convertToColorless = owner.hasKeyword("Convert unused mana to Colorless"); @@ -153,11 +153,12 @@ public class ManaPool implements Iterable { pMana.add(mana); } } + floatingMana.get(b).removeAll(pMana); if (convertToColorless) { - floatingMana.get(b).removeAll(pMana); convertManaColor(b, MagicColor.COLORLESS); + floatingMana.get(b).addAll(pMana); } else { - numRemoved += floatingMana.get(b).size() - pMana.size(); + cleared.addAll(floatingMana.get(b)); floatingMana.get(b).clear(); floatingMana.putAll(b, pMana); } @@ -166,14 +167,14 @@ public class ManaPool implements Iterable { if (convertToColorless) { convertManaColor(b, MagicColor.COLORLESS); } else { - numRemoved += floatingMana.get(b).size(); + cleared.addAll(floatingMana.get(b)); floatingMana.get(b).clear(); } } } owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Cleared, null)); - return numRemoved; + return cleared; } private void convertManaColor(final byte originalColor, final byte toColor) { 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 b998d1959df..267bbf6488b 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -410,7 +410,7 @@ public class PhaseHandler implements java.io.Serializable, IGameStateObject { } for (Player p : game.getPlayers()) { - int burn = p.getManaPool().clearPool(true); + int burn = p.getManaPool().clearPool(true).size(); boolean manaBurns = game.getRules().hasManaBurn(); if (manaBurns) { diff --git a/forge-gui/res/cardsfolder/d/drain_power.txt b/forge-gui/res/cardsfolder/d/drain_power.txt new file mode 100644 index 00000000000..f1acc3ae21f --- /dev/null +++ b/forge-gui/res/cardsfolder/d/drain_power.txt @@ -0,0 +1,7 @@ +Name:Drain Power +ManaCost:U U +Types:Sorcery +A:SP$ ActivateAbility | Cost$ U U | ValidTgts$ Player | Type$ Land | ManaAbility$ True | SubAbility$ DBDrainMana | SpellDescription$ Target player activates a mana ability of each land he or she controls. Then put all mana from that player's mana pool into yours. +SVar:DBDrainMana:DB$ DrainMana | Defined$ Targeted | DrainMana$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/drain_power.jpg +Oracle:Target player activates a mana ability of each land he or she controls. Then put all mana from that player's mana pool into yours.