Merge branch 'mdfc' into 'master'

Correctly evaluate modal faces

See merge request core-developers/forge!5112
This commit is contained in:
Michael Kamensky
2021-07-27 04:53:19 +00:00
26 changed files with 94 additions and 91 deletions

View File

@@ -750,7 +750,15 @@ public class AiController {
return AiPlayDecision.CantAfford;
}
// state needs to be switched here so API checks evaluate the right face
if (sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Modal, false);
}
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
if (sa.getCardState().getStateName() == CardStateName.Modal) {
sa.getHostCard().setState(CardStateName.Original, false);
}
if (canPlay != AiPlayDecision.WillPlay) {
return canPlay;
}
@@ -810,8 +818,7 @@ public class AiController {
if (!canPlay) {
return AiPlayDecision.CantPlayAi;
}
}
else {
} else {
Cost payCosts = sa.getPayCosts();
if (payCosts != null) {
ManaCost mana = payCosts.getTotalMana();

View File

@@ -484,7 +484,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(source);
}
if (cost.getType().equals("OriginalHost")) {
return PaymentDecision.card(ability.getHostCard());
return PaymentDecision.card(ability.getOriginalHost());
}
if (cost.getAmount().equals("All")) {
// Does the AI want to use Sacrifice All?
@@ -600,7 +600,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
// currently if amount is bigger than one,
// it tries to remove all counters from one source and type at once
int toRemove = 0;
final GameEntityCounterTable table = new GameEntityCounterTable();

View File

@@ -2890,10 +2890,10 @@ public class ComputerUtil {
return false;
}
public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost) {
public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost, boolean mandatory) {
// determine and target a card with a SA that the AI can afford and will play
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
Card targetSpellCard = null;
CardCollection targets = new CardCollection();
for (Card c : options) {
if (withoutPayingManaCost && c.getManaCost() != null && c.getManaCost().countX() > 0) {
// The AI will otherwise cheat with the mana payment, announcing X > 0 for spells like Heat Ray when replaying them
@@ -2913,18 +2913,19 @@ public class ComputerUtil {
// at this point, we're assuming that card will be castable from whichever zone it's in by the AI player.
abTest.setActivatingPlayer(ai);
abTest.getRestrictions().setZone(c.getZone().getZoneType());
final boolean play = AiPlayDecision.WillPlay == aic.canPlaySa(abTest);
final boolean pay = ComputerUtilCost.canPayCost(abTest, ai);
if (play && pay) {
targetSpellCard = c;
break;
if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai)) {
targets.add(c);
}
}
}
if (targetSpellCard == null) {
return false;
if (targets.isEmpty()) {
if (mandatory && !options.isEmpty()) {
targets = options;
} else {
return false;
}
}
sa.getTargets().add(targetSpellCard);
sa.getTargets().add(ComputerUtilCard.getBestAI(targets));
return true;
}

View File

@@ -633,7 +633,7 @@ public class ComputerUtilCost {
}
// Check if the AI intends to play the card and if it can pay for it with the mana it has
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
boolean canPay = c.getManaCost().canBePaidWithAvailable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
return canPay && willPlay;
}
}

View File

@@ -1099,7 +1099,7 @@ public class SpecialCardAi {
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
ManaCost cost = testSa.getPayCosts().getTotalMana();
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames(
ComputerUtilCost.getAvailableManaColors(ai, sa.getHostCard())).getColor());
byte colorProfile = cost.getColorProfile();

View File

