Merge branch 'Card-Forge:master' into master

This commit is contained in:
hovergoat
2024-08-26 21:30:20 -04:00
committed by GitHub
179 changed files with 1965 additions and 511 deletions

View File

@@ -64,7 +64,6 @@ import forge.util.Aggregates;
import forge.util.ComparatorUtil;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.collect.FCollectionView;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
@@ -437,11 +436,11 @@ public class AiController {
}
landList = CardLists.filter(landList, c -> {
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
if (canPlaySpellBasic(c, null) != AiPlayDecision.WillPlay) {
return false;
}
String name = c.getName();
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
return false;
@@ -461,15 +460,11 @@ public class AiController {
}
// don't play the land if it has cycling and enough lands are available
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
for (final SpellAbility sa : spellAbilities) {
if (sa.isCycling()) {
return false;
}
if (c.hasKeyword(Keyword.CYCLING)) {
return false;
}
}
return player.canPlayLand(c);
return Iterables.any(c.getAllPossibleAbilities(player, true), SpellAbility::isLandAbility);
});
return landList;
}
@@ -1376,30 +1371,12 @@ public class AiController {
Card land = chooseBestLandToPlay(landsWannaPlay);
if ((!player.canLoseLife() || player.cantLoseForZeroOrLessLife() || ComputerUtil.getDamageFromETB(player, land) < player.getLife())
&& (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land))) {
final List<SpellAbility> abilities = Lists.newArrayList();
final List<SpellAbility> abilities = land.getAllPossibleAbilities(player, true);
// skip non Land Abilities
abilities.removeIf(sa -> !sa.isLandAbility());
// TODO extend this logic to evaluate MDFC with both sides land
// this can only happen if its a MDFC land
if (!land.isLand()) {
land.setState(CardStateName.Modal, true);
land.setBackSide(true);
}
LandAbility la = new LandAbility(land, player, null);
la.setCardState(land.getCurrentState());
if (la.canPlay()) {
abilities.add(la);
}
// add mayPlay option
for (CardPlayOption o : land.mayPlay(player)) {
la = new LandAbility(land, player, o);
la.setCardState(land.getCurrentState());
if (la.canPlay()) {
abilities.add(la);
}
}
if (!abilities.isEmpty()) {
// TODO extend this logic to evaluate MDFC with both sides land
return abilities;
}
}
@@ -1570,7 +1547,7 @@ public class AiController {
Iterables.removeIf(saList, spellAbility -> { //don't include removedAI cards if somehow the AI can play the ability or gain control of unsupported card
// TODO allow when experimental profile?
return spellAbility instanceof LandAbility || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
return spellAbility.isLandAbility() || (spellAbility.getHostCard() != null && ComputerUtilCard.isCardRemAIDeck(spellAbility.getHostCard()));
});
//update LivingEndPlayer
useLivingEnd = Iterables.any(player.getZone(ZoneType.Library), CardPredicates.nameEquals("Living End"));

View File

@@ -14,7 +14,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.CostPart;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostPutCounter;
@@ -32,20 +31,14 @@ public class ComputerUtilAbility {
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
return null;
}
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
hand.addAll(player.getCardsIn(ZoneType.Exile));
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
CardCollection landList = new CardCollection(player.getCardsIn(ZoneType.Hand));
//filter out cards that can't be played
landList = CardLists.filter(landList, c -> {
if (!c.getSVar("NeedsToPlay").isEmpty()) {
final String needsToPlay = c.getSVar("NeedsToPlay");
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null);
if (list.isEmpty()) {
return false;
}
if (!c.hasPlayableLandFace()) {
return false;
}
return player.canPlayLand(c);
return player.canPlayLand(c, false, c.getFirstSpellAbility());
});
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
@@ -54,7 +47,7 @@ public class ComputerUtilAbility {
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
}
for (final Card crd : landsNotInHand) {
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
if (!(crd.hasPlayableLandFace() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
continue;
}
if (!crd.mayPlay(player).isEmpty()) {

View File

@@ -810,7 +810,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean playChosenSpellAbility(SpellAbility sa) {
if (sa instanceof LandAbility) {
if (sa.isLandAbility()) {
if (sa.canPlay()) {
sa.resolve();
}

View File

@@ -95,8 +95,8 @@ public class DamageDealAi extends DamageAiBase {
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
if (damage.equals("X") || sourceName.equals("Crater's Claws")) {
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
if (damage.equals("X") || source.getSVar("X").equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
@@ -723,6 +723,7 @@ public class DamageDealAi extends DamageAiBase {
if (sa.canTarget(enemy) && sa.canAddMoreTarget()) {
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2))
|| ("BurnCreatures".equals(logic) && !enemy.getCreaturesInPlay().isEmpty())
|| immediately) {
boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai);
boolean isPWAbility = sa.isPwAbility() && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);

View File

@@ -8,7 +8,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.LandAbility;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
@@ -45,7 +45,7 @@ public class DiscoverAi extends SpellAbilityAi {
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
Card c = (Card)params.get("Card");
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai)) {
if (s instanceof LandAbility) {
if (s.isLandAbility()) {
// return false or we get a ClassCastException later if the AI encounters MDFC with land backside
return false;
}

View File

@@ -154,7 +154,7 @@ public class PlayAi extends SpellAbilityAi {
if (!sa.matchesValidParam("ValidSA", s)) {
continue;
}
if (s instanceof LandAbility) {
if (s.isLandAbility()) {
// might want to run some checks here but it's rare anyway
return true;
}

View File

@@ -26,11 +26,29 @@ public class ScryAi extends SpellAbilityAi {
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
if (sa.usesTargeting()) {
// ability is targeted
sa.resetTargets();
sa.getTargets().add(ai);
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
for (Player p : ai.getAllies()) {
if (sa.canTarget(p)) {
sa.getTargets().add(p);
break;
}
}
if (mandatory && !sa.isTargetNumberValid()) {
for (Player p : ai.getOpponents()) {
if (sa.canTarget(p)) {
sa.getTargets().add(p);
break;
}
}
}
}
return mandatory || sa.isTargetNumberValid();
}
return true;
@@ -132,6 +150,21 @@ public class ScryAi extends SpellAbilityAi {
randomReturn = true;
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
for (Player p : ai.getAllies()) {
if (sa.canTarget(p)) {
sa.getTargets().add(p);
break;
}
}
}
randomReturn = sa.isTargetNumberValid();
}
return randomReturn;
}

View File

@@ -1,6 +1,6 @@
package forge.ai.simulation;
import forge.game.spellability.LandAbility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -158,9 +158,9 @@ public class GameSimulator {
}
public Score simulateSpellAbility(SpellAbility origSa, GameStateEvaluator eval, boolean resolve) {
SpellAbility sa;
if (origSa instanceof LandAbility) {
if (origSa.isLandAbility()) {
Card hostCard = (Card) copier.find(origSa.getHostCard());
if (!aiPlayer.playLand(hostCard, false)) {
if (!aiPlayer.playLand(hostCard, false, origSa)) {
System.err.println("Simulation: Couldn't play land! " + origSa);
}
sa = origSa;

View File

@@ -260,7 +260,10 @@ public class GameStateEvaluator {
// The value should be more than the value of having a card in hand, so if a land has an
// activated ability but not a mana ability, it will still be played.
for (SpellAbility m: c.getNonManaAbilities()) {
if (!m.getPayCosts().hasTapCost()) {
if (m.isLandAbility()) {
// Land Ability has no extra Score
continue;
} if (!m.getPayCosts().hasTapCost()) {
// probably a manland, rate it higher than a rainbow land
value += 25;
} else if (m.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {

View File

@@ -24,7 +24,7 @@ import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityCondition;
import forge.game.zone.ZoneType;
@@ -136,7 +136,7 @@ public class SpellAbilityPicker {
private static boolean isSorcerySpeed(SpellAbility sa, Player player) {
// TODO: Can we use the actual rules engine for this instead of trying to do the logic ourselves?
if (sa instanceof LandAbility) {
if (sa.isLandAbility()) {
return true;
}
if (sa.isSpell()) {
@@ -327,16 +327,16 @@ public class SpellAbilityPicker {
}
private AiPlayDecision canPlayAndPayForSim(final SpellAbility sa) {
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.CantPlaySa;
}
if (!sa.checkRestrictions(sa.getHostCard(), player)) {
return AiPlayDecision.CantPlaySa;
}
if (sa instanceof LandAbility) {
if (sa.isLandAbility()) {
return AiPlayDecision.WillPlay;
}
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.CantPlaySa;
}
if (!sa.canPlay()) {
return AiPlayDecision.CantPlaySa;
}

View File

@@ -24,7 +24,7 @@ public enum CardRarity {
Rare("R", "Rare"),
MythicRare("M", "Mythic Rare"),
Special("S", "Special"), // Timeshifted
None("N", "None"), // Tokens
Token("T", "Token"), // Tokens
Unknown("?", "Unknown"); // In development
public static final CardRarity[] FILTER_OPTIONS = new CardRarity[] {

View File

@@ -169,7 +169,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
@Override
public CardRarity getRarity() {
return CardRarity.None;
return CardRarity.Token;
}
@Override

View File

@@ -64,7 +64,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
* Keys that should not changed
*/
private static final ImmutableList<String> noChangeKeys = ImmutableList.<String>builder()
.add("TokenScript", "TokenImage", "NewName", "ChooseFromList")
.add("TokenScript", "TokenImage", "NewName" , "DefinedName", "ChooseFromList")
.add("AddAbility").build();
/**
@@ -170,7 +170,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
*
* @return a boolean.
*/
public final boolean isSecondary() {
public boolean isSecondary() {
return getParamOrDefault("Secondary", "False").equals("True");
}

View File

@@ -91,10 +91,10 @@ public final class GameActionUtil {
return alternatives;
}
if (sa.isSpell()) {
if (sa.isSpell() || sa.isLandAbility()) {
boolean lkicheck = false;
Card newHost = ((Spell)sa).getAlternateHost(source);
Card newHost = sa.getAlternateHost(source);
if (newHost != null) {
source = newHost;
lkicheck = true;

View File

@@ -2,7 +2,6 @@ package forge.game;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import com.google.common.collect.Iterables;
@@ -93,11 +92,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
if (event.sa.getTargetRestrictions() != null) {
StringBuilder sb = new StringBuilder();
List<TargetChoices> targets = event.sa.getAllTargetChoices();
// Include the TargetChoices from the stack instance, since the real target choices
// are on that object at this point (see SpellAbilityStackInstance constructor).
targets.add(event.si.getTargetChoices());
for (TargetChoices ch : targets) {
for (TargetChoices ch : event.sa.getAllTargetChoices()) {
if (null != ch) {
sb.append(ch);
}

View File

@@ -2953,7 +2953,8 @@ public class AbilityUtils {
}
for (SpellAbility s : list) {
if (s instanceof LandAbility) {
if (s.isLandAbility()) {
s.setActivatingPlayer(controller);
// CR 305.3
if (controller.getGame().getPhaseHandler().isPlayerTurn(controller) && controller.canPlayLand(tgtCard, true, s)) {
sas.add(s);
@@ -2981,9 +2982,7 @@ public class AbilityUtils {
private static void collectSpellsForPlayEffect(final List<SpellAbility> result, final CardState state, final Player controller, final boolean withAltCost) {
if (state.getType().isLand()) {
LandAbility la = new LandAbility(state.getCard(), controller, null);
la.setCardState(state);
result.add(la);
result.add(state.getFirstSpellAbility());
}
final Iterable<SpellAbility> spells = state.getSpellAbilities();
for (SpellAbility sa : spells) {

View File

@@ -13,7 +13,9 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ImageKeys;
import forge.StaticData;
import forge.card.CardRarity;
import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.game.Game;
@@ -46,53 +48,59 @@ public class CopyPermanentEffect extends TokenEffectBase {
final StringBuilder sb = new StringBuilder();
final Player activator = sa.getActivatingPlayer();
final List<Card> tgtCards = getTargetCards(sa);
boolean justOne = tgtCards.size() == 1;
boolean addKWs = sa.hasParam("AddKeywords");
final int numCopies = sa.hasParam("NumCopies") ?
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCopies"), sa) : 1;
sb.append(activator).append(" creates ").append(Lang.nounWithNumeralExceptOne(numCopies, "token"));
sb.append(numCopies == 1 ? " that's a copy" : " that are copies").append(" of ");
sb.append(Lang.joinHomogenous(tgtCards));
if (addKWs) {
final List<String> keywords = Lists.newArrayList();
keywords.addAll(Arrays.asList(sa.getParam("AddKeywords").split(" & ")));
if (sa.getDescription().contains("except")) {
sb.append(", except ").append(justOne ? "it has " : "they have ");
} else {
sb.append(". ").append(justOne ? "It gains " : "They gain ");
}
sb.append(Lang.joinHomogenous(keywords).toLowerCase());
}
if (sa.hasParam("AddTriggers")) {
final String oDesc = sa.getDescription();
final String trigStg = oDesc.contains("\"") ?
oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1) :
"[trigger text parsing error]";
if (addKWs) {
sb.append(" and ").append(trigStg);
} else {
sb.append(". ").append(justOne ? "It gains " : "They gain ").append(trigStg);
}
sb.append(activator).append(" creates ");
if (sa.hasParam("DefinedName")) {
sb.append(Lang.nounWithNumeralExceptOne(numCopies, sa.getParam("DefinedName") + " token"));
} else {
sb.append(".");
}
final List<Card> tgtCards = getTargetCards(sa);
boolean justOne = tgtCards.size() == 1;
boolean addKWs = sa.hasParam("AddKeywords");
if (sa.hasParam("AtEOT")) {
String atEOT = sa.getParam("AtEOT");
String verb = "Sacrifice ";
if (atEOT.startsWith("Exile")) {
verb = "Exile ";
sb.append(Lang.nounWithNumeralExceptOne(numCopies, "token"));
sb.append(numCopies == 1 ? " that's a copy" : " that are copies").append(" of ");
sb.append(Lang.joinHomogenous(tgtCards));
if (addKWs) {
final List<String> keywords = Lists.newArrayList();
keywords.addAll(Arrays.asList(sa.getParam("AddKeywords").split(" & ")));
if (sa.getDescription().contains("except")) {
sb.append(", except ").append(justOne ? "it has " : "they have ");
} else {
sb.append(". ").append(justOne ? "It gains " : "They gain ");
}
sb.append(Lang.joinHomogenous(keywords).toLowerCase());
}
sb.append(" ").append(verb).append(justOne ? "it " : "them ").append("at ");
String when = "the beginning of the next end step.";
if (atEOT.endsWith("Combat")) {
when = "end of combat.";
if (sa.hasParam("AddTriggers")) {
final String oDesc = sa.getDescription();
final String trigStg = oDesc.contains("\"") ?
oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1) :
"[trigger text parsing error]";
if (addKWs) {
sb.append(" and ").append(trigStg);
} else {
sb.append(". ").append(justOne ? "It gains " : "They gain ").append(trigStg);
}
} else {
sb.append(".");
}
if (sa.hasParam("AtEOT")) {
String atEOT = sa.getParam("AtEOT");
String verb = "Sacrifice ";
if (atEOT.startsWith("Exile")) {
verb = "Exile ";
}
sb.append(" ").append(verb).append(justOne ? "it " : "them ").append("at ");
String when = "the beginning of the next end step.";
if (atEOT.endsWith("Combat")) {
when = "end of combat.";
}
sb.append(when);
}
sb.append(when);
}
return sb.toString();
@@ -180,20 +188,21 @@ public class CopyPermanentEffect extends TokenEffectBase {
tgtCards = choice;
System.err.println("Copying random permanent(s): " + tgtCards.toString());
} else if (sa.hasParam("DefinedName")) {
String name = sa.getParam("DefinedName");
if (name.equals("NamedCard")) {
if (!host.getNamedCard().isEmpty()) {
name = host.getNamedCard();
}
}
} else if (sa.hasParam("DefinedName")) {
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
String name = sa.getParam("DefinedName");
if (name.equals("NamedCard")) {
if (!host.getNamedCard().isEmpty()) {
name = host.getNamedCard();
}
}
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard::getRules);
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard::getRules);
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
if (!cards.isEmpty()) {
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
}
if (!cards.isEmpty()) {
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
}
} else if (sa.hasParam("Choices")) {
Player chooser = activator;
@@ -272,31 +281,42 @@ public class CopyPermanentEffect extends TokenEffectBase {
}
public static Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
final Card host = sa.getHostCard();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
// need to create a physical card first, i need the original card faces
final Card copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
final Card copy;
if (sa.hasParam("DefinedName")) {
copy = original;
String name = TextUtil.fastReplace(TextUtil.fastReplace(original.getName(), ",", ""), " ", "_").toLowerCase();
String set = sa.getOriginalHost().getSetCode();
copy.getCurrentState().setRarity(CardRarity.Token);
copy.getCurrentState().setSetCode(set);
copy.getCurrentState().setImageKey(ImageKeys.getTokenKey(name + "_" + set.toLowerCase()));
} else {
final Card host = sa.getHostCard();
copy.setTokenSpawningAbility(sa);
if (original.isTransformable()) {
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
// the resulting token is a transforming token that has both a front face and a back face.
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
copy.setBackSide(original.isBackSide());
if (original.isTransformed()) {
copy.incrementTransformedTimestamp();
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
// need to create a physical card first, i need the original card faces
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
if (original.isTransformable()) {
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
// the resulting token is a transforming token that has both a front face and a back face.
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
copy.setBackSide(original.isBackSide());
if (original.isTransformed()) {
copy.incrementTransformedTimestamp();
}
}
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
// force update the now set State
if (original.isTransformable()) {
copy.setState(original.isTransformed() ? CardStateName.Transformed : CardStateName.Original, true, true);
} else {
copy.setState(copy.getCurrentStateName(), true, true);
}
}
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
// force update the now set State
if (original.isTransformable()) {
copy.setState(original.isTransformed() ? CardStateName.Transformed : CardStateName.Original, true, true);
} else {
copy.setState(copy.getCurrentStateName(), true, true);
}
copy.setTokenSpawningAbility(sa);
copy.setGamePieceType(GamePieceType.TOKEN);
return copy;

View File

@@ -1,7 +1,5 @@
package forge.game.ability.effects;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
@@ -15,7 +13,7 @@ import forge.game.cost.CostPart;
import forge.game.cost.CostReveal;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone;
@@ -94,7 +92,7 @@ public class DiscoverEffect extends SpellAbilityEffect {
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(found, p);
// filter out land abilities due to MDFC or similar
Iterables.removeIf(sas, Predicates.instanceOf(LandAbility.class));
sas.removeIf(sp -> sp.isLandAbility());
// the spell must also have a mana value equal to or less than the discover number
sas.removeIf(sp -> sp.getPayCosts().getTotalMana().getCMC() > num);

View File

@@ -35,7 +35,7 @@ public class InternalRadiationEffect extends SpellAbilityEffect {
final CardCollectionView milled = game.getAction().mill(new PlayerCollection(p), numRad, ZoneType.Graveyard, sa, moveParams);
table.triggerChangesZoneAll(game, sa);
int n = CardLists.count(milled, Predicates.not(CardPredicates.Presets.LANDS));
if (StaticAbilityGainLifeRadiation.gainLifeRadiation(p)) {
p.gainLife(n, sa.getHostCard(), sa);
} else {
@@ -49,7 +49,7 @@ public class InternalRadiationEffect extends SpellAbilityEffect {
game.getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
}
}
// and remove n rad counter
p.removeRadCounters(n);
}

View File

@@ -39,7 +39,7 @@ import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AlternativeCost;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.zone.Zone;
@@ -343,7 +343,7 @@ public class PlayEffect extends SpellAbilityEffect {
final Zone originZone = tgtCard.getZone();
// lands will be played
if (tgtSA instanceof LandAbility) {
if (tgtSA.isLandAbility()) {
tgtSA.resolve();
amount--;
if (remember) {

View File

@@ -57,7 +57,7 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
PaperCard ran = Aggregates.random(cards);
random = CardFactory.getCard(ran, activator, game);
cards.remove(ran);
} while (!activator.canPlayLand(random, false));
} while (!activator.canPlayLand(random, false, random.getFirstSpellAbility()));
source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp());
source.updateStateForView();

View File

@@ -5524,6 +5524,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return isInstant() || isSorcery() || (isAura() && !isInZone(ZoneType.Battlefield));
}
public final boolean hasPlayableLandFace() { return isLand() || (isModal() && getState(CardStateName.Modal).getType().isLand()); }
public final boolean isLand() { return getType().isLand(); }
public final boolean isBasicLand() { return getType().isBasicLand(); }
public final boolean isSnow() { return getType().isSnow(); }
@@ -7430,7 +7432,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
for (SpellAbility sa : getState(CardStateName.Modal).getSpellAbilities()) {
//add alternative costs as additional spell abilities
// only add Spells there
if (sa.isSpell()) {
if (sa.isSpell() || sa.isLandAbility()) {
abilities.add(sa);
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
}
@@ -7466,106 +7468,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
abilities.removeAll(toRemove);
// Land Abilities below, move them to CardFactory after MayPlayRefactor
if (getLastKnownZone().is(ZoneType.Battlefield)) {
return abilities;
}
if (getState(CardStateName.Original).getType().isLand()) {
LandAbility la = new LandAbility(this, player, null);
la.setCardState(oState);
if (la.canPlay()) {
abilities.add(la);
}
Card source = this;
boolean lkicheck = false;
// if Card is Facedown, need to check if MayPlay still applies
if (isFaceDown()) {
lkicheck = true;
source = CardCopyService.getLKICopy(source);
source.forceTurnFaceUp();
}
if (lkicheck) {
// double freeze tracker, so it doesn't update view
game.getTracker().freeze();
CardCollection preList = new CardCollection(source);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
}
// extra for MayPlay
for (CardPlayOption o : source.mayPlay(player)) {
la = new LandAbility(this, player, o);
la.setCardState(oState);
if (la.canPlay()) {
abilities.add(la);
}
}
// reset static abilities
if (lkicheck) {
game.getAction().checkStaticAbilities(false);
// clear delayed changes, this check should not have updated the view
game.getTracker().clearDelayed();
// need to unfreeze tracker
game.getTracker().unfreeze();
}
}
if (isModal() && hasState(CardStateName.Modal)) {
CardState modal = getState(CardStateName.Modal);
if (modal.getType().isLand()) {
LandAbility la = new LandAbility(this, player, null);
la.setCardState(modal);
Card source = CardCopyService.getLKICopy(this);
boolean lkicheck = true;
// if Card is Facedown, need to check if MayPlay still applies
if (isFaceDown()) {
source.forceTurnFaceUp();
}
// the modal state is not copied with lki, need to copy it extra
if (!source.hasState(CardStateName.Modal)) {
source.addAlternateState(CardStateName.Modal, false);
source.getState(CardStateName.Modal).copyFrom(this.getState(CardStateName.Modal), true);
}
source.setSplitStateToPlayAbility(la);
if (la.canPlay(source)) {
abilities.add(la);
}
if (lkicheck) {
// double freeze tracker, so it doesn't update view
game.getTracker().freeze();
CardCollection preList = new CardCollection(source);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
}
// extra for MayPlay
for (CardPlayOption o : source.mayPlay(player)) {
la = new LandAbility(this, player, o);
la.setCardState(modal);
if (la.canPlay(source)) {
abilities.add(la);
}
}
// reset static abilities
if (lkicheck) {
game.getAction().checkStaticAbilities(false);
// clear delayed changes, this check should not have updated the view
game.getTracker().clearDelayed();
// need to unfreeze tracker
game.getTracker().unfreeze();
}
}
}
return abilities;
}

View File

@@ -413,17 +413,16 @@ public class CardFactory {
// SpellPermanent only for Original State
if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal || c.getCurrentStateName().toString().startsWith("Specialize")) {
// this is the "default" spell for permanents like creatures and artifacts
if (c.isPermanent() && !c.isAura() && !c.isLand()) {
if (c.isLand()) {
SpellAbility sa = new LandAbility(c);
sa.setCardState(c.getCurrentState());
c.addSpellAbility(sa);
} else if (c.isPermanent() && !c.isAura()) {
// this is the "default" spell for permanents like creatures and artifacts
SpellAbility sa = new SpellPermanent(c);
// Currently only for Modal, might react different when state is always set
//if (c.getCurrentStateName() == CardStateName.Modal) {
sa.setCardState(c.getCurrentState());
//}
sa.setCardState(c.getCurrentState());
c.addSpellAbility(sa);
}
// TODO add LandAbility there when refactor MayPlay
}
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());

View File

@@ -22,7 +22,6 @@ import java.util.Comparator;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.game.CardTraitBase;
import forge.game.GameEntity;
import forge.game.combat.CombatUtil;
@@ -379,7 +378,7 @@ public final class CardPredicates {
/**
* a Predicate<Card> to get all lands.
*/
public static final Predicate<Card> LANDS = c -> c.isLand() || (!c.isInZone(ZoneType.Battlefield) && c.isModal() && c.getState(CardStateName.Modal).getType().isLand());
public static final Predicate<Card> LANDS = c -> c.isLand();
/**
* a Predicate<Card> to get all mana-producing lands.
*/

View File

@@ -32,7 +32,7 @@ import forge.game.event.*;
import forge.game.player.Player;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
@@ -1064,7 +1064,7 @@ public class PhaseHandler implements java.io.Serializable {
final Zone currentZone = saHost.getZone();
// Need to check if Zone did change
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa instanceof LandAbility)) {
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);

View File

@@ -43,7 +43,7 @@ import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.*;
import forge.game.trigger.Trigger;
@@ -1691,9 +1691,9 @@ public class Player extends GameEntity implements Comparable<Player> {
game.fireEvent(new GameEventShuffle(this));
}
public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) {
public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming, SpellAbility cause) {
// Dakkon Blackblade Avatar will use a similar effect
if (canPlayLand(land, ignoreZoneAndTiming)) {
if (canPlayLand(land, ignoreZoneAndTiming, cause)) {
playLandNoCheck(land, null);
return true;
}
@@ -1706,7 +1706,7 @@ public class Player extends GameEntity implements Comparable<Player> {
land.setController(this, 0);
if (land.isFaceDown()) {
land.turnFaceUp(null);
if (cause instanceof LandAbility) {
if (cause.isLandAbility()) {
land.changeToState(cause.getCardStateName());
}
}
@@ -1730,12 +1730,6 @@ public class Player extends GameEntity implements Comparable<Player> {
return c;
}
public final boolean canPlayLand(final Card land) {
return canPlayLand(land, false);
}
public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming) {
return canPlayLand(land, ignoreZoneAndTiming, null);
}
public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming, SpellAbility landSa) {
if (!ignoreZoneAndTiming) {
// CR 305.3

View File

@@ -21,24 +21,20 @@ import forge.card.CardStateName;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardCopyService;
import forge.game.card.CardPlayOption;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
public class LandAbility extends Ability {
public class LandAbility extends AbilityStatic {
public LandAbility(Card sourceCard, Player p, CardPlayOption mayPlay) {
super(sourceCard, new Cost(ManaCost.NO_COST, false));
setActivatingPlayer(p);
setMayPlay(mayPlay);
}
public LandAbility(Card sourceCard) {
this(sourceCard, sourceCard.getController(), null);
super(sourceCard, ManaCost.NO_COST);
getRestrictions().setZone(ZoneType.Hand);
}
public boolean canPlay(Card newHost) {
@@ -46,11 +42,21 @@ public class LandAbility extends Ability {
return p.canPlayLand(newHost, false, this);
}
@Override
public boolean isLandAbility() { return true; }
@Override
public boolean isSecondary() {
return true;
}
@Override
public boolean canPlay() {
Card land = this.getHostCard();
final Player p = this.getActivatingPlayer();
if (p == null || land.isInZone(ZoneType.Battlefield)) {
return false;
}
if (this.getCardState() != null && land.getCurrentStateName() != this.getCardStateName()) {
if (!land.isLKI()) {
land = CardCopyService.getLKICopy(land);
@@ -113,4 +119,41 @@ public class LandAbility extends Ability {
return sb.toString();
}
@Override
public Card getAlternateHost(Card source) {
boolean lkicheck = false;
// need to be done before so it works with Vivien and Zoetic Cavern
if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) {
if (!source.isLKI()) {
source = CardCopyService.getLKICopy(source);
}
source.forceTurnFaceUp();
lkicheck = true;
}
if (getCardState() != null && source.getCurrentStateName() != getCardStateName()) {
if (!source.isLKI()) {
source = CardCopyService.getLKICopy(source);
}
CardStateName stateName = getCardState().getStateName();
if (!source.hasState(stateName)) {
source.addAlternateState(stateName, false);
source.getState(stateName).copyFrom(getHostCard().getState(stateName), true);
}
source.setState(stateName, false);
if (getHostCard().isDoubleFaced()) {
source.setBackSide(getHostCard().getRules().getSplitType().getChangedStateName().equals(stateName));
}
// need to reset CMC
source.setLKICMC(-1);
source.setLKICMC(source.getCMC());
lkicheck = true;
}
return lkicheck ? source : null;
}
}

View File

@@ -153,6 +153,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
this.castFaceDown = faceDown;
}
@Override
public Card getAlternateHost(Card source) {
boolean lkicheck = false;

View File

@@ -527,6 +527,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public boolean isSpell() { return false; }
public boolean isAbility() { return true; }
public boolean isActivatedAbility() { return false; }
public boolean isLandAbility() { return false; }
public boolean isTurnFaceUp() {
return isMorphUp() || isDisguiseUp() || isManifestUp() || isCloakUp();
@@ -2185,7 +2186,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
}
else if (incR[0].contains("LandAbility")) {
if (!(root instanceof LandAbility)) {
if (!(root.isLandAbility())) {
return testFailed;
}
}
@@ -2544,7 +2545,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (getRestrictions().isInstantSpeed()) {
return true;
}
if ((isSpell() || this instanceof LandAbility) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
if ((isSpell() || this.isLandAbility()) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
return true;
}
@@ -2589,6 +2590,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return true;
}
public Card getAlternateHost(Card source) {
return null;
}
public boolean hasOptionalKeywordAmount(KeywordInterface kw) {
return this.optionalKeywordAmount.contains(kw.getKeyword(), Pair.of(kw.getIdx(), kw.getStaticId()));
}

View File

@@ -51,6 +51,7 @@ import com.badlogic.gdx.Version;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.badlogic.gdx.backends.android.AndroidAudio;
import com.badlogic.gdx.backends.android.AsynchronousAndroidAudio;
import com.getkeepsafe.relinker.ReLinker;
import de.cketti.fileprovider.PublicFileProvider;
import forge.Forge;
@@ -122,7 +123,8 @@ public class Main extends AndroidApplication {
@Override
public AndroidAudio createAudio(Context context, AndroidApplicationConfiguration config) {
return super.createAudio(context, config);
return new AsynchronousAndroidAudio(context, config);
//return super.createAudio(context, config);
}
@Override

View File

@@ -1,6 +1,6 @@
package forge.ai.simulation;
import forge.game.spellability.LandAbility;
import java.util.ArrayList;
import java.util.List;
@@ -84,7 +84,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest {
SpellAbilityPicker picker = new SpellAbilityPicker(game, p);
SpellAbility sa = picker.chooseSpellAbilityToPlay(null);
AssertJUnit.assertTrue(sa instanceof LandAbility);
AssertJUnit.assertTrue(sa.isLandAbility());
AssertJUnit.assertEquals(mountain, sa.getHostCard());
Plan plan = picker.getPlan();

View File

@@ -324,7 +324,8 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
if (_freeze){
//Mob has defeated player in battle, hold still until player has a chance to move away.
//Without this moving enemies can immediately restart battle.
if (spriteToPlayer.len() < unfreezeRange) {
float distance = spriteToPlayer.len();
if (distance < unfreezeRange) {
timer += delta;
return Vector2.Zero;
}
@@ -635,7 +636,8 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
}
public boolean isFrozen() {
return _freeze;
}
}

View File

@@ -2,15 +2,19 @@ package forge.adventure.player;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.github.tommyettinger.textra.TextraLabel;
import com.google.common.collect.Lists;
import forge.Forge;
import forge.adventure.data.*;
import forge.adventure.pointofintrest.PointOfInterestChanges;
import forge.adventure.scene.AdventureDeckEditor;
import forge.adventure.scene.DeckEditScene;
import forge.adventure.stage.GameStage;
import forge.adventure.stage.MapStage;
import forge.adventure.stage.WorldStage;
import forge.adventure.util.*;
import forge.adventure.world.WorldSave;
import forge.card.ColorSet;
@@ -223,6 +227,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return name;
}
public Boolean isFemale() {
return isFemale;
}
public float getWorldPosX() {
return worldPosX;
}
@@ -594,6 +602,24 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
return HeroListData.getRaces().get(Current.player().heroRace);
}
public GameStage getCurrentGameStage() {
if (MapStage.getInstance().isInMap())
return MapStage.getInstance();
return WorldStage.getInstance();
}
public void addStatusMessage(String iconName, String message, Integer itemCount, float x, float y) {
String symbol = itemCount == null || itemCount < 0 ? "" : " +";
String icon = iconName == null ? "" : "[+" + iconName + "]";
String count = itemCount == null ? "" : String.valueOf(itemCount);
TextraLabel actor = Controls.newTextraLabel("[%95]" + icon + "[WHITE]" + symbol + count + " " + message);
actor.setPosition(x, y);
actor.addAction(Actions.sequence(
Actions.parallel(Actions.moveBy(0f, 5f, 3f), Actions.fadeIn(2f)),
Actions.hide(),
Actions.removeActor())
);
getCurrentGameStage().addActor(actor);
}
public void addCard(PaperCard card) {
cards.add(card);
newCards.add(card);
@@ -752,8 +778,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
}
public void setShards(int number) {
shards = number;
onShardsChangeList.emit();
boolean changed = shards != number;
if (changed) {
shards = number;
onShardsChangeList.emit();
}
}
public void addBlessing(EffectData bless) {

View File

@@ -161,7 +161,7 @@ public class InventoryScene extends UIScene {
ConsoleCommandInterpreter.getInstance().command(data.commandOnUse);
if (data.dialogOnUse != null && data.dialogOnUse.text != null && !data.dialogOnUse.text.isEmpty()) {
MapDialog dialog = new MapDialog(data.dialogOnUse, MapStage.getInstance(),0,null);
MapStage.instance.showDialog();
MapStage.getInstance().showDialog();
dialog.activate();
ChangeListener listen = new ChangeListener() {
@Override

View File

@@ -3,12 +3,12 @@ package forge.adventure.scene;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.github.tommyettinger.textra.TextraButton;
import com.github.tommyettinger.textra.TextraLabel;
import forge.Forge;
import forge.adventure.data.DialogData;
@@ -48,20 +48,20 @@ public class NewGameScene extends MenuScene {
private final TextraLabel starterEditionLabel;
private final Array<String> custom;
private final TextraLabel colorLabel;
private final TextraButton difficultyHelp;
private final ImageButton difficultyHelp;
private DialogData difficultySummary;
private final TextraButton modeHelp;
private final ImageButton modeHelp;
private DialogData modeSummary;
private final Random rand = new Random();
private final Array<AdventureModes> modes = new Array<>();
private NewGameScene() {
super(Forge.isLandscapeMode() ? "ui/new_game.json" : "ui/new_game_portrait.json");
gender = ui.findActor("gender");
selectedName = ui.findActor("nameField");
selectedName.setText(NameGenerator.getRandomName(gender.getCurrentIndex() > 0 ? "Female" : "Male", "Any", ""));
generateName();
avatarImage = ui.findActor("avatarPreview");
mode = ui.findActor("mode");
modeHelp = ui.findActor("modeHelp");
@@ -127,13 +127,12 @@ public class NewGameScene extends MenuScene {
modeNames[i] = modes.get(i).getName();
mode.setTextList(modeNames);
gender.setTextList(new String[]{Forge.getLocalizer().getMessage("lblMale"), Forge.getLocalizer().getMessage("lblFemale")});
gender.setTextList(new String[]{Forge.getLocalizer().getMessage("lblMale") + "[%120][CYAN] \u2642",
Forge.getLocalizer().getMessage("lblFemale") + "[%120][MAGENTA] \u2640"});
gender.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
//gender should be either Male or Female
String val = gender.getCurrentIndex() > 0 ? "Female" : "Male";
selectedName.setText(NameGenerator.getRandomName(val, "Any", ""));
nameTT = 0.8f;
super.clicked(event, x, y);
}
});
@@ -150,6 +149,13 @@ public class NewGameScene extends MenuScene {
}
});
race = ui.findActor("race");
race.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
avatarTT = 0.7f;
super.clicked(event, x, y);
}
});
race.addListener(event -> NewGameScene.this.updateAvatar());
race.setTextList(HeroListData.getRaces());
difficulty = ui.findActor("difficulty");
@@ -161,15 +167,13 @@ public class NewGameScene extends MenuScene {
for (DifficultyData diff : Config.instance().getConfigData().difficulties) {
if (diff.startingDifficulty)
startingDifficulty = i;
diffList.add(Forge.getLocalizer().getInstance().getMessageorUseDefault("lbl" + diff.name, diff.name));
diffList.add(Forge.getLocalizer().getMessageorUseDefault("lbl" + diff.name, diff.name));
i++;
}
difficulty.setTextList(diffList);
difficulty.setCurrentIndex(startingDifficulty);
Random rand = new Random();
avatarIndex = rand.nextInt();
updateAvatar();
generateAvatar();
gender.setCurrentIndex(rand.nextInt());
colorId.setCurrentIndex(rand.nextInt());
race.setCurrentIndex(rand.nextInt());
@@ -177,8 +181,16 @@ public class NewGameScene extends MenuScene {
ui.onButtonPress("start", NewGameScene.this::start);
ui.onButtonPress("leftAvatar", NewGameScene.this::leftAvatar);
ui.onButtonPress("rightAvatar", NewGameScene.this::rightAvatar);
difficultyHelp.addListener(new ClickListener(){ public void clicked(InputEvent e, float x, float y){ showDifficultyHelp(); }});
modeHelp.addListener(new ClickListener(){ public void clicked(InputEvent e, float x, float y){ showModeHelp(); }});
difficultyHelp.addListener(new ClickListener() {
public void clicked(InputEvent e, float x, float y) {
showDifficultyHelp();
}
});
modeHelp.addListener(new ClickListener() {
public void clicked(InputEvent e, float x, float y) {
showModeHelp();
}
});
}
private static NewGameScene object;
@@ -189,6 +201,37 @@ public class NewGameScene extends MenuScene {
return object;
}
float avatarT = 1f, avatarTT = 1f;
float nameT = 1f, nameTT = 1f;
@Override
public void act(float delta) {
super.act(delta);
if (avatarT > avatarTT) {
avatarTT += (delta / 0.5f);
generateAvatar();
} else {
avatarTT = avatarT;
}
if (nameT > nameTT) {
nameTT += (delta / 0.5f);
generateName();
} else {
nameTT = nameT;
}
}
private void generateAvatar() {
avatarIndex = rand.nextInt();
updateAvatar();
}
private void generateName() {
//gender should be either Male or Female
String val = gender.getCurrentIndex() > 0 ? "Female" : "Male";
selectedName.setText(NameGenerator.getRandomName(val, "Any", ""));
}
boolean started = false;
public boolean start() {
@@ -196,7 +239,7 @@ public class NewGameScene extends MenuScene {
return true;
started = true;
if (selectedName.getText().isEmpty()) {
selectedName.setText(NameGenerator.getRandomName("Any", "Any", ""));
generateName();
}
Runnable runnable = () -> {
started = false;
@@ -295,11 +338,11 @@ public class NewGameScene extends MenuScene {
dismiss.name = "OK";
DialogData matchImpacts = new DialogData();
matchImpacts.text = String.format("Difficulty: %s\nStarting Life: %d\nEnemy Health: %d%%\nGold loss on defeat: %d%%\nLife loss on defeat: %d%%", selectedDifficulty.name, selectedDifficulty.startingLife, (int)(selectedDifficulty.enemyLifeFactor * 100) , (int)(selectedDifficulty.goldLoss*100), (int)(selectedDifficulty.lifeLoss*100));
matchImpacts.text = String.format("Difficulty: %s\nStarting Life: %d\nEnemy Health: %d%%\nGold loss on defeat: %d%%\nLife loss on defeat: %d%%", selectedDifficulty.name, selectedDifficulty.startingLife, (int) (selectedDifficulty.enemyLifeFactor * 100), (int) (selectedDifficulty.goldLoss * 100), (int) (selectedDifficulty.lifeLoss * 100));
matchImpacts.name = "Duels";
DialogData economyImpacts = new DialogData();
economyImpacts.text = String.format("Difficulty: %s\nStarting Gold: %d\nStarting Mana Shards: %d\nCard Sale Price: %d%%\nMana Shard Sale Price: %d%%\nRandom loot rate: %d%%", selectedDifficulty.name, selectedDifficulty.staringMoney, selectedDifficulty.startingShards, (int)(selectedDifficulty.sellFactor*100), (int)(selectedDifficulty.shardSellRatio*100), (int)(selectedDifficulty.rewardMaxFactor*100));
economyImpacts.text = String.format("Difficulty: %s\nStarting Gold: %d\nStarting Mana Shards: %d\nCard Sale Price: %d%%\nMana Shard Sale Price: %d%%\nRandom loot rate: %d%%", selectedDifficulty.name, selectedDifficulty.staringMoney, selectedDifficulty.startingShards, (int) (selectedDifficulty.sellFactor * 100), (int) (selectedDifficulty.shardSellRatio * 100), (int) (selectedDifficulty.rewardMaxFactor * 100));
economyImpacts.name = "Economy";
difficultySummary.options = new DialogData[3];

View File

@@ -12,6 +12,7 @@ import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Scaling;
import com.github.tommyettinger.textra.TextraButton;
import com.github.tommyettinger.textra.TextraLabel;
import com.github.tommyettinger.textra.TypingLabel;
import forge.Forge;
import forge.adventure.character.EnemySprite;
import forge.adventure.data.EnemyData;
@@ -44,7 +45,8 @@ public class PlayerStatisticScene extends UIScene {
TextraLabel wins, totalWins, eventWins, eventMatchWins;
TextraLabel loss, totalLoss, eventLosses, eventMatchLosses;
TextraLabel winloss, lossWinRatio, eventLossWinRatio, eventMatchLossWinRatio;
TextraLabel playerName, headerAchievements, headerAvatar, headerName, headerWinLoss;
TextraLabel headerAchievements, headerAvatar, headerName, headerWinLoss;
TypingLabel playerName;
TextraButton back, toggleAward;
private final Table scrollContainer, achievementContainer;
TextraLabel blessingScroll;
@@ -196,7 +198,9 @@ public class PlayerStatisticScene extends UIScene {
scrollContainer.clear();
if (playerName != null) {
playerName.setText(GamePlayerUtil.getGuiPlayer().getName());
String gender = Current.player().isFemale() ? "{GRADIENT=MAGENTA;MAUVE;1;1}\u2640{ENDGRADIENT}[BLACK] " : "{GRADIENT=CYAN;BLUE;1;1}\u2642{ENDGRADIENT}[BLACK] ";
playerName.setText(gender + GamePlayerUtil.getGuiPlayer().getName());
playerName.skipToTheEnd();
}
if (avatar != null) {
avatar.setDrawable(new TextureRegionDrawable(Current.player().avatar()));

View File

@@ -8,10 +8,20 @@ import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.actions.SequenceAction;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Dialog;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
@@ -28,8 +38,19 @@ import forge.adventure.character.CharacterSprite;
import forge.adventure.data.AdventureQuestData;
import forge.adventure.data.ItemData;
import forge.adventure.player.AdventurePlayer;
import forge.adventure.scene.*;
import forge.adventure.util.*;
import forge.adventure.scene.DeckSelectScene;
import forge.adventure.scene.GameScene;
import forge.adventure.scene.InventoryScene;
import forge.adventure.scene.MapViewScene;
import forge.adventure.scene.QuestLogScene;
import forge.adventure.scene.Scene;
import forge.adventure.scene.TileMapScene;
import forge.adventure.util.AdventureQuestController;
import forge.adventure.util.Config;
import forge.adventure.util.Controls;
import forge.adventure.util.Current;
import forge.adventure.util.KeyBinding;
import forge.adventure.util.UIActor;
import forge.adventure.world.WorldSave;
import forge.deck.Deck;
import forge.gui.GuiBase;
@@ -47,11 +68,11 @@ public class GameHUD extends Stage {
static public GameHUD instance;
private final GameStage gameStage;
private final Image avatar, miniMapPlayer;
private final TextraLabel lifePoints;
private final TextraLabel money;
private final TextraLabel shards;
private final TypingLabel lifePoints;
private final TypingLabel money;
private final TypingLabel shards;
private final TextraLabel keys;
private TextraLabel notificationText = Controls.newTextraLabel("");
private final TextraLabel notificationText = Controls.newTextraLabel("");
private final Image miniMap, gamehud, mapborder, avatarborder, blank;
private final InputEvent eventTouchDown, eventTouchUp;
private final TextraButton deckActor, openMapActor, menuActor, logbookActor, inventoryActor, exitToWorldMapActor, bookmarkActor;
@@ -60,7 +81,7 @@ public class GameHUD extends Stage {
private final Console console;
float TOUCHPAD_SCALE = 70f, referenceX;
float opacity = 1f;
private boolean debugMap, updatelife;
private boolean debugMap, transluscent, hidden;
private final Dialog dialog;
private boolean dialogOnlyInput;
@@ -70,6 +91,10 @@ public class GameHUD extends Stage {
private String lifepointsTextColor = "";
private final ScrollPane scrollPane;
private final ScrollPane notificationPane;
private final Group mapGroup = new Group();
private final Group hudGroup = new Group();
private final Group menuGroup = new Group();
private final Group avatarGroup = new Group();
private GameHUD(GameStage gameStage) {
super(new ScalingViewport(Scaling.stretch, Scene.getIntendedWidth(), Scene.getIntendedHeight()), gameStage.getBatch());
@@ -129,32 +154,50 @@ public class GameHUD extends Stage {
ui.onButtonPress("exittoworldmap", this::exitToWorldMap);
ui.onButtonPress("bookmark", this::bookmark);
lifePoints = ui.findActor("lifePoints");
lifePoints.skipToTheEnd();
shards = ui.findActor("shards");
shards.skipToTheEnd();
money = ui.findActor("money");
shards.setText("[%95][+Shards] 0");
money.setText("[%95][+Gold] ");
lifePoints.setText("[%95][+Life] 20/20");
money.skipToTheEnd();
shards.setText("[%95][+Shards]");
money.setText("[%95][+Gold]");
lifePoints.setText("[%95][+Life]");
keys = Controls.newTextraLabel("");
scrollPane = new ScrollPane(keys);
scrollPane.setPosition(2, 2);
scrollPane.setStyle(Controls.getSkin().get("translucent", ScrollPane.ScrollPaneStyle.class));
addActor(scrollPane);
AdventurePlayer.current().onLifeChange(() -> lifePoints.setText("[%95][+Life]" + lifepointsTextColor + " " + AdventurePlayer.current().getLife() + "/" + AdventurePlayer.current().getMaxLife()));
AdventurePlayer.current().onShardsChange(() -> shards.setText("[%95][+Shards] " + AdventurePlayer.current().getShards()));
AdventurePlayer.current().onLifeChange(() -> {
String effect = "{EMERGE}";
String effectEnd = "{ENDEMERGE}";
String heartbeat = "";
//colored lifepoints
if (Current.player().getLife() >= Current.player().getMaxLife()) {
//color green if max life
lifepointsTextColor = "[GREEN]";
} else if (Current.player().getLife() <= 5) {
//color red if critical
effect = "";
effectEnd = "";
heartbeat = "{HEARTBEAT=0.5;0.5}";
lifepointsTextColor = "{ENDHEARTBEAT}[RED]";
} else {
lifepointsTextColor = "[WHITE]";
}
lifePoints.restart("[%95]" + heartbeat + "[+Life]" + lifepointsTextColor + effect + " " + AdventurePlayer.current().getLife() + effectEnd + "/" + AdventurePlayer.current().getMaxLife());
});
AdventurePlayer.current().onShardsChange(() -> shards.restart("[%95][+Shards]{EMERGE} " + AdventurePlayer.current().getShards() + "{ENDEMERGE}"));
AdventurePlayer.current().onGoldChange(() -> money.restart("[%95][+Gold]{EMERGE} " + AdventurePlayer.current().getGold() + "{ENDEMERGE}"));
AdventurePlayer.current().onEquipmentChanged(this::updateAbility);
WorldSave.getCurrentSave().getPlayer().onGoldChange(() -> money.setText("[%95][+Gold] " + AdventurePlayer.current().getGold()));
addActor(ui);
addActor(miniMapPlayer);
console = new Console();
console.setBounds(0, GuiBase.isAndroid() ? getHeight() : 0, getWidth(), getHeight() / 2);
console.setVisible(false);
ui.addActor(console);
if (GuiBase.isAndroid()) {
avatar.addListener(new ConsoleToggleListener());
avatarborder.addListener(new ConsoleToggleListener());
gamehud.addListener(new ConsoleToggleListener());
}
avatar.addListener(new ConsoleToggleListener());
avatarborder.addListener(new ConsoleToggleListener());
gamehud.addListener(new ConsoleToggleListener());
WorldSave.getCurrentSave().onLoad(this::enter);
eventTouchDown = new InputEvent();
@@ -166,11 +209,36 @@ public class GameHUD extends Stage {
notificationPane = new ScrollPane(notificationText);
notificationPane.setTouchable(Touchable.childrenOnly);
notificationPane.setBounds(5, GuiBase.isAndroid() ? getHeight() : -notificationText.getPrefHeight(), getWidth()*0.4f, 25);
notificationPane.setBounds(5, GuiBase.isAndroid() ? getHeight() : -notificationText.getPrefHeight(), getWidth() * 0.4f, 25);
notificationPane.setStyle(Controls.getSkin().get("paper", ScrollPane.ScrollPaneStyle.class));
notificationPane.getColor().a = 0f;
ui.addActor(notificationPane);
//MAP
mapGroup.addActor(miniMap);
mapGroup.addActor(mapborder);
mapGroup.addActor(openMapActor);
mapGroup.addActor(miniMapPlayer);
ui.addActor(mapGroup);
//HUD
hudGroup.addActor(gamehud);
hudGroup.addActor(lifePoints);
hudGroup.addActor(shards);
hudGroup.addActor(money);
hudGroup.addActor(blank);
ui.addActor(hudGroup);
//MENU
menuGroup.addActor(deckActor);
menuGroup.addActor(menuActor);
menuGroup.addActor(logbookActor);
menuGroup.addActor(inventoryActor);
menuGroup.addActor(exitToWorldMapActor);
menuGroup.addActor(bookmarkActor);
ui.addActor(menuGroup);
//AVATAR
avatarGroup.addActor(avatar);
avatarGroup.addActor(avatarborder);
ui.addActor(avatarGroup);
}
private void openMap() {
@@ -268,7 +336,6 @@ public class GameHUD extends Stage {
@Override
public void draw() {
updatelife = false;
int yPos = (int) gameStage.player.getY();
int xPos = (int) gameStage.player.getX();
act(Gdx.graphics.getDeltaTime()); //act the Hud
@@ -278,32 +345,10 @@ public class GameHUD extends Stage {
miniMapPlayer.setPosition(miniMap.getX() + xPosMini - miniMapPlayer.getWidth() / 2, miniMap.getY() + yPosMini - miniMapPlayer.getHeight() / 2);
miniMapPlayer.setVisible(miniMap.isVisible() &&
!Controls.actorContainsVector(notificationPane, new Vector2(miniMapPlayer.getX(),miniMapPlayer.getY()))
&& (!Controls.actorContainsVector(console, new Vector2(miniMapPlayer.getX(),miniMapPlayer.getY()))
!Controls.actorContainsVector(notificationPane, new Vector2(miniMapPlayer.getX(), miniMapPlayer.getY()))
&& (!Controls.actorContainsVector(console, new Vector2(miniMapPlayer.getX(), miniMapPlayer.getY()))
|| !console.isVisible())); // prevent drawing on top of console or notifications
//colored lifepoints
if (Current.player().getLife() >= Current.player().getMaxLife()) {
//color green if max life
if (!lifepointsTextColor.equals("[GREEN]")) {
lifepointsTextColor = "[GREEN]";
updatelife = true;
}
} else if (Current.player().getLife() <= 5) {
//color red if critical
if (!lifepointsTextColor.equals("[RED]")) {
lifepointsTextColor = "[RED]";
updatelife = true;
}
} else {
if (!lifepointsTextColor.isEmpty()) {
lifepointsTextColor = "";
updatelife = true;
}
}
if (updatelife) {
updatelife = false;
lifePoints.setText("[%95][+Life]" + lifepointsTextColor + " " + AdventurePlayer.current().getLife() + "/" + AdventurePlayer.current().getMaxLife());
}
if (!MapStage.getInstance().isInMap())
updateMusic();
else
@@ -356,7 +401,7 @@ public class GameHUD extends Stage {
switch (GameScene.instance().getAdventurePlayerLocation(false, false)) {
case "capital":
case "town":
if(MapStage.getInstance().isInMap()) {
if (MapStage.getInstance().isInMap()) {
int rep = TileMapScene.instance().getPointOfInterestChanges().getMapReputation();
String reputationText = TileMapScene.instance().rootPoint.getDisplayName() + "\nReputation: " + (rep > 0 ? "[GREEN]" : rep < 0 ? "[RED]" : "[WHITE]") + rep + "[/]";
if (fromWorldMap) {
@@ -405,6 +450,7 @@ public class GameHUD extends Stage {
}
if (MapStage.getInstance().isInMap())
updateBookmarkActor(MapStage.getInstance().getChanges().isBookmarked());
avatarGroup.setZIndex(ui.getChildren().size);
}
void clearAbility() {
@@ -663,11 +709,6 @@ public class GameHUD extends Stage {
gameStage.openMenu();
}
private void setVisibility(Actor actor, boolean visible) {
if (actor != null)
actor.setVisible(visible);
}
private void setDisabled(Actor actor, boolean value, String enabled, String disabled) {
if (actor instanceof TextraButton) {
((TextraButton) actor).setDisabled(value);
@@ -678,38 +719,70 @@ public class GameHUD extends Stage {
private void setAlpha(Actor actor, boolean visible) {
if (actor != null) {
if (visible)
actor.getColor().a = 1f;
actor.addAction(Actions.alpha(1f, 0.5f));
else
actor.getColor().a = 0.4f;
actor.addAction(Actions.alpha(actor == mapGroup ? 0f : 0.4f, 0.5f));
}
}
public void showHideMap(boolean visible) {
setVisibility(miniMap, visible);
setVisibility(mapborder, visible);
setVisibility(openMapActor, visible);
setVisibility(miniMapPlayer, visible);
setVisibility(gamehud, visible);
setVisibility(lifePoints, visible);
setVisibility(shards, visible);
setVisibility(money, visible);
setVisibility(blank, visible);
setDisabled(exitToWorldMapActor, !MapStage.getInstance().isInMap(), "[%120][+ExitToWorldMap]", "---");
setDisabled(bookmarkActor, !MapStage.getInstance().isInMap(), "[%120][+Bookmark]", "---");
setAlpha(avatarborder, visible);
setAlpha(avatar, visible);
setAlpha(deckActor, visible);
setAlpha(menuActor, visible);
setAlpha(logbookActor, visible);
setAlpha(inventoryActor, visible);
setAlpha(exitToWorldMapActor, visible);
setAlpha(bookmarkActor, visible);
transluscent = !visible;
setAlpha(mapGroup, visible);
setAlpha(hudGroup, visible);
setAlpha(menuGroup, visible);
setAlpha(avatarGroup, visible);
setDisabled(exitToWorldMapActor, !MapStage.getInstance().isInMap(), "[%120][+ExitToWorldMap]", "\uFF0F");
setDisabled(bookmarkActor, !MapStage.getInstance().isInMap(), "[%120][+Bookmark]", "\uFF0F");
for (TextraButton button : abilityButtonMap) {
setAlpha(button, visible);
}
opacity = visible ? 1f : 0.4f;
}
public void setHUDOpacity(boolean translucent) {
if (translucent) {
if (!MapStage.getInstance().isInMap())
return; //WorldStage opacity issue
setAlpha(hudGroup, false);
setAlpha(menuGroup, false);
setAlpha(avatarGroup, false);
for (TextraButton button : abilityButtonMap) {
setAlpha(button, false);
}
transluscent = true;
} else {
setAlpha(hudGroup, true);
setAlpha(menuGroup, true);
setAlpha(avatarGroup, true);
for (TextraButton button : abilityButtonMap) {
setAlpha(button, true);
}
transluscent = false;
}
}
public void showHideHUD(boolean hide) {
if (hide) {
hudGroup.addAction(Actions.fadeOut(0.5f));
menuGroup.addAction(Actions.fadeOut(0.5f));
if (!MapStage.getInstance().isInMap())
mapGroup.addAction(Actions.fadeOut(0.5f));
if (MapStage.getInstance().isInMap())
avatarGroup.addAction(Actions.alpha(0.4f, 0.5f));
hidden = true;
} else {
float alpha = MapStage.getInstance().isInMap() ? 0.4f : 1f;
avatarGroup.addAction(Actions.alpha(alpha, 0.5f));
hudGroup.addAction(Actions.alpha(alpha, 0.5f));
menuGroup.addAction(Actions.alpha(alpha, 0.5f));
if (!MapStage.getInstance().isInMap())
mapGroup.addAction(Actions.fadeIn(0.5f));
hidden = false;
}
}
void toggleConsole() {
console.toggle();
if (console.isVisible()) {
@@ -810,8 +883,8 @@ public class GameHUD extends Stage {
public boolean act(float v) {
if (exitDungeon) {
MapStage.getInstance().exitDungeon();
setDisabled(exitToWorldMapActor, true, "[%120][+ExitToWorldMap]", "---");
setDisabled(bookmarkActor, true, "[%120][+Bookmark]", "---");
setDisabled(exitToWorldMapActor, true, "[%120][+ExitToWorldMap]", "\uFF0F");
setDisabled(bookmarkActor, true, "[%120][+Bookmark]", "\uFF0F");
}
return true;
}
@@ -861,9 +934,21 @@ public class GameHUD extends Stage {
@Override
public boolean longPress(Actor actor, float x, float y) {
toggleConsole();
if (GuiBase.isAndroid())
toggleConsole();
return super.longPress(actor, x, y);
}
@Override
public void tap(InputEvent event, float x, float y, int count, int button) {
if (console.isVisible())
return;
if (count > 1 && button == 0)
showHideHUD(!hidden);
else if (button == 0)
setHUDOpacity(!transluscent);
super.tap(event, x, y, count, button);
}
}
public void updateMusic() {
@@ -927,13 +1012,13 @@ public class GameHUD extends Stage {
notificationText.setWrap(false);
notificationText.setText(text);
notificationText.setColor(Color.BLACK);
notificationText.setWidth(Math.min(notificationText.getPrefWidth(), Forge.isLandscapeMode()?getWidth() * 0.25f : getWidth() - 25));
notificationText.setWidth(Math.min(notificationText.getPrefWidth(), Forge.isLandscapeMode() ? getWidth() * 0.25f : getWidth() - 25));
notificationText.setWrap(true);
notificationText.layout();
notificationPane.setSize(notificationText.getWidth() + 10, notificationText.getPrefHeight() + 20);
notificationPane.setPosition(5, Forge.isLandscapeMode()? -notificationPane.getHeight(): getHeight());
notificationPane.setPosition(5, Forge.isLandscapeMode() ? -notificationPane.getHeight() : getHeight());
notificationPane.getColor().a = 1f;
notificationPane.layout();
@@ -948,22 +1033,22 @@ public class GameHUD extends Stage {
newNotification = Actions.after(Actions.sequence(preconfigureNotification,
Actions.moveTo(5, 0, 2f),
Actions.delay(10f),
Actions.alpha(0f,3f),
Actions.sizeTo(0,0)));
Actions.alpha(0f, 3f),
Actions.sizeTo(0, 0)));
} else {
newNotification = Actions.after(Actions.sequence(preconfigureNotification,
Actions.moveToAligned(5, getHeight(), Align.topLeft, 2f),
Actions.delay(10f),
Actions.alpha(0f,3f),
Actions.sizeTo(0,0)));
Actions.alpha(0f, 3f),
Actions.sizeTo(0, 0)));
}
notificationPane.addAction(newNotification);
}
public void clearNotifications(){
public void clearNotifications() {
notificationText.setText("");
notificationPane.setBounds(5, Forge.isLandscapeMode() ? -notificationText.getPrefHeight() : getHeight(), getWidth()*0.4f, 25);
notificationPane.setBounds(5, Forge.isLandscapeMode() ? -notificationText.getPrefHeight() : getHeight(), getWidth() * 0.4f, 25);
notificationPane.setStyle(Controls.getSkin().get("paper", ScrollPane.ScrollPaneStyle.class));
notificationPane.getColor().a = 0f;
}

View File

@@ -27,6 +27,7 @@ import com.github.tommyettinger.textra.TypingLabel;
import forge.Forge;
import forge.adventure.character.*;
import forge.adventure.data.*;
import forge.adventure.player.AdventurePlayer;
import forge.adventure.pointofintrest.PointOfInterestChanges;
import forge.adventure.scene.*;
import forge.adventure.util.*;
@@ -959,10 +960,11 @@ public class MapStage extends GameStage {
} else {
Vector2 destination = mob.getTargetVector(player, verticesNearPlayer, delta);
if (destination.epsilonEquals(mob.pos()) && !mob.aggro) {
if (mob.isFrozen() || (destination.epsilonEquals(mob.pos()) && !mob.aggro)) {
mob.setAnimation(CharacterSprite.AnimationTypes.Idle);
continue;
}
if (destination.equals(mob.targetVector) && mob.getNavPath() != null)
navPath = mob.getNavPath();
@@ -1025,20 +1027,42 @@ public class MapStage extends GameStage {
Gdx.input.vibrate(50);
if (Controllers.getCurrent() != null && Controllers.getCurrent().canVibrate())
Controllers.getCurrent().startVibration(100, 1);
startPause(0.1f, () -> { //Switch to item pickup scene.
RewardSprite RS = (RewardSprite) actor;
RewardScene.instance().loadRewards(RS.getRewards(), RewardScene.Type.Loot, null);
RS.remove();
actors.removeValue(RS, true);
changes.deleteObject(RS.getId());
Forge.switchScene(RewardScene.instance());
});
RewardSprite RS = (RewardSprite) actor;
Array<Reward> rewards = RS.getRewards();
if (rewards.size == 1) {
Reward reward = rewards.get(0);
switch (reward.getType()) {
case Life:
case Shards:
case Gold:
String message = Forge.getLocalizer().getMessageorUseDefault("lbl" + reward.getType().name(), reward.getType().name());
AdventurePlayer.current().addStatusMessage(reward.getType().name(), message, reward.getCount(), actor.getX(), actor.getY() + player.getHeight());
AdventurePlayer.current().addReward(reward);
break;
default:
showRewardScene(rewards);
break;
}
} else {
showRewardScene(rewards);
}
RS.remove();
actors.removeValue(RS, true);
changes.deleteObject(RS.getId());
break;
}
}
}
}
private void showRewardScene(Array<Reward> rewards) {
startPause(0.1f, () -> {
RewardScene.instance().loadRewards(rewards, RewardScene.Type.Loot, null);
Forge.switchScene(RewardScene.instance());
});
}
boolean started = false;
public void beginDuel(EnemySprite mob) {
if (mob == null) return;

View File

@@ -139,6 +139,30 @@ public class Config {
return configData;
}
public int getBlurDivisor() {
int val = 1;
try {
switch(settingsData.videomode) {
case "720p":
case "768p":
val = 8;
break;
case "900p":
case "1080p":
val = 16;
break;
case "1440p":
case "2160p":
val = 32;
break;
default:
break;
}
} catch (Exception e) {
return val;
}
return val;
}
public String getPrefix() {
return prefix;
}

View File

@@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.Disposable;
import forge.adventure.util.Config;
import forge.util.BlurUtils;
import forge.Forge;
import forge.Graphics;
@@ -63,7 +64,7 @@ public class WorldSaveHeader implements java.io.Serializable, Disposable {
Pixmap pixmap = Pixmap.createFromFrameBuffer(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
if (Forge.lastPreview != null)
Forge.lastPreview.dispose();
Pixmap blurred = BlurUtils.blur(pixmap, 4, 2, false, true);
Pixmap blurred = BlurUtils.blur(pixmap, 4, 2, false, Config.instance().getBlurDivisor());
Forge.lastPreview = new Texture(blurred);
Pixmap scaled = new Pixmap(WorldSaveHeader.previewImageWidth, (int) (WorldSaveHeader.previewImageWidth / (Scene.getIntendedWidth() / (float) Scene.getIntendedHeight())), Pixmap.Format.RGBA8888);
scaled.drawPixmap(pixmap,

View File

@@ -270,12 +270,12 @@ public class BlurUtils {
pixmap.getWidth(), pixmap.getHeight(), radius, iterations,
disposePixmap);
}
public static Pixmap blur(Pixmap pixmap, int radius, int iterations, boolean disposePixmap, boolean crop) {
public static Pixmap blur(Pixmap pixmap, int radius, int iterations, boolean disposePixmap, int div) {
int x = (int)(pixmap.getWidth()*0.35f);
int y = (int)(pixmap.getHeight()*0.35f);
int width = pixmap.getWidth()-x;
int height = pixmap.getHeight()-y;
return blur(pixmap, x/2, y/2, width, height, 0, 0, width, height, radius, iterations, disposePixmap);
return blur(pixmap, x/2, y/2, width, height, 0, 0, width/div, height/div, radius, iterations, disposePixmap);
}
/**

View File

@@ -66,7 +66,14 @@
"UNH",
"PPC1",
"UND",
"PUST"
"PUST",
"UEPHI23",
"UEMIN23",
"UELAS23",
"UEIND23",
"UEBAR23",
"MB2",
"UNF"
],
"difficulties": [
{

View File

@@ -6,7 +6,8 @@ SVar:TrigSeek:DB$ Seek | Num$ 2 | Type$ Card.nonLand
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't lose the game. | CheckSVar$ YourLife | EffectZone$ Command | SVarCompare$ GE40 | Secondary$ True | Description$ You can't lose the game and your opponents can't win the game.
S:Mode$ Continuous | Affected$ Opponent | AddKeyword$ You can't win the game. | Secondary$ True | EffectZone$ Command | CheckSVar$ YourLife | Secondary$ True | SVarCompare$ GE40 | Description$ You can't lose the game and your opponents can't win the game.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLifeCompare | SVarCompare$ EQ2 | Execute$ TrigConjure | TriggerDescription$ As long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend.
SVar:TrigConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,Timothar; Baron of Bats,Olivia Voldaren,Patriarch's Bidding,Licia; Sanguine Tribune,Astarion; the Decadent,Strefan; Maurer Progenitor,Evelyn; the Covetous,Anje; Maid of Dishonor,Edgar Markov | WithCounters$ TIME | WithCountersAmount$ 2 | Zone$ Exile | RememberMade$ True | SubAbility$ GiveSuspend
SVar:TrigConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,Timothar; Baron of Bats,Olivia Voldaren,Patriarch's Bidding,Licia; Sanguine Tribune,Astarion; the Decadent,Strefan; Maurer Progenitor,Evelyn; the Covetous,Anje; Maid of Dishonor,Edgar Markov,Swords to Plowshares,Cruel Celebrant,Olivia's Wrath,Markov Baron,Champion of Dusk,Vein Ripper,Anguished Unmaking,Mortify,Void Rend,Terminate,Despark,Bedevil,Utter End,Ruinous Ultimatum,Sign in Blood,Reanimate,Victimize | Zone$ Exile | RememberMade$ True | SubAbility$ DBPutCounter
SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ 3 | CounterType$ TIME | SubAbility$ GiveSuspend
SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 2 | AddToughness$ 2 | CheckSVar$ YourLife | SVarCompare$ LT20 | AddKeyword$ Lifelink | Description$ As long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink.

View File

@@ -458,6 +458,11 @@
"down": "right_down",
"focused": "right_f"
},
"roundhint": {
"up": "unpressedround",
"down": "pressedround",
"focused": "unpressedround"
},
"item_frame": {
"imageCheckedOver": "item_frame_selected_hover",
"up": "item_frame",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -56,7 +56,7 @@
"y": 10
},
{
"type": "Label",
"type": "TypingLabel",
"name": "lifePoints",
"font": "default",
"width": 64,
@@ -65,7 +65,7 @@
"y": 56
},
{
"type": "Label",
"type": "TypingLabel",
"name": "shards",
"font": "default",
"width": 64,
@@ -74,7 +74,7 @@
"y": 70
},
{
"type": "Label",
"type": "TypingLabel",
"name": "money",
"font": "default",
"width": 64,

View File

@@ -56,7 +56,7 @@
"y": 10
},
{
"type": "Label",
"type": "TypingLabel",
"name": "lifePoints",
"font": "default",
"width": 64,
@@ -65,7 +65,7 @@
"y": 56
},
{
"type": "Label",
"type": "TypingLabel",
"name": "shards",
"font": "default",
"width": 64,
@@ -74,7 +74,7 @@
"y": 70
},
{
"type": "Label",
"type": "TypingLabel",
"name": "money",
"font": "default",
"width": 64,

View File

@@ -56,7 +56,7 @@
"y": 10
},
{
"type": "Label",
"type": "TypingLabel",
"name": "lifePoints",
"width": 48,
"height": 3,
@@ -64,7 +64,7 @@
"y": 62
},
{
"type": "Label",
"type": "TypingLabel",
"name": "shards",
"font": "default",
"width": 48,
@@ -73,7 +73,7 @@
"y": 76
},
{
"type": "Label",
"type": "TypingLabel",
"name": "money",
"font": "default",
"width": 48,

View File

@@ -65,14 +65,14 @@
"yOffset": 8
},
{
"type": "TextButton",
"type": "ImageButton",
"name": "difficultyHelp",
"text": "[GOLD]?",
"style": "roundhint",
"selectable": true,
"width": 16,
"height": 16,
"width": 12,
"height": 15,
"x": 145,
"yOffset": -16
"yOffset": -17
},
{
"type": "Label",
@@ -93,14 +93,14 @@
"yOffset": 8
},
{
"type": "TextButton",
"type": "ImageButton",
"name": "modeHelp",
"text": "[GOLD]?",
"style": "roundhint",
"selectable": true,
"width": 16,
"height": 16,
"width": 12,
"height": 15,
"x": 145,
"yOffset": -16
"yOffset": -17
},
{
"type": "Label",

View File

@@ -35,7 +35,7 @@
"width": 128,
"height": 24,
"x": 16,
"y": 140
"y": 148
},
{
"type": "Label",
@@ -65,14 +65,14 @@
"yOffset": 8
},
{
"type": "TextButton",
"type": "ImageButton",
"name": "difficultyHelp",
"text": "[GOLD]?",
"style": "roundhint",
"selectable": true,
"width": 24,
"height": 24,
"x": 72,
"yOffset": -24
"width": 12,
"height": 15,
"x": 80,
"yOffset": -17
},
{
"type": "Label",
@@ -93,14 +93,14 @@
"yOffset": 8
},
{
"type": "TextButton",
"type": "ImageButton",
"name": "modeHelp",
"text": "[GOLD]?",
"style": "roundhint",
"selectable": true,
"width": 24,
"height": 24,
"x": 72,
"yOffset": -24
"width": 12,
"height": 15,
"x": 80,
"yOffset": -17
},
{
"type": "Label",

View File

@@ -37,7 +37,7 @@
{
"type": "Image",
"name": "avatar",
"x": 321,
"x": 384,
"y": 28,
"width": 64,
"height": 64
@@ -46,7 +46,7 @@
"type": "Image",
"name": "colorFrame",
"image": "ui/colorC.png",
"x": 305,
"x": 368,
"y": 45,
"width": 64,
"height": 64
@@ -137,9 +137,9 @@
"y": 224
},
{
"type": "Label",
"type": "TypingLabel",
"name": "playerName",
"x": 394,
"x": 310,
"y": 20,
"width": 80,
"height": 24,
@@ -150,7 +150,7 @@
"name": "lifePoints",
"width": 64,
"height": 16,
"x": 394,
"x": 310,
"y": 40
},
{
@@ -158,7 +158,7 @@
"name": "money",
"width": 64,
"height": 16,
"x": 394,
"x": 310,
"y": 80
},
{
@@ -166,7 +166,7 @@
"name": "shards",
"width": 64,
"height": 16,
"x": 394,
"x": 310,
"y": 60
},
{

View File

@@ -136,7 +136,7 @@
"y": 440
},
{
"type": "Label",
"type": "TypingLabel",
"name": "playerName",
"x": 98,
"y": 4,

View File

@@ -0,0 +1,12 @@
Name:Anax and Cymede and Kynaios and Tiro
ManaCost:1 W U R G
Types:Legendary Creature Human Soldier
PT:3/8
K:First Strike
K:Vigilance
T:Mode$ SpellCast | ValidActivatingPlayer$ You | TargetsValid$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Heroic — Whenever you cast a spell that targets CARDNAME, draw a card. Each player may put a land card from their hand onto the battlefield, then each opponent who didn't draws a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ EachPlayLand
SVar:EachPlayLand:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land | DefinedPlayer$ Player | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DrawAbstainers
SVar:DrawAbstainers:DB$ Draw | Defined$ OppNonRememberedOwner | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:First strike, vigilance\nHeroic — Whenever you cast a spell that targets Anax and Cymede and Kynaios and Tiro, draw a card. Each player may put a land card from their hand onto the battlefield, then each opponent who didn't draws a card.

View File

@@ -0,0 +1,8 @@
Name:Artifact Unknown Shores
ManaCost:no cost
Types:Artifact Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ Mana | Cost$ 1 T | Produced$ Any | SpellDescription$ Add one mana of any color.
AI:RemoveDeck:Random
DeckHas:Ability$Mana.Colorless
Oracle:{T}: Add {C}.\n{1}, {T}: Add one mana of any color.

View File

@@ -0,0 +1,11 @@
Name:Avacyn and Griselbrand
ManaCost:4 W W B B
Types:Legendary Creature Angel Demon
PT:8/8
K:Flying
K:Vigilance
K:Lifelink
A:AB$ PumpAll | Cost$ PayLife<8> | ValidCards$ Creature.YouCtrl | KW$ Indestructible | SubAbility$ DBDraw | SpellDescription$ Each creature you control gains indestructible until end of turn. Then, draw a card for each creature you control.
SVar:DBDraw:DB$ Draw | NumCards$ X
SVar:X:Count$TypeYouCtrl.Creature
Oracle:Flying, vigilance, lifelink\nPay 8 life: Each creature you control gains indestructible until end of turn. Then, draw a card for each creature you control.

View File

@@ -0,0 +1,7 @@
Name:Believe in the Cleave
ManaCost:3 R
Types:Instant
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ CARDNAME costs {1} less to cast for each attacking creature you control.
SVar:X:Count$Valid Creature.attacking+YouCtrl
A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ 1 | NumDef$ 1 | KW$ Double Strike | SpellDescription$ Target creature gets +1/+1 and gains double strike until end of turn. | StackDescription$ SpellDescription
Oracle:Believe in the Cleave costs {1} less to cast for each attacking creature you control.\nTarget creature gets +1/+1 and gains double strike until end of turn.

View File

@@ -0,0 +1,11 @@
Name:Bram, Baguette Brawler
ManaCost:1 W
Types:Legendary Creature Human Peasant
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.")
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You
S:Mode$ Continuous | Affected$ Food.nonCreature+YouCtrl | AddType$ Equipment | AddStaticAbility$ FoodEquip | AddKeyword$ Equip:1 | Description$ Each noncreature Food you control is an Equipment with equip {1} and "Equipped creature gets +1/+1."
SVar:FoodEquip:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1.
DeckHas:Ability$Token|Sacrifice & Type$Food|Artifact|Equipment
DeckHints:Type$Food
Oracle:When Bram, Baguette Brawler enters the battlefield, create a Food token.\nEach noncreature Food you control is an Equipment with equip {1} and "Equipped creature gets +1/+1."

View File

@@ -0,0 +1,14 @@
Name:Bringer of Green Zenith's Twilight
ManaCost:X G G
Types:Creature Phyrexian Bringer
PT:2/2
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ W U B R G | Description$ You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost. If you do, X is 5.
K:etbCounter:P1P1:Y:no condition:CARDNAME enters the battlefield with X +1/+1 counters on it. If X was 5 or greater, it enters with twice that many counters instead.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME dies, shuffle it into its owner's library.
SVar:TrigChange:DB$ ChangeZone | Destination$ Library | Shuffle$ True | Defined$ TriggeredNewCardLKICopy
SVar:X:Count$xPaid
SVar:AltCostPaid:Count$AltCost.5.X
SVar:Y:Count$Compare AltCostPaid LT5.AltCostPaid.Z
SVar:Z:SVar$AltCostPaid/Twice
DeckHas:Ability$Counters
Oracle:You may pay {W}{U}{B}{R}{G} rather than pay this spell's mana cost. If you do, X is 5.\nBringer of Green Zenith's Twilight enters the battlefield with X +1/+1 counters on it. If X was 5 or greater, it enters with twice that many counters instead.\nWhen Bringer of Green Zenith's Twilight dies, shuffle it into its owner's library.

View File

@@ -0,0 +1,12 @@
Name:Cat Oven
ManaCost:1
Types:Artifact
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When CARDNAME enters the battlefield, create a Food token.
SVar:TrigFood:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ 1
A:AB$ Token | Cost$ T Sac<1/Food> | TokenScript$ b_1_1_cat | TokenAmount$ 1 | SubAbility$ DBDrain | SpellDescription$ Create a 1/1 black Cat creature token. Each opponent loses 1 life and you gain 1 life.
SVar:DBDrain:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1
SVar:AIPreference:SacCost$Food.token
DeckHas:Ability$Token|Sacrifice|LifeGain & Type$Food|Artifact|Cat
DeckHints:Type$Food
Oracle:When Cat Oven enters the battlefield, create a Food token.\n{T}, Sacrifice a Food: Create a 1/1 black Cat creature token. Each opponent loses 1 life and you gain 1 life.

View File

@@ -0,0 +1,7 @@
Name:Chatterstorm and Awaken the Woods
ManaCost:1 G
Types:Sorcery
K:Storm
A:SP$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_forest_dryad_squirrel | TokenOwner$ You | SpellDescription$ Create a 1/1 green Forest Dryad Squirrel land creature token.
DeckHas:Ability$Token
Oracle:Create a 1/1 green Forest Dryad Squirrel land creature token. (It's affected by summoning sickness.)\nStorm (When you cast this spell, copy it for each spell cast before it this turn.)

View File

@@ -0,0 +1,14 @@
Name:Cinnamon, Seasoned Steed
ManaCost:1
Types:Legendary Artifact Creature Food Horse
PT:1/1
S:Mode$ Continuous | Affected$ Creature.Food+Other+YouCtrl | AddKeyword$ Horsemanship | Description$ Other Food creatures you control have horsemanship. (They can't be blocked except by creatures with horsemanship.)
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | OptionalDecider$ You | TriggerDescription$ At the beginning of your combat step, you may put a +1/+1 counter on target non-creature Food you control. If you do, it becomes a 0/0 Knight creature in addition to its other types.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Food.nonCreature+YouCtrl | TgtPrompt$ Select target noncreature Food you control | CounterType$ P1P1 | CounterNum$ 1 | RememberCards$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Power$ 0 | Toughness$ 0 | Types$ Creature,Knight | Duration$ Permanent | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ GainLife | Cost$ 2 T Sac<1/CARDNAME> | LifeAmount$ 3 | SpellDescription$ You gain 3 life.
SVar:PlayMain1:TRUE
DeckHas:Ability$LifeGain|Counters & Type$Knight
DeckHints:Type$Food
Oracle:Other Food creatures you control have horsemanship. (They can't be blocked except by creatures with horsemanship.)\nAt the beginning of your combat step, you may put a +1/+1 counter on target non-creature Food you control. If you do, it becomes a 0/0 Knight creature in addition to its other types.\n{2}, {T}, Sacrifice Cinnamon, Seasoned Steed: You gain 3 life.

View File

@@ -0,0 +1,7 @@
Name:Colossal Dreadmaw and Storm Crow
ManaCost:5 U G
Types:Legendary Creature Dinosaur Bird
PT:7/8
K:Flying
K:Trample
Oracle:Flying, trample

View File

@@ -0,0 +1,9 @@
Name:Delve too Deep
ManaCost:X B
Types:Sorcery
K:Delve
A:SP$ Pump | ValidTgts$ Creature | NumAtt$ -X | NumDef$ -X | IsCurse$ True | SubAbility$ DBToken | SpellDescription$ Target creature gets -X/-X until end of turn. If X was 7 or greater, create a 7/7 black and red Demon creature token.
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ br_7_7_demon | TokenOwner$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ GE7
SVar:X:Count$xPaid
DeckHas:Ability$Token & Type$Demon
Oracle:Delve\nTarget creature gets -X/-X until end of turn. If X was 7 or greater, create a 7/7 black and red Demon creature token.

View File

@@ -5,7 +5,7 @@ PT:5/6
T:Mode$ ChangesZone | ValidCard$ Permanent.Lhurgoyf+YouOwn | Origin$ Library,Hand,Exile,Command,Stack | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigChangeZone | TriggerDescription$ Whenever a Lhurgoyf permanent card is put into your graveyard from anywhere other than the battlefield, put it onto the battlefield.
SVar:TrigChangeZone:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield
T:Mode$ DamageDoneOnce | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigToken | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, create a Tarmogoyf token. (It's a {1}{G} Lhurgoyf creature with "Tarmogoyf's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1.")
SVar:TrigToken:DB$ Token | TokenScript$ tarmogoyf
SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Tarmogoyf
DeckNeeds:Type$Lhurgoyf
DeckHas:Ability$Token & Type$Lhurgoyf
DeckHints:Ability$Mill

View File

@@ -0,0 +1,10 @@
Name:Eldest Dragon Highlander
ManaCost:W W U U B B R R G G
Types:Legendary Creature Elder Dragon
PT:7/7
K:Flying
K:Trample
K:Rampage:2
A:AB$ PumpAll | Cost$ W U B R G | ValidCards$ Dragon.Elder+YouCtrl | NumAtt$ 7 | NumDef$ 7 | SpellDescription$ Elder Dragons you control get +7/+7 until end of turn.
K:UpkeepCost:W U B R G
Oracle:Flying, trample, rampage 2\n{W}{U}{B}{R}{G}: Elder Dragons you control get +7/+7 until end of turn.\nAt the beginning of your upkeep, sacrifice Eldest Dragon Highlander unless you pay {W}{U}{B}{R}{G}.

View File

@@ -0,0 +1,9 @@
Name:Ensoul Ring
ManaCost:1 U
Types:Enchantment Aura
K:Enchant nonland permanent
A:SP$ Attach | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | AILogic$ Curse
S:Mode$ Continuous | Affected$ Permanent.EnchantedBy | SetColor$ Colorless | AddType$ Artifact | RemoveCardTypes$ True | RemoveArtifactTypes$ True | AddAbility$ SolMana | SetName$ Sol Ring | RemoveAllAbilities$ True | Description$ Enchanted permanent is a Sol Ring. (It's no longer anything else.)
SVar:SolMana:AB$ Mana | Cost$ T | Produced$ C | Amount$ 2 | SpellDescription$ Add {C}{C}.
SVar:NonStackingAttachEffect:True
Oracle:Enchant nonland permanent.\nEnchanted permanent is a Sol Ring. (It's no longer anything else.)

View File

@@ -0,0 +1,7 @@
Name:Force of Rowan
ManaCost:3 R R
Types:Instant
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ PayLife<1> ExileFromHand<1/Card.Red+Other> | Description$ You may pay 1 life and exile a red card from your hand rather than pay this spell's mana cost.
A:SP$ CopySpellAbility | ValidTgts$ Instant,Sorcery | TargetType$ Spell | MayChooseTarget$ True | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy.
DeckHints:Type$Instant|Sorcery
Oracle:You may pay 1 life and exile a red card from your hand rather than pay this spell's mana cost.\nCopy target instant or sorcery spell. You may choose new targets for the copy.

View File

@@ -0,0 +1,10 @@
Name:Forestfolk
ManaCost:2 G U
Types:Creature Elf Wizard
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME leaves the battlefield, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ TriggeredCardController | NumCards$ 1
SVar:SacMe:1
Oracle:When Forestfolk enters the battlefield, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nWhen Forestfolk leaves the battlefield, draw a card.

View File

@@ -0,0 +1,19 @@
Name:Garruk's Lost Wolf
ManaCost:3 G
Types:Creature Wolf
PT:2/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a Huntsman Role token attached to another target creature you control. (Enchanted creature gets +1/+1 and has "{T}: Add {G}")
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ role_huntsman | TokenOwner$ You | AttachedTo$ Targeted | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select another target creature you control
DeckHas:Ability$Mill|Graveyard|Token & Type$Aura|Enchantment|Role
AlternateMode:Adventure
Oracle:When Garruk's Lost Wolf enters the battlefield, create a Huntsman Role token attached to another target creature you control. (Enchanted creature gets +1/+1 and has "{T}: Add {G}")
ALTERNATE
Name:Hey, Has Anyone Seen Garruk?
ManaCost:1 G
Types:Sorcery Adventure
A:SP$ Mill | NumCards$ 4 | RememberMilled$ True | SubAbility$ DBChangeZone | SpellDescription$ Mill the top four cards of your library. Return a creature or planeswalker card milled this way to your hand.
SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | Origin$ Graveyard,Exile | Mandatory$ True | Destination$ Hand | ChangeType$ Card.IsRemembered+Creature,Card.IsRemembered+Planeswalker | SelectPrompt$ Return a creature or planeswalker card milled this way to your hand | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:Mill the top four cards of your library. Return a creature or planeswalker card milled this way to your hand.

View File

@@ -0,0 +1,12 @@
Name:Gingerbehemoth
ManaCost:6
Types:Artifact Creature Food Golem
PT:6/6
K:Vigilance
K:Trample
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Secondary$ True | Description$ CARDNAME costs {2} less to cast for each Food you've sacrificed this turn.
SVar:X:PlayerCountPropertyYou$SacrificedThisTurn Food/Times.2
A:AB$ GainLife | Cost$ 4 T Sac<1/CARDNAME> | LifeAmount$ 6 | SpellDescription$ You gain 6 life.
DeckHas:Ability$LifeGain
DeckHints:Type$Food
Oracle:Gingerbehemoth costs {2} less to cast for each Food you've sacrificed this turn.\nVigilance, trample\n{4}, {T}, Sacrifice Gingerbehemoth: You gain 6 life.

View File

@@ -0,0 +1,9 @@
Name:Glorious Dragon-Kin
ManaCost:7
Types:Artifact Creature Dragon
PT:6/6
K:Flying
K:ETBReplacement:Other:ChooseColor
SVar:ChooseColor:DB$ ChooseColor | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, choose a color. | AILogic$ MostProminentInHumanDeck
S:Mode$ Continuous | Affected$ Creature.Artifact+YouCtrl,Dragon.YouCtrl | AddKeyword$ Protection:Card.ChosenColor:chosenColor | Description$ Artifact creatures and Dragons you control have protection from the chosen color.
Oracle:Flying\nAs Glorious Dragon-Kin enters the battlefield, choose a color.\nArtifact creatures and Dragons you control have protection from the chosen color.

View File

@@ -0,0 +1,10 @@
Name:HONK!
ManaCost:G
Types:Instant
A:SP$ Charm | Choices$ TheGooseIsLoose,TheGooseLaidAnEgg
SVar:TheGooseIsLoose:DB$ Destroy | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SubAbility$ DBGiveFood | SpellDescription$ The Goose is Loose — Destroy target artifact or enchantment. Its controller creates a Food token.
SVar:DBGiveFood:DB$ Token | TokenScript$ c_a_food_sac | TokenOwner$ TargetedController | TokenAmount$ 1
SVar:TheGooseLaidAnEgg:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBGetFood | SpellDescription$ The Goose Laid an Egg — Put a +1/+1 counter on target creature. You create a Food token.
SVar:DBGetFood:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ 1
DeckHas:Ability$Token|Counters|LifeGain & Type$Food|Artifact
Oracle:Choose one —\n• The Goose is Loose — Destroy target artifact or enchantment. Its controller creates a Food token.\n• The Goose Laid an Egg — Put a +1/+1 counter on target creature. You create a Food token.

View File

@@ -0,0 +1,11 @@
Name:Hound of Urabrask
ManaCost:3 R R
Types:Creature Phyrexian
PT:3/3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+counters_EQ0_OIL | TriggerZones$ Battlefield | Execute$ DBReturn | TriggerDescription$ Oildying (When this creature dies, if it had no oil counters on it, return it to the battlefield under its owner's control with an oil counter on it.)
SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield | WithCountersType$ OIL
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | Description$ CARDNAME gets +1/+1 for each oil counter on it.
S:Mode$ Continuous | Affected$ Card.Self+counters_GE1_OIL | AddKeyword$ Double Strike | Description$ As long as CARDNAME has an oil counter on it, it has double strike.
SVar:X:Count$CardCounters.OIL
DeckHas:Ability$Counters
Oracle:Oildying (When this creature dies, if it had no oil counters on it, return it to the battlefield under its owner's control with an oil counter on it.)\nHound of Urabrask gets +1/+1 for each oil counter on it.\nAs long as Hound of Urabrask has an oil counter on it, it has double strike.

View File

@@ -2,7 +2,7 @@ Name:Ignite the Future
ManaCost:3 R
Types:Sorcery
A:SP$ Dig | Defined$ You | DigNum$ 3 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top three cards of your library. Until the end of your next turn, you may play those cards. If this spell was cast from a graveyard, you may play cards this way without paying their mana costs.
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | ForgetOnMoved$ Exile | Duration$ UntilTheEndOfYourNextTurn | ConditionDefined$ Self | ConditionPresent$ Card.wasCastFromGraveyard | ConditionCompare$ EQ0 | SubAbility$ DBEffect2
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | ForgetOnMoved$ Exile | Duration$ UntilTheEndOfYourNextTurn | SubAbility$ DBEffect2
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play remembered card.
SVar:DBEffect2:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play2 | ForgetOnMoved$ Exile | Duration$ UntilTheEndOfYourNextTurn | ConditionDefined$ Self | ConditionPresent$ Card.wasCastFromGraveyard | ConditionCompare$ EQ1 | SubAbility$ DBCleanup
SVar:Play2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play remembered card without paying their mana costs.

View File

@@ -0,0 +1,10 @@
Name:Incisor Steed
ManaCost:1 W
Types:Artifact Creature Phyrexian Horse
PT:1/4
K:Vigilance
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | CheckSVar$ X | SVarCompare$ GE3 | Condition$ Metalcraft | Description$ Corrupted Metalcraft — As long as you control three or more artifacts and an opponent has three or more poison counters, CARDNAME gets +3/+0.
SVar:X:PlayerCountOpponents$HighestCounters.Poison
SVar:BuffedBy:Artifact
DeckHints:Keyword$Toxic|Infect & Type$Artifact
Oracle:Vigilance\nCorrupted Metalcraft — As long as you control three or more artifacts and an opponent has three or more poison counters, Incisor Steed gets +3/+0.

View File

@@ -0,0 +1,7 @@
Name:Incubob
ManaCost:1 B
Types:Sorcery
K:Flashback:3 B PayLife<3>
A:SP$ Token | TokenScript$ incubator_dark_confidant | WithCountersType$ P1P1 | WithCountersAmount$ 1 | SpellDescription$ Incubate Dark Confidant 1. (Create an Incubator Dark Confidant token with a +1/+1 counter on it and "{2}: Transform this artifact." It transforms into Dark Confidant, except it's also a Phyrexian.)
DeckHas:Ability$Token|Counters & Type$Incubator|Artifact|Phyrexian|Human|Wizard
Oracle:Incubate Dark Confidant 1. (Create an Incubator Dark Confidant token with a +1/+1 counter on it and "{2}: Transform this artifact." It transforms into Dark Confidant, except it's also a Phyrexian.)\nFlashback—{3}{B}, Pay 3 life.

View File

@@ -0,0 +1,11 @@
Name:Innistrad Charm
ManaCost:1 B
Types:Sorcery
A:SP$ Charm | Choices$ CemeteryRecruitment,Duress,HumanFrailty | Defined$ You
SVar:CemeteryRecruitment:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | SubAbility$ DBDraw | RememberChanged$ True | SpellDescription$ Return target creature card from your graveyard to your hand. If it's a Zombie card, draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card.Zombie | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Duress:DB$ Discard | ValidTgts$ Opponent | Mode$ RevealYouChoose | DiscardValid$ Card.nonCreature+nonLand | NumCards$ 1 | SpellDescription$ Target opponent reveals their hand. You choose a noncreature, nonland card from it. That player discards that card.
SVar:HumanFrailty:DB$ Destroy | ValidTgts$ Creature.Human | TgtPrompt$ Choose target Human creature. | SpellDescription$ Destroy target Human creature.
DeckHints:Type$Zombie
Oracle:Choose one —\n• Cemetery Recruitment (Return a creature card from graveyard to hand, draw a card if it's a Zombie.)\n• Duress (Look at their hand, make them discard a noncreature, nonland.)\n• Human Frailty (Destroy target Human.)

View File

@@ -0,0 +1,9 @@
Name:Isamaru and Yoshimaru
ManaCost:W
Types:Legendary Creature Dog
PT:2/2
T:Mode$ ChangesZone | ValidCard$ Creature.Legendary+Other+YouCtrl,Creature.cmcEQ1+Other+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever another legendary creature or creature with mana value one enters the battlefield under your control, put a +1/+1 counter on CARDNAME.
SVar:TrigCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1
SVar:BuffedBy:Creature.Legendary,Creature.cmcEQ1
DeckHas:Ability$Counters
Oracle:Whenever another legendary creature or creature with mana value one enters the battlefield under your control, put a +1/+1 counter on Isamaru and Yoshimaru.

View File

@@ -0,0 +1,8 @@
Name:Jeska and Kamahl
ManaCost:3 R
Types:Legendary Creature Human Barbarian Warrior
PT:4/1
K:First Strike
K:Haste
A:AB$ DealDamage | Cost$ T | ValidTgts$ Opponent,Planeswalker,Battle | TgtPrompt$ Select target opponent, battle, or planeswalker | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to target opponent, battle, or planeswalker.
Oracle:Haste, first strike\n{T}: Jeska and Kamahl deals 2 damage to target opponent, battle, or planeswalker.

View File

@@ -0,0 +1,12 @@
Name:Joven and Chandler
ManaCost:3 R R
Types:Legendary Creature Human Rogue
PT:3/3
K:Backup:2:BackupAbilities
SVar:BackupAbilities:DB$ Animate | Keywords$ Haste | Triggers$ DamageTrig
SVar:DamageTrig:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | Execute$ TrigDestroy | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to an opponent, destroy target artifact that player controls.
K:Haste
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | Execute$ TrigDestroy | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to an opponent, destroy target artifact that player controls.
SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact.ControlledBy TriggeredTarget | TgtPrompt$ Select target artifact damaged player controls
DeckHas:Ability$Counters
Oracle:Backup 2 (When this creature enters the battlefield, put two +1/+1 counters on target creature. If that's another creature, it gains the following abilities until end of turn.)\nHaste\nWhenever this creature deals combat damage to an opponent, destroy target artifact that player controls.

View File

@@ -0,0 +1,14 @@
Name:Kamigawa Charm
ManaCost:1 G G
Types:Sorcery
A:SP$ Charm | Choices$ DosansOldestChant,KodamasReach,TimeOfNeed | Defined$ You
SVar:DosansOldestChant:DB$ GainLife | LifeAmount$ 6 | SubAbility$ DBDraw | SpellDescription$ You gain 6 life. Draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1
SVar:KodamasReach:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.
SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card for the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None
SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card for your hand | StackDescription$ None | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:TimeOfNeed:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Legendary | ChangeNum$ 1 | SpellDescription$ Search your library for a legendary creature card, reveal it, put it into your hand, then shuffle.
DeckHas:Ability$LifeGain
DeckHints:Type$Legendary
Oracle:Choose one —\n• Dosan's Oldest Chant (Gain 6 life, draw a card.)\n• Kodama's Reach (Search for two basic lands, put one onto the battlefield tapped and one to hand.)\n• Time of Need (Search for a legend.)

View File

@@ -0,0 +1,17 @@
Name:Kevin, Questing Dragon
ManaCost:4 R R R R
Types:Legendary Creature Dragon
PT:8/8
K:Devour:2
K:Flying
K:Landwalk:Mountain
K:Rampage:2
K:Bushido:2
K:Trample:Planeswalker
R:Event$ Counter | ValidCard$ Card.Self | ValidSA$ Spell | Layer$ CantHappen | Description$ This spell can't be countered.
S:Mode$ CantPreventDamage | Description$ Damage can't be prevented.
S:Mode$ CantGainLife | ValidPlayer$ Player | Description$ Players can't gain life.
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigControl | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, gain control of target land that player controls. Untap it.
SVar:TrigControl:DB$ GainControl | ValidTgts$ Land.ControlledBy TriggeredTarget | TgtPrompt$ Select target land damaged player controls | SubAbility$ DBUntap
SVar:DBUntap:DB$ Untap | Defined$ Targeted | SpellDescription$ Untap it.
Oracle:Kevin can't be countered, devour 2\nFlying, mountainwalk, rampage 2, bushido 2, trample over planeswalkers\nDamage can't be prevented.\nPlayers can't gain life.\nWhenever Kevin deals combat damage to a player, gain control of target land that player controls. Untap it.

View File

@@ -0,0 +1,15 @@
Name:Knowing
ManaCost:3 U U
Types:Sorcery
A:SP$ Draw | NumCards$ 3 | SpellDescription$ Draw three cards.
AlternateMode:Split
Oracle:Draw three cards.
ALTERNATE
Name:Half the Battle
ManaCost:2 R
Types:Sorcery
DeckHints:Type$Battle
A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Battle | ChangeNum$ 1 | SpellDescription$ Search your library for a Battle card, reveal it, put it into your hand, then shuffle.
Oracle:Search your library for a Battle card, reveal it, put it into your hand, then shuffle.

View File

@@ -0,0 +1,14 @@
Name:Koma and Toski, Compleated
ManaCost:6 U G
Types:Legendary Creature Phyrexian Serpent Squirrel
PT:7/7
R:Event$ Counter | ValidCard$ Card.Self | ValidSA$ Spell | Layer$ CantHappen | Description$ This spell can't be countered.
T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of each upkeep, create a 1/1 Phyrexian Serpent Squirrel artifact creature token named Toski's Coil with "Whenever this creature deals combat damage to a player, draw a card."
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ toskis_coil | TokenOwner$ You
A:AB$ Charm | Cost$ Sac<1/Serpent.Other;Squirrel.Other/another Serpent or Squirrel> | Choices$ DBEffect,DBPump
SVar:DBEffect:DB$ Effect | ValidTgts$ Creature | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ MustAttack | SubAbility$ DBConstrict | SpellDescription$ Target creature attacks this turn if able. Its activated abilities can't be activated this turn.
SVar:MustAttack:Mode$ MustAttack | ValidCreature$ Card.IsRemembered | Description$ This creature attacks this turn if able.
SVar:DBConstrict:DB$ Pump | Defined$ ParentTarget | KW$ HIDDEN CARDNAME's activated abilities can't be activated. | StackDescription$ None
SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Indestructible | SpellDescription$ CARDNAME gains indestructible until end of turn.
DeckHas:Ability$Token|Sacrifice & Type$Artifact
Oracle:This spell can't be countered.\nAt the beginning of each upkeep, create a 1/1 Phyrexian Serpent Squirrel artifact creature token named Toski's Coil with "Whenever this creature deals combat damage to a player, draw a card."\nSacrifice another Serpent or Squirrel: Choose one —\n• Target creature attacks this turn if able. Its activated abilities can't be activated this turn.\n• Koma and Toski, Compleated gains indestructible until end of turn.

View File

@@ -0,0 +1,7 @@
Name:Leech Medic
ManaCost:3 W
Types:Creature Leech Cleric
PT:3/4
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigConjure | TriggerDescription$ When CARDNAME enters the battlefield, create a Leeches token card and put it into your hand.
SVar:TrigConjure:DB$ MakeCard | Name$ Leeches | TokenCard$ True | Zone$ Hand
Oracle:When Leech Medic enters the battlefield, create a Leeches token card and put it into your hand.

View File

@@ -0,0 +1,11 @@
Name:Life Cloud
ManaCost:X W W W
Types:Sorcery
A:SP$ GainLife | Defined$ Player | LifeAmount$ X | SubAbility$ DBDraw | SpellDescription$ Each player gains X life, draws X cards, returns X creatures from their graveyard to the battlefield, then returns X lands from their graveyard to the battlefield.
SVar:DBDraw:DB$ Draw | NumCards$ X | Defined$ Player | SubAbility$ DBRepeatCreature
SVar:DBRepeatCreature:DB$ RepeatEach | RepeatSubAbility$ DBReturnCreature | RepeatPlayers$ Player | SubAbility$ DBRepeatLand
SVar:DBReturnCreature:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature.RememberedPlayerCtrl | DefinedPlayer$ Player.IsRemembered | Chooser$ Player.IsRemembered | ChangeNum$ X | Hidden$ True | Mandatory$ True
SVar:DBRepeatLand:DB$ RepeatEach | RepeatSubAbility$ DBReturnLand | RepeatPlayers$ Player
SVar:DBReturnLand:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Land.RememberedPlayerCtrl | DefinedPlayer$ Player.IsRemembered | Chooser$ Player.IsRemembered | ChangeNum$ X | Hidden$ True | Mandatory$ True
SVar:X:Count$xPaid
Oracle:Each player gains X life, draws X cards, returns X creatures from their graveyard to the battlefield, then returns X lands from their graveyard to the battlefield.

View File

@@ -0,0 +1,11 @@
Name:Locus Cobra
ManaCost:1 G
Types:Creature Phyrexian Snake
PT:1/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ Landfall — Whenever a land enters the battlefield under your control, add one mana of any color. If it was a Locus or Sphere land, put a +1/+1 counter on CARDNAME.
SVar:TrigMana:DB$ Mana | Produced$ Any | SubAbility$ DBPutCounter
SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | ConditionDefined$ TriggeredCard | ConditionPresent$ Locus,Sphere
SVar:BuffedBy:Locus,Sphere
DeckHas:Ability$Counters
DeckHints:Type$Locus|Sphere
Oracle:Landfall — Whenever a land enters the battlefield under your control, add one mana of any color. If it was a Locus or Sphere land, put a +1/+1 counter on Locus Cobra.

View File

@@ -0,0 +1,10 @@
Name:Luxior and Shadowspear
ManaCost:2
Types:Legendary Artifact Equipment
K:Equip:3:Planeswalker.YouCtrl:planeswalker
K:Equip:3
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ X | AddToughness$ X | AddKeyword$ Trample & Lifelink | Description$ Equipped creature gets +1/+1 for each counter on it, and has trample and lifelink.
SVar:X:Equipped$CardCounters.ALL
S:Mode$ Continuous | Affected$ Permanent.EquippedBy | RemoveType$ Planeswalker | AddType$ Creature | Description$ Equipped permanent isn't a planeswalker and is a creature in addition it its other types. (Loyalty abilities can still be activated.)
A:AB$ AnimateAll | Cost$ 1 | ValidCards$ Permanent.OppCtrl | RemoveKeywords$ Hexproof & Indestructible | SpellDescription$ Permanents your opponents control lose hexproof and indestructible until end of turn. | StackDescription$ SpellDescription
Oracle:Equipped creature gets +1/+1 for each counter on it, and has trample and lifelink.\n{1}: Permanents your opponents control lose hexproof and indestructible until end of turn.\nEquipped permanent isn't a planeswalker and is a creature in addition it its other types. (Loyalty abilities can still be activated.)\Equip creature or planeswalker {3}

View File

@@ -0,0 +1,8 @@
Name:Manakin and Millikin
ManaCost:3
Types:Artifact Creature Construct
PT:1/2
A:AB$ Mana | Cost$ T Mill<1> | Produced$ C | Amount$ 2 | SpellDescription$ Add {C}{C}.
DeckHas:Ability$Mill
DeckHints:Ability$Graveyard
Oracle:{T}, Mill a card: Add {C}{C}.

View File

@@ -0,0 +1,10 @@
Name:More of That Strange Oil...
ManaCost:2 U
Types:Instant
A:SP$ Charm | Choices$ DBProliferate,DBCounter
SVar:DBProliferate:DB$ Proliferate | SubAbility$ DBDraw | SpellDescription$ It's Probably Nothing — Proliferate. Draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1
SVar:DBCounter:DB$ Counter | TargetType$ Spell | TgtPrompt$ Select target creature, artifact, or planeswalker spell | ValidTgts$ Creature,Artifact,Planeswalker | SubAbility$ DBScry | SpellDescription$ That Could Actually Be Dangerous — Counter target creature, artifact, or planeswalker spell. Scry 1.
SVar:DBScry:DB$ Scry | ScryNum$ 1
DeckHas:Ability$Proliferate
Oracle:Choose one —\n• It's Probably Nothing — Proliferate. Draw a card.\n• That Could Actually Be Dangerous — Counter target creature, artifact, or planeswalker spell. Scry 1.

View File

@@ -0,0 +1,6 @@
Name:Mox Poison
ManaCost:0
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ Any | SubAbility$ DBPain | SpellDescription$ Add one mana of any color. You get two poison counters
SVar:DBPain:DB$ Poison | Defined$ You | Num$ 2
Oracle:{T}: Add one mana of any color. You get two poison counters

View File

@@ -0,0 +1,10 @@
Name:Myojin of Night's Reach Grim Betrayal
ManaCost:5 B B B
Types:Legendary Creature Spirit
PT:10/4
K:etbCounter:Indestructible:1:CheckSVar$ FromHand:CARDNAME enters the battlefield with an indestructible counter on it if you cast it from your hand.
SVar:FromHand:Count$wasCastFromYourHandByYou.1.0
A:AB$ Discard | Cost$ SubCounter<1/Indestructible> | Defined$ Player.Opponent | Mode$ Hand | SubAbility$ DBReturn | SpellDescription$ Each opponent discards their hand. Put onto the battlefield under your control all creature cards in all graveyards that were put there from anywhere this turn.
SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ ValidGraveyard Creature.ThisTurnEntered | GainControl$ True
DeckHas:Ability$Graveyard
Oracle:Myojin of Night's Reach Grim Betrayal enters the battlefield with an indestructible counter on it if you cast it from your hand.\nRemove an indestructible counter from Myojin of Night's Reach Grim Betrayal: Each opponent discards their hand. Put onto the battlefield under your control all creature cards in all graveyards that were put there from anywhere this turn.

View File

@@ -0,0 +1,7 @@
Name:Mysterious Confluence
ManaCost:5
Types:Instant
A:SP$ NameCard | AtRandom$ True | ChooseFromList$ Righteous Confluence,Mystic Confluence,Wretched Confluence,Fiery Confluence,Verdant Confluence,Brokers Confluence,Cabaretti Confluence,Maestros Confluence,Obscura Confluence,Riveteers Confluence | SubAbility$ DBCast | StackDescription$ SpellDescription | SpellDescription$ Choose a card at random from among Righteous Confluence, Mystic Confluence, Wretched Confluence, Fiery Confluence, Verdant Confluence, Brokers Confluence, Cabaretti Confluence, Maestros Confluence, Obscura Confluence, and Riveteers Confluence. Create a copy of that card. You may cast the copy without paying its mana cost.
SVar:DBCast:DB$ Play | WithoutManaCost$ True | CopyFromChosenName$ True | Optional$ True | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBCleanup:DB$ Cleanup | ClearNamedCard$ True
Oracle:Choose a card at random from among Righteous Confluence, Mystic Confluence, Wretched Confluence, Fiery Confluence, Verdant Confluence, Brokers Confluence, Cabaretti Confluence, Maestros Confluence, Obscura Confluence, and Riveteers Confluence. Create a copy of that card. You may cast the copy without paying its mana cost.

View File

@@ -2,16 +2,15 @@ Name:Nahiri's Lithoforming
ManaCost:X R R
Types:Sorcery
A:SP$ Sacrifice | SacValid$ Land | Amount$ X | RememberSacrificed$ True | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Sacrifice X lands. For each land sacrificed this way, draw a card. You may play X additional lands this turn. Lands you control enter tapped this turn.
SVar:DBDraw:DB$ Draw | NumCards$ Y | SubAbility$ DBStoreSVar | StackDescription$ None
SVar:DBStoreSVar:DB$ StoreSVar | SVar$ XLands | Type$ CountSVar | Expression$ X | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ PlayMoreLand | ReplacementEffects$ LandETB | SubAbility$ DBCleanup
SVar:PlayMoreLand:Mode$ Continuous | Affected$ You | AdjustLandPlays$ XLands | EffectZone$ Command | Description$ You may play X additional lands this turn.
SVar:DBDraw:DB$ Draw | NumCards$ Y | SubAbility$ DBEffect | StackDescription$ None
SVar:DBEffect:DB$ Effect | SetChosenNumber$ X | StaticAbilities$ PlayMoreLand | ReplacementEffects$ LandETB | SubAbility$ DBCleanup
SVar:PlayMoreLand:Mode$ Continuous | Affected$ You | AdjustLandPlays$ Z | EffectZone$ Command | Description$ You may play X additional lands this turn.
SVar:LandETB:Event$ Moved | ValidCard$ Land.YouCtrl | Destination$ Battlefield | ReplaceWith$ ETBTapped | ReplacementResult$ Updated | Description$ Lands you control enter tapped this turn.
SVar:ETBTapped:DB$ Tap | ETB$ True | Defined$ ReplacedCard
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$xPaid
SVar:Y:Count$RememberedSize
SVar:XLands:Number$0
SVar:Z:Count$ChosenNumber
DeckHas:Ability$Sacrifice
AI:RemoveDeck:All
Oracle:Sacrifice X lands. For each land sacrificed this way, draw a card. You may play X additional lands this turn. Lands you control enter tapped this turn.

View File

@@ -0,0 +1,15 @@
Name:Night Out in Vegas
ManaCost:2 B B
Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, ABILITY
SVar:TrigCharm:DB$ Charm | Choices$ Buffet,SeeAShow,PlayGames,GoToSleep | ChoiceRestriction$ ThisGame | CharmNum$ 1
SVar:Buffet:DB$ Token | TokenScript$ c_a_food_sac | TokenAmount$ 3 | SpellDescription$ Buffet — Create three Food tokens.
SVar:SeeAShow:DB$ Token | TokenScript$ w_2_2_performer | TokenAmount$ 2 | SpellDescription$ See a Show — Create two 2/2 white Performer creature tokens.
SVar:PlayGames:DB$ ChangeZone | Origin$ Library | NoShuffle$ True | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | SubAbility$ DBDiscard | Mandatory$ True | SpellDescription$ Play Games — Search your library for a card, put that card into your hand, discard a card at random, then shuffle.
SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ Random | SubAbility$ DBShuffle
SVar:DBShuffle:DB$ Shuffle | Defined$ You
SVar:GoToSleep:DB$ LoseLife | LifeAmount$ 15 | SubAbility$ DBSacSelf | SpellDescription$ Go to Sleep — You lose 15 life. Sacrifice CARDNAME.
SVar:DBSacSelf:DB$ Sacrifice
DeckHas:Ability$Token|Sacrifice|LifeGain & Type$Food|Artifact|Performer
AI:RemoveDeck:All
Oracle:At the beginning of your upkeep, choose one that hasn't been chosen —\n• Buffet — Create three Food tokens.\n• See a Show — Create two 2/2 white Performer creature tokens.\n• Play Games — Search your library for a card, put that card into your hand, discard a card at random, then shuffle.\n• Go to Sleep — You lose 15 life. Sacrifice Night Out in Vegas.

View File

@@ -0,0 +1,11 @@
Name:Nim Mongoose
ManaCost:B
Types:Creature Zombie Mongoose
PT:2/1
K:Shroud
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters the battlefield tapped.
SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | Condition$ Threshold | Description$ Threshold — CARDNAME gets +1/+0 for each artifact you control as long as seven or more cards are in your graveyard.
SVar:X:Count$Valid Artifact.YouCtrl
SVar:BuffedBy:Artifact
Oracle:Shroud\nNim Mongoose enters the battlefield tapped.\nThreshold — Nim Mongoose gets +1/+0 for each artifact you control as long as seven or more cards are in your graveyard.

View File

@@ -0,0 +1,9 @@
Name:Norin and Feldon
ManaCost:1 R
Types:Legendary Creature Human Warrior Artificer
PT:2/2
A:AB$ CopyPermanent | Cost$ 2 R T | SorcerySpeed$ True | TgtZone$ Graveyard | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature card in your graveyard | NumCopies$ 1 | AddTypes$ Artifact | PumpKeywords$ Haste | AddSVars$ NorinExile | AddTriggers$ Norin1,Norin2 | SpellDescription$ Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types, gains haste, and has "Whenever a player casts a spell or a creature attacks, exile this token." Activate only as a sorcery.
SVar:Norin1:Mode$ SpellCast | ValidCard$ Card | Execute$ NorinExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever a player casts a spell or a creature attacks, exile this token.
SVar:Norin2:Mode$ Attacks | ValidCard$ Creature | Execute$ NorinExile | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever a player casts a spell or a creature attacks, exile this token.
SVar:NorinExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
Oracle:{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types, gains haste, and has "Whenever a player casts a spell or a creature attacks, exile this token." Activate only as a sorcery.

View File

@@ -3,8 +3,8 @@ ManaCost:3 U
Types:Legendary Creature Human Scientist
PT:3/4
S:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigImmediateTrig | OptionalDecider$ You | TriggerDescription$ Sonic Booster — Whenever CARDNAME attacks, sacrifice X artifacts. When you sacrifice one or more artifacts this way, tap up to X target creatures and you draw X cards.
SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ Sac<X/Artifact> | Execute$ TrigTap | TriggerDescription$ When you sacrifice one or more artifacts this way, tap up to X target creatures and you draw X cards.
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigImmediateTrig | OptionalDecider$ You | TriggerDescription$ Sonic Booster — Whenever CARDNAME attacks, sacrifice any number artifacts. When you sacrifice one or more artifacts this way, tap up to that many target creatures and draw that many cards.
SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ Sac<X/Artifact> | Execute$ TrigTap | TriggerDescription$ When you sacrifice one or more artifacts this way, tap up to that many target creatures and draw that many cards.
SVar:TrigTap:DB$ Tap | TargetMin$ 0 | TargetMax$ X | TgtPrompt$ Select up to X target creatures to tap | ValidTgts$ Creature | SubAbility$ TrigDraw
SVar:TrigDraw:DB$ Draw | NumCards$ X
SVar:X:Count$xPaid
@@ -12,4 +12,4 @@ K:Doctor's companion
SVar:HasAttackEffect:TRUE
DeckHas:Ability$Sacrifice
DeckNeeds:Type$Artifact
Oracle:You have no maximum hand size.\nSonic Booster — Whenever Nyssa of Traken attacks, sacrifice X artifacts. When you sacrifice one or more artifacts this way, tap up to X target creatures and you draw X cards.\nDoctor's companion (You can have two commanders if the other is the Doctor.)
Oracle:You have no maximum hand size.\nSonic Booster — Whenever Nyssa of Traken attacks, sacrifice any number of artifacts. When you sacrifice one or more artifacts this way, tap up to that many target creatures and draw that many cards.\nDoctor's companion (You can have two commanders if the other is the Doctor.)

View File

@@ -0,0 +1,8 @@
Name:Original Skullclamp
ManaCost:1
Types:Artifact Equipment
K:Equip:1
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.EquippedBy | Execute$ TrigDraw | TriggerDescription$ Whenever equipped creature dies, draw two cards.
SVar:TrigDraw:DB$ Draw | NumCards$ 2
Oracle:Equipped creature gets +1/+1.\nWhenever equipped creature dies, draw two cards.\nEquip {1}

View File

@@ -4,5 +4,5 @@ Types:Vanguard
HandLifeModifier:+0/+9
A:AB$ RepeatEach | Cost$ 3 | ActivationZone$ Command | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ ArrestEach | StackDescription$ SpellDescription | SpellDescription$ For each opponent who controls a creature, create a token that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random.
SVar:ArrestEach:DB$ ChooseCard | Amount$ 1 | Choices$ Creature.RememberedPlayerCtrl | AtRandom$ True | SubAbility$ DBAttach
SVar:DBAttach:DB$ CopyPermanent | NumCopies$ 1 | ValidSupportedCopy$ Card.namedArrest | DefinedName$ Arrest | AttachAfter$ True | AttachedTo$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | ConditionCompare$ GE1
SVar:DBAttach:DB$ CopyPermanent | NumCopies$ 1 | DefinedName$ Arrest | AttachAfter$ True | AttachedTo$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | ConditionCompare$ GE1
Oracle:Hand +0, life +9\n{3}: For each opponent who controls a creature, create a token that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random.

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