Merge branch 'master' into newmaster

This commit is contained in:
Anthony Calosa
2022-07-10 22:18:08 +08:00
18 changed files with 158 additions and 122 deletions

View File

@@ -250,6 +250,13 @@ public class ComputerUtil {
return false;
final Card source = sa.getHostCard();
Zone fromZone = game.getZoneOf(source);
int zonePosition = 0;
if (fromZone != null) {
zonePosition = fromZone.getCards().indexOf(source);
}
if (sa.isSpell() && !source.isCopiedSpell()) {
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
@@ -257,11 +264,18 @@ public class ComputerUtil {
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts();
final CostPayment pay = new CostPayment(cost, sa);
// do this after card got added to stack
if (!sa.checkRestrictions(ai)) {
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
return false;
}
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa, false);
game.getStack().add(sa);
} else {
final CostPayment pay = new CostPayment(cost, sa);
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
game.getStack().add(sa);
}
@@ -292,6 +306,13 @@ public class ComputerUtil {
newSA = GameActionUtil.addExtraKeywordCost(newSA);
final Card source = newSA.getHostCard();
Zone fromZone = game.getZoneOf(source);
int zonePosition = 0;
if (fromZone != null) {
zonePosition = fromZone.getCards().indexOf(source);
}
if (newSA.isSpell() && !source.isCopiedSpell()) {
newSA.setHostCard(game.getAction().moveToStack(source, newSA));
@@ -303,6 +324,13 @@ public class ComputerUtil {
}
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
// do this after card got added to stack
if (!sa.checkRestrictions(ai)) {
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
return false;
}
pay.payComputerCosts(new AiCostDecision(ai, newSA, false));
game.getStack().add(newSA);

View File

@@ -1087,7 +1087,7 @@ public class PlayerControllerAi extends PlayerController {
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA;
// TODO if mandatory AI is only forced to use mana when it's already in the pool
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
if (noManaCost) {
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
}

View File

@@ -34,6 +34,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
@@ -842,4 +843,46 @@ public final class GameActionUtil {
c.getGame().getTriggerHandler().resetActiveTriggers();
}
public static void rollbackAbility(SpellAbility ability, final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) {
// cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy
oldCard.setCastSA(null);
oldCard.setCastFrom(null);
// add back to where it came from, hopefully old state
// skip GameAction
oldCard.getZone().remove(oldCard);
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
ability.setHostCard(oldCard);
ability.setXManaCostPaid(null);
ability.setSpendPhyrexianMana(false);
if (ability.hasParam("Announce")) {
for (final String aVar : ability.getParam("Announce").split(",")) {
final String varName = aVar.trim();
if (!varName.equals("X")) {
ability.setSVar(varName, "0");
}
}
}
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
for (SpellAbility sa : oldCard.getSpells()) {
sa.setHostCard(oldCard);
}
//for Chorus of the Conclave
ability.rollback();
oldCard.setBackSide(false);
oldCard.setState(oldCard.getFaceupCardStateName(), true);
oldCard.unanimateBestow();
}
ability.clearTargets();
ability.resetOnceResolved();
payment.refundPayment();
game.getStack().clearFrozen();
game.getTriggerHandler().clearWaitingTriggers();
}
}

View File

@@ -1,41 +1,18 @@
package forge.game.ability.effects;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import forge.game.*;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.*;
import forge.game.ability.AbilityKey;
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.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
import forge.game.card.CardZoneTable;
import forge.game.card.CounterType;
import forge.game.card.*;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.DelayedReveal;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerView;
import forge.game.player.*;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
@@ -43,13 +20,13 @@ import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.MessageUtil;
import forge.util.TextUtil;
import forge.util.*;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
public class ChangeZoneEffect extends SpellAbilityEffect {
@@ -689,7 +666,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown")) {
gameCard.turnFaceDown(true);
setFaceDownState(gameCard, sa);
CardFactoryUtil.setFaceDownState(gameCard, sa);
}
movedCard = game.getAction().moveTo(gameCard.getController().getZone(destination), gameCard, sa, moveParams);
@@ -1345,7 +1322,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
if (sa.hasParam("FaceDown")) {
c.turnFaceDown(true);
setFaceDownState(c, sa);
CardFactoryUtil.setFaceDownState(c, sa);
}
movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams);
@@ -1503,39 +1480,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
&& sa.getParam("WithTotalCMC") == null;
}
private static void setFaceDownState(Card c, SpellAbility sa) {
final Card source = sa.getHostCard();
CardState faceDown = c.getFaceDownState();
// set New Pt doesn't work because this values need to be copyable for clone effects
if (sa.hasParam("FaceDownPower")) {
faceDown.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
faceDown.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownSetType")) {
faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false));
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|| sa.hasParam("FaceDownSetType")) {
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = 8853789549297846163L;
@Override
public void run() {
c.clearStates(CardStateName.FaceDown, true);
}
};
c.addFaceupCommand(unanimate);
}
}
/**
* <p>
* removeFromStack.

View File

@@ -496,7 +496,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
// need to unfreeze tracker
game.getTracker().unfreeze();
// check if it can recive the Tribute
// check if it can receive the Tribute
if (abort) {
continue;
}

View File

@@ -162,6 +162,10 @@ public class SetStateEffect extends SpellAbilityEffect {
hasTransformed = gameCard.turnFaceUp(true, true, sa);
} else {
hasTransformed = gameCard.changeCardState(mode, sa.getParam("NewState"), sa);
if (gameCard.isFaceDown() && (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|| sa.hasParam("FaceDownSetType"))) {
CardFactoryUtil.setFaceDownState(gameCard, sa);
}
}
if (hasTransformed) {
if (sa.isMorphUp()) {

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import forge.GameCommand;
import forge.game.event.GameEventCardForetold;
import forge.game.trigger.TriggerType;
import org.apache.commons.lang3.StringUtils;
@@ -3748,4 +3749,37 @@ public class CardFactoryUtil {
re.setOverridingAbility(saExile);
card.addReplacementEffect(re);
}
public static void setFaceDownState(Card c, SpellAbility sa) {
final Card source = sa.getHostCard();
CardState faceDown = c.getFaceDownState();
// set New Pt doesn't work because this values need to be copyable for clone effects
if (sa.hasParam("FaceDownPower")) {
faceDown.setBasePower(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownPower"), sa));
}
if (sa.hasParam("FaceDownToughness")) {
faceDown.setBaseToughness(AbilityUtils.calculateAmount(
source, sa.getParam("FaceDownToughness"), sa));
}
if (sa.hasParam("FaceDownSetType")) {
faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false));
}
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|| sa.hasParam("FaceDownSetType")) {
final GameCommand unanimate = new GameCommand() {
private static final long serialVersionUID = 8853789549297846163L;
@Override
public void run() {
c.clearStates(CardStateName.FaceDown, true);
}
};
c.addFaceupCommand(unanimate);
}
}
}

View File

@@ -2,6 +2,7 @@ Name:Djeru's Resolve
ManaCost:W
Types:Instant
A:SP$ Untap | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBPump | SpellDescription$ Untap target creature. Prevent all damage that would be dealt to it this turn.
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Prevent all damage that would be dealt to CARDNAME.
SVar:DBPump:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
K:Cycling:2
Oracle:Untap target creature. Prevent all damage that would be dealt to it this turn.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -2,7 +2,8 @@ Name:Godtoucher
ManaCost:3 G
Types:Creature Elf Cleric
PT:2/2
A:AB$ Pump | Cost$ 1 W T | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
A:AB$ Effect | Cost$ 1 W T | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | ReplacementEffects$ RPrevent| RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
AI:RemoveDeck:All
AI:RemoveDeck:Random
Oracle:{1}{W}, {T}: Prevent all damage that would be dealt to target creature with power 5 or greater this turn.

View File

@@ -3,7 +3,8 @@ ManaCost:2 W
Types:Enchantment Creature Nymph
PT:2/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Enchantment.Other+YouCtrl | Execute$ TrigPump | TriggerDescription$ Constellation — Whenever CARDNAME or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Prevent all damage that would be dealt to CARDNAME.
SVar:TrigPump:DB$ Effect | Cost$ W | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
SVar:PlayMain1:TRUE
SVar:BuffedBy:Enchantment
Oracle:Constellation — Whenever Harvestguard Alseids or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.

View File

@@ -1,6 +1,7 @@
Name:Indestructible Aura
ManaCost:W
Types:Instant
A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
AI:RemoveDeck:All
Oracle:Prevent all damage that would be dealt to target creature this turn.

View File

@@ -4,7 +4,7 @@ Types:Creature Cyclops
PT:4/3
K:Defender
S:Mode$ CanAttackDefender | ValidCard$ Card.Self | CheckSVar$ X | Description$ As long as you've cast an instant or sorcery spell this turn, CARDNAME can attack as though it didn't have defender.
SVar:X:Count$ThisTurnCast_Instant.YouOwn,Sorcery.YouOwn
SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl
SVar:BuffedBy:Instant,Sorcery
DeckHints:Type$Instant|Sorcery
Oracle:Defender\nAs long as you've cast an instant or sorcery spell this turn, Piston-Fist Cyclops can attack as though it didn't have defender.

View File

@@ -1,6 +1,7 @@
Name:Shielded Passage
ManaCost:W
Types:Instant
A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
AI:RemoveDeck:All
Oracle:Prevent all damage that would be dealt to target creature this turn.

View File

@@ -0,0 +1,19 @@
Name:Illithid Harvester
ManaCost:4 U
Types:Creature Horror
PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTurnFaceDown | TriggerDescription$ Ceremorphosis — When CARDNAME enters the battlefield, turn any number of target tapped nontoken creatures face down. They're 2/2 Horror creatures.
SVar:TrigTurnFaceDown:DB$ SetState | ValidTgts$ Creature.tapped+nonToken | TgtPrompt$ Select any number of target tapped nontoken creatures | TargetMin$ 0 | TargetMax$ X | Mode$ TurnFace | FaceDownPower$ 2 | FaceDownToughness$ 2 | FaceDownSetType$ Horror & Creature
SVar:X:Count$Valid Creature.tapped+nonToken
AlternateMode:Adventure
Oracle:Ceremorphosis — When Illithid Harvester enters the battlefield, turn any number of target tapped nontoken creatures face down. They're 2/2 Horror creatures.
ALTERNATE
Name:Plant Tadpoles
ManaCost:X U U
Types:Sorcery Adventure
A:SP$ Tap | ValidTgts$ Creature | TgtPrompt$ Select X target creatures | TargetMin$ X | TargetMax$ X | AlwaysRemember$ True | SubAbility$ DBPump | SpellDescription$ Tap X target creatures.
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent | StackDescription$ SpellDescription | SpellDescription$ They don't untap during their controllers' next untap steps.
SVar:X:Count$xPaid
Oracle:Tap X target creatures. They don't untap during their controllers' next untap steps. (Then exile this card. You may cast the creature later from exile.)

View File

@@ -17,4 +17,4 @@ Types:Instant Adventure
A:SP$ ChangeZone | ValidTgts$ Creature.YouCtrl | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | RememberTargets$ True | SubAbility$ DBReturn | StackDescription$ Exile {c:Targeted}, | SpellDescription$ Exile target creature you control, then return that card to the battlefield under its owner's control.
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | StackDescription$ then return it to the battlefield under its owner's control.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Exile target creature you control, then return that card to the battlefield under its owner's control. (Then exile this card. You may cast the creature later from exile.)
Oracle:Exile target creature you control, then return that card to the battlefield under its owner's control. (Then exile this card. You may cast the creature later from exile.)

View File

@@ -2,5 +2,6 @@ Name:Wellgabber Apothecary
ManaCost:4 W
Types:Creature Merfolk Cleric
PT:2/3
A:AB$ Pump | Cost$ 1 W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
A:SP$ Effect | Cost$ 1 W | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
Oracle:{1}{W}: Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.

View File

@@ -1503,7 +1503,7 @@ lblAttachee=Anhang
lblNumberBlockers=Anzahl Blocker
lblBlocker=Blocker
#TriggerAttackerBlockedOnce.java
lblAttackers=Attackers
lblAttackers=Angreifer
#TriggerAttackersDeclared.java
lblNumberAttackers=Anzahl Angreifer
#TriggerAttackerUnblockedOnce.java

View File

@@ -164,7 +164,7 @@ public class HumanPlaySpellAbility {
if (!prerequisitesMet) {
if (!ability.isTrigger()) {
rollbackAbility(fromZone, zonePosition, payment, c);
GameActionUtil.rollbackAbility(ability, fromZone, zonePosition, payment, c);
if (ability.getHostCard().isMadness()) {
// if a player failed to play madness cost, move the card to graveyard
Card newCard = game.getAction().moveToGraveyard(c, null);
@@ -209,48 +209,6 @@ public class HumanPlaySpellAbility {
return true;
}
private void rollbackAbility(final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) {
// cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy
oldCard.setCastSA(null);
oldCard.setCastFrom(null);
// add back to where it came from, hopefully old state
// skip GameAction
oldCard.getZone().remove(oldCard);
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
ability.setHostCard(oldCard);
ability.setXManaCostPaid(null);
ability.setSpendPhyrexianMana(false);
if (ability.hasParam("Announce")) {
for (final String aVar : ability.getParam("Announce").split(",")) {
final String varName = aVar.trim();
if (!varName.equals("X")) {
ability.setSVar(varName, "0");
}
}
}
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
for (SpellAbility sa : oldCard.getSpells()) {
sa.setHostCard(oldCard);
}
//for Chorus of the Conclave
ability.rollback();
oldCard.setBackSide(false);
oldCard.setState(oldCard.getFaceupCardStateName(), true);
oldCard.unanimateBestow();
}
ability.clearTargets();
ability.resetOnceResolved();
payment.refundPayment();
game.getStack().clearFrozen();
game.getTriggerHandler().clearWaitingTriggers();
}
private boolean announceValuesLikeX() {
if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies