diff --git a/.gitattributes b/.gitattributes index 86ad18b654f..6ae20246811 100644 --- a/.gitattributes +++ b/.gitattributes @@ -392,6 +392,7 @@ forge-game/src/main/java/forge/game/ability/effects/TapOrUntapEffect.java -text forge-game/src/main/java/forge/game/ability/effects/TokenEffect.java svneol=native#text/plain forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java -text forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java -text +forge-game/src/main/java/forge/game/ability/effects/UnattachEffect.java -text forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java -text forge-game/src/main/java/forge/game/ability/effects/UntapEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java -text @@ -8654,6 +8655,7 @@ forge-gui/res/cardsfolder/o/ogre_arsonist.txt svneol=native#text/plain forge-gui/res/cardsfolder/o/ogre_battledriver.txt -text forge-gui/res/cardsfolder/o/ogre_berserker.txt svneol=native#text/plain forge-gui/res/cardsfolder/o/ogre_gatecrasher.txt svneol=native#text/plain +forge-gui/res/cardsfolder/o/ogre_geargrabber.txt -text forge-gui/res/cardsfolder/o/ogre_jailbreaker.txt -text forge-gui/res/cardsfolder/o/ogre_leadfoot.txt svneol=native#text/plain forge-gui/res/cardsfolder/o/ogre_marauder.txt -text svneol=unset#text/plain diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 352d075e889..aa1f7f6e749 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -40,6 +40,7 @@ public enum SpellApiToAi { apiToClass.put(ApiType.CopyPermanent, CopyPermanentAi.class); apiToClass.put(ApiType.CopySpellAbility, CanPlayAsDrawbackAi.class); apiToClass.put(ApiType.ControlPlayer, CannotPlayAi.class); + apiToClass.put(ApiType.ControlSpell, CannotPlayAi.class); apiToClass.put(ApiType.Counter, CounterAi.class); apiToClass.put(ApiType.DamageAll, DamageAllAi.class); @@ -125,6 +126,7 @@ public enum SpellApiToAi { apiToClass.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class); apiToClass.put(ApiType.Token, TokenAi.class); apiToClass.put(ApiType.TwoPiles, TwoPilesAi.class); + apiToClass.put(ApiType.Unattach, CannotPlayAi.class); apiToClass.put(ApiType.UnattachAll, UnattachAllAi.class); apiToClass.put(ApiType.Untap, UntapAi.class); apiToClass.put(ApiType.UntapAll, UntapAllAi.class); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index c741bd5c987..54f83113467 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -419,6 +419,7 @@ public class GameAction { if (oldBattlefield == null || oldBattlefield.getZoneType() == ZoneType.Stack) { return; } + final Player original = oldBattlefield.getPlayer(); final PlayerZone newBattlefield = c.getController().getZone(oldBattlefield.getZoneType()); if (newBattlefield == null || oldBattlefield.equals(newBattlefield)) { @@ -446,6 +447,7 @@ public class GameAction { final HashMap runParams = new HashMap(); runParams.put("Card", c); + runParams.put("OriginalController", original); game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); 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 a7e7b2e7b93..c5a27faa44d 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -124,6 +124,7 @@ public enum ApiType { TapOrUntapAll (TapOrUntapAllEffect.class), Token (TokenEffect.class, false), TwoPiles (TwoPilesEffect.class), + Unattach (UnattachEffect.class), UnattachAll (UnattachAllEffect.class), Untap (UntapEffect.class), UntapAll (UntapAllEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/UnattachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/UnattachEffect.java new file mode 100644 index 00000000000..1ada2ca58a9 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/UnattachEffect.java @@ -0,0 +1,45 @@ +package forge.game.ability.effects; + +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +public class UnattachEffect extends SpellAbilityEffect { + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + sb.append("Unattach "); + final List targets = getTargetCards(sa); + sb.append(StringUtils.join(targets, " ")); + return sb.toString(); + } + + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + public void resolve(SpellAbility sa) { + + final List unattachList = getTargetCards(sa); + for (final Card cardToUnattach : unattachList) { + if (cardToUnattach.isAura()) { + //final boolean gainControl = "GainControl".equals(af.parseParams().get("AILogic")); + //AbilityFactoryAttach.handleUnattachAura(cardToUnattach, c, gainControl); + } else if (cardToUnattach.isEquipment()) { + if (cardToUnattach.isEquipping()) { + cardToUnattach.unEquipCard(cardToUnattach.getEquipping().get(0)); + } + } else if (cardToUnattach.isFortification()) { + if (cardToUnattach.isFortifying()) { + cardToUnattach.unFortifyCard(cardToUnattach.getFortifying().get(0)); + } + } + } + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java index 8904523f88e..0dda8cf2dbd 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesController.java @@ -57,6 +57,12 @@ public class TriggerChangesController extends Trigger { return false; } } + if (this.mapParams.containsKey("ValidOriginalController")) { + if (!matchesValid(runParams2.get("OriginalController"), this.mapParams.get("ValidOriginalController").split(","), + this.getHostCard())) { + return false; + } + } return true; } diff --git a/forge-gui/res/cardsfolder/g/grindstone.txt b/forge-gui/res/cardsfolder/g/grindstone.txt index 9d16b1c032c..89c0fe05c49 100644 --- a/forge-gui/res/cardsfolder/g/grindstone.txt +++ b/forge-gui/res/cardsfolder/g/grindstone.txt @@ -1,7 +1,7 @@ Name:Grindstone ManaCost:1 Types:Artifact -A:AB$ Repeat | Cost$ 3 T | ValidTgts$ Player | RepeatSubAbility$ DBCleanAndGrind | MaxRepeat$ MaxRepeats | RepeatCheckSVar$ MilledSharesColor | References$ MilledSharesColor | RepeatSVarCompare$ EQ2 | SubAbility$ DBCleanup | SpellDescription$ Put the top two cards of target player's library into that player's graveyard. If both cards share a color, repeat this process. | StackDescription$ SpellDescription +A:AB$ Repeat | Cost$ 3 T | ValidTgts$ Player | RepeatSubAbility$ DBCleanAndGrind | MaxRepeat$ MaxRepeats | RepeatCheckSVar$ MilledSharesColor | References$ MilledSharesColor,MaxRepeats | RepeatSVarCompare$ EQ2 | SubAbility$ DBCleanup | SpellDescription$ Put the top two cards of target player's library into that player's graveyard. If both cards share a color, repeat this process. | StackDescription$ SpellDescription SVar:DBCleanAndGrind:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBPeek SVar:DBPeek:DB$ PeekAndReveal | Defined$ Targeted | PeekAmount$ 2 | NoPeek$ True | NoReveal$ True | RememberPeeked$ True | SubAbility$ DBGrind SVar:DBGrind:DB$ Mill | NumCards$ 2 | Defined$ Targeted diff --git a/forge-gui/res/cardsfolder/o/ogre_geargrabber.txt b/forge-gui/res/cardsfolder/o/ogre_geargrabber.txt new file mode 100644 index 00000000000..9998bbb56f5 --- /dev/null +++ b/forge-gui/res/cardsfolder/o/ogre_geargrabber.txt @@ -0,0 +1,14 @@ +Name:Ogre Geargrabber +ManaCost:4 R R +Types:Creature Ogre Warrior +PT:4/4 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigGainControl | TriggerDescription$ Whenever CARDNAME attacks, gain control of target Equipment an opponent controls until end of turn. Attach it to CARDNAME. When you lose control of that Equipment, unattach it. +SVar:TrigGainControl:AB$ GainControl | Cost$ 0 | ValidTgts$ Equipment.OppCtrl | LoseControl$ EOT | TgtPrompt$ Select target Equipment an opponent controls | SubAbility$ DBAttach +SVar:DBAttach:DB$ Attach | Object$ ParentTarget | Defined$ Self | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | RememberObjects$ ParentTarget | Triggers$ TrigChangeController,OutOfSight | SVars$ TrigUnattach,ExileSelf | Duration$ Permanent +SVar:TrigChangeController:Mode$ ChangesController | ValidCard$ Card.IsRemembered | ValidOriginalController$ You | TriggerZones$ Command | Execute$ TrigUnattach | TriggerDescription$ When you lose control of that Equipment, unattach it. +SVar:TrigUnattach:DB$ Unattach | Defined$ Remembered | SubAbility$ ExileSelf +SVar:OutOfSight:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsRemembered | Execute$ ExileSelf | Static$ True +SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile +SVar:Picture:http://www.wizards.com/global/images/magic/general/ogre_geargrabber.jpg +Oracle:Whenever Ogre Geargrabber attacks, gain control of target Equipment an opponent controls until end of turn. Attach it to Ogre Geargrabber. When you lose control of that Equipment, unattach it.