merge latest trunk

This commit is contained in:
myk
2013-03-01 18:59:44 +00:00
12 changed files with 338 additions and 297 deletions

View File

@@ -1,7 +1,7 @@
Forge Beta: 0#-##-2013 ver 1.3.9 Forge Beta: 03-01-2013 ver 1.3.9
12### cards in total. 12193 cards in total.
Release Notes: Release Notes:
@@ -23,7 +23,7 @@ Work was also done on making the UI more keyboard-friendly. For example, the OK
Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards). Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards).
The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator. The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator.
All Traditional sets are now up to 85% complete. Standard Format is supported at 99.19%. We are now at under 900 unsupported cards that are missing from Forge. All Traditional sets are now up to 85% complete. Standard Format is supported at 99.19%. We are now at under 800 unsupported cards that are missing from Forge.
A person reported "Love the game but I seem to be having a problem using a draft pool to start a quest. It works for sealed for me but when I select the draft deck option it's always blank even if I have one or several drafts completed." This should now be fixed and draft decks should now show up in quest start combobox. A person reported "Love the game but I seem to be having a problem using a draft pool to start a quest. It works for sealed for me but when I select the draft deck option it's always blank even if I have one or several drafts completed." This should now be fixed and draft decks should now show up in quest start combobox.
@@ -34,151 +34,155 @@ Our snapshot and beta releases should now display the correct SVN revision numbe
New Cards: New Cards:
Ghosts of the Innocent Archery Training
Pendrell Flux Aurelia's Fury
Disruption Aura
Library of Leng
Temporal Extortion
Aven Shrine Aven Shrine
Barrin's Spite
Battletide Alchemist
Bioshift
Blind Seer
Blinding Powder
Bloom Tender Bloom Tender
Bomb Squad Bomb Squad
Bounty of the Hunt
Builder's Bane
Cabal Shrine Cabal Shrine
Cannibalize
Cephalid Shrine
Chant of Vitu-Ghazi Chant of Vitu-Ghazi
Chaoslace
Charm Peddler
Circle of Despair
Cleansing Meditation
Common Cause
Conflagrate
Conjurer's Ban
Cornered Market
Covenant of Minds Covenant of Minds
Crashing Boars Crashing Boars
Crush Underfoot Crush Underfoot
Cryptic Gateway
Deathlace
Deepwood Elder
Desecrator Hag Desecrator Hag
Disruption Aura
Duplicity
Dwarven Shrine Dwarven Shrine
Eight-and-a-Half-Tails
Embolden
Endemic Plague
Epochrasite
Ersatz Gnomes
Eye for an Eye Eye for an Eye
Eye of Singularity
Eye of Yawgmoth
Feint
Fiery Bombardment
Fiery Justice
Fire and Brimstone
Fire Covenant
Flash
Flickerform
Forbidden Crypt
Forked Lightning
Frostwielder
Game Preserve
Gargantuan Gorilla
Ghosts of the Innocent
Glamer Spinners
Guard Dogs
Hail of Arrows
Heartseeker
Heroic Defiance
Hint of Insanity Hint of Insanity
Holistic Wisdom Holistic Wisdom
Infectious Rage Infectious Rage
Knowledge Exploitation Infernal Harvest
Lightning Dart Invoke Prejudice
Memory Crystal Jaded Response
Mist of Stagnation
Moonring Mirror
Nantuko Shrine
Phyrexian Purge
Planeswalker's Mischief
Protean Hulk
Purgatory
Quenchable Fire
Retribution
Reviving Vapors
Reweave
Sapphire Drake
Searing Rays
Sphinx Ambassador
Talara's Bane
Thelon of Havenwood
Mist of Stagnation
Unforge
Warren Weirding
Worldpurge
Flash
Forked Lightning
Embolden
Jaws of Stone Jaws of Stone
Killing Glare
Knollspine Invocation
Knowledge Exploitation
Kumano's Blessing
Kumano's Pupils
Kumano, Master Yamabushi
Leonin Bola
Library of Leng
Lifelace
Light from Within
Lightning Dart
Living Inferno Living Inferno
Magmatic Core Magmatic Core
Remedy
Rolling Thunder
Aurelia's Fury
Pollen Remedy
Conflagrate
Hail of Arrows
Fire Covenant
Meteor Shower
Infernal Harvest
Rock Slide
Serra's Hymn
Volcanic Wind
Spoils of War
Fiery Justice
Bounty of the Hunt
Shambling Swarm
Fire and Brimstone
Marble Priest
Talruum Piper
Endemic Plague
Feint
Reincarnation
Killing Glare
Gargantuan Gorilla
Psychic Allergy
Invoke Prejudice
Conjurer's Ban
Cephalid Shrine
Bioshift
Glamer Spinners
Mark of Eviction
Plague Boiler
Razia's Purification
Reins of the Vinesteed
Shared Animosity
Simic Guildmage
Rite of Ruin
Common Cause
Sabertooth Cobra
Archery Training
Barrin's Spite
Cryptic Gateway
Deepwood Elder
Knollspine Invocation
Eye of Yawgmoth
Spike Cannibal
Flickerform
Epochrasite
Eye of Singularity
Heroic Defiance
Forbidden Crypt
Charm Peddler
Heartseeker
Blinding Powder
Leonin Bola
Razor Boomerang
Surestrike Trident
Sunforger
Circle of Despair
Martyr's Cause
Cannibalize
Shuriken
Rally the Horde
Deathlace
Mana Vapors Mana Vapors
Samite Elder Marble Priest
Struggle for Sanity Mark of Eviction
Takeno, Samurai General Martyr's Cause
Thran Tome Memory Crystal
Chaoslace Meteor Shower
Lifelace
Moonlace
Purelace
Thoughtlace
Eight-and-a-Half-Tails
Pious Kitsune
Blind Seer
Ersatz Gnomes
Vodalian Mystic
Light from Within
Fiery Bombardment
Phosphorescent Feast
Guard Dogs
Jaded Response
Tainted Pact
Pledge of Loyalty
Mirror Golem Mirror Golem
Reverent Mantra Mist of Stagnation
Frostwielder Mist of Stagnation
Kumano, Master Yamabushi Moonlace
Kumano's Pupils Moonring Mirror
Kumano's Blessing Nantuko Shrine
Winnow
Cornered Market
Not of This World Not of This World
Duplicity Pendrell Flux
Phosphorescent Feast
Phyrexian Purge
Pious Kitsune
Plague Boiler
Planeswalker's Mischief
Pledge of Loyalty
Pollen Remedy
Protean Hulk
Psychic Allergy
Purelace
Purgatory
Quenchable Fire
Rally the Horde
Razia's Purification
Razor Boomerang
Reincarnation
Reins of the Vinesteed
Remedy
Retribution
Reverent Mantra
Reviving Vapors
Reweave
Rite of Ruin
Rock Slide
Rolling Thunder
Sabertooth Cobra
Samite Elder
Sapphire Drake
Searing Rays
Serra's Hymn
Shambling Swarm
Shared Animosity
Shuriken
Simic Guildmage
Sphinx Ambassador
Spike Cannibal
Spoils of War
Struggle for Sanity
Sunforger
Surestrike Trident
Tainted Pact
Takeno, Samurai General
Talara's Bane
Talruum Piper
Temporal Extortion
Thelon of Havenwood
Thought Gorger Thought Gorger
Thoughtlace
Thran Tome
Unforge
Vodalian Mystic
Volcanic Wind
Warren Weirding
Winnow
Worldpurge
New Phenomenons: New Phenomenons:
@@ -189,23 +193,25 @@ Planewide Disaster
New Planes: New Planes:
Undercity Reaches
Aretopolis Aretopolis
Undercity Reaches
New Vanguard Avatars: New Vanguard Avatars:
Arcanis the Omnipotent Avatar
Bosh, Iron Golem Avatar
Figure of Destiny Avatar Figure of Destiny Avatar
Haakon Stromgald Scourge Avatar
Jaya Ballard Avatar
Maro Avatar
Master of the Wild Hunt Avatar
Necropotence Avatar
Sisters of Stone Death Avatar Sisters of Stone Death Avatar
Stuffy Doll Avatar Stuffy Doll Avatar
Two Headed Giant of Foriys Avatar Two Headed Giant of Foriys Avatar
Viridian Zealot Avatar
Bosh, Iron Golem Avatar
Maro Avatar
Necropotence Avatar
Vampire Nocturnus Avatar Vampire Nocturnus Avatar
Arcanis the Omnipotent Avatar Viridian Zealot Avatar
Master of the Wild Hunt Avatar
Known Issues: Known Issues:

