eye_of_ojer_taq_apex_observatory.txt + support

This commit is contained in:
Northmoc
2023-11-20 22:45:03 -05:00
committed by Northmoc
parent a281d9931b
commit eac062a1c2
8 changed files with 96 additions and 14 deletions

View File

@@ -506,13 +506,7 @@ public class GameAction {
}
if (cause != null && cause.isCraft() && toBattlefield) { // retain cards crafted while ETB transformed
for (Card craft : cause.getPaidList("ExiledCards")) {
if (!craft.equals(copied) && !craft.isToken()) {
copied.addExiledCard(craft);
craft.setExiledWith(copied);
craft.setExiledBy(cause.getActivatingPlayer());
}
}
copied.retainPaidList(cause, "ExiledCards");
}
}

View File

@@ -9,6 +9,7 @@ import forge.card.CardType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.player.Player;
@@ -88,6 +89,24 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
}
}
}
break;
case "Shared":
if (sa.hasParam("TypesFromDefined")) {
CardCollection def = AbilityUtils.getDefinedCards(card, sa.getParam("TypesFromDefined"), sa);
if (def.size() < 2) break; // need at least 2 cards to work with to find shared types
final Card card1 = def.get(0);
def.remove(0);
for (final CardType.CoreType ct : card1.getType().getCoreTypes()) {
boolean shared = true;
for (final Card c : def) {
if (!c.getType().hasType(ct)) {
shared = false;
break;
}
}
if (shared) validTypes.add(ct.name());
}
}
}
}

View File

@@ -622,6 +622,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
boolean result = c.changeToState(c.backside ? CardStateName.Transformed : CardStateName.Original);
retResult = retResult || result;
if (cause != null && cause.isCraft()) { // retain cards crafted while transforming in exile
c.retainPaidList(cause, "ExiledCards");
}
}
if (hasMergedCard()) {
rebuildMutatedStates(cause);
@@ -1283,6 +1286,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
}
public final void retainPaidList(final SpellAbility cause, final String list) {
for (Card craft : cause.getPaidList(list)) {
if (!craft.equals(this) && !craft.isToken()) {
addExiledCard(craft);
craft.setExiledWith(this);
craft.setExiledBy(cause.getActivatingPlayer());
}
}
}
public final List<Integer> getStoredRolls() {
return storedRolls;
}

View File

