- 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.
This commit is contained in:
Agetian
2017-08-19 18:05:44 +00:00
parent 1a766c3ccc
commit a0bb52ff5f
9 changed files with 135 additions and 17 deletions

1
.gitattributes vendored
View File

@@ -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

View File

@@ -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;

View File

@@ -281,6 +281,7 @@ public class SpecialCardAi {
}
}
// Guilty Conscience
public static class GuiltyConscience {
public static Card getBestAttachTarget(final Player ai, SpellAbility sa, List<Card> list) {
Card chosen = null;
@@ -587,6 +588,39 @@ public class SpecialCardAi {
}
}
// 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();

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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();
}

View File

@@ -117,7 +117,7 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
layers.add(StaticAbilityLayer.CONTROL);
}
if (hasParam("ChangeColorWordsTo")) {
if (hasParam("ChangeColorWordsTo") || hasParam("GainTextOf")) {
layers.add(StaticAbilityLayer.TEXT);
}

View File

@@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import forge.card.CardStateName;
import forge.game.card.*;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Iterables;
@@ -37,12 +39,6 @@ import forge.game.StaticEffects;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
@@ -109,6 +105,7 @@ public final class StaticAbilityContinuous {
se.setTimestamp(hostCard.getTimestamp());
String changeColorWordsTo = null;
Card gainTextSource = null;
String addP = "";
int powerBonus = 0;
@@ -150,6 +147,19 @@ public final class StaticAbilityContinuous {
effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule")));
}
if (layer == StaticAbilityLayer.TEXT && params.containsKey("GainTextOf")) {
final String valid = params.get("GainTextOf");
CardCollection allValid = CardLists.getValidCards(game.getCardsInGame(), valid, hostCard.getController(), hostCard, null);
if (allValid.size() > 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");
}
@@ -461,6 +471,56 @@ public final class StaticAbilityContinuous {
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;

View File

@@ -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.)