Merge branch 'ranar-2' into 'master'

Ranar v2

See merge request core-developers/forge!4534
This commit is contained in:
Michael Kamensky
2021-04-17 04:22:26 +00:00
36 changed files with 89 additions and 40 deletions

View File

@@ -1260,7 +1260,7 @@ public class GameAction {
if (game.getCombat() != null) { if (game.getCombat() != null) {
game.getCombat().removeAbsentCombatants(); game.getCombat().removeAbsentCombatants();
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, null);
if (!checkAgain) { if (!checkAgain) {
break; // do not continue the loop break; // do not continue the loop
} }

View File

@@ -657,7 +657,7 @@ public abstract class SpellAbilityEffect {
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard); untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);
} }
} }
untilTable.triggerChangesZoneAll(game); untilTable.triggerChangesZoneAll(game, null);
} }
}; };

View File

@@ -74,7 +74,7 @@ public class AmassEffect extends TokenEffectBase {
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged); makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
} }
game.fireEvent(new GameEventTokenCreated()); game.fireEvent(new GameEventTokenCreated());

View File

@@ -43,7 +43,7 @@ public class AttachEffect extends SpellAbilityEffect {
if (newZone != previousZone) { if (newZone != previousZone) {
table.put(previousZone, newZone, c); table.put(previousZone, newZone, c);
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();

View File

@@ -68,6 +68,6 @@ public class BalanceEffect extends SpellAbilityEffect {
discard(sa, table, discardedMap); discard(sa, table, discardedMap);
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -268,7 +268,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
} }
} }
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
if (sa.hasParam("UntilHostLeavesPlay")) { if (sa.hasParam("UntilHostLeavesPlay")) {
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source)); source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));

View File

@@ -811,7 +811,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
game.getAction().reveal(commandCards, player, true, "Revealed cards in "); game.getAction().reveal(commandCards, player, true, "Revealed cards in ");
} }
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
counterTable.triggerCountersPutAll(game); counterTable.triggerCountersPutAll(game);
@@ -1411,7 +1411,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
if (sa.hasParam("UntilHostLeavesPlay")) { if (sa.hasParam("UntilHostLeavesPlay")) {
source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source)); source.addLeavesPlayCommand(untilHostLeavesPlayCommand(triggerList, source));

View File

@@ -17,7 +17,7 @@ public class ChangeZoneResolveEffect extends SpellAbilityEffect {
CardZoneTable table = sa.getChangeZoneTable(); CardZoneTable table = sa.getChangeZoneTable();
if (table != null) { if (table != null) {
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
table.clear(); table.clear();
} }
} }

View File

@@ -187,7 +187,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
} // end foreach Card } // end foreach Card
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
} }
if (combatChanged.isTrue()) { if (combatChanged.isTrue()) {

View File

@@ -97,7 +97,7 @@ public class DestroyAllEffect extends SpellAbilityEffect {
card.addRemembered(CardUtil.getLKICopy(c, cachedMap)); card.addRemembered(CardUtil.getLKICopy(c, cachedMap));
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -104,7 +104,7 @@ public class DestroyEffect extends SpellAbilityEffect {
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap) { protected void internalDestroy(Card gameCard, SpellAbility sa, CardZoneTable table, Map<Integer, Card> cachedMap) {

View File

@@ -406,7 +406,7 @@ public class DigEffect extends SpellAbilityEffect {
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
//table trigger there //table trigger there
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
counterTable.triggerCountersPutAll(game); counterTable.triggerCountersPutAll(game);
} }

View File

@@ -183,7 +183,7 @@ public class DigMultipleEffect extends SpellAbilityEffect {
} }
} }
//table trigger there //table trigger there
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -260,7 +260,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} // end resolve } // end resolve
} }

View File

@@ -292,6 +292,6 @@ public class DiscardEffect extends SpellAbilityEffect {
discard(sa, table, discardedMap); discard(sa, table, discardedMap);
// run trigger if something got milled // run trigger if something got milled
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} // discardResolve() } // discardResolve()
} }