12
pom.xml
View File

@@ -5,7 +5,7 @@
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Forge</name> <name>Forge</name>
<version>1.3.9-SNAPSHOT</version> <version>1.3.10-SNAPSHOT</version>
<description> <description>
Forge lets you play the card game Magic: The Gathering against a computer opponent Forge lets you play the card game Magic: The Gathering against a computer opponent
using all of the rules. using all of the rules.
@@ -211,7 +211,7 @@
<exportAntProperties>true</exportAntProperties> <exportAntProperties>true</exportAntProperties>
<target> <target>
<condition property="fullversionstring" value="${project.version}-r${forge.revision}${forge.specialStatus}" else="${project.version}-r${forge.revision}${forge.specialStatus} (mixed revisions detected; please update from the root directory)"> <condition property="fullversionstring" value="${project.version}-r${forge.revision}${forge.specialStatus}" else="${project.version}-r${forge.revision}${forge.specialStatus} (mixed revisions detected; please update from the root directory)">
<contains string="${forge.mixedRevisions}" substring="false"/> <contains string="${forge.mixedRevisions}" substring="false" />
</condition> </condition>
</target> </target>
</configuration> </configuration>
@@ -464,7 +464,7 @@
</goals> </goals>
</pluginExecutionFilter> </pluginExecutionFilter>
<action> <action>
<ignore/> <ignore />
</action> </action>
</pluginExecution> </pluginExecution>
<pluginExecution> <pluginExecution>
@@ -478,7 +478,7 @@
</goals> </goals>
</pluginExecutionFilter> </pluginExecutionFilter>
<action> <action>
<execute/> <execute />
</action> </action>
</pluginExecution> </pluginExecution>
<pluginExecution> <pluginExecution>
@@ -491,7 +491,7 @@
</goals> </goals>
</pluginExecutionFilter> </pluginExecutionFilter>
<action> <action>
<execute/> <execute />
</action> </action>
</pluginExecution> </pluginExecution>
<pluginExecution> <pluginExecution>
@@ -504,7 +504,7 @@
</goals> </goals>
</pluginExecutionFilter> </pluginExecutionFilter>
<action> <action>
<execute/> <execute />
</action> </action>
</pluginExecution> </pluginExecution>
</pluginExecutions> </pluginExecutions>

