mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
@@ -969,21 +969,16 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean canPlaySpellWithoutBuyback(Card card, SpellAbility sa) {
|
private boolean canPlaySpellWithoutBuyback(Card card, SpellAbility sa) {
|
||||||
boolean wasteBuybackAllowed = false;
|
|
||||||
|
|
||||||
// About to lose game : allow
|
|
||||||
if (ComputerUtil.aiLifeInDanger(player, true, 0)) {
|
|
||||||
wasteBuybackAllowed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int copies = CardLists.count(player.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(card.getName()));
|
int copies = CardLists.count(player.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(card.getName()));
|
||||||
// Have two copies : allow
|
// Have two copies : allow
|
||||||
if (copies >= 2) {
|
if (copies >= 2) {
|
||||||
wasteBuybackAllowed = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int neededMana = 0;
|
// About to lose game : allow
|
||||||
boolean dangerousRecurringCost = false;
|
if (ComputerUtil.aiLifeInDanger(player, true, 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Cost costWithBuyback = sa.getPayCosts().copy();
|
Cost costWithBuyback = sa.getPayCosts().copy();
|
||||||
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
||||||
@@ -991,22 +986,19 @@ public class AiController {
|
|||||||
costWithBuyback.add(opt.getCost());
|
costWithBuyback.add(opt.getCost());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CostAdjustment.adjust(costWithBuyback, sa);
|
costWithBuyback = CostAdjustment.adjust(costWithBuyback, sa);
|
||||||
if (costWithBuyback.getCostMana() != null) {
|
|
||||||
neededMana = costWithBuyback.getCostMana().getMana().getCMC();
|
|
||||||
}
|
|
||||||
if (costWithBuyback.hasSpecificCostType(CostPayLife.class)
|
if (costWithBuyback.hasSpecificCostType(CostPayLife.class)
|
||||||
|| costWithBuyback.hasSpecificCostType(CostDiscard.class)
|
|| costWithBuyback.hasSpecificCostType(CostDiscard.class)
|
||||||
|| costWithBuyback.hasSpecificCostType(CostSacrifice.class)) {
|
|| costWithBuyback.hasSpecificCostType(CostSacrifice.class)) {
|
||||||
dangerousRecurringCost = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// won't be able to afford buyback any time soon
|
// won't be able to afford buyback any time soon
|
||||||
// if Buyback cost includes sacrifice, life, discard
|
// if Buyback cost includes sacrifice, life, discard
|
||||||
if (dangerousRecurringCost) {
|
return true;
|
||||||
wasteBuybackAllowed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int neededMana = 0;
|
||||||
|
if (costWithBuyback.getCostMana() != null) {
|
||||||
|
neededMana = costWithBuyback.getCostMana().getMana().getCMC();
|
||||||
|
}
|
||||||
// Memory Crystal-like effects need special handling
|
// Memory Crystal-like effects need special handling
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (StaticAbility s : c.getStaticAbilities()) {
|
for (StaticAbility s : c.getStaticAbilities()) {
|
||||||
@@ -1022,10 +1014,10 @@ public class AiController {
|
|||||||
|
|
||||||
int hasMana = ComputerUtilMana.getAvailableManaEstimate(player, false);
|
int hasMana = ComputerUtilMana.getAvailableManaEstimate(player, false);
|
||||||
if (hasMana < neededMana - 1) {
|
if (hasMana < neededMana - 1) {
|
||||||
wasteBuybackAllowed = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return wasteBuybackAllowed;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not sure "playing biggest spell" matters?
|
// not sure "playing biggest spell" matters?
|
||||||
|
|||||||
@@ -1871,10 +1871,9 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
public static int getMaxSAEnergyCostOnBattlefield(final Player ai) {
|
public static int getMaxSAEnergyCostOnBattlefield(final Player ai) {
|
||||||
// returns the maximum energy cost of an ability that permanents on the battlefield under AI's control have
|
// returns the maximum energy cost of an ability that permanents on the battlefield under AI's control have
|
||||||
CardCollectionView otb = ai.getCardsIn(ZoneType.Battlefield);
|
|
||||||
int maxEnergyCost = 0;
|
int maxEnergyCost = 0;
|
||||||
|
|
||||||
for (Card c : otb) {
|
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||||
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
||||||
if (energyCost != null) {
|
if (energyCost != null) {
|
||||||
|
|||||||
@@ -669,8 +669,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// Prefer "tap to deal damage"
|
// Prefer "tap to deal damage"
|
||||||
// TODO : Skip this one if triggers on combat damage only?
|
// TODO : Skip this one if triggers on combat damage only?
|
||||||
for (SpellAbility sa2 : card.getSpellAbilities()) {
|
for (SpellAbility sa2 : card.getSpellAbilities()) {
|
||||||
if (ApiType.DealDamage.equals(sa2.getApi())
|
if (ApiType.DealDamage.equals(sa2.getApi()) && sa2.usesTargeting() && sa2.getTargetRestrictions().canTgtPlayer()) {
|
||||||
&& (sa2.getTargetRestrictions().canTgtPlayer())) {
|
|
||||||
cardPriority += 300;
|
cardPriority += 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
} else if (top.getApi() == ApiType.CopySpellAbility) {
|
||||||
// Don't try to copy a copy ability, too complex for the AI to handle
|
// Don't try to copy a copy ability, too complex for the AI to handle
|
||||||
return false;
|
return false;
|
||||||
|
} else if (top.getApi() == ApiType.Mana) {
|
||||||
|
// would lead to Stack Overflow by trying to play this again
|
||||||
|
return false;
|
||||||
} else if (top.getApi() == ApiType.DestroyAll || top.getApi() == ApiType.SacrificeAll || top.getApi() == ApiType.ChangeZoneAll || top.getApi() == ApiType.TapAll || top.getApi() == ApiType.UnattachAll) {
|
} else if (top.getApi() == ApiType.DestroyAll || top.getApi() == ApiType.SacrificeAll || top.getApi() == ApiType.ChangeZoneAll || top.getApi() == ApiType.TapAll || top.getApi() == ApiType.UnattachAll) {
|
||||||
if (!top.usesTargeting() || top.getActivatingPlayer().equals(aiPlayer)) {
|
if (!top.usesTargeting() || top.getActivatingPlayer().equals(aiPlayer)) {
|
||||||
// If we activated a mass removal / mass tap / mass bounce / etc. spell, or if the opponent activated it but
|
// If we activated a mass removal / mass tap / mass bounce / etc. spell, or if the opponent activated it but
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// currently there are no optional Trigger
|
// currently there are no optional Trigger
|
||||||
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"),
|
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
sa);
|
|
||||||
if (players.isEmpty()) {
|
if (players.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -553,25 +553,6 @@ public class CostAdjustment {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (st.hasParam("ValidSpellTarget")) {
|
|
||||||
SpellAbility curSa = sa;
|
|
||||||
boolean targetValid = false;
|
|
||||||
outer: while (curSa != null) {
|
|
||||||
if (!curSa.usesTargeting()) {
|
|
||||||
curSa = curSa.getSubAbility();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (SpellAbility target : curSa.getTargets().getTargetSpells()) {
|
|
||||||
Card targetCard = target.getHostCard();
|
|
||||||
if (targetCard.isValid(st.getParam("ValidSpellTarget").split(","), controller, hostCard, curSa)) {
|
|
||||||
targetValid = true;
|
|
||||||
break outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curSa = curSa.getSubAbility();
|
|
||||||
}
|
|
||||||
return targetValid;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ public class TriggerHandler {
|
|||||||
public final void resetActiveTriggers() {
|
public final void resetActiveTriggers() {
|
||||||
resetActiveTriggers(true);
|
resetActiveTriggers(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void resetActiveTriggers(boolean collect) {
|
public final void resetActiveTriggers(boolean collect) {
|
||||||
if (collect) {
|
if (collect) {
|
||||||
collectTriggerForWaiting();
|
collectTriggerForWaiting();
|
||||||
@@ -281,11 +280,11 @@ public class TriggerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean runWaitingTriggers() {
|
public final boolean runWaitingTriggers() {
|
||||||
final List<TriggerWaiting> waiting = new ArrayList<>(waitingTriggers);
|
if (waitingTriggers.isEmpty()) {
|
||||||
waitingTriggers.clear();
|
|
||||||
if (waiting.isEmpty()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
final List<TriggerWaiting> waiting = new ArrayList<>(waitingTriggers);
|
||||||
|
waitingTriggers.clear();
|
||||||
|
|
||||||
boolean haveWaiting = false;
|
boolean haveWaiting = false;
|
||||||
for (final TriggerWaiting wt : waiting) {
|
for (final TriggerWaiting wt : waiting) {
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Kaervek's Torch
|
|||||||
ManaCost:X R
|
ManaCost:X R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | SpellDescription$ CARDNAME deals X damage to any target.
|
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | SpellDescription$ CARDNAME deals X damage to any target.
|
||||||
S:Mode$ RaiseCost | ValidSpellTarget$ Card.Self | Activator$ Player | Type$ Spell | Amount$ 2 | EffectZone$ Stack | Description$ As long as CARDNAME is on the stack, spells that target it cost {2} more to cast.
|
S:Mode$ RaiseCost | ValidTarget$ Spell.Self | Activator$ Player | Type$ Spell | Amount$ 2 | EffectZone$ Stack | Description$ As long as CARDNAME is on the stack, spells that target it cost {2} more to cast.
|
||||||
SVar:X:Count$xPaid
|
SVar:X:Count$xPaid
|
||||||
Oracle:As long as Kaervek's Torch is on the stack, spells that target it cost {2} more to cast.\nKaervek's Torch deals X damage to any target.
|
Oracle:As long as Kaervek's Torch is on the stack, spells that target it cost {2} more to cast.\nKaervek's Torch deals X damage to any target.
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ A:AB$ Draw | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumCards$ 1 | Sp
|
|||||||
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
|
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | KW$ First Strike & Trample | SpellDescription$ Target creature gets +2/+0 and gains first strike and trample until end of turn.
|
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | KW$ First Strike & Trample | SpellDescription$ Target creature gets +2/+0 and gains first strike and trample until end of turn.
|
||||||
A:AB$ Draw | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | NumCards$ 4 | SubAbility$ DBTrigger | SpellDescription$ Draw four cards. When you do, CARDNAME deals damage to any target equal to the number of cards in your hand.
|
A:AB$ Draw | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | NumCards$ 4 | RememberDrawn$ True | SubAbility$ DBTrigger | SpellDescription$ Draw four cards. When you do, CARDNAME deals damage to any target equal to the number of cards in your hand.
|
||||||
SVar:DBTrigger:DB$ ImmediateTrigger | Execute$ DBDamage | TriggerDescription$ When you do, CARDNAME deals damage to any target equal to the number of cards in your hand.
|
SVar:DBTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE4 | Execute$ DBDamage | TriggerDescription$ When you do, CARDNAME deals damage to any target equal to the number of cards in your hand. | SubAbility$ DBCleanup
|
||||||
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X
|
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X
|
||||||
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
SVar:X:Count$InYourHand
|
SVar:X:Count$InYourHand
|
||||||
Oracle:[+1]: Draw a card, then discard a card.\n[+1]: Target creature gets +2/+0 and gains first strike and trample until end of turn.\n[-8]: Draw four cards. When you do, The Royal Scions deals damage to any target equal to the number of cards in your hand.
|
Oracle:[+1]: Draw a card, then discard a card.\n[+1]: Target creature gets +2/+0 and gains first strike and trample until end of turn.\n[-8]: Draw four cards. When you do, The Royal Scions deals damage to any target equal to the number of cards in your hand.
|
||||||
|
|||||||
Reference in New Issue
Block a user