View File

@@ -91,7 +91,7 @@ public class ExploreEffect extends SpellAbilityEffect {
game.getTriggerHandler().runTrigger(TriggerType.Explores, AbilityKey.mapFromCard(c), false); game.getTriggerHandler().runTrigger(TriggerType.Explores, AbilityKey.mapFromCard(c), false);
} }
table.triggerCountersPutAll(game); table.triggerCountersPutAll(game);
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -45,7 +45,7 @@ public class InvestigateEffect extends TokenEffectBase {
MutableBoolean combatChanged = new MutableBoolean(false); MutableBoolean combatChanged = new MutableBoolean(false);
makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged); makeTokens(prototype, p, sa, 1, true, false, triggerList, combatChanged);
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
p.addInvestigatedThisTurn(); p.addInvestigatedThisTurn();
game.fireEvent(new GameEventTokenCreated()); game.fireEvent(new GameEventTokenCreated());

View File

@@ -21,7 +21,7 @@ public class LearnEffect extends SpellAbilityEffect {
for (Player p : getTargetPlayers(sa)) { for (Player p : getTargetPlayers(sa)) {
p.learnLesson(sa, table); p.learnLesson(sa, table);
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -81,7 +81,7 @@ public class MillEffect extends SpellAbilityEffect {
} }
// run trigger if something got milled // run trigger if something got milled
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
@Override @Override

View File

@@ -40,6 +40,6 @@ public class PermanentEffect extends SpellAbilityEffect {
if (newZone != previousZone) { if (newZone != previousZone) {
table.put(previousZone, newZone, c); table.put(previousZone, newZone, c);
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -173,7 +173,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
sa.setDamageMap(null); sa.setDamageMap(null);
} }
if (sa.hasParam("ChangeZoneTable")) { if (sa.hasParam("ChangeZoneTable")) {
sa.getChangeZoneTable().triggerChangesZoneAll(game); sa.getChangeZoneTable().triggerChangesZoneAll(game, sa);
sa.setChangeZoneTable(null); sa.setChangeZoneTable(null);
} }
} }

View File

@@ -97,7 +97,7 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
} }
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
} }

View File

@@ -186,7 +186,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
@Override @Override

View File

@@ -92,7 +92,7 @@ public class TokenEffect extends TokenEffectBase {
} }
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
} }

View File

@@ -5,14 +5,12 @@ package forge.game.card;
import java.util.Map; import java.util.Map;
import com.google.common.collect.ForwardingTable; import com.google.common.collect.*;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -53,11 +51,12 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
return dataMap; return dataMap;
} }
public void triggerChangesZoneAll(final Game game) { public void triggerChangesZoneAll(final Game game, final SpellAbility cause) {
if (!isEmpty()) { if (!isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Cards, new CardZoneTable(this)); runParams.put(AbilityKey.Cards, new CardZoneTable(this));
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false); runParams.put(AbilityKey.Cause, cause);
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, AbilityKey.newMap(runParams), false);
} }
} }

View File

