- Changes to allow Mishra to be scriptable. Still room for improvement to make the Dialog boxes more informative about where the available cards are coming from.

- Tweaks to ChangeZone.filterListByType to allow a Triggered or Remembered card to be used as the "source"
This commit is contained in:
jendave
2011-08-06 23:32:43 +00:00
parent acd4302d2b
commit e7b331cc0d
3 changed files with 191 additions and 189 deletions

View File

@@ -3,7 +3,8 @@ ManaCost:1 U B R
Types:Legendary Creature Human Artificer Types:Legendary Creature Human Artificer
Text:no text Text:no text
PT:4/4 PT:4/4
K:WheneverKeyword:CastSpell/Controller:Type/Artifact:Play:MoveFrom-Any-Play:SearchShuffle_SameName/Library/Hand/Graveyard:ASAP:Choice_Instant-SearchLibrary:SearchType/Artifact:Whenever you cast an artifact spell, you may search your graveyard, hand, and/or library for a card with the same name as that spell and put it onto the battlefield. If you search your library this way, shuffle it. T:Mode$ SpellCast | ValidCard$ Card.Artifact | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChangeZone | TriggerDescription$ Whenever you cast an artifact spell, you may search your graveyard, hand, and/or library for a card with the same name as that spell and put it onto the battlefield. If you search your library this way, shuffle it.
SVar:TrigChangeZone:DB$ ChangeZone | Defined$ You | Hidden$ True | Origin$ Graveyard,Hand,Library | OriginChoice$ True | OriginAlternative$ Graveyard,Hand | AlternativeMessage$ Would you like to search your library with this ability? If you do, your library will be shuffled. | Destination$ Battlefield | ChangeType$ Triggered.sameName
SVar:Rarity:Rare SVar:Rarity:Rare
SVar:Picture:http://www.wizards.com/global/images/magic/general/mishra_artificer_prodigy.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/mishra_artificer_prodigy.jpg
SetInfo:TSP|Rare|http://magiccards.info/scans/en/ts/243.jpg SetInfo:TSP|Rare|http://magiccards.info/scans/en/ts/243.jpg

View File

