Merge branch 'patch' into 'master'

Add Inniaz, the Gale Force

See merge request core-developers/forge!2921
This commit is contained in:
swordshine
2020-06-28 01:44:50 +00:00
12 changed files with 171 additions and 116 deletions

View File

@@ -84,7 +84,7 @@ public enum SpellApiToAi {
.put(ApiType.FlipACoin, FlipACoinAi.class)
.put(ApiType.Fog, FogAi.class)
.put(ApiType.GainControl, ControlGainAi.class)
.put(ApiType.GainControlVariant, AlwaysPlayAi.class)
.put(ApiType.GainControlVariant, ControlGainVariantAi.class)
.put(ApiType.GainLife, LifeGainAi.class)
.put(ApiType.GainOwnership, CannotPlayAi.class)
.put(ApiType.GameDrawn, CannotPlayAi.class)

View File

@@ -0,0 +1,82 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* <p>
* AbilityFactory_GainControlVariant class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/
public class ControlGainVariantAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
String logic = sa.getParam("AILogic");
if ("GainControlOwns".equals(logic)) {
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
}
});
if (list.isEmpty()) {
return false;
}
for (final Card c : list) {
if (ai.equals(c.getController())) {
return false;
}
}
}
return true;
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
Iterable<Card> otherCtrl = CardLists.filter(options, Predicates.not(CardPredicates.isController(ai)));
if (Iterables.isEmpty(otherCtrl)) {
return ComputerUtilCard.getWorstAI(options);
} else {
return ComputerUtilCard.getBestAI(otherCtrl);
}
}
}

View File

