From e4ca2ca505cab6992a87e593345d5ad2c1423af3 Mon Sep 17 00:00:00 2001 From: jendave Date: Sat, 6 Aug 2011 16:03:02 +0000 Subject: [PATCH] - Added AF ChangeZoneAll with limited AI decision making - Converted Evacuation and Feldon's Cane as examples - Groundwork for LastKnownInformation being used with Zone movement. --- res/cardsfolder/evacuation.txt | 4 +- res/cardsfolder/feldons_cane.txt | 1 + src/forge/AbilityFactory.java | 9 + src/forge/AbilityFactory_ChangeZone.java | 224 ++++++++++++++++++++++- src/forge/CardFactory.java | 37 ---- src/forge/CardList.java | 8 + src/forge/GameAction.java | 89 +++++---- 7 files changed, 298 insertions(+), 74 deletions(-) diff --git a/res/cardsfolder/evacuation.txt b/res/cardsfolder/evacuation.txt index 81e37fdc6c4..58d7e1049a6 100644 --- a/res/cardsfolder/evacuation.txt +++ b/res/cardsfolder/evacuation.txt @@ -1,8 +1,8 @@ Name:Evacuation ManaCost:3 U U Types:Instant -Text:Return all creatures to their owners' hands. -K:spBounceAll:Creature:Hand +Text:no text +A:SP$ChangeZoneAll | Cost$ 3 U U | ChangeType$ Creature | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return all creatures to their owners' hands. SVar:Rarity:Rare SVar:Picture:http://www.wizards.com/global/images/magic/general/evacuation.jpg SetInfo:8ED|Rare|http://magiccards.info/scans/en/8e/76.jpg diff --git a/res/cardsfolder/feldons_cane.txt b/res/cardsfolder/feldons_cane.txt index 43d05ee6991..8ac3416a139 100644 --- a/res/cardsfolder/feldons_cane.txt +++ b/res/cardsfolder/feldons_cane.txt @@ -2,6 +2,7 @@ Name:Feldon's Cane ManaCost:1 Types:Artifact Text:no text +A:AB$ChangeZoneAll | Cost$ T Exile<1/CARDNAME> | ChangeType$ Card | Origin$ Graveyard | Destination$ Library | Shuffle$ True | SpellDescription$ Shuffle your graveyard into your library. SVar:RemAIDeck:True SVar:Rarity:Uncommon SVar:Picture:http://www.wizards.com/global/images/magic/general/feldons_cane.jpg diff --git a/src/forge/AbilityFactory.java b/src/forge/AbilityFactory.java index 89fb3910f4a..85a122cf7de 100644 --- a/src/forge/AbilityFactory.java +++ b/src/forge/AbilityFactory.java @@ -242,6 +242,15 @@ public class AbilityFactory { SA = AbilityFactory_ChangeZone.createDrawbackChangeZone(this); } + if (API.equals("ChangeZoneAll")){ + if (isAb) + SA = AbilityFactory_ChangeZone.createAbilityChangeZoneAll(this); + else if (isSp) + SA = AbilityFactory_ChangeZone.createSpellChangeZoneAll(this); + else if (isDb) + SA = AbilityFactory_ChangeZone.createDrawbackChangeZoneAll(this); + } + // Fetch, Retrieve and Bounce should be converted ChangeZone /* if (API.equals("Fetch")){ diff --git a/src/forge/AbilityFactory_ChangeZone.java b/src/forge/AbilityFactory_ChangeZone.java index 21388b5beeb..64fda243dc9 100644 --- a/src/forge/AbilityFactory_ChangeZone.java +++ b/src/forge/AbilityFactory_ChangeZone.java @@ -179,7 +179,7 @@ public class AbilityFactory_ChangeZone { } if (abCost.getDiscardCost()) return false; - if (abCost.getSubCounter()) return true; // only card that uses it is Fertilid + if (abCost.getSubCounter()) ; // SubCounter is fine } @@ -890,5 +890,227 @@ public class AbilityFactory_ChangeZone { return AbilityFactory.getDefinedCards(sa.getSourceCard(), defined, sa).get(0); } + // ************************************************************************************* + // ************************** ChangeZoneAll ******************************************** + // ************ All is non-targeted and should occur similarly to Hidden *************** + // ******* Instead of choosing X of type on resolution, all on type go ***************** + // ************************************************************************************* + public static SpellAbility createAbilityChangeZoneAll(final AbilityFactory AF){ + final SpellAbility abChangeZone = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){ + private static final long serialVersionUID = 3728332812890211671L; + + public boolean canPlayAI(){ + return changeZoneAllCanPlayAI(AF, this); + } + + @Override + public void resolve() { + changeZoneAllResolve(AF, this); + } + + @Override + public String getStackDescription(){ + return changeZoneAllDescription(AF, this); + } + + }; + setMiscellaneous(AF, abChangeZone); + return abChangeZone; + } + public static SpellAbility createSpellChangeZoneAll(final AbilityFactory AF){ + final SpellAbility spChangeZone = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { + private static final long serialVersionUID = 3270484211099902059L; + + public boolean canPlayAI(){ + return changeZoneAllCanPlayAI(AF, this); + } + + @Override + public void resolve() { + changeZoneAllResolve(AF, this); + } + + @Override + public String getStackDescription(){ + return changeZoneAllDescription(AF, this); + } + }; + setMiscellaneous(AF, spChangeZone); + return spChangeZone; + } + + public static SpellAbility createDrawbackChangeZoneAll(final AbilityFactory AF){ + final SpellAbility dbChangeZone = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) { + private static final long serialVersionUID = 3270484211099902059L; + + @Override + public void resolve() { + changeZoneAllResolve(AF, this); + } + + @Override + public boolean chkAI_Drawback() { + return changeZoneAllPlayDrawbackAI(AF, this); + } + + @Override + public String getStackDescription(){ + return changeZoneAllDescription(AF, this); + } + }; + setMiscellaneous(AF, dbChangeZone); + return dbChangeZone; + } + + + private static boolean changeZoneAllCanPlayAI(AbilityFactory af, SpellAbility sa){ + // Change Zone All, can be any type moving from one zone to another + Ability_Cost abCost = af.getAbCost(); + Card source = af.getHostCard(); + HashMap params = af.getMapParams(); + //String destination = params.get("Destination"); + String origin = params.get("Origin"); + + if (abCost != null){ + // AI currently disabled for these costs + if (abCost.getSacCost()){ + // Sac is ok in general, but should add some decision making based off what we Sacrifice and what we might get + } + if (abCost.getLifeCost()){ + if (AllZone.ComputerPlayer.getLife() - abCost.getLifeAmount() < 4) + return false; + } + if (abCost.getDiscardCost()) return false; + + if (abCost.getSubCounter()) + ; // subcounter is fine + + } + + if (!ComputerUtil.canPayCost(sa)) + return false; + + Random r = new Random(); + // prevent run-away activations - first time will always return true + boolean chance = r.nextFloat() <= Math.pow(.6667, source.getAbilityUsed()); + + // todo: targeting with ChangeZoneAll + // really two types of targeting. + // Target Player has all their types change zones + // or target permanent and do something relative to that permanent + // ex. "Return all Auras attached to target" + // ex. "Return all blocking/blocked by target creature" + + CardList humanType = AllZoneUtil.getCardsInZone(origin, AllZone.HumanPlayer); + humanType = filterListByType(humanType, params, "ChangeType", sa); + CardList computerType = AllZoneUtil.getCardsInZone(origin, AllZone.ComputerPlayer); + computerType = filterListByType(computerType, params, "ChangeType", sa); + + // todo: improve restrictions on when the AI would want to use this + // spBounceAll has some AI we can compare to. + if (origin.equals("Hand")){ + + } + else if (origin.equals("Library")){ + + } + else if (origin.equals("Battlefield")){ + // this statement is assuming the AI is trying to use this spell offensively + // if the AI is using it defensively, then something else needs to occur + if (computerType.size()+1 > humanType.size()) + return false; + + if (humanType.size() <= 1) // unless that 1 thing is going to kill me soon? + return false; + + // Don't cast during main1? + if (AllZone.Phase.is(Constant.Phase.Main1, AllZone.ComputerPlayer)) + return false; + } + else if (origin.equals("Graveyard")){ + + } + else if (origin.equals("Exile")){ + + } + else if (origin.equals("Stack")){ + // time stop can do something like this: + // Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip + // DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup + // otherwise, this situation doesn't exist + return false; + } + + else if (origin.equals("Sideboard")){ + // This situation doesn't exist + return false; + } + + Ability_Sub subAb = sa.getSubAbility(); + if (subAb != null) + chance &= subAb.chkAI_Drawback(); + + return ((r.nextFloat() < .8) && chance); + } + + private static boolean changeZoneAllPlayDrawbackAI(AbilityFactory af, SpellAbility sa){ + // if putting cards from hand to library and parent is drawing cards + // make sure this will actually do something: + + + return true; + } + + private static String changeZoneAllDescription(AbilityFactory af, SpellAbility sa){ + // TODO: build Stack Description will need expansion as more cards are added + StringBuilder sb = new StringBuilder(); + Card host = af.getHostCard(); + + if (!(sa instanceof Ability_Sub)) + sb.append(host.getName()).append(" -"); + + sb.append(" "); + + String[] desc = sa.getDescription().split(":"); + + if (desc.length > 1) + sb.append(desc[1]); + else + sb.append(desc[0]); + + Ability_Sub abSub = sa.getSubAbility(); + if (abSub != null) { + sb.append(abSub.getStackDescription()); + } + + return sb.toString(); + } + + private static void changeZoneAllResolve(AbilityFactory af, SpellAbility sa){ + HashMap params = af.getMapParams(); + String destination = params.get("Destination"); + String origin = params.get("Origin"); + CardList cards = AllZoneUtil.getCardsInZone(origin); + cards = filterListByType(cards, params, "ChangeType", sa); + + // I don't know if library position is necessary. It's here if it is, just in case + int libraryPos = params.containsKey("LibraryPosition") ? Integer.parseInt(params.get("LibraryPosition")) : 0; + for(Card c : cards){ + if (params.containsKey("GainControl")) + AllZone.GameAction.moveToPlay(c, sa.getActivatingPlayer()); + else + AllZone.GameAction.moveTo(destination, c, libraryPos); + } + + // if Shuffle parameter exists, and any amount of cards were owned by that player, then shuffle that library + if (params.containsKey("Shuffle")){ + if (cards.getOwner(AllZone.HumanPlayer).size() > 0) + AllZone.HumanPlayer.shuffle(); + if (cards.getOwner(AllZone.ComputerPlayer).size() > 0) + AllZone.ComputerPlayer.shuffle(); + } + } + + } diff --git a/src/forge/CardFactory.java b/src/forge/CardFactory.java index 690fa0548ea..fa31fe0e67a 100644 --- a/src/forge/CardFactory.java +++ b/src/forge/CardFactory.java @@ -6734,43 +6734,6 @@ public class CardFactory implements NewConstants { }//*************** END ************ END ************************** */ - //*************** START *********** START ************************** - else if(cardName.equals("Feldon's Cane")) { - /* - * Tap, Exile Feldon's Cane: Shuffle your graveyard into your library. - */ - Ability_Cost abCost = new Ability_Cost("T Exile<1/CARDNAME>", cardName, true); - final Ability_Activated ability = new Ability_Activated(card, abCost, null) { - private static final long serialVersionUID = -1299603105585632846L; - - @Override - public void resolve() { - final Player player = card.getController(); - CardList grave = AllZoneUtil.getPlayerGraveyard(player); - - for(Card c:grave) { - AllZone.GameAction.moveToLibrary(c); - } - - player.shuffle(); - } - - @Override - public boolean canPlayAI() { - CardList lib = AllZoneUtil.getPlayerCardsInLibrary(AllZone.ComputerPlayer); - return lib.size() < 5; - } - - };//SpellAbility - - StringBuilder sb = new StringBuilder(); - sb.append(cardName).append(" - Player shuffles grave into library."); - ability.setStackDescription(sb.toString()); - ability.setDescription(abCost+"Shuffle your graveyard into your library."); - card.addSpellAbility(ability); - }//*************** END ************ END ************************** - - //*************** START *********** START ************************** else if(cardName.equals("Elixir of Immortality")) { /* diff --git a/src/forge/CardList.java b/src/forge/CardList.java index 393f39b70fc..ae48e07c0f2 100644 --- a/src/forge/CardList.java +++ b/src/forge/CardList.java @@ -157,6 +157,14 @@ public class CardList implements Iterable { }); } + public CardList getOwner(final Player player) { + return this.filter(new CardListFilter() { + public boolean addCard(Card c) { + return c.getOwner().isPlayer(player); + } + }); + } + //cardType is like "Land" or "Goblin", returns a new CardList that is a subset of current CardList public CardList getType(final String cardType) { return this.filter(new CardListFilter() { diff --git a/src/forge/GameAction.java b/src/forge/GameAction.java index c3d1dc60796..7e8a6dd3e6f 100644 --- a/src/forge/GameAction.java +++ b/src/forge/GameAction.java @@ -17,13 +17,7 @@ import forge.properties.ForgeProps; import forge.properties.NewConstants.LANG.GameAction.GAMEACTION_TEXT; public class GameAction { - // private StaticEffects staticEffects = new StaticEffects(); - - //private CardList humanList; - //private CardList computerList; - - //private boolean fantasyQuest = false; - + public void resetActivationsPerTurn(){ CardList all = AllZoneUtil.getCardsInGame(); @@ -42,6 +36,8 @@ public class GameAction { String prevZone = ""; PlayerZone p = AllZone.getZone(c); + Card lastKnownInfo = c; + if(p != null){ if (p.is(Constant.Zone.Battlefield) && c.isCreature()) AllZone.Combat.removeFromCombat(c); @@ -52,19 +48,19 @@ public class GameAction { // things that were just created will not have zones! //System.out.println(c.getName() + " " + zone.getZoneName()); } - Card moving = c; + // Don't add the Token, unless it's moving to the battlefield if (!c.isToken() || zone.is(Constant.Zone.Battlefield)){ // If a nontoken card is moving from the Battlefield, to non-Battlefield zone copy it if (p != null && p.is(Constant.Zone.Battlefield) && !zone.is(Constant.Zone.Battlefield)) - moving = AllZone.CardFactory.copyCard(c); + c = AllZone.CardFactory.copyCard(c); - moving.setUnearthed(c.isUnearthed()); // this might be unnecessary - if (c.wasSuspendCast()) // these probably can be moved back to SubtractCounters - moving = addSuspendTriggers(moving); + c.setUnearthed(lastKnownInfo.isUnearthed()); // this might be unnecessary + if (lastKnownInfo.wasSuspendCast()) // these probably can be moved back to SubtractCounters + c = addSuspendTriggers(c); // todo: if zone is battlefied and prevZone is battlefield, temporarily disable enters battlefield triggers - zone.add(moving); + zone.add(c); } if (zone.is(Constant.Zone.Battlefield) && c.isAura()){ @@ -73,14 +69,24 @@ public class GameAction { //Run triggers HashMap runParams = new HashMap(); - // Should the MovedCard be the LKI, aka the original card that came in, not the card that's leaving? - runParams.put("Card", c); - //runParams.put("MovedCard",moving); + + runParams.put("Card", lastKnownInfo); runParams.put("Origin", prevZone); runParams.put("Destination", zone.getZoneName()); AllZone.TriggerHandler.runTrigger("ChangesZone", runParams); - return moving; + return c; + } + + public Card getLastKnownInformation(Card card){ + // record last known information before moving zones + Card lastKnown = AllZone.CardFactory.copyCard(card); + + for(Card attach : card.getAttachedCards()) + lastKnown.attachCard(attach); + lastKnown.setExtrinsicKeyword(card.getExtrinsicKeyword()); + + return lastKnown; } public void changeController(CardList list, Player oldController, Player newController){ @@ -112,7 +118,6 @@ public class GameAction { return moveTo(stack, c); } - //card can be anywhere like in Hand or in Play public Card moveToGraveyard(Card c) { final PlayerZone grave = AllZone.getZone(Constant.Zone.Graveyard, c.getOwner()); @@ -229,6 +234,7 @@ public class GameAction { } public Card moveToPlay(Card c, Player p) { + // move to a specific player's Battlefield PlayerZone play = AllZone.getZone(Constant.Zone.Battlefield, p); return moveTo(play, c); } @@ -261,6 +267,30 @@ public class GameAction { return c; } + public Card exile(Card c) { + if(AllZone.GameAction.isCardExiled(c)) return c; + + PlayerZone removed = AllZone.getZone(Constant.Zone.Exile, c.getOwner()); + + return AllZone.GameAction.moveTo(removed, c); + } + + public Card moveTo(String name, Card c, int libPosition){ + // Call specific functions to set PlayerZone, then move onto moveTo + if (name.equals(Constant.Zone.Hand)) + return moveToHand(c); + else if (name.equals(Constant.Zone.Library)) + return moveToLibrary(c, libPosition); + else if (name.equals(Constant.Zone.Battlefield)) + return moveToPlay(c); + else if (name.equals(Constant.Zone.Graveyard)) + return moveToGraveyard(c); + else if (name.equals(Constant.Zone.Exile)) + return exile(c); + else //if (name.equals(Constant.Zone.Stack)) + return moveToStack(c); + } + public boolean AI_discardNumType(int numDiscard, String[] uTypes, SpellAbility sa) { CardList hand = new CardList(); hand.addAll(AllZone.getZone(Constant.Zone.Hand, AllZone.ComputerPlayer).getCards()); @@ -2024,8 +2054,11 @@ public class GameAction { } //tokens don't go into the graveyard //TODO: must change this if any cards have effects that trigger "when creatures go to the graveyard" - //resets the card, untaps the card, removes anything "extra", resets attack and defense + + + + Card newCard = moveToGraveyard(c); // Destroy needs to be called with Last Known Information @@ -2065,18 +2098,18 @@ public class GameAction { AllZone.Stack.add(persistAb); } - if(newCard.getKeyword().contains( + if(c.getKeyword().contains( "When CARDNAME is put into a graveyard from the battlefield, return CARDNAME to its owner's hand.")) { PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, newCard.getOwner()); moveTo(hand, newCard); } - else if(newCard.getName().equals("Nissa's Chosen")) { + else if(c.getName().equals("Nissa's Chosen")) { PlayerZone library = AllZone.getZone(Constant.Zone.Library, newCard.getOwner()); moveTo(library, newCard); } - else if(newCard.getName().equals("Guan Yu, Sainted Warrior")) { + else if(c.getName().equals("Guan Yu, Sainted Warrior")) { PlayerZone library = AllZone.getZone(Constant.Zone.Library, newCard.getOwner()); newCard = moveTo(library, newCard); owner.shuffle(); @@ -2154,19 +2187,7 @@ public class GameAction { this.sacrificeDestroy(c); return true; } - - /** - * exile a card - * @param c the card to be exiled - */ - public void exile(Card c) { - if(AllZone.GameAction.isCardExiled(c)) return; - PlayerZone removed = AllZone.getZone(Constant.Zone.Exile, c.getOwner()); - - AllZone.GameAction.moveTo(removed, c); - } - //is this card a permanent that is in play? public boolean isCardInPlay(Card c) { return PlayerZoneUtil.isCardInZone(AllZone.Computer_Battlefield, c)