View File

@@ -186,13 +186,13 @@ while inputName != 'quit' :
if text.find("When CARDNAME enters the battlefield") != -1 : if text.find("When CARDNAME enters the battlefield") != -1 :
print "\n"+text print "\n"+text
print "<Trigger Script Start>" print "<Trigger Script Start>"
print 'T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ <TriggerFunc> | TriggerDescription$ '+text print 'T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ <TriggerFunc> | TriggerDescription$ '+text
print 'SVar:<TriggerFunc>:AB$ <Added Triggered Ability HERE>' print 'SVar:<TriggerFunc>:AB$ <Added Triggered Ability HERE>'
print "<Trigger Script End>\n" print "<Trigger Script End>\n"
elif text.find("When CARDNAME leaves the battlefield") != -1 : elif text.find("When CARDNAME leaves the battlefield") != -1 :
print "\n"+text print "\n"+text
print "<Trigger Script Start>" print "<Trigger Script Start>"
print 'T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Creature.Self | Execute$ <TriggerFunc> | TriggerDescription$ '+text print 'T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ <TriggerFunc> | TriggerDescription$ '+text
print 'SVar:<TriggerFunc>:AB$ <Added Triggered Ability HERE>' print 'SVar:<TriggerFunc>:AB$ <Added Triggered Ability HERE>'
print "<Trigger Script End>\n" print "<Trigger Script End>\n"
elif text.find("Unleash") != -1 : elif text.find("Unleash") != -1 :

View File

@@ -2,9 +2,12 @@ Name:Astral Slide
ManaCost:2 W ManaCost:2 W
Types:Enchantment Types:Enchantment
T:Mode$ Cycled | ValidCard$ Card | Execute$ TrigExile | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever a player cycles a card, you may exile target creature. If you do, return the exiled card to the battlefield under its owner's control at the beginning of the next end step. T:Mode$ Cycled | ValidCard$ Card | Execute$ TrigExile | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever a player cycles a card, you may exile target creature. If you do, return the exiled card to the battlefield under its owner's control at the beginning of the next end step.
SVar:TrigExile:AB$ ChangeZone | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ DelTrig SVar:TrigExile:AB$ ChangeZone | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DelTrigEffect
SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigBounce | TriggerDescription$ Return exiled creature to the battlefield. SVar:DelTrigEffect:DB$ Effect | Triggers$ EOTTrig | SVars$ TrigBounce,RemoveEffect | RememberObjects$ Remembered | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1
SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Battlefield | Defined$ Remembered SVar:EOTTrig:Mode$ Phase | Phase$ End of Turn | Execute$ TrigBounce | TriggerZones$ Command | TriggerDescription$ Return exiled creature to the battlefield.
SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Battlefield | Defined$ Remembered | SubAbility$ RemoveEffect
SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:RemAIDeck:True SVar:RemAIDeck:True
SVar:Rarity:Uncommon SVar:Rarity:Uncommon
SVar:Picture:http://www.wizards.com/global/images/magic/general/astral_slide.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/astral_slide.jpg

View File

@@ -4,8 +4,9 @@ Types:Creature Insect
PT:0/1 PT:0/1
K:Flying K:Flying
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ When CARDNAME attacks, defending player can't cast spells this turn. T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ When CARDNAME attacks, defending player can't cast spells this turn.
SVar:TrigEffect:AB$ Effect | Cost$ 0 | Name$ Xantid Swarm Effect | StaticAbilities$ CantBeCast SVar:TrigEffect:AB$ Effect | Cost$ 0 | Name$ Xantid Swarm Effect | RememberObjects$ DefendingPlayer | StaticAbilities$ CantBeCast | SubAbility$ DBCleanup
SVar:CantBeCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card | Caster$ DefendingPlayer | Description$ Defending player can't cast spells. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:CantBeCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card | Caster$ Player.IsRemembered | Description$ Defending player can't cast spells.
SVar:RemRandomDeck:True SVar:RemRandomDeck:True
SVar:HasAttackEffect:TRUE SVar:HasAttackEffect:TRUE
SVar:Rarity:Rare SVar:Rarity:Rare

View File