@@ -233,10 +233,10 @@ public class EffectAi extends SpellAbilityAi {
return ai.getCreaturesInPlay().size() >= i;
}
return true;
} else if (logic.equals("CastFromGraveThisTurn")) {
} else if (logic.equals("ReplaySpell")) {
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
return false;
}
} else if (logic.equals("Bribe")) {
@@ -313,6 +313,5 @@ public class EffectAi extends SpellAbilityAi {
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
}

View File

@@ -167,7 +167,7 @@ public class ManaEffectAi extends SpellAbilityAi {
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
ManaCost cost = testSa.getPayCosts().getTotalMana();
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames(
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
if (cost.getCMC() == 0 && cost.countX() == 0) {

View File

@@ -40,33 +40,9 @@ public class PlayAi extends SpellAbilityAi {
return false; // prevent infinite loop
}
CardCollection cards = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
ZoneType zone = tgt.getZone().get(0);
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
if (cards.isEmpty()) {
return false;
}
} else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (cards.isEmpty()) {
return false;
}
}
if (cards != null & sa.hasParam("ValidSA")) {
final String valid[] = sa.getParam("ValidSA").split(",");
final Iterator<Card> itr = cards.iterator();
while (itr.hasNext()) {
final Card c = itr.next();
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) {
itr.remove();
}
}
if (cards.isEmpty()) {
return false;
}
CardCollection cards = getPlayableCards(sa, ai);
if (cards.isEmpty()) {
return false;
}
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
@@ -85,25 +61,15 @@ public class PlayAi extends SpellAbilityAi {
}
}
// Ensure that if a ValidZone is specified, there's at least something to choose from in that zone.
CardCollectionView validOpts = new CardCollection();
if (sa.hasParam("ValidZone")) {
validOpts = AbilityUtils.filterListByType(game.getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))),
sa.getParam("Valid"), sa);
if (validOpts.isEmpty()) {
return false;
}
}
if ("ReplaySpell".equals(logic)) {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"), false);
} else if (logic.startsWith("NeedsChosenCard")) {
int minCMC = 0;
if (sa.getPayCosts().getCostMana() != null) {
minCMC = sa.getPayCosts().getTotalMana().getCMC();
}
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
cards = CardLists.filter(cards, CardPredicates.greaterCMC(minCMC));
return chooseSingleCard(ai, sa, cards, sa.hasParam("Optional"), null, null) != null;
} else if ("WithTotalCMC".equals(logic)) {
// Try to play only when there are more than three playable cards.
if (cards.size() < 3)
@@ -154,6 +120,10 @@ public class PlayAi extends SpellAbilityAi {
return false;
}
if ("ReplaySpell".equals(sa.getParam("AILogic"))) {
return ComputerUtil.targetPlayableSpellCard(ai, getPlayableCards(sa, ai), sa, sa.hasParam("WithoutManaCost"), mandatory);
}
return checkApiLogic(ai, sa);
}
@@ -218,4 +188,36 @@ public class PlayAi extends SpellAbilityAi {
});
return ComputerUtilCard.getBestAI(tgtCards);
}
private static CardCollection getPlayableCards(SpellAbility sa, Player ai) {
CardCollection cards = new CardCollection();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (tgt != null) {
ZoneType zone = tgt.getZone().get(0);
cards = CardLists.getValidCards(ai.getGame().getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
} else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
}
if (cards != null & sa.hasParam("ValidSA")) {
final String valid[] = sa.getParam("ValidSA").split(",");
final Iterator<Card> itr = cards.iterator();
while (itr.hasNext()) {
final Card c = itr.next();
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) {
itr.remove();
}
}
}
// Ensure that if a ValidZone is specified, there's at least something to choose from in that zone.
if (sa.hasParam("ValidZone")) {
cards = new CardCollection(AbilityUtils.filterListByType(ai.getGame().getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))),
sa.getParam("Valid"), sa));
}
return cards;
}
}

View File

@@ -596,7 +596,7 @@ public class PumpAi extends PumpAiBase {
}
if ("Snapcaster".equals(sa.getParam("AILogic"))) {
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
return false;
}
}

View File

@@ -107,7 +107,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
}
Player p = pc.getFirst(); // FIXME: is this always a single target spell?
Player p = pc.getFirst(); // currently always a single target spell
Card top = p.getCardsIn(ZoneType.Library).getFirst();
int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))

View File

