mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-12 00:38:44 +00:00
Compare commits
32 Commits
paperCardI
...
forge-2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08719b1596 | ||
|
|
840f3ea96c | ||
|
|
51929434be | ||
|
|
3ec480afa4 | ||
|
|
3d19e9e444 | ||
|
|
6d987791f7 | ||
|
|
7b7da00c22 | ||
|
|
34791bd892 | ||
|
|
e6bcd1be72 | ||
|
|
4cac354983 | ||
|
|
042f7f77a9 | ||
|
|
5fbb1dd0cd | ||
|
|
bb14dfc00e | ||
|
|
f83cc4ccfa | ||
|
|
d0d6835a5d | ||
|
|
104bc8fc55 | ||
|
|
057dd867a8 | ||
|
|
9eed8f5095 | ||
|
|
1f62be9773 | ||
|
|
7a46f92059 | ||
|
|
cfa79b9676 | ||
|
|
c0a63fa15b | ||
|
|
c61537ae16 | ||
|
|
3871095b92 | ||
|
|
dee846da49 | ||
|
|
2477553d13 | ||
|
|
c9df7d7f8e | ||
|
|
53cb093f9e | ||
|
|
28e86970dc | ||
|
|
f847fc1669 | ||
|
|
f3df55177a | ||
|
|
be6f345127 |
40
.github/workflows/maven-publish.yml
vendored
40
.github/workflows/maven-publish.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
export _JAVA_OPTIONS="-Xmx2g"
|
||||
d=$(date +%m.%d)
|
||||
# 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
|
||||
# move bz2 and jar from work dir to izpack dir
|
||||
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
||||
@@ -128,6 +128,44 @@ jobs:
|
||||
removeArtifacts: 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
|
||||
if: failure() # This step runs only if the job fails
|
||||
run: |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -347,13 +347,34 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
||||
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();
|
||||
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
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -548,20 +548,20 @@ public class GameAction {
|
||||
|
||||
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
|
||||
if (!suppress && toBattlefield) {
|
||||
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
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||
}
|
||||
// do ETB counters after zone add
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||
|
||||
// update static abilities after etb counters have been placed
|
||||
checkStaticAbilities();
|
||||
@@ -2794,6 +2794,7 @@ public class GameAction {
|
||||
if (aura == null) {
|
||||
return false;
|
||||
}
|
||||
aura.setActivatingPlayer(source.getController());
|
||||
|
||||
Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
|
||||
boolean canTargetPlayer = false;
|
||||
|
||||
@@ -52,6 +52,7 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
private String name = "";
|
||||
@@ -366,14 +367,16 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
public final FCollectionView<SpellAbility> getManaAbilities() {
|
||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||
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);
|
||||
return newCol;
|
||||
}
|
||||
public final FCollectionView<SpellAbility> getNonManaAbilities() {
|
||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||
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);
|
||||
return newCol;
|
||||
}
|
||||
@@ -385,7 +388,10 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
CardState leftState = getCard().getState(CardStateName.LeftSplit);
|
||||
Collection<SpellAbility> leftAbilities = leftState.abilities;
|
||||
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);
|
||||
leftState.updateSpellAbilities(newCol, mana);
|
||||
@@ -394,7 +400,10 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
CardState rightState = getCard().getState(CardStateName.RightSplit);
|
||||
Collection<SpellAbility> rightAbilities = rightState.abilities;
|
||||
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);
|
||||
rightState.updateSpellAbilities(newCol, mana);
|
||||
@@ -428,8 +437,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
CardTypeView type = getTypeWithChanges();
|
||||
if (type.isLand()) {
|
||||
if (landAbility == null) {
|
||||
landAbility = new LandAbility(card);
|
||||
landAbility.setCardState(this);
|
||||
landAbility = new LandAbility(card, this);
|
||||
}
|
||||
newCol.add(landAbility);
|
||||
} else if (type.isAura()) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.esotericsoftware.minlog.Log;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.cost.Cost;
|
||||
|
||||
/**
|
||||
@@ -37,6 +38,9 @@ public abstract class Ability extends SpellAbility {
|
||||
protected Ability(final Card sourceCard, final ManaCost manaCost) {
|
||||
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) {
|
||||
this(sourceCard, new Cost(manaCost, true), view0);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package forge.game.spellability;
|
||||
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
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) {
|
||||
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) {
|
||||
super(sourceCard, abCost);
|
||||
|
||||
@@ -21,6 +21,7 @@ import forge.card.CardStateName;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCopyService;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -31,8 +32,8 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class LandAbility extends AbilityStatic {
|
||||
|
||||
public LandAbility(Card sourceCard) {
|
||||
super(sourceCard, ManaCost.NO_COST);
|
||||
public LandAbility(Card sourceCard, CardState state) {
|
||||
super(sourceCard, ManaCost.NO_COST, state);
|
||||
|
||||
getRestrictions().setZone(ZoneType.Hand);
|
||||
}
|
||||
|
||||
@@ -195,12 +195,18 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
|
||||
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) {
|
||||
this(iSourceCard, toPay, view0, null);
|
||||
}
|
||||
protected SpellAbility(final Card iSourceCard, final Cost toPay, SpellAbilityView view0, CardState cs) {
|
||||
id = nextId();
|
||||
hostCard = iSourceCard;
|
||||
payCosts = toPay;
|
||||
if (cs != null) {
|
||||
cardState = cs;
|
||||
}
|
||||
if (view0 == null) {
|
||||
view0 = new SpellAbilityView(this);
|
||||
}
|
||||
@@ -1222,12 +1228,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
try {
|
||||
clone = (SpellAbility) clone();
|
||||
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
|
||||
|
||||
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
|
||||
clone.mayChooseNewTargets = false;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -35,7 +35,9 @@ import java.util.*;
|
||||
* Class that represents the player (not the player sprite)
|
||||
*/
|
||||
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.
|
||||
private String name;
|
||||
private int heroRace;
|
||||
@@ -45,7 +47,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
|
||||
// Deck data
|
||||
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 final DifficultyData difficultyData = new DifficultyData();
|
||||
|
||||
@@ -91,9 +93,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
return statistic;
|
||||
}
|
||||
|
||||
public int getDeckCount() { return decks.size(); }
|
||||
|
||||
private void clearDecks() {
|
||||
for (int i = 0; i < NUMBER_OF_DECKS; i++) decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
|
||||
deck = decks[0];
|
||||
decks.clear();
|
||||
for (int i = 0; i < MIN_DECK_COUNT; i++)
|
||||
decks.add(new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
|
||||
deck = decks.get(0);
|
||||
selectedDeckIndex = 0;
|
||||
}
|
||||
|
||||
@@ -140,7 +146,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
announceCustom = usingCustomDeck = isUsingCustomDeck;
|
||||
|
||||
deck = startingDeck;
|
||||
decks[0] = deck;
|
||||
decks.set(0, deck);
|
||||
|
||||
cards.addAllFlat(deck.getAllCardsInASinglePool().toFlatList());
|
||||
|
||||
@@ -173,9 +179,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
}
|
||||
|
||||
public void setSelectedDeckSlot(int slot) {
|
||||
if (slot >= 0 && slot < NUMBER_OF_DECKS) {
|
||||
if (slot >= 0 && slot < getDeckCount()) {
|
||||
selectedDeckIndex = slot;
|
||||
deck = decks[selectedDeckIndex];
|
||||
deck = decks.get(selectedDeckIndex);
|
||||
setColorIdentity(DeckProxy.getColorIdentity(deck));
|
||||
}
|
||||
}
|
||||
@@ -214,7 +220,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
}
|
||||
|
||||
public Deck getDeck(int index) {
|
||||
return decks[index];
|
||||
return decks.get(index);
|
||||
}
|
||||
|
||||
public CardPool getCards() {
|
||||
@@ -448,17 +454,44 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
|
||||
if (!data.containsKey("deck_name_" + i)) {
|
||||
if (i == 0) decks[i] = deck;
|
||||
else decks[i] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
|
||||
continue;
|
||||
// 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 (i == 0) decks.set(i, deck);
|
||||
else decks.set(i, new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck")));
|
||||
continue;
|
||||
}
|
||||
decks.set(i, 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))));
|
||||
}
|
||||
decks[i] = new Deck(data.readString("deck_name_" + i));
|
||||
decks[i].getMain().addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("deck_" + i))));
|
||||
if (data.containsKey("sideBoardCards_" + i))
|
||||
decks[i].getOrCreate(DeckSection.Sideboard).addAll(CardPool.fromCardList(Lists.newArrayList((String[]) data.readObject("sideBoardCards_" + i))));
|
||||
}
|
||||
|
||||
setSelectedDeckSlot(data.readInt("selectedDeckIndex"));
|
||||
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"));
|
||||
if (deck.get(DeckSection.Sideboard) != null)
|
||||
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());
|
||||
data.storeObject("deck_" + i, decks[i].getMain().toCardList("\n").split("\n"));
|
||||
if (decks[i].get(DeckSection.Sideboard) != null)
|
||||
data.storeObject("sideBoardCards_" + i, decks[i].get(DeckSection.Sideboard).toCardList("\n").split("\n"));
|
||||
|
||||
// save decks dynamically
|
||||
data.store("deckCount", getDeckCount());
|
||||
for (int i = 0; i < getDeckCount(); i++) {
|
||||
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.storeObject("cards", cards.toCardList("\n").split("\n"));
|
||||
@@ -933,7 +969,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
|
||||
public void renameDeck(String text) {
|
||||
deck = (Deck) deck.copyTo(text);
|
||||
decks[selectedDeckIndex] = deck;
|
||||
decks.set(selectedDeckIndex, deck);
|
||||
}
|
||||
|
||||
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 deleteDeck() {
|
||||
deck = decks[selectedDeckIndex] = new Deck(Forge.getLocalizer().getMessage("lblEmptyDeck"));
|
||||
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(){
|
||||
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
|
||||
*/
|
||||
public int copyDeck() {
|
||||
for (int i = 0; i < decks.length; i++) {
|
||||
for (int i = 0; i < MAX_DECK_COUNT; 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;
|
||||
}
|
||||
}
|
||||
@@ -1205,7 +1254,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1227,8 +1276,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
|
||||
// 2. Count max cards across all decks in excess of unsellable
|
||||
Map<PaperCard, Integer> maxCardCounts = new HashMap<>();
|
||||
for (int i = 0; i < NUMBER_OF_DECKS; i++) {
|
||||
for (final Map.Entry<PaperCard, Integer> cp : decks[i].getAllCardsInASinglePool()) {
|
||||
for (int i = 0; i < getDeckCount(); i++) {
|
||||
for (final Map.Entry<PaperCard, Integer> cp : decks.get(i).getAllCardsInASinglePool()) {
|
||||
int count = cp.getValue();
|
||||
if (count > maxCardCounts.getOrDefault(cp.getKey(), 0)) {
|
||||
maxCardCounts.put(cp.getKey(), cp.getValue());
|
||||
|
||||
@@ -389,7 +389,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
|
||||
@Override
|
||||
public void onActivate() {
|
||||
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);
|
||||
CardPool main = deck.getMain();
|
||||
for (final Map.Entry<PaperCard, Integer> e : main) {
|
||||
@@ -649,6 +649,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
|
||||
Map<String, CardEdition> editionsByName = new HashMap<>();
|
||||
for (CardEdition e : FModel.getMagicDb().getEditions()) {
|
||||
editionsByName.put(e.getName().toLowerCase(), e);
|
||||
editionsByName.put(e.getName().replace(":", "").toLowerCase(), e);
|
||||
}
|
||||
|
||||
String sketchbookPrefix = "landscape sketchbook - ";
|
||||
|
||||
@@ -16,11 +16,12 @@ import forge.adventure.util.Current;
|
||||
|
||||
public class DeckSelectScene extends UIScene {
|
||||
private final IntMap<TextraButton> buttons = new IntMap<>();
|
||||
private final IntMap<Label> labels = new IntMap<>();
|
||||
Color defColor;
|
||||
TextField textInput;
|
||||
Table layout;
|
||||
TextraLabel header;
|
||||
TextraButton back, edit, rename;
|
||||
TextraButton back, edit, rename, add;
|
||||
int currentSlot = 0;
|
||||
ScrollPane scrollPane;
|
||||
Dialog renameDialog;
|
||||
@@ -45,13 +46,13 @@ public class DeckSelectScene extends UIScene {
|
||||
root.add(header).colspan(2);
|
||||
root.row();
|
||||
root.add(scrollPane).expand().width(window.getWidth() - 20);
|
||||
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++)
|
||||
addDeckSlot(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
|
||||
this.layoutDeckButtons();
|
||||
|
||||
textInput = Controls.newTextField("");
|
||||
back = ui.findActor("return");
|
||||
edit = ui.findActor("edit");
|
||||
rename = ui.findActor("rename");
|
||||
add = ui.findActor("add");
|
||||
ui.onButtonPress("return", DeckSelectScene.this::back);
|
||||
ui.onButtonPress("edit", DeckSelectScene.this::edit);
|
||||
ui.onButtonPress("rename", () -> {
|
||||
@@ -59,11 +60,44 @@ public class DeckSelectScene extends UIScene {
|
||||
showRenameDialog();
|
||||
});
|
||||
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();
|
||||
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() {
|
||||
if (Current.player().isEmptyDeck(currentSlot)) return;
|
||||
int index = Current.player().copyDeck();
|
||||
@@ -79,17 +113,32 @@ public class DeckSelectScene extends UIScene {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeDelete() {
|
||||
if (Current.player().isEmptyDeck(currentSlot)) return;
|
||||
private void promptDelete() {
|
||||
Dialog deleteDialog = createGenericDialog(Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblAreYouSureProceedDelete"),
|
||||
Forge.getLocalizer().getMessage("lblOK"),
|
||||
Forge.getLocalizer().getMessage("lblAbort"), this::delete, this::removeDialog);
|
||||
Forge.getLocalizer().getMessage("lblAbort"), this::clearOrDelete, this::removeDialog);
|
||||
|
||||
showDialog(deleteDialog);
|
||||
}
|
||||
|
||||
private void delete() {
|
||||
Current.player().deleteDeck();
|
||||
/**
|
||||
* Clears or deletes the currently selected deck.
|
||||
*/
|
||||
private void clearOrDelete(){
|
||||
if (currentSlot >= AdventurePlayer.MIN_DECK_COUNT){
|
||||
Current.player().deleteDeck();
|
||||
}
|
||||
else {
|
||||
Current.player().clearDeck();
|
||||
}
|
||||
|
||||
refreshDeckButtons();
|
||||
select(0);
|
||||
removeDialog();
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
Current.player().clearDeck();
|
||||
updateDeckButton(currentSlot);
|
||||
removeDialog();
|
||||
}
|
||||
@@ -117,7 +166,18 @@ public class DeckSelectScene extends UIScene {
|
||||
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("-");
|
||||
button.addListener(new ClickListener() {
|
||||
@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(button).fill(true, false).expand(true, false).align(Align.left).expandX().pad(2);
|
||||
buttons.put(i, button);
|
||||
labels.put(i, label);
|
||||
addToSelectable(new Selectable(button));
|
||||
layout.row();
|
||||
return button;
|
||||
@@ -158,13 +221,7 @@ public class DeckSelectScene extends UIScene {
|
||||
|
||||
@Override
|
||||
public void enter() {
|
||||
for (int i = 0; i < AdventurePlayer.NUMBER_OF_DECKS; i++) {
|
||||
if (buttons.containsKey(i)) {
|
||||
buttons.get(i).setText(Current.player().getDeck(i).getName());
|
||||
buttons.get(i).getTextraLabel().layout();
|
||||
buttons.get(i).layout();
|
||||
}
|
||||
}
|
||||
refreshDeckButtons();
|
||||
GameHUD.getInstance().switchAudio();
|
||||
select(Current.player().getSelectedDeckIndex());
|
||||
performTouch(scrollPane); //can use mouse wheel if available to scroll after selection
|
||||
@@ -175,9 +232,7 @@ public class DeckSelectScene extends UIScene {
|
||||
private void rename() {
|
||||
String text = textInput.getText();
|
||||
Current.player().renameDeck(text);
|
||||
buttons.get(currentSlot).setText(Current.player().getDeck(currentSlot).getName());
|
||||
buttons.get(currentSlot).getTextraLabel().layout();
|
||||
buttons.get(currentSlot).layout();
|
||||
updateDeckButton(currentSlot);
|
||||
}
|
||||
|
||||
private void edit() {
|
||||
|
||||
@@ -511,13 +511,11 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
||||
|
||||
if (match.p1 instanceof AdventureEventData.AdventureEventHuman) {
|
||||
humanMatch = match;
|
||||
continue;
|
||||
} else if (match.p2 instanceof AdventureEventData.AdventureEventHuman) {
|
||||
AdventureEventData.AdventureEventParticipant placeholder = match.p1;
|
||||
match.p1 = match.p2;
|
||||
match.p2 = placeholder;
|
||||
humanMatch = match;
|
||||
continue;
|
||||
} else {
|
||||
//Todo: Actually run match simulation here
|
||||
if (MyRandom.percentTrue(50)) {
|
||||
@@ -530,7 +528,6 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
||||
match.winner = match.p2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (humanMatch != null && humanMatch.round != currentEvent.currentRound)
|
||||
@@ -539,14 +536,16 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
||||
DuelScene duelScene = DuelScene.instance();
|
||||
EnemySprite enemy = humanMatch.p2.getSprite();
|
||||
currentEvent.nextOpponent = humanMatch.p2;
|
||||
advance.setDisabled(true);
|
||||
FThreads.invokeInEdtNowOrLater(() -> Forge.setTransitionScreen(new TransitionScreen(() -> {
|
||||
duelScene.initDuels(WorldStage.getInstance().getPlayerSprite(), enemy, false, currentEvent);
|
||||
advance.setDisabled(false);
|
||||
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())));
|
||||
} else {
|
||||
finishRound();
|
||||
advance.setDisabled(false);
|
||||
}
|
||||
advance.setDisabled(false);
|
||||
}
|
||||
|
||||
AdventureEventData.AdventureEventMatch humanMatch = null;
|
||||
|
||||
@@ -249,6 +249,27 @@ public class Controls {
|
||||
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) {
|
||||
return new TextField(text, getSkin());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.04</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui</artifactId>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<object id="53" template="../../obj/treasure.tx" x="194" y="125"/>
|
||||
<object id="54" template="../../obj/enemy.tx" x="201.253" y="161.473">
|
||||
<properties>
|
||||
<property name="effect" value="{ "startBattleWithCard": [ "Dreadhorde Invasion" }"/>
|
||||
<property name="effect" value="{ "startBattleWithCard": ["Dreadhorde Invasion"]}"/>
|
||||
<property name="enemy" value="Big Zombie"/>
|
||||
<property name="speedModifier" type="float" value="3"/>
|
||||
<property name="threatRange" type="int" value="60"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Deathknightidle.png
|
||||
deathknightidle.png
|
||||
size: 80,16
|
||||
format: RGBA8888
|
||||
filter: Nearest,Nearest
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"width": 100,
|
||||
"height": 30,
|
||||
"x": 365,
|
||||
"y": 60
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
@@ -44,7 +44,7 @@
|
||||
"width": 100,
|
||||
"height": 30,
|
||||
"x": 365,
|
||||
"y": 110
|
||||
"y": 90
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
@@ -53,7 +53,7 @@
|
||||
"width": 100,
|
||||
"height": 30,
|
||||
"x": 365,
|
||||
"y": 160
|
||||
"y": 130
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
@@ -62,6 +62,15 @@
|
||||
"width": 100,
|
||||
"height": 30,
|
||||
"x": 365,
|
||||
"y": 170
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "add",
|
||||
"text": "tr(lblAddDeck)",
|
||||
"width": 100,
|
||||
"height": 30,
|
||||
"x": 365,
|
||||
"y": 210
|
||||
}
|
||||
]
|
||||
|
||||
@@ -15,33 +15,42 @@
|
||||
"x": 4,
|
||||
"y": 4,
|
||||
"width": 262,
|
||||
"height": 414
|
||||
"height": 384
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "add",
|
||||
"text": "tr(lblAddDeck)",
|
||||
"width": 130,
|
||||
"height": 30,
|
||||
"x": 4,
|
||||
"y": 388
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "delete",
|
||||
"text": "tr(lblDelete)",
|
||||
"width": 86,
|
||||
"width": 130,
|
||||
"height": 30,
|
||||
"x": 136,
|
||||
"y": 388
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "copy",
|
||||
"text": "tr(lblCopy)",
|
||||
"width": 130,
|
||||
"height": 30,
|
||||
"x": 4,
|
||||
"y": 418
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "copy",
|
||||
"text": "tr(lblCopy)",
|
||||
"width": 86,
|
||||
"height": 30,
|
||||
"x": 92,
|
||||
"y": 418
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "rename",
|
||||
"text": "tr(lblRename)",
|
||||
"width": 86,
|
||||
"width": 130,
|
||||
"height": 30,
|
||||
"x": 180,
|
||||
"x": 136,
|
||||
"y": 418
|
||||
},
|
||||
{
|
||||
|
||||
@@ -141,3 +141,4 @@ Pioneer Masters, 3/6/PIO, PIO
|
||||
Innistrad Remastered, 3/6/INR, INR
|
||||
Aetherdrift, 3/6/DFT, DFT
|
||||
Tarkir Dragonstorm, 3/6/TDM, TDM
|
||||
Final Fantasy, 3/6/FIN, FIN
|
||||
|
||||
@@ -5,4 +5,4 @@ A:SP$ Charm | Choices$ DBTokens,DBTap | CharmNum$ 1
|
||||
SVar:DBTokens:DB$ Token | TokenScript$ c_1_1_hero | TokenOwner$ You | TokenAmount$ 3 | SpellDescription$ Take the Elevator — Create three 1/1 colorless Hero creature tokens.
|
||||
SVar:DBTap:DB$ Tap | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures | SubAbility$ DBPutCounter | SpellDescription$ Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them.
|
||||
SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.targetedBy | CounterType$ Stun | CounterNum$ 1
|
||||
Oracle:Choose one—\n• Take the Elevator — Create three 1/1 colorless Hero creature tokens.\n• Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)
|
||||
Oracle:Choose one —\n• Take the Elevator — Create three 1/1 colorless Hero creature tokens.\n• Take 59 Flights of Stairs — Tap up to three target creatures. Put a stun counter on one of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)
|
||||
@@ -3,7 +3,7 @@ ManaCost:1 B
|
||||
Types:Enchantment Aura
|
||||
K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard
|
||||
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: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
|
||||
|
||||
@@ -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.
|
||||
DeckHas:Ability$Graveyard
|
||||
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.
|
||||
@@ -9,4 +9,4 @@ SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Count$DifferentCounterKinds_Ca
|
||||
SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable
|
||||
SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn.
|
||||
SVar:X:Count$xPaid
|
||||
Oracle:When this artifact enters, support X. (Put a +1/+1 counter on each of up to X target creatures.)\nGo for the Goal! — {3},{T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.
|
||||
Oracle:When this artifact enters, support X. (Put a +1/+1 counter on each of up to X target creatures.)\nGo for the Goal! — {3}, {T}: Until end of turn, target creature gains "Whenever this creature deals combat damage to a player, draw a card for each kind of counter on it" and it can't be blocked this turn.
|
||||
@@ -1,8 +1,8 @@
|
||||
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
|
||||
Name:Clackbridge Troll
|
||||
ManaCost:3 B B
|
||||
Types:Creature Troll
|
||||
PT:8/8
|
||||
# TODO: Improve AI logic. Currently AI will sacrifice even if Troll can't attack at all.
|
||||
K:Haste
|
||||
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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user