@@ -237,7 +237,7 @@ public class CostAdjustment {
table.put(ZoneType.Graveyard, d.getZone().getZoneType(), d); table.put(ZoneType.Graveyard, d.getZone().getZoneType(), d);
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, null);
} }
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) { if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test); adjustCostByConvokeOrImprovise(cost, sa, false, test);
@@ -510,6 +510,13 @@ public class CostAdjustment {
if (!sa.isPwAbility()) { if (!sa.isPwAbility()) {
return false; return false;
} }
} else if (type.equals("Foretell")) {
if (!sa.isForetelling()) {
return false;
}
if (st.hasParam("FirstForetell") && activator.getNumForetoldThisTurn() > 0) {
return false;
}
} }
} }
if (st.hasParam("AffectedZone")) { if (st.hasParam("AffectedZone")) {

View File

@@ -101,7 +101,7 @@ public class CostMill extends CostPart {
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) { public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
ability.getPaidHash().put("Milled", (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, false, ability, table)); ability.getPaidHash().put("Milled", (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, false, ability, table));
table.triggerChangesZoneAll(ai.getGame()); table.triggerChangesZoneAll(ai.getGame(), null);
return true; return true;
} }

View File

@@ -64,8 +64,6 @@ public abstract class CostPartWithList extends CostPart {
* *
* @param sa * @param sa
* the sa * the sa
* @param hash
* the hash
*/ */
public final void reportPaidCardsTo(final SpellAbility sa) { public final void reportPaidCardsTo(final SpellAbility sa) {
if (sa == null) { if (sa == null) {
@@ -177,7 +175,7 @@ public abstract class CostPartWithList extends CostPart {
// copy table because the original get cleaned after the cost is done // copy table because the original get cleaned after the cost is done
final CardZoneTable copyTable = new CardZoneTable(); final CardZoneTable copyTable = new CardZoneTable();
copyTable.putAll(table); copyTable.putAll(table);
copyTable.triggerChangesZoneAll(payer.getGame()); copyTable.triggerChangesZoneAll(payer.getGame(), null);
} }
} }

View File

@@ -398,7 +398,7 @@ public class PhaseHandler implements java.io.Serializable {
discarded.add(c); discarded.add(c);
} }
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, null);
if (!discarded.isEmpty()) { if (!discarded.isEmpty()) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
@@ -1068,7 +1068,7 @@ public class PhaseHandler implements java.io.Serializable {
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played // currently there can be only one Spell put on the Stack at once, or Land Abilities be played
final CardZoneTable triggerList = new CardZoneTable(); final CardZoneTable triggerList = new CardZoneTable();
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost); triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, null);
} }
} }
@@ -1262,7 +1262,7 @@ public class PhaseHandler implements java.io.Serializable {
/** /**
* returns the continuous extra turn count * returns the continuous extra turn count
* @param PLayer p * @param p
* @return int * @return int
*/ */
public int getExtraTurnForPlayer(final Player p) { public int getExtraTurnForPlayer(final Player p) {

View File

@@ -2599,6 +2599,7 @@ public class Player extends GameEntity implements Comparable<Player> {
resetPreventNextDamageWithEffect(); resetPreventNextDamageWithEffect();
resetNumDrawnThisTurn(); resetNumDrawnThisTurn();
resetNumDiscardedThisTurn(); resetNumDiscardedThisTurn();
resetNumForetoldThisTurn();
resetNumTokenCreatedThisTurn(); resetNumTokenCreatedThisTurn();
setNumCardsInHandStartedThisTurnWith(getCardsIn(ZoneType.Hand).size()); setNumCardsInHandStartedThisTurnWith(getCardsIn(ZoneType.Hand).size());
clearCreaturesAttackedThisTurn(); clearCreaturesAttackedThisTurn();

View File

@@ -2003,6 +2003,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false; return false;
} }
} }
else if (incR[0].equals("SpellAbility")) {
// Match anything
}
else { //not a spell/ability type else { //not a spell/ability type
return false; return false;
} }

View File

@@ -21,6 +21,10 @@ public class TriggerChangesZoneAll extends Trigger {
public boolean performTest(Map<AbilityKey, Object> runParams) { public boolean performTest(Map<AbilityKey, Object> runParams) {
final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards);
if (!matchesValidParam("ValidCause", runParams.get(AbilityKey.Cause))) {
return false;
}
return !filterCards(table).isEmpty(); return !filterCards(table).isEmpty();
} }
@@ -32,6 +36,7 @@ public class TriggerChangesZoneAll extends Trigger {
sa.setTriggeringObject(AbilityKey.Cards, allCards); sa.setTriggeringObject(AbilityKey.Cards, allCards);
sa.setTriggeringObject(AbilityKey.Amount, allCards.size()); sa.setTriggeringObject(AbilityKey.Amount, allCards.size());
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Cause);
} }
@Override @Override