@@ -182,7 +182,7 @@ public final class CardRules implements ICardCharacteristics {
//if card face has no cost, assume castable only by mana of its defined color
return face.getColor().hasNoColorsExcept(colorCode);
}
return face.getManaCost().canBePaidWithAvaliable(colorCode);
return face.getManaCost().canBePaidWithAvailable(colorCode);
}
public boolean canCastWithAvailable(byte colorCode) {

View File

@@ -353,7 +353,7 @@ public final class ManaCost implements Comparable<ManaCost>, Iterable<ManaCostSh
* @param colorCode
* @return
*/
public boolean canBePaidWithAvaliable(byte colorCode) {
public boolean canBePaidWithAvailable(byte colorCode) {
for (ManaCostShard shard : shards) {
if (!shard.isPhyrexian() && !shard.canBePaidWithManaOfColor(colorCode)) {
return false;

View File

@@ -82,7 +82,6 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
boolean isOptional = sa.hasParam("Optional");
for (Player controller : controllers) {
List<SpellAbility> copies = Lists.newArrayList();
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,

View File

@@ -6342,7 +6342,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
if (isFaceDown() && isInZone(ZoneType.Exile)) {
for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) {
for (final SpellAbility sa : oState.getSpellAbilities()) {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
}

View File

@@ -122,7 +122,7 @@ public class CardLists {
*/
public static void sortByCmcDesc(final List<Card> list) {
Collections.sort(list, CmcComparatorInv);
} // sortByCmcDesc
}
/**
* <p>
@@ -133,7 +133,7 @@ public class CardLists {
*/
public static void sortByToughnessAsc(final List<Card> list) {
Collections.sort(list, ToughnessComparator);
} // sortByToughnessAsc()
}
/**
* <p>
@@ -144,7 +144,7 @@ public class CardLists {
*/
public static void sortByToughnessDesc(final List<Card> list) {
Collections.sort(list, ToughnessComparatorInv);
} // sortByToughnessDesc()
}
/**
* <p>
@@ -155,7 +155,7 @@ public class CardLists {
*/
public static void sortByPowerAsc(final List<Card> list) {
Collections.sort(list, PowerComparator);
} // sortAttackLowFirst()
}
// the higher the attack the better
/**
@@ -167,7 +167,7 @@ public class CardLists {
*/
public static void sortByPowerDesc(final List<Card> list) {
Collections.sort(list, Collections.reverseOrder(PowerComparator));
} // sortAttack()
}
/**

View File

@@ -63,8 +63,6 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
return this.parent;
}
/** {@inheritDoc} */
@Override
public boolean canPlay() {
@@ -72,10 +70,8 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
return false;
}
private final SpellAbilityEffect effect;
public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map<String, String> params0) {
super(ca, Cost.Zero);
this.setTargetRestrictions(tgt);

View File

@@ -20,7 +20,7 @@ Colors:blue
Types:Legendary Planeswalker Jace
Loyalty:5
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumAtt$ -2 | IsCurse$ True | Duration$ UntilYourNextTurn | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Up to one target creature gets -2/-0 until your next turn.
A:AB$ Effect | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | AILogic$ CastFromGraveThisTurn | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TgtZone$ Graveyard | TgtPrompt$ Select target instant or sorcery card | RememberObjects$ Targeted | StaticAbilities$ Play | ExileOnMoved$ Graveyard | SubAbility$ DBEffect | SpellDescription$ You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard this turn, exile it instead.
A:AB$ Effect | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | AILogic$ ReplaySpell | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TgtZone$ Graveyard | TgtPrompt$ Select target instant or sorcery card | RememberObjects$ Targeted | StaticAbilities$ Play | ExileOnMoved$ Graveyard | SubAbility$ DBEffect | SpellDescription$ You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard this turn, exile it instead.
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Graveyard | Description$ You may play remembered card.
SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ExileOnMoved$ Stack | ReplacementEffects$ ReplaceGraveyard
SVar:ReplaceGraveyard:Event$ Moved | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Graveyard | ReplaceWith$ MoveExile | Description$ If that card would be put into your graveyard this turn, exile it instead.

View File

@@ -2,7 +2,7 @@ Name:Mission Briefing
ManaCost:U U
Types:Instant
A:SP$ Surveil | Cost$ U U | Amount$ 2 | SubAbility$ DBChooseCard | SpellDescription$ Surveil 2, then choose an instant or sorcery card in your graveyard. You may cast it this turn. If that spell would be put into your graveyard this turn, exile it instead. (To surveil 2, look at the top two cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.)
SVar:DBChooseCard:DB$ ChooseCard | Choices$ Instant.YouCtrl,Sorcery.YouCtrl | ChoiceZone$ Graveyard | AILogic$ CastFromGraveThisTurn | Mandatory$ True | RememberChosen$ True | SubAbility$ DBEffect | SpellDescription$ You may cast that card this turn. If that card would be put into your graveyard this turn, exile it instead.
SVar:DBChooseCard:DB$ ChooseCard | Choices$ Instant.YouCtrl,Sorcery.YouCtrl | ChoiceZone$ Graveyard | Mandatory$ True | RememberChosen$ True | SubAbility$ DBEffect | SpellDescription$ You may cast that card this turn. If that card would be put into your graveyard this turn, exile it instead.
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ExileOnMoved$ Stack | ReplacementEffects$ ReplaceGraveyard
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Graveyard | Description$ You may play remembered card.
SVar:ReplaceGraveyard:Event$ Moved | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Graveyard | ReplaceWith$ MoveExile | Description$ If that card would be put into your graveyard this turn, exile it instead.

View File

@@ -4,6 +4,6 @@ Types:Artifact Equipment
K:Equip:2
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddTrigger$ AttackTrigger | Description$ Invoke Duplicity — Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice CARDNAME. If you do, create a token that's a copy of this creature."
SVar:AttackTrigger:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature.
SVar:TrigCopy:AB$ CopyPermanent | Cost$ Sac<1/OriginalHost/Trickster's Talisman> | Defined$ Self | NumCopies$ 1 | AILogic$ DuplicatePerms
SVar:TrigCopy:AB$ CopyPermanent | Cost$ Sac<1/OriginalHost/Trickster's Talisman> | Defined$ Self | NumCopies$ 1
DeckHas:Ability$Token
Oracle:Invoke Duplicity — Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature."\nEquip {2}

View File

@@ -32,7 +32,7 @@ Strixhaven Stadium|The Mage Tower|And that's game, set, and match!
Test of Endurance|The Test|So... did I pass?
Thassa's Oracle|The Prophecy of Victory|I see... nothing. We must've won.
The Cheese Stands Alone|The Cheese|It's cheesy, but hey, it works!
The Deck of Many Things|Down on the Deck|Lucky draw!
The Deck of Many Things's Effect|Down on the Deck|Lucky draw!
Triskaidekaphobia|The Fear of 13|It's just a silly ancient superstition... right?
Vorpal Sword|Snicker-Snack!|He left it dead, and with its head / He went galumphing back.
Emblem - Vraska, Golgari Queen|The Flurry of Assassins|How good is your dodging?

View File

@@ -697,9 +697,9 @@ public enum ColumnDef {
return !(((IPaperCard) i).getRules().getType().isArtifact() && (toColor(i).isColorless() ||
//If it isn't colorless, see if it can be paid with only white, only blue, only black.
//No need to check others since three-color hybrid shards don't exist.
manaCost.canBePaidWithAvaliable(MagicColor.WHITE) &&
manaCost.canBePaidWithAvaliable(MagicColor.BLUE) &&
manaCost.canBePaidWithAvaliable(MagicColor.BLACK)))
manaCost.canBePaidWithAvailable(MagicColor.WHITE) &&
manaCost.canBePaidWithAvailable(MagicColor.BLUE) &&
manaCost.canBePaidWithAvailable(MagicColor.BLACK)))
? "0" + toSplitLast(i) : "1";
}
@@ -757,9 +757,9 @@ public enum ColumnDef {
private static String toGoldFirst(final InventoryItem i) {
forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost();
return !(manaCost.canBePaidWithAvaliable(MagicColor.WHITE) | manaCost.canBePaidWithAvaliable(MagicColor.BLUE) |
manaCost.canBePaidWithAvaliable(MagicColor.BLACK) | manaCost.canBePaidWithAvaliable(MagicColor.RED) |
manaCost.canBePaidWithAvaliable(MagicColor.GREEN)) ? "0" : "1";
return !(manaCost.canBePaidWithAvailable(MagicColor.WHITE) | manaCost.canBePaidWithAvailable(MagicColor.BLUE) |
manaCost.canBePaidWithAvailable(MagicColor.BLACK) | manaCost.canBePaidWithAvailable(MagicColor.RED) |
manaCost.canBePaidWithAvailable(MagicColor.GREEN)) ? "0" : "1";
}
/**

View File

@@ -90,12 +90,12 @@ public abstract class AchievementCollection implements Iterable<Achievement> {
filename = filename0;
isLimitedFormat = isLimitedFormat0;
path = path0;
addSharedAchivements();
addSharedAchievements();
addAchievements();
load();
}
protected void addSharedAchivements() {
protected void addSharedAchievements() {
add(new GameWinStreak(10, 25, 50, 100));
add(new MatchWinStreak(10, 25, 50, 100));
add(new TotalGameWins(250, 500, 1000, 2000));

View File

@@ -18,7 +18,7 @@ public class AltWinAchievements extends AchievementCollection {
}
@Override
protected void addSharedAchivements() {
protected void addSharedAchievements() {
//prevent including shared achievements
}

View File

@@ -15,7 +15,7 @@ public class ChallengeAchievements extends AchievementCollection {
}
@Override
protected void addSharedAchivements() {
protected void addSharedAchievements() {
//prevent including shared achievements
}

View File

@@ -23,7 +23,7 @@ public class PlaneswalkerAchievements extends AchievementCollection {
}
@Override
protected void addSharedAchivements() {
protected void addSharedAchievements() {
//prevent including shared achievements
}

View File

@@ -8,7 +8,7 @@ public class PuzzleAchievements extends AchievementCollection {
}
@Override
protected void addSharedAchivements() {
protected void addSharedAchievements() {
//prevent including shared achievements
}