@@ -2942,8 +2942,8 @@ public class CardFactoryUtil {
// Create return transformed ability string
String ab = "AB$ ChangeZone | CostDesc$ " + cd.toString() + " | Cost$ " + cost + " | Origin$ Exile | " +
"Destination$ Battlefield | Transformed$ True | Defined$ CorrectedSelf | Craft$ True | " +
"XAnnounceTitle$ " + Localizer.getInstance().getMessage("lblCraft") + " | " +
"StackDescription$ Return this card transformed under its owner's control. (Craft) | " +
"XAnnounceTitle$ " + Localizer.getInstance().getMessage("lblCraft") + " | SorcerySpeed$ True" +
" | StackDescription$ Return this card transformed under its owner's control. (Craft) | " +
"SpellDescription$ (" + inst.getReminderText() + ")";
final SpellAbility newSA = AbilityFactory.getAbility(ab, card);
newSA.setIntrinsic(intrinsic);

View File

@@ -195,10 +195,31 @@ public class CostExile extends CostPartWithList {
type = TextUtil.fastReplace(type, TextUtil.concatNoSpace("+withTotalCMCEQ", totalM), "");
}
boolean sharedType = false;
if (type.contains("+withSharedCardType")) {
sharedType = true;
type = TextUtil.fastReplace(type, "+withSharedCardType", "");
}
if (!type.contains("X") || ability.getXManaCostPaid() != null) {
list = CardLists.getValidCards(list, type.split(";"), payer, source, ability);
}
int amount = this.getAbilityAmount(ability);
if (sharedType) { // will need more logic if cost ever wants more than 2 that share a type
if (list.size() < amount) return false;
for (int i = 0; i < list.size(); i++) {
final Card card1 = list.get(i);
for (final Card compare : list) {
if (!compare.equals(card1) && compare.sharesCardTypeWith(card1)) {
return true;
}
}
}
return false;
}
if (totalCMC) {
int needed = Integer.parseInt(this.getAmount().split("\\+")[0]);
if (list.size() < needed) return false;
@@ -209,8 +230,6 @@ public class CostExile extends CostPartWithList {
return CardLists.cmcCanSumTo(i, list);
}
int amount = this.getAbilityAmount(ability);
// for cards like Allosaurus Rider, do not count it
if (this.from.size() == 1 && this.from.get(0).equals(ZoneType.Hand) && source.isInZone(ZoneType.Hand)
&& list.contains(source)) {

View File

@@ -0,0 +1,23 @@
Name:Eye of Ojer Taq
ManaCost:3
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color.
K:Craft:6 ExileCtrlOrGrave<2/Permanent.Other+withSharedCardType/permanent>:two that share a card type:the two
AlternateMode:DoubleFaced
DeckHints:Ability$Discard|Mill|Sacrifice
DeckHas:Ability$Mill|Graveyard|Token
Oracle:{T}: Add one mana of any color.\nCraft with two that share a card type {6} ({6}, Exile this artifact, Exile the two from among other permanents you control and/or cards from your graveyard: Return this card transformed under its owner's control. Craft only as a sorcery.)
ALTERNATE
Name:Apex Observatory
ManaCost:no cost
Types:Artifact
K:ETBReplacement:Other:Tap
SVar:Tap:DB$ Tap | Defined$ Self | ETB$ True | SubAbility$ DBChooseType | SpellDescription$ CARDNAME enters the battlefield tapped. As it enters, choose a card type shared among two exiled cards used to craft it.
SVar:DBChooseType:DB$ ChooseType | Type$ Shared | TypesFromDefined$ ExiledWith | AILogic$ MostProminentComputerControlsOrOwns
A:AB$ Effect | Cost$ T | StaticAbilities$ Play | Triggers$ CastTrig | SpellDescription$ The next spell you cast this turn of the chosen type can be cast without paying its mana cost.
SVar:Play:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | MayPlayDontGrantZonePermissions$ True | EffectZone$ Command | Affected$ Card.ChosenType | AffectedZone$ Hand,Graveyard,Library,Exile,Command | Description$ The next spell you cast this turn of the chosen type can be cast without paying its mana cost.
SVar:CastTrig:Mode$ SpellCast | ValidCard$ Card.ChosenType | ValidActivatingPlayer$ You | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
Oracle:Apex Observatory enters the battlefield tapped. As it enters, choose a card type shared among two exiled cards used to craft it.\n{T}: The next spell you cast this turn of the chosen type can be cast without paying its mana cost.

View File

@@ -5,4 +5,5 @@ A:AB$ Mana | Cost$ T | Produced$ W | RestrictValid$ Spell.Artifact,Activated.Art
A:AB$ ChangeZone | Cost$ 2 W T Exile<1+/Artifact.Other+withTotalCMCEQX/other artifacts you control with total mana value X> | Announce$ X | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Artifact.YouOwn+cmcLEX | TgtPrompt$ Select target artifact card with mana value X or less | SorcerySpeed$ True | SpellDescription$ Return target artifact card with mana value X or less from your graveyard to the battlefield. Activate only as a sorcery.
SVar:X:Count$xPaid
DeckNeeds:Type$Artifact
AI:RemoveDeck:All
Oracle:{T}: Add {W}. Spend this mana only to cast an artifact spell or activate an ability of an artifact source.\n{2}{W}, {T}, Exile one or more other artifacts you control with total mana value X: Return target artifact card with mana value X or less from your graveyard to the battlefield. Activate only as a sorcery.

View File

@@ -241,6 +241,11 @@ public class HumanCostDecision extends CostDecisionMakerBase {
totalM = type.split("withTotalCMCEQ")[1];
type = TextUtil.fastReplace(type, TextUtil.concatNoSpace("+withTotalCMCEQ", totalM), "");
}
boolean sharedType = false;
if (type.contains("+withSharedCardType")) {
sharedType = true;
type = TextUtil.fastReplace(type, "+withSharedCardType", "");
}
CardCollection list;
if (cost.zoneRestriction != 1) {
@@ -292,7 +297,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (fromZone == ZoneType.Library) { return exileFromTop(cost, c); }
}
if (fromTopGrave) { return exileFromTopGraveType(c, list); }
if (cost.zoneRestriction != 0) { return exileFromMiscZone(cost, c, list); }
if (cost.zoneRestriction != 0) { return exileFromMiscZone(cost, c, list, sharedType); }
final FCollectionView<Player> players = game.getPlayers();
final List<Player> payableZone = new ArrayList<>();
@@ -394,7 +399,8 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(list);
}
private PaymentDecision exileFromMiscZone(final CostExile cost, final int nNeeded, final CardCollection typeList) {
private PaymentDecision exileFromMiscZone(final CostExile cost, final int nNeeded, final CardCollection typeList,
final boolean sharedType) {
// when it's always a single triggered card getting exiled don't act like it might be different by offering the zone for choice
if (cost.zoneRestriction == -1 && ability.isTrigger() && nNeeded == 1 && typeList.size() == 1) {
if (confirmAction(cost, Localizer.getInstance().getMessage("lblExileConfirm", CardTranslation.getTranslatedName(typeList.getFirst().getName())))) {
@@ -405,9 +411,15 @@ public class HumanCostDecision extends CostDecisionMakerBase {
final List<ZoneType> origin = Lists.newArrayList(cost.from);
final CardCollection exiled = new CardCollection();
final String required = sharedType ? " (must share a card type)" : "";
final List<Card> chosen = controller.chooseCardsForZoneChange(ZoneType.Exile, origin, ability, typeList,
mandatory ? nNeeded : 0, nNeeded, null, cost.toString(nNeeded), null);
mandatory ? nNeeded : 0, nNeeded, null, cost.toString(nNeeded) + required,
null);
if (sharedType) {
if (!chosen.get(1).sharesCardTypeWith(chosen.get(0))) return null;
}
exiled.addAll(chosen);
if (exiled.size() < nNeeded) {