@@ -293,3 +293,37 @@ http://www.cardforge.org/fpics/questAvatars/WereHyena.jpg
http://www.cardforge.org/fpics/questAvatars/WitchDoctor.jpg http://www.cardforge.org/fpics/questAvatars/WitchDoctor.jpg
http://www.cardforge.org/fpics/questAvatars/Yemaya.jpg http://www.cardforge.org/fpics/questAvatars/Yemaya.jpg
http://www.cardforge.org/fpics/questAvatars/Yewa.jpg http://www.cardforge.org/fpics/questAvatars/Yewa.jpg
#RAVNICA WORLD ICONS
http://www.cardforge.org/fpics/questAvatars/Agrus.jpg
http://www.cardforge.org/fpics/questAvatars/Aurelia.jpg
http://www.cardforge.org/fpics/questAvatars/Azorius-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Bep.jpg
http://www.cardforge.org/fpics/questAvatars/Borborygmos.jpg
http://www.cardforge.org/fpics/questAvatars/Boros-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Caprio.jpg
http://www.cardforge.org/fpics/questAvatars/Ghost%20Council.jpg
http://www.cardforge.org/fpics/questAvatars/Golgari-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Gruul-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Hameln.jpg
http://www.cardforge.org/fpics/questAvatars/Isperia.jpg
http://www.cardforge.org/fpics/questAvatars/Izzet-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Jarad.jpg
http://www.cardforge.org/fpics/questAvatars/Kraj.jpg
http://www.cardforge.org/fpics/questAvatars/Lazav.jpg
http://www.cardforge.org/fpics/questAvatars/Lyzolda.jpg
http://www.cardforge.org/fpics/questAvatars/Momir.jpg
http://www.cardforge.org/fpics/questAvatars/Niv-Mizzet.jpg
http://www.cardforge.org/fpics/questAvatars/Orzhov-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Rakdos.jpg
http://www.cardforge.org/fpics/questAvatars/Savra.jpg
http://www.cardforge.org/fpics/questAvatars/Selesnya-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Simic-precon.jpg
http://www.cardforge.org/fpics/questAvatars/Sisters%20of%20Stone%20Death.jpg
http://www.cardforge.org/fpics/questAvatars/Sus Antigoon.jpg
http://www.cardforge.org/fpics/questAvatars/Szadek.jpg
http://www.cardforge.org/fpics/questAvatars/Teysa.jpg
http://www.cardforge.org/fpics/questAvatars/Token.jpg
http://www.cardforge.org/fpics/questAvatars/Tolsimir.jpg
http://www.cardforge.org/fpics/questAvatars/Trostani.jpg
http://www.cardforge.org/fpics/questAvatars/Ulasht.jpg
http://www.cardforge.org/fpics/questAvatars/Zegana.jpg

View File

@@ -19,6 +19,7 @@ package forge.deck;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import forge.Card; import forge.Card;
@@ -139,7 +140,7 @@ public class CardPool extends ItemPool<CardPrinted> {
if ( cp != null) if ( cp != null)
this.add(cp); this.add(cp);
else else
throw new RuntimeException(String.format("Card %s is not supported by Forge, as it's neither a known common card nor one of casual variants' card.", cardName)); throw new NoSuchElementException(String.format("Card %s is not supported by Forge, as it's neither a known common card nor one of casual variants' card.", cardName));
} }
/** /**

View File

@@ -65,7 +65,7 @@ public abstract class PlayerController {
*/ */
public void passPriority() { public void passPriority() {
PhaseHandler handler = game.getPhaseHandler(); PhaseHandler handler = game.getPhaseHandler();
// may pass only priority is has
if ( handler.getPriorityPlayer() == getPlayer() ) if ( handler.getPriorityPlayer() == getPlayer() )
game.getPhaseHandler().passPriority(); game.getPhaseHandler().passPriority();
} }
@@ -95,5 +95,6 @@ public abstract class PlayerController {
public Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); } public Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); }
public abstract Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title, boolean isOptional); public abstract Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title, boolean isOptional);
public abstract boolean confirmAction(SpellAbility sa, String mode, String message); public abstract boolean confirmAction(SpellAbility sa, String mode, String message);
public abstract boolean getWillPlayOnFirstTurn(String message);
public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message); public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message);
} }

View File

@@ -198,6 +198,10 @@ public class PlayerControllerAi extends PlayerController {
return brains.confirmAction(sa, mode, message); return brains.confirmAction(sa, mode, message);
} }
@Override
public boolean getWillPlayOnFirstTurn(String message) {
return true; // AI is brave :)
}
@Override @Override
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
return brains.confirmStaticApplication(hostCard, affected, logic, message); return brains.confirmStaticApplication(hostCard, affected, logic, message);

View File

@@ -250,5 +250,14 @@ public class PlayerControllerHuman extends PlayerController {
return GuiDialog.confirm(hostCard, message); return GuiDialog.confirm(hostCard, message);
} }
@Override
public boolean getWillPlayOnFirstTurn(String message) {
final String[] possibleValues = { "Play", "Draw" };
final Object playDraw = JOptionPane.showOptionDialog(null, message + "\n\nWould you like to play or draw?",
"Play or Draw?", JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null,
possibleValues, possibleValues[0]);
return !playDraw.equals(1);
}
} }