View File

@@ -0,0 +1,12 @@
Name:Hero of Bretagard
ManaCost:2 W
Types:Creature Human Warrior
PT:1/1
T:Mode$ ChangesZoneAll | ValidCause$ SpellAbility.YouCtrl | Origin$ Hand,Battlefield | Destination$ Exile | ValidCards$ Card.YouOwn,Card.inZoneBattlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a spell or ability you control exiles one or more cards from your hand and/or permanents from the battlefield, put that many +1/+1 counters on CARDNAME.
SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X
S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ Y | SVarCompare$ GT4 | AddKeyword$ Flying | AddType$ Angel | Description$ As long as CARDNAME has five or more counters on it, it has flying and is an Angel in addition to its other types.
S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ Y | SVarCompare$ GT9 | AddKeyword$ Indestructible | AddType$ Angel & God | Description$ As long as CARDNAME has ten or more counters on it, it indestructible and is a God in addition to its other types.
SVar:X:TriggerCount$Amount
SVar:Y:Count$CardCounters.ALL
DeckHas:Ability$Counters
Oracle:Whenever a spell or ability you control exiles one or more cards from your hand and/or permanents from the battlefield, put that many +1/+1 counters on Hero of Bretagard.\nAs long as Hero of Bretagard has five or more counters on it, it has flying and is an Angel in addition to its other types.\nAs long as Hero of Bretagard has ten or more counters on it, it has indestructible and is a God in addition to its other types.

View File

@@ -0,0 +1,13 @@
Name:Laelia, the Blade Reforged
ManaCost:2 R
Types:Legendary Creature Spirit Warrior
PT:2/2
K:Haste
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME attacks, exile the top card of your library. You may play that card this turn.
SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play this card this turn.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
T:Mode$ ChangesZoneAll | ValidCause$ SpellAbility.YouCtrl | Origin$ Library,Graveyard | Destination$ Exile | ValidCards$ Card.YouOwn | Execute$ TrigPutCounter | TriggerDescription$ Whenever you exile one or more cards from your library and/or your graveyard, put a +1/+1 counter on NICKNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
Oracle:Haste\nWhenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn.\nWhenever you exile one or more cards from your library and/or your graveyard, put a +1/+1 counter on Laelia.

View File

@@ -0,0 +1,11 @@
Name:Ranar the Ever-Watchful
ManaCost:2 W U
Types:Legendary Creature Spirit Warrior
PT:2/3
K:Flying
K:Vigilance
S:Mode$ ReduceCost | ValidCard$ Card | Type$ Foretell | Amount$ 2 | FirstForetell$ True | Activator$ You | Description$ The first card you foretell each turn costs {0} to foretell.
T:Mode$ ChangesZoneAll | ValidCause$ SpellAbility.YouCtrl | Origin$ Hand,Battlefield | Destination$ Exile | ValidCards$ Card.YouOwn,Card.inZoneBattlefield | Execute$ TrigToken | TriggerDescription$ Whenever a spell or ability you control exiles one or more cards from your hand and/or permanents from the battlefield, create a 1/1 white Spirit creature token with flying.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_spirit_flying | TokenOwner$ You
DeckHas:Ability$Token
Oracle:Flying, vigilance\nThe first card you foretell each turn costs {0} to foretell.\nWhenever a spell or ability you control exiles one or more cards from your hand and/or permanents from the battlefield, create a 1/1 white Spirit creature token with flying.

View File

@@ -698,7 +698,7 @@ public class HumanPlay {
ability.clearTappedForConvoke(); ability.clearTappedForConvoke();
} }
if (!table.isEmpty() && !manaInputCancelled) { if (!table.isEmpty() && !manaInputCancelled) {
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, null);
} }
return !manaInputCancelled; return !manaInputCancelled;
} }