From a0bb52ff5fb2ea4fea5fb8ab4099306580eb779d Mon Sep 17 00:00:00 2001 From: Agetian Date: Sat, 19 Aug 2017 18:05:44 +0000 Subject: [PATCH] - Added Volrath's Shapeshifter with rudimentary, simple AI support. - Was tested in most typical circumstances, including cloning it. However, may not yet be perfect in some corner cases. Improvements are welcome. --- .gitattributes | 1 + .../src/main/java/forge/ai/AiController.java | 4 + .../src/main/java/forge/ai/SpecialCardAi.java | 36 ++++++++- .../main/java/forge/ai/ability/DiscardAi.java | 11 +-- .../main/java/forge/card/CardStateName.java | 3 +- .../src/main/java/forge/game/GameAction.java | 9 +++ .../game/staticability/StaticAbility.java | 2 +- .../StaticAbilityContinuous.java | 78 ++++++++++++++++--- .../cardsfolder/v/volraths_shapeshifter.txt | 8 ++ 9 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt diff --git a/.gitattributes b/.gitattributes index ae716be0c3e..c392d7f12a9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17614,6 +17614,7 @@ forge-gui/res/cardsfolder/v/volraths_curse.txt -text forge-gui/res/cardsfolder/v/volraths_dungeon.txt svneol=native#text/plain forge-gui/res/cardsfolder/v/volraths_gardens.txt svneol=native#text/plain forge-gui/res/cardsfolder/v/volraths_laboratory.txt -text +forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt -text forge-gui/res/cardsfolder/v/volraths_stronghold.txt svneol=native#text/plain forge-gui/res/cardsfolder/v/volt_charge.txt svneol=native#text/plain forge-gui/res/cardsfolder/v/voltaic_brawler.txt -text diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 064877e93b1..3cfc204d48b 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -843,6 +843,10 @@ public class AiController { } } + if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) { + return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa); + } + // look for good discards while (count < min) { Card prefCard = null; diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index a8f8c986ec6..1f0b72557e8 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -281,6 +281,7 @@ public class SpecialCardAi { } } + // Guilty Conscience public static class GuiltyConscience { public static Card getBestAttachTarget(final Player ai, SpellAbility sa, List list) { Card chosen = null; @@ -586,7 +587,40 @@ public class SpecialCardAi { return false; } } - + + // Volrath's Shapeshifter + public static class VolrathsShapeshifter { + public static boolean consider(Player ai, SpellAbility sa) { + CardCollectionView aiGY = ai.getCardsIn(ZoneType.Graveyard); + Card topGY = null; + Card creatHand = ComputerUtilCard.getBestCreatureAI(ai.getCardsIn(ZoneType.Hand)); + + if (aiGY.size() > 0) { + topGY = ai.getCardsIn(ZoneType.Graveyard).get(0); + } + + if ((topGY != null && !topGY.isCreature()) || creatHand != null) { + return true; + } + + return false; + } + + public static CardCollection targetBestCreature(Player ai, SpellAbility sa) { + Card creatHand = ComputerUtilCard.getBestCreatureAI(ai.getCardsIn(ZoneType.Hand)); + if (creatHand != null) { + CardCollection cc = new CardCollection(); + cc.add(creatHand); + return cc; + } + + // Should ideally never get here + System.err.println("Volrath's Shapeshifter AI: Could not find a discard target despite the previous confirmation to proceed!"); + return null; + } + } + + // Ugin, the Spirit Dragon public static class UginTheSpiritDragon { public static boolean considerPWAbilityPriority(Player ai, SpellAbility sa, ZoneType origin, CardCollectionView oppType, CardCollectionView computerType) { Card source = sa.getHostCard(); diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java index 1903686d8cc..11409147bf7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -1,10 +1,6 @@ package forge.ai.ability; -import forge.ai.ComputerUtil; -import forge.ai.ComputerUtilAbility; -import forge.ai.ComputerUtilCost; -import forge.ai.ComputerUtilMana; -import forge.ai.SpellAbilityAi; +import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.cost.Cost; @@ -27,6 +23,7 @@ public class DiscardAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final Cost abCost = sa.getPayCosts(); + final String aiLogic = sa.getParamOrDefault("AILogic", ""); if (abCost != null) { // AI currently disabled for these costs @@ -53,6 +50,10 @@ public class DiscardAi extends SpellAbilityAi { return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand)); } + if (aiLogic.equals("VolrathsShapeshifter")) { + return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa); + } + final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0; if (tgt != null) { diff --git a/forge-core/src/main/java/forge/card/CardStateName.java b/forge-core/src/main/java/forge/card/CardStateName.java index 9b244d683d8..405c6341703 100644 --- a/forge-core/src/main/java/forge/card/CardStateName.java +++ b/forge-core/src/main/java/forge/card/CardStateName.java @@ -10,7 +10,8 @@ public enum CardStateName { Meld, Cloned, LeftSplit, - RightSplit; + RightSplit, + OriginalText; // backup state for cards like Volrath's Shapeshifter /** * TODO: Write javadoc for this method. diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index e5200630258..3649131352e 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -227,6 +227,15 @@ public class GameAction { if (c.isFlipCard()) { c.clearStates(CardStateName.Flipped, false); } + if (c.getStates().contains(CardStateName.OriginalText)) { + c.clearStates(CardStateName.OriginalText, false); + } + c.updateStateForView(); + } else if (c.getStates().contains(CardStateName.OriginalText)) { + // Volrath's Shapeshifter + CardFactory.copyState(c, CardStateName.OriginalText, c, CardStateName.Original, false); + c.setState(CardStateName.Original, false); + c.clearStates(CardStateName.OriginalText, false); c.updateStateForView(); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 47c0dee32c0..67de73faf67 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -117,7 +117,7 @@ public class StaticAbility extends CardTraitBase implements Comparable 1) { + // TODO: if ever necessary, support gaining text of multiple cards at the same time + System.err.println("Error: GainTextOf parameter was not defined as a unique card for " + hostCard); + } else if (allValid.size() == 1) { + gainTextSource = allValid.get(0); + } else { + gainTextSource = null; + } + } + if (layer == StaticAbilityLayer.TEXT && params.containsKey("ChangeColorWordsTo")) { changeColorWordsTo = params.get("ChangeColorWordsTo"); } @@ -455,12 +465,62 @@ public final class StaticAbilityContinuous { // start modifying the cards for (int i = 0; i < affectedCards.size(); i++) { final Card affectedCard = affectedCards.get(i); - + // Gain control if (layer == StaticAbilityLayer.CONTROL && params.containsKey("GainControl")) { affectedCard.addTempController(hostCard.getController(), hostCard.getTimestamp()); } + // Gain text from another card + if (layer == StaticAbilityLayer.TEXT) { + // Restore the original text in case it was remembered before + if (affectedCard.getStates().contains(CardStateName.OriginalText)) { + affectedCard.clearTriggersNew(); + for (SpellAbility saTemp : affectedCard.getSpellAbilities()) { + if (saTemp.isTemporary()) { + affectedCard.removeSpellAbility(saTemp); + } + } + CardFactory.copyState(affectedCard, CardStateName.OriginalText, affectedCard, CardStateName.Original, false); + } + + if (gainTextSource != null) { + if (!affectedCard.getStates().contains(CardStateName.OriginalText)) { + // Remember the original text first in case it hasn't been done yet + CardFactory.copyState(affectedCard, CardStateName.Original, affectedCard, CardStateName.OriginalText, false); + } + + CardFactory.copyState(gainTextSource, CardStateName.Original, affectedCard, CardStateName.Original, false); + + // Enable this in case Volrath's original image is to be used + affectedCard.getState(CardStateName.Original).setImageKey(affectedCard.getState(CardStateName.OriginalText).getImageKey()); + + // Activated abilities (statics and repleffects are apparently copied vis copyState?) + for (SpellAbility sa : gainTextSource.getSpellAbilities()) { + if (sa instanceof AbilityActivated) { + SpellAbility newSA = ((AbilityActivated) sa).getCopy(); + newSA.setOriginalHost(gainTextSource); + newSA.setIntrinsic(false); + newSA.setTemporary(true); + newSA.setHostCard(affectedCard); + affectedCard.addSpellAbility(newSA); + } + } + // Triggered abilities + for (Trigger t: gainTextSource.getTriggers()) { + affectedCard.addTrigger(t.getCopyForHostCard(affectedCard)); + } + + // Volrath's Shapeshifter shapeshifting ability needs to be added onto the new text + if (params.containsKey("GainedTextHasThisStaticAbility")) { + affectedCard.getCurrentState().addStaticAbility(stAb); + } + + } + + affectedCard.updateStateForView(); + } + // Change color words if (changeColorWordsTo != null) { final byte color; diff --git a/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt new file mode 100644 index 00000000000..6299521fc34 --- /dev/null +++ b/forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt @@ -0,0 +1,8 @@ +Name:Volrath's Shapeshifter +ManaCost:1 U U +Types:Creature Shapeshifter +PT:0/1 +A:AB$ Discard | Cost$ 2 | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | AILogic$ VolrathsShapeshifter | SpellDescription$ Discard a card. +S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainTextOf$ Creature.TopGraveyard+YouCtrl | GainedTextHasThisStaticAbility$ True | Description$ As long as the top card of your graveyard is a creature card, CARDNAME has the full text of that card and has the text "{2}: Discard a card." (CARDNAME has that card's name, mana cost, color, types, abilities, power, and toughness.) +SVar:Picture:http://www.wizards.com/global/images/magic/general/volraths_shapeshifter.jpg +Oracle:As long as the top card of your graveyard is a creature card, Volrath's Shapeshifter has the full text of that card and has the text "{2}: Discard a card." (Volrath's Shapeshifter has that card's name, mana cost, color, types, abilities, power, and toughness.) \ No newline at end of file