mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
- 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:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
8
forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt
Normal file
8
forge-gui/res/cardsfolder/v/volraths_shapeshifter.txt
Normal 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.)
|
||||
Reference in New Issue
Block a user