@@ -1,6 +1,5 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
@@ -48,21 +47,6 @@ public class RepeatEachAi extends SpellAbilityAi {
return false;
}
}
} else if ("GainControlOwns".equals(logic)) {
List<Card> list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
}
});
if (list.isEmpty()) {
return false;
}
for (final Card c : list) {
if (aiPlayer.equals(c.getController())) {
return false;
}
}
} else if ("OpponentHasCreatures".equals(logic)) {
for (Player opp : aiPlayer.getOpponents()) {
if (!opp.getCreaturesInPlay().isEmpty()){

View File

@@ -1,13 +1,22 @@
package forge.game.ability.effects;
import java.util.Collections;
import java.util.Map;
import com.google.common.collect.Maps;
import forge.game.Direction;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
public class ControlGainVariantEffect extends SpellAbilityEffect {
/* (non-Javadoc)
@@ -20,28 +29,68 @@ public class ControlGainVariantEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
// Aminatou, the Fateshifter (multiple players gain control of multiple permanents in an effect)
// Consider migrating cards with similar effects
// Multiple players gain control of multiple permanents in an effect
// GainControl embedded in RepeatEach effects don't work well with timestamps
final Card source = sa.getHostCard();
final Game game = source.getGame();
long tStamp = game.getNextTimestamp();
final String controller = sa.getParam("ChangeController");
final Map<Player, CardCollection> gainControl = Maps.newHashMap(); // {newController, CardCollection}
final PlayerCollection players = game.getPlayers();
int aidx = players.indexOf(sa.getActivatingPlayer());
if (aidx != -1) {
Collections.rotate(players, -aidx);
}
CardCollection tgtCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
sa.getParam("AllValid"), source.getController(), source);
if ("NextPlayerInChosenDirection".equals(sa.getParam("ChangeController")) && (source.getChosenDirection() != null) ) {
long tStamp = game.getNextTimestamp();
for (final Player p : game.getPlayers()) {
if ("NextPlayerInChosenDirection".equals(controller) && (source.getChosenDirection() != null) ) {// Aminatou, the Fateshifter
for (final Player p : players) {
gainControl.put(p, CardLists.filterControlledBy(tgtCards, game.getNextPlayerAfter(p, source.getChosenDirection())));
}
} else if ("CardOwner".equals(controller)) {// Homeward Path, Trostani Discordant etc.
for (final Player p : players) {
gainControl.put(p, CardLists.filter(tgtCards, CardPredicates.isOwner(p)));
}
} else if ("Random".equals(controller)) {// Scrambleverse
for (final Card c : tgtCards) {
final Player p = Aggregates.random(players);
if (gainControl.containsKey(p)) {
gainControl.get(p).add(0, c);
} else {
gainControl.put(p, new CardCollection(c));
}
}
} else if ("ChooseNextPlayerInChosenDirection".equals(controller) && (source.getChosenDirection() != null)) {// Order of Succession
Player p = sa.getActivatingPlayer();
do {
final CardCollection valid = CardLists.filterControlledBy(tgtCards, game.getNextPlayerAfter(p, source.getChosenDirection()));
final Card c = p.getController().chooseSingleEntityForEffect(valid, sa, " ", null);
if (c != null) {
gainControl.put(p, new CardCollection(c));
}
p = game.getNextPlayerAfter(p, source.getChosenDirection());
} while (!p.equals(sa.getActivatingPlayer()));
} else if ("ChooseFromPlayerToTheirRight".equals(controller)) {// Inniaz, the Gale Force
for (final Player p : players) {
final CardCollection valid = CardLists.filterControlledBy(tgtCards, game.getNextPlayerAfter(p, Direction.Right));
final Card c = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(valid, sa,
"Choose one for the new Controller: " + p.getName(), null);
if (c != null) {
gainControl.put(p, new CardCollection(c));
}
}
}
CardCollection valid = CardLists.filterControlledBy(tgtCards, game.getNextPlayerAfter(p, source.getChosenDirection()));
for (Card tgtC : valid) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(p)) {
for (Map.Entry<Player, CardCollection> e : gainControl.entrySet()) {
final Player newController = e.getKey();
for (Card tgtC : e.getValue()) {
if (!tgtC.isInPlay() || !tgtC.canBeControlledBy(newController)) {
continue;
}
tgtC.setController(p, tStamp);
tgtCards.remove(tgtC); // remove from the list if controller is changed
}
tgtC.setController(newController, tStamp);
}
}
}

View File

@@ -1,8 +1,6 @@
package forge.game.ability.effects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -13,13 +11,9 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
import forge.util.Localizer;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class RepeatEachEffect extends SpellAbilityEffect {
@@ -46,7 +40,6 @@ public class RepeatEachEffect extends SpellAbilityEffect {
boolean useImprinted = sa.hasParam("UseImprinted");
boolean loopOverCards = false;
boolean recordChoice = sa.hasParam("RecordChoice");
CardCollectionView repeatCards = null;
List<SpellAbility> repeatSas = null;
@@ -59,7 +52,6 @@ public class RepeatEachEffect extends SpellAbilityEffect {
}
repeatCards = CardLists.getValidCards(game.getCardsIn(zone),
sa.getParam("RepeatCards"), source.getController(), source);
loopOverCards = !recordChoice;
}
else if (sa.hasParam(("RepeatSpellAbilities"))) {
repeatSas = Lists.newArrayList();
@@ -138,10 +130,9 @@ public class RepeatEachEffect extends SpellAbilityEffect {
boolean optional = sa.hasParam("RepeatOptionalForEachPlayer");
boolean nextTurn = sa.hasParam("NextTurnForEachPlayer");
if (sa.hasParam("StartingWithActivator")) {
int size = repeatPlayers.size();
Player activator = sa.getActivatingPlayer();
while (!activator.equals(repeatPlayers.getFirst())) {
repeatPlayers.add(size - 1, repeatPlayers.remove(0));
int aidx = repeatPlayers.indexOf(player);
if (aidx != -1) {
Collections.rotate(repeatPlayers, -aidx);
}
}
for (final Player p : repeatPlayers) {
@@ -164,59 +155,6 @@ public class RepeatEachEffect extends SpellAbilityEffect {
}
}
}
if (recordChoice) {
boolean random = sa.hasParam("Random");
Map<Player, List<Card>> recordMap = Maps.newHashMap();
if (sa.hasParam("ChoosePlayer")) {
for (Card card : repeatCards) {
Player p;
if (random) {
p = Aggregates.random(game.getPlayers());
} else {
p = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(game.getPlayers(), sa, Localizer.getInstance().getMessage("lblChoosePlayer"), null);
}
if (recordMap.containsKey(p)) {
recordMap.get(p).add(0, card);
} else {
recordMap.put(p, Lists.newArrayList(card));
}
}
}
else if (sa.hasParam("ChooseCard")) {
List<Card> list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
sa.getParam("ChooseCard"), source.getController(), source);
String filterController = sa.getParam("FilterControlledBy");
// default: Starting with you and proceeding in the chosen direction
Player p = sa.getActivatingPlayer();
do {
CardCollection valid = new CardCollection(list);
if ("NextPlayerInChosenDirection".equals(filterController)) {
valid = CardLists.filterControlledBy(valid,
game.getNextPlayerAfter(p, source.getChosenDirection()));
}
Card card = p.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseaCard"), null);
if (recordMap.containsKey(p)) {
recordMap.get(p).add(0, card);
} else {
recordMap.put(p, Lists.newArrayList(card));
}
if (source.getChosenDirection() != null) {
p = game.getNextPlayerAfter(p, source.getChosenDirection());
} else {
p = game.getNextPlayerAfter(p);
}
} while (!p.equals(sa.getActivatingPlayer()));
}
for (Entry<Player, List<Card>> entry : recordMap.entrySet()) {
// Remember the player and imprint the cards
source.addRemembered(entry.getKey());
source.addImprintedCards(entry.getValue());
AbilityUtils.resolve(repeat);
source.removeRemembered(entry.getKey());
source.removeImprintedCards(entry.getValue());
}
}
if(sa.hasParam("DamageMap")) {
sa.getPreventMap().triggerPreventDamage(false);

View File

@@ -3,7 +3,5 @@ ManaCost:2 G G
Types:Creature Lizard
PT:4/4
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigGainControl | TriggerDescription$ At the beginning of each end step, each player gains control of all nontoken permanents they own.
SVar:TrigGainControl:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBGainControl
SVar:DBGainControl:DB$ GainControl | AllValid$ Permanent.nonToken+RememberedPlayerOwn | NewController$ Player.IsRemembered
SVar:Picture:http://www.wizards.com/global/images/magic/general/brooding_saurian.jpg
SVar:TrigGainControl:DB$ GainControlVariant | AllValid$ Permanent.nonToken | ChangeController$ CardOwner
Oracle:At the beginning of each end step, each player gains control of all nontoken permanents they own.

View File

@@ -6,8 +6,6 @@ K:Flying
K:Cumulative upkeep:GainControl<1/Land.YouDontCtrl/land you don't control>:Gain control of a land you don't control.
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | References$ X | Description$ CARDNAME gets +1/+1 for each land you control but don't own.
SVar:X:Count$Valid Land.YouCtrl+YouDontOwn
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME leaves the battlefield, each player gains control of each land they own that you control.
SVar:TrigRepeat:DB$ RepeatEach | RepeatSubAbility$ DBGainControl | RepeatCards$ Land.YouCtrl
SVar:DBGainControl:DB$ GainControl | Defined$ Remembered | NewController$ RememberedOwner
SVar:Picture:http://www.wizards.com/global/images/magic/general/herald_of_leshrac.jpg
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigGainControl | TriggerDescription$ When CARDNAME leaves the battlefield, each player gains control of each land they own that you control.
SVar:TrigGainControl:DB$ GainControlVariant | AllValid$ Land.YouCtrl | ChangeController$ CardOwner
Oracle:Flying\nCumulative upkeep—Gain control of a land you don't control. (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nHerald of Leshrac gets +1/+1 for each land you control but don't own.\nWhen Herald of Leshrac leaves the battlefield, each player gains control of each land they own that you control.

View File

@@ -2,7 +2,5 @@ Name:Homeward Path
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ RepeatEach | Cost$ T | RepeatPlayers$ Player | AILogic$ GainControlOwns | RepeatSubAbility$ DBGainControl | SpellDescription$ Each player gains control of all creatures they own.
SVar:DBGainControl:DB$ GainControl | AllValid$ Creature.RememberedPlayerOwn | NewController$ Player.IsRemembered
SVar:Picture:http://www.wizards.com/global/images/magic/general/homeward_path.jpg
A:AB$ GainControlVariant | Cost$ T | AILogic$ GainControlOwns | AllValid$ Creature | ChangeController$ CardOwner | SpellDescription$ Each player gains control of all creatures they own.
Oracle:{T}: Add {C}.\n{T}: Each player gains control of all creatures they own.

View File

@@ -0,0 +1,12 @@
Name:Inniaz, the Gale Force
ManaCost:3 U U
Types:Legendary Creature Djinn
PT:4/4
K:Flying
A:AB$ PumpAll | Cost$ 2 WU | ValidCards$ Creature.attacking+withFlying | NumAtt$ 1 | NumDef$ 1 | SpellDescription$ Attacking creatures with flying get +1/+1 until end of turn.
T:Mode$ AttackersDeclared | AttackingPlayer$ You | CheckSVar$ CheckAttackers | SVarCompare$ GE3 | NoResolvingCheck$ True | Execute$ TrigGainControl | TriggerZones$ Battlefield | TriggerDescription$ Whenever three or more creatures you control with flying attack, each player gains control of a nonland permanent of your choice controlled by the player to their right.
SVar:CheckAttackers:Count$Valid Creature.withFlying+YouCtrl+attacking
SVar:TrigGainControl:DB$ GainControlVariant | AllValid$ Permanent.nonLand | ChangeController$ ChooseFromPlayerToTheirRight
SVar:PlayMain1:TRUE
DeckHints:Keyword$Flying
Oracle:Flying\n{2}{W/U}: Attacking creatures with flying get +1/+1 until end of turn. ({W/U} can be paid with either {W} or {U}.)\nWhenever three or more creatures you control with flying attack, each player gains control of a nonland permanent of your choice controlled by the player to their right.

View File

@@ -1,9 +1,8 @@
Name:Order of Succession
ManaCost:3 U
Types:Sorcery
A:SP$ ChooseDirection | Cost$ 3 U | SubAbility$ DBRepeat | AILogic$ GainControl | SpellDescription$ Choose left or right. Starting with you and proceeding in the chosen direction, each player chooses a creature controlled by the next player in that direction. Each player gains control of the creature they chose.
SVar:DBRepeat:DB$ RepeatEach | RepeatSubAbility$ DBGainControl | RecordChoice$ True | ChooseCard$ Creature | FilterControlledBy$ NextPlayerInChosenDirection
SVar:DBGainControl:DB$ GainControl | NewController$ Remembered | AllValid$ Card.IsImprinted
A:SP$ ChooseDirection | Cost$ 3 U | SubAbility$ DBGainControl | AILogic$ GainControl | SpellDescription$ Choose left or right. Starting with you and proceeding in the chosen direction, each player chooses a creature controlled by the next player in that direction. Each player gains control of the creature they chose.
SVar:DBGainControl:DB$ GainControlVariant | AllValid$ Creature | ChangeController$ ChooseNextPlayerInChosenDirection
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/order_of_succession.jpg
Oracle:Choose left or right. Starting with you and proceeding in the chosen direction, each player chooses a creature controlled by the next player in that direction. Each player gains control of the creature they chose.

View File

@@ -1,9 +1,8 @@
Name:Scrambleverse
ManaCost:6 R R
Types:Sorcery
A:SP$ RepeatEach | Cost$ 6 R R | RecordChoice$ True | ChoosePlayer$ True | Random$ True | RepeatSubAbility$ DBGainControl | RepeatCards$ Permanent.nonLand | SpellDescription$ For each nonland permanent, choose a player at random. Then each player gains control of each permanent for which they were chosen. Untap those permanents.
SVar:DBGainControl:DB$ GainControl | NewController$ Player.IsRemembered | AllValid$ Permanent.IsImprinted | Untap$ True
A:SP$ GainControlVariant | Cost$ 6 R R | AllValid$ Permanent.nonLand | ChangeController$ Random | SubAbility$ DBUntap | SpellDescription$ For each nonland permanent, choose a player at random. Then each player gains control of each permanent for which they were chosen. Untap those permanents.
SVar:DBUntap:DB$ UntapAll | ValidCards$ Permanent.nonLand
AI:RemoveDeck:All
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/scrambleverse.jpg
Oracle:For each nonland permanent, choose a player at random. Then each player gains control of each permanent for which they were chosen. Untap those permanents.

View File

@@ -6,7 +6,5 @@ S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | AddToughne
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create two 1/1 white Soldier creature tokens with lifelink.
SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ w_1_1_soldier_lifelink | LegacyImage$ w 1 1 soldier lifelink grn
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigGainControl | TriggerDescription$ At the beginning of your end step, each player gains control of all creatures they own.
SVar:TrigGainControl:DB$ RepeatEach | RepeatPlayers$ Player | AILogic$ GainControlOwns | RepeatSubAbility$ DBGainControl | SpellDescription$ Each player gains control of all creatures they own.
SVar:DBGainControl:DB$ GainControl | AllValid$ Creature.RememberedPlayerOwn | NewController$ Player.IsRemembered
DeckHas:Ability$Token & Ability$LifeGain
SVar:TrigGainControl:DB$ GainControlVariant | AILogic$ GainControlOwns | AllValid$ Creature | ChangeController$ CardOwner |
Oracle:Other creatures you control get +1/+1.\nWhen Trostani Discordant enters the battlefield, create two 1/1 white Soldier creature tokens with lifelink.\nAt the beginning of your end step, each player gains control of all creatures they own.