@@ -478,39 +478,38 @@ public class AllZoneUtil {
return card; return card;
} }
public static CardList getCardsInZone(String zone){ public static CardList getCardsInZone(String zone){
return getCardsInZone(zone, null); return getCardsInZone(zone, null);
} }
public static CardList getCardsInZone(String zone, Player player){ public static CardList getCardsInZone(String zone, Player player){
CardList all = new CardList(); CardList all = new CardList();
if (zone.equals(Constant.Zone.Graveyard)){ if (zone.contains(Constant.Zone.Graveyard)){
if (player == null || player.isHuman()) if (player == null || player.isHuman())
all.addAll(AllZone.Human_Graveyard.getCards()); all.addAll(AllZone.Human_Graveyard.getCards());
if (player == null || player.isComputer()) if (player == null || player.isComputer())
all.addAll(AllZone.Computer_Graveyard.getCards()); all.addAll(AllZone.Computer_Graveyard.getCards());
} }
else if (zone.equals(Constant.Zone.Hand)){ if (zone.contains(Constant.Zone.Hand)){
if (player == null || player.isHuman()) if (player == null || player.isHuman())
all.addAll(AllZone.Human_Hand.getCards()); all.addAll(AllZone.Human_Hand.getCards());
if (player == null || player.isComputer()) if (player == null || player.isComputer())
all.addAll(AllZone.Computer_Hand.getCards()); all.addAll(AllZone.Computer_Hand.getCards());
} }
else if (zone.equals(Constant.Zone.Battlefield)){ if (zone.contains(Constant.Zone.Battlefield)){
if (player == null || player.isHuman()) if (player == null || player.isHuman())
all.addAll(AllZone.Human_Battlefield.getCards()); all.addAll(AllZone.Human_Battlefield.getCards());
if (player == null || player.isComputer()) if (player == null || player.isComputer())
all.addAll(AllZone.Computer_Battlefield.getCards()); all.addAll(AllZone.Computer_Battlefield.getCards());
} }
else if (zone.equals(Constant.Zone.Exile)){ if (zone.contains(Constant.Zone.Exile)){
if (player == null || player.isHuman()) if (player == null || player.isHuman())
all.addAll(AllZone.Human_Exile.getCards()); all.addAll(AllZone.Human_Exile.getCards());
if (player == null || player.isComputer()) if (player == null || player.isComputer())
all.addAll(AllZone.Computer_Exile.getCards()); all.addAll(AllZone.Computer_Exile.getCards());
} }
else if (zone.equals(Constant.Zone.Library)){ if (zone.contains(Constant.Zone.Library)){
if (player == null || player.isHuman()) if (player == null || player.isHuman())
all.addAll(AllZone.Human_Library.getCards()); all.addAll(AllZone.Human_Library.getCards());
if (player == null || player.isComputer()) if (player == null || player.isComputer())

View File

@@ -241,54 +241,37 @@ public class AbilityFactory_ChangeZone {
pDefined = tgt.getTargetPlayers(); pDefined = tgt.getTargetPlayers();
} }
else{ else{
pDefined = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); pDefined = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa);
}
String type = params.get("ChangeType");
if (type != null){
if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")){
// Set PayX here to maximum value.
int xPay = ComputerUtil.determineLeftoverMana(sa);
source.setSVar("PayX", Integer.toString(xPay));
}
} }
for(Player p : pDefined){ for(Player p : pDefined){
CardList list = new CardList(); CardList list = AllZoneUtil.getCardsInZone(origin, p);
if (origin.equals("Hand")){
list = AllZoneUtil.getPlayerHand(p); if (type != null && p.isComputer()){
if (list.size() == 0) // AI only "knows" about his information
return false; list = filterListByType(list, params, sa);
}
else if (origin.equals("Graveyard")){
list = AllZoneUtil.getPlayerGraveyard(p);
if (list.size() == 0)
return false;
}
else if (origin.equals("Library")){
list = AllZoneUtil.getPlayerCardsInLibrary(p);
if (list.size() == 0)
return false;
}
if (p.isComputer() && !list.isEmpty() &&
(destination.equals("Hand") || destination.equals("Battlefield") ||
(destination.equals("Graveyard") && origin.equals("Library")))){
if (params.containsKey("ChangeType")){
list = filterListByType(list, params, "ChangeType", sa);
if (list.size() == 0)
return false;
}
}
else if (origin.equals("Sideboard")){
// TODO: once sideboard is added
// canPlayAI for Wishes will go here
} }
if (list.isEmpty())
return false;
} }
// this works for hidden because the mana is paid first. chance &= (r.nextFloat() < .8);
String type = params.get("ChangeType");
if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")){
// Set PayX here to maximum value.
int xPay = ComputerUtil.determineLeftoverMana(sa);
source.setSVar("PayX", Integer.toString(xPay));
}
Ability_Sub subAb = sa.getSubAbility(); Ability_Sub subAb = sa.getSubAbility();
if (subAb != null) if (subAb != null)
chance &= subAb.chkAI_Drawback(); chance &= subAb.chkAI_Drawback();
return ((r.nextFloat() < .8) && chance); return chance;
} }
private static boolean changeHiddenOriginPlayDrawbackAI(AbilityFactory af, SpellAbility sa){ private static boolean changeHiddenOriginPlayDrawbackAI(AbilityFactory af, SpellAbility sa){
@@ -327,30 +310,27 @@ public class AbilityFactory_ChangeZone {
ArrayList<Player> pDefined; ArrayList<Player> pDefined;
Target tgt = af.getAbTgt(); Target tgt = af.getAbTgt();
if(tgt != null && tgt.canTgtPlayer()) { if(tgt != null && tgt.canTgtPlayer()) {
if (af.isCurse()) if (af.isCurse()){
tgt.addTarget(AllZone.HumanPlayer); if (AllZone.HumanPlayer.canTarget(source))
else tgt.addTarget(AllZone.HumanPlayer);
tgt.addTarget(AllZone.ComputerPlayer); else if (mandatory && AllZone.ComputerPlayer.canTarget(source))
tgt.addTarget(AllZone.ComputerPlayer);
}
else{
if (AllZone.ComputerPlayer.canTarget(source))
tgt.addTarget(AllZone.ComputerPlayer);
else if (mandatory && AllZone.HumanPlayer.canTarget(source))
tgt.addTarget(AllZone.HumanPlayer);
}
pDefined = tgt.getTargetPlayers(); pDefined = tgt.getTargetPlayers();
if (pDefined.isEmpty())
return false;
if (mandatory){ if (mandatory){
if (pDefined.size() > 0) return pDefined.size() > 0;
return true;
// unfavorable targeting
if (!af.isCurse())
tgt.addTarget(AllZone.ComputerPlayer);
else
tgt.addTarget(AllZone.HumanPlayer);
if (pDefined.size() > 0)
return true;
// no targets
return false;
} }
} }
else{ else{
if (mandatory) if (mandatory)
@@ -359,31 +339,13 @@ public class AbilityFactory_ChangeZone {
} }
for(Player p : pDefined){ for(Player p : pDefined){
if (origin.equals("Hand")){ CardList list = AllZoneUtil.getCardsInZone(origin, p);
CardList hand = AllZoneUtil.getPlayerHand(p);
if (hand.size() == 0)
return false;
if (p.isComputer()){ if (p.isComputer()) // Computer should "know" his deck
if (params.containsKey("ChangeType")){ list = filterListByType(list, params, sa);
hand = filterListByType(hand, params, "ChangeType", sa);
if (hand.size() == 0) if (list.isEmpty())
return false; return false;
}
}
// TODO: add some more improvements based on Destination and Type
}
else if (origin.equals("Library")){
CardList library = AllZoneUtil.getPlayerCardsInLibrary(p);
if (library.size() == 0)
return false;
// TODO: add some more improvements based on Destination and Type
}
else if (origin.equals("Sideboard")){
// TODO: once sideboard is added
// canPlayAI for Wishes will go here
}
} }
Ability_Sub subAb = sa.getSubAbility(); Ability_Sub subAb = sa.getSubAbility();
@@ -397,73 +359,78 @@ public class AbilityFactory_ChangeZone {
// TODO: build Stack Description will need expansion as more cards are added // TODO: build Stack Description will need expansion as more cards are added
HashMap<String,String> params = af.getMapParams(); HashMap<String,String> params = af.getMapParams();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Card host = af.getHostCard(); Card host = af.getHostCard();
if (!(sa instanceof Ability_Sub)) if (!(sa instanceof Ability_Sub))
sb.append(host.getName()).append(" -"); sb.append(host.getName()).append(" -");
sb.append(" "); sb.append(" ");
String origin = params.get("Origin"); if (params.containsKey("StackDescription"))
String destination = params.get("Destination"); sb.append(params.get("StackDescription"));
String type = params.containsKey("ChangeType") ? params.get("ChangeType") : ""; else{
int num = params.containsKey("ChangeNum") ? AbilityFactory.calculateAmount(host, params.get("ChangeNum"), sa) : 1; String origin = params.get("Origin");
String destination = params.get("Destination");
if (origin.equals("Library")){
sb.append("Search your library for ").append(num).append(" ").append(type).append(" and "); String type = params.containsKey("ChangeType") ? params.get("ChangeType") : "Card";
int num = params.containsKey("ChangeNum") ? AbilityFactory.calculateAmount(host, params.get("ChangeNum"), sa) : 1;
if (params.get("ChangeNum").equals("1"))
sb.append("put that card "); if (origin.equals("Library")){
else sb.append("Search your library for ").append(num).append(" ").append(type).append(" and ");
sb.append("put those cards ");
if (params.get("ChangeNum").equals("1"))
if (destination.equals("Battlefield")){ sb.append("put that card ");
sb.append("onto the battlefield"); else
if (params.containsKey("Tapped")) sb.append("put those cards ");
sb.append(" tapped");
if (destination.equals("Battlefield")){
sb.append("onto the battlefield");
sb.append("."); if (params.containsKey("Tapped"))
sb.append(" tapped");
}
if (destination.equals("Hand"))
sb.append("into your hand."); sb.append(".");
if (destination.equals("Graveyard"))
sb.append("into your graveyard."); }
if (destination.equals("Hand"))
sb.append("Then shuffle your library."); sb.append("into your hand.");
} if (destination.equals("Graveyard"))
else if (origin.equals("Hand")){ sb.append("into your graveyard.");
sb.append("Put ").append(num).append(" ").append(type).append(" card(s) from your hand ");
sb.append("Then shuffle your library.");
if (destination.equals("Battlefield")) }
sb.append("onto the battlefield."); else if (origin.equals("Hand")){
if (destination.equals("Library")){ sb.append("Put ").append(num).append(" ").append(type).append(" card(s) from your hand ");
int libraryPos = params.containsKey("LibraryPosition") ? Integer.parseInt(params.get("LibraryPosition")) : 0;
if (destination.equals("Battlefield"))
if (libraryPos == 0) sb.append("onto the battlefield.");
sb.append("on top"); if (destination.equals("Library")){
if (libraryPos == -1) int libraryPos = params.containsKey("LibraryPosition") ? Integer.parseInt(params.get("LibraryPosition")) : 0;
sb.append("on bottom");
if (libraryPos == 0)
sb.append(" of your library."); sb.append("on top");
} if (libraryPos == -1)
} sb.append("on bottom");
else if (origin.equals("Battlefield")){
// TODO: Expand on this Description as more cards use it sb.append(" of your library.");
// for the non-targeted SAs when you choose what is returned on resolution }
sb.append("Return ").append(num).append(" ").append(type).append(" card(s) "); }
sb.append(" to your ").append(destination); else if (origin.equals("Battlefield")){
} // TODO: Expand on this Description as more cards use it
// for the non-targeted SAs when you choose what is returned on resolution
Ability_Sub abSub = sa.getSubAbility(); sb.append("Return ").append(num).append(" ").append(type).append(" card(s) ");
if (abSub != null) { sb.append(" to your ").append(destination);
sb.append(abSub.getStackDescription()); }
} }
return sb.toString(); Ability_Sub abSub = sa.getSubAbility();
if (abSub != null) {
sb.append(abSub.getStackDescription());
}
return sb.toString();
} }
private static void changeHiddenOriginResolve(AbilityFactory af, SpellAbility sa){ private static void changeHiddenOriginResolve(AbilityFactory af, SpellAbility sa){
@@ -472,9 +439,11 @@ public class AbilityFactory_ChangeZone {
ArrayList<Player> fetchers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa); ArrayList<Player> fetchers = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Defined"), sa);
Player chooser = null; Player chooser = null;
if (params.containsKey("Chooser")) { if (params.containsKey("Chooser")) {
if (params.get("Chooser").equals("Targeted") && af.getAbTgt().getTargetPlayers() != null) String choose = params.get("Chooser");
if (choose.equals("Targeted") && af.getAbTgt().getTargetPlayers() != null)
chooser = af.getAbTgt().getTargetPlayers().get(0); chooser = af.getAbTgt().getTargetPlayers().get(0);
else chooser = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("Chooser"), sa).get(0); else
chooser = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), choose, sa).get(0);
} }
for(Player player : fetchers){ for(Player player : fetchers){
@@ -494,23 +463,38 @@ public class AbilityFactory_ChangeZone {
Card card = af.getHostCard(); Card card = af.getHostCard();
Target tgt = af.getAbTgt(); Target tgt = af.getAbTgt();
if (tgt != null){ if (tgt != null){
if(!tgt.getTargetPlayers().isEmpty()) { ArrayList<Player> players = tgt.getTargetPlayers();
player = tgt.getTargetPlayers().get(0); if (players.contains(player) && !player.canTarget(sa.getSourceCard()))
if (!player.canTarget(sa.getSourceCard())) return;
return;
}
} }
String origin = params.get("Origin"); String origin = params.get("Origin");
String destination = params.get("Destination"); String destination = params.get("Destination");
if (params.containsKey("OriginChoice")){
// Currently only used for Mishra, but may be used by other things
// Improve how this message reacts for other cards
String alt = params.get("OriginAlternative");
CardList altFetchList = AllZoneUtil.getCardsInZone(alt, player);
altFetchList = filterListByType(altFetchList, params, sa);
StringBuilder sb = new StringBuilder();
sb.append(params.get("AlternativeMessage")).append(" ");
sb.append(altFetchList.size()).append(" cards match your searching type in Alternate Zones.");
if (!GameActionUtil.showYesNoDialog(card, sb.toString()))
origin = alt;
}
CardList fetchList = AllZoneUtil.getCardsInZone(origin, player); CardList fetchList = AllZoneUtil.getCardsInZone(origin, player);
if (origin.equals("Library")) // Look at whole library before moving onto choosing a card if (origin.contains("Library")) // Look at whole library before moving onto choosing a card{
GuiUtils.getChoiceOptional(af.getHostCard().getName() + " - Looking at " + origin, fetchList.toArray()); GuiUtils.getChoiceOptional(af.getHostCard().getName() + " - Looking at Library", AllZoneUtil.getCardsInZone("Library", player).toArray());
if (origin.equals("Hand") && player.isComputer()) // Look at opponents hand before moving onto choosing a card
GuiUtils.getChoiceOptional(af.getHostCard().getName() + " - Looking at " + origin, fetchList.toArray()); if (origin.contains("Hand") && player.isComputer()) // Look at opponents hand before moving onto choosing a card
GuiUtils.getChoiceOptional(af.getHostCard().getName() + " - Looking at Human's Hand", AllZoneUtil.getCardsInZone("Hand", player).toArray());
fetchList = filterListByType(fetchList, params, "ChangeType", sa); fetchList = filterListByType(fetchList, params, sa);
PlayerZone destZone = AllZone.getZone(destination, player); PlayerZone destZone = AllZone.getZone(destination, player);
@@ -534,7 +518,7 @@ public class AbilityFactory_ChangeZone {
// this needs to be zero indexed. Top = 0, Third = 2 // this needs to be zero indexed. Top = 0, Third = 2
int libraryPos = params.containsKey("LibraryPosition") ? Integer.parseInt(params.get("LibraryPosition")) : 0; int libraryPos = params.containsKey("LibraryPosition") ? Integer.parseInt(params.get("LibraryPosition")) : 0;
// do not shuffle the library once we have placed a fetched card on top. // do not shuffle the library once we have placed a fetched card on top.
if (origin.equals("Library") && i < 1) { if (origin.contains("Library") && i < 1) {
player.shuffle(); player.shuffle();
} }
AllZone.GameAction.moveToLibrary(c, libraryPos); AllZone.GameAction.moveToLibrary(c, libraryPos);
@@ -563,18 +547,15 @@ public class AbilityFactory_ChangeZone {
} }
} }
if ((origin.equals("Library") && !destination.equals("Library")) || params.containsKey("Shuffle")) if ((origin.contains("Library") && !destination.equals("Library")) || params.containsKey("Shuffle"))
player.shuffle(); player.shuffle();
String DrawBack = params.get("SubAbility"); String DrawBack = params.get("SubAbility");
if (af.hasSubAbility()){ if (af.hasSubAbility()){
Ability_Sub abSub = sa.getSubAbility(); Ability_Sub abSub = sa.getSubAbility();
if (abSub != null){ if (abSub != null)
abSub.resolve(); abSub.resolve();
}
else
CardFactoryUtil.doDrawBack(DrawBack, 0, card.getController(), card.getController().getOpponent(), card.getController(), card, null, sa);
} }
} }
@@ -594,7 +575,7 @@ public class AbilityFactory_ChangeZone {
String origin = params.get("Origin"); String origin = params.get("Origin");
CardList fetchList = AllZoneUtil.getCardsInZone(origin, player); CardList fetchList = AllZoneUtil.getCardsInZone(origin, player);
fetchList = filterListByType(fetchList, params, "ChangeType", sa); fetchList = filterListByType(fetchList, params, sa);
String destination = params.get("Destination"); String destination = params.get("Destination");
@@ -631,7 +612,7 @@ public class AbilityFactory_ChangeZone {
} }
else{ else{
//Don't fetch another tutor with the same name //Don't fetch another tutor with the same name
if (origin.equals("Library") && !fetchList.getNotName(card.getName()).isEmpty()) if (origin.contains("Library") && !fetchList.getNotName(card.getName()).isEmpty())
fetchList = fetchList.getNotName(card.getName()); fetchList = fetchList.getNotName(card.getName());
fetchList.shuffle(); fetchList.shuffle();
@@ -643,7 +624,7 @@ public class AbilityFactory_ChangeZone {
fetchList.remove(c); fetchList.remove(c);
} }
if (origin.equals("Library")) if (origin.contains("Library"))
player.shuffle(); player.shuffle();
for(Card c : fetched){ for(Card c : fetched){
@@ -673,24 +654,45 @@ public class AbilityFactory_ChangeZone {
else else
GuiUtils.getChoice(picked, new String[]{ "<Nothing>" } ); GuiUtils.getChoice(picked, new String[]{ "<Nothing>" } );
} }
String DrawBack = params.get("SubAbility");
if (af.hasSubAbility()){ if (af.hasSubAbility()){
Ability_Sub abSub = sa.getSubAbility(); Ability_Sub abSub = sa.getSubAbility();
if (abSub != null){ if (abSub != null){
abSub.resolve(); abSub.resolve();
} }
else
CardFactoryUtil.doDrawBack(DrawBack, 0, card.getController(), card.getController().getOpponent(), card.getController(), card, null, sa);
} }
} }
// *********** Utility functions for Hidden ******************** // *********** Utility functions for Hidden ********************
private static CardList filterListByType(CardList list, HashMap<String,String> params, String type, SpellAbility sa){ private static CardList filterListByType(CardList list, HashMap<String,String> params, SpellAbility sa){
if (params.containsKey(type)) String type = params.get("ChangeType");
list = list.getValidCards(params.get(type).split(","), sa.getActivatingPlayer(), sa.getSourceCard()); if (type == null)
return list; return list;
// Filter List Can send a different Source card in for things like Mishra and Lobotomy
Card source = sa.getSourceCard();
if (type.contains("Triggered")){
Object o = source.getTriggeringObject("Card");
// I won't the card attached to the Triggering object
if (!(o instanceof Card))
return new CardList();
source = (Card)(o);
type = type.replace("Triggered", "Card");
}
else if (type.contains("Remembered")){
ArrayList<Card> rem = source.getRemembered();
if (rem.isEmpty())
return new CardList();
source = rem.get(0);
type.replace("Remembered", "Card");
}
return list.getValidCards(type.split(","), sa.getActivatingPlayer(), source);
} }
private static Card basicManaFixing(CardList list){ // Search for a Basic Land private static Card basicManaFixing(CardList list){ // Search for a Basic Land
@@ -1334,9 +1336,9 @@ public class AbilityFactory_ChangeZone {
// ex. "Return all blocking/blocked by target creature" // ex. "Return all blocking/blocked by target creature"
CardList humanType = AllZoneUtil.getCardsInZone(origin, AllZone.HumanPlayer); CardList humanType = AllZoneUtil.getCardsInZone(origin, AllZone.HumanPlayer);
humanType = filterListByType(humanType, params, "ChangeType", sa); humanType = filterListByType(humanType, params, sa);
CardList computerType = AllZoneUtil.getCardsInZone(origin, AllZone.ComputerPlayer); CardList computerType = AllZoneUtil.getCardsInZone(origin, AllZone.ComputerPlayer);
computerType = filterListByType(computerType, params, "ChangeType", sa); computerType = filterListByType(computerType, params, sa);
// TODO: improve restrictions on when the AI would want to use this // TODO: improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to. // spBounceAll has some AI we can compare to.
@@ -1460,7 +1462,7 @@ public class AbilityFactory_ChangeZone {
tgtPlayer = af.getAbTgt().getTargetPlayers().get(0); tgtPlayer = af.getAbTgt().getTargetPlayers().get(0);
cards = AllZoneUtil.getCardsInZone(origin,tgtPlayer); cards = AllZoneUtil.getCardsInZone(origin,tgtPlayer);
} }
cards = filterListByType(cards, params, "ChangeType", sa); cards = filterListByType(cards, params, sa);
// I don't know if library position is necessary. It's here if it is, just in case // I don't know if library position is necessary. It's here if it is, just in case