mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Bunch of fixes (#2706)
* Fix Tawnos's Coffin not using ETB counters * Cost fix * Saga tweaks --------- Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
This commit is contained in:
@@ -355,6 +355,31 @@ public class AiController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (final Trigger tr : card.getTriggers()) {
|
||||
if (!card.hasStartOfKeyword("Saga") && !card.hasStartOfKeyword("Read ahead")) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (tr.getMode() != TriggerType.CounterAdded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellAbility exSA = tr.ensureAbility().copy(activator);
|
||||
|
||||
if (api != null && exSA.getApi() == api) {
|
||||
rightapi = true;
|
||||
}
|
||||
|
||||
if (exSA instanceof AbilitySub && !doTrigger(exSA, false)) {
|
||||
// AI would not run this chapter if given the chance
|
||||
// TODO eventually we'll want to consider playing it anyway, especially if Read ahead would still allow an immediate benefit
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (api != null && !rightapi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
// TODO Determine exile from same zone for AI
|
||||
return null;
|
||||
} else {
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost, source, c, ability);
|
||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostExile;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
@@ -641,9 +642,14 @@ public class ComputerUtil {
|
||||
return sacList;
|
||||
}
|
||||
|
||||
public static CardCollection chooseExileFrom(final Player ai, final ZoneType zone, final String type, final Card activate,
|
||||
final Card target, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
||||
public static CardCollection chooseExileFrom(final Player ai, CostExile cost, final Card activate, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList;
|
||||
if (cost.zoneRestriction != 1) {
|
||||
typeList = new CardCollection(ai.getGame().getCardsIn(cost.from));
|
||||
} else {
|
||||
typeList = new CardCollection(ai.getCardsIn(cost.from));
|
||||
}
|
||||
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activate.getController(), activate, sa);
|
||||
|
||||
// don't exile the card we're pumping
|
||||
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||
|
||||
@@ -2410,7 +2410,6 @@ public class ComputerUtilCombat {
|
||||
// (currently looks for the creature with maximum raw power since that's what the AI usually judges by when
|
||||
// deciding whether the creature is worth blocking).
|
||||
// If the creature doesn't change into anything, returns the original creature.
|
||||
if (attacker == null) { return null; }
|
||||
Card attackerAfterTrigs = attacker;
|
||||
|
||||
// Test for some special triggers that can change the creature in combat
|
||||
|
||||
@@ -1412,7 +1412,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// avoid randomly moving the equipment back and forth between several creatures in one turn
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN) && !mandatory) {
|
||||
if (AiCardMemory.isRememberedCard(aiPlayer, attachSource, AiCardMemory.MemorySet.ATTACHED_THIS_TURN) && !mandatory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1423,7 +1423,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN);
|
||||
AiCardMemory.rememberCard(aiPlayer, attachSource, AiCardMemory.MemorySet.ATTACHED_THIS_TURN);
|
||||
|
||||
if (c == null && mandatory) {
|
||||
CardLists.shuffle(list);
|
||||
@@ -1674,12 +1674,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (c == null) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getHostCard() == null) {
|
||||
// FIXME: Not sure what should the resolution be if a SpellAbility has no host card. This should
|
||||
// not happen normally. Possibly remove this block altogether? (if it's an impossible condition).
|
||||
System.out.println("AttachAi: isUsefulAttachAction unexpectedly called with SpellAbility with no host card. Assuming it's a determined useful action.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// useless to equip a creature that can't attack or block.
|
||||
return !sa.getHostCard().isEquipment() || !ComputerUtilCard.isUselessCreature(ai, c);
|
||||
|
||||
@@ -69,7 +69,6 @@ public class CloneAi extends SpellAbilityAi {
|
||||
bFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||
|
||||
@@ -1221,4 +1221,12 @@ public class CountersPutAi extends CountersAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
if (sa.hasParam("ReadAhead")) {
|
||||
return 1;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
@@ -21,19 +19,16 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
Player p = sa.getActivatingPlayer();
|
||||
String mode = sa.getParamOrDefault("Mode", "Load");
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (Card c : getDefinedCardsOrTargeted(sa)) {
|
||||
if (mode.equals(MODE_STORE)) {
|
||||
noteCounters(c, source);
|
||||
} else if (mode.equals(MODE_LOAD)) {
|
||||
loadCounters(c, source, p, sa, table);
|
||||
loadCounters(c, source, p, sa);
|
||||
}
|
||||
}
|
||||
table.replaceCounterEffect(game, sa, false);
|
||||
}
|
||||
|
||||
public static void noteCounters(Card notee, Card source) {
|
||||
@@ -44,13 +39,13 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCounters(Card notee, Card source, final Player p, final SpellAbility sa, GameEntityCounterTable table) {
|
||||
private void loadCounters(Card notee, Card source, final Player p, final SpellAbility sa) {
|
||||
for (Entry<String, String> svar : source.getSVars().entrySet()) {
|
||||
String key = svar.getKey();
|
||||
if (key.startsWith(NOTE_COUNTERS)) {
|
||||
notee.addCounter(
|
||||
notee.addEtbCounter(
|
||||
CounterType.getType(key.substring(NOTE_COUNTERS.length())),
|
||||
Integer.parseInt(svar.getValue()), p, table);
|
||||
Integer.parseInt(svar.getValue()), p);
|
||||
}
|
||||
// TODO Probably should "remove" the svars that were temporarily used
|
||||
}
|
||||
|
||||
@@ -6374,7 +6374,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
|
||||
if (!ZoneType.Battlefield.toString().equals(params.get("Destination"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Creeping Chill
|
||||
ManaCost:3 B
|
||||
Types:Sorcery
|
||||
A:SP$ DamageAll | StackDescription$ CARDNAME deals 3 damage to each opponent and | Cost$ 3 B | ValidPlayers$ Player.Opponent | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to each opponent and you gain 3 life.
|
||||
T:Mode$ ChangesZone | Origin$ Library | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigExile | OptionalDecider$ You | TriggerDescription$ When CARDNAME is put into your graveyard from your library, you may exile it. If you do, then a opponent and you gain 3 life.
|
||||
T:Mode$ ChangesZone | Origin$ Library | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigExile | OptionalDecider$ You | TriggerDescription$ When CARDNAME is put into your graveyard from your library, you may exile it. If you do, CARDNAME deals 3 damage to each opponent and you gain 3 life.
|
||||
SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCardLKICopy | Origin$ Graveyard | Destination$ Exile | SubAbility$ DBDamage
|
||||
SVar:DBDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 3 | SubAbility$ DBGainLife
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 3
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Legendary Creature Phyrexian Vampire
|
||||
PT:5/4
|
||||
K:Flying
|
||||
T:Mode$ ChangesZone | ValidCard$ Creature.nonToken+Other+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigExile | OptionalDecider$ You | TriggerDescription$ Whenever another nontoken creature you control dies, you may pay 2 life and exile it. If you do, create a token that's a copy of that creature, except it's 1/1 and has toxic 1. (Players dealt combat damage by it also get a poison counter.)
|
||||
SVar:TrigExile:AB$ CopyPermanent | Cost$ PayLife<2> ExileFromGrave<1/Card.TriggeredCard/Exile nontoken creature that just died> | AddKeywords$ Toxic:1 | Defined$ TriggeredCardLKICopy | SetPower$ 1 | SetToughness$ 1
|
||||
SVar:TrigExile:AB$ CopyPermanent | Cost$ PayLife<2> ExileAnyGrave<1/Card.TriggeredCard/Exile nontoken creature that just died> | AddKeywords$ Toxic:1 | Defined$ TriggeredCardLKICopy | SetPower$ 1 | SetToughness$ 1
|
||||
DeckHas:Ability$Token
|
||||
Oracle:Flying\nWhenever another nontoken creature you control dies, you may pay 2 life and exile it. If you do, create a token that's a copy of that creature, except it's 1/1 and has toxic 1. (Players dealt combat damage by it also get a poison counter.)
|
||||
|
||||
@@ -2,16 +2,12 @@ Name:Tawnos's Coffin
|
||||
ManaCost:4
|
||||
Types:Artifact
|
||||
K:You may choose not to untap CARDNAME during your untap step.
|
||||
A:AB$ Pump | Cost$ 3 T | ValidTgts$ Creature | ImprintCards$ Targeted | SubAbility$ DBRememberAura | StackDescription$ SpellDescription | SpellDescription$ Exile target creature and all Auras attached to it. Note the number and kind of counters that were on that creature. When CARDNAME leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:DBRememberAura:DB$ PumpAll | ValidCards$ Aura.AttachedTo Creature.IsImprinted | RememberAllPumped$ True | StackDescription$ None | SubAbility$ DBEffect
|
||||
SVar:DBEffect:DB$ Effect | Triggers$ LeavesPlay,Untap | ImprintCards$ ParentTarget | RememberObjects$ Remembered | NoteCounterDefined$ Imprinted | Duration$ Permanent | SubAbility$ DBExile
|
||||
SVar:DBExile:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Card.IsRemembered,Card.IsImprinted | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
|
||||
SVar:LeavesPlay:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.EffectSource | Execute$ RestoreCounters | TriggerController$ TriggeredCardController | TriggerDescription$ When EFFECTSOURCE leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:Untap:Mode$ Untaps | ValidCard$ Card.EffectSource | Execute$ TrigReturn | TriggerController$ TriggeredCardController | Secondary$ True | TriggerDescription$ When EFFECTSOURCE leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | Tapped$ True | SubAbility$ RestoreCounters
|
||||
SVar:RestoreCounters:DB$ NoteCounters | Mode$ Load | Defined$ Imprinted | SubAbility$ TrigAuraReturn
|
||||
SVar:TrigAuraReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | AttachedTo$ Valid Creature.IsImprinted | SubAbility$ ExileSelf
|
||||
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
|
||||
A:AB$ Effect | Cost$ 3 T | ValidTgts$ Creature | Triggers$ LeavesPlay,Untap | ImprintCards$ Targeted | RememberObjects$ Valid Aura.AttachedTo Targeted | NoteCounterDefined$ Targeted | Duration$ Permanent | SubAbility$ DBExile | StackDescription$ SpellDescription | SpellDescription$ Exile target creature and all Auras attached to it. Note the number and kind of counters that were on that creature. When CARDNAME leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:DBExile:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Targeted.Self,Aura.AttachedTo Card.Self
|
||||
SVar:LeavesPlay:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.EffectSource | Execute$ TrigCounters | TriggerController$ TriggeredCardController | OneOff$ True | TriggerDescription$ When EFFECTSOURCE leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:Untap:Mode$ Untaps | ValidCard$ Card.EffectSource | Execute$ TrigCounters | TriggerController$ TriggeredCardController | OneOff$ True | Secondary$ True | TriggerDescription$ When EFFECTSOURCE leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
SVar:TrigCounters:DB$ NoteCounters | Mode$ Load | Defined$ Imprinted | SubAbility$ TrigReturn
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | Tapped$ True | SubAbility$ TrigAuraReturn
|
||||
SVar:TrigAuraReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | AttachedTo$ Imprinted
|
||||
AI:RemoveDeck:All
|
||||
Oracle:You may choose not to untap Tawnos's Coffin during your untap step.\n{3}, {T}: Exile target creature and all Auras attached to it. Note the number and kind of counters that were on that creature. When Tawnos's Coffin leaves the battlefield or becomes untapped, return that exiled card to the battlefield under its owner's control tapped with the noted number and kind of counters on it. If you do, return the other exiled cards to the battlefield under their owner's control attached to that permanent.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Vampire Noble
|
||||
PT:4/4
|
||||
K:Ward:Discard<1/Card>
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Vampire.Other+nonToken+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever another nontoken Vampire you control dies, you may pay {1} and exile it. If you do, create a 1/1 black Bat creature token with flying. It gains "When this creature deals combat damage to a player, sacrifice it and return the exiled card to the battlefield tapped."
|
||||
SVar:TrigToken:AB$ Token | Cost$ 1 ExileFromGrave<1/Card.TriggeredNewCard/the Vampire card> | TokenRemembered$ ExiledCards | TokenScript$ b_1_1_bat_flying | ImprintTokens$ True | SubAbility$ DBAnimate
|
||||
SVar:TrigToken:AB$ Token | Cost$ 1 ExileAnyGrave<1/Card.TriggeredNewCard/the Vampire card> | TokenRemembered$ ExiledCards | TokenScript$ b_1_1_bat_flying | ImprintTokens$ True | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ Imprinted | Duration$ Permanent | Triggers$ CDTrigger
|
||||
SVar:CDTrigger:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ When this creature deals combat damage to a player, sacrifice it and return the exiled card to the battlefield tapped.
|
||||
SVar:TrigSac:DB$ Sacrifice | SubAbility$ DBReturn
|
||||
|
||||
@@ -320,7 +320,13 @@ public class HumanPlay {
|
||||
costExile.payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Graveyard)), sourceAbility, hcd.isEffect());
|
||||
} else {
|
||||
from = costExile.getFrom();
|
||||
CardCollection list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source, sourceAbility);
|
||||
CardCollection list;
|
||||
if (costExile.zoneRestriction != 1) {
|
||||
list = new CardCollection(p.getGame().getCardsIn(from));
|
||||
} else {
|
||||
list = new CardCollection(p.getCardsIn(from));
|
||||
}
|
||||
list = CardLists.getValidCards(list, part.getType().split(";"), p, source, sourceAbility);
|
||||
final int nNeeded = getAmountFromPart(part, source, sourceAbility);
|
||||
if (list.size() < nNeeded) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user