...
 
Commits (25)
......@@ -73,6 +73,7 @@ public class ComputerUtil {
public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) {
game.getStack().freezeStack();
final Card source = sa.getHostCard();
source.setSplitStateToPlayAbility(sa);
if (sa.isSpell() && !source.isCopiedSpell()) {
if (source.getType().hasStringType("Arcane")) {
......
......@@ -410,10 +410,11 @@ public class TokenAi extends SpellAbilityAi {
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
String tokenPower = sa.getParam("TokenPower");
String tokenToughness = sa.getParam("TokenToughness");
String tokenName = sa.getParam("TokenName");
String[] tokenTypes = sa.getParam("TokenTypes").split(",");
String tokenName = sa.getParamOrDefault("TokenName","");
String[] tokenTypes = sa.getParamOrDefault("TokenTypes","").split(",");
String cost = "";
String[] tokenColors = sa.getParam("TokenColors").split(",");
String[] tokenColors = sa.getParamOrDefault("TokenColors","").split(",");
String tokenImage = sa.hasParam("TokenImage") ? PaperToken.makeTokenFileName(sa.getParam("TokenImage")) : "";
String[] tokenAbilities = sa.hasParam("TokenAbilities") ? sa.getParam("TokenAbilities").split(",") : null;
String[] tokenTriggers = sa.hasParam("TokenTriggers") ? sa.getParam("TokenTriggers").split(",") : null;
......
......@@ -1580,7 +1580,7 @@ public class GameAction {
game.setAge(GameStage.Mulligan);
for (final Player p1 : game.getPlayers()) {
if (StaticData.instance().getFilteredHandsEnabled() ) {
if (true /*StaticData.instance().getFilteredHandsEnabled()*/ ) {
drawStartingHand(p1);
} else {
p1.drawCards(p1.getStartingHandSize());
......
......@@ -10,9 +10,11 @@ import forge.game.trigger.TriggerType;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
public class BecomesBlockedEffect extends SpellAbilityEffect {
......@@ -38,10 +40,11 @@ public class BecomesBlockedEffect extends SpellAbilityEffect {
game.getCombat().setBlocked(c, true);
if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) {
isCombatChanged = true;
final HashMap<String, Object> runParams = new HashMap<String, Object>();
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Attacker", c);
runParams.put("Blockers", new ArrayList<Card>());
runParams.put("Blockers", Lists.<Card>newArrayList());
runParams.put("NumBlockers", 0);
runParams.put("Defender", game.getCombat().getDefenderByAttacker(c));
runParams.put("DefendingPlayer", game.getCombat().getDefenderPlayerByAttacker(c));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
}
......
......@@ -1689,7 +1689,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|| keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize")
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|| keyword.startsWith("Transfigure")
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) {
// keyword parsing takes care of adding a proper description
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
......
......@@ -3746,6 +3746,19 @@ public class CardFactoryUtil {
origSA.setAftermath(true);
origSA.getRestrictions().setZone(ZoneType.Graveyard);
// The Exile part is done by the System itself
} else if (keyword.startsWith("Aura swap")) {
final String[] k = keyword.split(":");
final String manacost = k[1];
final String effect = "AB$ ExchangeZone | Cost$ " + manacost + " | Zone2$ Hand | Type$ Aura "
+ " | PrecostDesc$ Aura swap | CostDesc$ " + ManaCostParser.parse(manacost)
+ " | StackDescription$ SpellDescription | SpellDescription$ (" + inst.getReminderText() + ")";
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
sa.setIntrinsic(intrinsic);
sa.setTemporary(!intrinsic);
inst.addSpellAbility(sa);
} else if (keyword.startsWith("Awaken")) {
final String[] k = keyword.split(":");
final String counters = k[1];
......
......@@ -65,7 +65,7 @@ public final class CardUtil {
"Transmute", "Replicate", "Recover", "Suspend", "Aura swap",
"Fortify", "Transfigure", "Champion", "Evoke", "Prowl", "IfReach",
"Reinforce", "Unearth", "Level up", "Miracle", "Overload",
"Scavenge", "Bestow", "Outlast", "Dash", "Renown", "Surge", "Emerge", "Hexproof:").build();
"Scavenge", "Bestow", "Outlast", "Dash", "Surge", "Emerge", "Hexproof:").build();
/** List of keyword endings of keywords that could be modified by text changes. */
public static final ImmutableList<String> modifiableKeywordEndings = ImmutableList.<String>builder().add(
"walk", "cycling", "offering").build();
......
......@@ -389,7 +389,6 @@ public class PhaseHandler implements java.io.Serializable {
}
playerTurn.removeKeyword("Skip all combat phases of this turn.");
game.getCleanup().executeUntil(getNextTurn());
nUpkeepsThisTurn = 0;
// Rule 514.3
......@@ -397,6 +396,9 @@ public class PhaseHandler implements java.io.Serializable {
// Rule 514.3a - state-based actions
game.getAction().checkStateEffects(true);
// done this after check state effects, so it only has effect next check
game.getCleanup().executeUntil(getNextTurn());
break;
default:
......@@ -722,6 +724,7 @@ public class PhaseHandler implements java.io.Serializable {
runParams.put("Attacker", a);
runParams.put("Blockers", blockers);
runParams.put("NumBlockers", blockers.size());
runParams.put("Defender", combat.getDefenderByAttacker(a));
runParams.put("DefendingPlayer", combat.getDefenderPlayerByAttacker(a));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
......
......@@ -52,25 +52,25 @@ public class TriggerAttackerBlocked extends Trigger {
/** {@inheritDoc} */
@Override
public final boolean performTest(final Map<String, Object> runParams2) {
if (this.mapParams.containsKey("ValidCard")) {
if (!matchesValid(runParams2.get("Attacker"), this.mapParams.get("ValidCard").split(","),
this.getHostCard())) {
if (hasParam("ValidCard")) {
if (!matchesValid(runParams2.get("Attacker"), getParam("ValidCard").split(","),
getHostCard())) {
return false;
}
}
if (this.mapParams.containsKey("MinBlockers")) {
if ((int)runParams2.get("NumBlockers") < Integer.valueOf(this.mapParams.get("MinBlockers"))) {
if (hasParam("MinBlockers")) {
if ((int)runParams2.get("NumBlockers") < Integer.valueOf(getParam("MinBlockers"))) {
return false;
}
}
if (this.mapParams.containsKey("ValidBlocker")) {
if (hasParam("ValidBlocker")) {
@SuppressWarnings("unchecked")
int count = CardLists.getValidCardCount(
(Iterable<Card>) runParams2.get("Blockers"),
this.mapParams.get("ValidBlocker"),
this.getHostCard().getController(), this.getHostCard()
getParam("ValidBlocker"),
getHostCard().getController(), getHostCard()
);
if ( count == 0 ) {
......@@ -84,10 +84,11 @@ public class TriggerAttackerBlocked extends Trigger {
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa) {
sa.setTriggeringObject("Attacker", this.getRunParams().get("Attacker"));
sa.setTriggeringObject("Blockers", this.getRunParams().get("Blockers"));
sa.setTriggeringObject("DefendingPlayer", this.getRunParams().get("DefendingPlayer"));
sa.setTriggeringObject("NumBlockers", this.getRunParams().get("NumBlockers"));
sa.setTriggeringObject("Attacker", getRunParams().get("Attacker"));
sa.setTriggeringObject("Blockers", getRunParams().get("Blockers"));
sa.setTriggeringObject("Defender", getRunParams().get("Defender"));
sa.setTriggeringObject("DefendingPlayer", getRunParams().get("DefendingPlayer"));
sa.setTriggeringObject("NumBlockers", getRunParams().get("NumBlockers"));
}
@Override
......
......@@ -331,7 +331,7 @@ public class GridArchetype {
if (rc == null) return;
if (!set.allowedColours.contains(rc) && !isSeeded())
return;
if (rc.equals(RenColour.Colourless) && set.colourCounts[5] < 20)
if (set.getIsActualSet() && rc.equals(RenColour.Colourless) && set.colourCounts[5] < 20)
return;
//if (!GridTourney.loadDerp && rc.equals(RenColour.Colourless))
// return;
......
......@@ -84,7 +84,9 @@ public class GridDeck {
public void saveGenes(){
GridUtilities.Log("save"," Saving Genes: "+id+"\t"+getDeckDir());
int gen = getGeneration();
geneProperties.clear();
setGeneration(gen);
for (int x = 0; x < mainCards.length; x++){
if (mainCards[x] == null)
geneProperties.setProperty("Main_"+x,"null");
......@@ -341,10 +343,12 @@ public class GridDeck {
}
public int getGeneration(){
String s = geneProperties.getProperty("Generation","0");
//System.out.println("Generation: "+s);
return Integer.parseInt(s);
}
public void setGeneration(int generation){
geneProperties.setProperty("Generation",generation+"");
System.out.println("Set Generation: "+generation);
setSaveGenes();
}
......@@ -943,7 +947,7 @@ public class GridDeck {
buildDeck();
return deck;
}
public void buildDeck(){
private void buildDeck(){
deck = new Deck(tribe.getViewName()+"\n");
PaperCard pc;
......
......@@ -223,22 +223,22 @@ public class GridTourney implements Runnable {
}
gf = set.format;
if (!seededOnly) {
/*if (!seededOnly) {
set.checkAdvance(cap);
}
}*/
if (setList.size() > 0 && !doBestDeck && !doBestTribe) {
System.out.println("Cap: " + cap + "\t" + set.setName + "\t" + gf.name());
/*for (GridSet gs : setList) {
if (rand.nextInt(100) > 2)
for (GridSet gs : setList) {
if (rand.nextInt(100) != 42)
continue;
if (gs.format.ordinal() < gf.ordinal()) {
gf = gs.format;
set = gs;
}
}*/
System.out.println("\t\t"+set.setName+"\t"+gf.name());
}
System.out.println("\t\t\t"+set.setName+"\t"+gf.name());
//gf = GridFormat.Arena;
mini = true;
GridTribe.tribeList.sort(GridTribe.tribeAdjustedRunComparator);
......@@ -434,6 +434,9 @@ public class GridTourney implements Runnable {
if (gf1.equals(GridFormat.Frontier) && gf2.equals(GridFormat.Origin))
return true;
if (gf1.equals(GridFormat.Origin) && gf2.equals(GridFormat.Arena))
return true;
return false;
}
......
......@@ -19,7 +19,10 @@ package forge.view.arcane;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import java.util.Comparator;
import java.awt.event.MouseEvent;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
......@@ -30,8 +33,10 @@ import forge.game.zone.ZoneType;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.CMatchUI;
import forge.toolbox.FScrollPane;
import forge.toolbox.FMouseAdapter;
import forge.toolbox.FSkin;
import forge.util.Lang;
import forge.util.collect.FCollection;
public class FloatingZone extends FloatingCardArea {
private static final long serialVersionUID = 1927906492186378596L;
......@@ -101,8 +106,28 @@ public class FloatingZone extends FloatingCardArea {
private final ZoneType zone;
private PlayerView player;
protected boolean sortedByName = false;
protected FCollection<CardView> cardList;
private final Comparator<CardView> comp = new Comparator<CardView>() {
@Override
public int compare(CardView lhs, CardView rhs) {
if ( !getMatchUI().mayView(lhs) ) {
return ( getMatchUI().mayView(rhs) ) ? 1 : 0 ;
} else if ( !getMatchUI().mayView(rhs) ) {
return -1;
} else {
return lhs.getName().compareTo(rhs.getName());
}
}
};
protected Iterable<CardView> getCards() {
return player.getCards(zone);
cardList = new FCollection<CardView>(player.getCards(zone));
if ( sortedByName ) {
Collections.sort(cardList, comp);
}
return cardList;
}
private FloatingZone(final CMatchUI matchUI, final PlayerView player0, final ZoneType zone0) {
......@@ -136,10 +161,35 @@ public class FloatingZone extends FloatingCardArea {
setVertical(true);
}
private void toggleSorted() {
sortedByName = !sortedByName;
setTitle();
refresh();
// revalidation does not appear to be necessary here
getWindow().repaint();
}
@Override
protected void onShow() {
super.onShow();
if (!hasBeenShown) {
getWindow().getTitleBar().addMouseListener(new FMouseAdapter() {
@Override public final void onRightClick(final MouseEvent e) {
toggleSorted();
}
});
}
}
private void setTitle() {
title = Lang.getPossessedObject(player.getName(), zone.name()) + " (%d)" +
( sortedByName ? " - sorted by name (right click in title to not sort)" : " (right click in title to sort)" ) ;
}
private void setPlayer(PlayerView player0) {
if (player == player0) { return; }
player = player0;
title = Lang.getPossessedObject(player0.getName(), zone.name()) + " (%d)";
setTitle();
boolean isAi = player0.isAI();
switch (zone) {
......
......@@ -16,5 +16,6 @@
<orderEntry type="module" module-name="main" />
<orderEntry type="library" name="Maven: com.miglayout:miglayout:3.7.4" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:16.0.1" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:24.1-android" level="project" />
</component>
</module>
\ No newline at end of file
- Ordering Pop-up Zones -
In the desktop interface the pop-up windows showing players' zones can be ordered as the cards in the zone are or by card name. Right click in the window title to toggle between the two orderings.
- Bug fixes -
As always, this release of Forge features an assortment of bug fixes and improvements based on user feedback during the previous release run.
......@@ -4,7 +4,7 @@ Types:Creature Elemental
PT:1/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature card. Until your next turn, CARDNAME's base power becomes twice that card's power and its toughness. Put the revealed cards on the bottom of your library in a random order.
SVar:TrigDig:DB$ DigUntil | Reveal$ True | Valid$ Creature | ValidDescription$ creature card | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y | UntilYourNextTurn$ True | SubAbility$ DBMovetoLib
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y | UntilYourNextTurn$ True | SubAbility$ DBMovetoLib | References$ X,Y
SVar:DBMovetoLib:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:X:Remembered$CardPower/Times.2
......
......@@ -7,6 +7,6 @@ K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffect | TriggerDescription$ When CARDNAME enters the battlefield, until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
SVar:TrigEffect:DB$ Effect | Name$ Angel of Grace Effect | StaticAbilities$ SelflessDamage | Description$ Until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
SVar:SelflessDamage:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ DamageLifeThreshold:1 | Description$ Until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
A:AB$ SetLife | Cost$ 4 W W ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | LifeAmount$ 10 | SpellDescription$ Your life total becomes 10.
A:AB$ SetLife | Cost$ 4 W W ExileFromGrave<1/CARDNAME> | Defined$ You | ActivationZone$ Graveyard | LifeAmount$ 10 | SpellDescription$ Your life total becomes 10.
SVar:RemRandomDeck:True
Oracle:Flash\nFlying\nWhen Angel of Grace enters the battlefield, until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.\n{4}{W}{W}, Exile Angel of Grace from your graveyard: Your life total becomes 10.
......@@ -4,4 +4,5 @@ Types:Enchantment
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of creatures you control.
SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ +X | NumDef$ +X | References$ X
SVar:X:Count$Valid Creature.YouCtrl
SVar:PlayMain1:TRUE
Oracle:Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of creatures you control.
......@@ -2,8 +2,8 @@ Name:Arcanum Wings
ManaCost:1 U
Types:Enchantment Aura
K:Enchant creature
K:Aura swap:2 U
A:SP$ Attach | Cost$ 1 U | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Flying | Description$ Enchanted creature has flying.
A:AB$ ExchangeZone | Cost$ 2 U | Zone2$ Hand | Type$ Aura | CostDesc$ Aura swap {2}{U} | SpellDescription$ ({2}{U}: Exchange this Aura with an Aura card in your hand.)
SVar:Picture:http://www.wizards.com/global/images/magic/general/arcanum_wings.jpg
Oracle:Enchant creature\nEnchanted creature has flying.\nAura swap {2}{U} ({2}{U}: Exchange this Aura with an Aura card in your hand.)
\ No newline at end of file
Oracle:Enchant creature\nEnchanted creature has flying.\nAura swap {2}{U} ({2}{U}: Exchange this Aura with an Aura card in your hand.)
......@@ -2,5 +2,5 @@ Name:Clear the Stage
ManaCost:4 B
Types:Instant
A:SP$ Pump | Cost$ 4 B | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -3 | NumDef$ -3 | IsCurse$ True | SubAbility$ DBChangeZone | SpellDescription$ Target creature gets -3/-3 until end of turn. If you control a creature with power 4 or greater, you may return up to one target creature card from your graveyard to your hand.
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TargetsWithDefinedController$ ParentTarget | ConditionPresent$ Creature.YouCtrl+powerGE4
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | TargetMin$ 0 | TargetMax$ 1 | ConditionPresent$ Creature.YouCtrl+powerGE4
Oracle:Target creature gets -3/-3 until end of turn. If you control a creature with power 4 or greater, you may return up to one target creature card from your graveyard to your hand.
......@@ -4,8 +4,9 @@ Types:Artifact Creature Construct
PT:1/1
K:etbCounter:P1P1:3:ValidCard$ Card.Self+wasNotCastFromHand:CARDNAME enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand.
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile it with three time counters on it and it gains suspend.
SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile | SubAbility$ DBPutCounter
SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredCardLKICopy | CounterType$ TIME | CounterNum$ 3 | SubAbility$ GiveSuspend
SVar:GiveSuspend:DB$ Pump | Defined$ TriggeredCard | KW$ Suspend | PumpZone$ Exile | Permanent$ True
SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile | SubAbility$ DBPutCounter | RememberChanged$ True
SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ TIME | CounterNum$ 3 | SubAbility$ GiveSuspend
SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Permanent$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/epochrasite.jpg
Oracle:Epochrasite enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand.\nWhen Epochrasite dies, exile it with three time counters on it and it gains suspend. (At the beginning of your upkeep, remove a time counter. When the last is removed, cast this card without paying its mana cost. It has haste.)
[metadata]
Name:Possibility Storm - Ravnica Allegiance #03
URL:http://www.possibilitystorm.com/wp-content/uploads/2019/02/100.-RNA3.jpg
Goal:Win
Turns:1
Difficulty:Mythic
Description:Win this turn. Your solution must satisfy all possible blocking scenarios.
[state]
humanlife=20
ailife=15
turn=1
activeplayer=human
activephase=MAIN1
humanhand=Status // Statue;Grand Warlord Radha;Hero of Precinct One;Gruul Spellbreaker
humanbattlefield=Judith, the Scourge Diva;Novice Knight;Pitiless Pontiff;Teysa Karlov;Elenda, the Dusk Rose;Godless Shrine|NoETBTrigs;Godless Shrine|NoETBTrigs;Stomping Ground|NoETBTrigs;Stomping Ground|NoETBTrigs
aibattlefield=Shalai, Voice of Plenty;Humongulus;Rakdos, the Showstopper