View File

@@ -144,7 +144,8 @@ public class QuestEventManager {
outList.add(duel); outList.add(duel);
return; return;
} }
continue;
} }
targetIdx -= opponents.size(); targetIdx -= opponents.size();

View File

@@ -23,11 +23,9 @@ import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import forge.Card; import forge.Card;
import forge.view.arcane.util.CardPanelMouseListener; import forge.view.arcane.util.CardPanelMouseListener;
@@ -76,6 +74,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
* a {@link javax.swing.JScrollPane} object. * a {@link javax.swing.JScrollPane} object.
* @param mirror * @param mirror
* a boolean. * a boolean.
* @param modelRef
*/ */
public PlayArea(final JScrollPane scrollPane, final boolean mirror) { public PlayArea(final JScrollPane scrollPane, final boolean mirror) {
super(scrollPane); super(scrollPane);
@@ -196,46 +195,40 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
this.playAreaWidth = rect.width; this.playAreaWidth = rect.width;
this.playAreaHeight = rect.height; this.playAreaHeight = rect.height;
final CardStackRow allLands = collectAllLands(); final CardStackRow lands = collectAllLands();
final CardStackRow allTokens = collectAllTokens(); final CardStackRow tokens = collectAllTokens();
final CardStackRow allCreatures = new CardStackRow(this.getCardPanels(), RowType.creatureNonToken); final CardStackRow creatures = new CardStackRow(this.getCardPanels(), RowType.CreatureNonToken);
final CardStackRow allOthers = new CardStackRow(this.getCardPanels(), RowType.other); final CardStackRow others = new CardStackRow(this.getCardPanels(), RowType.Other);
// should find an appropriate width of card // should find an appropriate width of card
this.cardWidth = this.getCardWidthMax();
int maxCardWidth = this.getCardWidthMax(); int maxCardWidth = this.getCardWidthMax();
setCardWidth(maxCardWidth);
int minCardWidth = this.getCardWidthMin(); int minCardWidth = this.getCardWidthMin();
int lastGoodCardWidth = minCardWidth; int lastGoodCardWidth = minCardWidth;
int deltaCardWidth = (maxCardWidth - minCardWidth) / 2; int deltaCardWidth = (maxCardWidth - minCardWidth) / 2;
boolean workedLastTime = false; List<CardStackRow> lastTemplate = null;
//boolean isFirstRun = true;
while (deltaCardWidth > 0) { while (deltaCardWidth > 0) {
final CardStackRow creatures = (CardStackRow) allCreatures.clone(); List<CardStackRow> template = tryArrangePilesOfWidth(lands, tokens, creatures, others);
final CardStackRow tokens = (CardStackRow) allTokens.clone();
final CardStackRow lands = (CardStackRow) allLands.clone(); deltaCardWidth = (getCardWidth() - lastGoodCardWidth) / 2;
CardStackRow others = (CardStackRow) allOthers.clone(); if (template != null) {
workedLastTime = canAdjustWidth(lands, tokens, creatures, others); lastTemplate = template;
lastGoodCardWidth = getCardWidth();
deltaCardWidth = (cardWidth - lastGoodCardWidth) / 2; setCardWidth(getCardWidth() + deltaCardWidth);
if (workedLastTime) {
lastGoodCardWidth = cardWidth;
cardWidth += deltaCardWidth;
if (lastGoodCardWidth == maxCardWidth) { if (lastGoodCardWidth == maxCardWidth) {
break; break;
} }
} }
else { else {
cardWidth -= deltaCardWidth; setCardWidth(getCardWidth() - deltaCardWidth);
} }
} }
cardWidth = lastGoodCardWidth; setCardWidth(lastGoodCardWidth);
final CardStackRow creatures = (CardStackRow) allCreatures.clone(); if ( null == lastTemplate )
final CardStackRow tokens = (CardStackRow) allTokens.clone(); lastTemplate = tryArrangePilesOfWidth(lands, tokens, creatures, others);
final CardStackRow lands = (CardStackRow) allLands.clone();
CardStackRow others = (CardStackRow) allOthers.clone();
workedLastTime = canAdjustWidth(lands, tokens, creatures, others);
this.rows = lastTemplate;
// Get size of all the rows. // Get size of all the rows.
int x, y = PlayArea.GUTTER_Y; int x, y = PlayArea.GUTTER_Y;
int maxRowWidth = 0; int maxRowWidth = 0;
@@ -252,25 +245,26 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
} }
this.setPreferredSize(new Dimension(maxRowWidth - this.cardSpacingX, y - this.cardSpacingY)); this.setPreferredSize(new Dimension(maxRowWidth - this.cardSpacingX, y - this.cardSpacingY));
this.revalidate(); this.revalidate();
positionAllCards(); positionAllCards(lastTemplate);
} }
private void positionAllCards() { private void positionAllCards(List<CardStackRow> template) {
// Position all card panels. // Position all card panels.
int x = 0; int x = 0;
int y = PlayArea.GUTTER_Y; int y = PlayArea.GUTTER_Y;
for (final CardStackRow row : this.rows) { for (final CardStackRow row : template) {
int rowBottom = 0; int rowBottom = 0;
x = PlayArea.GUTTER_X; x = PlayArea.GUTTER_X;
for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) { for (int stackIndex = 0, stackCount = row.size(); stackIndex < stackCount; stackIndex++) {
final CardStack stack = row.get(stackIndex); final CardStack stack = row.get(stackIndex);
// Align others to the right. // Align others to the right.
if (RowType.other.isType(stack.get(0).getGameCard())) { if (RowType.Other.isGoodFor(stack.get(0).getGameCard())) {
x = (this.playAreaWidth - PlayArea.GUTTER_X) + this.extraCardSpacingX; x = (this.playAreaWidth - PlayArea.GUTTER_X) + this.extraCardSpacingX;
for (int i = stackIndex, n = row.size(); i < n; i++) { for (int i = stackIndex, n = row.size(); i < n; i++) {
x -= row.get(i).getWidth(); CardStack r = row.get(i);
x -= r.getWidth();
} }
} }
for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) { for (int panelIndex = 0, panelCount = stack.size(); panelIndex < panelCount; panelIndex++) {
@@ -279,68 +273,62 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
this.setComponentZOrder(panel, panelIndex); this.setComponentZOrder(panel, panelIndex);
final int panelX = x + (stackPosition * this.stackSpacingX); final int panelX = x + (stackPosition * this.stackSpacingX);
final int panelY = y + (stackPosition * this.stackSpacingY); final int panelY = y + (stackPosition * this.stackSpacingY);
panel.setCardBounds(panelX, panelY, this.cardWidth, this.cardHeight); panel.setCardBounds(panelX, panelY, this.getCardWidth(), this.cardHeight);
} }
rowBottom = Math.max(rowBottom, y + stack.getHeight()); rowBottom = Math.max(rowBottom, y + stack.getHeight());
x += stack.getWidth(); x += stack.getWidth() + cardSpacingX;
} }
y = rowBottom; y = rowBottom;
} }
} }
private boolean canAdjustWidth(final CardStackRow lands, final CardStackRow tokens, final CardStackRow creatures, CardStackRow others) { private List<CardStackRow> tryArrangePilesOfWidth(final CardStackRow lands, final CardStackRow tokens, final CardStackRow creatures, CardStackRow others) {
this.rows.clear(); List<CardStackRow> template = new ArrayList<PlayArea.CardStackRow>();
this.cardHeight = Math.round(this.cardWidth * CardPanel.ASPECT_RATIO);
this.extraCardSpacingX = Math.round(this.cardWidth * PlayArea.EXTRA_CARD_SPACING_X);
this.cardSpacingX = (this.cardHeight - this.cardWidth) + this.extraCardSpacingX;
this.cardSpacingY = Math.round(this.cardHeight * PlayArea.CARD_SPACING_Y);
this.stackSpacingX = Math.round(this.cardWidth * PlayArea.STACK_SPACING_X);
this.stackSpacingY = Math.round(this.cardHeight * PlayArea.STACK_SPACING_Y);
int afterFirstRow; int afterFirstRow;
boolean landsFit, tokensFit, creaturesFit;
if (this.mirror) { if (this.mirror) {
// Wrap all creatures and lands. // Wrap all creatures and lands.
this.wrap(lands, this.rows, -1); landsFit = this.planRow(lands, template, -1);
afterFirstRow = this.rows.size(); afterFirstRow = template.size();
this.wrap(tokens, this.rows, afterFirstRow); tokensFit = this.planRow(tokens, template, afterFirstRow);
this.wrap(creatures, this.rows, this.rows.size()); creaturesFit = this.planRow(creatures, template, template.size());
} else { } else {
// Wrap all creatures and lands. // Wrap all creatures and lands.
this.wrap(creatures, this.rows, -1); creaturesFit = this.planRow(creatures, template, -1);
afterFirstRow = this.rows.size(); afterFirstRow = template.size();
this.wrap(tokens, this.rows, afterFirstRow); tokensFit = this.planRow(tokens, template, afterFirstRow);
this.wrap(lands, this.rows, this.rows.size()); landsFit = this.planRow(lands, template, template.size());
} }
// Store the current rows and others.
final List<CardStackRow> storedRows = new ArrayList<CardStackRow>(this.rows.size()); if ( !landsFit || !creaturesFit || !tokensFit )
for (final CardStackRow row : this.rows) { return null;
try {
storedRows.add((CardStackRow) row.clone()); // Other cards may be stored at end of usual rows or on their own row.
} int cntOthers = others.size();
catch (NullPointerException e) {
System.out.println("Null pointer exception in Row Spacing. Possibly also part of the issue."); // Copy the template for the case 1st approach won't work
} final List<CardStackRow> templateCopy = new ArrayList<CardStackRow>(template.size());
for (final CardStackRow row : template) {
templateCopy.add((CardStackRow) row.clone());
} }
final CardStackRow storedOthers = (CardStackRow) others.clone();
// Fill in all rows with others. // Fill in all rows with others.
for (final CardStackRow row : this.rows) { int nextOther = 0;
this.fillRow(others, this.rows, row); for (final CardStackRow row : template) {
nextOther = this.planOthersRow(others, nextOther, template, row);
if ( nextOther == cntOthers )
return template; // everything was successfully placed
} }
// Stop if everything fits, otherwise revert back to the stored
// values. template = templateCopy;
if (creatures.isEmpty() && tokens.isEmpty() && lands.isEmpty() && others.isEmpty()) { // Try to put others on their own row(s)
return true; if ( this.planRow(others, template, afterFirstRow) )
} return template;
this.rows = storedRows;
others = storedOthers;
// Try to put others on their own row(s) and fill in the rest. return null; // Cannot fit everything with that width;
this.wrap(others, this.rows, afterFirstRow);
for (final CardStackRow row : this.rows) {
this.fillRow(others, this.rows, row);
}
// If that still doesn't fit, scale down.
return creatures.isEmpty() && tokens.isEmpty() && lands.isEmpty() && others.isEmpty();
} }
/** /**
@@ -350,69 +338,53 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
* *
* @param sourceRow * @param sourceRow
* a {@link forge.view.arcane.PlayArea.CardStackRow} object. * a {@link forge.view.arcane.PlayArea.CardStackRow} object.
* @param rows * @param template
* a {@link java.util.List} object. * a {@link java.util.List} object.
* @param insertIndex * @param insertIndex
* a int. * a int.
* @return a int. * @return a int.
*/ */
// private int cntRepaints = 0; // Won't modify the first parameter
private int wrap(final CardStackRow sourceRow, final List<CardStackRow> rows, final int insertIndex) { private boolean planRow(final CardStackRow sourceRow, final List<CardStackRow> template, final int insertIndex) {
// The cards are sure to fit (with vertical scrolling) at the minimum // The cards are sure to fit (with vertical scrolling) at the minimum
// card width. // card width.
final boolean allowHeightOverflow = this.cardWidth == this.getCardWidthMin(); final boolean isMinimalSize = this.getCardWidth() == this.getCardWidthMin();
// System.err.format("[%d] @ %d - Repaint playarea - %s %n", new Date().getTime(), cntRepaints++, mirror ? "MIRROR" : "DIRECT"); // System.err.format("[%d] @ %d - Repaint playarea - %s %n", new Date().getTime(), cntRepaints++, mirror ? "MIRROR" : "DIRECT");
CardStackRow currentRow = new CardStackRow(); CardStackRow currentRow = new CardStackRow();
for (int i = 0, n = sourceRow.size() - 1; i <= n; i++) { for (final CardStack stack : sourceRow) {
final CardStack stack = sourceRow.get(i);
// If the row is not empty and this stack doesn't fit, add the row.
final int rowWidth = currentRow.getWidth(); final int rowWidth = currentRow.getWidth();
if (!currentRow.isEmpty() && ((rowWidth + stack.getWidth()) > this.playAreaWidth)) { // If the row is not empty and this stack doesn't fit, add the row.
if (rowWidth + stack.getWidth() > this.playAreaWidth && !currentRow.isEmpty() ) {
// Stop processing if the row is too wide or tall. // Stop processing if the row is too wide or tall.
if (!allowHeightOverflow && (rowWidth > this.playAreaWidth)) { if (rowWidth > this.playAreaWidth || this.getRowsHeight(template) + sourceRow.getHeight() > this.playAreaHeight) {
break; if ( !isMinimalSize )
} return false;
if (!allowHeightOverflow && ((this.getRowsHeight(rows) + sourceRow.getHeight()) > this.playAreaHeight)) {
break;
}
try {
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow);
}
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndex Out of Bounds when trying to add row in PlayArea. Someone fix this logic, "
+ " I believe it causes the no cards loading in issue we've noticed.");
// TODO: There's a crash here, maybe when rows == [null] and currentRow == [[Plant Wall]] and insertIndex is 0
} }
if ( insertIndex == -1)
template.add(currentRow);
else
template.add(insertIndex, currentRow);
currentRow = new CardStackRow(); currentRow = new CardStackRow();
} }
currentRow.add(stack); currentRow.add(stack);
} }
// Add the last row if it is not empty and it fits. // Add the last row if it is not empty and it fits.
if (!currentRow.isEmpty()) { if (!currentRow.isEmpty()) {
final int rowWidth = currentRow.getWidth(); final int rowWidth = currentRow.getWidth();
if (allowHeightOverflow if (isMinimalSize || rowWidth <= this.playAreaWidth && this.getRowsHeight(template) + sourceRow.getHeight() <= this.playAreaHeight) {
|| (rowWidth <= this.playAreaWidth) if ( insertIndex == -1)
&& (allowHeightOverflow || ((this.getRowsHeight(rows) + sourceRow.getHeight()) <= this.playAreaHeight))) { template.add(currentRow);
try { else
rows.add(insertIndex == -1 ? rows.size() : insertIndex, currentRow); template.add(insertIndex, currentRow);
} } else return false;
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndex Out of Bounds when trying to add row in PlayArea. Someone fix this logic, "
+ " I believe it causes the no cards loading in issue we've noticed.");
// TODO: There's a crash here, maybe when rows == [null] and currentRow == [[Plant Wall]] and insertIndex is 0
}
}
} }
// Remove the wrapped stacks from the source row. return true;
for (int iRow = 0; iRow < rows.size(); iRow++) {
CardStackRow row = rows.get(iRow);
if (row != null) {
sourceRow.removeAll(row);
}
}
return insertIndex;
} }
@@ -423,32 +395,30 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
* *
* @param sourceRow * @param sourceRow
* a {@link forge.view.arcane.PlayArea.CardStackRow} object. * a {@link forge.view.arcane.PlayArea.CardStackRow} object.
* @param rows * @param template
* a {@link java.util.List} object. * a {@link java.util.List} object.
* @param rows * @param template
* a {@link java.util.List} object. * a {@link java.util.List} object.
* @param row * @param rowToFill
* a {@link forge.view.arcane.PlayArea.CardStackRow} object. * a {@link forge.view.arcane.PlayArea.CardStackRow} object.
*/ */
private void fillRow(final CardStackRow sourceRow, final List<CardStackRow> rows, final CardStackRow row) { private int planOthersRow(final List<CardStack> sourceRow, final int firstPile, final List<CardStackRow> template, final CardStackRow rowToFill) {
int rowWidth = row.getWidth(); int rowWidth = rowToFill.getWidth();
final Iterator<CardStack> itr = sourceRow.iterator(); for (int i = firstPile; i < sourceRow.size(); i++ ) {
CardStack stack = sourceRow.get(i);
while (itr.hasNext()) {
final CardStack stack = itr.next();
rowWidth += stack.getWidth(); rowWidth += stack.getWidth();
if (rowWidth > this.playAreaWidth) { if (rowWidth > this.playAreaWidth) return i; // cannot add any more piles in a row
break;
if (stack.getHeight() > rowToFill.getHeight()) { // if row becomes taller
int newAllRowsHeight = this.getRowsHeight(template) - rowToFill.getHeight() + stack.getHeight();
if ( newAllRowsHeight > this.playAreaHeight)
return i; // refuse to add here because it won't fit in height
} }
if (stack.getHeight() > row.getHeight() rowToFill.add(stack);
&& (((this.getRowsHeight(rows) - row.getHeight()) + stack.getHeight()) > this.playAreaHeight)) {
break;
}
row.add(stack);
itr.remove();
} }
return sourceRow.size();
} }
/** /**
@@ -509,20 +479,18 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
} }
private static enum RowType { private static enum RowType {
land, creature, creatureNonToken, other; Land,
Creature,
CreatureNonToken,
Other;
public boolean isType(final Card card) { public boolean isGoodFor(final Card card) {
switch (this) { switch (this) {
case land: case Land: return card.isLand();
return card.isLand(); case Creature: return card.isCreature();
case creature: case CreatureNonToken: return card.isCreature() && !card.isToken();
return card.isCreature(); case Other: return !card.isLand() && !card.isCreature();
case creatureNonToken: default: throw new RuntimeException("Unhandled type: " + this);
return card.isCreature() && !card.isToken();
case other:
return !card.isLand() && !card.isCreature();
default:
throw new RuntimeException("Unhandled type: " + this);
} }
} }
} }
@@ -541,7 +509,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
private void addAll(final List<CardPanel> cardPanels, final RowType type) { private void addAll(final List<CardPanel> cardPanels, final RowType type) {
for (final CardPanel panel : cardPanels) { for (final CardPanel panel : cardPanels) {
if (!type.isType(panel.getGameCard()) || (panel.getAttachedToPanel() != null)) { if (!type.isGoodFor(panel.getGameCard()) || (panel.getAttachedToPanel() != null)) {
continue; continue;
} }
final CardStack stack = new CardStack(); final CardStack stack = new CardStack();
@@ -606,4 +574,17 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
+ PlayArea.this.cardSpacingY; + PlayArea.this.cardSpacingY;
} }
} }
private int getCardWidth() {
return cardWidth;
}
private void setCardWidth(int cardWidth0) {
this.cardWidth = cardWidth0;
this.cardHeight = Math.round(this.cardWidth * CardPanel.ASPECT_RATIO);
this.extraCardSpacingX = Math.round(this.cardWidth * PlayArea.EXTRA_CARD_SPACING_X);
this.cardSpacingX = (this.cardHeight - this.cardWidth) + this.extraCardSpacingX;
this.cardSpacingY = Math.round(this.cardHeight * PlayArea.CARD_SPACING_Y);
this.stackSpacingX = Math.round(this.cardWidth * PlayArea.STACK_SPACING_X);
this.stackSpacingY = Math.round(this.cardHeight * PlayArea.STACK_SPACING_Y);
}
} }