mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-12 08:48:39 +00:00
Compare commits
93 Commits
additional
...
forge-2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a29da54b2e | ||
|
|
ec63b230c8 | ||
|
|
e880a83df2 | ||
|
|
80cc7218a3 | ||
|
|
4809fb858a | ||
|
|
3af62888dc | ||
|
|
79e1d0a0f0 | ||
|
|
c447dfc888 | ||
|
|
8149966915 | ||
|
|
472f9481e8 | ||
|
|
2209ce3cee | ||
|
|
258c89e65d | ||
|
|
11913085ef | ||
|
|
53fca12a57 | ||
|
|
8e8a795f19 | ||
|
|
a4b27321ac | ||
|
|
ead83d932f | ||
|
|
900bd4327d | ||
|
|
1a2bb054f4 | ||
|
|
f908df46c8 | ||
|
|
f8c97842c4 | ||
|
|
c324b45025 | ||
|
|
5061ceda0e | ||
|
|
d1e677eb4f | ||
|
|
493a8f351b | ||
|
|
04172eead0 | ||
|
|
f0ed9288b3 | ||
|
|
03fe3d63ea | ||
|
|
83438ef72b | ||
|
|
cf18808a70 | ||
|
|
49dc2c1c42 | ||
|
|
692400db2a | ||
|
|
751c31b226 | ||
|
|
7be252c509 | ||
|
|
288eac743c | ||
|
|
d0bd80f158 | ||
|
|
7fe8154bcb | ||
|
|
87cd5c90a3 | ||
|
|
12399fca48 | ||
|
|
aaf17553c1 | ||
|
|
9dedd24d3e | ||
|
|
2443f1486d | ||
|
|
cfd1822198 | ||
|
|
7930c4949b | ||
|
|
ef6d0707ac | ||
|
|
cfd792cb69 | ||
|
|
f5352662cd | ||
|
|
bf1192f80d | ||
|
|
dee2150cf9 | ||
|
|
c44b105d9f | ||
|
|
b624fb3cf8 | ||
|
|
45396c1bf4 | ||
|
|
f562ae6fdb | ||
|
|
6615090bda | ||
|
|
eaf6f117a2 | ||
|
|
a8488502e7 | ||
|
|
309e36827c | ||
|
|
fe7883ddd8 | ||
|
|
06e5ff5174 | ||
|
|
2c31dd01dd | ||
|
|
d8a92c4879 | ||
|
|
16baeadf0c | ||
|
|
88ed81f75f | ||
|
|
70d9df1db2 | ||
|
|
aaa04570f2 | ||
|
|
9365d55964 | ||
|
|
0c61139f51 | ||
|
|
34bd623e45 | ||
|
|
0e31bb8565 | ||
|
|
0b87094f96 | ||
|
|
42e53c66f6 | ||
|
|
25a7d80146 | ||
|
|
a6170745b1 | ||
|
|
db32547a6e | ||
|
|
4df6d9998b | ||
|
|
2f42f6ca28 | ||
|
|
6617c10946 | ||
|
|
1d34e02957 | ||
|
|
132f8d3d4f | ||
|
|
d3961b1a53 | ||
|
|
2c04ef9e1f | ||
|
|
f599e3ead6 | ||
|
|
e16da84a75 | ||
|
|
0a622f5282 | ||
|
|
bb40138c52 | ||
|
|
2a7bd8bbd2 | ||
|
|
e6fc666012 | ||
|
|
a1297e593c | ||
|
|
2026c7eca0 | ||
|
|
137076f224 | ||
|
|
5538650681 | ||
|
|
0e36e6b6d9 | ||
|
|
f4c786763a |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -45,17 +45,16 @@ public class BiomeStructureDataMappingEditor extends JComponent {
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof BiomeStructureData.BiomeStructureDataMapping))
|
||||
if(!(value instanceof BiomeStructureData.BiomeStructureDataMapping biomeData))
|
||||
return label;
|
||||
BiomeStructureData.BiomeStructureDataMapping data=(BiomeStructureData.BiomeStructureDataMapping) value;
|
||||
// Get the renderer component from parent class
|
||||
|
||||
label.setText(data.name);
|
||||
label.setText(biomeData.name);
|
||||
if(editor.data!=null)
|
||||
{
|
||||
SwingAtlas itemAtlas=new SwingAtlas(Config.instance().getFile(editor.data.structureAtlasPath));
|
||||
if(itemAtlas.has(data.name))
|
||||
label.setIcon(itemAtlas.get(data.name));
|
||||
if(itemAtlas.has(biomeData.name))
|
||||
label.setIcon(itemAtlas.get(biomeData.name));
|
||||
else
|
||||
{
|
||||
ImageIcon img=itemAtlas.getAny();
|
||||
|
||||
@@ -25,9 +25,8 @@ public class DialogOptionEditor extends JComponent{
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof DialogData))
|
||||
if(!(value instanceof DialogData dialog))
|
||||
return label;
|
||||
DialogData dialog=(DialogData) value;
|
||||
StringBuilder builder=new StringBuilder();
|
||||
if(dialog.name==null||dialog.name.isEmpty())
|
||||
builder.append("[[Blank Option]]");
|
||||
|
||||
@@ -27,17 +27,16 @@ public class ItemsEditor extends JComponent {
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof ItemData))
|
||||
if(!(value instanceof ItemData item))
|
||||
return label;
|
||||
ItemData Item=(ItemData) value;
|
||||
// Get the renderer component from parent class
|
||||
|
||||
label.setText(Item.name);
|
||||
label.setText(item.name);
|
||||
if(itemAtlas==null)
|
||||
itemAtlas=new SwingAtlas(Config.instance().getFile(Paths.ITEMS_ATLAS));
|
||||
|
||||
if(itemAtlas.has(Item.iconName))
|
||||
label.setIcon(itemAtlas.get(Item.iconName));
|
||||
if(itemAtlas.has(item.iconName))
|
||||
label.setIcon(itemAtlas.get(item.iconName));
|
||||
else
|
||||
{
|
||||
ImageIcon img=itemAtlas.getAny();
|
||||
|
||||
@@ -26,9 +26,8 @@ public class QuestEditor extends JComponent {
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof AdventureQuestData))
|
||||
if(!(value instanceof AdventureQuestData quest))
|
||||
return label;
|
||||
AdventureQuestData quest=(AdventureQuestData) value;
|
||||
// Get the renderer component from parent class
|
||||
|
||||
label.setText(quest.name);
|
||||
|
||||
@@ -26,9 +26,8 @@ public class QuestStageEditor extends JComponent{
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof AdventureQuestStage))
|
||||
if(!(value instanceof AdventureQuestStage stageData))
|
||||
return label;
|
||||
AdventureQuestStage stageData=(AdventureQuestStage) value;
|
||||
label.setText(stageData.name);
|
||||
//label.setIcon(new ImageIcon(Config.instance().getFilePath(stageData.sourcePath))); //Type icon eventually?
|
||||
return label;
|
||||
|
||||
@@ -43,9 +43,8 @@ public class WorldEditor extends JComponent {
|
||||
JList list, Object value, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if(!(value instanceof BiomeData))
|
||||
if(!(value instanceof BiomeData biome))
|
||||
return label;
|
||||
BiomeData biome=(BiomeData) value;
|
||||
// Get the renderer component from parent class
|
||||
|
||||
label.setText(biome.name);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -115,8 +115,8 @@ public class AiAttackController {
|
||||
} // overloaded constructor to evaluate single specified attacker
|
||||
|
||||
private void refreshCombatants(GameEntity defender) {
|
||||
if (defender instanceof Card && ((Card) defender).isBattle()) {
|
||||
this.oppList = getOpponentCreatures(((Card) defender).getProtectingPlayer());
|
||||
if (defender instanceof Card card && card.isBattle()) {
|
||||
this.oppList = getOpponentCreatures(card.getProtectingPlayer());
|
||||
} else {
|
||||
this.oppList = getOpponentCreatures(defendingOpponent);
|
||||
}
|
||||
@@ -312,7 +312,8 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// Poison opponent if unblocked
|
||||
if (defender instanceof Player && ComputerUtilCombat.poisonIfUnblocked(attacker, (Player) defender) > 0) {
|
||||
if (defender instanceof Player player
|
||||
&& ComputerUtilCombat.poisonIfUnblocked(attacker, player) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -849,10 +850,9 @@ public class AiAttackController {
|
||||
// decided to attack another defender so related lists need to be updated
|
||||
// (though usually rather try to avoid this situation for performance reasons)
|
||||
if (defender != defendingOpponent) {
|
||||
if (defender instanceof Player) {
|
||||
defendingOpponent = (Player) defender;
|
||||
} else if (defender instanceof Card) {
|
||||
Card defCard = (Card) defender;
|
||||
if (defender instanceof Player p) {
|
||||
defendingOpponent = p;
|
||||
} else if (defender instanceof Card defCard) {
|
||||
if (defCard.isBattle()) {
|
||||
defendingOpponent = defCard.getProtectingPlayer();
|
||||
} else {
|
||||
@@ -946,8 +946,8 @@ public class AiAttackController {
|
||||
return 1;
|
||||
}
|
||||
// or weakest player
|
||||
if (r1.getKey() instanceof Player && r2.getKey() instanceof Player) {
|
||||
return ((Player) r1.getKey()).getLife() - ((Player) r2.getKey()).getLife();
|
||||
if (r1.getKey() instanceof Player p1 && r2.getKey() instanceof Player p2) {
|
||||
return p1.getLife() - p2.getLife();
|
||||
}
|
||||
}
|
||||
return r2.getValue() - r1.getValue();
|
||||
@@ -1314,7 +1314,7 @@ public class AiAttackController {
|
||||
attackersAssigned.add(attacker);
|
||||
|
||||
// check if attackers are enough to finish the attacked planeswalker
|
||||
if (i < left.size() - 1 && defender instanceof Card) {
|
||||
if (i < left.size() - 1 && defender instanceof Card card) {
|
||||
final int blockNum = this.blockers.size();
|
||||
int attackNum = 0;
|
||||
int damage = 0;
|
||||
@@ -1328,7 +1328,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// if enough damage: switch to next planeswalker
|
||||
if (damage >= ComputerUtilCombat.getDamageToKill((Card) defender, true)) {
|
||||
if (damage >= ComputerUtilCombat.getDamageToKill(card, true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1754,10 +1754,12 @@ public class AiAttackController {
|
||||
private boolean doRevengeOfRavensAttackLogic(final GameEntity defender, final Queue<Card> attackersLeft, int numForcedAttackers, int maxAttack) {
|
||||
// TODO: detect Revenge of Ravens by the trigger instead of by name
|
||||
boolean revengeOfRavens = false;
|
||||
if (defender instanceof Player) {
|
||||
revengeOfRavens = !CardLists.filter(((Player)defender).getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
} else if (defender instanceof Card) {
|
||||
revengeOfRavens = !CardLists.filter(((Card)defender).getController().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
if (defender instanceof Player player) {
|
||||
revengeOfRavens = !CardLists.filter(player.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
} else if (defender instanceof Card card) {
|
||||
revengeOfRavens = !CardLists.filter(card.getController().getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
}
|
||||
|
||||
if (!revengeOfRavens) {
|
||||
|
||||
@@ -161,12 +161,12 @@ public class AiBlockController {
|
||||
// defend battles with fewer defense counters before battles with more defense counters,
|
||||
// if planeswalker/battle will be too difficult to defend don't even bother
|
||||
for (GameEntity defender : defenders) {
|
||||
if ((defender instanceof Card && ((Card) defender).getController().equals(ai))
|
||||
|| (defender instanceof Card && ((Card) defender).isBattle() && ((Card) defender).getProtectingPlayer().equals(ai))) {
|
||||
final CardCollection attackers = combat.getAttackersOf(defender);
|
||||
if ((defender instanceof Card card1 && card1.getController().equals(ai))
|
||||
|| (defender instanceof Card card2 && card2.isBattle() && card2.getProtectingPlayer().equals(ai))) {
|
||||
final CardCollection ccAttackers = combat.getAttackersOf(defender);
|
||||
// Begin with the attackers that pose the biggest threat
|
||||
CardLists.sortByPowerDesc(attackers);
|
||||
sortedAttackers.addAll(attackers);
|
||||
CardLists.sortByPowerDesc(ccAttackers);
|
||||
sortedAttackers.addAll(ccAttackers);
|
||||
} else if (defender instanceof Player && defender.equals(ai)) {
|
||||
firstAttacker = combat.getAttackersOf(defender);
|
||||
CardLists.sortByPowerDesc(firstAttacker);
|
||||
@@ -872,9 +872,9 @@ public class AiBlockController {
|
||||
CardCollection threatenedPWs = new CardCollection();
|
||||
for (final Card attacker : attackers) {
|
||||
GameEntity def = combat.getDefenderByAttacker(attacker);
|
||||
if (def instanceof Card) {
|
||||
if (def instanceof Card card) {
|
||||
if (!onlyIfLethal) {
|
||||
threatenedPWs.add((Card) def);
|
||||
threatenedPWs.add(card);
|
||||
} else {
|
||||
int damageToPW = 0;
|
||||
for (final Card pwatkr : combat.getAttackersOf(def)) {
|
||||
@@ -906,12 +906,12 @@ public class AiBlockController {
|
||||
continue;
|
||||
}
|
||||
GameEntity def = combat.getDefenderByAttacker(attacker);
|
||||
if (def instanceof Card && threatenedPWs.contains(def)) {
|
||||
if (def instanceof Card card && threatenedPWs.contains(def)) {
|
||||
Card blockerDecided = null;
|
||||
for (final Card blocker : chumpPWDefenders) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
pwsWithChumpBlocks.add((Card) def);
|
||||
pwsWithChumpBlocks.add(card);
|
||||
chosenChumpBlockers.add(blocker);
|
||||
blockerDecided = blocker;
|
||||
blockersLeft.remove(blocker);
|
||||
@@ -1346,8 +1346,8 @@ public class AiBlockController {
|
||||
&& ai.getZone(ZoneType.Hand).contains(CardPredicates.CREATURES)
|
||||
&& aiCreatureCount + maxCreatDiffWithRepl >= oppCreatureCount;
|
||||
boolean wantToSavePlaneswalker = MyRandom.percentTrue(chanceToSavePW)
|
||||
&& combat.getDefenderByAttacker(attacker) instanceof Card
|
||||
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
|
||||
&& combat.getDefenderByAttacker(attacker) instanceof Card card
|
||||
&& card.isPlaneswalker();
|
||||
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
|
||||
|
||||
return ((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
|
||||
|
||||
@@ -1408,9 +1408,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
final CostSacrifice sac = (CostSacrifice) part;
|
||||
|
||||
if (part instanceof CostSacrifice sac) {
|
||||
final String type = sac.getType();
|
||||
|
||||
if (type.equals("CARDNAME")) {
|
||||
@@ -1776,9 +1774,7 @@ public class ComputerUtil {
|
||||
noRegen = true;
|
||||
}
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
|
||||
if (o instanceof Card c) {
|
||||
// indestructible
|
||||
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
continue;
|
||||
@@ -1842,9 +1838,7 @@ public class ComputerUtil {
|
||||
if (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c, false)) {
|
||||
threatened.add(c);
|
||||
}
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
|
||||
} else if (o instanceof Player p) {
|
||||
if (source.hasKeyword(Keyword.INFECT)) {
|
||||
if (p.canReceiveCounters(CounterEnumType.POISON) && ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= 10 - p.getPoisonCounters()) {
|
||||
threatened.add(p);
|
||||
@@ -1862,8 +1856,7 @@ public class ComputerUtil {
|
||||
|| saviourApi == null)) {
|
||||
final int dmg = -AbilityUtils.calculateAmount(source, topStack.getParam("NumDef"), topStack);
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
final boolean canRemove = (c.getNetToughness() <= dmg)
|
||||
|| (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && dmg >= ComputerUtilCombat.getDamageToKill(c, false));
|
||||
if (!canRemove) {
|
||||
@@ -1909,9 +1902,7 @@ public class ComputerUtil {
|
||||
|| saviourApi == ApiType.Protection || saviourApi == null
|
||||
|| saviorWithSubsApi == ApiType.Pump || saviorWithSubsApi == ApiType.PumpAll)) {
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
// indestructible
|
||||
if (o instanceof Card c) {
|
||||
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1960,8 +1951,7 @@ public class ComputerUtil {
|
||||
&& topStack.hasParam("Destination")
|
||||
&& topStack.getParam("Destination").equals("Exile")) {
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
// give Shroud to targeted creatures
|
||||
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
|
||||
continue;
|
||||
@@ -1988,8 +1978,7 @@ public class ComputerUtil {
|
||||
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
||||
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
// give Shroud to targeted creatures
|
||||
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
|
||||
continue;
|
||||
@@ -2011,8 +2000,7 @@ public class ComputerUtil {
|
||||
boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false;
|
||||
if (enableCurseAuraRemoval) {
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
// give Shroud to targeted creatures
|
||||
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
|
||||
continue;
|
||||
|
||||
@@ -176,7 +176,7 @@ public class ComputerUtilCombat {
|
||||
public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
|
||||
int damage = attacker.getNetCombatDamage();
|
||||
int sum = 0;
|
||||
if (attacked instanceof Player && !((Player) attacked).canLoseLife()) {
|
||||
if (attacked instanceof Player player && !player.canLoseLife()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2539,20 +2539,20 @@ public class ComputerUtilCombat {
|
||||
if (combat != null) {
|
||||
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
|
||||
// 1. If the card that spawned the attacker was sent at a card, attack the same. Consider improving.
|
||||
if (def instanceof Card && Iterables.contains(defenders, def)) {
|
||||
if (((Card) def).isPlaneswalker()) {
|
||||
if (def instanceof Card card && Iterables.contains(defenders, def)) {
|
||||
if (card.isPlaneswalker()) {
|
||||
return def;
|
||||
}
|
||||
if (((Card) def).isBattle()) {
|
||||
if (card.isBattle()) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
|
||||
for (GameEntity p : defenders) {
|
||||
if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker, true)) {
|
||||
if (p instanceof Player p1 && !ComputerUtilCard.canBeBlockedProfitably(p1, attacker, true)) {
|
||||
return p;
|
||||
}
|
||||
if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker, true)) {
|
||||
if (p instanceof Card card && !ComputerUtilCard.canBeBlockedProfitably(card.getController(), attacker, true)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostPutCounter) {
|
||||
final CostPutCounter addCounter = (CostPutCounter) part;
|
||||
if (part instanceof CostPutCounter addCounter) {
|
||||
final CounterType type = addCounter.getCounter();
|
||||
|
||||
if (type.is(CounterEnumType.M1M1)) {
|
||||
@@ -77,9 +76,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa, false);
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
|
||||
if (part instanceof CostRemoveCounter remCounter) {
|
||||
final CounterType type = remCounter.counter;
|
||||
if (!part.payCostFromSource()) {
|
||||
if (type.is(CounterEnumType.P1P1)) {
|
||||
@@ -106,9 +103,7 @@ public class ComputerUtilCost {
|
||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
} else if (part instanceof CostRemoveAnyCounter) {
|
||||
final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part;
|
||||
|
||||
} else if (part instanceof CostRemoveAnyCounter remCounter) {
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
return pay != null;
|
||||
}
|
||||
@@ -133,9 +128,7 @@ public class ComputerUtilCost {
|
||||
CardCollection hand = new CardCollection(ai.getCardsIn(ZoneType.Hand));
|
||||
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
final CostDiscard disc = (CostDiscard) part;
|
||||
|
||||
if (part instanceof CostDiscard disc) {
|
||||
final String type = disc.getType();
|
||||
final CardCollection typeList;
|
||||
int num;
|
||||
@@ -187,8 +180,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostDamage) {
|
||||
final CostDamage pay = (CostDamage) part;
|
||||
if (part instanceof CostDamage pay) {
|
||||
int realDamage = ComputerUtilCombat.predictDamageTo(ai, pay.getAbilityAmount(sa), source, false);
|
||||
if (ai.getLife() - realDamage < remainingLife
|
||||
&& realDamage > 0 && !ai.cantLoseForZeroOrLessLife()
|
||||
@@ -220,9 +212,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostPayLife) {
|
||||
final CostPayLife payLife = (CostPayLife) part;
|
||||
|
||||
if (part instanceof CostPayLife payLife) {
|
||||
int amount = payLife.getAbilityAmount(sourceAbility);
|
||||
|
||||
// check if there's override for the remainingLife threshold
|
||||
@@ -296,8 +286,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
final CostSacrifice sac = (CostSacrifice) part;
|
||||
if (part instanceof CostSacrifice sac) {
|
||||
final int amount = AbilityUtils.calculateAmount(source, sac.getAmount(), sourceAbility);
|
||||
|
||||
if (sac.payCostFromSource() && source.isCreature()) {
|
||||
@@ -346,12 +335,11 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
if (part instanceof CostSacrifice sac) {
|
||||
if (suppressRecursiveSacCostCheck) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CostSacrifice sac = (CostSacrifice) part;
|
||||
final int amount = AbilityUtils.calculateAmount(source, sac.getAmount(), sourceAbility);
|
||||
|
||||
String type = sac.getType();
|
||||
|
||||
@@ -1326,7 +1326,9 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
CostAdjustment.adjust(manaCost, sa, null, test);
|
||||
if (!effect) {
|
||||
CostAdjustment.adjust(manaCost, sa, null, test);
|
||||
}
|
||||
|
||||
if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle
|
||||
ManaCost mkCost = sa.getPayCosts().getTotalMana();
|
||||
|
||||
@@ -1265,8 +1265,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public boolean playSaFromPlayEffect(SpellAbility tgtSA) {
|
||||
boolean optional = !tgtSA.getPayCosts().isMandatory();
|
||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||
Spell spell = (Spell) tgtSA;
|
||||
if (tgtSA instanceof Spell spell) { // Isn't it ALWAYS a spell?
|
||||
// TODO if mandatory AI is only forced to use mana when it's already in the pool
|
||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||
|
||||
@@ -342,9 +342,9 @@ public abstract class SpellAbilityAi {
|
||||
for (T ent : options) {
|
||||
if (ent instanceof Player) {
|
||||
hasPlayer = true;
|
||||
} else if (ent instanceof Card) {
|
||||
} else if (ent instanceof Card card) {
|
||||
hasCard = true;
|
||||
if (((Card)ent).isPlaneswalker() || ((Card)ent).isBattle()) {
|
||||
if (card.isPlaneswalker() || card.isBattle()) {
|
||||
hasAttackableCard = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -138,8 +137,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (aiLogic != null) {
|
||||
if (aiLogic.equals("Always")) {
|
||||
return true;
|
||||
} else if (aiLogic.startsWith("ExileSpell")) {
|
||||
return doExileSpellLogic(aiPlayer, sa);
|
||||
} else if (aiLogic.startsWith("SacAndUpgrade")) { // Birthing Pod, Natural Order, etc.
|
||||
return doSacAndUpgradeLogic(aiPlayer, sa);
|
||||
} else if (aiLogic.startsWith("SacAndRetFromGrave")) { // Recurring Nightmare, etc.
|
||||
@@ -878,6 +875,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
origin.addAll(ZoneType.listValueOf(sa.getParam("TgtZone")));
|
||||
}
|
||||
|
||||
if (origin.contains(ZoneType.Stack) && doExileSpellLogic(ai, sa, mandatory)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final Game game = ai.getGame();
|
||||
|
||||
@@ -902,7 +903,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||
list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30);
|
||||
@@ -2061,31 +2061,24 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doExileSpellLogic(final Player aiPlayer, final SpellAbility sa) {
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
SpellAbilityStackInstance top = aiPlayer.getGame().getStack().peek();
|
||||
List<ApiType> dangerousApi = Arrays.asList(ApiType.DealDamage, ApiType.DamageAll, ApiType.Destroy, ApiType.DestroyAll, ApiType.Sacrifice, ApiType.SacrificeAll);
|
||||
int manaCost = 0;
|
||||
int minCost = 0;
|
||||
|
||||
if (aiLogic.contains(".")) {
|
||||
minCost = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1));
|
||||
private static boolean doExileSpellLogic(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
List<ApiType> dangerousApi = null;
|
||||
CardCollection spells = new CardCollection(ai.getGame().getStackZone().getCards());
|
||||
Collections.reverse(spells);
|
||||
if (!mandatory && !spells.isEmpty()) {
|
||||
spells = spells.subList(0, 1);
|
||||
spells = ComputerUtil.filterAITgts(sa, ai, spells, true);
|
||||
dangerousApi = Arrays.asList(ApiType.DealDamage, ApiType.DamageAll, ApiType.Destroy, ApiType.DestroyAll, ApiType.Sacrifice, ApiType.SacrificeAll);
|
||||
}
|
||||
|
||||
if (top != null) {
|
||||
SpellAbility topSA = top.getSpellAbility();
|
||||
if (topSA != null) {
|
||||
if (topSA.getPayCosts().hasManaCost()) {
|
||||
manaCost = topSA.getPayCosts().getTotalMana().getCMC();
|
||||
}
|
||||
|
||||
if ((manaCost >= minCost || dangerousApi.contains(topSA.getApi()))
|
||||
&& topSA.getActivatingPlayer().isOpponentOf(aiPlayer)
|
||||
&& sa.canTargetSpellAbility(topSA)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(topSA);
|
||||
return sa.isTargetNumberValid();
|
||||
}
|
||||
for (Card c : spells) {
|
||||
SpellAbility topSA = ai.getGame().getStack().getSpellMatchingHost(c);
|
||||
if (topSA != null && (dangerousApi == null ||
|
||||
(dangerousApi.contains(topSA.getApi()) && topSA.getActivatingPlayer().isOpponentOf(ai)))
|
||||
&& sa.canTarget(topSA)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(topSA);
|
||||
return sa.isTargetNumberValid();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -222,7 +222,7 @@ final class CardFace implements ICardFace, Cloneable {
|
||||
else variant.replacements.addAll(0, this.replacements);
|
||||
|
||||
if(variant.variables == null) variant.variables = this.variables;
|
||||
else variant.variables.putAll(this.variables);
|
||||
else this.variables.forEach((k, v) -> variant.variables.putIfAbsent(k, v));
|
||||
|
||||
if(variant.nonAbilityText == null) variant.nonAbilityText = this.nonAbilityText;
|
||||
if(variant.draftActions == null) variant.draftActions = this.draftActions;
|
||||
|
||||
@@ -645,9 +645,8 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof Deck) {
|
||||
final DeckBase dbase = (DeckBase) o;
|
||||
boolean deckBaseEquals = super.equals(dbase);
|
||||
if (o instanceof DeckBase deckBase) {
|
||||
boolean deckBaseEquals = super.equals(deckBase);
|
||||
if (!deckBaseEquals)
|
||||
return false;
|
||||
// ok so far we made sure they do have the same name. Now onto comparing parts
|
||||
|
||||
@@ -472,7 +472,8 @@ public class DeckRecognizer {
|
||||
"side", "sideboard", "sb",
|
||||
"main", "card", "mainboard",
|
||||
"avatar", "commander", "schemes",
|
||||
"conspiracy", "planes", "deck", "dungeon"};
|
||||
"conspiracy", "planes", "deck", "dungeon",
|
||||
"attractions", "contraptions"};
|
||||
|
||||
private static CharSequence[] allCardTypes(){
|
||||
List<String> cardTypesList = new ArrayList<>();
|
||||
@@ -670,6 +671,9 @@ public class DeckRecognizer {
|
||||
// ok so the card has been found - let's see if there's any restriction on the set
|
||||
return checkAndSetCardToken(pc, edition, cardCount, deckSecFromCardLine,
|
||||
currentDeckSection, true);
|
||||
// On the off chance we accidentally interpreted part of the card's name as a set code, e.g. "Tyrranax Rex"
|
||||
if (data.isMTGCard(cardName + " " + setCode))
|
||||
continue;
|
||||
// UNKNOWN card as in the Counterspell|FEM case
|
||||
return Token.UnknownCard(cardName, setCode, cardCount);
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ public class BoosterGenerator {
|
||||
}
|
||||
|
||||
public static List<PaperCard> getBoosterPack(SealedTemplate template) {
|
||||
if (template instanceof SealedTemplateWithSlots) {
|
||||
return BoosterGenerator.getBoosterPack((SealedTemplateWithSlots) template);
|
||||
if (template instanceof SealedTemplateWithSlots slots) {
|
||||
return BoosterGenerator.getBoosterPack(slots);
|
||||
}
|
||||
|
||||
List<PaperCard> result = new ArrayList<>();
|
||||
|
||||
@@ -275,7 +275,7 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (obj instanceof ItemPool) &&
|
||||
(this.items.equals(((ItemPool)obj).items));
|
||||
return (obj instanceof ItemPool ip) &&
|
||||
(this.items.equals(ip.items));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -337,9 +337,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
if (params.containsKey("Blessing")) {
|
||||
if ("True".equalsIgnoreCase(params.get("Blessing")) != hostController.hasBlessing()) return false;
|
||||
}
|
||||
if (params.containsKey("MaxSpeed")) {
|
||||
if ("True".equalsIgnoreCase(params.get("MaxSpeed")) != hostController.maxSpeed()) return false;
|
||||
}
|
||||
|
||||
if (params.containsKey("DayTime")) {
|
||||
if ("Day".equalsIgnoreCase(params.get("DayTime"))) {
|
||||
|
||||
@@ -1185,6 +1185,12 @@ public class Game {
|
||||
for (Player player : getRegisteredPlayers()) {
|
||||
player.onCleanupPhase();
|
||||
}
|
||||
for (final Card c : getCardsIncludePhasingIn(ZoneType.Battlefield)) {
|
||||
c.onCleanupPhase(getPhaseHandler().getPlayerTurn());
|
||||
}
|
||||
for (final Card card : getCardsInGame()) {
|
||||
card.resetActivationsPerTurn();
|
||||
}
|
||||
}
|
||||
|
||||
public void addCounterAddedThisTurn(Player putter, CounterType cType, Card card, Integer value) {
|
||||
|
||||
@@ -82,12 +82,6 @@ public class GameAction {
|
||||
game = game0;
|
||||
}
|
||||
|
||||
public final void resetActivationsPerTurn() {
|
||||
for (final Card card : game.getCardsInGame()) {
|
||||
card.resetActivationsPerTurn();
|
||||
}
|
||||
}
|
||||
|
||||
public Card changeZone(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause) {
|
||||
return changeZone(zoneFrom, zoneTo, c, position, cause, null);
|
||||
}
|
||||
@@ -107,6 +101,8 @@ public class GameAction {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// dev mode
|
||||
if (zoneFrom == null && !c.isToken()) {
|
||||
zoneTo.add(c, position, CardCopyService.getLKICopy(c));
|
||||
checkStaticAbilities();
|
||||
@@ -314,37 +310,34 @@ public class GameAction {
|
||||
c.getOwner().setCommanderReplacementSuppressed(true);
|
||||
}
|
||||
|
||||
// in addition to actual tokens, cards "made" by digital-only mechanics
|
||||
// are also added to inbound tokens so their etb replacements will work
|
||||
if (zoneFrom == null || zoneFrom.is(ZoneType.None)) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
}
|
||||
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(copied);
|
||||
repParams.put(AbilityKey.CardLKI, lastKnownInfo);
|
||||
repParams.put(AbilityKey.Cause, cause);
|
||||
repParams.put(AbilityKey.Origin, zoneFrom != null ? zoneFrom.getZoneType() : null);
|
||||
repParams.put(AbilityKey.Destination, zoneTo.getZoneType());
|
||||
|
||||
if (toBattlefield) {
|
||||
repParams.put(AbilityKey.EffectOnly, true);
|
||||
repParams.put(AbilityKey.CounterTable, table);
|
||||
repParams.put(AbilityKey.CounterMap, table.column(copied));
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
repParams.putAll(params);
|
||||
}
|
||||
|
||||
// in addition to actual tokens, cards "made" by digital-only mechanics
|
||||
// are also added to inbound tokens so their etb replacements will work
|
||||
if (zoneFrom == null || zoneFrom.is(ZoneType.None)) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
}
|
||||
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams);
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
|
||||
if (repres != ReplacementResult.NotReplaced && repres != ReplacementResult.Updated) {
|
||||
// reset failed manifested Cards back to original
|
||||
if ((c.isManifested() || c.isCloaked()) && !c.isInPlay()) {
|
||||
c.forceTurnFaceUp();
|
||||
}
|
||||
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
|
||||
if (repres == ReplacementResult.Prevented) {
|
||||
c.clearControllers();
|
||||
cleanStaticEffect(staticEff, copied);
|
||||
@@ -359,10 +352,6 @@ public class GameAction {
|
||||
if (c.isInZone(ZoneType.Stack) && !zoneTo.is(ZoneType.Graveyard)) {
|
||||
return moveToGraveyard(c, cause, params);
|
||||
}
|
||||
|
||||
copied.clearDevoured();
|
||||
copied.clearDelved();
|
||||
copied.clearExploited();
|
||||
} else if (toBattlefield && !c.isInPlay()) {
|
||||
// was replaced with another Zone Change
|
||||
if (c.removeChangedState()) {
|
||||
@@ -379,8 +368,6 @@ public class GameAction {
|
||||
copied.setGameTimestamp(game.getNextTimestamp());
|
||||
}
|
||||
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
|
||||
// Aura entering as Copy from stack
|
||||
// without targets it is sent to graveyard
|
||||
if (copied.isAura() && !copied.isAttachedToEntity() && toBattlefield) {
|
||||
@@ -432,10 +419,6 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (suppress) {
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
}
|
||||
|
||||
if (zoneFrom != null) {
|
||||
if (fromBattlefield && game.getCombat() != null) {
|
||||
if (!toBattlefield) {
|
||||
@@ -549,29 +532,25 @@ public class GameAction {
|
||||
// order here is important so it doesn't unattach cards that might have returned from UntilHostLeavesPlay
|
||||
unattachCardLeavingBattlefield(copied, c);
|
||||
c.runLeavesPlayCommands();
|
||||
|
||||
if (copied.isTapped()) {
|
||||
copied.setTapped(false); //untap card after it leaves the battlefield if needed
|
||||
game.fireEvent(new GameEventCardTapped(c, false));
|
||||
}
|
||||
}
|
||||
if (fromGraveyard) {
|
||||
game.addLeftGraveyardThisTurn(lastKnownInfo);
|
||||
}
|
||||
|
||||
// do ETB counters after zone add
|
||||
if (!suppress && toBattlefield && !table.isEmpty()) {
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
}
|
||||
|
||||
if (c.hasChosenColorSpire()) {
|
||||
copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID()));
|
||||
}
|
||||
|
||||
copied.updateStateForView();
|
||||
|
||||
if (fromBattlefield) {
|
||||
copied.setDamage(0); //clear damage after a card leaves the battlefield
|
||||
copied.setHasBeenDealtDeathtouchDamage(false);
|
||||
if (copied.isTapped()) {
|
||||
copied.setTapped(false); //untap card after it leaves the battlefield if needed
|
||||
game.fireEvent(new GameEventCardTapped(c, false));
|
||||
}
|
||||
// needed for counters + ascend
|
||||
if (!suppress && toBattlefield) {
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
}
|
||||
|
||||
if (!table.isEmpty()) {
|
||||
@@ -579,12 +558,12 @@ public class GameAction {
|
||||
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);
|
||||
}
|
||||
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
|
||||
// update static abilities after etb counters have been placed
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||
checkStaticAbilities();
|
||||
|
||||
// 400.7g try adding keyword back into card if it doesn't already have it
|
||||
@@ -607,25 +586,26 @@ public class GameAction {
|
||||
c.cleanupExiledWith();
|
||||
}
|
||||
|
||||
game.getTriggerHandler().clearActiveTriggers(copied, null);
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
|
||||
// play the change zone sound
|
||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(copied);
|
||||
runParams.put(AbilityKey.CardLKI, lastKnownInfo);
|
||||
runParams.put(AbilityKey.Cause, cause);
|
||||
runParams.put(AbilityKey.Origin, zoneFrom != null ? zoneFrom.getZoneType().name() : null);
|
||||
runParams.put(AbilityKey.Destination, zoneTo.getZoneType().name());
|
||||
runParams.put(AbilityKey.IndividualCostPaymentInstance, game.costPaymentStack.peek());
|
||||
runParams.put(AbilityKey.MergedCards, mergedCards);
|
||||
game.getTriggerHandler().clearActiveTriggers(copied, null);
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
|
||||
if (params != null) {
|
||||
runParams.putAll(params);
|
||||
if (!suppress) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(copied);
|
||||
runParams.put(AbilityKey.CardLKI, lastKnownInfo);
|
||||
runParams.put(AbilityKey.Cause, cause);
|
||||
runParams.put(AbilityKey.Origin, zoneFrom != null ? zoneFrom.getZoneType().name() : null);
|
||||
runParams.put(AbilityKey.Destination, zoneTo.getZoneType().name());
|
||||
runParams.put(AbilityKey.IndividualCostPaymentInstance, game.costPaymentStack.peek());
|
||||
runParams.put(AbilityKey.MergedCards, mergedCards);
|
||||
if (params != null) {
|
||||
runParams.putAll(params);
|
||||
}
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true);
|
||||
}
|
||||
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true);
|
||||
if (fromBattlefield && !zoneFrom.getPlayer().equals(zoneTo.getPlayer())) {
|
||||
final Map<AbilityKey, Object> runParams2 = AbilityKey.mapFromCard(lastKnownInfo);
|
||||
runParams2.put(AbilityKey.OriginalController, zoneFrom.getPlayer());
|
||||
@@ -635,31 +615,18 @@ public class GameAction {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams2, false);
|
||||
}
|
||||
|
||||
if (suppress) {
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
}
|
||||
|
||||
if (zoneFrom == null) {
|
||||
return copied;
|
||||
}
|
||||
|
||||
if (!c.isRealToken() && !toBattlefield) {
|
||||
copied.clearDevoured();
|
||||
copied.clearDelved();
|
||||
copied.clearExploited();
|
||||
}
|
||||
|
||||
// rule 504.6: reveal a face-down card leaving the stack
|
||||
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) {
|
||||
// CR 708.9 reveal face-down card leaving
|
||||
if (wasFacedown && (fromBattlefield || (zoneFrom.is(ZoneType.Stack) && !toBattlefield))) {
|
||||
Card revealLKI = CardCopyService.getLKICopy(c);
|
||||
revealLKI.forceTurnFaceUp();
|
||||
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card moves from the stack: ");
|
||||
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card leaves the " + zoneFrom.toString() + ": ");
|
||||
}
|
||||
|
||||
if (fromBattlefield) {
|
||||
if (!c.isRealToken() && !c.isSpecialized()) {
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
// Soulbond unpairing
|
||||
if (c.isPaired()) {
|
||||
c.getPairedWith().setPairedWith(null);
|
||||
@@ -680,27 +647,12 @@ public class GameAction {
|
||||
}
|
||||
changeZone(null, zoneTo, unmeld, position, cause, params);
|
||||
}
|
||||
// Reveal if face-down
|
||||
if (wasFacedown) {
|
||||
Card revealLKI = CardCopyService.getLKICopy(c);
|
||||
revealLKI.forceTurnFaceUp();
|
||||
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card leaves the battlefield: ");
|
||||
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
} else if (toBattlefield) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p);
|
||||
copied.getDamageHistory().setNotBlockedSinceLastUpkeepOf(p);
|
||||
copied.getDamageHistory().setNotBeenBlockedSinceLastUpkeepOf(p);
|
||||
}
|
||||
} else if (zoneTo.is(ZoneType.Graveyard)
|
||||
|| zoneTo.is(ZoneType.Hand)
|
||||
|| zoneTo.is(ZoneType.Library)
|
||||
|| zoneTo.is(ZoneType.Exile)) {
|
||||
if (copied.isFaceDown()) {
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Cards not on the battlefield / stack should not have controller
|
||||
@@ -748,12 +700,12 @@ public class GameAction {
|
||||
eff.setLayerTimestamp(timestamp);
|
||||
} else {
|
||||
// otherwise create effect first
|
||||
eff = SpellAbilityEffect.createEffect(cause, cause.getActivatingPlayer(), name, source.getImageKey(), timestamp);
|
||||
eff = SpellAbilityEffect.createEffect(cause, cause.getHostCard(), cause.getActivatingPlayer(), name, source.getImageKey(), timestamp);
|
||||
eff.setRenderForUI(false);
|
||||
StaticAbility stAb = eff.addStaticAbility(AbilityUtils.getSVar(cause, cause.getParam("StaticEffect")));
|
||||
stAb.setActiveZone(EnumSet.of(ZoneType.Command));
|
||||
// needed for ETB lookahead like Bronzehide Lion
|
||||
stAb.putParam("AffectedZone", "Battlefield,Hand,Graveyard,Exile,Stack,Library,Command");
|
||||
stAb.putParam("AffectedZone", "All");
|
||||
SpellAbilityEffect.addForgetOnMovedTrigger(eff, "Battlefield");
|
||||
game.getAction().moveToCommand(eff, cause);
|
||||
}
|
||||
@@ -1038,7 +990,8 @@ public class GameAction {
|
||||
lki = CardCopyService.getLKICopy(c);
|
||||
}
|
||||
game.addChangeZoneLKIInfo(lki);
|
||||
if (lki.isInPlay()) {
|
||||
// CR 702.26k
|
||||
if (lki.isInPlay() && !lki.isPhasedOut()) {
|
||||
if (game.getCombat() != null) {
|
||||
game.getCombat().saveLKI(lki);
|
||||
game.getCombat().removeFromCombat(c);
|
||||
@@ -1282,9 +1235,9 @@ public class GameAction {
|
||||
// Update P/T and type in the view only once after all the cards have been processed, to avoid flickering
|
||||
for (Card c : affectedCards) {
|
||||
c.updateNameforView();
|
||||
c.updatePowerToughnessForView();
|
||||
c.updatePTforView();
|
||||
c.updateTypesForView();
|
||||
c.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
|
||||
c.updateKeywords();
|
||||
}
|
||||
|
||||
// TODO filter out old copies from zone change
|
||||
@@ -1569,6 +1522,12 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// 704.5z If a player controls a permanent with start your engines! and that player has no speed, that player’s speed becomes 1. See rule 702.179, “Start Your Engines!”
|
||||
if (p.getSpeed() == 0 && p.getCardsIn(ZoneType.Battlefield).anyMatch(c -> c.hasKeyword(Keyword.START_YOUR_ENGINES))) {
|
||||
p.increaseSpeed();
|
||||
checkAgain = true;
|
||||
}
|
||||
|
||||
if (handlePlaneswalkerRule(p, noRegCreats)) {
|
||||
checkAgain = true;
|
||||
}
|
||||
@@ -2684,8 +2643,8 @@ public class GameAction {
|
||||
if (isCombat) {
|
||||
for (Map.Entry<GameEntity, Map<Card, Integer>> et : damageMap.columnMap().entrySet()) {
|
||||
final GameEntity ge = et.getKey();
|
||||
if (ge instanceof Card) {
|
||||
((Card) ge).clearAssignedDamage();
|
||||
if (ge instanceof Card c) {
|
||||
c.clearAssignedDamage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2705,8 +2664,7 @@ public class GameAction {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.getKey() instanceof Card && !lethalDamage.containsKey(e.getKey())) {
|
||||
Card c = (Card) e.getKey();
|
||||
if (e.getKey() instanceof Card c && !lethalDamage.containsKey(c)) {
|
||||
lethalDamage.put(c, c.getExcessDamageValue(false));
|
||||
}
|
||||
|
||||
|
||||
@@ -273,8 +273,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
}
|
||||
|
||||
String controllerName;
|
||||
if (defender instanceof Card) {
|
||||
Card c = ((Card)defender);
|
||||
if (defender instanceof Card c) {
|
||||
controllerName = c.isBattle() ? c.getProtectingPlayer().getName() : c.getController().getName();
|
||||
} else {
|
||||
controllerName = defender.getName();
|
||||
|
||||
@@ -219,7 +219,9 @@ public class StaticEffect {
|
||||
|
||||
if (layers.contains(StaticAbilityLayer.TEXT)) {
|
||||
// Revert changed color words
|
||||
affectedCard.removeChangedTextColorWord(getTimestamp(), ability.getId());
|
||||
if (hasParam("ChangeColorWordsTo")) {
|
||||
affectedCard.removeChangedTextColorWord(getTimestamp(), ability.getId());
|
||||
}
|
||||
|
||||
// remove changed name
|
||||
if (hasParam("SetName") || hasParam("AddNames")) {
|
||||
@@ -265,7 +267,7 @@ public class StaticEffect {
|
||||
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf")
|
||||
|| hasParam("GainsAbilitiesOfDefined") || hasParam("GainsTriggerAbsOf")
|
||||
|| hasParam("AddTrigger") || hasParam("AddStaticAbility")
|
||||
|| hasParam("AddReplacementEffects") || hasParam("RemoveAllAbilities")
|
||||
|| hasParam("AddReplacementEffect") || hasParam("RemoveAllAbilities")
|
||||
|| hasParam("RemoveLandTypes")) {
|
||||
affectedCard.removeChangedCardTraits(getTimestamp(), ability.getId());
|
||||
}
|
||||
@@ -275,11 +277,14 @@ public class StaticEffect {
|
||||
}
|
||||
|
||||
affectedCard.removeChangedSVars(getTimestamp(), ability.getId());
|
||||
|
||||
// need update for clean reapply
|
||||
affectedCard.updateKeywordsCache(affectedCard.getCurrentState());
|
||||
}
|
||||
|
||||
if (layers.contains(StaticAbilityLayer.SETPT)) {
|
||||
if (layers.contains(StaticAbilityLayer.CHARACTERISTIC) || layers.contains(StaticAbilityLayer.SETPT)) {
|
||||
if (hasParam("SetPower") || hasParam("SetToughness")) {
|
||||
affectedCard.removeNewPT(getTimestamp(), ability.getId());
|
||||
affectedCard.removeNewPT(getTimestamp(), ability.getId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,8 +316,6 @@ public class StaticEffect {
|
||||
affectedCard.removeCanBlockAdditional(getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
affectedCard.updateAbilityTextForView(); // need to update keyword cache for clean reapply
|
||||
}
|
||||
return affectedCards;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import forge.game.*;
|
||||
import forge.game.ability.AbilityFactory.AbilityRecordType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.IndividualCostPaymentInstance;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.mana.Mana;
|
||||
@@ -1362,10 +1363,8 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
// do blessing there before condition checks
|
||||
if (source.hasKeyword(Keyword.ASCEND)) {
|
||||
if (controller.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||
controller.setBlessing(true);
|
||||
}
|
||||
if (source.hasKeyword(Keyword.ASCEND) && controller.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||
controller.setBlessing(true);
|
||||
}
|
||||
|
||||
if (source.hasKeyword(Keyword.GIFT) && sa.isGiftPromised()) {
|
||||
@@ -2839,7 +2838,13 @@ public class AbilityUtils {
|
||||
final String[] workingCopy = paidparts[0].split("_");
|
||||
final String validFilter = workingCopy[1];
|
||||
// use objectXCount ?
|
||||
return CardUtil.getThisTurnActivated(validFilter, c, ctb, player).size();
|
||||
int activated = CardUtil.getThisTurnActivated(validFilter, c, ctb, player).size();
|
||||
for (IndividualCostPaymentInstance i : game.costPaymentStack) {
|
||||
if (i.getPayment().getAbility().isValid(validFilter, player, c, ctb)) {
|
||||
activated++;
|
||||
}
|
||||
}
|
||||
return activated;
|
||||
}
|
||||
|
||||
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
|
||||
@@ -3699,6 +3704,10 @@ public class AbilityUtils {
|
||||
return doXMath(amount, m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.equals("AttractionsVisitedThisTurn")) {
|
||||
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.startsWith("PlaneswalkedToThisTurn")) {
|
||||
int found = 0;
|
||||
String name = value.split(" ")[1];
|
||||
|
||||
@@ -588,11 +588,10 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
// create a basic template for Effect to be used somewhere els
|
||||
public static Card createEffect(final SpellAbility sa, final Player controller, final String name, final String image) {
|
||||
return createEffect(sa, controller, name, image, controller.getGame().getNextTimestamp());
|
||||
return createEffect(sa, sa.getHostCard(), controller, name, image, controller.getGame().getNextTimestamp());
|
||||
}
|
||||
public static Card createEffect(final SpellAbility sa, final Player controller, final String name, final String image, final long timestamp) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = hostCard.getGame();
|
||||
public static Card createEffect(final SpellAbility sa, final Card hostCard, final Player controller, final String name, final String image, final long timestamp) {
|
||||
final Game game = controller.getGame();
|
||||
final Card eff = new Card(game.nextCardId(), game);
|
||||
|
||||
eff.setGameTimestamp(timestamp);
|
||||
@@ -608,12 +607,7 @@ public abstract class SpellAbilityEffect {
|
||||
eff.setRarity(hostCard.getRarity());
|
||||
}
|
||||
|
||||
if (sa.hasParam("Boon")) {
|
||||
eff.setBoon(true);
|
||||
}
|
||||
|
||||
eff.setOwner(controller);
|
||||
eff.setSVars(sa.getSVars());
|
||||
|
||||
eff.setSetCode(hostCard.getSetCode());
|
||||
if (image != null) {
|
||||
@@ -621,7 +615,12 @@ public abstract class SpellAbilityEffect {
|
||||
}
|
||||
|
||||
eff.setGamePieceType(GamePieceType.EFFECT);
|
||||
eff.setEffectSource(sa);
|
||||
if (sa != null) {
|
||||
eff.setEffectSource(sa);
|
||||
eff.setSVars(sa.getSVars());
|
||||
} else {
|
||||
eff.setEffectSource(hostCard);
|
||||
}
|
||||
|
||||
return eff;
|
||||
}
|
||||
@@ -1041,7 +1040,9 @@ public abstract class SpellAbilityEffect {
|
||||
exilingSource = cause.getOriginalHost();
|
||||
}
|
||||
movedCard.setExiledWith(exilingSource);
|
||||
movedCard.setExiledBy(cause.getActivatingPlayer());
|
||||
Player exiler = cause.hasParam("DefinedExiler") ?
|
||||
getDefinedPlayersOrTargeted(cause, "DefinedExiler").get(0) : cause.getActivatingPlayer();
|
||||
movedCard.setExiledBy(exiler);
|
||||
}
|
||||
|
||||
public static GameCommand exileEffectCommand(final Game game, final Card effect) {
|
||||
|
||||
@@ -202,7 +202,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
if (sa.isCrew()) {
|
||||
gameCard.becomesCrewed(sa);
|
||||
gameCard.updatePowerToughnessForView();
|
||||
gameCard.updatePTforView();
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(gameCard));
|
||||
|
||||
@@ -132,7 +132,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
source = changingTgtSA.getTargetCard();
|
||||
}
|
||||
Predicate<GameObject> filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, source, sa) : null;
|
||||
// TODO Creature.Other might not work yet as it should
|
||||
TargetChoices newTarget = chooser.getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
||||
changingTgtSI.updateTarget(newTarget, sa.getHostCard());
|
||||
}
|
||||
|
||||
@@ -107,10 +107,8 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
final Zone originZone = game.getZoneOf(c);
|
||||
|
||||
// Fizzle spells so that they are removed from stack (e.g. Summary Dismissal)
|
||||
if (sa.hasParam("Fizzle")) {
|
||||
if (originZone.is(ZoneType.Exile) || originZone.is(ZoneType.Hand) || originZone.is(ZoneType.Stack)) {
|
||||
game.getStack().remove(c);
|
||||
}
|
||||
if (originZone.is(ZoneType.Stack)) {
|
||||
game.getStack().remove(c);
|
||||
}
|
||||
|
||||
if (remLKI) {
|
||||
|
||||
@@ -558,22 +558,15 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (originZone.is(ZoneType.Stack)) {
|
||||
game.getStack().remove(gameCard);
|
||||
}
|
||||
|
||||
Card movedCard = null;
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
AbilityKey.addCardZoneTableParams(moveParams, triggerList);
|
||||
|
||||
if (destination.equals(ZoneType.Library)) {
|
||||
// If a card is moved to library from the stack, remove its spells from the stack
|
||||
if (sa.hasParam("Fizzle")) {
|
||||
// TODO only AI still targets as card, try to remove it
|
||||
if (gameCard.isInZone(ZoneType.Exile) || gameCard.isInZone(ZoneType.Hand) || gameCard.isInZone(ZoneType.Stack)) {
|
||||
// This only fizzles spells, not anything else.
|
||||
game.getStack().remove(gameCard);
|
||||
}
|
||||
}
|
||||
|
||||
movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa, moveParams);
|
||||
} else if (destination.equals(ZoneType.Battlefield)) {
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
moveParams.put(AbilityKey.SimultaneousETB, tgtCards);
|
||||
if (sa.isReplacementAbility()) {
|
||||
ReplacementEffect re = sa.getReplacementEffect();
|
||||
@@ -725,15 +718,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
commandCards.add(movedCard); //add to list to reveal the commandzone cards
|
||||
}
|
||||
|
||||
// If a card is Exiled from the stack, remove its spells from the stack
|
||||
if (sa.hasParam("Fizzle")) {
|
||||
if (gameCard.isInZone(ZoneType.Exile) || gameCard.isInZone(ZoneType.Hand)
|
||||
|| gameCard.isInZone(ZoneType.Stack) || gameCard.isInZone(ZoneType.Command)) {
|
||||
// This only fizzles spells, not anything else.
|
||||
game.getStack().remove(gameCard);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("WithCountersType")) {
|
||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||
int cAmount = AbilityUtils.calculateAmount(hostCard, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
|
||||
@@ -63,8 +63,7 @@ public class ControlSpellEffect extends SpellAbilityEffect {
|
||||
// Expand this area as it becomes needed
|
||||
// Use "DefinedExchange" to Reference Object that is Exchanging the other direction
|
||||
GameObject obj = Iterables.getFirst(getDefinedOrTargeted(sa, "DefinedExchange"), null);
|
||||
if (obj instanceof Card) {
|
||||
Card c = (Card)obj;
|
||||
if (obj instanceof Card c) {
|
||||
if (!c.isInPlay() || si == null) {
|
||||
// Exchanging object isn't available, continue
|
||||
continue;
|
||||
|
||||
@@ -257,7 +257,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
|
||||
params.put(AbilityKey.StackSa, tgtSA);
|
||||
|
||||
String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : tgtSA.isAftermath() ? "Exile" : "Graveyard";
|
||||
String destination = srcSA.getParamOrDefault("Destination", "Graveyard");
|
||||
if (srcSA.hasParam("DestinationChoice")) { //Hinder
|
||||
List<String> pos = Arrays.asList(srcSA.getParam("DestinationChoice").split(","));
|
||||
destination = srcSA.getActivatingPlayer().getController().chooseSomeType(Localizer.getInstance().getMessage("lblRemoveDestination"), tgtSA, pos);
|
||||
|
||||
@@ -252,8 +252,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("DividedRandomly")) {
|
||||
CardCollection targets = new CardCollection();
|
||||
for (final GameEntity obj : tgtObjects) { // check if each target is still OK
|
||||
if (obj instanceof Card) {
|
||||
Card tgtCard = (Card) obj;
|
||||
if (obj instanceof Card tgtCard) {
|
||||
Card gameCard = game.getCardState(tgtCard, null);
|
||||
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
|
||||
tgtObjects.remove(obj);
|
||||
@@ -284,8 +283,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
for (final GameEntity obj : tgtObjects) {
|
||||
// check if the object is still in game or if it was moved
|
||||
Card gameCard = null;
|
||||
if (obj instanceof Card) {
|
||||
Card tgtCard = (Card) obj;
|
||||
if (obj instanceof Card tgtCard) {
|
||||
gameCard = game.getCardState(tgtCard, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
// or the timestamp did change
|
||||
@@ -572,9 +570,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) {
|
||||
counterRemain = counterRemain - counterAmount;
|
||||
}
|
||||
} else if (obj instanceof Player) {
|
||||
} else if (obj instanceof Player pl) {
|
||||
// Add Counters to players!
|
||||
Player pl = (Player) obj;
|
||||
pl.addCounter(counterType, counterAmount, placer, table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,8 +248,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
if (chosenAmount > 0) {
|
||||
removed += chosenAmount;
|
||||
entity.subtractCounter(chosenType, chosenAmount, activator);
|
||||
if (entity instanceof Card) {
|
||||
Card gameCard = (Card) entity;
|
||||
if (entity instanceof Card gameCard) {
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,16 +252,14 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
final Card gc = game.getCardState(c, null);
|
||||
if (gc == null || !c.equalsWithGameTimestamp(gc) || !gc.isInPlay() || gc.isPhasedOut()) {
|
||||
// timestamp different or not in play
|
||||
continue;
|
||||
}
|
||||
internalDamageDeal(sa, sourceLKI, gc, dmg, damageMap);
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
} else if (o instanceof Player p) {
|
||||
damageMap.put(sourceLKI, p, dmg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,7 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
}
|
||||
} else for (GameEntity ge : getTargetEntities(sa)) {
|
||||
// check before checking sources
|
||||
if (ge instanceof Card) {
|
||||
final Card c = (Card) ge;
|
||||
if (ge instanceof Card c) {
|
||||
if (!c.isInPlay() || c.isPhasedOut()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ public class DamagePreventEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final Object o = tgts.get(i);
|
||||
if (o instanceof Card) {
|
||||
final Card tgtC = (Card) o;
|
||||
if (o instanceof Card tgtC) {
|
||||
if (tgtC.isFaceDown()) {
|
||||
sb.append("Morph");
|
||||
} else {
|
||||
@@ -104,8 +103,7 @@ public class DamagePreventEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final GameEntity o : tgts) {
|
||||
numDam = sa.usesTargeting() && sa.isDividedAsYouChoose() ? sa.getDividedValue(o) : numDam;
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
if (c.isInPlay()) {
|
||||
addPreventNextDamage(sa, o, numDam);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
final boolean skipReorder = sa.hasParam("SkipReorder");
|
||||
|
||||
// A hack for cards like Explorer's Scope that need to ensure that a card is revealed to the player activating the ability
|
||||
final boolean forceRevealToController = sa.hasParam("ForceRevealToController");
|
||||
final boolean forceReveal = sa.hasParam("ForceRevealToController") ||
|
||||
sa.hasParam("ForceReveal");
|
||||
|
||||
// These parameters are used to indicate that a dialog box must be show to the player asking if the player wants to proceed
|
||||
// with an optional ability, otherwise the optional ability is skipped.
|
||||
@@ -236,9 +237,12 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
valid = top;
|
||||
}
|
||||
|
||||
if (forceRevealToController) {
|
||||
// Force revealing the card to the player activating the ability (e.g. Explorer's Scope)
|
||||
game.getAction().revealTo(top, activator);
|
||||
if (forceReveal) {
|
||||
// Force revealing the card to defined (e.g. Gonti, Night Minister) or the player activating the
|
||||
// ability (e.g. Explorer's Scope)
|
||||
Player revealTo = sa.hasParam("ForceReveal") ?
|
||||
getDefinedPlayersOrTargeted(sa, "ForceReveal").get(0) : activator;
|
||||
game.getAction().revealTo(top, revealTo);
|
||||
delayedReveal = null; // top is already seen by the player, do not reveal twice
|
||||
}
|
||||
|
||||
|
||||
@@ -161,6 +161,9 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
|
||||
for (Player controller : effectOwner) {
|
||||
final Card eff = createEffect(sa, controller, name, image);
|
||||
if (sa.hasParam("Boon")) {
|
||||
eff.setBoon(true);
|
||||
}
|
||||
|
||||
// Abilities and triggers work the same as they do for Token
|
||||
// Grant abilities
|
||||
|
||||
@@ -32,6 +32,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final AbilityManaPart abMana = sa.getManaPart();
|
||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
@@ -39,10 +40,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
// Spells are not undoable
|
||||
sa.setUndoable(sa.isAbility() && sa.isUndoable() && tgtPlayers.size() < 2 && !sa.hasParam("ActivationLimit"));
|
||||
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
final Game game = activator.getGame();
|
||||
|
||||
if (optional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantAddMana"), null)) {
|
||||
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantAddMana"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,24 +17,25 @@ public class ManifestDreadEffect extends ManifestEffect {
|
||||
final Game game = p.getGame();
|
||||
for (int i = 0; i < amount; i++) {
|
||||
CardCollection tgtCards = p.getTopXCardsFromLibrary(2);
|
||||
Card manifest = null;
|
||||
Card toGrave = null;
|
||||
CardCollection toGrave = new CardCollection();
|
||||
if (!tgtCards.isEmpty()) {
|
||||
manifest = p.getController().chooseSingleEntityForEffect(tgtCards, sa, getDefaultMessage(), null);
|
||||
Card manifest = p.getController().chooseSingleEntityForEffect(tgtCards, sa, getDefaultMessage(), null);
|
||||
tgtCards.remove(manifest);
|
||||
toGrave = tgtCards.isEmpty() ? null : tgtCards.getFirst();
|
||||
|
||||
// CR 701.34d If an effect instructs a player to manifest multiple cards from their library, those cards are manifested one at a time.
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
|
||||
internalEffect(manifest, p, sa, moveParams);
|
||||
if (toGrave != null) {
|
||||
toGrave = game.getAction().moveToGraveyard(toGrave, sa, moveParams);
|
||||
manifest = internalEffect(manifest, p, sa, moveParams);
|
||||
// CR 701.60a
|
||||
if (!manifest.isManifested()) {
|
||||
tgtCards.add(manifest);
|
||||
}
|
||||
for (Card c : tgtCards) {
|
||||
toGrave.add(game.getAction().moveToGraveyard(c, sa, moveParams));
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(p);
|
||||
runParams.put(AbilityKey.Card, toGrave);
|
||||
runParams.put(AbilityKey.Cards, toGrave);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ManifestDread, runParams, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
tgtC.addChangedCardKeywords(kws, null, false, timestamp, null);
|
||||
}
|
||||
if (redrawPT) {
|
||||
tgtC.updatePowerToughnessForView();
|
||||
tgtC.updatePTforView();
|
||||
}
|
||||
|
||||
if (!hiddenkws.isEmpty()) {
|
||||
@@ -93,7 +93,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
tgtC.removeChangedCardKeywords(timestamp, 0);
|
||||
tgtC.removeHiddenExtrinsicKeywords(timestamp, 0);
|
||||
|
||||
tgtC.updatePowerToughnessForView();
|
||||
tgtC.updatePTforView();
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtC));
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
gameCard.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
|
||||
}
|
||||
if (redrawPT) {
|
||||
gameCard.updatePowerToughnessForView();
|
||||
gameCard.updatePTforView();
|
||||
}
|
||||
|
||||
if (sa.hasParam("CanBlockAny")) {
|
||||
@@ -120,7 +120,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
gameCard.removeHiddenExtrinsicKeywords(timestamp, 0);
|
||||
gameCard.removeChangedCardKeywords(timestamp, 0);
|
||||
}
|
||||
gameCard.updatePowerToughnessForView();
|
||||
gameCard.updatePTforView();
|
||||
if (updateText) {
|
||||
gameCard.updateAbilityTextForView();
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ public class UnattachAllEffect extends SpellAbilityEffect {
|
||||
|
||||
// If Cast Targets will be checked on the Stack
|
||||
for (GameEntity ge : getTargetEntities(sa)) {
|
||||
if (ge instanceof Card) {
|
||||
Card gc = (Card) ge;
|
||||
if (ge instanceof Card gc) {
|
||||
// check if the object is still in game or if it was moved
|
||||
Card gameCard = game.getCardState(gc, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -214,8 +214,8 @@ public class CardCopyService {
|
||||
if (cachedCard != null) {
|
||||
return cachedCard;
|
||||
}
|
||||
String msg = "CardUtil:getLKICopy copy object";
|
||||
|
||||
String msg = "CardUtil:getLKICopy copy object";
|
||||
Breadcrumb bread = new Breadcrumb(msg);
|
||||
bread.setData("Card", copyFrom.getName());
|
||||
bread.setData("CardState", copyFrom.getCurrentStateName().toString());
|
||||
@@ -304,19 +304,19 @@ public class CardCopyService {
|
||||
|
||||
newCopy.setCounters(Maps.newHashMap(copyFrom.getCounters()));
|
||||
|
||||
newCopy.setColor(copyFrom.getColor().getColor());
|
||||
newCopy.setPhasedOut(copyFrom.getPhasedOut());
|
||||
newCopy.setTapped(copyFrom.isTapped());
|
||||
newCopy.setTributed(copyFrom.isTributed());
|
||||
newCopy.setMonstrous(copyFrom.isMonstrous());
|
||||
newCopy.setRenowned(copyFrom.isRenowned());
|
||||
newCopy.setSolved(copyFrom.isSolved());
|
||||
newCopy.setSaddled(copyFrom.isSaddled());
|
||||
newCopy.setPromisedGift(copyFrom.getPromisedGift());
|
||||
newCopy.setSaddled(copyFrom.isSaddled());
|
||||
if (newCopy.isSaddled()) newCopy.setSaddledByThisTurn(copyFrom.getSaddledByThisTurn());
|
||||
newCopy.setSuspectedTimestamp(copyFrom.getSuspectedTimestamp());
|
||||
|
||||
newCopy.setColor(copyFrom.getColor().getColor());
|
||||
newCopy.setPhasedOut(copyFrom.getPhasedOut());
|
||||
|
||||
newCopy.setTapped(copyFrom.isTapped());
|
||||
if (copyFrom.isSuspected()) {
|
||||
newCopy.setSuspectedEffect(getLKICopy(copyFrom.getSuspectedEffect(), cachedMap));
|
||||
}
|
||||
|
||||
newCopy.setDamageHistory(copyFrom.getDamageHistory());
|
||||
newCopy.setDamageReceivedThisTurn(copyFrom.getDamageReceivedThisTurn());
|
||||
@@ -352,8 +352,6 @@ public class CardCopyService {
|
||||
}
|
||||
newCopy.setChosenEvenOdd(copyFrom.getChosenEvenOdd());
|
||||
|
||||
//newCopy.getEtbCounters().putAll(copyFrom.getEtbCounters());
|
||||
|
||||
newCopy.setUnearthed(copyFrom.isUnearthed());
|
||||
|
||||
newCopy.copyFrom(copyFrom);
|
||||
|
||||
@@ -1835,15 +1835,6 @@ public class CardFactoryUtil {
|
||||
squadTrigger.setOverridingAbility(squadAbility);
|
||||
squadTrigger.setSVar("SquadAmount", "Count$OptionalKeywordAmount");
|
||||
inst.addTrigger(squadTrigger);
|
||||
} else if (keyword.equals("Start your engines")) {
|
||||
final String trig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | CheckDefinedPlayer$ " +
|
||||
"You.NoSpeed | TriggerDescription$ Start your engines! (" + inst.getReminderText() + ")";
|
||||
final String effect = "DB$ ChangeSpeed";
|
||||
|
||||
final Trigger trigger = TriggerHandler.parseTrigger(trig, card, intrinsic);
|
||||
trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.equals("Storm")) {
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True"
|
||||
+ "| TriggerDescription$ Storm (" + inst.getReminderText() + ")";
|
||||
@@ -3773,6 +3764,8 @@ public class CardFactoryUtil {
|
||||
desc = "Basic land";
|
||||
} else if (type.equals("Land.Artifact")) {
|
||||
desc = "Artifact land";
|
||||
} else if (type.startsWith("Card.with")) {
|
||||
desc = type.substring(9);
|
||||
}
|
||||
|
||||
sb.append(" Discard<1/CARDNAME> | ActivationZone$ Hand | PrecostDesc$ ").append(desc).append("cycling ");
|
||||
@@ -3793,17 +3786,20 @@ public class CardFactoryUtil {
|
||||
if (keyword.startsWith("Affinity")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String t = k[1];
|
||||
String d = "";
|
||||
if (k.length > 2) {
|
||||
final StringBuilder s = new StringBuilder();
|
||||
s.append(k[2]).append("s");
|
||||
d = s.toString();
|
||||
}
|
||||
|
||||
String desc = "Artifact".equals(t) ? "artifacts" : CardType.getPluralType(t);
|
||||
if (!d.isEmpty()) {
|
||||
desc = d;
|
||||
String desc;
|
||||
if(k.length > 2) {
|
||||
String typeText = k[2];
|
||||
if(typeText.contains(" with "))
|
||||
desc = typeText.substring(typeText.indexOf(" with ") + 6);
|
||||
else
|
||||
desc = typeText + "s";
|
||||
}
|
||||
else if ("Artifact".equals(t))
|
||||
desc = "artifacts";
|
||||
else
|
||||
desc = CardType.getPluralType(t);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ AffinityX | EffectZone$ All");
|
||||
sb.append("| Description$ Affinity for ").append(desc);
|
||||
|
||||
@@ -491,6 +491,7 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void updateCurrentRoom(Card c) {
|
||||
set(TrackableProperty.CurrentRoom, c.getCurrentRoom());
|
||||
updateMarkerText(c);
|
||||
}
|
||||
|
||||
public int getIntensity() {
|
||||
@@ -514,6 +515,7 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void updateClassLevel(Card c) {
|
||||
set(TrackableProperty.ClassLevel, c.getClassLevel());
|
||||
updateMarkerText(c);
|
||||
}
|
||||
|
||||
public int getRingLevel() {
|
||||
@@ -525,6 +527,41 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.RingLevel, p.getNumRingTemptedYou());
|
||||
}
|
||||
|
||||
public String getOverlayText() {
|
||||
return get(TrackableProperty.OverlayText);
|
||||
}
|
||||
public List<String> getMarkerText() {
|
||||
return get(TrackableProperty.MarkerText);
|
||||
}
|
||||
void updateMarkerText(Card c) {
|
||||
List<String> markerItems = new ArrayList<>();
|
||||
if(c.getCurrentRoom() != null && !c.getCurrentRoom().isEmpty()) {
|
||||
markerItems.add("In Room:");
|
||||
markerItems.add(c.getCurrentRoom());
|
||||
}
|
||||
if(c.isClassCard() && c.isInZone(ZoneType.Battlefield)) {
|
||||
markerItems.add("CL:" + c.getClassLevel());
|
||||
}
|
||||
if(getRingLevel() > 0) {
|
||||
markerItems.add("RL:" + getRingLevel());
|
||||
}
|
||||
|
||||
if(StringUtils.isNotEmpty(c.getOverlayText())) {
|
||||
set(TrackableProperty.OverlayText, c.getOverlayText());
|
||||
markerItems.add(c.getOverlayText());
|
||||
}
|
||||
else {
|
||||
//Overlay text is any custom string. It gets mixed in with the other marker lines, but it also needs its
|
||||
//own property so that it can display in the card detail text.
|
||||
set(TrackableProperty.OverlayText, null);
|
||||
}
|
||||
|
||||
if(markerItems.isEmpty())
|
||||
set(TrackableProperty.MarkerText, null);
|
||||
else
|
||||
set(TrackableProperty.MarkerText, markerItems);
|
||||
}
|
||||
|
||||
private String getRemembered() {
|
||||
return get(TrackableProperty.Remembered);
|
||||
}
|
||||
@@ -974,6 +1011,7 @@ public class CardView extends GameEntityView {
|
||||
updateDamage(c);
|
||||
updateSpecialize(c);
|
||||
updateRingLevel(c);
|
||||
updateMarkerText(c);
|
||||
|
||||
if (c.getIntensity(false) > 0) {
|
||||
updateIntensity(c);
|
||||
@@ -1603,7 +1641,9 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void updateKeywords(Card c, CardState state) {
|
||||
c.updateKeywordsCache(state);
|
||||
// deeper check for Idris
|
||||
set(TrackableProperty.HasAnnihilator, c.hasKeyword(Keyword.ANNIHILATOR, state) || state.getTriggers().anyMatch(t -> t.isKeyword(Keyword.ANNIHILATOR)));
|
||||
set(TrackableProperty.HasWard, c.hasKeyword(Keyword.WARD, state) || state.getTriggers().anyMatch(t -> t.isKeyword(Keyword.WARD)));
|
||||
set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state));
|
||||
set(TrackableProperty.HasToxic, c.hasKeyword(Keyword.TOXIC, state));
|
||||
set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state));
|
||||
@@ -1617,7 +1657,6 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.HasFear, c.hasKeyword(Keyword.FEAR, state));
|
||||
set(TrackableProperty.HasHexproof, c.hasKeyword(Keyword.HEXPROOF, state));
|
||||
set(TrackableProperty.HasHorsemanship, c.hasKeyword(Keyword.HORSEMANSHIP, state));
|
||||
set(TrackableProperty.HasWard, c.hasKeyword(Keyword.WARD, state) || state.getTriggers().anyMatch(t -> t.isKeyword(Keyword.WARD)));
|
||||
set(TrackableProperty.HasWither, c.hasKeyword(Keyword.WITHER, state));
|
||||
set(TrackableProperty.HasIndestructible, c.hasKeyword(Keyword.INDESTRUCTIBLE, state));
|
||||
set(TrackableProperty.HasIntimidate, c.hasKeyword(Keyword.INTIMIDATE, state));
|
||||
|
||||
@@ -95,8 +95,6 @@ public enum CounterEnumType {
|
||||
|
||||
CORRUPTION("CRPTN", 210, 121, 210),
|
||||
|
||||
CRANK("CRANK!", 181, 148, 15),
|
||||
|
||||
CROAK("CROAK", 155, 255, 5),
|
||||
|
||||
CREDIT("CRDIT", 188, 197, 234),
|
||||
|
||||
@@ -648,8 +648,7 @@ public class Combat {
|
||||
missingCombatants.add(c);
|
||||
}
|
||||
}
|
||||
if (ee.getKey() instanceof Card) {
|
||||
Card c = (Card) ee.getKey();
|
||||
if (ee.getKey() instanceof Card c) {
|
||||
if (!c.isBattle() && !c.isPlaneswalker()) {
|
||||
missingCombatants.add(c);
|
||||
}
|
||||
|
||||
@@ -374,7 +374,6 @@ public class CombatUtil {
|
||||
final GameEntity defender = combat.getDefenderByAttacker(c);
|
||||
final List<Card> otherAttackers = combat.getAttackers();
|
||||
|
||||
// Run triggers
|
||||
if (triggers) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Attacker, c);
|
||||
|
||||
@@ -547,18 +547,6 @@ public class CostAdjustment {
|
||||
if (!sa.isActivatedAbility() || sa.isReplacementAbility()) {
|
||||
return false;
|
||||
}
|
||||
if (st.hasParam("OnlyFirstActivation")) {
|
||||
int times = 0;
|
||||
for (IndividualCostPaymentInstance i : game.costPaymentStack) {
|
||||
SpellAbility paymentSa = i.getPayment().getAbility();
|
||||
if (paymentSa.isActivatedAbility() && st.matchesValidParam("ValidCard", paymentSa.getHostCard())) {
|
||||
times++;
|
||||
if (times > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "Foretell" -> {
|
||||
if (!sa.isForetelling()) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package forge.game.event;
|
||||
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventSpeedChanged extends GameEvent {
|
||||
|
||||
public final Player player;
|
||||
public final int oldValue;
|
||||
public final int newValue;
|
||||
|
||||
public GameEventSpeedChanged(Player affected, int oldValue, int newValue) {
|
||||
player = affected;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package forge.game.event;
|
||||
|
||||
public class GameEventSpeedUp extends GameEvent {
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public interface IGameEventVisitor<T> {
|
||||
T visit(GameEventRollDie event);
|
||||
T visit(GameEventScry event);
|
||||
T visit(GameEventShuffle event);
|
||||
T visit(GameEventSpeedUp event);
|
||||
T visit(GameEventSpeedChanged event);
|
||||
T visit(GameEventSpellAbilityCast event);
|
||||
T visit(GameEventSpellResolved event);
|
||||
T visit(GameEventSpellRemovedFromStack event);
|
||||
@@ -101,7 +101,7 @@ public interface IGameEventVisitor<T> {
|
||||
public T visit(GameEventRollDie event) { return null; }
|
||||
public T visit(GameEventScry event) { return null; }
|
||||
public T visit(GameEventShuffle event) { return null; }
|
||||
public T visit(GameEventSpeedUp event) { return null; }
|
||||
public T visit(GameEventSpeedChanged event) { return null; }
|
||||
public T visit(GameEventSpellResolved event) { return null; }
|
||||
public T visit(GameEventSpellAbilityCast event) { return null; }
|
||||
public T visit(GameEventSpellRemovedFromStack event) { return null; }
|
||||
|
||||
@@ -26,6 +26,8 @@ public class KeywordWithCostAndType extends KeywordInstance<KeywordWithCostAndTy
|
||||
n[i] = "basic land";
|
||||
} else if (n[i].equals("Land.Artifact")) {
|
||||
n[i] = "artifact land";
|
||||
} else if (n[i].startsWith("Card.with")) {
|
||||
n[i] = n[i].substring(9);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityNoCleanupDamage;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
@@ -180,8 +181,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
playerTurn.incrementTurn();
|
||||
|
||||
game.getAction().resetActivationsPerTurn();
|
||||
|
||||
final int lands = CardLists.count(playerTurn.getLandsInPlay(), CardPredicates.UNTAPPED);
|
||||
playerTurn.setNumPowerSurgeLands(lands);
|
||||
}
|
||||
@@ -395,7 +394,10 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
// Rule 514.2
|
||||
// Reset Damage received map
|
||||
for (final Card c : game.getCardsIncludePhasingIn(ZoneType.Battlefield)) {
|
||||
c.onCleanupPhase(playerTurn);
|
||||
if (!StaticAbilityNoCleanupDamage.damageNotRemoved(c)) {
|
||||
c.setDamage(0);
|
||||
}
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
}
|
||||
|
||||
game.getEndOfTurn().executeUntil();
|
||||
|
||||
@@ -112,6 +112,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private int expentThisTurn;
|
||||
private int numLibrarySearchedOwn; //The number of times this player has searched his library
|
||||
private int venturedThisTurn;
|
||||
private int attractionsVisitedThisTurn;
|
||||
private int descended;
|
||||
private int numRingTemptedYou;
|
||||
private int devotionMod;
|
||||
@@ -189,7 +190,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private final Map<Card, Integer> commanderCast = Maps.newHashMap();
|
||||
private final Map<Card, Integer> commanderDamage = Maps.newHashMap();
|
||||
private DetachedCardEffect commanderEffect = null;
|
||||
private DetachedCardEffect speedEffect;
|
||||
|
||||
private Card monarchEffect;
|
||||
private Card initiativeEffect;
|
||||
@@ -197,6 +197,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private Card contraptionSprocketEffect;
|
||||
private Card radiationEffect;
|
||||
private Card keywordEffect;
|
||||
private Card speedEffect;
|
||||
|
||||
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
|
||||
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
|
||||
@@ -237,10 +238,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return achievementTracker;
|
||||
}
|
||||
|
||||
public final PlayerOutcome getOutcome() {
|
||||
return stats.getOutcome();
|
||||
}
|
||||
|
||||
private String chooseName(String originalName) {
|
||||
String nameCandidate = originalName;
|
||||
for (int i = 2; i <= 8; i++) { // several tries, not matter how many
|
||||
@@ -1333,6 +1330,25 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return drawn;
|
||||
}
|
||||
|
||||
public final void resetNumDrawnThisDrawStep() {
|
||||
numDrawnThisDrawStep = 0;
|
||||
}
|
||||
|
||||
public final void resetNumDrawnThisTurn() {
|
||||
numDrawnThisTurn = 0;
|
||||
view.updateNumDrawnThisTurn(this);
|
||||
}
|
||||
|
||||
public final int getNumDrawnThisTurn() {
|
||||
return numDrawnThisTurn;
|
||||
}
|
||||
public final int getNumDrawnLastTurn() {
|
||||
return numDrawnLastTurn;
|
||||
}
|
||||
public final int numDrawnThisDrawStep() {
|
||||
return numDrawnThisDrawStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PlayerZone corresponding to the given zone of game.
|
||||
*/
|
||||
@@ -1360,7 +1376,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final CardCollectionView getCardsIn(final ZoneType zoneType) {
|
||||
return getCardsIn(zoneType, true);
|
||||
}
|
||||
@@ -1448,35 +1463,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return CardCollection.combine(getCardsIn(Player.ALL_ZONES), getCardsIn(ZoneType.Stack), inboundTokens);
|
||||
}
|
||||
|
||||
public final void resetNumDrawnThisDrawStep() {
|
||||
numDrawnThisDrawStep = 0;
|
||||
}
|
||||
|
||||
public final void resetNumDrawnThisTurn() {
|
||||
numDrawnThisTurn = 0;
|
||||
view.updateNumDrawnThisTurn(this);
|
||||
}
|
||||
|
||||
public final int getNumDrawnThisTurn() {
|
||||
return numDrawnThisTurn;
|
||||
}
|
||||
|
||||
public final int getNumDrawnLastTurn() {
|
||||
return numDrawnLastTurn;
|
||||
}
|
||||
|
||||
public final int numDrawnThisDrawStep() {
|
||||
return numDrawnThisDrawStep;
|
||||
}
|
||||
|
||||
public final void resetNumRollsThisTurn() {
|
||||
numRollsThisTurn = 0;
|
||||
}
|
||||
|
||||
public final int getNumRollsThisTurn() {
|
||||
return numRollsThisTurn;
|
||||
}
|
||||
|
||||
public void roll() {
|
||||
numRollsThisTurn++;
|
||||
}
|
||||
@@ -1717,15 +1709,16 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause);
|
||||
game.updateLastStateForCard(c);
|
||||
|
||||
// play a sound
|
||||
game.fireEvent(new GameEventLandPlayed(this, land));
|
||||
|
||||
// Run triggers
|
||||
runParams.put(AbilityKey.SpellAbility, cause);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false);
|
||||
|
||||
game.getStack().unfreezeStack();
|
||||
addLandPlayedThisTurn();
|
||||
|
||||
// play a sound
|
||||
game.fireEvent(new GameEventLandPlayed(this, land));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1878,6 +1871,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
stats.nextTurn();
|
||||
}
|
||||
|
||||
public final int getLastTurnNr() {
|
||||
return this.lastTurnNr;
|
||||
}
|
||||
|
||||
public boolean hasTappedLandForManaThisTurn() {
|
||||
return tappedLandForManaThisTurn;
|
||||
}
|
||||
@@ -1949,35 +1946,23 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
completedDungeons.clear();
|
||||
}
|
||||
|
||||
public final int getNumRingTemptedYou() {
|
||||
return numRingTemptedYou;
|
||||
}
|
||||
public final void incrementRingTemptedYou() {
|
||||
numRingTemptedYou++;
|
||||
}
|
||||
public final void setNumRingTemptedYou(int value) {
|
||||
numRingTemptedYou = value;
|
||||
}
|
||||
public final void resetRingTemptedYou() {
|
||||
numRingTemptedYou = 0;
|
||||
}
|
||||
|
||||
public final int getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
public final void increaseSpeed() {
|
||||
if (speedEffect == null) createSpeedEffect();
|
||||
if (!maxSpeed()) { // can't increase past 4
|
||||
int old = speed;
|
||||
speed++;
|
||||
view.updateSpeed(this);
|
||||
game.fireEvent(new GameEventSpeedUp()); //play sound effect
|
||||
getGame().fireEvent(new GameEventSpeedChanged(this, old, speed)); //play sound effect
|
||||
updateSpeedEffect();
|
||||
}
|
||||
}
|
||||
public final void decreaseSpeed() {
|
||||
if (speed > 1) { // can't decrease speed below 1
|
||||
int old = speed;
|
||||
speed--;
|
||||
view.updateSpeed(this);
|
||||
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
|
||||
game.fireEvent(new GameEventSpeedChanged(this, old, speed));
|
||||
updateSpeedEffect();
|
||||
}
|
||||
}
|
||||
public final boolean noSpeed() {
|
||||
@@ -1988,24 +1973,62 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
public final void setSpeed(int i) { //just used for copy/save
|
||||
speed = i;
|
||||
if (speed > 0) view.updateSpeed(this);
|
||||
if(this.speedEffect != null)
|
||||
updateSpeedEffect();
|
||||
}
|
||||
public final void createSpeedEffect() {
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
DetachedCardEffect eff = new DetachedCardEffect(this, "Speed Effect");
|
||||
String trigger = "Mode$ LifeLost | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | " +
|
||||
"PlayerTurn$ True | TriggerDescription$ Your speed increases once on each of your turns when an " +
|
||||
"opponent loses life.";
|
||||
String speedUp = "DB$ ChangeSpeed";
|
||||
Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, eff, true);
|
||||
lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, eff));
|
||||
eff.addTrigger(lifeLostTrigger);
|
||||
this.speedEffect = eff;
|
||||
com.add(eff);
|
||||
}
|
||||
if(this.speedEffect != null || this.noSpeed())
|
||||
return;
|
||||
|
||||
public final List<Card> getPlaneswalkedToThisTurn() {
|
||||
return planeswalkedToThisTurn;
|
||||
speedEffect = new Card(game.nextCardId(), null, game);
|
||||
speedEffect.setOwner(this);
|
||||
speedEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
|
||||
speedEffect.addAlternateState(CardStateName.Flipped, false);
|
||||
CardState speedFront = speedEffect.getState(CardStateName.Original);
|
||||
CardState speedBack = speedEffect.getState(CardStateName.Flipped);
|
||||
|
||||
speedFront.setImageKey("t:speed");
|
||||
speedFront.setName("Start Your Engines!");
|
||||
|
||||
speedBack.setImageKey("t:max_speed");
|
||||
speedBack.setName("Max Speed!");
|
||||
|
||||
String label = Localizer.getInstance().getMessage("lblSpeed", this.speed);
|
||||
speedEffect.setOverlayText(label);
|
||||
|
||||
// 702.179d There is an inherent triggered ability associated with a player having 1 or more speed. This ability has no source and is controlled by that player.
|
||||
// That ability is “Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.”
|
||||
String trigger = "Mode$ LifeLostAll | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | " +
|
||||
"PlayerTurn$ True | CheckSVar$ Count$YourSpeed | SVarCompare$ LT4 | "
|
||||
+ "TriggerDescription$ Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.";
|
||||
String speedUp = "DB$ ChangeSpeed";
|
||||
Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, speedEffect, true);
|
||||
lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, speedEffect));
|
||||
speedFront.addTrigger(lifeLostTrigger);
|
||||
|
||||
speedEffect.updateStateForView();
|
||||
|
||||
if(this.maxSpeed())
|
||||
speedEffect.setState(CardStateName.Flipped, true);
|
||||
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
com.add(speedEffect);
|
||||
this.updateZoneForView(com);
|
||||
}
|
||||
protected void updateSpeedEffect() {
|
||||
if(this.speedEffect == null) {
|
||||
if(this.noSpeed())
|
||||
return;
|
||||
createSpeedEffect();
|
||||
}
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
String label = this.maxSpeed() ? localizer.getMessage("lblMaxSpeed") : localizer.getMessage("lblSpeed", this.speed);
|
||||
speedEffect.setOverlayText(label);
|
||||
if(maxSpeed() && speedEffect.getCurrentStateName() == CardStateName.Original)
|
||||
speedEffect.setState(CardStateName.Flipped, true);
|
||||
else if(!maxSpeed() && speedEffect.getCurrentStateName() == CardStateName.Flipped)
|
||||
speedEffect.setState(CardStateName.Original, true);
|
||||
}
|
||||
|
||||
public final void altWinBySpellEffect(final String sourceName) {
|
||||
@@ -2285,6 +2308,13 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
view.updateUnlimitedHandSize(this);
|
||||
}
|
||||
|
||||
public int getStartingHandSize() {
|
||||
return startingHandSize;
|
||||
}
|
||||
public void setStartingHandSize(int shs) {
|
||||
startingHandSize = shs;
|
||||
}
|
||||
|
||||
public final int getLandsPlayedThisTurn() {
|
||||
return landsPlayedThisTurn;
|
||||
}
|
||||
@@ -2461,6 +2491,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return game.getMatch().getPlayers().get(game.getRegisteredPlayers().indexOf(this));
|
||||
}
|
||||
|
||||
public final PlayerOutcome getOutcome() {
|
||||
return stats.getOutcome();
|
||||
}
|
||||
private void setOutcome(PlayerOutcome outcome) {
|
||||
stats.setOutcome(outcome);
|
||||
}
|
||||
@@ -2519,10 +2552,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return keywords.getAmount(k);
|
||||
}
|
||||
|
||||
public final int getLastTurnNr() {
|
||||
return this.lastTurnNr;
|
||||
}
|
||||
|
||||
public void onCleanupPhase() {
|
||||
for (Card c : getCardsIn(ZoneType.Hand)) {
|
||||
c.setDrawnThisTurn(false);
|
||||
@@ -2561,6 +2590,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
setCommitedCrimeThisTurn(0);
|
||||
diceRollsThisTurn = Lists.newArrayList();
|
||||
setExpentThisTurn(0);
|
||||
attractionsVisitedThisTurn = 0;
|
||||
|
||||
damageReceivedThisTurn.clear();
|
||||
planeswalkedToThisTurn.clear();
|
||||
@@ -2693,11 +2723,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return !isInGame();
|
||||
}
|
||||
|
||||
public int getStartingHandSize() {
|
||||
return startingHandSize;
|
||||
}
|
||||
public void setStartingHandSize(int shs) {
|
||||
startingHandSize = shs;
|
||||
public final List<Card> getPlaneswalkedToThisTurn() {
|
||||
return planeswalkedToThisTurn;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3386,6 +3413,19 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
getTheRing().updateStateForView();
|
||||
}
|
||||
|
||||
public final int getNumRingTemptedYou() {
|
||||
return numRingTemptedYou;
|
||||
}
|
||||
public final void incrementRingTemptedYou() {
|
||||
numRingTemptedYou++;
|
||||
}
|
||||
public final void setNumRingTemptedYou(int value) {
|
||||
numRingTemptedYou = value;
|
||||
}
|
||||
public final void resetRingTemptedYou() {
|
||||
numRingTemptedYou = 0;
|
||||
}
|
||||
|
||||
public void changeOwnership(Card card) {
|
||||
// If lost then gained, just clear out of lost.
|
||||
// If gained then lost, just clear out of gained.
|
||||
@@ -3610,12 +3650,31 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return radiationEffect != null;
|
||||
}
|
||||
|
||||
public Card getKeywordCard() {
|
||||
if (keywordEffect != null) {
|
||||
return keywordEffect;
|
||||
}
|
||||
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
|
||||
keywordEffect = new Card(game.nextCardId(), null, game);
|
||||
keywordEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
keywordEffect.setOwner(this);
|
||||
keywordEffect.setName("Keyword Effects");
|
||||
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
|
||||
|
||||
keywordEffect.updateStateForView();
|
||||
|
||||
com.add(keywordEffect);
|
||||
|
||||
this.updateZoneForView(com);
|
||||
return keywordEffect;
|
||||
}
|
||||
public void updateKeywordCardAbilityText() {
|
||||
if (getKeywordCard() == null)
|
||||
return;
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
keywordEffect.setText("");
|
||||
keywordEffect.updateAbilityTextForView();
|
||||
boolean headerAdded = false;
|
||||
StringBuilder kw = new StringBuilder();
|
||||
for (KeywordInterface k : keywords) {
|
||||
@@ -3627,8 +3686,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
if (!kw.toString().isEmpty()) {
|
||||
keywordEffect.setText(trimKeywords(kw.toString()));
|
||||
keywordEffect.updateAbilityTextForView();
|
||||
}
|
||||
keywordEffect.updateAbilityTextForView();
|
||||
this.updateZoneForView(com);
|
||||
}
|
||||
public String trimKeywords(String keywordTexts) {
|
||||
@@ -3686,6 +3745,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
blessingEffect.updateStateForView();
|
||||
|
||||
com.add(blessingEffect);
|
||||
|
||||
// 702.131d. After a player gets the city's blessing, continuous effects are reapplied
|
||||
game.getAction().checkStaticAbilities();
|
||||
} else {
|
||||
com.remove(blessingEffect);
|
||||
blessingEffect = null;
|
||||
@@ -3732,27 +3794,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|| !hasKeyword("Spells and abilities you control can't cause you to search your library.");
|
||||
}
|
||||
|
||||
public Card getKeywordCard() {
|
||||
if (keywordEffect != null) {
|
||||
return keywordEffect;
|
||||
}
|
||||
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
|
||||
keywordEffect = new Card(game.nextCardId(), null, game);
|
||||
keywordEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
keywordEffect.setOwner(this);
|
||||
keywordEffect.setName("Keyword Effects");
|
||||
keywordEffect.setImageKey(ImageKeys.HIDDEN_CARD);
|
||||
|
||||
keywordEffect.updateStateForView();
|
||||
|
||||
com.add(keywordEffect);
|
||||
|
||||
this.updateZoneForView(com);
|
||||
return keywordEffect;
|
||||
}
|
||||
|
||||
public void addAdditionalVote(long timestamp, int value) {
|
||||
additionalVotes.put(timestamp, value);
|
||||
getView().updateAdditionalVote(this);
|
||||
@@ -3943,7 +3984,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CommitCrime, runParams, false);
|
||||
|
||||
}
|
||||
|
||||
public int getCommittedCrimeThisTurn() {
|
||||
@@ -3970,12 +4010,17 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void visitAttractions(int light) {
|
||||
CardCollection attractions = CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isAttractionWithLight(light));
|
||||
for (Card c : attractions) {
|
||||
if(!c.wasVisitedThisTurn())
|
||||
this.attractionsVisitedThisTurn++;
|
||||
c.visitAttraction(this);
|
||||
}
|
||||
}
|
||||
public void rollToVisitAttractions() {
|
||||
this.visitAttractions(RollDiceEffect.rollDiceForPlayerToVisitAttractions(this));
|
||||
}
|
||||
public int getAttractionsVisitedThisTurn() {
|
||||
return this.attractionsVisitedThisTurn;
|
||||
}
|
||||
|
||||
public int getCrankCounter() {
|
||||
return this.crankCounter;
|
||||
@@ -3983,8 +4028,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void setCrankCounter(int counters) {
|
||||
this.crankCounter = counters;
|
||||
if (this.contraptionSprocketEffect != null) {
|
||||
Map<CounterType, Integer> counterMap = Map.of(CounterType.get(CounterEnumType.CRANK), this.crankCounter);
|
||||
contraptionSprocketEffect.setCounters(counterMap);
|
||||
String label = Localizer.getInstance().getMessage("lblCrank", this.crankCounter);
|
||||
contraptionSprocketEffect.setOverlayText(label);
|
||||
}
|
||||
else if (this.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.CONTRAPTIONS)) {
|
||||
this.createContraptionSprockets();
|
||||
@@ -4010,12 +4055,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
contraptionSprocketEffect.setName("Contraption Sprockets");
|
||||
contraptionSprocketEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
|
||||
//Add "counters" on the effect to represent the current CRANK counter position.
|
||||
//This and some other un-cards could benefit from a distinct system for positional counters or markers,
|
||||
//see for instance Baron von Count or B-I-N-G-O. For now this is sufficient to display the current sprocket
|
||||
//on the counter overlay, and I don't think any existing effect will notice it.
|
||||
Map<CounterType, Integer> counterMap = Map.of(CounterType.get(CounterEnumType.CRANK), this.crankCounter);
|
||||
contraptionSprocketEffect.setCounters(counterMap);
|
||||
String label = Localizer.getInstance().getMessage("lblCrank", this.crankCounter);
|
||||
contraptionSprocketEffect.setOverlayText(label);
|
||||
contraptionSprocketEffect.setText("At the beginning of your upkeep, if you control a Contraption, move the CRANK! counter to the next sprocket and crank any number of that sprocket's Contraptions.");
|
||||
|
||||
contraptionSprocketEffect.updateStateForView();
|
||||
@@ -4028,11 +4069,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void addDeclaresAttackers(long ts, Player p) {
|
||||
this.declaresAttackers.put(ts, p);
|
||||
}
|
||||
|
||||
public void removeDeclaresAttackers(long ts) {
|
||||
this.declaresAttackers.remove(ts);
|
||||
}
|
||||
|
||||
public Player getDeclaresAttackers() {
|
||||
Map.Entry<Long, Player> e = declaresAttackers.lastEntry();
|
||||
return e == null ? null : e.getValue();
|
||||
@@ -4041,11 +4080,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void addDeclaresBlockers(long ts, Player p) {
|
||||
this.declaresBlockers.put(ts, p);
|
||||
}
|
||||
|
||||
public void removeDeclaresBlockers(long ts) {
|
||||
this.declaresBlockers.remove(ts);
|
||||
}
|
||||
|
||||
public Player getDeclaresBlockers() {
|
||||
Map.Entry<Long, Player> e = declaresBlockers.lastEntry();
|
||||
return e == null ? null : e.getValue();
|
||||
|
||||
@@ -329,13 +329,6 @@ public class PlayerView extends GameEntityView {
|
||||
set(TrackableProperty.ControlVotes, val);
|
||||
}
|
||||
|
||||
public int getSpeed() {
|
||||
return get(TrackableProperty.Speed);
|
||||
}
|
||||
void updateSpeed(Player p) {
|
||||
set(TrackableProperty.Speed, p.getSpeed());
|
||||
}
|
||||
|
||||
public int getAdditionalVillainousChoices() {
|
||||
return get(TrackableProperty.AdditionalVillainousChoices);
|
||||
}
|
||||
@@ -604,10 +597,6 @@ public class PlayerView extends GameEntityView {
|
||||
}
|
||||
details.add(Localizer.getInstance().getMessage("lblExtraTurnCountHas", String.valueOf(getExtraTurnCount())));
|
||||
|
||||
if (getSpeed() > 0) {
|
||||
details.add(Localizer.getInstance().getMessage("lblSpeed", String.valueOf(getSpeed())));
|
||||
}
|
||||
|
||||
final String keywords = Lang.joinHomogenous(getDisplayableKeywords());
|
||||
if (!keywords.isEmpty()) {
|
||||
details.add(keywords);
|
||||
|
||||
@@ -322,16 +322,13 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
return this.triggersWhenSpent != null;
|
||||
}
|
||||
|
||||
public void addTriggersWhenSpent(SpellAbility saBeingPaid, Card card) {
|
||||
if (this.triggersWhenSpent == null)
|
||||
return;
|
||||
|
||||
TriggerHandler handler = card.getGame().getTriggerHandler();
|
||||
public void addTriggersWhenSpent(SpellAbility saBeingPaid) {
|
||||
Trigger trig = TriggerHandler.parseTrigger(sVarHolder.getSVar(this.triggersWhenSpent), sourceCard, false, sVarHolder);
|
||||
if (sVarHolder instanceof SpellAbility) {
|
||||
trig.setSpawningAbility((SpellAbility) sVarHolder);
|
||||
trig.addRemembered(saBeingPaid);
|
||||
if (getSourceSA() != null) {
|
||||
trig.setSpawningAbility(getSourceSA());
|
||||
}
|
||||
handler.registerOneTrigger(trig);
|
||||
saBeingPaid.getHostCard().getGame().getTriggerHandler().registerThisTurnDelayedTrigger(trig);
|
||||
}
|
||||
|
||||
public SpellAbility getSourceSA() {
|
||||
|
||||
@@ -70,6 +70,7 @@ import forge.game.staticability.StaticAbilityCastWithFlash;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
//only SpellAbility can go on the stack
|
||||
@@ -837,7 +838,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
|
||||
for (Mana mana : getPayingMana()) {
|
||||
if (mana.triggersWhenSpent()) {
|
||||
mana.getManaAbility().addTriggersWhenSpent(this, host);
|
||||
mana.getManaAbility().addTriggersWhenSpent(this);
|
||||
}
|
||||
|
||||
if (mana.addsCounters(this)) {
|
||||
@@ -1072,11 +1073,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
|
||||
public void rebuiltDescription() {
|
||||
|
||||
// SubAbilities don't have Costs or Cost descriptors
|
||||
|
||||
String sb = getCostDescription() +
|
||||
getParam("SpellDescription");
|
||||
String sb = getCostDescription() + getParam("SpellDescription");
|
||||
setDescription(sb);
|
||||
}
|
||||
|
||||
@@ -1536,8 +1535,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
|
||||
if (entity instanceof GameEntity) {
|
||||
GameEntity e = (GameEntity)entity;
|
||||
if (entity instanceof GameEntity e) {
|
||||
if (!e.isValid(tr.getValidTgts(), getActivatingPlayer(), getHostCard(), this)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1546,8 +1544,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
|
||||
if (entity instanceof Card) {
|
||||
final Card c = (Card) entity;
|
||||
if (entity instanceof Card c) {
|
||||
if (c.getZone() != null && !tr.getZone().contains(c.getZone().getZoneType())) {
|
||||
return false;
|
||||
}
|
||||
@@ -1991,7 +1988,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return targets;
|
||||
}
|
||||
|
||||
public boolean canTargetSpellAbility(final SpellAbility topSA) {
|
||||
public boolean canTargetSpellAbility(SpellAbility topSA) {
|
||||
if (topSA.isWrapper()) {
|
||||
topSA = ((WrappedAbility) topSA).getWrappedAbility();
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = getTargetRestrictions();
|
||||
|
||||
if (this.equals(topSA)) {
|
||||
|
||||
@@ -271,7 +271,6 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
if (this.isRevolt() && !activator.hasRevolt()) return false;
|
||||
if (this.isDesert() && !activator.hasDesert()) return false;
|
||||
if (this.isBlessing() && !activator.hasBlessing()) return false;
|
||||
if (this.isMaxSpeed() && !activator.maxSpeed()) return false;
|
||||
|
||||
if (this.kicked && !sa.isKicked()) return false;
|
||||
if (this.kicked1 && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) return false;
|
||||
|
||||
@@ -98,9 +98,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
if (value.equals("Solved")) {
|
||||
this.setSolved(true);
|
||||
}
|
||||
if (value.equals("MaxSpeed")) {
|
||||
this.setMaxSpeed(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("ActivationZone")) {
|
||||
@@ -443,9 +440,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isMaxSpeed()) {
|
||||
if (!activator.maxSpeed()) return false;
|
||||
}
|
||||
if (isBlessing()) {
|
||||
if (!activator.hasBlessing()) {
|
||||
return false;
|
||||
|
||||
@@ -96,7 +96,6 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
private boolean desert = false;
|
||||
private boolean blessing = false;
|
||||
private boolean solved = false;
|
||||
private boolean maxSpeed = false;
|
||||
|
||||
/** The s is present. */
|
||||
private String isPresent = null;
|
||||
@@ -347,7 +346,6 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
public void setDesert(final boolean bDesert) { desert = bDesert; }
|
||||
public void setBlessing(final boolean bBlessing) { blessing = bBlessing; }
|
||||
public void setSolved(final boolean bSolved) { solved = bSolved; }
|
||||
public void setMaxSpeed(final boolean b) { maxSpeed = b; }
|
||||
|
||||
/** Optional Costs */
|
||||
protected boolean kicked = false;
|
||||
@@ -540,8 +538,6 @@ public class SpellAbilityVariables implements Cloneable {
|
||||
|
||||
public final boolean isSolved() { return this.solved; }
|
||||
|
||||
public final boolean isMaxSpeed() { return this.maxSpeed; }
|
||||
|
||||
public String getNoDifferentColors() {
|
||||
return noDifferentColors;
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ public class TargetChoices extends ForwardingList<GameObject> implements Cloneab
|
||||
|
||||
public final boolean add(final GameObject o) {
|
||||
if (o instanceof Player || o instanceof Card || o instanceof SpellAbility) {
|
||||
if (o instanceof Card) {
|
||||
Card c = (Card) o;
|
||||
if (o instanceof Card c) {
|
||||
cardControllers.put(c, c.getController());
|
||||
}
|
||||
return super.add(o);
|
||||
|
||||
@@ -344,10 +344,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
if (zone == null || !this.validHostZones.contains(zone.getZoneType())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!getHostCard().isInPlay()) { // default
|
||||
return false;
|
||||
}
|
||||
} else if (!getHostCard().isInPlay()) { // default
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -452,29 +452,11 @@ public final class StaticAbilityContinuous {
|
||||
|
||||
if (layer == StaticAbilityLayer.COLOR) {
|
||||
if (params.containsKey("AddColor")) {
|
||||
final String colors = params.get("AddColor");
|
||||
if (colors.equals("ChosenColor")) {
|
||||
if (hostCard.hasChosenColor()) {
|
||||
addColors = ColorSet.fromNames(hostCard.getChosenColors());
|
||||
}
|
||||
} else if (colors.equals("All")) {
|
||||
addColors = ColorSet.ALL_COLORS;
|
||||
} else {
|
||||
addColors = ColorSet.fromNames(colors.split(" & "));
|
||||
}
|
||||
addColors = getColorsFromParam(stAb, params.get("AddColor"));
|
||||
}
|
||||
|
||||
if (params.containsKey("SetColor")) {
|
||||
final String colors = params.get("SetColor");
|
||||
if (colors.equals("ChosenColor")) {
|
||||
if (hostCard.hasChosenColor()) {
|
||||
addColors = ColorSet.fromNames(hostCard.getChosenColors());
|
||||
}
|
||||
} else if (colors.equals("All")) {
|
||||
addColors = ColorSet.ALL_COLORS;
|
||||
} else {
|
||||
addColors = ColorSet.fromNames(colors.split(" & "));
|
||||
}
|
||||
addColors = getColorsFromParam(stAb, params.get("SetColor"));
|
||||
overwriteColors = true;
|
||||
}
|
||||
}
|
||||
@@ -704,7 +686,7 @@ public final class StaticAbilityContinuous {
|
||||
setToughness = AbilityUtils.calculateAmount(affectedCard, setT, stAb, true);
|
||||
}
|
||||
affectedCard.addNewPT(setPower, setToughness,
|
||||
se.getTimestamp(), stAb.getId(), layer == StaticAbilityLayer.CHARACTERISTIC);
|
||||
se.getTimestamp(), stAb.getId(), layer == StaticAbilityLayer.CHARACTERISTIC, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,7 +734,8 @@ public final class StaticAbilityContinuous {
|
||||
}
|
||||
|
||||
affectedCard.addChangedCardKeywords(newKeywords, removeKeywords,
|
||||
removeAllAbilities, se.getTimestamp(), stAb, true);
|
||||
removeAllAbilities, se.getTimestamp(), stAb, false);
|
||||
affectedCard.updateKeywordsCache(affectedCard.getCurrentState());
|
||||
}
|
||||
|
||||
// add HIDDEN keywords
|
||||
@@ -862,7 +845,7 @@ public final class StaticAbilityContinuous {
|
||||
|| removeAllAbilities) {
|
||||
affectedCard.addChangedCardTraits(
|
||||
addedAbilities, null, addedTrigger, addedReplacementEffects, addedStaticAbility, removeAllAbilities, removeNonMana,
|
||||
se.getTimestamp(), stAb.getId()
|
||||
se.getTimestamp(), stAb.getId(), false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -874,7 +857,7 @@ public final class StaticAbilityContinuous {
|
||||
// add Types
|
||||
if ((addTypes != null && !addTypes.isEmpty()) || (removeTypes != null && !removeTypes.isEmpty()) || addAllCreatureTypes || !remove.isEmpty()) {
|
||||
affectedCard.addChangedCardTypes(addTypes, removeTypes, addAllCreatureTypes, remove,
|
||||
se.getTimestamp(), stAb.getId(), true, stAb.isCharacteristicDefining());
|
||||
se.getTimestamp(), stAb.getId(), false, stAb.isCharacteristicDefining());
|
||||
}
|
||||
|
||||
// add colors
|
||||
@@ -932,6 +915,21 @@ public final class StaticAbilityContinuous {
|
||||
return affectedCards;
|
||||
}
|
||||
|
||||
private static ColorSet getColorsFromParam(StaticAbility stAb, final String colors) {
|
||||
final Card hostCard = stAb.getHostCard();
|
||||
ColorSet addColors = null;
|
||||
if (colors.equals("ChosenColor")) {
|
||||
if (hostCard.hasChosenColor()) {
|
||||
addColors = ColorSet.fromNames(hostCard.getChosenColors());
|
||||
}
|
||||
} else if (colors.equals("All")) {
|
||||
addColors = ColorSet.ALL_COLORS;
|
||||
} else {
|
||||
addColors = ColorSet.fromNames(colors.split(" & "));
|
||||
}
|
||||
return addColors;
|
||||
}
|
||||
|
||||
private static void buildIgnorEffectAbility(final StaticAbility stAb, final String costString, final List<Player> players, final CardCollectionView cards) {
|
||||
final List<Player> validActivator = new ArrayList<>(players);
|
||||
for (final Card c : cards) {
|
||||
@@ -1031,10 +1029,18 @@ public final class StaticAbilityContinuous {
|
||||
// non - CharacteristicDefining
|
||||
CardCollection affectedCards = new CardCollection();
|
||||
|
||||
CardCollection definedCards = null;
|
||||
if (stAb.hasParam("AffectedDefined")) {
|
||||
definedCards = AbilityUtils.getDefinedCards(hostCard, stAb.getParam("AffectedDefined"), stAb).filter(CardPredicates.phasedIn());
|
||||
}
|
||||
|
||||
// add preList in addition to the normal affected cards
|
||||
// need to add before game cards to have preference over them
|
||||
if (!preList.isEmpty()) {
|
||||
if (stAb.hasParam("AffectedZone")) {
|
||||
if (stAb.hasParam("AffectedDefined")) {
|
||||
affectedCards.addAll(preList);
|
||||
affectedCards.retainAll(definedCards);
|
||||
} else if (stAb.hasParam("AffectedZone")) {
|
||||
affectedCards.addAll(CardLists.filter(preList, CardPredicates.inZone(
|
||||
ZoneType.listValueOf(stAb.getParam("AffectedZone")))));
|
||||
} else {
|
||||
@@ -1042,7 +1048,9 @@ public final class StaticAbilityContinuous {
|
||||
}
|
||||
}
|
||||
|
||||
if (stAb.hasParam("AffectedZone")) {
|
||||
if (stAb.hasParam("AffectedDefined")) {
|
||||
affectedCards.addAll(definedCards);
|
||||
} else if (stAb.hasParam("AffectedZone")) {
|
||||
affectedCards.addAll(game.getCardsIn(ZoneType.listValueOf(stAb.getParam("AffectedZone"))));
|
||||
} else {
|
||||
affectedCards.addAll(game.getCardsIn(ZoneType.Battlefield));
|
||||
|
||||
@@ -30,13 +30,11 @@ public class StaticAbilityMustAttack {
|
||||
if (stAb.hasParam("MustAttack")) {
|
||||
List<GameEntity> def = AbilityUtils.getDefinedEntities(stAb.getHostCard(), stAb.getParam("MustAttack"), stAb);
|
||||
for (GameEntity e : def) {
|
||||
if (e instanceof Player) {
|
||||
Player attackPl = (Player) e;
|
||||
if (e instanceof Player attackPl) {
|
||||
if (!game.getPhaseHandler().isPlayerTurn(attackPl)) { // CR 506.2
|
||||
entityList.add(e);
|
||||
}
|
||||
} else if (e instanceof Card) {
|
||||
Card attackPW = (Card) e;
|
||||
} else if (e instanceof Card attackPW) {
|
||||
if (!game.getPhaseHandler().isPlayerTurn(attackPW.getController())) { // CR 506.2
|
||||
entityList.add(e);
|
||||
}
|
||||
|
||||
@@ -56,9 +56,6 @@ public class TriggerAttackerBlockedByCreature extends Trigger {
|
||||
public final boolean performTest(final Map<AbilityKey, Object> runParams) {
|
||||
final Object a = runParams.get(AbilityKey.Attacker),
|
||||
b = runParams.get(AbilityKey.Blocker);
|
||||
if (!(a instanceof Card && b instanceof Card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card attacker = (Card) a,
|
||||
blocker = (Card) b;
|
||||
|
||||
@@ -113,8 +113,7 @@ public class TriggerDamageDone extends Trigger {
|
||||
final Object target = runParams.get(AbilityKey.DamageTarget);
|
||||
final Card source = (Card) runParams.get(AbilityKey.DamageSource);
|
||||
|
||||
if (target instanceof Player) {
|
||||
final Player trigTgt = (Player) target;
|
||||
if (target instanceof Player trigTgt) {
|
||||
if (!Expressions.compare(trigTgt.getAssignedDamage(null, source), operator, operand)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -384,17 +384,15 @@ public class TriggerHandler {
|
||||
return false; // It's not the right phase to go off.
|
||||
}
|
||||
|
||||
if (TriggerType.Always.equals(regtrig.getMode())) {
|
||||
if (game.getStack().hasStateTrigger(regtrig.getId())) {
|
||||
return false; // State triggers that are already on the stack
|
||||
// don't trigger again.
|
||||
}
|
||||
}
|
||||
|
||||
if (regtrig.isSuppressed()) {
|
||||
return false; // Trigger removed by effect
|
||||
}
|
||||
|
||||
if (TriggerType.Always.equals(regtrig.getMode()) && game.getStack().hasStateTrigger(regtrig.getId())) {
|
||||
return false; // State triggers that are already on the stack
|
||||
// don't trigger again.
|
||||
}
|
||||
|
||||
// do not check delayed
|
||||
if (regtrig.getSpawningAbility() == null && !regtrig.zonesCheck(game.getZoneOf(regtrig.getHostCard()))) {
|
||||
return false; // Host card isn't where it needs to be.
|
||||
@@ -415,6 +413,10 @@ public class TriggerHandler {
|
||||
return false; // Not the right mode.
|
||||
}
|
||||
|
||||
if (regtrig.isSuppressed()) {
|
||||
return false; // Trigger removed by effect
|
||||
}
|
||||
|
||||
/* this trigger can only be activated once per turn, verify it hasn't already run */
|
||||
if (!regtrig.checkActivationLimit()) {
|
||||
return false;
|
||||
@@ -431,21 +433,17 @@ public class TriggerHandler {
|
||||
if (!regtrig.performTest(runParams)) {
|
||||
return false; // Test failed.
|
||||
}
|
||||
if (regtrig.isSuppressed()) {
|
||||
return false; // Trigger removed by effect
|
||||
}
|
||||
|
||||
if (TriggerType.Always.equals(regtrig.getMode())) {
|
||||
if (game.getStack().hasStateTrigger(regtrig.getId())) {
|
||||
return false; // State triggers that are already on the stack
|
||||
// don't trigger again.
|
||||
}
|
||||
if (TriggerType.Always.equals(regtrig.getMode()) && game.getStack().hasStateTrigger(regtrig.getId())) {
|
||||
return false; // State triggers that are already on the stack
|
||||
// don't trigger again.
|
||||
}
|
||||
|
||||
// check if any static abilities are disabling the trigger (Torpor Orb and the like)
|
||||
if (!regtrig.isStatic() && StaticAbilityDisableTriggers.disabled(game, regtrig, runParams)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TriggerManifestDread extends Trigger {
|
||||
|
||||
@Override
|
||||
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
sa.setTriggeringObject(AbilityKey.NewCard, runParams.get(AbilityKey.Card));
|
||||
sa.setTriggeringObject(AbilityKey.Cards, runParams.get(AbilityKey.Cards));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +251,13 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (getSpawningAbility() != null && getSpawningAbility().hasParam("TriggersWhenSpent")) {
|
||||
if (!getTriggerRemembered().contains(spellAbility)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ package forge.game.trigger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.TreeBasedTable;
|
||||
|
||||
@@ -39,141 +36,6 @@ import forge.game.spellability.TargetRestrictions;
|
||||
// use of any of the methods)
|
||||
public class WrappedAbility extends Ability {
|
||||
|
||||
static Set<ApiType> noTimestampCheck = ImmutableSet.of(
|
||||
ApiType.Abandon, // no Triggered
|
||||
ApiType.ActivateAbility, // no Triggered
|
||||
ApiType.AddPhase, // only player
|
||||
ApiType.AddTurn, // only player
|
||||
ApiType.SkipPhase, // only player
|
||||
ApiType.SkipTurn, // only player
|
||||
ApiType.AlterAttribute, // updated
|
||||
|
||||
ApiType.Amass, // no Triggered only you
|
||||
ApiType.Ascend, // only player (you)
|
||||
|
||||
ApiType.Animate, // updated
|
||||
ApiType.AnimateAll, // no triggered
|
||||
|
||||
ApiType.Attach, // checked
|
||||
|
||||
ApiType.Balance, // only player
|
||||
|
||||
ApiType.BecomesBlocked, // no triggered
|
||||
ApiType.MustBlock, // updated
|
||||
ApiType.SwitchBlock, // no triggered
|
||||
|
||||
ApiType.BecomeMonarch, // only player
|
||||
ApiType.Bond, // updated
|
||||
|
||||
ApiType.BidLife, // only player
|
||||
ApiType.Clash, // only player
|
||||
|
||||
ApiType.Clone, // checked
|
||||
|
||||
ApiType.GenericChoice, // only player
|
||||
|
||||
ApiType.Connive, // no triggered
|
||||
ApiType.Encode, // no triggered
|
||||
ApiType.Meld, // no triggered
|
||||
ApiType.Haunt, // has own logic
|
||||
|
||||
ApiType.PutCounter,
|
||||
ApiType.PutCounterAll, // no triggered
|
||||
ApiType.MoveCounter,
|
||||
ApiType.MultiplyCounter,
|
||||
ApiType.MoveCounter,
|
||||
ApiType.RemoveCounter,
|
||||
ApiType.AddOrRemoveCounter,
|
||||
ApiType.MoveCounter,
|
||||
|
||||
ApiType.Draw, // only player
|
||||
ApiType.GainLife, // only player
|
||||
ApiType.SetLife, // only player
|
||||
ApiType.LoseLife, // only player
|
||||
ApiType.ChangeZone,
|
||||
ApiType.Destroy,
|
||||
ApiType.Token,
|
||||
ApiType.SetState,
|
||||
ApiType.Play,
|
||||
ApiType.Sacrifice, // no triggered
|
||||
ApiType.SacrificeAll,
|
||||
ApiType.Pump,
|
||||
|
||||
ApiType.Phases, // updated
|
||||
|
||||
ApiType.DealDamage, // checked
|
||||
|
||||
ApiType.DelayedTrigger,
|
||||
ApiType.ImmediateTrigger,
|
||||
|
||||
ApiType.Goad, // updated
|
||||
|
||||
ApiType.EachDamage,
|
||||
ApiType.WinsGame, // only player
|
||||
ApiType.LosesGame, // only player
|
||||
ApiType.Incubate, // only player
|
||||
ApiType.Mill, // only player
|
||||
|
||||
ApiType.Explore,
|
||||
ApiType.Protection, // should not care about triggered
|
||||
ApiType.ProtectionAll, // No Triggered
|
||||
ApiType.Proliferate, // only player no triggered interaction
|
||||
ApiType.TimeTravel, // only player no triggered interaction
|
||||
ApiType.CopyPermanent,
|
||||
ApiType.Token,
|
||||
ApiType.Debuff, // updated
|
||||
ApiType.Manifest, // no triggered
|
||||
ApiType.Scry, // only player
|
||||
ApiType.SetInMotion, // No Triggered
|
||||
ApiType.Shuffle, // only player
|
||||
ApiType.Surveil, // only player
|
||||
ApiType.Tap, // Done
|
||||
ApiType.TapAll, // uses filterListByType
|
||||
ApiType.TapOrUntap, // No TriggeredCard
|
||||
ApiType.TapOrUntapAll, // No TriggeredCard
|
||||
ApiType.Untap, // Done
|
||||
ApiType.UntapAll, // only player
|
||||
ApiType.Unattach, // No Triggered
|
||||
ApiType.UnattachAll, // No Triggered
|
||||
|
||||
ApiType.Regenerate, // Updated
|
||||
ApiType.Regeneration, // Replacement Effect only
|
||||
|
||||
ApiType.RemoveFromCombat, // Done
|
||||
|
||||
// only Replacement Effects, no Trigger
|
||||
ApiType.ReplaceCounter,
|
||||
ApiType.ReplaceDamage,
|
||||
ApiType.ReplaceEffect,
|
||||
ApiType.ReplaceMana,
|
||||
ApiType.ReplaceSplitDamage,
|
||||
ApiType.ReplaceToken,
|
||||
|
||||
ApiType.Repeat,
|
||||
ApiType.RepeatEach,
|
||||
|
||||
ApiType.RollDice, // only player
|
||||
ApiType.RollPlanarDice, // only player
|
||||
ApiType.Seek, // only player
|
||||
ApiType.Heist, // only player
|
||||
|
||||
ApiType.RingTemptsYou, // only player
|
||||
ApiType.TakeInitiative, // only player
|
||||
|
||||
ApiType.UnlockDoor, // no triggered
|
||||
|
||||
ApiType.Poison, // only player
|
||||
ApiType.Venture, // only player
|
||||
ApiType.VillainousChoice, // only player
|
||||
ApiType.Vote, // only player
|
||||
// internal
|
||||
ApiType.BlankLine,
|
||||
ApiType.DamageResolve,
|
||||
ApiType.ChangeZoneResolve,
|
||||
ApiType.InternalLegendaryRule,
|
||||
ApiType.InternalIgnoreEffect
|
||||
);
|
||||
|
||||
private final SpellAbility sa;
|
||||
private Player decider;
|
||||
|
||||
@@ -323,7 +185,7 @@ public class WrappedAbility extends Ability {
|
||||
// a real solution would include only the triggering information that actually is used, but that's a major change
|
||||
@Override
|
||||
public String toUnsuppressedString() {
|
||||
String desc = this.getStackDescription(); /* use augmented stack description as string for wrapped things */
|
||||
String desc = this.getStackDescription(false); /* use augmented stack description as string for wrapped things */
|
||||
String card = getHostCard().toString();
|
||||
if (!desc.contains(card) && desc.contains(" this ")) { /* a hack for Evolve and similar that don't have CARDNAME */
|
||||
return card + ": " + desc;
|
||||
@@ -332,15 +194,23 @@ public class WrappedAbility extends Ability {
|
||||
|
||||
@Override
|
||||
public String getStackDescription() {
|
||||
return getStackDescription(true);
|
||||
}
|
||||
|
||||
public String getStackDescription(boolean withTargets) {
|
||||
final Trigger regtrig = getTrigger();
|
||||
if (regtrig == null) return "";
|
||||
final StringBuilder sb =
|
||||
new StringBuilder(regtrig.replaceAbilityText(regtrig.toString(true), this, true));
|
||||
List<TargetChoices> allTargets = sa.getAllTargetChoices();
|
||||
if (!allTargets.isEmpty() && !ApiType.Charm.equals(sa.getApi())) {
|
||||
sb.append(" (Targeting: ");
|
||||
sb.append(allTargets);
|
||||
sb.append(")");
|
||||
|
||||
// prevent text growing too long when SA target other in a chain and also potential StackOverflow
|
||||
if (withTargets) {
|
||||
List<TargetChoices> allTargets = sa.getAllTargetChoices();
|
||||
if (!allTargets.isEmpty() && !ApiType.Charm.equals(sa.getApi())) {
|
||||
sb.append(" (Targeting: ");
|
||||
sb.append(allTargets);
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
String important = regtrig.getImportantStackObjects(this);
|
||||
@@ -577,35 +447,9 @@ public class WrappedAbility extends Ability {
|
||||
}
|
||||
}
|
||||
|
||||
timestampCheck();
|
||||
|
||||
getActivatingPlayer().getController().playSpellAbilityNoStack(sa, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO remove this function after the Effects are updated
|
||||
*/
|
||||
protected void timestampCheck() {
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
|
||||
if (noTimestampCheck.contains(sa.getApi())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<AbilityKey, Object> triggerMap = AbilityKey.newMap(sa.getTriggeringObjects());
|
||||
for (Entry<AbilityKey, Object> ev : triggerMap.entrySet()) {
|
||||
if (ev.getValue() instanceof Card) {
|
||||
Card card = (Card) ev.getValue();
|
||||
Card current = game.getCardState(card);
|
||||
if (card.isInPlay() && current.isInPlay() && !current.equalsWithGameTimestamp(card)) {
|
||||
// TODO: figure out if NoTimestampCheck should be the default for ChangesZone triggers
|
||||
sa.getTriggeringObjects().remove(ev.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: CardCollection
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardDamageMap getDamageMap() {
|
||||
return sa.getDamageMap();
|
||||
|
||||
@@ -85,6 +85,8 @@ public enum TrackableProperty {
|
||||
RingLevel(TrackableTypes.IntegerType),
|
||||
CurrentRoom(TrackableTypes.StringType),
|
||||
Intensity(TrackableTypes.IntegerType),
|
||||
OverlayText(TrackableTypes.StringType),
|
||||
MarkerText(TrackableTypes.StringListType),
|
||||
Remembered(TrackableTypes.StringType),
|
||||
NamedCard(TrackableTypes.StringListType),
|
||||
PlayerMayLook(TrackableTypes.PlayerViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
@@ -217,7 +219,6 @@ public enum TrackableProperty {
|
||||
CommanderCast(TrackableTypes.IntegerMapType),
|
||||
CommanderDamage(TrackableTypes.IntegerMapType),
|
||||
MindSlaveMaster(TrackableTypes.PlayerViewType),
|
||||
Speed(TrackableTypes.IntegerType),
|
||||
|
||||
Ante(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
Battlefield(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze), //zones can't respect freeze, otherwise cards that die from state based effects won't have that reflected in the UI
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
@@ -66,7 +66,7 @@
|
||||
<configuration>
|
||||
<!-- generate versionName from revision property to snapshot-version property -->
|
||||
<name>snapshot-version</name>
|
||||
<value>${revision}</value>
|
||||
<value>2.0.02-SNAPSHOT</value>
|
||||
<regex>-SNAPSHOT</regex>
|
||||
<replacement>-SNAPSHOT-${month.date}</replacement>
|
||||
<failIfNoMatch>false</failIfNoMatch>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
@@ -50,7 +49,7 @@
|
||||
<configuration>
|
||||
<!-- generate versionName from revision property to snapshot-version property -->
|
||||
<name>snapshot-version</name>
|
||||
<value>${revision}</value>
|
||||
<value>2.0.02-SNAPSHOT</value>
|
||||
<regex>-SNAPSHOT</regex>
|
||||
<replacement>-SNAPSHOT-${month.date}</replacement>
|
||||
<failIfNoMatch>false</failIfNoMatch>
|
||||
@@ -486,63 +485,42 @@
|
||||
<phase>pre-integration-test</phase>
|
||||
<configuration>
|
||||
<target>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx"/>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx" />
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx">
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="LICENSE.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/"
|
||||
includes="CHANGES.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/"
|
||||
includes="CONTRIBUTORS.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/"
|
||||
includes="ISSUES.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/"
|
||||
includes="INSTALLATION.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/"
|
||||
includes="GAMEPAD_README.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt"/>
|
||||
<fileset dir="${basedir}/" includes="sentry.properties"/>
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="LICENSE.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/" includes="CHANGES.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/" includes="CONTRIBUTORS.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/" includes="ISSUES.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/" includes="INSTALLATION.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/release-files/" includes="GAMEPAD_README.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt" />
|
||||
<fileset dir="${basedir}/" includes="sentry.properties" />
|
||||
</copy>
|
||||
<taskdef name="bundleapp"
|
||||
classpath="${basedir}/../forge-gui/${configSourceDirectory}/appbundler-1.0-custom.jar"
|
||||
classname="com.oracle.appbundler.AppBundlerTask"/>
|
||||
<bundleapp
|
||||
outputdirectory="${project.build.directory}/${project.build.finalName}-osx"
|
||||
name="${project.name}" displayname="${project.name}"
|
||||
shortversion="${project.version}" identifier="forge.view.Main"
|
||||
icon="${basedir}/${configSourceDirectory}/Forge.icns"
|
||||
applicationCategory="public.app-category.games"
|
||||
mainclassname="forge.view.Main">
|
||||
<classpath
|
||||
file="${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar"/>
|
||||
<classpath file="${basedir}/../forge-gui/forge.profile.properties.example"/>
|
||||
<option value="-Dapple.laf.useScreenMenuBar=true"/>
|
||||
<option value="-Dcom.apple.macos.use-file-dialog-packages=true"/>
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true"/>
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge"/>
|
||||
<option value="-Dcom.apple.smallTabs=true"/>
|
||||
<option value="-Xmx4096M"/>
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/"/>
|
||||
<taskdef name="bundleapp" classpath="${basedir}/../forge-gui/${configSourceDirectory}/appbundler-1.0-custom.jar" classname="com.oracle.appbundler.AppBundlerTask" />
|
||||
<bundleapp outputdirectory="${project.build.directory}/${project.build.finalName}-osx" name="${project.name}" displayname="${project.name}" shortversion="${project.version}" identifier="forge.view.Main" icon="${basedir}/${configSourceDirectory}/Forge.icns" applicationCategory="public.app-category.games" mainclassname="forge.view.Main">
|
||||
<classpath file="${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar" />
|
||||
<classpath file="${basedir}/../forge-gui/forge.profile.properties.example" />
|
||||
<option value="-Dapple.laf.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.macos.use-file-dialog-packages=true" />
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
|
||||
<option value="-Dcom.apple.smallTabs=true" />
|
||||
<option value="-Xmx4096M" />
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
|
||||
</bundleapp>
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">
|
||||
<fileset dir="${basedir}/../forge-gui/res" excludes="**/cardsfolder/**"/>
|
||||
<fileset dir="${basedir}/../forge-gui/res" excludes="**/cardsfolder/**" />
|
||||
</copy>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder"/>
|
||||
<zip destfile="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder/cardsfolder.zip"
|
||||
basedir="${basedir}/../forge-gui/res/cardsfolder" level="1"/>
|
||||
<symlink
|
||||
link="${project.build.directory}/${project.build.finalName}-osx/Applications"
|
||||
resource="/Applications"/>
|
||||
<exec executable="${basedir}/../forge-gui/${configSourceDirectory}/create-dmg"
|
||||
failonerror="false">
|
||||
<arg line="--volname ${project.name}-${project.version} --background ${basedir}/../forge-gui/${configSourceDirectory}/backgroundImage.jpg --window-size 700 419 --icon-size 64 --icon ${forge.file.name} 141 283 --icon ${applications.file.name} 452 283 --icon ${changes.file.name} 645 80 --icon ${license.file.name} 645 200 --icon ${readme.file.name} 645 320 ${project.build.directory}/${project.build.finalName}.dmg ${project.build.directory}/${project.build.finalName}-osx"/>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder" />
|
||||
<zip destfile="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder/cardsfolder.zip" basedir="${basedir}/../forge-gui/res/cardsfolder" level="1" />
|
||||
<symlink link="${project.build.directory}/${project.build.finalName}-osx/Applications" resource="/Applications" />
|
||||
<exec executable="${basedir}/../forge-gui/${configSourceDirectory}/create-dmg" failonerror="false">
|
||||
<arg line="--volname ${project.name}-${project.version} --background ${basedir}/../forge-gui/${configSourceDirectory}/backgroundImage.jpg --window-size 700 419 --icon-size 64 --icon ${forge.file.name} 141 283 --icon ${applications.file.name} 452 283 --icon ${changes.file.name} 645 80 --icon ${license.file.name} 645 200 --icon ${readme.file.name} 645 320 ${project.build.directory}/${project.build.finalName}.dmg ${project.build.directory}/${project.build.finalName}-osx" />
|
||||
</exec>
|
||||
<tar basedir="${project.build.directory}"
|
||||
includes="${project.build.finalName}.dmg"
|
||||
destfile="${project.build.directory}/${project.build.finalName}-osx.tar.bz2"
|
||||
compression="bzip2"/>
|
||||
<tar basedir="${project.build.directory}" includes="${project.build.finalName}.dmg" destfile="${project.build.directory}/${project.build.finalName}-osx.tar.bz2" compression="bzip2" />
|
||||
<!--<symlink link="${project.build.directory}/${project.build.finalName}-osx/Applications" action="delete" /> -->
|
||||
<exec executable="rm" failonerror="false">
|
||||
<arg line="-f ${project.build.directory}/${project.build.finalName}-osx/Applications"/>
|
||||
<arg line="-f ${project.build.directory}/${project.build.finalName}-osx/Applications" />
|
||||
</exec>
|
||||
</target>
|
||||
</configuration>
|
||||
@@ -622,54 +600,38 @@
|
||||
<phase>pre-integration-test</phase>
|
||||
<configuration>
|
||||
<target>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx"/>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx" />
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx">
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="LICENSE.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="README.txt"/>
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt"/>
|
||||
<fileset dir="${basedir}/" includes="sentry.properties"/>
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="LICENSE.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="README.txt" />
|
||||
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt" />
|
||||
<fileset dir="${basedir}/" includes="sentry.properties" />
|
||||
</copy>
|
||||
<taskdef name="bundleapp"
|
||||
classpath="${basedir}/../forge-gui/${configSourceDirectory}/appbundler-1.0-custom.jar"
|
||||
classname="com.oracle.appbundler.AppBundlerTask"/>
|
||||
<bundleapp
|
||||
outputdirectory="${project.build.directory}/${project.build.finalName}-osx"
|
||||
name="${project.name}" displayname="${project.name}"
|
||||
shortversion="${project.version}" identifier="forge.view.Main"
|
||||
icon="${basedir}/${configSourceDirectory}/Forge.icns"
|
||||
applicationCategory="public.app-category.games"
|
||||
mainclassname="forge.view.Main">
|
||||
<classpath
|
||||
file="${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar"/>
|
||||
<classpath file="${basedir}/../forge-gui/forge.profile.properties.example"/>
|
||||
<option value="-Dapple.laf.useScreenMenuBar=true"/>
|
||||
<option value="-Dcom.apple.macos.use-file-dialog-packages=true"/>
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true"/>
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge"/>
|
||||
<option value="-Dcom.apple.smallTabs=true"/>
|
||||
<option value="-Xmx4096M"/>
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/"/>
|
||||
<taskdef name="bundleapp" classpath="${basedir}/../forge-gui/${configSourceDirectory}/appbundler-1.0-custom.jar" classname="com.oracle.appbundler.AppBundlerTask" />
|
||||
<bundleapp outputdirectory="${project.build.directory}/${project.build.finalName}-osx" name="${project.name}" displayname="${project.name}" shortversion="${project.version}" identifier="forge.view.Main" icon="${basedir}/${configSourceDirectory}/Forge.icns" applicationCategory="public.app-category.games" mainclassname="forge.view.Main">
|
||||
<classpath file="${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar" />
|
||||
<classpath file="${basedir}/../forge-gui/forge.profile.properties.example" />
|
||||
<option value="-Dapple.laf.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.macos.use-file-dialog-packages=true" />
|
||||
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
|
||||
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
|
||||
<option value="-Dcom.apple.smallTabs=true" />
|
||||
<option value="-Xmx4096M" />
|
||||
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
|
||||
</bundleapp>
|
||||
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">
|
||||
<fileset dir="${basedir}/../forge-gui/res" excludes="**/cardsfolder/**"/>
|
||||
<fileset dir="${basedir}/../forge-gui/res" excludes="**/cardsfolder/**" />
|
||||
</copy>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder"/>
|
||||
<zip destfile="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder/cardsfolder.zip"
|
||||
basedir="${basedir}/../forge-gui/res/cardsfolder" level="1"/>
|
||||
<symlink
|
||||
link="${project.build.directory}/${project.build.finalName}-osx/Applications"
|
||||
resource="/Applications"/>
|
||||
<exec executable="${basedir}/../forge-gui/${configSourceDirectory}/create-dmg"
|
||||
failonerror="false">
|
||||
<arg line="--volname ${project.name}-${project.version} --background ${basedir}/../forge-gui/${configSourceDirectory}/backgroundImage.jpg --window-size 700 419 --icon-size 64 --icon ${forge.file.name} 141 283 --icon ${applications.file.name} 452 283 --icon ${changes.file.name} 645 80 --icon ${license.file.name} 645 200 --icon ${readme.file.name} 645 320 ${project.build.directory}/${project.build.finalName}.dmg ${project.build.directory}/${project.build.finalName}-osx"/>
|
||||
<mkdir dir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder" />
|
||||
<zip destfile="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res/cardsfolder/cardsfolder.zip" basedir="${basedir}/../forge-gui/res/cardsfolder" level="1" />
|
||||
<symlink link="${project.build.directory}/${project.build.finalName}-osx/Applications" resource="/Applications" />
|
||||
<exec executable="${basedir}/../forge-gui/${configSourceDirectory}/create-dmg" failonerror="false">
|
||||
<arg line="--volname ${project.name}-${project.version} --background ${basedir}/../forge-gui/${configSourceDirectory}/backgroundImage.jpg --window-size 700 419 --icon-size 64 --icon ${forge.file.name} 141 283 --icon ${applications.file.name} 452 283 --icon ${changes.file.name} 645 80 --icon ${license.file.name} 645 200 --icon ${readme.file.name} 645 320 ${project.build.directory}/${project.build.finalName}.dmg ${project.build.directory}/${project.build.finalName}-osx" />
|
||||
</exec>
|
||||
<tar basedir="${project.build.directory}"
|
||||
includes="${project.build.finalName}.dmg"
|
||||
destfile="${project.build.directory}/${project.build.finalName}-osx.tar.bz2"
|
||||
compression="bzip2"/>
|
||||
<tar basedir="${project.build.directory}" includes="${project.build.finalName}.dmg" destfile="${project.build.directory}/${project.build.finalName}-osx.tar.bz2" compression="bzip2" />
|
||||
<!--<symlink link="${project.build.directory}/${project.build.finalName}-osx/Applications" action="delete" /> -->
|
||||
<exec executable="rm" failonerror="false">
|
||||
<arg line="-f ${project.build.directory}/${project.build.finalName}-osx/Applications"/>
|
||||
<arg line="-f ${project.build.directory}/${project.build.finalName}-osx/Applications" />
|
||||
</exec>
|
||||
</target>
|
||||
</configuration>
|
||||
|
||||
@@ -533,11 +533,8 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
}
|
||||
|
||||
if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("In Room:");
|
||||
markers.add(card.getCurrentRoom());
|
||||
drawMarkersTabs(g, markers);
|
||||
if(card.getMarkerText() != null) {
|
||||
drawMarkersTabs(g, card.getMarkerText());
|
||||
}
|
||||
|
||||
final int combatXSymbols = (cardXOffset + (cardWidth / 4)) - 16;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
@@ -35,7 +35,7 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<finalName>forge-ios-${revision}</finalName>
|
||||
<finalName>forge-ios-2.0.02-SNAPSHOT</finalName>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
@@ -45,7 +45,7 @@
|
||||
<configuration>
|
||||
<!-- generate versionName from revision property to snapshot-version property -->
|
||||
<name>snapshot-version</name>
|
||||
<value>${revision}</value>
|
||||
<value>2.0.02-SNAPSHOT</value>
|
||||
<regex>-SNAPSHOT</regex>
|
||||
<replacement>-SNAPSHOT-${month.date}</replacement>
|
||||
<failIfNoMatch>false</failIfNoMatch>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -606,7 +606,7 @@ public class AdventureEventData implements Serializable {
|
||||
description += "\n";
|
||||
}
|
||||
description += "Prizes\n3 round wins: 500 gold\n2 round wins: 200 gold\n1 round win: 100 gold\n";
|
||||
description += "Finishing event will award an unsellable copy of each card in your Jumpstart deck.";
|
||||
description += "Participating in this event will award a valueless copy of each card in your Jumpstart deck.";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
@@ -908,14 +908,45 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
}
|
||||
|
||||
public int cardSellPrice(PaperCard card) {
|
||||
int valuable = cards.count(card) - noSellCards.count(card);
|
||||
if (valuable == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int basePrice = (int) (CardUtil.getCardPrice(card) * difficultyData.sellFactor);
|
||||
|
||||
float townPriceModifier = currentLocationChanges == null ? 1f : currentLocationChanges.getTownPriceModifier();
|
||||
return (int) (CardUtil.getCardPrice(card) * difficultyData.sellFactor * (2.0f - townPriceModifier));
|
||||
return (int) (basePrice * (2.0f - townPriceModifier));
|
||||
}
|
||||
|
||||
public void sellCard(PaperCard card, Integer result) {
|
||||
float price = cardSellPrice(card) * result;
|
||||
cards.remove(card, result);
|
||||
addGold((int) price);
|
||||
public int sellCard(PaperCard card, Integer result, boolean addGold) {
|
||||
// When selling cards, always try to sell cards worth something before selling cards that aren't worth anything
|
||||
if (result == null || result < 1) return 0;
|
||||
|
||||
float earned = 0;
|
||||
|
||||
int valuableCount = cards.count(card) - noSellCards.count(card);
|
||||
int noValueToSell = result - valuableCount;
|
||||
int amountValuableToSell = Math.min(result, valuableCount);
|
||||
|
||||
if (amountValuableToSell > 0) {
|
||||
earned += cardSellPrice(card) * amountValuableToSell;
|
||||
cards.remove(card, amountValuableToSell);
|
||||
}
|
||||
if (noValueToSell > 0) {
|
||||
cards.remove(card, noValueToSell);
|
||||
noSellCards.remove(card, noValueToSell);
|
||||
}
|
||||
|
||||
if (addGold) {
|
||||
addGold((int) earned);
|
||||
}
|
||||
|
||||
return (int) earned;
|
||||
}
|
||||
|
||||
public int sellOneCard(PaperCard card) {
|
||||
return sellCard(card, 1, false);
|
||||
}
|
||||
|
||||
public void removeItem(String name) {
|
||||
@@ -1166,8 +1197,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
ItemPool<PaperCard> sellableCards = new ItemPool<>(PaperCard.class);
|
||||
sellableCards.addAllFlat(cards.toFlatList());
|
||||
|
||||
// 1. Remove cards you can't sell
|
||||
sellableCards.removeAll(noSellCards);
|
||||
// Nosell cards used to be filtered out here. Instead we're going to replace their value with 0
|
||||
|
||||
// 1a. Potentially return here if we want to give config option to sell cards from decks
|
||||
// but would need to update the decks on sell, not just the catalog
|
||||
|
||||
@@ -1175,11 +1206,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
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()) {
|
||||
|
||||
int count = cp.getValue() - noSellCards.count(cp.getKey());
|
||||
|
||||
if (count <= 0) continue;
|
||||
|
||||
int count = cp.getValue();
|
||||
if (count > maxCardCounts.getOrDefault(cp.getKey(), 0)) {
|
||||
maxCardCounts.put(cp.getKey(), cp.getValue());
|
||||
}
|
||||
@@ -1213,9 +1240,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
public void doAutosell() {
|
||||
int profit = 0;
|
||||
for (PaperCard cardToSell : autoSellCards.toFlatList()) {
|
||||
profit += AdventurePlayer.current().cardSellPrice(cardToSell);
|
||||
profit += AdventurePlayer.current().sellOneCard(cardToSell);
|
||||
autoSellCards.remove(cardToSell);
|
||||
cards.remove(cardToSell, 1);
|
||||
}
|
||||
addGold(profit); //do this as one transaction so as not to get multiple copies of sound effect
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
|
||||
if (!cardManager.isInfinite()) {
|
||||
removeCard(card, result);
|
||||
}
|
||||
AdventurePlayer.current().sellCard(card, result);
|
||||
AdventurePlayer.current().sellCard(card, result, true);
|
||||
lblGold.setText(String.valueOf(AdventurePlayer.current().getGold()));
|
||||
}
|
||||
});
|
||||
@@ -601,7 +601,7 @@ public class AdventureDeckEditor extends TabPageScreen<AdventureDeckEditor> {
|
||||
public void run(Boolean result) {
|
||||
if (result) {
|
||||
for (Map.Entry<PaperCard, Integer> entry : catalogPage.cardManager.getFilteredItems()) {
|
||||
AdventurePlayer.current().sellCard(entry.getKey(), entry.getValue());
|
||||
AdventurePlayer.current().sellCard(entry.getKey(), entry.getValue(), true);
|
||||
}
|
||||
catalogPage.refresh();
|
||||
lblGold.setText(String.valueOf(AdventurePlayer.current().getGold()));
|
||||
|
||||
@@ -47,6 +47,10 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
private transient boolean enterSpawnPOI = false;
|
||||
|
||||
NavArrowActor navArrow;
|
||||
final Rectangle tempBoundingRect = new Rectangle();
|
||||
final Vector2 enemyMoveVector = new Vector2();
|
||||
boolean collided = false;
|
||||
|
||||
public WorldStage() {
|
||||
super();
|
||||
background = new WorldBackground(this);
|
||||
@@ -61,10 +65,6 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
return instance == null ? instance = new WorldStage() : instance;
|
||||
}
|
||||
|
||||
final Rectangle tempBoundingRect = new Rectangle();
|
||||
final Vector2 enemyMoveVector = new Vector2();
|
||||
|
||||
boolean collided = false;
|
||||
@Override
|
||||
protected void onActing(float delta) {
|
||||
if (isPaused() || MapStage.getInstance().isDialogOnlyInput() || Forge.advFreezePlayerControls)
|
||||
@@ -72,7 +72,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
drawNavigationArrow();
|
||||
if (player.isMoving()) {
|
||||
handleMonsterSpawn(delta);
|
||||
handlePointsOfInterestCollision();
|
||||
collided = collided || handlePointsOfInterestCollision();
|
||||
globalTimer += delta;
|
||||
Iterator<Pair<Float, EnemySprite>> it = enemies.iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -146,6 +146,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
pair.getValue().setAnimation(CharacterSprite.AnimationTypes.Idle);
|
||||
}
|
||||
}
|
||||
collided = false;
|
||||
}
|
||||
|
||||
private void removeEnemy(EnemySprite currentMob) {
|
||||
@@ -200,7 +201,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePointsOfInterestCollision() {
|
||||
public boolean handlePointsOfInterestCollision() {
|
||||
for (Actor actor : foregroundSprites.getChildren()) {
|
||||
if (actor.getClass() == PointOfInterestMapSprite.class) {
|
||||
PointOfInterestMapSprite point = (PointOfInterestMapSprite) actor;
|
||||
@@ -215,6 +216,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
WorldSave.getCurrentSave().autoSave();
|
||||
loadPOI(point.getPointOfInterest());
|
||||
point.getMapSprite().checkOut();
|
||||
return true;
|
||||
} else {
|
||||
if (point == collidingPoint) {
|
||||
collidingPoint = null;
|
||||
@@ -222,6 +224,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void loadPOI(PointOfInterest poi) {
|
||||
|
||||
@@ -98,6 +98,7 @@ public class AdventureEventController implements Serializable {
|
||||
|
||||
AdventureEventData e;
|
||||
|
||||
// TODO After a certain amount of wins, stop offering jump start events
|
||||
if (random.nextInt(10) <= 2) {
|
||||
e = new AdventureEventData(eventSeed, EventFormat.Jumpstart);
|
||||
} else {
|
||||
|
||||
@@ -336,11 +336,11 @@ public class MapDialog {
|
||||
else Current.player().takeGold(-E.addShards);
|
||||
}
|
||||
if (E.addMapReputation != 0) {
|
||||
if (!E.POIReference.isEmpty() && !E.POIReference.contains("$")) {
|
||||
if (E.POIReference != null && !E.POIReference.isEmpty() && !E.POIReference.contains("$")) {
|
||||
WorldSave.getCurrentSave().getPointOfInterestChanges(E.POIReference).addMapReputation(E.addMapReputation);
|
||||
}
|
||||
else
|
||||
} else {
|
||||
stage.getChanges().addMapReputation(E.addMapReputation);
|
||||
}
|
||||
}
|
||||
if (E.deleteMapObject != 0) { //Removes a dummy object from the map.
|
||||
if (E.deleteMapObject < 0) stage.deleteObject(parentID);
|
||||
|
||||
@@ -378,8 +378,7 @@ public class TextRenderer {
|
||||
lastPieceIdx = pieces.size() - 1; //don't re-wrap anything if reached previous line
|
||||
break;
|
||||
}
|
||||
if (lastPiece instanceof TextPiece) {
|
||||
TextPiece textPiece = (TextPiece)lastPiece;
|
||||
if (lastPiece instanceof TextPiece textPiece) {
|
||||
int index = textPiece.text.lastIndexOf(' ');
|
||||
if (index != -1) {
|
||||
if (index == 0) {
|
||||
|
||||
@@ -781,23 +781,12 @@ public class CardRenderer {
|
||||
|
||||
}
|
||||
|
||||
if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("In Room:");
|
||||
markers.add(card.getCurrentRoom());
|
||||
drawMarkersTabs(markers, g, x, y, w, h, false);
|
||||
}
|
||||
//Class level
|
||||
if (card.getCurrentState().getType().hasStringType("Class") && ZoneType.Battlefield.equals(card.getZone())) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("CL:" + card.getClassLevel());
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
}
|
||||
//Ring level
|
||||
if (card.getRingLevel() > 0) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("RL:" + card.getRingLevel());
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
if(card.getMarkerText() != null) {
|
||||
List<String> markers = card.getMarkerText();
|
||||
if(markers.size() > 1) //Use smaller text for multi-line strings.
|
||||
drawMarkersTabs(markers, g, x, y, w, h, false);
|
||||
else
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
}
|
||||
|
||||
float otherSymbolsSize = w / 4f;
|
||||
@@ -1528,7 +1517,7 @@ public class CardRenderer {
|
||||
int pageSize = 128;
|
||||
|
||||
//only generate images for characters that could be used by Forge
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'";
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'!—";
|
||||
|
||||
final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false);
|
||||
final FreeTypeFontParameter parameter = new FreeTypeFontParameter();
|
||||
|
||||
@@ -53,8 +53,8 @@ public abstract class FScreen extends FContainer {
|
||||
}
|
||||
|
||||
public void setHeaderCaption(String headerCaption) {
|
||||
if (header instanceof DefaultHeader) {
|
||||
((DefaultHeader)header).lblCaption.setText(headerCaption);
|
||||
if (header instanceof DefaultHeader dh) {
|
||||
dh.lblCaption.setText(headerCaption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ public abstract class FScreen extends FContainer {
|
||||
}
|
||||
|
||||
public void showMenu() {
|
||||
if (header instanceof MenuHeader) {
|
||||
((MenuHeader)header).btnMenu.trigger();
|
||||
if (header instanceof MenuHeader mh) {
|
||||
mh.btnMenu.trigger();
|
||||
}
|
||||
else { //just so settings screen if no menu header
|
||||
SettingsScreen.show(false);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.02</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui</artifactId>
|
||||
|
||||
@@ -49,7 +49,25 @@
|
||||
"Mishra's Workshop",
|
||||
"Yawgmoth's Bargain",
|
||||
"Gaea's Cradle",
|
||||
"Commander's Sphere"
|
||||
"Commander's Sphere",
|
||||
"Drake Stone",
|
||||
"Wrenn and One",
|
||||
"Under-Construction Skyscraper",
|
||||
"Temur Elevator",
|
||||
"Slumbering Waterways",
|
||||
"Omenpath to Naya",
|
||||
"The Heron Moon",
|
||||
"Gobland",
|
||||
"Fetching Garden",
|
||||
"Mox Poison",
|
||||
"Wisedrafter's Will",
|
||||
"New Master of Arms",
|
||||
"Halving Season",
|
||||
"Questing Cosplayer",
|
||||
"Teferi, Druid of Argoth",
|
||||
"Anax and Cymede & Kynaios and Tiro",
|
||||
"Call from the Grave"
|
||||
|
||||
],
|
||||
"restrictedEditions": [
|
||||
"HTR",
|
||||
@@ -58,7 +76,6 @@
|
||||
"HTR19",
|
||||
"HTR20",
|
||||
"PCEL",
|
||||
"DS0",
|
||||
"HHO",
|
||||
"CMB1",
|
||||
"UST",
|
||||
@@ -67,14 +84,9 @@
|
||||
"PPC1",
|
||||
"UND",
|
||||
"PUST",
|
||||
"UEPHI23",
|
||||
"UEMIN23",
|
||||
"UELAS23",
|
||||
"UEIND23",
|
||||
"UEBAR23",
|
||||
"MB2",
|
||||
"UNF",
|
||||
"DA1"
|
||||
"DA1",
|
||||
"UST",
|
||||
"UNF"
|
||||
],
|
||||
"difficulties": [
|
||||
{
|
||||
|
||||
@@ -10,4 +10,4 @@ SVar:DBChoose:DB$ ChooseCard | AtRandom$ True | Choices$ Creature.nonToken+OppCt
|
||||
SVar:DBCopy:DB$ CopyPermanent | Defined$ ChosenCard | AddTypes$ Sliver | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
|
||||
SVar:DBSeek:DB$ Seek | Type$ Card.Sliver | SpellDescription$ Seek a Sliver card.
|
||||
Oracle:{4}: Create a 1/1 colorless Sliver creature token. Every player may activate this ability but only once each turn. \n At the gebinning of your upkeep, choose one at random\n• Create a 1/1 colorless Sliver creature token.\nCreate a token of a random nontoken creature your opponent controls. That creature becomes a Sliver in addition to its other types.\n• Seek a Sliver card.
|
||||
Oracle:{4}: Create a 1/1 colorless Sliver creature token. Every player may activate this ability but only once each turn. \n At the beginning of your upkeep, choose one at random\n• Create a 1/1 colorless Sliver creature token.\nCreate a token of a random nontoken creature your opponent controls. That creature becomes a Sliver in addition to its other types.\n• Seek a Sliver card.
|
||||
|
||||
@@ -15,17 +15,16 @@ Name=eldrazilarge
|
||||
3 Eye of Ugin|J20|1
|
||||
4 Fellwar Stone|C15|1
|
||||
2 Flayer of Loyalties|CMM|1
|
||||
1 It That Betrays|CMM|1
|
||||
2 It That Betrays|CMM|1
|
||||
1 Kozilek, Butcher of Truth|UMA|1
|
||||
4 Matter Reshaper|OGW|1
|
||||
1 Not of This World|CMM|1
|
||||
4 Reality Smasher|SLD|1
|
||||
1 Rise of the Eldrazi|CMM|1
|
||||
2 Sol Ring|CM2|1
|
||||
4 Thought-Knot Seer|PLIST|1
|
||||
4 Thran Dynamo|MB1|1
|
||||
1 Ulamog, the Ceaseless Hunger|CMM|1
|
||||
1 Ulamog, the Infinite Gyre|2X2|2
|
||||
2 Ulamog, the Infinite Gyre|2X2|2
|
||||
2 Void Winnower|BFZ|1
|
||||
23 Wastes|SLD|1
|
||||
[Sideboard]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="17" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="54">
|
||||
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="30" height="17" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="54">
|
||||
<editorsettings>
|
||||
<export target="wastetown..tmx" format="tmx"/>
|
||||
</editorsettings>
|
||||
@@ -36,7 +36,7 @@
|
||||
</object>
|
||||
<object id="53" template="../../obj/enemy.tx" x="197.5" y="138.75">
|
||||
<properties>
|
||||
<property name="enemy" value="Mimic"/>
|
||||
<property name="enemy" value="Fog Trap"/>
|
||||
</properties>
|
||||
</object>
|
||||
</objectgroup>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user