mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
2 Commits
cleaveKeyw
...
forge-2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a69333b528 | ||
|
|
2beb71cdce |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
|||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
type: 'Bug'
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,6 +31,7 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
**Smartphone (please complete the following information):**
|
**Smartphone (please complete the following information):**
|
||||||
- Device: [e.g. iPhone6]
|
- Device: [e.g. iPhone6]
|
||||||
- OS: [e.g. iOS8.1]
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -4,7 +4,6 @@ about: Suggest an idea for this project
|
|||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
type: 'Feature'
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -887,8 +887,27 @@ public class AiController {
|
|||||||
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) {
|
// Check a predefined condition
|
||||||
return AiPlayDecision.AnotherTime;
|
if (sa.hasParam("AICheckSVar")) {
|
||||||
|
final String svarToCheck = sa.getParam("AICheckSVar");
|
||||||
|
String comparator = "GE";
|
||||||
|
int compareTo = 1;
|
||||||
|
|
||||||
|
if (sa.hasParam("AISVarCompare")) {
|
||||||
|
final String fullCmp = sa.getParam("AISVarCompare");
|
||||||
|
comparator = fullCmp.substring(0, 2);
|
||||||
|
final String strCmpTo = fullCmp.substring(2);
|
||||||
|
try {
|
||||||
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
|
} catch (final Exception ignored) {
|
||||||
|
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
||||||
|
if (!Expressions.compare(left, comparator, compareTo)) {
|
||||||
|
return AiPlayDecision.AnotherTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
// this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
@@ -906,7 +925,7 @@ public class AiController {
|
|||||||
|
|
||||||
// check if enough left (pass memory indirectly because we don't want to include those)
|
// check if enough left (pass memory indirectly because we don't want to include those)
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
||||||
if (tappedForMana != null && !tappedForMana.isEmpty() &&
|
if (tappedForMana != null && tappedForMana.isEmpty() &&
|
||||||
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
@@ -1798,9 +1817,14 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final GameEntity affected) {
|
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
||||||
|
Card hostCard = effect.getHostCard();
|
||||||
|
if (hostCard.hasAlternateState()) {
|
||||||
|
hostCard = game.getCardState(hostCard);
|
||||||
|
}
|
||||||
|
|
||||||
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
||||||
final Player controller = host.getController();
|
final Player controller = hostCard.getController();
|
||||||
if (affected instanceof Player) {
|
if (affected instanceof Player) {
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
}
|
}
|
||||||
@@ -1809,6 +1833,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
String comparator = "GE";
|
String comparator = "GE";
|
||||||
int compareTo = 1;
|
int compareTo = 1;
|
||||||
@@ -1821,9 +1846,9 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1831,12 +1856,13 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, effect);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
|
System.out.println("aiShouldRun?" + left + comparator + compareTo);
|
||||||
return Expressions.compare(left, comparator, compareTo);
|
return Expressions.compare(left, comparator, compareTo);
|
||||||
} else if (effect.isKeyword(Keyword.DREDGE)) {
|
} else if (effect.hasParam("AICheckDredge")) {
|
||||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||||
} else return sa != null && doTrigger(sa, false);
|
} else return sa != null && doTrigger(sa, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
private final CardCollection tapped;
|
private final CardCollection tapped;
|
||||||
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
|
||||||
this(ai0, sa, effect, false);
|
|
||||||
}
|
|
||||||
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect, final boolean payMana) {
|
|
||||||
super(ai0, effect, sa, sa.getHostCard());
|
super(ai0, effect, sa, sa.getHostCard());
|
||||||
|
|
||||||
discarded = new CardCollection();
|
discarded = new CardCollection();
|
||||||
tapped = new CardCollection();
|
tapped = new CardCollection();
|
||||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
||||||
if (!payMana && tappedForMana != null) {
|
if (tappedForMana != null) {
|
||||||
tapped.addAll(tappedForMana);
|
tapped.addAll(tappedForMana);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +110,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(randomSubset);
|
return PaymentDecision.card(randomSubset);
|
||||||
} else if (type.contains("+WithDifferentNames")) {
|
} else if (type.equals("DifferentNames")) {
|
||||||
CardCollection differentNames = new CardCollection();
|
CardCollection differentNames = new CardCollection();
|
||||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||||
while (c > 0) {
|
while (c > 0) {
|
||||||
|
|||||||
@@ -3104,38 +3104,41 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (source == null || !sa.hasParam("AITgts")) {
|
if (source == null) { return srcList; }
|
||||||
return srcList;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection list;
|
if (sa.hasParam("AITgts")) {
|
||||||
String aiTgts = sa.getParam("AITgts");
|
CardCollection list;
|
||||||
if (aiTgts.startsWith("BetterThan")) {
|
String aiTgts = sa.getParam("AITgts");
|
||||||
int value = 0;
|
if (aiTgts.startsWith("BetterThan")) {
|
||||||
if (aiTgts.endsWith("Source")) {
|
int value = 0;
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
if (aiTgts.endsWith("Source")) {
|
||||||
if (source.isEnchanted()) {
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
for (Card enc : source.getEnchantedBy()) {
|
if (source.isEnchanted()) {
|
||||||
if (enc.getController().equals(ai)) {
|
for (Card enc : source.getEnchantedBy()) {
|
||||||
value += 100; // is 100 per AI's own aura enough?
|
if (enc.getController().equals(ai)) {
|
||||||
|
value += 100; // is 100 per AI's own aura enough?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (aiTgts.contains("EvalRating.")) {
|
||||||
|
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
||||||
|
} else {
|
||||||
|
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
||||||
|
value = ComputerUtilCard.evaluateCreature(source);
|
||||||
}
|
}
|
||||||
} else if (aiTgts.contains("EvalRating.")) {
|
final int totalValue = value;
|
||||||
value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa);
|
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa);
|
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
||||||
value = ComputerUtilCard.evaluateCreature(source);
|
}
|
||||||
|
|
||||||
|
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return srcList;
|
||||||
}
|
}
|
||||||
final int totalValue = value;
|
|
||||||
list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30);
|
|
||||||
} else {
|
|
||||||
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return srcList;
|
return srcList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,6 +287,10 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
// wrong gamestate for variable amount
|
// wrong gamestate for variable amount
|
||||||
@@ -353,14 +357,9 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// these should come last since they reserve the paying cards
|
|
||||||
// (this means if a mana ability has both parts it doesn't currently undo reservations if the second part fails)
|
|
||||||
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
@@ -816,11 +815,11 @@ public class ComputerUtilMana {
|
|||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove to prevent re-usage since resources don't get consumed
|
// remove from available lists
|
||||||
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect, true))) {
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect))) {
|
||||||
saList.remove(saPayment);
|
saList.remove(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -829,10 +828,8 @@ public class ComputerUtilMana {
|
|||||||
// subtract mana from mana pool
|
// subtract mana from mana pool
|
||||||
manapool.payManaFromAbility(sa, cost, saPayment);
|
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||||
|
|
||||||
// need to consider if another use is now prevented
|
// no need to remove abilities from resource map,
|
||||||
if (!cost.isPaid() && saPayment.isActivatedAbility() && !saPayment.getRestrictions().canPlay(saPayment.getHostCard(), saPayment)) {
|
// once their costs are paid and consume resources, they can not be used again
|
||||||
sourcesForShards.values().removeIf(s -> s == saPayment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasConverge) {
|
if (hasConverge) {
|
||||||
// hack to prevent converge re-using sources
|
// hack to prevent converge re-using sources
|
||||||
@@ -1665,6 +1662,7 @@ public class ComputerUtilMana {
|
|||||||
if (replaced.contains("C")) {
|
if (replaced.contains("C")) {
|
||||||
manaMap.put(ManaAtom.COLORLESS, m);
|
manaMap.put(ManaAtom.COLORLESS, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,11 +460,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
Card host = replacementEffect.getHostCard();
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
if (host.hasAlternateState()) {
|
|
||||||
host = host.getGame().getCardState(host);
|
|
||||||
}
|
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA, host, affected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("SurpriseBlock")) {
|
if (aiLogic.equals("BeforeCombat")) {
|
||||||
|
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (aiLogic.equals("SurpriseBlock")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -761,8 +765,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
} else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (aiLogic.equals("BeforeCombat")) {
|
|
||||||
return !ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isHidden()) {
|
if (sa.isHidden()) {
|
||||||
@@ -889,6 +891,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
list = ComputerUtil.filterAITgts(sa, ai, list, true);
|
||||||
|
if (sa.hasParam("AITgtsOnlyBetterThanSelf")) {
|
||||||
|
list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30);
|
||||||
|
}
|
||||||
|
|
||||||
if (source.isInZone(ZoneType.Hand)) {
|
if (source.isInZone(ZoneType.Hand)) {
|
||||||
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back.
|
||||||
|
|||||||
@@ -96,10 +96,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
chance = cloneTgtAI(sa);
|
||||||
} else {
|
} else {
|
||||||
if (sa.isReplacementAbility() && host.isCloned()) {
|
|
||||||
// prevent StackOverflow from infinite loop copying another ETB RE
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations);
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("Choices")) {
|
||||||
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
sa.getParam("Choices"), host.getController(), host, sa);
|
sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
@@ -192,7 +188,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
: "Permanent.YouDontCtrl+!named" + name + ",Permanent.nonLegendary+!named" + name;
|
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
||||||
|
|
||||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(
|
return new AiAbilityDecision(
|
||||||
sa.isTargetNumberValid() ? 100 : 0,
|
sa.isTargetNumberValid() && !sa.getTargets().isEmpty() ? 100 : 0,
|
||||||
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,17 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
} else if (mandatory) {
|
|
||||||
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
|
||||||
if (sa.isTargetNumberValid()) {
|
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return decision;
|
|
||||||
} else {
|
} else {
|
||||||
return canPlay(aiPlayer, sa);
|
if (mandatory) {
|
||||||
|
AiAbilityDecision decision = chkDrawback(sa, aiPlayer);
|
||||||
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decision;
|
||||||
|
} else {
|
||||||
|
return canPlay(aiPlayer, sa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
// Not at EOT phase
|
// Not at EOT phase
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn);
|
||||||
}
|
}
|
||||||
} else if ("DuplicatePerms".equals(aiLogic)) {
|
} if ("DuplicatePerms".equals(aiLogic)) {
|
||||||
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
final List<Card> valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
if (valid.size() < 2) {
|
if (valid.size() < 2) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
||||||
@@ -212,7 +212,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
} else {
|
} else {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,8 +92,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return chance > MyRandom.getRandom().nextFloat();
|
return chance > MyRandom.getRandom().nextFloat();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
if (sa.isKeyword(Keyword.LEVEL_UP)) {
|
||||||
@@ -123,6 +124,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
CardCollection list;
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
@@ -290,8 +292,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
if (willActivate) {
|
if (willActivate) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
} else if (logic.equals("ChargeToBestCMC")) {
|
} else if (logic.equals("ChargeToBestCMC")) {
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return doChargeToCMCLogic(ai, sa);
|
||||||
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
} else if (logic.equals("ChargeToBestOppControlledCMC")) {
|
||||||
@@ -344,7 +348,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
return FightAi.canFight(ai, sa, nPump, nPump);
|
return FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amountStr.equals("X")) {
|
if (amountStr.equals("X")) {
|
||||||
@@ -447,7 +451,6 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list;
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
@@ -743,7 +746,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String aiLogic = sa.getParam("AILogic");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
@@ -762,10 +765,14 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("ChargeToBestCMC".equals(aiLogic)) {
|
if ("ChargeToBestCMC".equals(aiLogic)) {
|
||||||
|
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||||
|
if (decision.willingToPlay()) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||||
}
|
}
|
||||||
return doChargeToCMCLogic(ai, sa);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.usesTargeting()) {
|
if (!sa.usesTargeting()) {
|
||||||
@@ -789,6 +796,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
// things like Powder Keg, which are way too complex for the AI
|
// things like Powder Keg, which are way too complex for the AI
|
||||||
}
|
}
|
||||||
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||||
|
// can only target opponent
|
||||||
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
PlayerCollection playerList = new PlayerCollection(IterableUtil.filter(
|
||||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||||
|
|
||||||
@@ -803,12 +811,13 @@ public class CountersPutAi extends CountersAi {
|
|||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic)) {
|
String logic = sa.getParam("AILogic");
|
||||||
|
if ("Fight".equals(logic) || "PowerDmg".equals(logic)) {
|
||||||
int nPump = 0;
|
int nPump = 0;
|
||||||
if (type.equals("P1P1")) {
|
if (type.equals("P1P1")) {
|
||||||
nPump = amount;
|
nPump = amount;
|
||||||
}
|
}
|
||||||
AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump);
|
AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump);
|
||||||
if (decision.willingToPlay()) {
|
if (decision.willingToPlay()) {
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
@@ -829,6 +838,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
|
// When things are mandatory, gotta handle a little differently
|
||||||
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
}
|
}
|
||||||
@@ -853,7 +863,7 @@ public class CountersPutAi extends CountersAi {
|
|||||||
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice;
|
Card choice = null;
|
||||||
|
|
||||||
// Choose targets here:
|
// Choose targets here:
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
@@ -879,10 +889,10 @@ public class CountersPutAi extends CountersAi {
|
|||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
sa.addDividedAllocation(choice, left);
|
sa.addDividedAllocation(choice, left);
|
||||||
} else {
|
} else {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
|
||||||
sa.addDividedAllocation(choice, alloc);
|
sa.addDividedAllocation(choice, alloc);
|
||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
@@ -972,7 +982,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
final boolean isCurse = sa.isCurse();
|
||||||
|
|
||||||
|
if (isCurse) {
|
||||||
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||||
|
|
||||||
if (!opponents.isEmpty()) {
|
if (!opponents.isEmpty()) {
|
||||||
@@ -1198,8 +1210,9 @@ public class CountersPutAi extends CountersAi {
|
|||||||
}
|
}
|
||||||
if (numCtrs < optimalCMC) {
|
if (numCtrs < optimalCMC) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) {
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
} else if (logic.equals("Fight")) {
|
} else if (logic.equals("Fight")) {
|
||||||
return FightAi.canFight(ai, sa, 0,0);
|
return FightAi.canFightAi(ai, sa, 0,0);
|
||||||
} else if (logic.equals("Pump")) {
|
} else if (logic.equals("Pump")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
List<Card> options = CardUtil.getValidCardsToTarget(sa);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
* @param power bonus to power
|
* @param power bonus to power
|
||||||
* @return true if fight effect should be played, false otherwise
|
* @return true if fight effect should be played, false otherwise
|
||||||
*/
|
*/
|
||||||
public static AiAbilityDecision canFight(final Player ai, final SpellAbility sa, int power, int toughness) {
|
public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
AbilitySub tgtFight = sa.getSubAbility();
|
AbilitySub tgtFight = sa.getSubAbility();
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
|
protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String ailogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (ailogic.equals("PhaseOut")) {
|
if (ailogic.equals("Never")) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
if (aiLogic.equals("LilianaMill")) {
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
|
if (aiLogic.equals("Main1")) {
|
||||||
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
||||||
|
|| ComputerUtil.castSpellInMain1(ai, sa);
|
||||||
|
} else if (aiLogic.equals("LilianaMill")) {
|
||||||
// TODO convert to AICheckSVar
|
// TODO convert to AICheckSVar
|
||||||
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1;
|
||||||
@@ -50,10 +55,9 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
// because they are also potentially useful for combat
|
// because they are also potentially useful for combat
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
||||||
}
|
}
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases")
|
return true;
|
||||||
|| ComputerUtil.castSpellInMain1(ai, sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
|
if (blocker == null) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
sa.getTargets().add(blocker);
|
sa.getTargets().add(blocker);
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
}
|
}
|
||||||
@@ -60,6 +63,11 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
// only use on creatures that can attack
|
||||||
|
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
}
|
||||||
|
|
||||||
Card attacker = source;
|
Card attacker = source;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
||||||
@@ -73,9 +81,13 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
final List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
||||||
if (list.isEmpty() && mandatory) {
|
if (list.isEmpty()) {
|
||||||
list = CardUtil.getValidCardsToTarget(sa);
|
if (sa.isTargetNumberValid()) {
|
||||||
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (blocker == null) {
|
if (blocker == null) {
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ public class PhasesAi extends SpellAbilityAi {
|
|||||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
|
||||||
if (isThreatened) {
|
if (isThreatened) {
|
||||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||||
|
} else {
|
||||||
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|
||||||
}
|
}
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFight(ai, sa, attack, defense).willingToPlay();
|
return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-core</artifactId>
|
<artifactId>forge-core</artifactId>
|
||||||
|
|||||||
@@ -324,12 +324,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) {
|
||||||
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
legal = true; // Stranger Things Secret Lair gimmick partner commander
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Survivors") && b.hasKeyword("Partner - Survivors")) {
|
|
||||||
legal = true; // The Last of Us Secret Lair gimmick partner commander
|
|
||||||
}
|
|
||||||
if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) {
|
|
||||||
legal = true; // God of War Secret Lair gimmick partner commander
|
|
||||||
}
|
}
|
||||||
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
if (hasKeyword("Choose a Background") && b.canBeBackground()
|
||||||
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
|| b.hasKeyword("Choose a Background") && canBeBackground()) {
|
||||||
@@ -348,7 +342,6 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
||||||
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
hasKeyword("Friends forever") || hasKeyword("Choose a Background") ||
|
||||||
hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") ||
|
|
||||||
hasKeyword("Doctor's companion") || isDoctor());
|
hasKeyword("Doctor's companion") || isDoctor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import forge.util.ITranslatable;
|
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,7 +157,7 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Color implements ITranslatable {
|
public enum Color {
|
||||||
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
||||||
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
||||||
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
||||||
@@ -190,7 +188,6 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -198,8 +195,7 @@ public final class MagicColor {
|
|||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String getLocalizedName() {
|
||||||
public String getTranslatedName() {
|
|
||||||
return Localizer.getInstance().getMessage(label);
|
return Localizer.getInstance().getMessage(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +205,10 @@ public final class MagicColor {
|
|||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getLocalizedName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -994,7 +994,7 @@ public class DeckRecognizer {
|
|||||||
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
||||||
if (magicColor == null) // Multicolour
|
if (magicColor == null) // Multicolour
|
||||||
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
||||||
return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol());
|
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
||||||
@@ -1013,8 +1013,8 @@ public class DeckRecognizer {
|
|||||||
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
||||||
|| magicColor1 == MagicColor.Color.COLORLESS)
|
|| magicColor1 == MagicColor.Color.COLORLESS)
|
||||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||||
String localisedName1 = magicColor1.getTranslatedName();
|
String localisedName1 = magicColor1.getLocalizedName();
|
||||||
String localisedName2 = magicColor2.getTranslatedName();
|
String localisedName2 = magicColor2.getLocalizedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask());
|
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask());
|
||||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,9 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
|||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return getRules().getType().toString();
|
return getRules().getType().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return getRules().getOracleText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,13 @@ public interface ITranslatable extends IHasName {
|
|||||||
default String getUntranslatedName() {
|
default String getUntranslatedName() {
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
default String getTranslatedName() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
default String getUntranslatedType() {
|
default String getUntranslatedType() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default String getUntranslatedOracle() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-game</artifactId>
|
<artifactId>forge-game</artifactId>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-logback</artifactId>
|
<artifactId>sentry-logback</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.19.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jgrapht</groupId>
|
<groupId>org.jgrapht</groupId>
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
|
|
||||||
/** Keys of descriptive (text) parameters. */
|
/** Keys of descriptive (text) parameters. */
|
||||||
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
||||||
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription")
|
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build();
|
||||||
.add("ChangeTypeDesc")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys that should not changed
|
* Keys that should not changed
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ public class ForgeScript {
|
|||||||
Card source, CardTraitBase spellAbility) {
|
Card source, CardTraitBase spellAbility) {
|
||||||
if (property.equals("ManaAbility")) {
|
if (property.equals("ManaAbility")) {
|
||||||
return sa.isManaAbility();
|
return sa.isManaAbility();
|
||||||
|
} else if (property.equals("nonManaAbility")) {
|
||||||
|
return !sa.isManaAbility();
|
||||||
} else if (property.equals("withoutXCost")) {
|
} else if (property.equals("withoutXCost")) {
|
||||||
return !sa.costHasManaX();
|
return !sa.costHasManaX();
|
||||||
} else if (property.startsWith("XCost")) {
|
} else if (property.startsWith("XCost")) {
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ import forge.item.PaperCard;
|
|||||||
import forge.util.*;
|
import forge.util.*;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
import io.sentry.Breadcrumb;
|
|
||||||
import io.sentry.Sentry;
|
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles;
|
||||||
import org.jgrapht.graph.DefaultDirectedGraph;
|
import org.jgrapht.graph.DefaultDirectedGraph;
|
||||||
@@ -751,29 +749,26 @@ public class GameAction {
|
|||||||
|
|
||||||
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||||
// Call specific functions to set PlayerZone, then move onto moveTo
|
// Call specific functions to set PlayerZone, then move onto moveTo
|
||||||
try {
|
switch(name) {
|
||||||
return switch (name) {
|
case Hand: return moveToHand(c, cause, params);
|
||||||
case Hand -> moveToHand(c, cause, params);
|
case Library: return moveToLibrary(c, libPosition, cause, params);
|
||||||
case Library -> moveToLibrary(c, libPosition, cause, params);
|
case Battlefield: return moveToPlay(c, c.getController(), cause, params);
|
||||||
case Battlefield -> moveToPlay(c, c.getController(), cause, params);
|
case Graveyard: return moveToGraveyard(c, cause, params);
|
||||||
case Graveyard -> moveToGraveyard(c, cause, params);
|
case Exile:
|
||||||
case Exile -> !c.canExiledBy(cause, true) ? null : exile(c, cause, params);
|
if (!c.canExiledBy(cause, true)) {
|
||||||
case Stack -> moveToStack(c, cause, params);
|
return null;
|
||||||
case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params);
|
}
|
||||||
case Junkyard -> moveToJunkyard(c, cause, params);
|
return exile(c, cause, params);
|
||||||
default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there
|
case Stack: return moveToStack(c, cause, params);
|
||||||
};
|
case PlanarDeck:
|
||||||
} catch (Exception e) {
|
case SchemeDeck:
|
||||||
String msg = "GameAction:moveTo: Exception occured";
|
case AttractionDeck:
|
||||||
|
case ContraptionDeck:
|
||||||
Breadcrumb bread = new Breadcrumb(msg);
|
return moveToVariantDeck(c, name, libPosition, cause, params);
|
||||||
bread.setData("Card", c.getName());
|
case Junkyard:
|
||||||
bread.setData("SA", cause.toString());
|
return moveToJunkyard(c, cause, params);
|
||||||
bread.setData("ZoneType", name.name());
|
default: // sideboard will also get there
|
||||||
bread.setData("Player", c.getOwner());
|
return moveTo(c.getOwner().getZone(name), c, cause);
|
||||||
Sentry.addBreadcrumb(bread);
|
|
||||||
|
|
||||||
throw new RuntimeException("Error in GameAction moveTo " + c.getName() + " to Player Zone " + name.name(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1602,7 +1597,9 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
// recheck the game over condition at this point to make sure no other win conditions apply now.
|
||||||
checkGameOverCondition();
|
if (!game.isGameOver()) {
|
||||||
|
checkGameOverCondition();
|
||||||
|
}
|
||||||
|
|
||||||
if (game.getAge() != GameStage.Play) {
|
if (game.getAge() != GameStage.Play) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1883,10 +1880,6 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void checkGameOverCondition() {
|
public void checkGameOverCondition() {
|
||||||
if (game.isGameOver()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// award loses as SBE
|
// award loses as SBE
|
||||||
GameEndReason reason = null;
|
GameEndReason reason = null;
|
||||||
List<Player> losers = null;
|
List<Player> losers = null;
|
||||||
|
|||||||
@@ -34,17 +34,15 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.event.GameEventCardAttachment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.keyword.KeywordWithType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
|
||||||
import forge.game.staticability.StaticAbilityCantAttach;
|
import forge.game.staticability.StaticAbilityCantAttach;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Lang;
|
|
||||||
|
|
||||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||||
protected int id;
|
protected int id;
|
||||||
@@ -198,12 +196,14 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
public final void addAttachedCard(final Card c) {
|
public final void addAttachedCard(final Card c) {
|
||||||
if (attachedCards.add(c)) {
|
if (attachedCards.add(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void removeAttachedCard(final Card c) {
|
public final void removeAttachedCard(final Card c) {
|
||||||
if (attachedCards.remove(c)) {
|
if (attachedCards.remove(c)) {
|
||||||
updateAttachedCards();
|
updateAttachedCards();
|
||||||
|
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,83 +221,63 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
return canBeAttached(attach, sa, false);
|
return canBeAttached(attach, sa, false);
|
||||||
}
|
}
|
||||||
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
||||||
return cantBeAttachedMsg(attach, sa, checkSBA) == null;
|
// master mode
|
||||||
}
|
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|
||||||
|
|| equals(attach)) {
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa) {
|
return false;
|
||||||
return cantBeAttachedMsg(attach, sa, false);
|
|
||||||
}
|
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
|
|
||||||
if (!attach.isAttachment()) {
|
|
||||||
return attach.getName() + " is not an attachment";
|
|
||||||
}
|
|
||||||
if (equals(attach)) {
|
|
||||||
return attach.getName() + " can't attach to itself";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) {
|
|
||||||
return attach.getName() + " is a creature without reconfigure";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isPhasedOut()) {
|
if (attach.isPhasedOut()) {
|
||||||
return attach.getName() + " is phased out";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attach.isAura()) {
|
// check for rules
|
||||||
String msg = cantBeEnchantedByMsg(attach);
|
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
||||||
if (msg != null) {
|
return false;
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isEquipment()) {
|
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
|
||||||
String msg = cantBeEquippedByMsg(attach, sa);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (attach.isFortification()) {
|
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
|
||||||
String msg = cantBeFortifiedByMsg(attach);
|
return false;
|
||||||
if (msg != null) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticAbility stAb = StaticAbilityCantAttach.cantAttach(this, attach, checkSBA);
|
// check for can't attach static
|
||||||
if (stAb != null) {
|
if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) {
|
||||||
return stAb.toString();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// true for all
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeEquippedByMsg(final Card aura, SpellAbility sa) {
|
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
|
||||||
|
/**
|
||||||
|
* Equip only to Creatures which are cards
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canBeFortifiedBy(final Card aura) {
|
||||||
/**
|
/**
|
||||||
* Equip only to Lands which are cards
|
* Equip only to Lands which are cards
|
||||||
*/
|
*/
|
||||||
return getName() + " is not a Creature";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String cantBeFortifiedByMsg(final Card fort) {
|
protected boolean canBeEnchantedBy(final Card aura) {
|
||||||
/**
|
|
||||||
* Equip only to Lands which are cards
|
|
||||||
*/
|
|
||||||
return getName() + " is not a Land";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String cantBeEnchantedByMsg(final Card aura) {
|
|
||||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return "No Enchant Keyword";
|
return false;
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String k = ki.getOriginal();
|
||||||
String v = kwt.getValidType();
|
String m[] = k.split(":");
|
||||||
String desc = kwt.getTypeDescription();
|
String v = m[1];
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCounters() {
|
public boolean hasCounters() {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class AbilityUtils {
|
public class AbilityUtils {
|
||||||
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
|
||||||
|
|
||||||
@@ -522,8 +523,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
} else if (calcX[0].equals("OriginalHost")) {
|
} else if (calcX[0].equals("OriginalHost")) {
|
||||||
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
||||||
} else if (calcX[0].equals("DungeonsCompleted")) {
|
|
||||||
val = handlePaid(player.getCompletedDungeons(), calcX[1], card, ability);
|
|
||||||
} else if (calcX[0].startsWith("ExiledWith")) {
|
} else if (calcX[0].startsWith("ExiledWith")) {
|
||||||
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
val = handlePaid(card.getExiledCards(), calcX[1], card, ability);
|
||||||
} else if (calcX[0].startsWith("Convoked")) {
|
} else if (calcX[0].startsWith("Convoked")) {
|
||||||
@@ -1717,10 +1716,6 @@ public class AbilityUtils {
|
|||||||
return doXMath(calculateAmount(c, sq[sa.isBargained() ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[sa.isBargained() ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("Cleave")) {
|
|
||||||
return doXMath(calculateAmount(c, sq[sa.isCleave() ? 1 : 2], ctb), expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sq[0].startsWith("Freerunning")) {
|
if (sq[0].startsWith("Freerunning")) {
|
||||||
return doXMath(calculateAmount(c, sq[sa.isFreerunning() ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[sa.isFreerunning() ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -2547,13 +2542,34 @@ public class AbilityUtils {
|
|||||||
return doXMath(CardLists.getValidCardCount(game.getLeftGraveyardThisTurn(), validFilter, player, c, ctb), expr, c, ctb);
|
return doXMath(CardLists.getValidCardCount(game.getLeftGraveyardThisTurn(), validFilter, player, c, ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].equals("UnlockedDoors")) {
|
// Count$UnlockedDoors <Valid>
|
||||||
return doXMath(player.getUnlockedDoors().size(), expr, c, ctb);
|
if (sq[0].startsWith("UnlockedDoors")) {
|
||||||
|
final String[] workingCopy = l[0].split(" ", 2);
|
||||||
|
final String validFilter = workingCopy[1];
|
||||||
|
|
||||||
|
int unlocked = 0;
|
||||||
|
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
|
||||||
|
unlocked += doorCard.getUnlockedRooms().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return doXMath(unlocked, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count$DistinctUnlockedDoors <Valid>
|
||||||
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
|
// Counts the distinct names of unlocked doors. Used for the "Promising Stairs"
|
||||||
if (sq[0].equals("DistinctUnlockedDoors")) {
|
if (sq[0].startsWith("DistinctUnlockedDoors")) {
|
||||||
return doXMath(Sets.newHashSet(player.getUnlockedDoors()).size(), expr, c, ctb);
|
final String[] workingCopy = l[0].split(" ", 2);
|
||||||
|
final String validFilter = workingCopy[1];
|
||||||
|
|
||||||
|
Set<String> viewedNames = new HashSet<>();
|
||||||
|
for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) {
|
||||||
|
for(CardStateName stateName : doorCard.getUnlockedRooms()) {
|
||||||
|
viewedNames.add(doorCard.getState(stateName).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int distinctUnlocked = viewedNames.size();
|
||||||
|
|
||||||
|
return doXMath(distinctUnlocked, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manapool
|
// Manapool
|
||||||
@@ -2873,6 +2889,21 @@ public class AbilityUtils {
|
|||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].startsWith("DifferentCardNames_")) {
|
||||||
|
final List<String> crdname = Lists.newArrayList();
|
||||||
|
final String restriction = l[0].substring(19);
|
||||||
|
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb);
|
||||||
|
// TODO rewrite with sharesName to respect Spy Kit
|
||||||
|
for (final Card card : list) {
|
||||||
|
String name = card.getName();
|
||||||
|
// CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common
|
||||||
|
if (!crdname.contains(name) && !name.isEmpty()) {
|
||||||
|
crdname.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(crdname.size(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("MostProminentCreatureType")) {
|
if (sq[0].startsWith("MostProminentCreatureType")) {
|
||||||
String restriction = l[0].split(" ")[1];
|
String restriction = l[0].split(" ")[1];
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||||
@@ -2887,6 +2918,13 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO move below to handlePaid
|
// TODO move below to handlePaid
|
||||||
|
if (sq[0].startsWith("SumPower")) {
|
||||||
|
final String[] restrictions = l[0].split("_");
|
||||||
|
int sumPower = game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
|
.filter(CardPredicates.restriction(restrictions[1], player, c, ctb))
|
||||||
|
.mapToInt(Card::getNetPower).sum();
|
||||||
|
return doXMath(sumPower, expr, c, ctb);
|
||||||
|
}
|
||||||
if (sq[0].startsWith("DifferentPower_")) {
|
if (sq[0].startsWith("DifferentPower_")) {
|
||||||
final String restriction = l[0].substring(15);
|
final String restriction = l[0].substring(15);
|
||||||
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream()
|
||||||
@@ -3406,7 +3444,6 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) {
|
||||||
|
|
||||||
final String[] l = s.split("/");
|
final String[] l = s.split("/");
|
||||||
final String m = CardFactoryUtil.extractOperators(s);
|
final String m = CardFactoryUtil.extractOperators(s);
|
||||||
|
|
||||||
@@ -3593,10 +3630,46 @@ public class AbilityUtils {
|
|||||||
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.equals("DungeonsCompleted")) {
|
||||||
|
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("RingTemptedYou")) {
|
if (value.equals("RingTemptedYou")) {
|
||||||
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
return doXMath(player.getNumRingTemptedYou(), m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.startsWith("DungeonCompletedNamed")) {
|
||||||
|
String [] full = value.split("_");
|
||||||
|
String name = full[1];
|
||||||
|
int completed = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (Card c : dungeons) {
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
++completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(completed, m, source, ctb);
|
||||||
|
}
|
||||||
|
if (value.equals("DifferentlyNamedDungeonsCompleted")) {
|
||||||
|
int amount = 0;
|
||||||
|
List<Card> dungeons = player.getCompletedDungeons();
|
||||||
|
for (int i = 0; i < dungeons.size(); ++i) {
|
||||||
|
Card d1 = dungeons.get(i);
|
||||||
|
boolean hasSameName = false;
|
||||||
|
for (int j = i - 1; j >= 0; --j) {
|
||||||
|
Card d2 = dungeons.get(j);
|
||||||
|
if (d1.getName().equals(d2.getName())) {
|
||||||
|
hasSameName = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasSameName) {
|
||||||
|
++amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doXMath(amount, m, source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.equals("AttractionsVisitedThisTurn")) {
|
if (value.equals("AttractionsVisitedThisTurn")) {
|
||||||
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb);
|
||||||
}
|
}
|
||||||
@@ -3675,6 +3748,10 @@ public class AbilityUtils {
|
|||||||
return CardLists.getTotalPower(paidList, ctb);
|
return CardLists.getTotalPower(paidList, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("SumToughness")) {
|
||||||
|
return Aggregates.sum(paidList, Card::getNetToughness);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.startsWith("GreatestCMC")) {
|
if (string.startsWith("GreatestCMC")) {
|
||||||
return Aggregates.max(paidList, Card::getCMC);
|
return Aggregates.max(paidList, Card::getCMC);
|
||||||
}
|
}
|
||||||
@@ -3683,10 +3760,6 @@ public class AbilityUtils {
|
|||||||
return CardUtil.getColorsFromCards(paidList).countColors();
|
return CardUtil.getColorsFromCards(paidList).countColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.startsWith("DifferentCardNames")) {
|
|
||||||
return doXMath(CardLists.getDifferentNamesCount(paidList), CardFactoryUtil.extractOperators(string), source, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.equals("DifferentColorPair")) {
|
if (string.equals("DifferentColorPair")) {
|
||||||
final Set<ColorSet> diffPair = new HashSet<>();
|
final Set<ColorSet> diffPair = new HashSet<>();
|
||||||
for (final Card card : paidList) {
|
for (final Card card : paidList) {
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
// if ability was granted use that source so they can be kept apart later
|
// if ability was granted use that source so they can be kept apart later
|
||||||
if (cause.isCopiedTrait()) {
|
if (cause.isCopiedTrait()) {
|
||||||
exilingSource = cause.getOriginalHost();
|
exilingSource = cause.getOriginalHost();
|
||||||
} else if (!cause.isSpell() && cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
} else if (cause.getKeyword() != null && cause.getKeyword().getStatic() != null) {
|
||||||
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
exilingSource = cause.getKeyword().getStatic().getOriginalHost();
|
||||||
}
|
}
|
||||||
movedCard.setExiledWith(exilingSource);
|
movedCard.setExiledWith(exilingSource);
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
if (perpetual) {
|
if (perpetual) {
|
||||||
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite));
|
||||||
}
|
}
|
||||||
c.addColor(colors, !overwrite, timestamp, null);
|
c.addColor(colors, !overwrite, timestamp, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("LeaveBattlefield")) {
|
if (sa.hasParam("LeaveBattlefield")) {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ChangeZoneEffect extends SpellAbilityEffect {
|
public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@@ -104,7 +103,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
|
||||||
String type = "card";
|
String type = "card";
|
||||||
boolean defined = false;
|
boolean defined = false;
|
||||||
if (sa.hasParam("ChangeTypeDesc")) {
|
if (sa.hasParam("ChangeTypeDesc")) {
|
||||||
@@ -119,11 +117,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
type = Lang.joinHomogenous(tgts);
|
type = Lang.joinHomogenous(tgts);
|
||||||
defined = true;
|
defined = true;
|
||||||
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
} else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) {
|
||||||
List<String> typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList());
|
final String ct = sa.getParam("ChangeType");
|
||||||
type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or");
|
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
|
||||||
}
|
}
|
||||||
final String cardTag = type.contains("card") ? "" : " card";
|
final String cardTag = type.contains("card") ? "" : " card";
|
||||||
|
|
||||||
|
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1;
|
||||||
boolean tapped = sa.hasParam("Tapped");
|
boolean tapped = sa.hasParam("Tapped");
|
||||||
boolean attacking = sa.hasParam("Attacking");
|
boolean attacking = sa.hasParam("Attacking");
|
||||||
if (sa.isNinjutsu()) {
|
if (sa.isNinjutsu()) {
|
||||||
@@ -153,9 +152,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
sb.append(" for ");
|
sb.append(" for ");
|
||||||
}
|
}
|
||||||
if (num != 1) {
|
|
||||||
sb.append(" up to ");
|
|
||||||
}
|
|
||||||
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
||||||
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) {
|
||||||
if (choosers.size() == 1) {
|
if (choosers.size() == 1) {
|
||||||
|
|||||||
@@ -73,11 +73,6 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||||
if (zone.equals(ZoneType.Battlefield)) {
|
|
||||||
if (sa.hasParam("Tapped")) {
|
|
||||||
made.setTapped(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (zone.equals(ZoneType.Exile)) {
|
if (zone.equals(ZoneType.Exile)) {
|
||||||
handleExiledWith(made, sa);
|
handleExiledWith(made, sa);
|
||||||
if (sa.hasParam("ExileFaceDown")) {
|
if (sa.hasParam("ExileFaceDown")) {
|
||||||
|
|||||||
@@ -269,22 +269,22 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Color(s)
|
||||||
if (hostCard.hasChosenColor()) {
|
if (hostCard.hasChosenColor()) {
|
||||||
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Cards
|
||||||
if (hostCard.hasChosenCard()) {
|
if (hostCard.hasChosenCard()) {
|
||||||
eff.setChosenCards(hostCard.getChosenCards());
|
eff.setChosenCards(hostCard.getChosenCards());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen Player
|
||||||
if (hostCard.hasChosenPlayer()) {
|
if (hostCard.hasChosenPlayer()) {
|
||||||
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
eff.setChosenPlayer(hostCard.getChosenPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostCard.getChosenDirection() != null) {
|
// Set Chosen Type
|
||||||
eff.setChosenDirection(hostCard.getChosenDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostCard.hasChosenType()) {
|
if (hostCard.hasChosenType()) {
|
||||||
eff.setChosenType(hostCard.getChosenType());
|
eff.setChosenType(hostCard.getChosenType());
|
||||||
}
|
}
|
||||||
@@ -292,10 +292,12 @@ public class EffectEffect extends SpellAbilityEffect {
|
|||||||
eff.setChosenType2(hostCard.getChosenType2());
|
eff.setChosenType2(hostCard.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set Chosen name
|
||||||
if (hostCard.hasNamedCard()) {
|
if (hostCard.hasNamedCard()) {
|
||||||
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chosen number
|
||||||
if (sa.hasParam("SetChosenNumber")) {
|
if (sa.hasParam("SetChosenNumber")) {
|
||||||
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa));
|
||||||
} else if (hostCard.hasChosenNumber()) {
|
} else if (hostCard.hasChosenNumber()) {
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
|||||||
p.clearController();
|
p.clearController();
|
||||||
|
|
||||||
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
|
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
|
||||||
|
List<Card> filteredCards = null;
|
||||||
if (leaveZone != null) {
|
if (leaveZone != null) {
|
||||||
List<Card> filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa);
|
||||||
newLibrary.addAll(filteredCards);
|
newLibrary.addAll(filteredCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
public final void updateColorForView() {
|
public final void updateColorForView() {
|
||||||
currentState.getView().updateColors(this);
|
currentState.getView().updateColors(this);
|
||||||
currentState.getView().updateHasChangeColors(hasChangedCardColors());
|
currentState.getView().updateHasChangeColors(!Iterables.isEmpty(getChangedCardColors()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAttackingForView() {
|
public void updateAttackingForView() {
|
||||||
@@ -965,7 +965,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
String name = state.getName();
|
String name = state.getName();
|
||||||
for (CardChangedName change : this.changedCardNames.values()) {
|
for (CardChangedName change : this.changedCardNames.values()) {
|
||||||
if (change.isOverwrite()) {
|
if (change.isOverwrite()) {
|
||||||
name = change.newName();
|
name = change.getNewName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
||||||
@@ -980,7 +980,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
for (CardChangedName change : this.changedCardNames.values()) {
|
for (CardChangedName change : this.changedCardNames.values()) {
|
||||||
if (change.isOverwrite()) {
|
if (change.isOverwrite()) {
|
||||||
result = false;
|
result = false;
|
||||||
} else if (change.addNonLegendaryCreatureNames()) {
|
} else if (change.isAddNonLegendaryCreatureNames()) {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1013,12 +1013,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
currentState.getView().updateName(currentState);
|
currentState.getView().updateName(currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CardChangedName(String newName, boolean addNonLegendaryCreatureNames) {
|
|
||||||
public boolean isOverwrite() {
|
|
||||||
return newName != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGamePieceType(GamePieceType gamePieceType) {
|
public void setGamePieceType(GamePieceType gamePieceType) {
|
||||||
this.gamePieceType = gamePieceType;
|
this.gamePieceType = gamePieceType;
|
||||||
this.view.updateGamePieceType(this);
|
this.view.updateGamePieceType(this);
|
||||||
@@ -2459,8 +2453,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
} else if (keyword.startsWith("DeckLimit")) {
|
} else if (keyword.startsWith("DeckLimit")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
sbLong.append(k[2]).append("\r\n");
|
sbLong.append(k[2]).append("\r\n");
|
||||||
} else if (keyword.startsWith("Enchant") && inst instanceof KeywordWithType kwt) {
|
} else if (keyword.startsWith("Enchant")) {
|
||||||
String desc = kwt.getTypeDescription();
|
String m[] = keyword.split(":");
|
||||||
|
String desc;
|
||||||
|
if (m.length > 2) {
|
||||||
|
desc = m[2];
|
||||||
|
} else {
|
||||||
|
desc = m[1];
|
||||||
|
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
|
||||||
|
desc = desc.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
sbLong.append("Enchant ").append(desc).append("\r\n");
|
sbLong.append("Enchant ").append(desc).append("\r\n");
|
||||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
||||||
|| keyword.startsWith("Disguise") || keyword.startsWith("Reflect")
|
|| keyword.startsWith("Disguise") || keyword.startsWith("Reflect")
|
||||||
@@ -2604,7 +2607,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
||||||
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
||||||
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|
||||||
|| keyword.equals("Partner - Father & Son") || keyword.equals("Partner - Survivors")
|
|
||||||
|| keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")
|
|| keyword.equals("Space sculptor") || keyword.equals("Doctor's companion")
|
||||||
|| keyword.equals("Start your engines")) {
|
|| keyword.equals("Start your engines")) {
|
||||||
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||||
@@ -3324,24 +3326,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
// Pseudo keywords, only print Reminder
|
// Pseudo keywords, only print Reminder
|
||||||
sbBefore.append(inst.getReminderText());
|
sbBefore.append(inst.getReminderText());
|
||||||
sbBefore.append("\r\n");
|
sbBefore.append("\r\n");
|
||||||
} else if (keyword.startsWith("Cleave")) {
|
|
||||||
final String[] k = keyword.split(":");
|
|
||||||
final Cost mCost;
|
|
||||||
if (k.length < 2 || "ManaCost".equals(k[1])) {
|
|
||||||
mCost = new Cost(getManaCost(), false);
|
|
||||||
} else {
|
|
||||||
mCost = new Cost(k[1], false);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder sbCost = new StringBuilder(k[0]);
|
|
||||||
if (!mCost.isOnlyManaCost()) {
|
|
||||||
sbCost.append("—");
|
|
||||||
} else {
|
|
||||||
sbCost.append(" ");
|
|
||||||
}
|
|
||||||
sbCost.append(mCost.toSimpleString());
|
|
||||||
sbBefore.append(sbCost).append(" (").append(inst.getReminderText()).append(")");
|
|
||||||
sbBefore.append("\r\n");
|
|
||||||
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|
||||||
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|
||||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||||
@@ -3813,7 +3797,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public final void addLeavesPlayCommand(final GameCommand c) {
|
public final void addLeavesPlayCommand(final GameCommand c) {
|
||||||
leavePlayCommandList.add(c);
|
leavePlayCommandList.add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStaticCommandList(Object[] objects) {
|
public void addStaticCommandList(Object[] objects) {
|
||||||
staticCommandList.add(objects);
|
staticCommandList.add(objects);
|
||||||
}
|
}
|
||||||
@@ -4315,10 +4299,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean clearChangedCardColors() {
|
public boolean clearChangedCardColors() {
|
||||||
boolean changed = hasChangedCardColors();
|
boolean changed = false;
|
||||||
|
|
||||||
|
if (!changedCardColorsByText.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardColorsByText.clear();
|
changedCardColorsByText.clear();
|
||||||
|
|
||||||
|
if (!changedCardTypesCharacterDefining.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardTypesCharacterDefining.clear();
|
changedCardTypesCharacterDefining.clear();
|
||||||
|
|
||||||
|
if (!changedCardColors.isEmpty())
|
||||||
|
changed = true;
|
||||||
changedCardColors.clear();
|
changedCardColors.clear();
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
@@ -4404,19 +4396,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasChangedCardColors() {
|
public Iterable<CardColor> getChangedCardColors() {
|
||||||
return !changedCardColorsByText.isEmpty() || !changedCardColorsCharacterDefining.isEmpty() || !changedCardColors.isEmpty();
|
return Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addColorByText(final ColorSet color, final long timestamp, final StaticAbility stAb) {
|
public void addColorByText(final ColorSet color, final long timestamp, final long staticId) {
|
||||||
changedCardColorsByText.put(timestamp, (long)stAb.getId(), new CardColor(color, false));
|
changedCardColorsByText.put(timestamp, staticId, new CardColor(color, false));
|
||||||
updateColorForView();
|
updateColorForView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final StaticAbility stAb) {
|
public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final long staticId, final boolean cda) {
|
||||||
(stAb != null && stAb.isCharacteristicDefining() ? changedCardColorsCharacterDefining : changedCardColors).put(
|
(cda ? changedCardColorsCharacterDefining : changedCardColors).put(timestamp, staticId, new CardColor(color, addToColors));
|
||||||
timestamp, stAb != null ? stAb.getId() : (long)0, new CardColor(color, addToColors)
|
|
||||||
);
|
|
||||||
updateColorForView();
|
updateColorForView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4443,20 +4433,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
public final ColorSet getColor(CardState state) {
|
public final ColorSet getColor(CardState state) {
|
||||||
byte colors = state.getColor();
|
byte colors = state.getColor();
|
||||||
for (final CardColor cc : Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values())) {
|
for (final CardColor cc : getChangedCardColors()) {
|
||||||
if (cc.additional()) {
|
if (cc.isAdditional()) {
|
||||||
colors |= cc.color().getColor();
|
colors |= cc.getColorMask();
|
||||||
} else {
|
} else {
|
||||||
colors = cc.color().getColor();
|
colors = cc.getColorMask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ColorSet.fromMask(colors);
|
return ColorSet.fromMask(colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record CardColor(ColorSet color, boolean additional) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int getCurrentLoyalty() {
|
public final int getCurrentLoyalty() {
|
||||||
return getCounters(CounterEnumType.LOYALTY);
|
return getCounters(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
@@ -4826,7 +4812,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public void addDraftAction(String s) {
|
public void addDraftAction(String s) {
|
||||||
draftActions.add(s);
|
draftActions.add(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int intensity = 0;
|
private int intensity = 0;
|
||||||
public final void addIntensity(final int n) {
|
public final void addIntensity(final int n) {
|
||||||
intensity += n;
|
intensity += n;
|
||||||
@@ -6875,7 +6861,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWebSlinged() {
|
public boolean isWebSlinged() {
|
||||||
return getCastSA() != null && getCastSA().isAlternativeCost(AlternativeCost.WebSlinging);
|
return getCastSA() != null & getCastSA().isAlternativeCost(AlternativeCost.WebSlinging);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSpecialized() {
|
public boolean isSpecialized() {
|
||||||
@@ -7185,62 +7171,51 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final String cantBeEnchantedByMsg(final Card aura) {
|
protected final boolean canBeEnchantedBy(final Card aura) {
|
||||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
return "No Enchant Keyword";
|
return false;
|
||||||
}
|
}
|
||||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String k = ki.getOriginal();
|
||||||
String v = kwt.getValidType();
|
String m[] = k.split(":");
|
||||||
String desc = kwt.getTypeDescription();
|
String v = m[1];
|
||||||
if (!isValid(v.split(","), aura.getController(), aura, null) || (!v.contains("inZone") && !isInPlay())) {
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, desc);
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!v.contains("inZone") && !isInPlay()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String cantBeEquippedByMsg(final Card equip, SpellAbility sa) {
|
protected final boolean canBeEquippedBy(final Card equip, SpellAbility sa) {
|
||||||
if (!isInPlay()) {
|
if (!isInPlay()) {
|
||||||
return getName() + " is not in play";
|
return false;
|
||||||
}
|
}
|
||||||
if (sa != null && sa.isEquip()) {
|
if (sa != null && sa.isEquip()) {
|
||||||
if (!isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa)) {
|
return isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa);
|
||||||
Equip eq = (Equip) sa.getKeyword();
|
|
||||||
return getName() + " is not " + Lang.nounWithAmount(1, eq.getValidDescription());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
if (!isCreature()) {
|
return isCreature();
|
||||||
return getName() + " is not a creature";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String cantBeFortifiedByMsg(final Card fort) {
|
protected boolean canBeFortifiedBy(final Card fort) {
|
||||||
if (!isLand()) {
|
return isLand() && isInPlay() && !fort.isLand();
|
||||||
return getName() + " is not a Land";
|
|
||||||
}
|
|
||||||
if (!isInPlay()) {
|
|
||||||
return getName() + " is not in play";
|
|
||||||
}
|
|
||||||
if (fort.isLand()) {
|
|
||||||
return fort.getName() + " is a Land";
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.game.GameEntity#canBeAttached(forge.game.card.Card, boolean)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
|
public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) {
|
||||||
|
// phase check there
|
||||||
if (isPhasedOut() && !attach.isPhasedOut()) {
|
if (isPhasedOut() && !attach.isPhasedOut()) {
|
||||||
return getName() + " is phased out";
|
return false;
|
||||||
}
|
}
|
||||||
return super.cantBeAttachedMsg(attach, sa, checkSBA);
|
|
||||||
|
return super.canBeAttached(attach, sa, checkSBA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) {
|
public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) {
|
||||||
@@ -7874,8 +7849,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
return currentState.getUntranslatedType();
|
return currentState.getUntranslatedType();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getTranslatedName() {
|
public String getUntranslatedOracle() {
|
||||||
return CardTranslation.getTranslatedName(this);
|
return currentState.getUntranslatedOracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package forge.game.card;
|
||||||
|
|
||||||
|
public class CardChangedName {
|
||||||
|
|
||||||
|
protected String newName;
|
||||||
|
protected boolean addNonLegendaryCreatureNames = false;
|
||||||
|
|
||||||
|
public CardChangedName(String newName, boolean addNonLegendaryCreatureNames) {
|
||||||
|
this.newName = newName;
|
||||||
|
this.addNonLegendaryCreatureNames = addNonLegendaryCreatureNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewName() {
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOverwrite() {
|
||||||
|
return newName != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAddNonLegendaryCreatureNames() {
|
||||||
|
return addNonLegendaryCreatureNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
forge-game/src/main/java/forge/game/card/CardColor.java
Normal file
45
forge-game/src/main/java/forge/game/card/CardColor.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Forge: Play Magic: the Gathering.
|
||||||
|
* Copyright (C) 2011 Forge Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package forge.game.card;
|
||||||
|
|
||||||
|
import forge.card.ColorSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Card_Color class.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Forge
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class CardColor {
|
||||||
|
private final byte colorMask;
|
||||||
|
public final byte getColorMask() {
|
||||||
|
return colorMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean additional;
|
||||||
|
public final boolean isAdditional() {
|
||||||
|
return this.additional;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardColor(final ColorSet colors, final boolean addToColors) {
|
||||||
|
this.colorMask = colors.getColor();
|
||||||
|
this.additional = addToColors;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -466,29 +466,29 @@ public class CardFactory {
|
|||||||
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), sa.getDecider());
|
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), sa.getDecider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase cause) {
|
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
|
||||||
final Card host = cause.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Map<String,String> origSVars = host.getSVars();
|
final Map<String,String> origSVars = host.getSVars();
|
||||||
final List<String> types = Lists.newArrayList();
|
final List<String> types = Lists.newArrayList();
|
||||||
final List<String> keywords = Lists.newArrayList();
|
final List<String> keywords = Lists.newArrayList();
|
||||||
boolean KWifNew = false;
|
boolean KWifNew = false;
|
||||||
final List<String> removeKeywords = Lists.newArrayList();
|
final List<String> removeKeywords = Lists.newArrayList();
|
||||||
List<String> creatureTypes = null;
|
List<String> creatureTypes = null;
|
||||||
final CardCloneStates result = new CardCloneStates(in, cause);
|
final CardCloneStates result = new CardCloneStates(in, sa);
|
||||||
|
|
||||||
final String newName = cause.getParam("NewName");
|
final String newName = sa.getParam("NewName");
|
||||||
ColorSet colors = null;
|
ColorSet colors = null;
|
||||||
|
|
||||||
if (cause.hasParam("AddTypes")) {
|
if (sa.hasParam("AddTypes")) {
|
||||||
types.addAll(Arrays.asList(cause.getParam("AddTypes").split(" & ")));
|
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes")) {
|
||||||
creatureTypes = ImmutableList.copyOf(cause.getParam("SetCreatureTypes").split(" "));
|
creatureTypes = ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddKeywords")) {
|
if (sa.hasParam("AddKeywords")) {
|
||||||
String kwString = cause.getParam("AddKeywords");
|
String kwString = sa.getParam("AddKeywords");
|
||||||
if (kwString.startsWith("IfNew ")) {
|
if (kwString.startsWith("IfNew ")) {
|
||||||
KWifNew = true;
|
KWifNew = true;
|
||||||
kwString = kwString.substring(6);
|
kwString = kwString.substring(6);
|
||||||
@@ -496,21 +496,21 @@ public class CardFactory {
|
|||||||
keywords.addAll(Arrays.asList(kwString.split(" & ")));
|
keywords.addAll(Arrays.asList(kwString.split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveKeywords")) {
|
if (sa.hasParam("RemoveKeywords")) {
|
||||||
removeKeywords.addAll(Arrays.asList(cause.getParam("RemoveKeywords").split(" & ")));
|
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddColors")) {
|
if (sa.hasParam("AddColors")) {
|
||||||
colors = ColorSet.fromNames(cause.getParam("AddColors").split(","));
|
colors = ColorSet.fromNames(sa.getParam("AddColors").split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColor")) {
|
if (sa.hasParam("SetColor")) {
|
||||||
colors = ColorSet.fromNames(cause.getParam("SetColor").split(","));
|
colors = ColorSet.fromNames(sa.getParam("SetColor").split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColorByManaCost")) {
|
||||||
if (cause.hasParam("SetManaCost")) {
|
if (sa.hasParam("SetManaCost")) {
|
||||||
colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost"))));
|
colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost"))));
|
||||||
} else {
|
} else {
|
||||||
colors = ColorSet.fromManaCost(host.getManaCost());
|
colors = ColorSet.fromManaCost(host.getManaCost());
|
||||||
}
|
}
|
||||||
@@ -522,55 +522,56 @@ public class CardFactory {
|
|||||||
// if something is cloning a facedown card, it only clones the
|
// if something is cloning a facedown card, it only clones the
|
||||||
// facedown state into original
|
// facedown state into original
|
||||||
final CardState ret = new CardState(out, CardStateName.Original);
|
final CardState ret = new CardState(out, CardStateName.Original);
|
||||||
ret.copyFrom(in.getFaceDownState(), false, cause);
|
ret.copyFrom(in.getFaceDownState(), false, sa);
|
||||||
result.put(CardStateName.Original, ret);
|
result.put(CardStateName.Original, ret);
|
||||||
} else if (in.isFlipCard()) {
|
} else if (in.isFlipCard()) {
|
||||||
// if something is cloning a flip card, copy both original and
|
// if something is cloning a flip card, copy both original and
|
||||||
// flipped state
|
// flipped state
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Flipped);
|
final CardState ret2 = new CardState(out, CardStateName.Flipped);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Flipped), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Flipped), false, sa);
|
||||||
result.put(CardStateName.Flipped, ret2);
|
result.put(CardStateName.Flipped, ret2);
|
||||||
} else if (in.hasState(CardStateName.Secondary)) {
|
} else if (in.hasState(CardStateName.Secondary)) {
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Secondary);
|
final CardState ret2 = new CardState(out, CardStateName.Secondary);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Secondary), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Secondary), false, sa);
|
||||||
result.put(CardStateName.Secondary, ret2);
|
result.put(CardStateName.Secondary, ret2);
|
||||||
} else if (in.isTransformable() && cause instanceof SpellAbility sa && (
|
} else if (in.isTransformable() && sa instanceof SpellAbility && (
|
||||||
ApiType.CopyPermanent.equals(sa.getApi()) ||
|
ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi()) ||
|
||||||
ApiType.CopySpellAbility.equals(sa.getApi()) ||
|
ApiType.CopySpellAbility.equals(((SpellAbility)sa).getApi()) ||
|
||||||
ApiType.ReplaceToken.equals(sa.getApi()))) {
|
ApiType.ReplaceToken.equals(((SpellAbility)sa).getApi())
|
||||||
|
)) {
|
||||||
// CopyPermanent can copy token
|
// CopyPermanent can copy token
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.Backside);
|
final CardState ret2 = new CardState(out, CardStateName.Backside);
|
||||||
ret2.copyFrom(in.getState(CardStateName.Backside), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.Backside), false, sa);
|
||||||
result.put(CardStateName.Backside, ret2);
|
result.put(CardStateName.Backside, ret2);
|
||||||
} else if (in.isSplitCard()) {
|
} else if (in.isSplitCard()) {
|
||||||
// for split cards, copy all three states
|
// for split cards, copy all three states
|
||||||
final CardState ret1 = new CardState(out, CardStateName.Original);
|
final CardState ret1 = new CardState(out, CardStateName.Original);
|
||||||
ret1.copyFrom(in.getState(CardStateName.Original), false, cause);
|
ret1.copyFrom(in.getState(CardStateName.Original), false, sa);
|
||||||
result.put(CardStateName.Original, ret1);
|
result.put(CardStateName.Original, ret1);
|
||||||
|
|
||||||
final CardState ret2 = new CardState(out, CardStateName.LeftSplit);
|
final CardState ret2 = new CardState(out, CardStateName.LeftSplit);
|
||||||
ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, cause);
|
ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, sa);
|
||||||
result.put(CardStateName.LeftSplit, ret2);
|
result.put(CardStateName.LeftSplit, ret2);
|
||||||
|
|
||||||
final CardState ret3 = new CardState(out, CardStateName.RightSplit);
|
final CardState ret3 = new CardState(out, CardStateName.RightSplit);
|
||||||
ret3.copyFrom(in.getState(CardStateName.RightSplit), false, cause);
|
ret3.copyFrom(in.getState(CardStateName.RightSplit), false, sa);
|
||||||
result.put(CardStateName.RightSplit, ret3);
|
result.put(CardStateName.RightSplit, ret3);
|
||||||
} else {
|
} else {
|
||||||
// in all other cases just copy the current state to original
|
// in all other cases just copy the current state to original
|
||||||
final CardState ret = new CardState(out, CardStateName.Original);
|
final CardState ret = new CardState(out, CardStateName.Original);
|
||||||
ret.copyFrom(in.getState(in.getCurrentStateName()), false, cause);
|
ret.copyFrom(in.getState(in.getCurrentStateName()), false, sa);
|
||||||
result.put(CardStateName.Original, ret);
|
result.put(CardStateName.Original, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,32 +581,32 @@ public class CardFactory {
|
|||||||
final CardState state = e.getValue();
|
final CardState state = e.getValue();
|
||||||
|
|
||||||
// has Embalm Condition for extra changes of Vizier of Many Faces
|
// has Embalm Condition for extra changes of Vizier of Many Faces
|
||||||
if (cause.hasParam("Embalm") && !out.isEmbalmed()) {
|
if (sa.hasParam("Embalm") && !out.isEmbalmed()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the names for the states
|
// update the names for the states
|
||||||
if (cause.hasParam("KeepName")) {
|
if (sa.hasParam("KeepName")) {
|
||||||
state.setName(originalState.getName());
|
state.setName(originalState.getName());
|
||||||
} else if (newName != null) {
|
} else if (newName != null) {
|
||||||
// convert NICKNAME descriptions?
|
// convert NICKNAME descriptions?
|
||||||
state.setName(newName);
|
state.setName(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("AddColors")) {
|
if (sa.hasParam("AddColors")) {
|
||||||
state.addColor(colors.getColor());
|
state.addColor(colors.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) {
|
||||||
state.setColor(colors.getColor());
|
state.setColor(colors.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("NonLegendary")) {
|
if (sa.hasParam("NonLegendary")) {
|
||||||
state.removeType(CardType.Supertype.Legendary);
|
state.removeType(CardType.Supertype.Legendary);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveCardTypes")) {
|
if (sa.hasParam("RemoveCardTypes")) {
|
||||||
state.removeCardTypes(cause.hasParam("RemoveSubTypes"));
|
state.removeCardTypes(sa.hasParam("RemoveSubTypes"));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.addType(types);
|
state.addType(types);
|
||||||
@@ -637,31 +638,31 @@ public class CardFactory {
|
|||||||
|
|
||||||
// CR 208.3 A noncreature object not on the battlefield has power or toughness only if it has a power and toughness printed on it.
|
// CR 208.3 A noncreature object not on the battlefield has power or toughness only if it has a power and toughness printed on it.
|
||||||
// currently only LKI can be trusted?
|
// currently only LKI can be trusted?
|
||||||
if ((cause.hasParam("SetPower") || cause.hasParam("SetToughness")) &&
|
if ((sa.hasParam("SetPower") || sa.hasParam("SetToughness")) &&
|
||||||
(state.getType().isCreature() || (originalState != null && in.getOriginalState(originalState.getStateName()).getBasePowerString() != null))) {
|
(state.getType().isCreature() || (originalState != null && in.getOriginalState(originalState.getStateName()).getBasePowerString() != null))) {
|
||||||
if (cause.hasParam("SetPower")) {
|
if (sa.hasParam("SetPower")) {
|
||||||
state.setBasePower(AbilityUtils.calculateAmount(host, cause.getParam("SetPower"), cause));
|
state.setBasePower(AbilityUtils.calculateAmount(host, sa.getParam("SetPower"), sa));
|
||||||
}
|
}
|
||||||
if (cause.hasParam("SetToughness")) {
|
if (sa.hasParam("SetToughness")) {
|
||||||
state.setBaseToughness(AbilityUtils.calculateAmount(host, cause.getParam("SetToughness"), cause));
|
state.setBaseToughness(AbilityUtils.calculateAmount(host, sa.getParam("SetToughness"), sa));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.getType().isPlaneswalker() && cause.hasParam("SetLoyalty")) {
|
if (state.getType().isPlaneswalker() && sa.hasParam("SetLoyalty")) {
|
||||||
state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, cause.getParam("SetLoyalty"), cause)));
|
state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, sa.getParam("SetLoyalty"), sa)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("RemoveCost")) {
|
if (sa.hasParam("RemoveCost")) {
|
||||||
state.setManaCost(ManaCost.NO_COST);
|
state.setManaCost(ManaCost.NO_COST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetManaCost")) {
|
if (sa.hasParam("SetManaCost")) {
|
||||||
state.setManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost"))));
|
state.setManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVars to add to clone
|
// SVars to add to clone
|
||||||
if (cause.hasParam("AddSVars") || cause.hasParam("GainTextSVars")) {
|
if (sa.hasParam("AddSVars") || sa.hasParam("GainTextSVars")) {
|
||||||
final String str = cause.getParamOrDefault("GainTextSVars", cause.getParam("AddSVars"));
|
final String str = sa.getParamOrDefault("GainTextSVars", sa.getParam("AddSVars"));
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualsVar = origSVars.get(s);
|
final String actualsVar = origSVars.get(s);
|
||||||
@@ -671,8 +672,8 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// triggers to add to clone
|
// triggers to add to clone
|
||||||
if (cause.hasParam("AddTriggers")) {
|
if (sa.hasParam("AddTriggers")) {
|
||||||
for (final String s : cause.getParam("AddTriggers").split(",")) {
|
for (final String s : sa.getParam("AddTriggers").split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualTrigger = origSVars.get(s);
|
final String actualTrigger = origSVars.get(s);
|
||||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true, state);
|
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true, state);
|
||||||
@@ -682,8 +683,8 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// abilities to add to clone
|
// abilities to add to clone
|
||||||
if (cause.hasParam("AddAbilities") || cause.hasParam("GainTextAbilities")) {
|
if (sa.hasParam("AddAbilities") || sa.hasParam("GainTextAbilities")) {
|
||||||
final String str = cause.getParamOrDefault("GainTextAbilities", cause.getParam("AddAbilities"));
|
final String str = sa.getParamOrDefault("GainTextAbilities", sa.getParam("AddAbilities"));
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualAbility = origSVars.get(s);
|
final String actualAbility = origSVars.get(s);
|
||||||
@@ -695,18 +696,18 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// static abilities to add to clone
|
// static abilities to add to clone
|
||||||
if (cause.hasParam("AddStaticAbilities")) {
|
if (sa.hasParam("AddStaticAbilities")) {
|
||||||
final String str = cause.getParam("AddStaticAbilities");
|
final String str = sa.getParam("AddStaticAbilities");
|
||||||
for (final String s : str.split(",")) {
|
for (final String s : str.split(",")) {
|
||||||
if (origSVars.containsKey(s)) {
|
if (origSVars.containsKey(s)) {
|
||||||
final String actualStatic = origSVars.get(s);
|
final String actualStatic = origSVars.get(s);
|
||||||
state.addStaticAbility(StaticAbility.create(actualStatic, out, cause.getCardState(), true));
|
state.addStaticAbility(StaticAbility.create(actualStatic, out, sa.getCardState(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("GainThisAbility") && cause instanceof SpellAbility sa) {
|
if (sa.hasParam("GainThisAbility") && sa instanceof SpellAbility) {
|
||||||
SpellAbility root = sa.getRootAbility();
|
SpellAbility root = ((SpellAbility) sa).getRootAbility();
|
||||||
|
|
||||||
// Aurora Shifter
|
// Aurora Shifter
|
||||||
if (root.isTrigger() && root.getTrigger().getSpawningAbility() != null) {
|
if (root.isTrigger() && root.getTrigger().getSpawningAbility() != null) {
|
||||||
@@ -723,35 +724,35 @@ public class CardFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special Rules for Embalm and Eternalize
|
// Special Rules for Embalm and Eternalize
|
||||||
if (cause.isEmbalm() && cause.isIntrinsic()) {
|
if (sa.isEmbalm() && sa.isIntrinsic()) {
|
||||||
String name = "embalm_" + TextUtil.fastReplace(
|
String name = "embalm_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isEternalize() && cause.isIntrinsic()) {
|
if (sa.isEternalize() && sa.isIntrinsic()) {
|
||||||
String name = "eternalize_" + TextUtil.fastReplace(
|
String name = "eternalize_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isKeyword(Keyword.OFFSPRING) && cause.isIntrinsic()) {
|
if (sa.isKeyword(Keyword.OFFSPRING) && sa.isIntrinsic()) {
|
||||||
String name = "offspring_" + TextUtil.fastReplace(
|
String name = "offspring_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.isKeyword(Keyword.SQUAD) && cause.isIntrinsic()) {
|
if (sa.isKeyword(Keyword.SQUAD) && sa.isIntrinsic()) {
|
||||||
String name = "squad_" + TextUtil.fastReplace(
|
String name = "squad_" + TextUtil.fastReplace(
|
||||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||||
" ", "_").toLowerCase();
|
" ", "_").toLowerCase();
|
||||||
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("GainTextOf") && originalState != null) {
|
if (sa.hasParam("GainTextOf") && originalState != null) {
|
||||||
state.setSetCode(originalState.getSetCode());
|
state.setSetCode(originalState.getSetCode());
|
||||||
state.setRarity(originalState.getRarity());
|
state.setRarity(originalState.getRarity());
|
||||||
state.setImageKey(originalState.getImageKey());
|
state.setImageKey(originalState.getImageKey());
|
||||||
@@ -763,27 +764,27 @@ public class CardFactory {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cause.hasParam("SetPower") && sta.hasParam("SetPower"))
|
if (sa.hasParam("SetPower") && sta.hasParam("SetPower"))
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
|
|
||||||
if (cause.hasParam("SetToughness") && sta.hasParam("SetToughness"))
|
if (sa.hasParam("SetToughness") && sta.hasParam("SetToughness"))
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
|
|
||||||
// currently only Changeling and similar should be affected by that
|
// currently only Changeling and similar should be affected by that
|
||||||
// other cards using AddType$ ChosenType should not
|
// other cards using AddType$ ChosenType should not
|
||||||
if (cause.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) {
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
}
|
}
|
||||||
if ((cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) {
|
if ((sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) {
|
||||||
state.removeStaticAbility(sta);
|
state.removeStaticAbility(sta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove some keywords
|
// remove some keywords
|
||||||
if (cause.hasParam("SetCreatureTypes")) {
|
if (sa.hasParam("SetCreatureTypes")) {
|
||||||
state.removeIntrinsicKeyword(Keyword.CHANGELING);
|
state.removeIntrinsicKeyword(Keyword.CHANGELING);
|
||||||
}
|
}
|
||||||
if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) {
|
if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) {
|
||||||
state.removeIntrinsicKeyword(Keyword.DEVOID);
|
state.removeIntrinsicKeyword(Keyword.DEVOID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2238,7 +2238,7 @@ public class CardFactoryUtil {
|
|||||||
final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
|
final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | "
|
||||||
+ "Secondary$ True | Optional$ True | CheckSVar$ "
|
+ "Secondary$ True | Optional$ True | CheckSVar$ "
|
||||||
+ "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount
|
+ "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount
|
||||||
+ " | Description$ CARDNAME - Dredge " + dredgeAmount;
|
+ " | AICheckDredge$ True | Description$ CARDNAME - Dredge " + dredgeAmount;
|
||||||
|
|
||||||
final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount;
|
final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount;
|
||||||
|
|
||||||
@@ -2852,23 +2852,6 @@ public class CardFactoryUtil {
|
|||||||
final SpellAbility sa = AbilityFactory.getAbility(sbClass.toString(), card);
|
final SpellAbility sa = AbilityFactory.getAbility(sbClass.toString(), card);
|
||||||
sa.setIntrinsic(intrinsic);
|
sa.setIntrinsic(intrinsic);
|
||||||
inst.addSpellAbility(sa);
|
inst.addSpellAbility(sa);
|
||||||
} else if (keyword.startsWith("Cleave")) {
|
|
||||||
final String[] k = keyword.split(":");
|
|
||||||
final Cost cost = new Cost(k[1], false);
|
|
||||||
|
|
||||||
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(cost);
|
|
||||||
|
|
||||||
final StringBuilder desc = new StringBuilder();
|
|
||||||
desc.append("Cleave ").append(cost.toSimpleString()).append(" (");
|
|
||||||
desc.append(inst.getReminderText());
|
|
||||||
desc.append(")");
|
|
||||||
|
|
||||||
newSA.setDescription(desc.toString());
|
|
||||||
newSA.putParam("Secondary", "True");
|
|
||||||
|
|
||||||
newSA.setAlternativeCost(AlternativeCost.Cleave);
|
|
||||||
newSA.setIntrinsic(intrinsic);
|
|
||||||
inst.addSpellAbility(newSA);
|
|
||||||
} else if (keyword.startsWith("Dash")) {
|
} else if (keyword.startsWith("Dash")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
final Cost dashCost = new Cost(k[1], false);
|
final Cost dashCost = new Cost(k[1], false);
|
||||||
|
|||||||
@@ -26,17 +26,12 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.staticability.StaticAbilityTapPowerValue;
|
import forge.game.staticability.StaticAbilityTapPowerValue;
|
||||||
import forge.util.IterableUtil;
|
import forge.util.IterableUtil;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.StreamUtil;
|
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collector;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -485,26 +480,4 @@ public class CardLists {
|
|||||||
// (b) including the last element
|
// (b) including the last element
|
||||||
return isSubsetSum(numList, sum) || isSubsetSum(numList, sum - last);
|
return isSubsetSum(numList, sum) || isSubsetSum(numList, sum - last);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDifferentNamesCount(Iterable<Card> cardList) {
|
|
||||||
// first part the ones with SpyKit, and already collect them via
|
|
||||||
Map<Boolean, List<Card>> parted = StreamUtil.stream(cardList).collect(Collectors
|
|
||||||
.partitioningBy(Card::hasNonLegendaryCreatureNames, Collector.of(ArrayList::new, (list, c) -> {
|
|
||||||
if (!c.hasNoName() && list.stream().noneMatch(c2 -> c.sharesNameWith(c2))) {
|
|
||||||
list.add(c);
|
|
||||||
}
|
|
||||||
}, (l1, l2) -> {
|
|
||||||
l1.addAll(l2);
|
|
||||||
return l1;
|
|
||||||
})));
|
|
||||||
List<Card> preList = parted.get(Boolean.FALSE);
|
|
||||||
|
|
||||||
// then try to apply the SpyKit ones
|
|
||||||
for (Card c : parted.get(Boolean.TRUE)) {
|
|
||||||
if (preList.stream().noneMatch(c2 -> c.sharesNameWith(c2))) {
|
|
||||||
preList.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return preList.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ public class CardProperty {
|
|||||||
if (!card.sharesNameWith(name)) {
|
if (!card.sharesNameWith(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notnamed")) {
|
||||||
|
String name = TextUtil.fastReplace(property.substring(8), ";", ","); // workaround for card name with ","
|
||||||
|
name = TextUtil.fastReplace(name, "_", " ");
|
||||||
|
if (card.sharesNameWith(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (property.equals("NamedCard")) {
|
} else if (property.equals("NamedCard")) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (String name : source.getNamedCards()) {
|
for (String name : source.getNamedCards()) {
|
||||||
@@ -1558,6 +1564,8 @@ public class CardProperty {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notattacking")) {
|
||||||
|
return null == combat || !combat.isAttacking(card);
|
||||||
} else if (property.startsWith("enlistedThisCombat")) {
|
} else if (property.startsWith("enlistedThisCombat")) {
|
||||||
if (card.getEnlistedThisCombat() == false) return false;
|
if (card.getEnlistedThisCombat() == false) return false;
|
||||||
} else if (property.startsWith("attackedThisCombat")) {
|
} else if (property.startsWith("attackedThisCombat")) {
|
||||||
@@ -1611,6 +1619,8 @@ public class CardProperty {
|
|||||||
if (Collections.disjoint(combat.getAttackersBlockedBy(source), combat.getAttackersBlockedBy(card))) {
|
if (Collections.disjoint(combat.getAttackersBlockedBy(source), combat.getAttackersBlockedBy(card))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("notblocking")) {
|
||||||
|
return null == combat || !combat.isBlocking(card);
|
||||||
}
|
}
|
||||||
// Nex predicates refer to past combat and don't need a reference to actual combat
|
// Nex predicates refer to past combat and don't need a reference to actual combat
|
||||||
else if (property.equals("blocked")) {
|
else if (property.equals("blocked")) {
|
||||||
@@ -2063,6 +2073,16 @@ public class CardProperty {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("NotTriggered")) {
|
||||||
|
final String key = property.substring("NotTriggered".length());
|
||||||
|
if (spellAbility instanceof SpellAbility) {
|
||||||
|
SpellAbility sa = (SpellAbility) spellAbility;
|
||||||
|
if (card.equals(sa.getRootAbility().getTriggeringObject(AbilityKey.fromString(key)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (property.startsWith("NotDefined")) {
|
} else if (property.startsWith("NotDefined")) {
|
||||||
final String key = property.substring("NotDefined".length());
|
final String key = property.substring("NotDefined".length());
|
||||||
if (AbilityUtils.getDefinedCards(source, key, spellAbility).contains(card)) {
|
if (AbilityUtils.getDefinedCards(source, key, spellAbility).contains(card)) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import forge.game.card.CardView.CardStateView;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordCollection;
|
import forge.game.keyword.KeywordCollection;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.keyword.KeywordWithType;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.spellability.LandAbility;
|
import forge.game.spellability.LandAbility;
|
||||||
@@ -41,7 +40,6 @@ import forge.game.spellability.SpellAbilityPredicates;
|
|||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.util.CardTranslation;
|
|
||||||
import forge.util.ITranslatable;
|
import forge.util.ITranslatable;
|
||||||
import forge.util.IterableUtil;
|
import forge.util.IterableUtil;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
@@ -502,8 +500,15 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
String desc = "";
|
String desc = "";
|
||||||
String extra = "";
|
String extra = "";
|
||||||
for (KeywordInterface ki : this.getCachedKeyword(Keyword.ENCHANT)) {
|
for (KeywordInterface ki : this.getCachedKeyword(Keyword.ENCHANT)) {
|
||||||
if (ki instanceof KeywordWithType kwt) {
|
String o = ki.getOriginal();
|
||||||
desc = kwt.getTypeDescription();
|
String m[] = o.split(":");
|
||||||
|
if (m.length > 2) {
|
||||||
|
desc = m[2];
|
||||||
|
} else {
|
||||||
|
desc = m[1];
|
||||||
|
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
|
||||||
|
desc = desc.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -941,7 +946,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTranslatedName() {
|
public String getUntranslatedOracle() {
|
||||||
return CardTranslation.getTranslatedName(this);
|
return getOracleText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1095,7 +1095,7 @@ public class CardView extends GameEntityView {
|
|||||||
if (c.getGame() != null) {
|
if (c.getGame() != null) {
|
||||||
if (c.hasPerpetual()) currentStateView.updateColors(c);
|
if (c.hasPerpetual()) currentStateView.updateColors(c);
|
||||||
else currentStateView.updateColors(currentState);
|
else currentStateView.updateColors(currentState);
|
||||||
currentStateView.updateHasChangeColors(c.hasChangedCardColors());
|
currentStateView.updateHasChangeColors(!Iterables.isEmpty(c.getChangedCardColors()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
currentStateView.updateLoyalty(currentState);
|
currentStateView.updateLoyalty(currentState);
|
||||||
@@ -1841,8 +1841,8 @@ public class CardView extends GameEntityView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTranslatedName() {
|
public String getUntranslatedOracle() {
|
||||||
return CardTranslation.getTranslatedName(this);
|
return getOracleText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public record PerpetualColors(long timestamp, ColorSet colors, boolean overwrite
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyEffect(Card c) {
|
public void applyEffect(Card c) {
|
||||||
c.addColor(colors, !overwrite, timestamp, null);
|
c.addColor(colors, !overwrite, timestamp, (long) 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public record PerpetualIncorporate(long timestamp, ManaCost incorporate) impleme
|
|||||||
ColorSet colors = ColorSet.fromMask(incorporate.getColorProfile());
|
ColorSet colors = ColorSet.fromMask(incorporate.getColorProfile());
|
||||||
final ManaCost newCost = ManaCost.combine(c.getManaCost(), incorporate);
|
final ManaCost newCost = ManaCost.combine(c.getManaCost(), incorporate);
|
||||||
c.addChangedManaCost(newCost, timestamp, (long) 0);
|
c.addChangedManaCost(newCost, timestamp, (long) 0);
|
||||||
c.addColor(colors, true, timestamp, null);
|
c.addColor(colors, true, timestamp, (long) 0, false);
|
||||||
c.updateManaCostForView();
|
c.updateManaCostForView();
|
||||||
|
|
||||||
if (c.getFirstSpellAbility() != null) {
|
if (c.getFirstSpellAbility() != null) {
|
||||||
|
|||||||
@@ -237,17 +237,17 @@ public class Cost implements Serializable {
|
|||||||
CostPartMana parsedMana = null;
|
CostPartMana parsedMana = null;
|
||||||
for (String part : parts) {
|
for (String part : parts) {
|
||||||
if (part.startsWith("XMin")) {
|
if (part.startsWith("XMin")) {
|
||||||
xMin = part;
|
xMin = (part);
|
||||||
} else if ("Mandatory".equals(part)) {
|
} else if ("Mandatory".equals(part)) {
|
||||||
this.isMandatory = true;
|
this.isMandatory = true;
|
||||||
} else {
|
} else {
|
||||||
CostPart cp = parseCostPart(part, tapCost, untapCost);
|
CostPart cp = parseCostPart(part, tapCost, untapCost);
|
||||||
if (null != cp)
|
if (null != cp)
|
||||||
if (cp instanceof CostPartMana p) {
|
if (cp instanceof CostPartMana) {
|
||||||
parsedMana = p;
|
parsedMana = (CostPartMana) cp;
|
||||||
} else {
|
} else {
|
||||||
if (cp instanceof CostPartWithList p) {
|
if (cp instanceof CostPartWithList) {
|
||||||
p.setIntrinsic(intrinsic);
|
((CostPartWithList)cp).setIntrinsic(intrinsic);
|
||||||
}
|
}
|
||||||
this.costParts.add(cp);
|
this.costParts.add(cp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.game.cost;
|
package forge.game.cost;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -28,6 +29,7 @@ import forge.util.TextUtil;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Class CostDiscard.
|
* The Class CostDiscard.
|
||||||
@@ -61,20 +63,11 @@ public class CostDiscard extends CostPartWithList {
|
|||||||
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
|
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
|
||||||
final Card source = ability.getHostCard();
|
final Card source = ability.getHostCard();
|
||||||
String type = this.getType();
|
String type = this.getType();
|
||||||
|
|
||||||
boolean differentNames = false;
|
|
||||||
if (type.contains("+WithDifferentNames")) {
|
|
||||||
type = type.replace("+WithDifferentNames", "");
|
|
||||||
differentNames = true;
|
|
||||||
}
|
|
||||||
CardCollectionView handList = payer.canDiscardBy(ability, effect) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
|
CardCollectionView handList = payer.canDiscardBy(ability, effect) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
|
||||||
|
|
||||||
if (!type.equals("Random")) {
|
if (!type.equals("Random")) {
|
||||||
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
|
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
|
||||||
}
|
}
|
||||||
if (differentNames) {
|
|
||||||
return CardLists.getDifferentNamesCount(handList);
|
|
||||||
}
|
|
||||||
return handList.size();
|
return handList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +92,7 @@ public class CostDiscard extends CostPartWithList {
|
|||||||
else if (this.getType().equals("LastDrawn")) {
|
else if (this.getType().equals("LastDrawn")) {
|
||||||
sb.append("the last card you drew this turn");
|
sb.append("the last card you drew this turn");
|
||||||
}
|
}
|
||||||
else if (this.getType().contains("+WithDifferentNames")) {
|
else if (this.getType().equals("DifferentNames")) {
|
||||||
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "Card")).append(" with different names");
|
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "Card")).append(" with different names");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -152,17 +145,21 @@ public class CostDiscard extends CostPartWithList {
|
|||||||
final Card c = payer.getLastDrawnCard();
|
final Card c = payer.getLastDrawnCard();
|
||||||
return handList.contains(c);
|
return handList.contains(c);
|
||||||
}
|
}
|
||||||
|
else if (type.equals("DifferentNames")) {
|
||||||
|
Set<String> cardNames = Sets.newHashSet();
|
||||||
|
for (Card c : handList) {
|
||||||
|
if (!c.hasNoName()) {
|
||||||
|
cardNames.add(c.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cardNames.size() >= amount;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
boolean sameName = false;
|
boolean sameName = false;
|
||||||
boolean differentNames = false;
|
|
||||||
if (type.contains("+WithSameName")) {
|
if (type.contains("+WithSameName")) {
|
||||||
sameName = true;
|
sameName = true;
|
||||||
type = TextUtil.fastReplace(type, "+WithSameName", "");
|
type = TextUtil.fastReplace(type, "+WithSameName", "");
|
||||||
}
|
}
|
||||||
if (type.contains("+WithDifferentNames")) {
|
|
||||||
type = type.replace("+WithDifferentNames", "");
|
|
||||||
differentNames = true;
|
|
||||||
}
|
|
||||||
if (type.contains("ChosenColor") && !source.hasChosenColor()) {
|
if (type.contains("ChosenColor") && !source.hasChosenColor()) {
|
||||||
//color hasn't been chosen yet, so skip getValidCards
|
//color hasn't been chosen yet, so skip getValidCards
|
||||||
} else if (!type.equals("Random") && !type.contains("X")) {
|
} else if (!type.equals("Random") && !type.contains("X")) {
|
||||||
@@ -176,10 +173,6 @@ public class CostDiscard extends CostPartWithList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (differentNames) {
|
|
||||||
if (CardLists.getDifferentNamesCount(handList) < amount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int adjustment = 0;
|
int adjustment = 0;
|
||||||
if (source.isInZone(ZoneType.Hand) && payer.equals(source.getOwner())) {
|
if (source.isInZone(ZoneType.Hand) && payer.equals(source.getOwner())) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.game.cost;
|
package forge.game.cost;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
@@ -30,6 +31,7 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.Lang;
|
import forge.util.Lang;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Class CostSacrifice.
|
* The Class CostSacrifice.
|
||||||
@@ -72,7 +74,16 @@ public class CostSacrifice extends CostPartWithList {
|
|||||||
}
|
}
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability, effect));
|
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability, effect));
|
||||||
if (differentNames) {
|
if (differentNames) {
|
||||||
return CardLists.getDifferentNamesCount(typeList);
|
// TODO rewrite with sharesName to respect Spy Kit
|
||||||
|
final Set<String> crdname = Sets.newHashSet();
|
||||||
|
for (final Card card : typeList) {
|
||||||
|
String name = card.getName();
|
||||||
|
// CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common
|
||||||
|
if (!card.hasNoName()) {
|
||||||
|
crdname.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crdname.size();
|
||||||
}
|
}
|
||||||
return typeList.size();
|
return typeList.size();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package forge.game.event;
|
|||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
|
||||||
public record GameEventCardAttachment(Card equipment, GameEntity oldEntity, GameEntity newTarget) implements GameEvent {
|
public record GameEventCardAttachment(Card equipment, GameEntity newTarget, GameEntity oldEntity) implements GameEvent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.game.event;
|
|||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.util.CardTranslation;
|
||||||
import forge.util.Lang;
|
import forge.util.Lang;
|
||||||
|
|
||||||
public record GameEventDoorChanged(Player activatingPlayer, Card card, CardStateName state, boolean unlock) implements GameEvent {
|
public record GameEventDoorChanged(Player activatingPlayer, Card card, CardStateName state, boolean unlock) implements GameEvent {
|
||||||
@@ -14,7 +15,7 @@ public record GameEventDoorChanged(Player activatingPlayer, Card card, CardState
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String doorName = card.getState(state).getTranslatedName();
|
String doorName = CardTranslation.getTranslatedName(card.getState(state));
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(activatingPlayer);
|
sb.append(activatingPlayer);
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ public class Equip extends KeywordWithCost {
|
|||||||
public Equip() {
|
public Equip() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValidDescription() { return type; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void parse(String details) {
|
protected void parse(String details) {
|
||||||
String[] k = details.split(":");
|
String[] k = details.split(":");
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ public enum Keyword {
|
|||||||
CHANGELING("Changeling", SimpleKeyword.class, true, "This card is every creature type."),
|
CHANGELING("Changeling", SimpleKeyword.class, true, "This card is every creature type."),
|
||||||
CHOOSE_A_BACKGROUND("Choose a Background", Partner.class, true, "You can have a Background as a second commander."),
|
CHOOSE_A_BACKGROUND("Choose a Background", Partner.class, true, "You can have a Background as a second commander."),
|
||||||
CIPHER("Cipher", SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."),
|
CIPHER("Cipher", SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."),
|
||||||
CLEAVE("Cleave", KeywordWithCost.class, false, "You may cast this spell for its cleave cost. If you do, remove the words in square brackets."),
|
|
||||||
COMPANION("Companion", Companion.class, true, "Reveal your companion from outside the game if your deck meets the companion restriction."),
|
COMPANION("Companion", Companion.class, true, "Reveal your companion from outside the game if your deck meets the companion restriction."),
|
||||||
COMPLEATED("Compleated", SimpleKeyword.class, true, "This planeswalker enters with two fewer loyalty counters for each Phyrexian mana symbol life was paid for."),
|
COMPLEATED("Compleated", SimpleKeyword.class, true, "This planeswalker enters with two fewer loyalty counters for each Phyrexian mana symbol life was paid for."),
|
||||||
CONSPIRE("Conspire", SimpleKeyword.class, false, "As an additional cost to cast this spell, you may tap two untapped creatures you control that each share a color with it. If you do, copy it."),
|
CONSPIRE("Conspire", SimpleKeyword.class, false, "As an additional cost to cast this spell, you may tap two untapped creatures you control that each share a color with it. If you do, copy it."),
|
||||||
@@ -142,8 +141,6 @@ public enum Keyword {
|
|||||||
OFFSPRING("Offspring", KeywordWithCost.class, false, "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it."),
|
OFFSPRING("Offspring", KeywordWithCost.class, false, "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it."),
|
||||||
OVERLOAD("Overload", KeywordWithCost.class, false, "You may cast this spell for its overload cost. If you do, change its text by replacing all instances of \"target\" with \"each.\""),
|
OVERLOAD("Overload", KeywordWithCost.class, false, "You may cast this spell for its overload cost. If you do, change its text by replacing all instances of \"target\" with \"each.\""),
|
||||||
PARTNER("Partner", Partner.class, true, "You can have two commanders if both have partner."),
|
PARTNER("Partner", Partner.class, true, "You can have two commanders if both have partner."),
|
||||||
PARTNER_SURVIVOR("Partner - Survivors", Partner.class, true, "You can have two commanders if both have this ability."),
|
|
||||||
PARTNER_FATHER_AND_SON("Partner - Father & Son", Partner.class, true, "You can have two commanders if both have this ability."),
|
|
||||||
PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."),
|
PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."),
|
||||||
PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."),
|
PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."),
|
||||||
PLOT("Plot", KeywordWithCost.class, false, "You may pay %s and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery."),
|
PLOT("Plot", KeywordWithCost.class, false, "You may pay %s and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery."),
|
||||||
|
|||||||
@@ -3,50 +3,38 @@ package forge.game.keyword;
|
|||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
|
|
||||||
public class KeywordWithType extends KeywordInstance<KeywordWithType> {
|
public class KeywordWithType extends KeywordInstance<KeywordWithType> {
|
||||||
protected String type = null;
|
protected String type;
|
||||||
protected String descType = null;
|
|
||||||
protected String reminderType = null;
|
|
||||||
|
|
||||||
public String getValidType() { return type; }
|
|
||||||
public String getTypeDescription() { return descType; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void parse(String details) {
|
protected void parse(String details) {
|
||||||
String k[];
|
if (CardType.isACardType(details)) {
|
||||||
if (details.contains(":")) {
|
type = details.toLowerCase();
|
||||||
|
} else if (details.contains(":")) {
|
||||||
switch (getKeyword()) {
|
switch (getKeyword()) {
|
||||||
case AFFINITY:
|
case AFFINITY:
|
||||||
|
type = details.split(":")[1];
|
||||||
|
// type lists defined by rules should not be changed by TextChange in reminder text
|
||||||
|
if (type.equalsIgnoreCase("Outlaw")) {
|
||||||
|
type = "Assassin, Mercenary, Pirate, Rogue, and/or Warlock";
|
||||||
|
} else if (type.equalsIgnoreCase("historic permanent")) {
|
||||||
|
type = "artifact, legendary, and/or Saga permanent";
|
||||||
|
}
|
||||||
|
break;
|
||||||
case BANDSWITH:
|
case BANDSWITH:
|
||||||
case ENCHANT:
|
|
||||||
case HEXPROOF:
|
case HEXPROOF:
|
||||||
case LANDWALK:
|
case LANDWALK:
|
||||||
k = details.split(":");
|
type = details.split(":")[1];
|
||||||
type = k[0];
|
|
||||||
descType = k[1];
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
k = details.split(":");
|
type = details.split(":")[0];
|
||||||
type = k[1];
|
|
||||||
descType = k[0];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
descType = type = details;
|
type = details;
|
||||||
}
|
|
||||||
|
|
||||||
if (CardType.isACardType(descType) || "Permanent".equals(descType) || "Player".equals(descType) || "Opponent".equals(descType)) {
|
|
||||||
descType = descType.toLowerCase();
|
|
||||||
} else if (descType.equalsIgnoreCase("Outlaw")) {
|
|
||||||
reminderType = "Assassin, Mercenary, Pirate, Rogue, and/or Warlock";
|
|
||||||
} else if (type.equalsIgnoreCase("historic permanent")) {
|
|
||||||
reminderType = "artifact, legendary, and/or Saga permanent";
|
|
||||||
}
|
|
||||||
if (reminderType == null) {
|
|
||||||
reminderType = type;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String formatReminderText(String reminderText) {
|
protected String formatReminderText(String reminderText) {
|
||||||
return String.format(reminderText, reminderType);
|
return String.format(reminderText, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
|
|||||||
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
|
String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this);
|
||||||
ITranslatable nameSource = getHostName(this);
|
ITranslatable nameSource = getHostName(this);
|
||||||
desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource);
|
desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource);
|
||||||
String translatedName = nameSource.getTranslatedName();
|
String translatedName = CardTranslation.getTranslatedName(nameSource);
|
||||||
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
||||||
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
||||||
if (desc.contains("EFFECTSOURCE")) {
|
if (desc.contains("EFFECTSOURCE")) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getRestrictions().canPlay(c, this)) {
|
if (!(this.getRestrictions().canPlay(c, this))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ public enum AlternativeCost {
|
|||||||
Awaken,
|
Awaken,
|
||||||
Bestow,
|
Bestow,
|
||||||
Blitz,
|
Blitz,
|
||||||
Cleave,
|
|
||||||
Dash,
|
Dash,
|
||||||
Disturb,
|
Disturb,
|
||||||
Emerge,
|
Emerge,
|
||||||
|
|||||||
@@ -646,10 +646,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return isAlternativeCost(AlternativeCost.Blitz);
|
return isAlternativeCost(AlternativeCost.Blitz);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isCleave() {
|
|
||||||
return isAlternativeCost(AlternativeCost.Cleave);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean isDash() {
|
public final boolean isDash() {
|
||||||
return isAlternativeCost(AlternativeCost.Dash);
|
return isAlternativeCost(AlternativeCost.Dash);
|
||||||
}
|
}
|
||||||
@@ -1125,7 +1121,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
if (node.getHostCard() != null && !desc.isEmpty()) {
|
if (node.getHostCard() != null && !desc.isEmpty()) {
|
||||||
ITranslatable nameSource = getHostName(node);
|
ITranslatable nameSource = getHostName(node);
|
||||||
desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource);
|
desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource);
|
||||||
String translatedName = nameSource.getTranslatedName();
|
String translatedName = CardTranslation.getTranslatedName(nameSource);
|
||||||
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
||||||
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
||||||
if (node.getOriginalHost() != null) {
|
if (node.getOriginalHost() != null) {
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
|||||||
if (hasParam("Description") && !this.isSuppressed()) {
|
if (hasParam("Description") && !this.isSuppressed()) {
|
||||||
ITranslatable nameSource = getHostName(this);
|
ITranslatable nameSource = getHostName(this);
|
||||||
String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), nameSource);
|
String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), nameSource);
|
||||||
String translatedName = nameSource.getTranslatedName();
|
String translatedName = CardTranslation.getTranslatedName(nameSource);
|
||||||
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
|
||||||
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
public class StaticAbilityCantAttach {
|
public class StaticAbilityCantAttach {
|
||||||
|
|
||||||
public static StaticAbility cantAttach(final GameEntity target, final Card card, boolean checkSBA) {
|
public static boolean cantAttach(final GameEntity target, final Card card, boolean checkSBA) {
|
||||||
// CantTarget static abilities
|
// CantTarget static abilities
|
||||||
for (final Card ca : target.getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
for (final Card ca : target.getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||||
@@ -15,11 +15,11 @@ public class StaticAbilityCantAttach {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (applyCantAttachAbility(stAb, card, target, checkSBA)) {
|
if (applyCantAttachAbility(stAb, card, target, checkSBA)) {
|
||||||
return stAb;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean applyCantAttachAbility(final StaticAbility stAb, final Card card, final GameEntity target, boolean checkSBA) {
|
public static boolean applyCantAttachAbility(final StaticAbility stAb, final Card card, final GameEntity target, boolean checkSBA) {
|
||||||
|
|||||||
@@ -623,7 +623,7 @@ public final class StaticAbilityContinuous {
|
|||||||
// Mana cost
|
// Mana cost
|
||||||
affectedCard.addChangedManaCost(state.getManaCost(), se.getTimestamp(), stAb.getId());
|
affectedCard.addChangedManaCost(state.getManaCost(), se.getTimestamp(), stAb.getId());
|
||||||
// color
|
// color
|
||||||
affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), se.getTimestamp(), stAb);
|
affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), se.getTimestamp(), stAb.getId());
|
||||||
// type
|
// type
|
||||||
affectedCard.addChangedCardTypesByText(new CardType(state.getType()), se.getTimestamp(), stAb.getId());
|
affectedCard.addChangedCardTypesByText(new CardType(state.getType()), se.getTimestamp(), stAb.getId());
|
||||||
// abilities
|
// abilities
|
||||||
@@ -856,7 +856,7 @@ public final class StaticAbilityContinuous {
|
|||||||
|
|
||||||
// add colors
|
// add colors
|
||||||
if (addColors != null) {
|
if (addColors != null) {
|
||||||
affectedCard.addColor(addColors, !overwriteColors, se.getTimestamp(), stAb);
|
affectedCard.addColor(addColors, !overwriteColors, se.getTimestamp(), stAb.getId(), stAb.isCharacteristicDefining());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layer == StaticAbilityLayer.RULES) {
|
if (layer == StaticAbilityLayer.RULES) {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public abstract class Trigger extends TriggerReplacementBase {
|
|||||||
String desc = getParam("TriggerDescription");
|
String desc = getParam("TriggerDescription");
|
||||||
if (!desc.contains("ABILITY")) {
|
if (!desc.contains("ABILITY")) {
|
||||||
desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), nameSource);
|
desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), nameSource);
|
||||||
String translatedName = nameSource.getTranslatedName();
|
String translatedName = CardTranslation.getTranslatedName(nameSource);
|
||||||
desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName);
|
desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName);
|
||||||
desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName));
|
desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName));
|
||||||
if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) {
|
if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) {
|
||||||
@@ -218,7 +218,7 @@ public abstract class Trigger extends TriggerReplacementBase {
|
|||||||
result = TextUtil.fastReplace(result, "ABILITY", saDesc);
|
result = TextUtil.fastReplace(result, "ABILITY", saDesc);
|
||||||
|
|
||||||
result = CardTranslation.translateMultipleDescriptionText(result, sa.getHostCard());
|
result = CardTranslation.translateMultipleDescriptionText(result, sa.getHostCard());
|
||||||
String translatedName = sa.getHostCard().getTranslatedName();
|
String translatedName = CardTranslation.getTranslatedName(sa.getHostCard());
|
||||||
result = TextUtil.fastReplace(result,"CARDNAME", translatedName);
|
result = TextUtil.fastReplace(result,"CARDNAME", translatedName);
|
||||||
result = TextUtil.fastReplace(result,"NICKNAME", Lang.getInstance().getNickName(translatedName));
|
result = TextUtil.fastReplace(result,"NICKNAME", Lang.getInstance().getNickName(translatedName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Expressions;
|
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,13 +59,8 @@ public class TriggerAttackerBlocked extends Trigger {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasParam("ValidBlocker")) {
|
if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) {
|
||||||
String param = getParamOrDefault("ValidBlockerAmount", "GE1");
|
return false;
|
||||||
int attackers = CardLists.getValidCardCount((CardCollection) runParams.get(AbilityKey.Blockers), getParam("ValidBlocker"), getHostCard().getController(), getHostCard(), this);
|
|
||||||
int amount = AbilityUtils.calculateAmount(getHostCard(), param.substring(2), this);
|
|
||||||
if (!Expressions.compare(attackers, param, amount)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ public enum TriggerType {
|
|||||||
SpellCast(TriggerSpellAbilityCastOrCopy.class),
|
SpellCast(TriggerSpellAbilityCastOrCopy.class),
|
||||||
SpellCastOrCopy(TriggerSpellAbilityCastOrCopy.class),
|
SpellCastOrCopy(TriggerSpellAbilityCastOrCopy.class),
|
||||||
SpellCopy(TriggerSpellAbilityCastOrCopy.class),
|
SpellCopy(TriggerSpellAbilityCastOrCopy.class),
|
||||||
Stationed(TriggerCrewedSaddled.class),
|
|
||||||
Surveil(TriggerSurveil.class),
|
Surveil(TriggerSurveil.class),
|
||||||
TakesInitiative(TriggerTakesInitiative.class),
|
TakesInitiative(TriggerTakesInitiative.class),
|
||||||
TapAll(TriggerTapAll.class),
|
TapAll(TriggerTapAll.class),
|
||||||
|
|||||||
@@ -453,16 +453,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sp.isKeyword(Keyword.STATION) && (sp.getHostCard().getType().hasSubtype("Spacecraft") || (sp.getHostCard().getType().hasSubtype("Planet")))) {
|
|
||||||
Iterable<Card> crews = sp.getPaidList("Tapped", true);
|
|
||||||
if (crews != null) {
|
|
||||||
for (Card c : crews) {
|
|
||||||
Map<AbilityKey, Object> stationParams = AbilityKey.mapFromCard(sp.getHostCard());
|
|
||||||
stationParams.put(AbilityKey.Crew, c);
|
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.Stationed, stationParams, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Run Copy triggers
|
// Run Copy triggers
|
||||||
if (sp.isSpell()) {
|
if (sp.isSpell()) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-android</artifactId>
|
<artifactId>forge-gui-android</artifactId>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<groupId>org.robolectric</groupId>
|
<groupId>org.robolectric</groupId>
|
||||||
<artifactId>android-all</artifactId>
|
<artifactId>android-all</artifactId>
|
||||||
<!-- update version: 16-robolectric-13921718 but needs to fix Android 16 Edge to edge enforcement -->
|
<!-- update version: 16-robolectric-13921718 but needs to fix Android 16 Edge to edge enforcement -->
|
||||||
<version>15-robolectric-13954326</version>
|
<version>15-robolectric-12650502</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-android</artifactId>
|
<artifactId>sentry-android</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.19.1</version>
|
||||||
<type>aar</type>
|
<type>aar</type>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-android-core</artifactId>
|
<artifactId>sentry-android-core</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.19.1</version>
|
||||||
<type>aar</type>
|
<type>aar</type>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-android-ndk</artifactId>
|
<artifactId>sentry-android-ndk</artifactId>
|
||||||
<version>8.21.1</version>
|
<version>8.19.1</version>
|
||||||
<type>aar</type>
|
<type>aar</type>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-desktop</artifactId>
|
<artifactId>forge-gui-desktop</artifactId>
|
||||||
|
|||||||
@@ -69,7 +69,22 @@ public class PlayerDetailsPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FSkinProp iconFromZone(ZoneType zoneType) {
|
public static FSkinProp iconFromZone(ZoneType zoneType) {
|
||||||
return FSkinProp.iconFromZone(zoneType, false);
|
switch (zoneType) {
|
||||||
|
case Hand: return FSkinProp.IMG_ZONE_HAND;
|
||||||
|
case Library: return FSkinProp.IMG_ZONE_LIBRARY;
|
||||||
|
case Graveyard: return FSkinProp.IMG_ZONE_GRAVEYARD;
|
||||||
|
case Exile: return FSkinProp.IMG_ZONE_EXILE;
|
||||||
|
case Sideboard: return FSkinProp.IMG_ZONE_SIDEBOARD;
|
||||||
|
case Flashback: return FSkinProp.IMG_ZONE_FLASHBACK;
|
||||||
|
case Command: return FSkinProp.IMG_ZONE_COMMAND; //IMG_PLANESWALKER
|
||||||
|
case PlanarDeck: return FSkinProp.IMG_ZONE_PLANAR;
|
||||||
|
case SchemeDeck: return FSkinProp.IMG_ZONE_SCHEME;
|
||||||
|
case AttractionDeck: return FSkinProp.IMG_ZONE_ATTRACTION;
|
||||||
|
case ContraptionDeck: return FSkinProp.IMG_ZONE_CONTRAPTION;
|
||||||
|
case Ante: return FSkinProp.IMG_ZONE_ANTE;
|
||||||
|
case Junkyard: return FSkinProp.IMG_ZONE_JUNKYARD;
|
||||||
|
default: return FSkinProp.IMG_HDZONE_LIBRARY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds various labels to pool area JPanel container. */
|
/** Adds various labels to pool area JPanel container. */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-ios</artifactId>
|
<artifactId>forge-gui-ios</artifactId>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.oshi</groupId>
|
<groupId>com.github.oshi</groupId>
|
||||||
<artifactId>oshi-core</artifactId>
|
<artifactId>oshi-core</artifactId>
|
||||||
<version>6.9.0</version>
|
<version>6.8.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-mobile</artifactId>
|
<artifactId>forge-gui-mobile</artifactId>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public class AdventureEventData implements Serializable {
|
public class AdventureEventData implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final int JUMPSTART_TO_PICK_FROM = 6;
|
|
||||||
public transient BoosterDraft draft;
|
public transient BoosterDraft draft;
|
||||||
public AdventureEventParticipant[] participants;
|
public AdventureEventParticipant[] participants;
|
||||||
public int rounds;
|
public int rounds;
|
||||||
@@ -79,6 +78,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
matchesLost = other.matchesLost;
|
matchesLost = other.matchesLost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Deck[] getRewardPacks(int count) {
|
public Deck[] getRewardPacks(int count) {
|
||||||
Deck[] ret = new Deck[count];
|
Deck[] ret = new Deck[count];
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@@ -104,17 +104,173 @@ public class AdventureEventData implements Serializable {
|
|||||||
return;
|
return;
|
||||||
cardBlockName = cardBlock.getName();
|
cardBlockName = cardBlock.getName();
|
||||||
|
|
||||||
setupDraftRewards();
|
//Below all to be fully generated in later release
|
||||||
|
rewardPacks = getRewardPacks(3);
|
||||||
|
generateParticipants(7);
|
||||||
|
if (cardBlock != null) {
|
||||||
|
packConfiguration = getBoosterConfiguration(cardBlock);
|
||||||
|
|
||||||
|
rewards = new AdventureEventData.AdventureEventReward[4];
|
||||||
|
AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward();
|
||||||
|
r0.minWins = 0;
|
||||||
|
r0.maxWins = 0;
|
||||||
|
r0.cardRewards = new Deck[]{rewardPacks[0]};
|
||||||
|
rewards[0] = r0;
|
||||||
|
r1.minWins = 1;
|
||||||
|
r1.maxWins = 3;
|
||||||
|
r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]};
|
||||||
|
rewards[1] = r1;
|
||||||
|
r2.minWins = 2;
|
||||||
|
r2.maxWins = 3;
|
||||||
|
r2.itemRewards = new String[]{"Challenge Coin"};
|
||||||
|
rewards[2] = r2;
|
||||||
|
}
|
||||||
} else if (format == AdventureEventController.EventFormat.Jumpstart) {
|
} else if (format == AdventureEventController.EventFormat.Jumpstart) {
|
||||||
|
int numPacksToPickFrom = 6;
|
||||||
|
generateParticipants(7);
|
||||||
|
|
||||||
cardBlock = pickJumpstartCardBlock();
|
cardBlock = pickJumpstartCardBlock();
|
||||||
if (cardBlock == null)
|
if (cardBlock == null)
|
||||||
return;
|
return;
|
||||||
cardBlockName = cardBlock.getName();
|
cardBlockName = cardBlock.getName();
|
||||||
|
|
||||||
jumpstartBoosters = AdventureEventController.instance().getJumpstartBoosters(cardBlock, JUMPSTART_TO_PICK_FROM);
|
jumpstartBoosters = AdventureEventController.instance().getJumpstartBoosters(cardBlock, numPacksToPickFrom);
|
||||||
|
|
||||||
packConfiguration = new String[]{cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode()};
|
packConfiguration = new String[]{cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode()};
|
||||||
|
|
||||||
setupJumpstartRewards();
|
for (AdventureEventParticipant participant : participants) {
|
||||||
|
List<Deck> availableOptions = AdventureEventController.instance().getJumpstartBoosters(cardBlock, numPacksToPickFrom);
|
||||||
|
List<Deck> chosenPacks = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<String, List<Deck>> themeMap = new HashMap<>();
|
||||||
|
|
||||||
|
//1. Search for matching themes from deck names, fill deck with them if possible
|
||||||
|
for (Deck option : availableOptions) {
|
||||||
|
// This matches up theme for all except DMU - with only 2 per color the next part will handle that
|
||||||
|
String theme = option.getName().replaceAll("\\d$", "").trim();
|
||||||
|
if (!themeMap.containsKey(theme)) {
|
||||||
|
themeMap.put(theme, new ArrayList<>());
|
||||||
|
}
|
||||||
|
themeMap.get(theme).add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
String themeAdded = "";
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
for (int i = packConfiguration.length - chosenPacks.size(); i > 1; i--) {
|
||||||
|
if (themeAdded.isEmpty()) {
|
||||||
|
for (String theme : themeMap.keySet()) {
|
||||||
|
if (themeMap.get(theme).size() >= i) {
|
||||||
|
themeAdded = theme;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (themeAdded.isEmpty()) {
|
||||||
|
done = true;
|
||||||
|
} else {
|
||||||
|
chosenPacks.addAll(themeMap.get(themeAdded).subList(0, Math.min(themeMap.get(themeAdded).size(), packConfiguration.length - chosenPacks.size())));
|
||||||
|
availableOptions.removeAll(themeMap.get(themeAdded));
|
||||||
|
themeMap.remove(themeAdded);
|
||||||
|
themeAdded = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//2. Fill remaining slots with colors already picked whenever possible
|
||||||
|
Map<String, List<Deck>> colorMap = new HashMap<>();
|
||||||
|
for (Deck option : availableOptions) {
|
||||||
|
if (option.getTags().contains("black"))
|
||||||
|
colorMap.computeIfAbsent("black", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("blue"))
|
||||||
|
colorMap.computeIfAbsent("blue", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("green"))
|
||||||
|
colorMap.computeIfAbsent("green", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("red"))
|
||||||
|
colorMap.computeIfAbsent("red", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("white"))
|
||||||
|
colorMap.computeIfAbsent("white", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("multicolor"))
|
||||||
|
colorMap.computeIfAbsent("multicolor", (k) -> new ArrayList<>()).add(option);
|
||||||
|
if (option.getTags().contains("colorless"))
|
||||||
|
colorMap.computeIfAbsent("colorless", (k) -> new ArrayList<>()).add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
done = false;
|
||||||
|
String colorAdded = "";
|
||||||
|
while (!done) {
|
||||||
|
List<String> colorsAlreadyPicked = new ArrayList<>();
|
||||||
|
for (Deck picked : chosenPacks) {
|
||||||
|
if (picked.getTags().contains("black")) colorsAlreadyPicked.add("black");
|
||||||
|
if (picked.getTags().contains("blue")) colorsAlreadyPicked.add("blue");
|
||||||
|
if (picked.getTags().contains("green")) colorsAlreadyPicked.add("green");
|
||||||
|
if (picked.getTags().contains("red")) colorsAlreadyPicked.add("red");
|
||||||
|
if (picked.getTags().contains("white")) colorsAlreadyPicked.add("white");
|
||||||
|
if (picked.getTags().contains("multicolor")) colorsAlreadyPicked.add("multicolor");
|
||||||
|
if (picked.getTags().contains("colorless")) colorsAlreadyPicked.add("colorless");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (colorAdded.isEmpty() && !colorsAlreadyPicked.isEmpty()) {
|
||||||
|
String colorToTry = Aggregates.removeRandom(colorsAlreadyPicked);
|
||||||
|
for (Deck toCheck : availableOptions) {
|
||||||
|
if (toCheck.getTags().contains(colorToTry)) {
|
||||||
|
colorAdded = colorToTry;
|
||||||
|
chosenPacks.add(toCheck);
|
||||||
|
availableOptions.remove(toCheck);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//3. If no matching color found and need more packs, add any available at random.
|
||||||
|
if (packConfiguration.length > chosenPacks.size() && colorAdded.isEmpty() && !availableOptions.isEmpty()) {
|
||||||
|
chosenPacks.add(Aggregates.removeRandom(availableOptions));
|
||||||
|
colorAdded = "";
|
||||||
|
} else {
|
||||||
|
done = colorAdded.isEmpty() || packConfiguration.length <= chosenPacks.size();
|
||||||
|
colorAdded = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
participant.registeredDeck = new Deck();
|
||||||
|
for (Deck chosen : chosenPacks) {
|
||||||
|
participant.registeredDeck.getMain().addAllFlat(chosen.getMain().toFlatList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rewards = new AdventureEventData.AdventureEventReward[4];
|
||||||
|
AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward();
|
||||||
|
AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward();
|
||||||
|
|
||||||
|
RewardData r0gold = new RewardData();
|
||||||
|
r0gold.count = 100;
|
||||||
|
r0gold.type = "gold";
|
||||||
|
r0.rewards = new RewardData[]{r0gold};
|
||||||
|
r0.minWins = 1;
|
||||||
|
r0.maxWins = 1;
|
||||||
|
rewards[0] = r0;
|
||||||
|
RewardData r1gold = new RewardData();
|
||||||
|
r1gold.count = 200;
|
||||||
|
r1gold.type = "gold";
|
||||||
|
r1.rewards = new RewardData[]{r1gold};
|
||||||
|
r1.minWins = 2;
|
||||||
|
r1.maxWins = 2;
|
||||||
|
rewards[1] = r1;
|
||||||
|
r2.minWins = 3;
|
||||||
|
r2.maxWins = 3;
|
||||||
|
RewardData r2gold = new RewardData();
|
||||||
|
r2gold.count = 500;
|
||||||
|
r2gold.type = "gold";
|
||||||
|
r2.rewards = new RewardData[]{r2gold};
|
||||||
|
rewards[2] = r2;
|
||||||
|
r3.minWins = 0;
|
||||||
|
r3.maxWins = 3;
|
||||||
|
rewards[3] = r3;
|
||||||
|
//r3 will be the selected card packs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +292,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
Random placeholder = MyRandom.getRandom();
|
Random placeholder = MyRandom.getRandom();
|
||||||
MyRandom.setRandom(getEventRandom());
|
MyRandom.setRandom(getEventRandom());
|
||||||
if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)) {
|
if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)) {
|
||||||
draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration, participants.length);
|
draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration, 8);
|
||||||
registeredDeck = draft.getHumanPlayer().getDeck();
|
registeredDeck = draft.getHumanPlayer().getDeck();
|
||||||
assignPlayerNames(draft);
|
assignPlayerNames(draft);
|
||||||
}
|
}
|
||||||
@@ -153,7 +309,6 @@ public class AdventureEventData implements Serializable {
|
|||||||
private static final Predicate<CardEdition> filterStandard = FModel.getFormats().getStandard().editionLegalPredicate;
|
private static final Predicate<CardEdition> filterStandard = FModel.getFormats().getStandard().editionLegalPredicate;
|
||||||
|
|
||||||
public static Predicate<CardEdition> selectSetPool() {
|
public static Predicate<CardEdition> selectSetPool() {
|
||||||
// Should we negate any of these to avoid overlap?
|
|
||||||
final int rollD100 = MyRandom.getRandom().nextInt(100);
|
final int rollD100 = MyRandom.getRandom().nextInt(100);
|
||||||
Predicate<CardEdition> rolledFilter;
|
Predicate<CardEdition> rolledFilter;
|
||||||
if (rollD100 < 30) {
|
if (rollD100 < 30) {
|
||||||
@@ -168,6 +323,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
return rolledFilter;
|
return rolledFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private CardBlock pickWeightedCardBlock() {
|
private CardBlock pickWeightedCardBlock() {
|
||||||
CardEdition.Collection editions = FModel.getMagicDb().getEditions();
|
CardEdition.Collection editions = FModel.getMagicDb().getEditions();
|
||||||
ConfigData configData = Config.instance().getConfigData();
|
ConfigData configData = Config.instance().getConfigData();
|
||||||
@@ -276,66 +432,6 @@ public class AdventureEventData implements Serializable {
|
|||||||
return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks);
|
return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDraftRewards() {
|
|
||||||
//Below all to be fully generated in later release
|
|
||||||
rewardPacks = getRewardPacks(3);
|
|
||||||
if (cardBlock != null) {
|
|
||||||
packConfiguration = getBoosterConfiguration(cardBlock);
|
|
||||||
|
|
||||||
rewards = new AdventureEventData.AdventureEventReward[4];
|
|
||||||
AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward();
|
|
||||||
r0.minWins = 0;
|
|
||||||
r0.maxWins = 0;
|
|
||||||
r0.cardRewards = new Deck[]{rewardPacks[0]};
|
|
||||||
rewards[0] = r0;
|
|
||||||
r1.minWins = 1;
|
|
||||||
r1.maxWins = 3;
|
|
||||||
r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]};
|
|
||||||
rewards[1] = r1;
|
|
||||||
r2.minWins = 2;
|
|
||||||
r2.maxWins = 3;
|
|
||||||
r2.itemRewards = new String[]{"Challenge Coin"};
|
|
||||||
rewards[2] = r2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupJumpstartRewards() {
|
|
||||||
rewards = new AdventureEventData.AdventureEventReward[4];
|
|
||||||
AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward();
|
|
||||||
AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward();
|
|
||||||
|
|
||||||
RewardData r0gold = new RewardData();
|
|
||||||
r0gold.count = 100;
|
|
||||||
r0gold.type = "gold";
|
|
||||||
r0.rewards = new RewardData[]{r0gold};
|
|
||||||
r0.minWins = 1;
|
|
||||||
r0.maxWins = 1;
|
|
||||||
rewards[0] = r0;
|
|
||||||
RewardData r1gold = new RewardData();
|
|
||||||
r1gold.count = 200;
|
|
||||||
r1gold.type = "gold";
|
|
||||||
r1.rewards = new RewardData[]{r1gold};
|
|
||||||
r1.minWins = 2;
|
|
||||||
r1.maxWins = 2;
|
|
||||||
rewards[1] = r1;
|
|
||||||
r2.minWins = 3;
|
|
||||||
r2.maxWins = 3;
|
|
||||||
RewardData r2gold = new RewardData();
|
|
||||||
r2gold.count = 500;
|
|
||||||
r2gold.type = "gold";
|
|
||||||
r2.rewards = new RewardData[]{r2gold};
|
|
||||||
rewards[2] = r2;
|
|
||||||
r3.minWins = 0;
|
|
||||||
r3.maxWins = 3;
|
|
||||||
rewards[3] = r3;
|
|
||||||
//r3 will be the selected card packs
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String[] getBoosterConfiguration(CardBlock selectedBlock) {
|
public String[] getBoosterConfiguration(CardBlock selectedBlock) {
|
||||||
Random placeholder = MyRandom.getRandom();
|
Random placeholder = MyRandom.getRandom();
|
||||||
@@ -369,106 +465,6 @@ public class AdventureEventData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
participants[numberOfOpponents] = getHumanPlayer();
|
participants[numberOfOpponents] = getHumanPlayer();
|
||||||
|
|
||||||
if (format == AdventureEventController.EventFormat.Jumpstart) {
|
|
||||||
for (AdventureEventParticipant participant : participants) {
|
|
||||||
List<Deck> availableOptions = AdventureEventController.instance().getJumpstartBoosters(cardBlock, JUMPSTART_TO_PICK_FROM);
|
|
||||||
List<Deck> chosenPacks = new ArrayList<>();
|
|
||||||
|
|
||||||
Map<String, List<Deck>> themeMap = new HashMap<>();
|
|
||||||
|
|
||||||
//1. Search for matching themes from deck names, fill deck with them if possible
|
|
||||||
for (Deck option : availableOptions) {
|
|
||||||
// This matches up theme for all except DMU - with only 2 per color the next part will handle that
|
|
||||||
String theme = option.getName().replaceAll("\\d$", "").trim();
|
|
||||||
if (!themeMap.containsKey(theme)) {
|
|
||||||
themeMap.put(theme, new ArrayList<>());
|
|
||||||
}
|
|
||||||
themeMap.get(theme).add(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
String themeAdded = "";
|
|
||||||
boolean done = false;
|
|
||||||
while (!done) {
|
|
||||||
for (int i = packConfiguration.length - chosenPacks.size(); i > 1; i--) {
|
|
||||||
if (themeAdded.isEmpty()) {
|
|
||||||
for (String theme : themeMap.keySet()) {
|
|
||||||
if (themeMap.get(theme).size() >= i) {
|
|
||||||
themeAdded = theme;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (themeAdded.isEmpty()) {
|
|
||||||
done = true;
|
|
||||||
} else {
|
|
||||||
chosenPacks.addAll(themeMap.get(themeAdded).subList(0, Math.min(themeMap.get(themeAdded).size(), packConfiguration.length - chosenPacks.size())));
|
|
||||||
availableOptions.removeAll(themeMap.get(themeAdded));
|
|
||||||
themeMap.remove(themeAdded);
|
|
||||||
themeAdded = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//2. Fill remaining slots with colors already picked whenever possible
|
|
||||||
Map<String, List<Deck>> colorMap = new HashMap<>();
|
|
||||||
for (Deck option : availableOptions) {
|
|
||||||
if (option.getTags().contains("black"))
|
|
||||||
colorMap.computeIfAbsent("black", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("blue"))
|
|
||||||
colorMap.computeIfAbsent("blue", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("green"))
|
|
||||||
colorMap.computeIfAbsent("green", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("red"))
|
|
||||||
colorMap.computeIfAbsent("red", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("white"))
|
|
||||||
colorMap.computeIfAbsent("white", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("multicolor"))
|
|
||||||
colorMap.computeIfAbsent("multicolor", (k) -> new ArrayList<>()).add(option);
|
|
||||||
if (option.getTags().contains("colorless"))
|
|
||||||
colorMap.computeIfAbsent("colorless", (k) -> new ArrayList<>()).add(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
done = false;
|
|
||||||
String colorAdded = "";
|
|
||||||
while (!done) {
|
|
||||||
List<String> colorsAlreadyPicked = new ArrayList<>();
|
|
||||||
for (Deck picked : chosenPacks) {
|
|
||||||
if (picked.getTags().contains("black")) colorsAlreadyPicked.add("black");
|
|
||||||
if (picked.getTags().contains("blue")) colorsAlreadyPicked.add("blue");
|
|
||||||
if (picked.getTags().contains("green")) colorsAlreadyPicked.add("green");
|
|
||||||
if (picked.getTags().contains("red")) colorsAlreadyPicked.add("red");
|
|
||||||
if (picked.getTags().contains("white")) colorsAlreadyPicked.add("white");
|
|
||||||
if (picked.getTags().contains("multicolor")) colorsAlreadyPicked.add("multicolor");
|
|
||||||
if (picked.getTags().contains("colorless")) colorsAlreadyPicked.add("colorless");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (colorAdded.isEmpty() && !colorsAlreadyPicked.isEmpty()) {
|
|
||||||
String colorToTry = Aggregates.removeRandom(colorsAlreadyPicked);
|
|
||||||
for (Deck toCheck : availableOptions) {
|
|
||||||
if (toCheck.getTags().contains(colorToTry)) {
|
|
||||||
colorAdded = colorToTry;
|
|
||||||
chosenPacks.add(toCheck);
|
|
||||||
availableOptions.remove(toCheck);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//3. If no matching color found and need more packs, add any available at random.
|
|
||||||
if (packConfiguration.length > chosenPacks.size() && colorAdded.isEmpty() && !availableOptions.isEmpty()) {
|
|
||||||
chosenPacks.add(Aggregates.removeRandom(availableOptions));
|
|
||||||
colorAdded = "";
|
|
||||||
} else {
|
|
||||||
done = colorAdded.isEmpty() || packConfiguration.length <= chosenPacks.size();
|
|
||||||
colorAdded = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
participant.registeredDeck = new Deck();
|
|
||||||
for (Deck chosen : chosenPacks) {
|
|
||||||
participant.registeredDeck.getMain().addAllFlat(chosen.getMain().toFlatList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignPlayerNames(BoosterDraft draft) {
|
private void assignPlayerNames(BoosterDraft draft) {
|
||||||
@@ -521,31 +517,6 @@ public class AdventureEventData implements Serializable {
|
|||||||
}
|
}
|
||||||
matches.get(round).add(match);
|
matches.get(round).add(match);
|
||||||
}
|
}
|
||||||
} else if (style == AdventureEventController.EventStyle.RoundRobin) {
|
|
||||||
// In a roundrobin everyone plays everyone else once
|
|
||||||
// We do have this logic already in ForgeTOurnament, we should see if we could reuse it
|
|
||||||
matches.put(round, new ArrayList<>());
|
|
||||||
activePlayers = Arrays.stream(participants).collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (round > 1) {
|
|
||||||
AdventureEventParticipant pivot = activePlayers.remove(0);
|
|
||||||
for(int i = 1; i < round; i++) {
|
|
||||||
// Rotate X amount of players, where X is the current round-1
|
|
||||||
AdventureEventParticipant rotate = activePlayers.remove(0);
|
|
||||||
activePlayers.add(rotate);
|
|
||||||
}
|
|
||||||
activePlayers.add(0, pivot);
|
|
||||||
}
|
|
||||||
|
|
||||||
int numPlayers = activePlayers.size();
|
|
||||||
for (int i = 0; i < numPlayers / 2; i++) {
|
|
||||||
AdventureEventMatch match = new AdventureEventMatch();
|
|
||||||
match.p1 = activePlayers.get(i);
|
|
||||||
match.p2 = activePlayers.get(numPlayers - i - 1);
|
|
||||||
matches.get(round).add(match);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.out.println(style + " not yet implemented!!!");
|
|
||||||
}
|
}
|
||||||
return matches.get(currentRound);
|
return matches.get(currentRound);
|
||||||
}
|
}
|
||||||
@@ -608,58 +579,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//todo: more robust logic for event types that can be won without perfect record (Swiss w/cut, round robin)
|
//todo: more robust logic for event types that can be won without perfect record (Swiss w/cut, round robin)
|
||||||
if (style == AdventureEventController.EventStyle.Bracket) {
|
playerWon = matchesLost == 0 || matchesWon == rounds;
|
||||||
playerWon = matchesLost == 0 || matchesWon == rounds;
|
|
||||||
} else if (style == AdventureEventController.EventStyle.RoundRobin) {
|
|
||||||
if (matchesWon == rounds) {
|
|
||||||
playerWon = true;
|
|
||||||
} else {
|
|
||||||
//If multiple players are tied for first, only the one with the best tiebreaker wins
|
|
||||||
List<AdventureEventParticipant> topPlayers = new ArrayList<>();
|
|
||||||
int bestRecord = 0;
|
|
||||||
for (AdventureEventParticipant p : participants) {
|
|
||||||
if (p.wins > bestRecord) {
|
|
||||||
bestRecord = p.wins;
|
|
||||||
topPlayers.clear();
|
|
||||||
topPlayers.add(p);
|
|
||||||
} else if (p.wins == bestRecord) {
|
|
||||||
topPlayers.add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (topPlayers.size() == 1) {
|
|
||||||
playerWon = topPlayers.get(0).getName().equals(getHumanPlayer().getName());
|
|
||||||
} else {
|
|
||||||
//multiple players tied for first, use tiebreaker
|
|
||||||
Map<AdventureEventParticipant, Integer> tiebreakers = new HashMap<>();
|
|
||||||
for (AdventureEventParticipant p : topPlayers) {
|
|
||||||
int tb = 0;
|
|
||||||
for (AdventureEventMatch m : matches.values().stream().flatMap(List::stream).collect(Collectors.toList())) {
|
|
||||||
if (m.p1 == p && m.winner != null && m.winner != p) {
|
|
||||||
tb += m.p2.wins;
|
|
||||||
} else if (m.p2 == p && m.winner != null && m.winner != p) {
|
|
||||||
tb += m.p1.wins;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tiebreakers.put(p, tb);
|
|
||||||
}
|
|
||||||
int bestTiebreaker = 0;
|
|
||||||
AdventureEventParticipant winner = null;
|
|
||||||
boolean tie = false;
|
|
||||||
for (AdventureEventParticipant p : tiebreakers.keySet()) {
|
|
||||||
if (tiebreakers.get(p) > bestTiebreaker) {
|
|
||||||
bestTiebreaker = tiebreakers.get(p);
|
|
||||||
winner = p;
|
|
||||||
tie = false;
|
|
||||||
} else if (tiebreakers.get(p) == bestTiebreaker) {
|
|
||||||
tie = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
playerWon = !tie && winner != null && winner.getName().equals(getHumanPlayer().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
playerWon = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventStatus = AdventureEventController.EventStatus.Awarded;
|
eventStatus = AdventureEventController.EventStatus.Awarded;
|
||||||
}
|
}
|
||||||
@@ -889,7 +809,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
public boolean isNoSell = false;
|
public boolean isNoSell = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PairingStyle {
|
enum PairingStyle {
|
||||||
SingleElimination,
|
SingleElimination,
|
||||||
DoubleElimination,
|
DoubleElimination,
|
||||||
Swiss,
|
Swiss,
|
||||||
|
|||||||
@@ -1194,7 +1194,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeItem(String name) {
|
public void removeItem(String name) {
|
||||||
inventoryItems.stream().filter(itemData -> name.equalsIgnoreCase(itemData.name)).findFirst().ifPresent(this::removeItem);
|
ItemData item = ItemListData.getItem(name);
|
||||||
|
if (item != null)
|
||||||
|
removeItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeItem(ItemData item) {
|
public void removeItem(ItemData item) {
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ public class DuelScene extends ForgeScene {
|
|||||||
|
|
||||||
currentEnemy = enemy.getData();
|
currentEnemy = enemy.getData();
|
||||||
boolean bossBattle = currentEnemy.boss;
|
boolean bossBattle = currentEnemy.boss;
|
||||||
for (int i = 0; i < playerCount && currentEnemy != null; i++) {
|
for (int i = 0; i < 8 && currentEnemy != null; i++) {
|
||||||
Deck deck;
|
Deck deck;
|
||||||
|
|
||||||
if (this.chaosBattle) { //random challenge for chaos mode
|
if (this.chaosBattle) { //random challenge for chaos mode
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
|||||||
static PointOfInterestChanges changes;
|
static PointOfInterestChanges changes;
|
||||||
|
|
||||||
private Array<DialogData> entryDialog;
|
private Array<DialogData> entryDialog;
|
||||||
private AdventureEventData.AdventureEventMatch humanMatch = null;
|
|
||||||
|
|
||||||
private int packsSelected = 0; //Used for meta drafts, booster drafts will use existing logic.
|
private int packsSelected = 0; //Used for meta drafts, booster drafts will use existing logic.
|
||||||
|
|
||||||
@@ -491,7 +490,7 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startRound() {
|
public void startRound() {
|
||||||
for (AdventureEventData.AdventureEventMatch match : currentEvent.getMatches(currentEvent.currentRound)) {
|
for (AdventureEventData.AdventureEventMatch match : currentEvent.matches.get(currentEvent.currentRound)) {
|
||||||
match.round = currentEvent.currentRound;
|
match.round = currentEvent.currentRound;
|
||||||
if (match.winner != null) continue;
|
if (match.winner != null) continue;
|
||||||
|
|
||||||
@@ -540,6 +539,8 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AdventureEventData.AdventureEventMatch humanMatch = null;
|
||||||
|
|
||||||
public void setWinner(boolean winner, boolean isArena) {
|
public void setWinner(boolean winner, boolean isArena) {
|
||||||
if (winner) {
|
if (winner) {
|
||||||
humanMatch.winner = humanMatch.p1;
|
humanMatch.winner = humanMatch.p1;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import forge.StaticData;
|
|||||||
import forge.adventure.data.AdventureEventData;
|
import forge.adventure.data.AdventureEventData;
|
||||||
import forge.adventure.player.AdventurePlayer;
|
import forge.adventure.player.AdventurePlayer;
|
||||||
import forge.adventure.pointofintrest.PointOfInterestChanges;
|
import forge.adventure.pointofintrest.PointOfInterestChanges;
|
||||||
import forge.card.CardEdition;
|
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.item.BoosterPack;
|
import forge.item.BoosterPack;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
@@ -100,9 +99,8 @@ public class AdventureEventController implements Serializable {
|
|||||||
|
|
||||||
AdventureEventData e;
|
AdventureEventData e;
|
||||||
|
|
||||||
// After a certain amount of wins, stop offering jump start events
|
// TODO After a certain amount of wins, stop offering jump start events
|
||||||
if (Current.player().getStatistic().totalWins() < 10 &&
|
if (random.nextInt(10) <= 2) {
|
||||||
random.nextInt(10) <= 2) {
|
|
||||||
e = new AdventureEventData(eventSeed, EventFormat.Jumpstart);
|
e = new AdventureEventData(eventSeed, EventFormat.Jumpstart);
|
||||||
} else {
|
} else {
|
||||||
e = new AdventureEventData(eventSeed, EventFormat.Draft);
|
e = new AdventureEventData(eventSeed, EventFormat.Draft);
|
||||||
@@ -112,27 +110,12 @@ public class AdventureEventController implements Serializable {
|
|||||||
//covers cases where (somehow) editions that do not match the event style have been picked up
|
//covers cases where (somehow) editions that do not match the event style have been picked up
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the chosen event seed recommends a four-person pod, run it as a RoundRobin
|
|
||||||
// Set can be null when it is only a meta set such as some Jumpstart events.
|
|
||||||
CardEdition firstSet = e.cardBlock.getSets().isEmpty() ? null : e.cardBlock.getSets().get(0);
|
|
||||||
int podSize = firstSet == null ? 8 : firstSet.getDraftOptions().getRecommendedPodSize();
|
|
||||||
|
|
||||||
e.sourceID = pointID;
|
e.sourceID = pointID;
|
||||||
e.eventOrigin = eventOrigin;
|
e.eventOrigin = eventOrigin;
|
||||||
e.style = podSize == 4 ? EventStyle.RoundRobin : style;
|
e.eventRules = new AdventureEventData.AdventureEventRules(e.format, changes == null ? 1f : changes.getTownPriceModifier());
|
||||||
|
e.style = style;
|
||||||
|
|
||||||
AdventureEventData.PairingStyle pairingStyle;
|
switch (style) {
|
||||||
if (e.style == EventStyle.RoundRobin) {
|
|
||||||
pairingStyle = AdventureEventData.PairingStyle.RoundRobin;
|
|
||||||
} else {
|
|
||||||
pairingStyle = AdventureEventData.PairingStyle.SingleElimination;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.eventRules = new AdventureEventData.AdventureEventRules(e.format, pairingStyle, changes == null ? 1f : changes.getTownPriceModifier());
|
|
||||||
e.generateParticipants(podSize - 1); //-1 to account for the player
|
|
||||||
|
|
||||||
switch (e.style) {
|
|
||||||
case Swiss:
|
case Swiss:
|
||||||
case Bracket:
|
case Bracket:
|
||||||
e.rounds = (e.participants.length / 2) - 1;
|
e.rounds = (e.participants.length / 2) - 1;
|
||||||
@@ -156,8 +139,9 @@ public class AdventureEventController implements Serializable {
|
|||||||
output.setComment(setCode);
|
output.setComment(setCode);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
public Deck generateBoosterByColor(String color)
|
||||||
|
{
|
||||||
|
|
||||||
public Deck generateBoosterByColor(String color) {
|
|
||||||
List<PaperCard> cards = BoosterPack.fromColor(color).getCards();
|
List<PaperCard> cards = BoosterPack.fromColor(color).getCards();
|
||||||
Deck output = new Deck();
|
Deck output = new Deck();
|
||||||
output.getMain().add(cards);
|
output.getMain().add(cards);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import forge.itemmanager.ItemManager.ContextMenuBuilder;
|
|||||||
import forge.itemmanager.ItemManagerConfig;
|
import forge.itemmanager.ItemManagerConfig;
|
||||||
import forge.itemmanager.filters.ItemFilter;
|
import forge.itemmanager.filters.ItemFilter;
|
||||||
import forge.localinstance.properties.ForgePreferences.FPref;
|
import forge.localinstance.properties.ForgePreferences.FPref;
|
||||||
import forge.localinstance.skin.FSkinProp;
|
|
||||||
import forge.menu.*;
|
import forge.menu.*;
|
||||||
import forge.model.FModel;
|
import forge.model.FModel;
|
||||||
import forge.screens.FScreen;
|
import forge.screens.FScreen;
|
||||||
@@ -410,7 +409,18 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FImage iconFromDeckSection(DeckSection deckSection) {
|
public static FImage iconFromDeckSection(DeckSection deckSection) {
|
||||||
return FSkin.getImages().get(FSkinProp.iconFromDeckSection(deckSection, Forge.hdbuttons));
|
return switch (deckSection) {
|
||||||
|
case Main -> MAIN_DECK_ICON;
|
||||||
|
case Sideboard -> SIDEBOARD_ICON;
|
||||||
|
case Commander -> FSkinImage.COMMAND;
|
||||||
|
case Avatar -> FSkinImage.AVATAR;
|
||||||
|
case Conspiracy -> FSkinImage.CONSPIRACY;
|
||||||
|
case Planes -> FSkinImage.PLANAR;
|
||||||
|
case Schemes -> FSkinImage.SCHEME;
|
||||||
|
case Attractions -> FSkinImage.ATTRACTION;
|
||||||
|
case Contraptions -> FSkinImage.CONTRAPTION;
|
||||||
|
default -> FSkinImage.HDSIDEBOARD;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DeckEditorConfig editorConfig;
|
private final DeckEditorConfig editorConfig;
|
||||||
|
|||||||
@@ -92,12 +92,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
|
|
||||||
private T get(int index) {
|
private T get(int index) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
try {
|
return internalList.get(index);
|
||||||
// TODO: Find cause why index is invalid on some cases...
|
|
||||||
return internalList.get(index);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,8 +579,6 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
maxPileHeight = 0;
|
maxPileHeight = 0;
|
||||||
for (int j = 0; j < group.piles.size(); j++) {
|
for (int j = 0; j < group.piles.size(); j++) {
|
||||||
Pile pile = group.piles.get(j);
|
Pile pile = group.piles.get(j);
|
||||||
if (pile == null)
|
|
||||||
continue;
|
|
||||||
y = pileY;
|
y = pileY;
|
||||||
for (int k = 0; k < pile.items.size(); k++) {
|
for (int k = 0; k < pile.items.size(); k++) {
|
||||||
ItemInfo itemInfo = pile.items.get(k);
|
ItemInfo itemInfo = pile.items.get(k);
|
||||||
@@ -595,10 +588,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
itemInfo.setBounds(x, y, itemWidth, itemHeight);
|
itemInfo.setBounds(x, y, itemWidth, itemHeight);
|
||||||
y += dy;
|
y += dy;
|
||||||
}
|
}
|
||||||
ItemInfo itemInfo = pile.items.get(pile.items.size() - 1);
|
pile.items.get(pile.items.size() - 1).pos = CardStackPosition.Top;
|
||||||
if (itemInfo == null)
|
|
||||||
continue;
|
|
||||||
itemInfo.pos = CardStackPosition.Top;
|
|
||||||
pileHeight = y + itemHeight - dy - pileY;
|
pileHeight = y + itemHeight - dy - pileY;
|
||||||
if (pileHeight > maxPileHeight) {
|
if (pileHeight > maxPileHeight) {
|
||||||
maxPileHeight = pileHeight;
|
maxPileHeight = pileHeight;
|
||||||
@@ -715,13 +705,9 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
float relX = x + group.getScrollLeft() - group.getLeft();
|
float relX = x + group.getScrollLeft() - group.getLeft();
|
||||||
float relY = y + getScrollValue();
|
float relY = y + getScrollValue();
|
||||||
Pile pile = group.piles.get(j);
|
Pile pile = group.piles.get(j);
|
||||||
if (pile == null)
|
|
||||||
continue;
|
|
||||||
if (pile.contains(relX, relY)) {
|
if (pile.contains(relX, relY)) {
|
||||||
for (int k = pile.items.size() - 1; k >= 0; k--) {
|
for (int k = pile.items.size() - 1; k >= 0; k--) {
|
||||||
ItemInfo item = pile.items.get(k);
|
ItemInfo item = pile.items.get(k);
|
||||||
if (item == null)
|
|
||||||
continue;
|
|
||||||
if (item.contains(relX, relY)) {
|
if (item.contains(relX, relY)) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class VAvatar extends FDisplayObject {
|
|||||||
float w = isHovered() ? getWidth()/16f+getWidth() : getWidth();
|
float w = isHovered() ? getWidth()/16f+getWidth() : getWidth();
|
||||||
float h = isHovered() ? getWidth()/16f+getHeight() : getHeight();
|
float h = isHovered() ? getWidth()/16f+getHeight() : getHeight();
|
||||||
|
|
||||||
if (avatarAnimation != null && MatchController.instance.getGameView() != null && !MatchController.instance.getGameView().isMatchOver()) {
|
if (avatarAnimation != null && !MatchController.instance.getGameView().isMatchOver()) {
|
||||||
if (player.wasAvatarLifeChanged()) {
|
if (player.wasAvatarLifeChanged()) {
|
||||||
avatarAnimation.start();
|
avatarAnimation.start();
|
||||||
avatarAnimation.drawAvatar(g, image, 0, 0, w, h);
|
avatarAnimation.drawAvatar(g, image, 0, 0, w, h);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class VManaPool extends VDisplayArea {
|
|||||||
float x = 0;
|
float x = 0;
|
||||||
float y = 0;
|
float y = 0;
|
||||||
|
|
||||||
if (Forge.isLandscapeMode() && (!Forge.altZoneTabs || !"Horizontal".equalsIgnoreCase(Forge.altZoneTabMode))) {
|
if (Forge.isLandscapeMode() && !Forge.altZoneTabs) {
|
||||||
float labelWidth = visibleWidth / 2;
|
float labelWidth = visibleWidth / 2;
|
||||||
float labelHeight = visibleHeight / 3;
|
float labelHeight = visibleHeight / 3;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.badlogic.gdx.utils.Align;
|
|||||||
|
|
||||||
import forge.Forge;
|
import forge.Forge;
|
||||||
import forge.Graphics;
|
import forge.Graphics;
|
||||||
import forge.assets.FSkin;
|
|
||||||
import forge.assets.FSkinColor;
|
import forge.assets.FSkinColor;
|
||||||
import forge.assets.FSkinColor.Colors;
|
import forge.assets.FSkinColor.Colors;
|
||||||
import forge.assets.FSkinFont;
|
import forge.assets.FSkinFont;
|
||||||
@@ -20,7 +19,6 @@ import forge.game.card.CounterEnumType;
|
|||||||
import forge.game.player.PlayerView;
|
import forge.game.player.PlayerView;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.localinstance.properties.ForgePreferences.FPref;
|
import forge.localinstance.properties.ForgePreferences.FPref;
|
||||||
import forge.localinstance.skin.FSkinProp;
|
|
||||||
import forge.menu.FMenuBar;
|
import forge.menu.FMenuBar;
|
||||||
import forge.menu.FMenuItem;
|
import forge.menu.FMenuItem;
|
||||||
import forge.menu.FPopupMenu;
|
import forge.menu.FPopupMenu;
|
||||||
@@ -141,8 +139,23 @@ public class VPlayerPanel extends FContainer {
|
|||||||
tabs.add(zoneTab);
|
tabs.add(zoneTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FSkinImageInterface iconFromZone(ZoneType zoneType) {
|
public static FSkinImage iconFromZone(ZoneType zoneType) {
|
||||||
return FSkin.getImages().get(FSkinProp.iconFromZone(zoneType, Forge.hdbuttons));
|
return switch (zoneType) {
|
||||||
|
case Hand -> Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND;
|
||||||
|
case Library -> Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY;
|
||||||
|
case Graveyard -> Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD;
|
||||||
|
case Exile -> Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE;
|
||||||
|
case Sideboard -> Forge.hdbuttons ? FSkinImage.HDSIDEBOARD : FSkinImage.SIDEBOARD;
|
||||||
|
case Flashback -> Forge.hdbuttons ? FSkinImage.HDFLASHBACK : FSkinImage.FLASHBACK;
|
||||||
|
case Command -> FSkinImage.COMMAND;
|
||||||
|
case PlanarDeck -> FSkinImage.PLANAR;
|
||||||
|
case SchemeDeck -> FSkinImage.SCHEME;
|
||||||
|
case AttractionDeck -> FSkinImage.ATTRACTION;
|
||||||
|
case ContraptionDeck -> FSkinImage.CONTRAPTION;
|
||||||
|
case Ante -> FSkinImage.ANTE;
|
||||||
|
case Junkyard -> FSkinImage.JUNKYARD;
|
||||||
|
default -> FSkinImage.HDLIBRARY;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<InfoTab> getTabs() {
|
public Iterable<InfoTab> getTabs() {
|
||||||
@@ -295,7 +308,6 @@ public class VPlayerPanel extends FContainer {
|
|||||||
tabManaPool.update();
|
tabManaPool.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("incomplete-switch")
|
|
||||||
public void updateZone(ZoneType zoneType) {
|
public void updateZone(ZoneType zoneType) {
|
||||||
if (zoneType == ZoneType.Battlefield) {
|
if (zoneType == ZoneType.Battlefield) {
|
||||||
field.update(true);
|
field.update(true);
|
||||||
|
|||||||
@@ -224,17 +224,17 @@ public class VStack extends FDropDown {
|
|||||||
activeItem.getLeft() + VStack.CARD_WIDTH * FCardPanel.TARGET_ORIGIN_FACTOR_X + VStack.PADDING + VStack.BORDER_THICKNESS,
|
activeItem.getLeft() + VStack.CARD_WIDTH * FCardPanel.TARGET_ORIGIN_FACTOR_X + VStack.PADDING + VStack.BORDER_THICKNESS,
|
||||||
activeItem.getTop() + VStack.CARD_HEIGHT * FCardPanel.TARGET_ORIGIN_FACTOR_Y + VStack.PADDING + VStack.BORDER_THICKNESS);
|
activeItem.getTop() + VStack.CARD_HEIGHT * FCardPanel.TARGET_ORIGIN_FACTOR_Y + VStack.PADDING + VStack.BORDER_THICKNESS);
|
||||||
|
|
||||||
PlayerView activator = activeStackInstance == null ? null : activeStackInstance.getActivatingPlayer();
|
PlayerView activator = activeStackInstance.getActivatingPlayer();
|
||||||
arrowOrigin = arrowOrigin.add(screenPos.x, screenPos.y);
|
arrowOrigin = arrowOrigin.add(screenPos.x, screenPos.y);
|
||||||
|
|
||||||
StackItemView instance = activeStackInstance;
|
StackItemView instance = activeStackInstance;
|
||||||
while (instance != null) {
|
while (instance != null) {
|
||||||
for (CardView c : instance.getTargetCards()) {
|
for (CardView c : instance.getTargetCards()) {
|
||||||
TargetingOverlay.ArcConnection conn = activator != null && activator.isOpponentOf(c.getController()) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting;
|
TargetingOverlay.ArcConnection conn = activator.isOpponentOf(c.getController()) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting;
|
||||||
TargetingOverlay.drawArrow(g, arrowOrigin, VCardDisplayArea.CardAreaPanel.get(c).getTargetingArrowOrigin(), conn);
|
TargetingOverlay.drawArrow(g, arrowOrigin, VCardDisplayArea.CardAreaPanel.get(c).getTargetingArrowOrigin(), conn);
|
||||||
}
|
}
|
||||||
for (PlayerView p : instance.getTargetPlayers()) {
|
for (PlayerView p : instance.getTargetPlayers()) {
|
||||||
TargetingOverlay.ArcConnection conn = activator != null && activator.isOpponentOf(p) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting;
|
TargetingOverlay.ArcConnection conn = activator.isOpponentOf(p) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting;
|
||||||
TargetingOverlay.drawArrow(g, arrowOrigin, MatchScreen.getPlayerPanel(p).getAvatar().getTargetingArrowOrigin(), conn);
|
TargetingOverlay.drawArrow(g, arrowOrigin, MatchScreen.getPlayerPanel(p).getAvatar().getTargetingArrowOrigin(), conn);
|
||||||
}
|
}
|
||||||
instance = instance.getSubInstance();
|
instance = instance.getSubInstance();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui</artifactId>
|
<artifactId>forge-gui</artifactId>
|
||||||
|
|||||||
@@ -170,13 +170,13 @@
|
|||||||
"rewards": [
|
"rewards": [
|
||||||
{
|
{
|
||||||
"count":6,
|
"count":6,
|
||||||
"cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure",
|
"cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure ",
|
||||||
"colors": ["blue"],
|
"colors": ["blue"],
|
||||||
"editions":["AKH","HOU","MP2","AKR"]
|
"editions":["AKH","HOU","MP2","AKR"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"count":2,
|
"count":2,
|
||||||
"cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure",
|
"cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure ",
|
||||||
"editions":["AKH","HOU","MP2","AKR"]
|
"editions":["AKH","HOU","MP2","AKR"]
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
@@ -1391,7 +1391,7 @@
|
|||||||
}]
|
}]
|
||||||
},{
|
},{
|
||||||
"name":"Azorius",
|
"name":"Azorius",
|
||||||
"description":"Azorius Shop, LLC",
|
"description":"Azorious Shop, LLC",
|
||||||
"spriteAtlas":"maps/tileset/buildings.atlas",
|
"spriteAtlas":"maps/tileset/buildings.atlas",
|
||||||
"sprite":"AzoriusShop",
|
"sprite":"AzoriusShop",
|
||||||
"rewards": [
|
"rewards": [
|
||||||
|
|||||||
@@ -6347,7 +6347,7 @@
|
|||||||
"POIReference": ""
|
"POIReference": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "I'll take care of it. If you'd note the location of the factory on my map... (Accept Quest) (WARNING HARD QUEST)",
|
"name": "I'll take care of it, note the location of the factory on my map.(Accept Quest) (WARNING HARD QUEST)",
|
||||||
"text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!"
|
"text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1439,7 +1439,7 @@
|
|||||||
}]
|
}]
|
||||||
},{
|
},{
|
||||||
"name":"Azorius",
|
"name":"Azorius",
|
||||||
"description":"Azorius Shop, LLC",
|
"description":"Azorious Shop, LLC",
|
||||||
"spriteAtlas":"maps/tileset/buildings.atlas",
|
"spriteAtlas":"maps/tileset/buildings.atlas",
|
||||||
"sprite":"AzoriusShop",
|
"sprite":"AzoriusShop",
|
||||||
"rewards": [
|
"rewards": [
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ Fourmill Run
|
|||||||
Port Rachkham
|
Port Rachkham
|
||||||
Cloudy Shallows
|
Cloudy Shallows
|
||||||
Slumnis
|
Slumnis
|
||||||
Silver Point
|
Silver Pointe
|
||||||
Abjuration Point
|
Abjuration Point
|
||||||
Crow's Nest
|
Crow's Nest
|
||||||
The Rookery
|
The Rookery
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Name=INN_Missionaries
|
|||||||
2 Blazing Torch|ISD|1
|
2 Blazing Torch|ISD|1
|
||||||
1 Burn at the Stake|AVR|1
|
1 Burn at the Stake|AVR|1
|
||||||
1 Chapel Geist|ISD|1
|
1 Chapel Geist|ISD|1
|
||||||
2 Chaplain of Alms|MID|1
|
2 Chaplain of ALms|MID|1
|
||||||
1 Chaplain's Blessing|SOI|1
|
1 Chaplain's Blessing|SOI|1
|
||||||
1 Cloistered Youth|ISD|1
|
1 Cloistered Youth|ISD|1
|
||||||
2 Crossroads Consecrator|EMN|1
|
2 Crossroads Consecrator|EMN|1
|
||||||
@@ -32,7 +32,7 @@ Name=INN_Missionaries
|
|||||||
1 Forsaken Sanctuary|SOI|1
|
1 Forsaken Sanctuary|SOI|1
|
||||||
1 Geist of the Lonely Vigil|EMN|1
|
1 Geist of the Lonely Vigil|EMN|1
|
||||||
1 Isolated Chapel|ISD|1
|
1 Isolated Chapel|ISD|1
|
||||||
1 Jerren, Corrupted Bishop|MID|1
|
1 Jerren. Corrupted Bishop|MID|1
|
||||||
1 Kindly Ancestor|VOW|1
|
1 Kindly Ancestor|VOW|1
|
||||||
1 Mad Prophet|SOI|1
|
1 Mad Prophet|SOI|1
|
||||||
1 Make a Wish|ISD|1
|
1 Make a Wish|ISD|1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
Name=INN_Peasants
|
Name=INN_Peasants
|
||||||
[Main]
|
[Main]
|
||||||
1 Alchemist's Apprentice|AVR|1
|
1 Alchemists's Apprentice|AVR|1
|
||||||
3 Ambitious Farmhand|MID|1
|
3 Ambitious Farmhand|MID|1
|
||||||
1 Apothecary Geist|SOI|1
|
1 Apothecary Geist|SOI|1
|
||||||
3 Baithook Angler|MID|1
|
3 Baithook Angler|MID|1
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Name=INN_peasant_easy
|
|||||||
2 Angel's Mercy|AVR|1
|
2 Angel's Mercy|AVR|1
|
||||||
3 Ambitious Farmhand|MID|1
|
3 Ambitious Farmhand|MID|1
|
||||||
2 Beloved Beggar|MID|1
|
2 Beloved Beggar|MID|1
|
||||||
2 Bereaved Survivor|MID|1
|
2 Berieved Survivor|MID|1
|
||||||
2 Doomed Traveler|ISD|1
|
2 Doomed Traveler|ISD|1
|
||||||
2 Bar the Door|DKA|1
|
2 Bar the Door|DKA|1
|
||||||
2 Gather the Townsfolk|DKA|1
|
2 Gather the Townsfolk|DKA|1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Name=INN_The_Whisperers
|
|||||||
2 Battleground Geist|ISD|1
|
2 Battleground Geist|ISD|1
|
||||||
2 Drogskol Captain|DKA|1
|
2 Drogskol Captain|DKA|1
|
||||||
2 Gallows Warden|ISD|1
|
2 Gallows Warden|ISD|1
|
||||||
2 Midnight Haunting|ISD|1
|
2 Midknight Haunting|ISD|1
|
||||||
2 Malevolent Hermit|MID|1
|
2 Malevolent Hermit|MID|1
|
||||||
2 Thing in the Ice|SOI|1
|
2 Thing in the Ice|SOI|1
|
||||||
2 Ambitious Farmhand|MID|1
|
2 Ambitious Farmhand|MID|1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Name=Adventure - INN Low Red
|
|||||||
1 Brimstone Vandal|MID|1
|
1 Brimstone Vandal|MID|1
|
||||||
2 Festival Crasher|MID|1
|
2 Festival Crasher|MID|1
|
||||||
2 Pyre Hound|SOI|1
|
2 Pyre Hound|SOI|1
|
||||||
1 Curse of Bloodletting|DKA|1
|
1 Curse of BLoodletting|DKA|1
|
||||||
1 Incendiary Flow|EMN|1
|
1 Incendiary Flow|EMN|1
|
||||||
1 Rage Thrower|ISD|1
|
1 Rage Thrower|ISD|1
|
||||||
1 Spellrune Painter|MID|1
|
1 Spellrune Painter|MID|1
|
||||||
|
|||||||
@@ -268,7 +268,7 @@
|
|||||||
0,4901,4902,4748,782,3146,1259,621,782,782,3146,1259,621,3146,1259,1259,1259,621,782,462,4743,4744,0,0,0,0,0,0,0,0,0,0
|
0,4901,4902,4748,782,3146,1259,621,782,782,3146,1259,621,3146,1259,1259,1259,621,782,462,4743,4744,0,0,0,0,0,0,0,0,0,0
|
||||||
</data>
|
</data>
|
||||||
</layer>
|
</layer>
|
||||||
<layer id="7" name="High Clutter" width="32" height="40">
|
<layer id="7" name="High CLutter" width="32" height="40">
|
||||||
<data encoding="csv">
|
<data encoding="csv">
|
||||||
0,0,22705,0,0,0,0,0,0,0,0,0,0,0,0,0,7082,0,0,22547,0,24596,0,0,0,0,0,0,0,0,0,0,
|
0,0,22705,0,0,0,0,0,0,0,0,0,0,0,0,0,7082,0,0,22547,0,24596,0,0,0,0,0,0,0,0,0,0,
|
||||||
22550,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
22550,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
</data>
|
</data>
|
||||||
</layer>
|
</layer>
|
||||||
<layer id="6" name="Walls Supplementary" width="40" height="35">
|
<layer id="6" name="Walls Suplementary" width="40" height="35">
|
||||||
<data encoding="csv">
|
<data encoding="csv">
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
|||||||
@@ -343,7 +343,7 @@
|
|||||||
"questItem": true
|
"questItem": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Grolnok's Key",
|
"name": "Grolnoks Key",
|
||||||
"iconName": "StrangeKey",
|
"iconName": "StrangeKey",
|
||||||
"questItem": true
|
"questItem": true
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user