Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding The tale of Tamiyo (v1.0) #13086

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package mage.cards.t;

import java.util.*;

import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileTargetCardCopyAndCastEffect;
import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInYourGraveyard;

/**
*
* @author frafen and chat gpt
*/
public final class TheTaleOfTamiyo extends CardImpl {


public TheTaleOfTamiyo(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}");

this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SAGA);

// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)
SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV);
// I, II, III -- Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process.
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III,
new TheTaleOfTamiyoEffect1()
);
// Saga effect for Chapter IV -- "Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards from your graveyard. Copy them. You may cast any number of the copies."
sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_IV,
new TheTaleOfTamiyoEffect2()
);

this.addAbility(sagaAbility);
}

private TheTaleOfTamiyo(final TheTaleOfTamiyo card) {
super(card);
}

@Override
public TheTaleOfTamiyo copy() {return new TheTaleOfTamiyo(this); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style - new line after opening brace and before/after closing brace

}
class TheTaleOfTamiyoEffect1 extends OneShotEffect {

TheTaleOfTamiyoEffect1() {
super(Outcome.DrawCard);
this.staticText = "Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process.";
}
private TheTaleOfTamiyoEffect1(final TheTaleOfTamiyoEffect1 effect) {
super(effect);
}

@Override
public TheTaleOfTamiyoEffect1 copy() {
return new TheTaleOfTamiyoEffect1(this);
}

@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());

if (controller != null) {
int possibleIterations = controller.getLibrary().size() / 2;
int iteration = 0;

// Stop if there are not enough cards
if (controller.getLibrary().size() < 2) {
return true;
}

do {
iteration++;
if (iteration > possibleIterations + 20) {
// Protection against infinity loops
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "protection" is not standard and I can't think of why manually counting iterations would be beneficial.

Instead use:

} while (controller.canRespond());

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either, I copy/pasted it from Grindstone's code (that is the base of this effect's code)

game.setDraw(source.getControllerId());
return true;
}

// Mill 2 cards
List<Card> cards = new ArrayList<>(controller
.millCards(2, source, game)
.getCards(game));

// Stop if less than 2 cards were milled
if (cards.size() < 2) {
break;
}

// Set to hold types of the first card
Set<CardType> firstCardTypes = new HashSet<>(cards.get(0).getCardType());

// Check if there's at least one type in common
boolean typesMatch = false;

// Iterate over the second card's types to see if there is any overlap with the first card's types
for (CardType type : cards.get(1).getCardType()) {
if (firstCardTypes.contains(type)) {
typesMatch = true;
break;
}
}

// If there is a match, draw a card and continue; otherwise, break
if (typesMatch) {
controller.drawCards(1, source, game);
} else {
break;
}

} while (controller.getLibrary().size() >= 2); // Continue if there are at least 2 cards left in the library
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. Nothing about the effect states that there must be two cards left in the library to mill. Specifically, if there is one card left, it should be milled.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I can fix this. For the other requests I'm afraid I can't do much, my coding experience is almost nonexistant. I tried to fix some of those other problems with the help of Chat gpt but I'm not having much success.


return true;
}
return false;
}



}
class TheTaleOfTamiyoEffect2 extends OneShotEffect {

private final FilterCard filter;

TheTaleOfTamiyoEffect2() {
super(Outcome.PlayForFree);
this.staticText = "Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards " +
"from your graveyard. Copy them. You may cast any number of the copies.";

// Initialize filter
this.filter = new FilterCard("instant, sorcery, or Tamiyo planeswalker card");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make the filter static and put this initialization in a static block after it, not in the constructor.

this.filter.add(Predicates.or(
CardType.INSTANT.getPredicate(),
CardType.SORCERY.getPredicate(),
SubType.TAMIYO.getPredicate()
));
}

private TheTaleOfTamiyoEffect2(final TheTaleOfTamiyoEffect2 effect) {
super(effect);
this.filter = effect.filter; // Copy filter
}

@Override
public TheTaleOfTamiyoEffect2 copy() {
return new TheTaleOfTamiyoEffect2(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
// Use filter to choose the cards to exile
Set<Card> cardsToExile = controller.getGraveyard().getCards(
filter, source.getControllerId(), source, game);

if (!cardsToExile.isEmpty()) {
// Move cards from graveyard to exile
if (controller.moveCards(cardsToExile, Zone.EXILED, source, game)) {
Cards copiedCards = new CardsImpl();
// Copy cards
for (Card card : cardsToExile) {
copiedCards.add(game.copyCard(card, source, source.getControllerId()));
}

// Cast copies
boolean continueCasting = true;
while (controller.canRespond() && continueCasting && !copiedCards.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use a while loop for this. Instead, use Integer.MAX_VALUE for the max number of targets in the target card, so all the cards to cast can be selected at once. Then for the casting process iterate through those targets with a for loop.

// Ask player what to cast
TargetCard targetCard = new TargetCard(0, 1, Zone.EXILED,
new FilterCard("copied card to cast paying its mana cost?"));
targetCard.withNotTarget(true); // To not select one card multiple times

if (controller.chooseTarget(Outcome.PlayForFree, copiedCards, targetCard, source, game)) {
Card selectedCard = game.getCard(targetCard.getFirstTarget());
if (selectedCard != null
&& selectedCard.getSpellAbility().canChooseTarget(game, controller.getId())) {
game.getState().setValue("PlayFromNotOwnHandZone" + selectedCard.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(selectedCard, game, false),
game, false, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + selectedCard.getId(), null);
}
copiedCards.remove(selectedCard);
}

// Verify if player wants to cast more cards
continueCasting = !copiedCards.isEmpty()
&& controller.chooseUse(Outcome.Benefit, "Continue to choose copies and cast?", source, game);
}
}
}
return true;
}
return false;
}
}
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ private DuskmournHouseOfHorror() {
cards.add(new SetCardInfo("The Jolly Balloon Man", 219, Rarity.RARE, mage.cards.t.TheJollyBalloonMan.class));
cards.add(new SetCardInfo("The Mindskinner", 66, Rarity.RARE, mage.cards.t.TheMindskinner.class));
cards.add(new SetCardInfo("The Swarmweaver", 236, Rarity.RARE, mage.cards.t.TheSwarmweaver.class));
cards.add(new SetCardInfo("The Tale of Tamiyo", 75, Rarity.RARE, mage.cards.t.TheTaleOfTamiyo.class));
cards.add(new SetCardInfo("The Wandering Rescuer", 41, Rarity.MYTHIC, mage.cards.t.TheWanderingRescuer.class));
cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class));
cards.add(new SetCardInfo("Threats Around Every Corner", 200, Rarity.UNCOMMON, mage.cards.t.ThreatsAroundEveryCorner.class));
Expand Down