- Added AF ChangeZoneAll with limited AI decision making

- Converted Evacuation and Feldon's Cane as examples
- Groundwork for LastKnownInformation being used with Zone movement.
This commit is contained in:
jendave
2011-08-06 16:03:02 +00:00
parent 8e562b9b45
commit e4ca2ca505
7 changed files with 298 additions and 74 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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")) {
/*

View File

@@ -157,6 +157,14 @@ public class CardList implements Iterable<Card> {
});
}
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() {

View File

@@ -17,12 +17,6 @@ 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<String,Object> runParams = new HashMap<String,Object>();
// 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();
@@ -2155,18 +2188,6 @@ public class GameAction {
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)