Compare commits

...

32 Commits

Author SHA1 Message Date
GitHub Actions
08719b1596 [maven-release-plugin] prepare release forge-2.0.04 2025-06-06 02:56:11 +00:00
Chris H
840f3ea96c Automate patch version increment 2025-06-05 22:45:57 -04:00
Chris H
51929434be Update flatten to work with full release 2025-06-05 22:45:57 -04:00
gsonnier333
3ec480afa4 fixed weapons_vendor.txt power/toughness 2025-06-05 21:21:27 -04:00
Chris H
3d19e9e444 Update summon_g_f_ifrit.txt 2025-06-05 21:21:14 -04:00
Renato Filipe Vidal Santos
6d987791f7 Fixing Animate Dead and similar effects) 2025-06-05 14:25:08 +02:00
Renato Filipe Vidal Santos
7b7da00c22 Incidental fixes: 2025-06-05 2025-06-05 08:54:38 +02:00
tool4ever
34791bd892 Update stangg.txt 2025-06-05 08:53:38 +02:00
Renato Filipe Vidal Santos
e6bcd1be72 Add files via upload 2025-06-04 20:33:46 -04:00
Paul Hammerton
4cac354983 Merge pull request #7794 from paulsnoops/fin-formats
Add Final Fantasy to formats
2025-06-04 18:11:13 +01:00
Paul Hammerton
042f7f77a9 Add Final Fantasy to formats 2025-06-04 18:06:21 +01:00
Hans Mackowiak
5fbb1dd0cd Update summon_leviathan.txt
Fix Draw
2025-06-04 08:55:00 +02:00
Hans Mackowiak
bb14dfc00e Update summon_leviathan.txt
Closes #7784
2025-06-04 07:20:24 +02:00
Renato Filipe Vidal Santos
f83cc4ccfa Update fin.rnk 2025-06-03 11:05:10 -04:00
tool4ever
d0d6835a5d Update zodiark_umbral_god.txt 2025-06-03 13:24:17 +00:00
tool4EvEr
104bc8fc55 SpellAbility: set CardState inside constructor 2025-06-03 13:26:25 +02:00
SprinkleMeTimbers
057dd867a8 Adventure: added support for dynamic deck count (#7669)
* Added: utility method for adding an integer selecting combo-box.

* Added: Adventure now supports dynamic amount of decks

* Tweaked: lowered max deck count to 20.

* Added: dynamic deck count can't ever be higher than the maximum in current version.
2025-06-03 13:17:30 +03:00
Chris H
9eed8f5095 Update some names in fin rankings (#7776) 2025-06-03 13:17:18 +03:00
Chris H
1f62be9773 Migrate FIN upcoming (#7777) 2025-06-03 10:13:01 +00:00
Hans Mackowiak
7a46f92059 Update CardState updateSpellAbilities only when not inPlay (#7780)
* Fix copy order
2025-06-03 10:12:35 +00:00
tool4EvEr
cfa79b9676 attachAuraOnIndirectETB: fix missing activator 2025-06-03 09:54:37 +02:00
tool4ever
c0a63fa15b Fix Dark Depths triggering vs. Blood Moon (#7768) 2025-06-03 08:07:10 +02:00
Renato Filipe Vidal Santos
c61537ae16 Add files via upload 2025-06-02 23:44:22 -04:00
Renato Filipe Vidal Santos
3871095b92 Add files via upload 2025-06-02 23:44:22 -04:00
Simisays
dee846da49 Update cave_bigzombie.tmx 2025-06-02 22:08:08 -04:00
Jason Vorenkamp
2477553d13 fix incorrect resource name 2025-06-02 21:46:37 -04:00
Renato Filipe Vidal Santos
c9df7d7f8e Cleaning upcoming: 2025-06-02 2025-06-02 16:02:17 +00:00
rwalters
53cb093f9e (Adventure) Fix draft scene transition (#7666)
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)

* Fixing a bug in which the transition screen's non-blocking of the start match button can be clicked multiple times, which results in a crash when the match ends
2025-06-02 10:24:50 -04:00
MD200210
28e86970dc Added Draft Info (#7764)
* FIN Draft Info

(cherry picked from commit ad0385617b26391efa2b866157eea674ea850e1e)

* Correction

* Correction

* Correction
2025-06-02 10:24:10 -04:00
Chris H
f847fc1669 Fix sketchbooks that are missing colons in the name 2025-06-02 07:22:26 -04:00
Hans Mackowiak
f3df55177a CardState: fix Android not having stream().toList(); Closes #7761 2025-06-02 07:10:26 +02:00
Chris H
be6f345127 Hotfix PaperCard edition issues 2025-06-01 22:50:58 -04:00
554 changed files with 1643 additions and 866 deletions

View File

@@ -81,7 +81,7 @@ jobs:
export _JAVA_OPTIONS="-Xmx2g" export _JAVA_OPTIONS="-Xmx2g"
d=$(date +%m.%d) d=$(date +%m.%d)
# build only desktop and only try to move desktop files # build only desktop and only try to move desktop files
mvn -U -B clean -P windows-linux install -e -T 1C release:clean release:prepare release:perform -DskipTests mvn -U -B clean -P windows-linux install -DskipTests -Dskip.flatten=true -e -T 1C release:clean release:prepare release:perform
mkdir izpack mkdir izpack
# move bz2 and jar from work dir to izpack dir # move bz2 and jar from work dir to izpack dir
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/ mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
@@ -128,6 +128,44 @@ jobs:
removeArtifacts: true removeArtifacts: true
makeLatest: true makeLatest: true
- name: 🔧 Install XML tools
run: sudo apt-get install -y libxml2-utils
- name: 🔼 Bump versionCode in root POM
id: bump_version
run: |
cd /home/runner/work/forge/forge/
current_version=$(xmllint --xpath "//*[local-name()='versionCode']/text()" pom.xml)
echo "Current versionCode: $current_version"
IFS='.' read -r major minor patch <<< "${current_version}"
new_patch=$(printf "%02d" $((10#$patch + 1)))
new_version="${major}.${minor}.${new_patch}"
sed -i -E "s|<versionCode>.*</versionCode>|<versionCode>${new_version}</versionCode>|" pom.xml
echo "version_code=${new_version}" >> $GITHUB_OUTPUT
- name: ♻️ Restore {revision} in child POMs
run: |
find . -name pom.xml ! -path "./pom.xml" | while read -r pom; do
sed -i -E 's|<version>2\.0+\.[0-9]+(-SNAPSHOT)?</version>|<version>${revision}</version>|' "$pom"
done
- name: 💾 Commit restored {revision}
run: |
# Add only pom.xml files
find . -name pom.xml -exec git add {} \;
# Commit if there are changes
if git diff --cached --quiet; then
echo "No pom.xml changes to commit."
else
git commit -m "Restore POM files for preparation of next release" || echo "No changes to commit"
git push
fi
- name: Send failure notification to Discord - name: Send failure notification to Discord
if: failure() # This step runs only if the job fails if: failure() # This step runs only if the job fails
run: | run: |

View File

@@ -3,7 +3,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-ai</artifactId> <artifactId>forge-ai</artifactId>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-core</artifactId> <artifactId>forge-core</artifactId>

View File

@@ -347,13 +347,34 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
if (pc == null) { if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex); pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
if (pc == null) { if (pc == null) {
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found")); System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
pc = readObjectAlternate(name, edition);
if (pc == null) {
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
}
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
} }
} }
rules = pc.getRules(); rules = pc.getRules();
rarity = pc.getRarity(); rarity = pc.getRarity();
} }
private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException {
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition);
if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name, edition);
}
if (pc == null) {
pc = StaticData.instance().getCommonCards().getCard(name);
if (pc == null) {
pc = StaticData.instance().getVariantCards().getCard(name);
}
}
return pc;
}
@Serial @Serial
private Object readResolve() throws ObjectStreamException { private Object readResolve() throws ObjectStreamException {
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags. //If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-game</artifactId> <artifactId>forge-game</artifactId>

View File

@@ -548,20 +548,20 @@ public class GameAction {
copied.updateStateForView(); copied.updateStateForView();
// we don't want always trigger before counters are placed
game.getTriggerHandler().suppressMode(TriggerType.Always);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
// needed for counters + ascend // needed for counters + ascend
if (!suppress && toBattlefield) { if (!suppress && toBattlefield) {
game.getTriggerHandler().registerActiveTrigger(copied, false); game.getTriggerHandler().registerActiveTrigger(copied, false);
} }
if (!table.isEmpty()) {
// we don't want always trigger before counters are placed
game.getTriggerHandler().suppressMode(TriggerType.Always);
// Need to apply any static effects to produce correct triggers
checkStaticAbilities();
// do ETB counters after zone add // do ETB counters after zone add
table.replaceCounterEffect(game, null, true, true, params); table.replaceCounterEffect(game, null, true, true, params);
game.getTriggerHandler().clearSuppression(TriggerType.Always); game.getTriggerHandler().clearSuppression(TriggerType.Always);
}
// update static abilities after etb counters have been placed // update static abilities after etb counters have been placed
checkStaticAbilities(); checkStaticAbilities();
@@ -2794,6 +2794,7 @@ public class GameAction {
if (aura == null) { if (aura == null) {
return false; return false;
} }
aura.setActivatingPlayer(source.getController());
Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class); Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
boolean canTargetPlayer = false; boolean canTargetPlayer = false;

View File

@@ -52,6 +52,7 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
public class CardState extends GameObject implements IHasSVars, ITranslatable { public class CardState extends GameObject implements IHasSVars, ITranslatable {
private String name = ""; private String name = "";
@@ -366,14 +367,16 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
public final FCollectionView<SpellAbility> getManaAbilities() { public final FCollectionView<SpellAbility> getManaAbilities() {
FCollection<SpellAbility> newCol = new FCollection<>(); FCollection<SpellAbility> newCol = new FCollection<>();
updateSpellAbilities(newCol, true); updateSpellAbilities(newCol, true);
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).toList()); // stream().toList() causes crash on Android, use Collectors.toList()
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList()));
card.updateSpellAbilities(newCol, this, true); card.updateSpellAbilities(newCol, this, true);
return newCol; return newCol;
} }
public final FCollectionView<SpellAbility> getNonManaAbilities() { public final FCollectionView<SpellAbility> getNonManaAbilities() {
FCollection<SpellAbility> newCol = new FCollection<>(); FCollection<SpellAbility> newCol = new FCollection<>();
updateSpellAbilities(newCol, false); updateSpellAbilities(newCol, false);
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).toList()); // stream().toList() causes crash on Android, use Collectors.toList()
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList()));
card.updateSpellAbilities(newCol, this, false); card.updateSpellAbilities(newCol, this, false);
return newCol; return newCol;
} }
@@ -385,7 +388,10 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardState leftState = getCard().getState(CardStateName.LeftSplit); CardState leftState = getCard().getState(CardStateName.LeftSplit);
Collection<SpellAbility> leftAbilities = leftState.abilities; Collection<SpellAbility> leftAbilities = leftState.abilities;
if (null != mana) { if (null != mana) {
leftAbilities = leftAbilities.stream().filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)).toList(); leftAbilities = leftAbilities.stream()
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
// stream().toList() causes crash on Android, use Collectors.toList()
.collect(Collectors.toList());
} }
newCol.addAll(leftAbilities); newCol.addAll(leftAbilities);
leftState.updateSpellAbilities(newCol, mana); leftState.updateSpellAbilities(newCol, mana);
@@ -394,7 +400,10 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardState rightState = getCard().getState(CardStateName.RightSplit); CardState rightState = getCard().getState(CardStateName.RightSplit);
Collection<SpellAbility> rightAbilities = rightState.abilities; Collection<SpellAbility> rightAbilities = rightState.abilities;
if (null != mana) { if (null != mana) {
rightAbilities = rightAbilities.stream().filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility)).toList(); rightAbilities = rightAbilities.stream()
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
// stream().toList() causes crash on Android, use Collectors.toList()
.collect(Collectors.toList());
} }
newCol.addAll(rightAbilities); newCol.addAll(rightAbilities);
rightState.updateSpellAbilities(newCol, mana); rightState.updateSpellAbilities(newCol, mana);
@@ -428,8 +437,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
CardTypeView type = getTypeWithChanges(); CardTypeView type = getTypeWithChanges();
if (type.isLand()) { if (type.isLand()) {
if (landAbility == null) { if (landAbility == null) {
landAbility = new LandAbility(card); landAbility = new LandAbility(card, this);
landAbility.setCardState(this);
} }
newCol.add(landAbility); newCol.add(landAbility);
} else if (type.isAura()) { } else if (type.isAura()) {

View File

@@ -22,6 +22,7 @@ import com.esotericsoftware.minlog.Log;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost; import forge.game.cost.Cost;
/** /**
@@ -37,6 +38,9 @@ public abstract class Ability extends SpellAbility {
protected Ability(final Card sourceCard, final ManaCost manaCost) { protected Ability(final Card sourceCard, final ManaCost manaCost) {
this(sourceCard, new Cost(manaCost, true), null); this(sourceCard, new Cost(manaCost, true), null);
} }
protected Ability(final Card sourceCard, final ManaCost manaCost, final CardState state) {
super(sourceCard, new Cost(manaCost, true), null, state);
}
protected Ability(final Card sourceCard, final ManaCost manaCost, SpellAbilityView view0) { protected Ability(final Card sourceCard, final ManaCost manaCost, SpellAbilityView view0) {
this(sourceCard, new Cost(manaCost, true), view0); this(sourceCard, new Cost(manaCost, true), view0);
} }

View File

@@ -19,6 +19,7 @@ package forge.game.spellability;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardState;
import forge.game.cost.Cost; import forge.game.cost.Cost;
/** /**
@@ -43,6 +44,9 @@ public abstract class AbilityStatic extends Ability implements Cloneable {
public AbilityStatic(final Card sourceCard, final ManaCost manaCost) { public AbilityStatic(final Card sourceCard, final ManaCost manaCost) {
super(sourceCard, manaCost); super(sourceCard, manaCost);
} }
public AbilityStatic(final Card sourceCard, final ManaCost manaCost, final CardState state) {
super(sourceCard, manaCost, state);
}
public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) {
super(sourceCard, abCost); super(sourceCard, abCost);

View File

@@ -21,6 +21,7 @@ import forge.card.CardStateName;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCopyService; import forge.game.card.CardCopyService;
import forge.game.card.CardState;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -31,8 +32,8 @@ import org.apache.commons.lang3.StringUtils;
public class LandAbility extends AbilityStatic { public class LandAbility extends AbilityStatic {
public LandAbility(Card sourceCard) { public LandAbility(Card sourceCard, CardState state) {
super(sourceCard, ManaCost.NO_COST); super(sourceCard, ManaCost.NO_COST, state);
getRestrictions().setZone(ZoneType.Hand); getRestrictions().setZone(ZoneType.Hand);
} }

View File

@@ -195,12 +195,18 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
protected SpellAbility(final Card iSourceCard, final Cost toPay) { protected SpellAbility(final Card iSourceCard, final Cost toPay) {
this(iSourceCard, toPay, null); this(iSourceCard, toPay, null, null);
} }
protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0) { protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0) {
this(iSourceCard, toPay, view0, null);
}
protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0, CardState cs) {
id = nextId(); id = nextId();
hostCard = iSourceCard; hostCard = iSourceCard;
payCosts = toPay; payCosts = toPay;
if (cs != null) {
cardState = cs;
}
if (view0 == null) { if (view0 == null) {
view0 = new SpellAbilityView(this); view0 = new SpellAbilityView(this);
} }
@@ -1222,12 +1228,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
try { try {
clone = (SpellAbility) clone(); clone = (SpellAbility) clone();
clone.id = lki ? id : nextId(); clone.id = lki ? id : nextId();
clone.view = new SpellAbilityView(clone, lki || host.getGame() == null ? null : host.getGame().getTracker());
// don't use setHostCard to not trigger the not copied parts yet // don't use setHostCard to not trigger the not copied parts yet
copyHelper(clone, host, lki || keepTextChanges); copyHelper(clone, host, lki || keepTextChanges);
// need CardState before View
clone.view = new SpellAbilityView(clone, lki || host.getGame() == null ? null : host.getGame().getTracker());
// always set this to false, it is only set in CopyEffect // always set this to false, it is only set in CopyEffect
clone.mayChooseNewTargets = false; clone.mayChooseNewTargets = false;

View File

@@ -23,7 +23,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui-android</artifactId> <artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui-desktop</artifactId> <artifactId>forge-gui-desktop</artifactId>

View File

@@ -11,7 +11,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui-ios</artifactId> <artifactId>forge-gui-ios</artifactId>

View File

@@ -9,7 +9,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui-mobile-dev</artifactId> <artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui-mobile</artifactId> <artifactId>forge-gui-mobile</artifactId>

View File

@@ -35,7 +35,9 @@ import java.util.*;
* Class that represents the player (not the player sprite) * Class that represents the player (not the player sprite)
*/ */
public class AdventurePlayer implements Serializable, SaveFileContent { public class AdventurePlayer implements Serializable, SaveFileContent {
public static final int NUMBER_OF_DECKS = 10; public static final int MIN_DECK_COUNT = 10;
// this is a purely arbitrary limit, could be higher or lower; just meant as some sort of reasonable limit for the user
public static final int MAX_DECK_COUNT = 20;
// Player profile data. // Player profile data.
private String name; private String name;
private int heroRace; private int heroRace;
@@ -45,7 +47,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
// Deck data // Deck data
private Deck deck; private Deck deck;
private final Deck[] decks = new Deck[NUMBER_OF_DECKS]; private final ArrayList<Deck> decks = new ArrayList<Deck>(MIN_DECK_COUNT);
private int selectedDeckIndex = 0; private int selectedDeckIndex = 0;
private final DifficultyData difficultyData = new DifficultyData(); private final DifficultyData difficultyData = new DifficultyData();
@@ -91,9 +93,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return statistic; return statistic;
} }
public int getDeckCount() { return decks.size(); }
private void clearDecks() { private void clearDecks() {
for (int i = 0; i < NUMBER_OF_DECKS; i++) decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")); decks.clear();
deck = decks[0]; for (int i = 0; i < MIN_DECK_COUNT; i++)
decks.add(new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
deck = decks.get(0);
selectedDeckIndex = 0; selectedDeckIndex = 0;
} }
@@ -140,7 +146,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
announceCustom = usingCustomDeck = isUsingCustomDeck; announceCustom = usingCustomDeck = isUsingCustomDeck;
deck = startingDeck; deck = startingDeck;
decks[0] = deck; decks.set(0, deck);
cards.addAllFlat(deck.getAllCardsInASinglePool().toFlatList()); cards.addAllFlat(deck.getAllCardsInASinglePool().toFlatList());
@@ -173,9 +179,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
public void setSelectedDeckSlot(int slot) { public void setSelectedDeckSlot(int slot) {
if (slot >= 0 && slot < NUMBER_OF_DECKS) { if (slot >= 0 && slot < getDeckCount()) {
selectedDeckIndex = slot; selectedDeckIndex = slot;
deck = decks[selectedDeckIndex]; deck = decks.get(selectedDeckIndex);
setColorIdentity(DeckProxy.getColorIdentity(deck)); setColorIdentity(DeckProxy.getColorIdentity(deck));
} }
} }
@@ -214,7 +220,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
public Deck getDeck(int index) { public Deck getDeck(int index) {
return decks[index]; return decks.get(index);
} }
public CardPool getCards() { public CardPool getCards() {
@@ -448,17 +454,44 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
} }
for (int i = 0; i < NUMBER_OF_DECKS; i++) { // load decks
// check if this save has dynamic deck count, use set-count load if not
boolean hasDynamicDeckCount = data.containsKey("deckCount");
if (hasDynamicDeckCount) {
int dynamicDeckCount = data.readInt("deckCount");
// in case the save had previously saved more decks than the current version allows (in case of the max being lowered)
dynamicDeckCount = Math.min(MAX_DECK_COUNT, dynamicDeckCount);
for (int i = 0; i < dynamicDeckCount; i++){
// the first x elements are pre-created
if (i < MIN_DECK_COUNT) {
decks.set(i, new Deck(data.readString("deck_name_" + i)));
}
else {
decks.add(new Deck(data.readString("deck_name_" + i)));
}
decks.get(i).getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
if (data.containsKey("sideBoardCards_" + i))
decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
}
// in case we allow removing decks from the deck selection GUI, populate up to the minimum
for (int i = dynamicDeckCount++; i < MIN_DECK_COUNT; i++) {
decks.set(i, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
}
// legacy load
} else {
for (int i = 0; i < MIN_DECK_COUNT; i++) {
if (!data.containsKey("deck_name_" + i)) { if (!data.containsKey("deck_name_" + i)) {
if (i == 0) decks[i] = deck; if (i == 0) decks.set(i, deck);
else decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")); else decks.set(i, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
continue; continue;
} }
decks[i] = new Deck(data.readString("deck_name_" + i)); decks.set(i, new Deck(data.readString("deck_name_" + i)));
decks[i].getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i)))); decks.get(i).getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
if (data.containsKey("sideBoardCards_" + i)) if (data.containsKey("sideBoardCards_" + i))
decks[i].getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i)))); decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
} }
}
setSelectedDeckSlot(data.readInt("selectedDeckIndex")); setSelectedDeckSlot(data.readInt("selectedDeckIndex"));
cards.addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("cards")))); cards.addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("cards"))));
@@ -602,11 +635,14 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
data.storeObject("deckCards", deck.getMain().toCardList("\n").split("\n")); data.storeObject("deckCards", deck.getMain().toCardList("\n").split("\n"));
if (deck.get(DeckSection.Sideboard) != null) if (deck.get(DeckSection.Sideboard) != null)
data.storeObject("sideBoardCards", deck.get(DeckSection.Sideboard).toCardList("\n").split("\n")); data.storeObject("sideBoardCards", deck.get(DeckSection.Sideboard).toCardList("\n").split("\n"));
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
data.store("deck_name_" + i, decks[i].getName()); // save decks dynamically
data.storeObject("deck_" + i, decks[i].getMain().toCardList("\n").split("\n")); data.store("deckCount", getDeckCount());
if (decks[i].get(DeckSection.Sideboard) != null) for (int i = 0; i < getDeckCount(); i++) {
data.storeObject("sideBoardCards_" + i, decks[i].get(DeckSection.Sideboard).toCardList("\n").split("\n")); data.store("deck_name_" + i, decks.get(i).getName());
data.storeObject("deck_" + i, decks.get(i).getMain().toCardList("\n").split("\n"));
if (decks.get(i).get(DeckSection.Sideboard) != null)
data.storeObject("sideBoardCards_" + i, decks.get(i).get(DeckSection.Sideboard).toCardList("\n").split("\n"));
} }
data.store("selectedDeckIndex", selectedDeckIndex); data.store("selectedDeckIndex", selectedDeckIndex);
data.storeObject("cards", cards.toCardList("\n").split("\n")); data.storeObject("cards", cards.toCardList("\n").split("\n"));
@@ -933,7 +969,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
public void renameDeck(String text) { public void renameDeck(String text) {
deck = (Deck) deck.copyTo(text); deck = (Deck) deck.copyTo(text);
decks[selectedDeckIndex] = deck; decks.set(selectedDeckIndex, deck);
} }
public int cardSellPrice(PaperCard card) { public int cardSellPrice(PaperCard card) {
@@ -1182,10 +1218,23 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
/** /**
* Deletes a deck by replacing the current selected deck with a new deck * Clears a deck by replacing the current selected deck with a new deck
*/
public void clearDeck() {
deck = decks.set(selectedDeckIndex, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
}
/**
* Actually removes the deck from the list of decks.
*/ */
public void deleteDeck(){ public void deleteDeck(){
deck = decks[selectedDeckIndex] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")); int oldIndex = selectedDeckIndex;
this.setSelectedDeckSlot(0);
decks.remove(oldIndex);
}
public void addDeck(){
decks.add(new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
} }
/** /**
@@ -1194,9 +1243,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
* @return int - index of new copy slot, or -1 if no slot was available * @return int - index of new copy slot, or -1 if no slot was available
*/ */
public int copyDeck() { public int copyDeck() {
for (int i = 0; i < decks.length; i++) { for (int i = 0; i < MAX_DECK_COUNT; i++) {
if (isEmptyDeck(i)) { if (isEmptyDeck(i)) {
decks[i] = (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")"); decks.set(i, (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")"));
return i; return i;
} }
} }
@@ -1205,7 +1254,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
} }
public boolean isEmptyDeck(int deckIndex) { public boolean isEmptyDeck(int deckIndex) {
return decks[deckIndex].isEmpty() && decks[deckIndex].getName().equals(Forge.getLocalizer().getMessage("lblEmptyDeck")); return decks.get(deckIndex).isEmpty() && decks.get(deckIndex).getName().equals(Forge.getLocalizer().getMessage("lblEmptyDeck"));
} }
public void removeEvent(AdventureEventData completedEvent) { public void removeEvent(AdventureEventData completedEvent) {
@@ -1227,8 +1276,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
// 2. Count max cards across all decks in excess of unsellable // 2. Count max cards across all decks in excess of unsellable
Map<PaperCard, Integer> maxCardCounts = new HashMap<>(); Map<PaperCard, Integer> maxCardCounts = new HashMap<>();
for (int i = 0; i < NUMBER_OF_DECKS; i++) { for (int i = 0; i < getDeckCount(); i++) {
for (final Map.Entry<PaperCard, Integer> cp : decks[i].getAllCardsInASinglePool()) { for (final Map.Entry<PaperCard, Integer> cp : decks.get(i).getAllCardsInASinglePool()) {
int count = cp.getValue(); int count = cp.getValue();
if (count > maxCardCounts.getOrDefault(cp.getKey(), 0)) { if (count > maxCardCounts.getOrDefault(cp.getKey(), 0)) {
maxCardCounts.put(cp.getKey(), cp.getValue()); maxCardCounts.put(cp.getKey(), cp.getValue());

View File

@@ -389,7 +389,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
@Override @Override
public void onActivate() { public void onActivate() {
decksUsingMyCards = new ItemPool<>(InventoryItem.class); decksUsingMyCards = new ItemPool<>(InventoryItem.class);
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) { for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++) {
final Deck deck = AdventurePlayer.current().getDeck(i); final Deck deck = AdventurePlayer.current().getDeck(i);
CardPool main = deck.getMain(); CardPool main = deck.getMain();
for (final Map.Entry<PaperCard, Integer> e : main) { for (final Map.Entry<PaperCard, Integer> e : main) {
@@ -649,6 +649,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
Map<String, CardEdition> editionsByName = new HashMap<>(); Map<String, CardEdition> editionsByName = new HashMap<>();
for (CardEdition e : FModel.getMagicDb().getEditions()) { for (CardEdition e : FModel.getMagicDb().getEditions()) {
editionsByName.put(e.getName().toLowerCase(), e); editionsByName.put(e.getName().toLowerCase(), e);
editionsByName.put(e.getName().replace(":", "").toLowerCase(), e);
} }
String sketchbookPrefix = "landscape sketchbook - "; String sketchbookPrefix = "landscape sketchbook - ";

View File

@@ -16,11 +16,12 @@ import forge.adventure.util.Current;
public class DeckSelectScene extends UIScene { public class DeckSelectScene extends UIScene {
private final IntMap<TextraButton> buttons = new IntMap<>(); private final IntMap<TextraButton> buttons = new IntMap<>();
private final IntMap<Label> labels = new IntMap<>();
Color defColor; Color defColor;
TextField textInput; TextField textInput;
Table layout; Table layout;
TextraLabel header; TextraLabel header;
TextraButton back, edit, rename; TextraButton back, edit, rename, add;
int currentSlot = 0; int currentSlot = 0;
ScrollPane scrollPane; ScrollPane scrollPane;
Dialog renameDialog; Dialog renameDialog;
@@ -45,13 +46,13 @@ public class DeckSelectScene extends UIScene {
root.add(header).colspan(2); root.add(header).colspan(2);
root.row(); root.row();
root.add(scrollPane).expand().width(window.getWidth() - 20); root.add(scrollPane).expand().width(window.getWidth() - 20);
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) this.layoutDeckButtons();
addDeckSlot(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
textInput = Controls.newTextField(""); textInput = Controls.newTextField("");
back = ui.findActor("return"); back = ui.findActor("return");
edit = ui.findActor("edit"); edit = ui.findActor("edit");
rename = ui.findActor("rename"); rename = ui.findActor("rename");
add = ui.findActor("add");
ui.onButtonPress("return", DeckSelectScene.this::back); ui.onButtonPress("return", DeckSelectScene.this::back);
ui.onButtonPress("edit", DeckSelectScene.this::edit); ui.onButtonPress("edit", DeckSelectScene.this::edit);
ui.onButtonPress("rename", () -> { ui.onButtonPress("rename", () -> {
@@ -59,11 +60,44 @@ public class DeckSelectScene extends UIScene {
showRenameDialog(); showRenameDialog();
}); });
ui.onButtonPress("copy", DeckSelectScene.this::copy); ui.onButtonPress("copy", DeckSelectScene.this::copy);
ui.onButtonPress("delete", DeckSelectScene.this::maybeDelete); ui.onButtonPress("delete", DeckSelectScene.this::promptDelete);
ui.onButtonPress("add", DeckSelectScene.this::addDeck);
defColor = ui.findActor("return").getColor(); defColor = ui.findActor("return").getColor();
window.add(root); window.add(root);
} }
private void refreshDeckButtons(){
clearDeckButtons();
layoutDeckButtons();
}
private void clearDeckButtons(){
int count = AdventurePlayer.current().getDeckCount();
for (int i = count; i >= 0; i--){
clearDeckButton(i);
}
layout.clearChildren();
buttons.clear();
labels.clear();
}
private void layoutDeckButtons() {
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++)
addDeckButton(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
}
private void addDeck(){
if (Current.player().getDeckCount() >= AdventurePlayer.MAX_DECK_COUNT){
showDialog(createGenericDialog(Forge.getLocalizer().getMessage("lblAddDeck"), Forge.getLocalizer().getMessage("lblMaxDeckCountReached"),
Forge.getLocalizer().getMessage("lblOK"), null, this::removeDialog, null));
return;
}
Current.player().addDeck();
refreshDeckButtons();
select(Current.player().getSelectedDeckIndex());
}
private void copy() { private void copy() {
if (Current.player().isEmptyDeck(currentSlot)) return; if (Current.player().isEmptyDeck(currentSlot)) return;
int index = Current.player().copyDeck(); int index = Current.player().copyDeck();
@@ -79,17 +113,32 @@ public class DeckSelectScene extends UIScene {
} }
} }
private void maybeDelete() { private void promptDelete() {
if (Current.player().isEmptyDeck(currentSlot)) return;
Dialog deleteDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblAreYouSureProceedDelete"), Dialog deleteDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblAreYouSureProceedDelete"),
Forge.getLocalizer().getMessage("lblOK"), Forge.getLocalizer().getMessage("lblOK"),
Forge.getLocalizer().getMessage("lblAbort"), this::delete, this::removeDialog); Forge.getLocalizer().getMessage("lblAbort"), this::clearOrDelete, this::removeDialog);
showDialog(deleteDialog); showDialog(deleteDialog);
} }
private void delete() { /**
* Clears or deletes the currently selected deck.
*/
private void clearOrDelete(){
if (currentSlot >= AdventurePlayer.MIN_DECK_COUNT){
Current.player().deleteDeck(); Current.player().deleteDeck();
}
else {
Current.player().clearDeck();
}
refreshDeckButtons();
select(0);
removeDialog();
}
private void clear() {
Current.player().clearDeck();
updateDeckButton(currentSlot); updateDeckButton(currentSlot);
removeDialog(); removeDialog();
} }
@@ -117,7 +166,18 @@ public class DeckSelectScene extends UIScene {
showDialog(renameDialog); showDialog(renameDialog);
} }
private TextraButton addDeckSlot(String name, int i) { /**
* Not sure if this is strictly necessary in Java but wouldn't want to leak before clearing the layout table.
*/
private void clearDeckButton(int i){
if (buttons.containsKey(i)) {
TextraButton button = buttons.remove(i);
button.clearListeners();
button.clearActions();
}
}
private TextraButton addDeckButton(String name, int i) {
TextraButton button = Controls.newTextButton("-"); TextraButton button = Controls.newTextButton("-");
button.addListener(new ClickListener() { button.addListener(new ClickListener() {
@Override @Override
@@ -131,9 +191,12 @@ public class DeckSelectScene extends UIScene {
} }
}); });
button.setText(Current.player().getDeck(i).getName());
Label label = Controls.newLabel(name);
layout.add(Controls.newLabel(name)).pad(2); layout.add(Controls.newLabel(name)).pad(2);
layout.add(button).fill(true, false).expand(true, false).align(Align.left).expandX().pad(2); layout.add(button).fill(true, false).expand(true, false).align(Align.left).expandX().pad(2);
buttons.put(i, button); buttons.put(i, button);
labels.put(i, label);
addToSelectable(new Selectable(button)); addToSelectable(new Selectable(button));
layout.row(); layout.row();
return button; return button;
@@ -158,13 +221,7 @@ public class DeckSelectScene extends UIScene {
@Override @Override
public void enter() { public void enter() {
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) { refreshDeckButtons();
if (buttons.containsKey(i)) {
buttons.get(i).setText(Current.player().getDeck(i).getName());
buttons.get(i).getTextraLabel().layout();
buttons.get(i).layout();
}
}
GameHUD.getInstance().switchAudio(); GameHUD.getInstance().switchAudio();
select(Current.player().getSelectedDeckIndex()); select(Current.player().getSelectedDeckIndex());
performTouch(scrollPane); //can use mouse wheel if available to scroll after selection performTouch(scrollPane); //can use mouse wheel if available to scroll after selection
@@ -175,9 +232,7 @@ public class DeckSelectScene extends UIScene {
private void rename() { private void rename() {
String text = textInput.getText(); String text = textInput.getText();
Current.player().renameDeck(text); Current.player().renameDeck(text);
buttons.get(currentSlot).setText(Current.player().getDeck(currentSlot).getName()); updateDeckButton(currentSlot);
buttons.get(currentSlot).getTextraLabel().layout();
buttons.get(currentSlot).layout();
} }
private void edit() { private void edit() {

View File

@@ -511,13 +511,11 @@ public class EventScene extends MenuScene implements IAfterMatch {
if (match.p1 instanceof AdventureEventData.AdventureEventHuman) { if (match.p1 instanceof AdventureEventData.AdventureEventHuman) {
humanMatch = match; humanMatch = match;
continue;
} else if (match.p2 instanceof AdventureEventData.AdventureEventHuman) { } else if (match.p2 instanceof AdventureEventData.AdventureEventHuman) {
AdventureEventData.AdventureEventParticipant placeholder = match.p1; AdventureEventData.AdventureEventParticipant placeholder = match.p1;
match.p1 = match.p2; match.p1 = match.p2;
match.p2 = placeholder; match.p2 = placeholder;
humanMatch = match; humanMatch = match;
continue;
} else { } else {
//Todo: Actually run match simulation here //Todo: Actually run match simulation here
if (MyRandom.percentTrue(50)) { if (MyRandom.percentTrue(50)) {
@@ -530,7 +528,6 @@ public class EventScene extends MenuScene implements IAfterMatch {
match.winner = match.p2; match.winner = match.p2;
} }
} }
} }
if (humanMatch != null && humanMatch.round != currentEvent.currentRound) if (humanMatch != null && humanMatch.round != currentEvent.currentRound)
@@ -539,15 +536,17 @@ public class EventScene extends MenuScene implements IAfterMatch {
DuelScene duelScene = DuelScene.instance(); DuelScene duelScene = DuelScene.instance();
EnemySprite enemy = humanMatch.p2.getSprite(); EnemySprite enemy = humanMatch.p2.getSprite();
currentEvent.nextOpponent = humanMatch.p2; currentEvent.nextOpponent = humanMatch.p2;
advance.setDisabled(true);
FThreads.invokeInEdtNowOrLater(() -> Forge.setTransitionScreen(new TransitionScreen(() -> { FThreads.invokeInEdtNowOrLater(() -> Forge.setTransitionScreen(new TransitionScreen(() -> {
duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, false, currentEvent); duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, false, currentEvent);
advance.setDisabled(false);
Forge.switchScene(duelScene); Forge.switchScene(duelScene);
}, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.getName(), humanMatch.p1.getRecord(), humanMatch.p2.getRecord()))); }, Forge.takeScreenshot(), true, false, false, false, "", Current.player().avatar(), enemy.getAtlasPath(), Current.player().getName(), enemy.getName(), humanMatch.p1.getRecord(), humanMatch.p2.getRecord())));
} else { } else {
finishRound(); finishRound();
}
advance.setDisabled(false); advance.setDisabled(false);
} }
}
AdventureEventData.AdventureEventMatch humanMatch = null; AdventureEventData.AdventureEventMatch humanMatch = null;

View File

@@ -249,6 +249,27 @@ public class Controls {
return ret; return ret;
} }
static public SelectBox<Integer> newComboBox(Integer[] text, int item, Function<Object, Void> func) {
SelectBox<Integer> ret = newComboBox();
ret.getStyle().listStyle.selection.setTopHeight(4);
ret.setItems(text);
ret.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
try {
func.apply(((SelectBox) actor).getSelected());
} catch (Exception e) {
e.printStackTrace();
}
}
});
func.apply(item);
ret.getList().setAlignment(Align.center);
ret.setSelected(item);
ret.setAlignment(Align.right);
return ret;
}
static public TextField newTextField(String text) { static public TextField newTextField(String text) {
return new TextField(text, getSkin()); return new TextField(text, getSkin());
} }

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>${revision}</version> <version>2.0.04</version>
</parent> </parent>
<artifactId>forge-gui</artifactId> <artifactId>forge-gui</artifactId>

View File

@@ -32,7 +32,7 @@
<object id="53" template="../../obj/treasure.tx" x="194" y="125"/> <object id="53" template="../../obj/treasure.tx" x="194" y="125"/>
<object id="54" template="../../obj/enemy.tx" x="201.253" y="161.473"> <object id="54" template="../../obj/enemy.tx" x="201.253" y="161.473">
<properties> <properties>
<property name="effect" value="{ &quot;startBattleWithCard&quot;: [ &quot;Dreadhorde Invasion&quot; }"/> <property name="effect" value="{ &quot;startBattleWithCard&quot;: [&quot;Dreadhorde Invasion&quot;]}"/>
<property name="enemy" value="Big Zombie"/> <property name="enemy" value="Big Zombie"/>
<property name="speedModifier" type="float" value="3"/> <property name="speedModifier" type="float" value="3"/>
<property name="threatRange" type="int" value="60"/> <property name="threatRange" type="int" value="60"/>

View File

@@ -1,4 +1,4 @@
Deathknightidle.png deathknightidle.png
size: 80,16 size: 80,16
format: RGBA8888 format: RGBA8888
filter: Nearest,Nearest filter: Nearest,Nearest

View File

@@ -34,7 +34,7 @@
"width": 100, "width": 100,
"height": 30, "height": 30,
"x": 365, "x": 365,
"y": 60 "y": 50
}, },
{ {
"type": "TextButton", "type": "TextButton",
@@ -44,7 +44,7 @@
"width": 100, "width": 100,
"height": 30, "height": 30,
"x": 365, "x": 365,
"y": 110 "y": 90
}, },
{ {
"type": "TextButton", "type": "TextButton",
@@ -53,7 +53,7 @@
"width": 100, "width": 100,
"height": 30, "height": 30,
"x": 365, "x": 365,
"y": 160 "y": 130
}, },
{ {
"type": "TextButton", "type": "TextButton",
@@ -62,6 +62,15 @@
"width": 100, "width": 100,
"height": 30, "height": 30,
"x": 365, "x": 365,
"y": 170
},
{
"type": "TextButton",
"name": "add",
"text": "tr(lblAddDeck)",
"width": 100,
"height": 30,
"x": 365,
"y": 210 "y": 210
} }
] ]

View File

@@ -15,33 +15,42 @@
"x": 4, "x": 4,
"y": 4, "y": 4,
"width": 262, "width": 262,
"height": 414 "height": 384
},
{
"type": "TextButton",
"name": "add",
"text": "tr(lblAddDeck)",
"width": 130,
"height": 30,
"x": 4,
"y": 388
}, },
{ {
"type": "TextButton", "type": "TextButton",
"name": "delete", "name": "delete",
"text": "tr(lblDelete)", "text": "tr(lblDelete)",
"width": 86, "width": 130,
"height": 30,
"x": 136,
"y": 388
},
{
"type": "TextButton",
"name": "copy",
"text": "tr(lblCopy)",
"width": 130,
"height": 30, "height": 30,
"x": 4, "x": 4,
"y": 418 "y": 418
}, },
{
"type": "TextButton",
"name": "copy",
"text": "tr(lblCopy)",
"width": 86,
"height": 30,
"x": 92,
"y": 418
},
{ {
"type": "TextButton", "type": "TextButton",
"name": "rename", "name": "rename",
"text": "tr(lblRename)", "text": "tr(lblRename)",
"width": 86, "width": 130,
"height": 30, "height": 30,
"x": 180, "x": 136,
"y": 418 "y": 418
}, },
{ {

View File

@@ -141,3 +141,4 @@ Pioneer Masters, 3/6/PIO, PIO
Innistrad Remastered, 3/6/INR, INR Innistrad Remastered, 3/6/INR, INR
Aetherdrift, 3/6/DFT, DFT Aetherdrift, 3/6/DFT, DFT
Tarkir Dragonstorm, 3/6/TDM, TDM Tarkir Dragonstorm, 3/6/TDM, TDM
Final Fantasy, 3/6/FIN, FIN

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard
SVar:AttachAILogic:Reanimate SVar:AttachAILogic:Reanimate
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it.
SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Self | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach SVar:DBAnimate:DB$ Animate | Defined$ Self | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay

View File

@@ -10,4 +10,4 @@ SVar:DBUntap:DB$ Untap
A:AB$ PumpAll | Cost$ R | ValidCards$ Creature.YouCtrl | KW$ Haste | Exhaust$ True | SpellDescription$ Creatures you control gain haste until end of turn. A:AB$ PumpAll | Cost$ R | ValidCards$ Creature.YouCtrl | KW$ Haste | Exhaust$ True | SpellDescription$ Creatures you control gain haste until end of turn.
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard
DeckHints:Name$Audacious Knuckleblade DeckHints:Name$Audacious Knuckleblade
Oracle:Exhaust — {2}{G}: Seek a card named Audacious Knuckleblade and put it onto the battlefield tapped.\nExhaust — {1}{U}: Surveil 2, then untap this creature./nExhaust — {R}: Creatures you control gain haste until end of turn. Oracle:Exhaust — {2}{G}: Seek a card named Audacious Knuckleblade and put it onto the battlefield tapped.\nExhaust — {1}{U}: Surveil 2, then untap this creature.\nExhaust — {R}: Creatures you control gain haste until end of turn.

View File

@@ -1,8 +1,8 @@
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
Name:Clackbridge Troll Name:Clackbridge Troll
ManaCost:3 B B ManaCost:3 B B
Types:Creature Troll Types:Creature Troll
PT:8/8 PT:8/8
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
K:Haste K:Haste
K:Trample K:Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TripleGoat | TriggerDescription$ When CARDNAME enters, target opponent creates three 0/1 white Goat creature tokens. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TripleGoat | TriggerDescription$ When CARDNAME enters, target opponent creates three 0/1 white Goat creature tokens.

Some files were not shown because too many files have changed in this diff Show More