diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 06608e6a93b..68885f748fc 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -306,7 +306,7 @@ public class CountersPutAi extends CountersAi { } } else if (logic.equals("CheckDFC")) { // for cards like Ludevic's Test Subject - if (!source.canTransform()) { + if (!source.canTransform(null)) { return false; } } else if (logic.startsWith("MoveCounter")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java index 1dd0f31f305..91b74d2566f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java @@ -1,18 +1,20 @@ package forge.ai.ability; +import java.util.List; + import com.google.common.base.Predicate; import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCost; import forge.ai.SpellAbilityAi; import forge.card.CardStateName; -import forge.game.Game; + import forge.game.GlobalRuleChange; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; -import forge.game.card.CardPredicates.Presets; +import forge.game.card.CardPredicates; import forge.game.card.CardState; import forge.game.card.CardUtil; import forge.game.card.CounterEnumType; @@ -67,12 +69,11 @@ public class SetStateAi extends SpellAbilityAi { final String mode = sa.getParam("Mode"); final Card source = sa.getHostCard(); final String logic = sa.getParamOrDefault("AILogic", ""); - final Game game = source.getGame(); if ("Transform".equals(mode)) { if (!sa.usesTargeting()) { // no Transform with Defined which is not Self - if (!source.canTransform()) { + if (!source.canTransform(sa)) { return false; } return shouldTransformCard(source, ai, ph) || "Always".equals(logic); @@ -80,15 +81,13 @@ public class SetStateAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); - CardCollection list = CardLists.getValidCards(CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source, sa); // select only the ones that can transform - list = CardLists.filter(list, new Predicate() { + CardCollection list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.Presets.CREATURES, new Predicate() { @Override public boolean apply(Card c) { - return c.canTransform(); + return c.canTransform(sa); } }); - list = CardLists.getTargetableCards(list, sa); if (list.isEmpty()) { return false; @@ -116,8 +115,7 @@ public class SetStateAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa); - list = CardLists.getTargetableCards(list, sa); + List list = CardUtil.getValidCardsToTarget(tgt, sa); if (list.isEmpty()) { return false; @@ -126,13 +124,13 @@ public class SetStateAi extends SpellAbilityAi { for (final Card c : list) { if (shouldTurnFace(c, ai, ph) || "Always".equals(logic)) { sa.getTargets().add(c); - if (sa.getTargets().size() == tgt.getMaxTargets(source, sa)) { + if (!sa.canAddMoreTarget()) { break; } } } - return sa.getTargets().size() >= tgt.getMinTargets(source, sa); + return sa.isTargetNumberValid(); } } return true; diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 6116792c3d1..f751b4b5620 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import forge.card.CardStateName; @@ -16,8 +17,8 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.card.CardPredicates; import forge.game.card.CardState; -import forge.game.card.CardUtil; import forge.game.card.CardView; import forge.game.card.IHasCardView; import forge.game.player.Player; @@ -264,6 +265,22 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, if ("True".equalsIgnoreCase(params.get("Blessing")) != hostController.hasBlessing()) return false; } + if (params.containsKey("DayTime")) { + if ("Day".equalsIgnoreCase(params.get("DayTime"))) { + if (!game.isDay()) { + return false; + } + } else if ("Night".equalsIgnoreCase(params.get("DayTime"))) { + if (!game.isNight()) { + return false; + } + } else if ("Neither".equalsIgnoreCase(params.get("DayTime"))) { + if (!game.isNeitherDayNorNight()) { + return false; + } + } + } + if (params.containsKey("Adamant")) { if (hostCard.getCastSA() == null) { return false; @@ -450,7 +467,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, } if (params.containsKey("WerewolfTransformCondition")) { - if (!CardUtil.getLastTurnCast("Card", this.getHostCard(), this).isEmpty()) { + if (!game.getStack().getSpellsCastLastTurn().isEmpty()) { return false; } } @@ -459,7 +476,10 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, List casted = game.getStack().getSpellsCastLastTurn(); boolean conditionMet = false; for (Player p : game.getPlayers()) { - conditionMet |= CardLists.filterControlledBy(casted, p).size() > 1; + if (Iterables.size(Iterables.filter(casted, CardPredicates.isController(p))) > 1) { + conditionMet = true; + break; + } } if (!conditionMet) { return false; diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index f3520952c2c..b13c469b83b 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -167,6 +167,10 @@ public class ForgeScript { return sa.isForetold(); } else if (property.equals("ClassLevelUp")) { return sa.getApi() == ApiType.ClassLevelUp; + } else if (property.equals("Daybound")) { + return sa.hasParam("Daybound"); + } else if (property.equals("Nightbound")) { + return sa.hasParam("Nightbound"); } else if (property.equals("MayPlaySource")) { StaticAbility m = sa.getMayPlay(); if (m == null) { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 3e497ce9e6b..15024df82b7 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import forge.game.event.GameEventDayTimeChanged; import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Predicate; @@ -129,6 +130,8 @@ public class Game { private Direction turnOrder = Direction.getDefaultDirection(); + private Boolean daytime = null; + private long timestamp = 0; public final GameAction action; private final Match match; @@ -1164,4 +1167,29 @@ public class Game { public void addFacedownWhileCasting(Card c, int numDrawn) { facedownWhileCasting.put(c, Integer.valueOf(numDrawn)); } + + public boolean isDay() { + return this.daytime != null && this.daytime == false; + } + public boolean isNight() { + return this.daytime != null && this.daytime == true; + } + public boolean isNeitherDayNorNight() { + return this.daytime == null; + } + + public Boolean getDayTime() { + return this.daytime; + } + public void setDayTime(Boolean value) { + Boolean previous = this.daytime; + this.daytime = value; + + if (previous != null && value != null && previous != value) { + Map params = AbilityKey.newMap(); + this.getTriggerHandler().runTrigger(TriggerType.DayTimeChanges, params, false); + } + if (!isNeitherDayNorNight()) + fireEvent(new GameEventDayTimeChanged(isDay())); + } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index 1a36cbe44b6..ac652ad32ed 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -126,7 +126,6 @@ public enum AbilityKey { TgtSA("TgtSA"), Token("Token"), TokenNum("TokenNum"), - Transformer("Transformer"), TriggeredParams("TriggeredParams"), Vehicle("Vehicle"), Won("Won"); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index ef26747b4a5..b5525274857 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2275,6 +2275,10 @@ public class AbilityUtils { return doXMath(player.getSpellsCastThisGame(), expr, c, ctb); } + if (sq[0].equals("Night")) { + return doXMath(calculateAmount(c, sq[game.isNight() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].contains("CardControllerTypes")) { return doXMath(getCardTypesFromList(player.getCardsIn(ZoneType.listValueOf(sq[1]))), expr, c, ctb); } diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index f7a687e37d1..1d8b282189c 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -58,6 +58,7 @@ public enum ApiType { Counter (CounterEffect.class), DamageAll (DamageAllEffect.class), DealDamage (DamageDealEffect.class), + DayTime (DayTimeEffect.class), Debuff (DebuffEffect.class), DeclareCombatants (DeclareCombatantsEffect.class), DelayedTrigger (DelayedTriggerEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java index d5302886f02..14bd44d2fb3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java @@ -71,23 +71,38 @@ public class AttachEffect extends SpellAbilityEffect { GameEntity attachTo; - if (sa.hasParam("Object") && sa.hasParam("Choices")) { + if (sa.hasParam("Object") && (sa.hasParam("Choices") || sa.hasParam("PlayerChoices"))) { ZoneType choiceZone = ZoneType.Battlefield; if (sa.hasParam("ChoiceZone")) { choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone")); } - String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoose") + " "; + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : + Localizer.getInstance().getMessage("lblChoose") + " "; - CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), p, source, sa); - // Object + Choices means Attach Aura/Equipment onto new another card it can attach - // if multiple attachments, all of them need to be able to attach to new card - for (final Card attachment : attachments) { - if (sa.hasParam("Move")) { - Card e = attachment.getAttachedTo(); - if (e != null) - choices.remove(e); + FCollection choices = new FCollection<>(); + if (sa.hasParam("PlayerChoices")) { + choices = AbilityUtils.getDefinedEntities(source, sa.getParam("PlayerChoices"), sa); + for (final Card attachment : attachments) { + for (GameEntity g : choices) { + if (!g.canBeAttached(attachment)) { + choices.remove(g); + } + } } - choices = CardLists.filter(choices, CardPredicates.canBeAttached(attachment)); + } else { + CardCollection cardChoices = CardLists.getValidCards(game.getCardsIn(choiceZone), + sa.getParam("Choices"), p, source, sa); + // Object + Choices means Attach Aura/Equipment onto new another card it can attach + // if multiple attachments, all of them need to be able to attach to new card + for (final Card attachment : attachments) { + if (sa.hasParam("Move")) { + Card e = attachment.getAttachedTo(); + if (e != null) + cardChoices.remove(e); + } + cardChoices = CardLists.filter(cardChoices, CardPredicates.canBeAttached(attachment)); + } + choices.addAll(cardChoices); } Map params = Maps.newHashMap(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DayTimeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DayTimeEffect.java new file mode 100644 index 00000000000..d5ba9c75d8d --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/DayTimeEffect.java @@ -0,0 +1,37 @@ +package forge.game.ability.effects; + +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +public class DayTimeEffect extends SpellAbilityEffect { + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + sb.append("It becomes ").append(sa.getParam("Value").toLowerCase()).append("."); + return sb.toString(); + } + + @Override + public void resolve(SpellAbility sa) { + Card host = sa.getHostCard(); + Game game = host.getGame(); + + String newValue = sa.getParam("Value"); + if (newValue.equals("Day")) { + game.setDayTime(false); + } else if (newValue.equals("Night")) { + game.setDayTime(true); + } else if (newValue.equals("Switch")) { + // logic for the Celestus + Boolean oldValue = game.getDayTime(); + if (oldValue == null) { + game.setDayTime(true); // if it was neither it becomes night + } else { + game.setDayTime(!oldValue); + } + } + } +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java index be4d8709613..b288474f5d3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java @@ -4,11 +4,9 @@ import forge.card.CardStateName; import forge.game.Game; import forge.game.GameEntityCounterTable; import forge.game.GameLogEntryType; +import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardUtil; -import forge.game.card.CounterEnumType; +import forge.game.card.*; import forge.game.event.GameEventCardStatsChanged; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -17,6 +15,7 @@ import forge.game.zone.ZoneType; import forge.util.Lang; import forge.util.Localizer; import forge.util.TextUtil; +import org.apache.commons.lang3.StringUtils; public class SetStateEffect extends SpellAbilityEffect { @@ -47,9 +46,32 @@ public class SetStateEffect extends SpellAbilityEffect { final boolean optional = sa.hasParam("Optional"); final CardCollection transformedCards = new CardCollection(); + CardCollection cardsToTransform = new CardCollection(); + if (sa.hasParam("Choices")) { + CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield); + choices = CardLists.getValidCards(choices, sa.getParam("Choices"), p, host, sa); + + final String numericAmount = sa.getParamOrDefault("Amount", "1"); + final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : + AbilityUtils.calculateAmount(host, numericAmount, sa); + final int minAmount = sa.hasParam("MinAmount") ? Integer.parseInt(sa.getParam("MinAmount")) : + validAmount; + + if (validAmount <= 0) { + return; + } + + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : + Localizer.getInstance().getMessage("lblChooseaCard") + " "; + cardsToTransform.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, + !sa.hasParam("Mandatory"), null)); + } else { + cardsToTransform = getTargetCards(sa); + } + GameEntityCounterTable table = new GameEntityCounterTable(); - for (final Card tgtCard : getTargetCards(sa)) { + for (final Card tgtCard : cardsToTransform) { // check if the object is still in game or if it was moved Card gameCard = game.getCardState(tgtCard, null); // gameCard is LKI in that case, the card is not in game anymore @@ -65,7 +87,7 @@ public class SetStateEffect extends SpellAbilityEffect { // Cards which are not on the battlefield should not be able to transform. // TurnFace should be allowed in other zones like Exile too - if (!"TurnFace".equals(mode) && !gameCard.isInZone(ZoneType.Battlefield)) { + if (!"TurnFace".equals(mode) && !gameCard.isInZone(ZoneType.Battlefield) && !sa.hasParam("ETB")) { continue; } @@ -112,7 +134,7 @@ public class SetStateEffect extends SpellAbilityEffect { } // for reasons it can't transform, skip - if ("Transform".equals(mode) && !gameCard.canTransform()) { + if ("Transform".equals(mode) && !gameCard.canTransform(sa)) { continue; } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 6c9b8d9bc8a..6d6601f2229 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -56,6 +56,7 @@ import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityCantAttackBlock; +import forge.game.staticability.StaticAbilityCantTransform; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; @@ -576,7 +577,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // Proof: Morph cards never have ability that makes them flip, Ixidron does not suppose cards to be turned face up again, // Illusionary Mask affects cards in hand. if (mode.equals("Transform") && (isDoubleFaced() || hasMergedCard())) { - if (!canTransform()) { + if (!canTransform(cause)) { return false; } @@ -606,9 +607,11 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // Clear old dfc trigger from the trigger handler getGame().getTriggerHandler().clearActiveTriggers(this, null); getGame().getTriggerHandler().registerActiveTrigger(this, false); - final Map runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Transformer, this); - getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); + + if (cause == null || !cause.hasParam("ETB")) { + final Map runParams = AbilityKey.mapFromCard(this); + getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); + } incrementTransformedTimestamp(); return retResult; @@ -761,7 +764,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return false; } - public boolean canTransform() { + public boolean canTransform(SpellAbility cause) { if (isFaceDown()) { return false; } @@ -794,7 +797,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return false; } - return !hasKeyword("CARDNAME can't transform"); + return !StaticAbilityCantTransform.cantTransform(this, cause); } public int getHiddenId() { @@ -2082,7 +2085,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { || keyword.equals("Suspend") // for the ones without amount || keyword.equals("Foretell") // for the ones without cost || keyword.equals("Hideaway") || keyword.equals("Ascend") || keyword.equals("Totem armor") - || keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")) { + || keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot") + || keyword.equals("Daybound") || keyword.equals("Nightbound")) { sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")"); } else if (keyword.startsWith("Partner:")) { final String[] k = keyword.split(":"); diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 1c6377cf859..5aad0dcc1ca 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -978,6 +978,24 @@ public class CardFactoryUtil { trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); inst.addTrigger(trigger); + } else if (keyword.equals("Daybound")) { + // Set Day when it's Neither + final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with daybound, if it’s neither day nor night, it becomes day."; + String setDayEff = "DB$ DayTime | Value$ Day"; + + Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic); + trigger.setOverridingAbility(AbilityFactory.getAbility(setDayEff, card)); + + inst.addTrigger(trigger); + + final String transformTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Night | IsPresent$ Card.Self+FrontSide | Secondary$ True | TriggerDescription$ As it becomes night, if this permanent is front face up, transform it."; + String transformEff = "DB$ SetState | Mode$ Transform | Daybound$ True"; + + trigger = TriggerHandler.parseTrigger(transformTrig, card, intrinsic); + trigger.setOverridingAbility(AbilityFactory.getAbility(transformEff, card)); + + inst.addTrigger(trigger); + } else if (keyword.equals("Decayed")) { final String attackTrig = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True | TriggerDescription$ " + "When a creature with decayed attacks, sacrifice it at end of combat."; @@ -1507,6 +1525,24 @@ public class CardFactoryUtil { parsedTrigger.setOverridingAbility(repeatSA); inst.addTrigger(parsedTrigger); + } else if (keyword.equals("Nightbound")) { + // Set Night when it's Neither + final String setDayTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Neither | IsPresent$ Card.Daybound | PresentCompare$ EQ0 | Secondary$ True | TriggerDescription$ Any time a player controls a permanent with nightbound, if it’s neither day nor night and there are no permanents with daybound on the battlefield, it becomes night."; + String setDayEff = "DB$ DayTime | Value$ Night"; + + Trigger trigger = TriggerHandler.parseTrigger(setDayTrig, card, intrinsic); + trigger.setOverridingAbility(AbilityFactory.getAbility(setDayEff, card)); + + inst.addTrigger(trigger); + + final String transformTrig = "Mode$ Always | TriggerZones$ Battlefield | Static$ True | DayTime$ Day | IsPresent$ Card.Self+BackSide | Secondary$ True | TriggerDescription$ As it becomes day, if this permanent is back face up, transform it"; + String transformEff = "DB$ SetState | Mode$ Transform | Nightbound$ True"; + + trigger = TriggerHandler.parseTrigger(transformTrig, card, intrinsic); + trigger.setOverridingAbility(AbilityFactory.getAbility(transformEff, card)); + + inst.addTrigger(trigger); + } else if (keyword.startsWith("Partner:")) { // Partner With final String[] k = keyword.split(":"); @@ -2124,6 +2160,17 @@ public class CardFactoryUtil { re.setSVar("DredgeCheckLib", "Count$ValidLibrary Card.YouOwn"); + inst.addReplacement(re); + } else if (keyword.equals("Daybound")) { + final String actualRep = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Night | Secondary$ True | Description$ If it is night, this permanent enters the battlefield transformed."; + final String abTransform = "DB$ SetState | Defined$ ReplacedCard | Mode$ Transform | ETB$ True | Daybound$ True"; + + ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card); + + SpellAbility saTransform = AbilityFactory.getAbility(abTransform, card); + setupETBReplacementAbility(saTransform); + re.setOverridingAbility(saTransform); + inst.addReplacement(re); } else if (keyword.startsWith("Devour")) { final String[] k = keyword.split(":"); @@ -3438,6 +3485,8 @@ public class CardFactoryUtil { } } else if (keyword.startsWith("Dash")) { effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste"; + } else if (keyword.equals("Daybound")) { + effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can’t be transformed except by its daybound ability."; } else if (keyword.equals("Decayed")) { effect = "Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ CARDNAME can't block. | " + "Secondary$ True"; @@ -3490,6 +3539,8 @@ public class CardFactoryUtil { } else if (keyword.equals("Intimidate")) { effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.nonArtifact+notSharesColorWith | Secondary$ True " + " | Description$ Intimidate ( " + inst.getReminderText() + ")"; + } else if (keyword.equals("Nightbound")) { + effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Nightbound | Secondary$ True | Description$ This permanent can’t be transformed except by its nightbound ability."; } else if (keyword.startsWith("Protection")) { String valid = getProtectionValid(keyword, false); effect = "Mode$ CantBlockBy | ValidAttacker$ Creature.Self "; diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 17ce2309480..51cadf709c5 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -114,6 +114,14 @@ public class CardProperty { if (!card.isDoubleFaced()) { return false; } + } else if (property.equals("FrontSide")) { + if (card.isBackSide()) { + return false; + } + } else if (property.equals("BackSide")) { + if (!card.isBackSide()) { + return false; + } } else if (property.equals("Flip")) { if (!card.isFlipCard()) { return false; diff --git a/forge-game/src/main/java/forge/game/event/GameEventDayTimeChanged.java b/forge-game/src/main/java/forge/game/event/GameEventDayTimeChanged.java new file mode 100644 index 00000000000..e4c67e4cb2f --- /dev/null +++ b/forge-game/src/main/java/forge/game/event/GameEventDayTimeChanged.java @@ -0,0 +1,14 @@ +package forge.game.event; + +public class GameEventDayTimeChanged extends GameEvent { + public final boolean daytime; + + public GameEventDayTimeChanged(final boolean daytime) { + this.daytime = daytime; + } + + @Override + public T visit(IGameEventVisitor visitor) { + return visitor.visit(this); + } +} \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java index 72672f003fa..1566cfef093 100644 --- a/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/forge-game/src/main/java/forge/game/event/IGameEventVisitor.java @@ -54,7 +54,7 @@ public interface IGameEventVisitor { T visit(GameEventTurnPhase event); T visit(GameEventZone event); T visit(GameEventCardForetold gameEventCardForetold); - + T visit(GameEventDayTimeChanged gameEventDayTimeChanged); // This is base class for all visitors. class Base implements IGameEventVisitor{ @@ -109,5 +109,8 @@ public interface IGameEventVisitor { public T visit(GameEventCardForetold gameEventCardForetold) { return null; } + public T visit(GameEventDayTimeChanged gameEventDayTimeChanged) { + return null; + } } } diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 3cf7cb33722..d184c619c1d 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -45,6 +45,7 @@ public enum Keyword { CUMULATIVE_UPKEEP("Cumulative upkeep", KeywordWithCost.class, false, "At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it."), CYCLING("Cycling", KeywordWithCost.class, false, "%s, Discard this card: Draw a card."), //Typecycling reminder text handled by Cycling class DASH("Dash", KeywordWithCost.class, false, "You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step."), + DAYBOUND("Daybound", SimpleKeyword.class, true, "If a player casts no spells during their own turn, it becomes night next turn."), DEATHTOUCH("Deathtouch", SimpleKeyword.class, true, "Any amount of damage this deals to a creature is enough to destroy it."), DECAYED("Decayed", SimpleKeyword.class, true, "This creature can't block. When it attacks, sacrifice it at end of combat."), DEFENDER("Defender", SimpleKeyword.class, true, "This creature can't attack."), @@ -116,6 +117,7 @@ public enum Keyword { MULTIKICKER("Multikicker", KeywordWithCost.class, false, "You may pay an additional %s any number of times as you cast this spell."), MUTATE("Mutate", KeywordWithCost.class, true, "If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it."), MYRIAD("Myriad", SimpleKeyword.class, false, "Whenever this creature attacks, for each opponent other than defending player, you may create a token that's a copy of this creature that's tapped and attacking that player or a planeswalker they control. Exile the tokens at end of combat."), + NIGHTBOUND("Nightbound", SimpleKeyword.class, true, "If a player casts at least two spells during their own turn, it becomes day next turn."), NINJUTSU("Ninjutsu", Ninjutsu.class, false, "%s, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your %s tapped and attacking."), OUTLAST("Outlast", KeywordWithCost.class, false, "%s, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery."), OFFERING("Offering", KeywordWithType.class, false, "You may cast this card any time you could cast an instant by sacrificing a %1$s and paying the difference in mana costs between this and the sacrificed %1$s. Mana cost includes color."), diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithCost.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithCost.java index e6d3d8d82a1..98f83a95879 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithCost.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithCost.java @@ -14,7 +14,16 @@ public class KeywordWithCost extends KeywordInstance { protected String formatReminderText(String reminderText) { // some reminder does not contain cost if (reminderText.contains("%")) { - return String.format(reminderText, cost.toSimpleString()); + String costString = cost.toSimpleString(); + if (reminderText.contains("pays %")) { + if (costString.startsWith("Pay ")) { + costString = costString.substring(4); + } else if (costString.startsWith("Discard ")) { + reminderText = reminderText.replace("pays", ""); + costString = costString.replace("Discard", "discards"); + } + } + return String.format(reminderText, costString); } else { return reminderText; } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 8b84e6fc37d..aa976ed0cb9 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -95,6 +95,7 @@ public class PhaseHandler implements java.io.Serializable { private int planarDiceRolledthisTurn = 0; private transient Player playerTurn = null; + private transient Player playerPreviousTurn = null; // priority player @@ -142,6 +143,10 @@ public class PhaseHandler implements java.io.Serializable { setPriority(playerTurn); } + public final Player getPreviousPlayerTurn() { + return playerPreviousTurn; + } + public final Player getPriorityPlayer() { return pPlayerPriority; } @@ -523,6 +528,8 @@ public class PhaseHandler implements java.io.Serializable { if (!bRepeatCleanup) { // only call onCleanupPhase when Cleanup is not repeated game.onCleanupPhase(); + // set previous player + playerPreviousTurn = this.getPlayerTurn(); setPlayerTurn(handleNextTurn()); // "Trigger" for begin turn to get around a phase skipping final Map runParams = AbilityKey.newMap(); diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index ca1d1ee350c..c95fa0fe207 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -24,6 +24,7 @@ import java.util.Map.Entry; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -33,6 +34,7 @@ import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.card.CardPredicates; import forge.game.card.CardPredicates.Presets; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; @@ -69,9 +71,9 @@ public class Untap extends Phase { public void executeAt() { this.execute(this.at); - final Player turn = game.getPhaseHandler().getPlayerTurn(); - Untap.doPhasing(turn); - + doPhasing(game.getPhaseHandler().getPlayerTurn()); + doDayTime(game.getPhaseHandler().getPreviousPlayerTurn()); + game.getAction().checkStaticAbilities(); doUntap(); @@ -273,19 +275,28 @@ public class Untap extends Phase { } else if (c.hasKeyword(Keyword.PHASING)) { // 702.23g If an object would simultaneously phase out directly // and indirectly, it just phases out indirectly. - if (c.isAura() || c.isFortification()) { + if (c.isAttachment()) { final Card ent = c.getAttachedTo(); if (ent != null && list.contains(ent)) { continue; } - } else if (c.isEquipment() && c.isEquipping()) { - if (list.contains(c.getEquipping())) { - continue; - } } c.phase(true); } } } + private static void doDayTime(final Player previous) { + if (previous == null) { + return; + } + final Game game = previous.getGame(); + List casted = game.getStack().getSpellsCastLastTurn(); + + if (game.isDay() && !Iterables.any(casted, CardPredicates.isController(previous))) { + game.setDayTime(true); + } else if (game.isNight() && Iterables.size(Iterables.filter(casted, CardPredicates.isController(previous))) > 1) { + game.setDayTime(false); + } + } } //end class Untap diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 32ab2a9f630..d5a922a73ee 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -484,6 +484,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone if (condition.equals("Desert") && !controller.hasDesert()) return false; if (condition.equals("Blessing") && !controller.hasBlessing()) return false; if (condition.equals("Monarch") & !controller.isMonarch()) return false; + if (condition.equals("Night") & !game.isNight()) return false; if (condition.equals("PlayerTurn")) { if (!ph.isPlayerTurn(controller)) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTransform.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTransform.java new file mode 100644 index 00000000000..79dab28f382 --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTransform.java @@ -0,0 +1,38 @@ +package forge.game.staticability; + +import forge.game.CardTraitBase; +import forge.game.Game; +import forge.game.card.Card; +import forge.game.zone.ZoneType; + +public class StaticAbilityCantTransform { + + static String MODE = "CantTransform"; + + static public boolean cantTransform(Card card, CardTraitBase cause) { + final Game game = card.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + if (applyCantTransformAbility(stAb, card, cause)) { + return true; + } + } + } + return false; + } + + static public boolean applyCantTransformAbility(StaticAbility stAb, Card card, CardTraitBase cause) { + if (!stAb.matchesValidParam("ValidCard", card)) { + return false; + } + if (stAb.hasParam("ExceptCause")) { + if (stAb.matchesValidParam("ExceptCause", cause)) { + return false; + } + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDayTimeChanges.java b/forge-game/src/main/java/forge/game/trigger/TriggerDayTimeChanges.java new file mode 100644 index 00000000000..7518f977f0c --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDayTimeChanges.java @@ -0,0 +1,29 @@ +package forge.game.trigger; + +import java.util.Map; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; + +public class TriggerDayTimeChanges extends Trigger { + + public TriggerDayTimeChanges(Map params, Card host, boolean intrinsic) { + super(params, host, intrinsic); + } + + @Override + public boolean performTest(Map runParams) { + return true; + } + + @Override + public void setTriggeringObjects(SpellAbility sa, Map runParams) { + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + return ""; + } + +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java b/forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java index f2fcb6084ab..83b110cf3d6 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerTransformed.java @@ -46,7 +46,7 @@ public class TriggerTransformed extends Trigger { */ @Override public boolean performTest(Map runParams) { - if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Transformer))) { + if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) { return false; } @@ -55,13 +55,13 @@ public class TriggerTransformed extends Trigger { @Override public void setTriggeringObjects(SpellAbility sa, Map runParams) { - sa.setTriggeringObjectsFrom(runParams, AbilityKey.Transformer); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card); } @Override public String getImportantStackObjects(SpellAbility sa) { StringBuilder sb = new StringBuilder(); - sb.append(Localizer.getInstance().getMessage("lblTransformed")).append(": ").append(sa.getTriggeringObject(AbilityKey.Transformer)); + sb.append(Localizer.getInstance().getMessage("lblTransformed")).append(": ").append(sa.getTriggeringObject(AbilityKey.Card)); return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java index 9329d34d685..387b06abd38 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -54,6 +54,7 @@ public enum TriggerType { DamageDoneOnce(TriggerDamageDoneOnce.class), DamagePrevented(TriggerDamagePrevented.class), DamagePreventedOnce(TriggerDamagePreventedOnce.class), + DayTimeChanges (TriggerDayTimeChanges.class), Destroyed(TriggerDestroyed.class), Devoured(TriggerDevoured.class), Discarded(TriggerDiscarded.class), diff --git a/forge-gui-android/assets/shaders/grayscale.frag b/forge-gui-android/assets/shaders/grayscale.frag new file mode 100644 index 00000000000..aa5fd3ef8ec --- /dev/null +++ b/forge-gui-android/assets/shaders/grayscale.frag @@ -0,0 +1,15 @@ +#ifdef GL_ES +precision mediump float; +#endif + +varying vec4 v_color; +varying vec2 v_texCoords; +uniform sampler2D u_texture; +uniform float u_grayness; + +void main() { + vec4 c = v_color * texture2D(u_texture, v_texCoords); + float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) ); + vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness); + gl_FragColor = vec4(blendedColor.rgb, c.a); +} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/grayscale.vert b/forge-gui-android/assets/shaders/grayscale.vert new file mode 100644 index 00000000000..17d96ca8dde --- /dev/null +++ b/forge-gui-android/assets/shaders/grayscale.vert @@ -0,0 +1,14 @@ +attribute vec4 a_position; +attribute vec4 a_color; +attribute vec2 a_texCoord0; + +uniform mat4 u_projTrans; + +varying vec4 v_color; +varying vec2 v_texCoords; + +void main() { + v_color = a_color; + v_texCoords = a_texCoord0; + gl_Position = u_projTrans * a_position; +} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/outline.frag b/forge-gui-android/assets/shaders/outline.frag new file mode 100644 index 00000000000..738d23c1f71 --- /dev/null +++ b/forge-gui-android/assets/shaders/outline.frag @@ -0,0 +1,40 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; +uniform vec2 u_viewportInverse; +uniform vec3 u_color; +uniform float u_offset; +uniform float u_step; + +varying vec4 v_color; +varying vec2 v_texCoord; + +#define ALPHA_VALUE_BORDER 0.5 + +void main() { + vec2 T = v_texCoord.xy; + + float alpha = 0.0; + bool allin = true; + for( float ix = -u_offset; ix < u_offset; ix += u_step ) + { + for( float iy = -u_offset; iy < u_offset; iy += u_step ) + { + float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a; + allin = allin && newAlpha > ALPHA_VALUE_BORDER; + if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha) + { + alpha = newAlpha; + } + } + } + if (allin) + { + alpha = 0.0; + } + + gl_FragColor = vec4(u_color,alpha); +} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/outline.vert b/forge-gui-android/assets/shaders/outline.vert new file mode 100644 index 00000000000..1b6e438116d --- /dev/null +++ b/forge-gui-android/assets/shaders/outline.vert @@ -0,0 +1,16 @@ +uniform mat4 u_projTrans; + +attribute vec4 a_position; +attribute vec2 a_texCoord0; +attribute vec4 a_color; + +varying vec4 v_color; +varying vec2 v_texCoord; + +uniform vec2 u_viewportInverse; + +void main() { + gl_Position = u_projTrans * a_position; + v_texCoord = a_texCoord0; + v_color = a_color; +} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/underwater.frag b/forge-gui-android/assets/shaders/underwater.frag new file mode 100644 index 00000000000..974a1e8ae92 --- /dev/null +++ b/forge-gui-android/assets/shaders/underwater.frag @@ -0,0 +1,23 @@ +#ifdef GL_ES +#define PRECISION mediump +precision PRECISION float; +precision PRECISION int; +#else +#define PRECISION +#endif + +varying vec2 v_texCoords; +uniform sampler2D u_texture; +uniform float u_amount; +uniform float u_speed; +uniform float u_time; + +void main () { + vec2 uv = v_texCoords; + + uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount); + + uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount); + + gl_FragColor = texture2D(u_texture, uv); +} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/warp.frag b/forge-gui-android/assets/shaders/warp.frag new file mode 100644 index 00000000000..f8a7022fa2c --- /dev/null +++ b/forge-gui-android/assets/shaders/warp.frag @@ -0,0 +1,57 @@ +#ifdef GL_ES +precision mediump float; +#endif + +varying vec2 v_texCoords; +uniform sampler2D u_texture; + +uniform float u_time; +uniform float u_speed; +uniform float u_amount; +uniform vec2 u_viewport; +uniform vec2 u_position; + +float random2d(vec2 n) { + return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); +} + +float randomRange (in vec2 seed, in float min, in float max) { + return min + random2d(seed) * (max - min); +} + +float insideRange(float v, float bottom, float top) { + return step(bottom, v) - step(top, v); +} + +void main() +{ + float time = floor(u_time * u_speed * 60.0); + + vec3 outCol = texture2D(u_texture, v_texCoords).rgb; + + float maxOffset = u_amount/2.0; + for (float i = 0.0; i < 2.0; i += 1.0) { + float sliceY = random2d(vec2(time, 2345.0 + float(i))); + float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25; + float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset); + vec2 uvOff = v_texCoords; + uvOff.x += hOffset; + if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){ + outCol = texture2D(u_texture, uvOff).rgb; + } + } + + float maxColOffset = u_amount / 6.0; + float rnd = random2d(vec2(time , 9545.0)); + vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset), + randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset)); + if (rnd < 0.33) { + outCol.r = texture2D(u_texture, v_texCoords + colOffset).r; + } else if (rnd < 0.66) { + outCol.g = texture2D(u_texture, v_texCoords + colOffset).g; + } else { + outCol.b = texture2D(u_texture, v_texCoords + colOffset).b; + } + + gl_FragColor = vec4(outCol, 1.0); +} \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java index 2dcb1ebe1bd..3a39c9bccd2 100644 --- a/forge-gui-desktop/src/main/java/forge/control/FControl.java +++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java @@ -17,10 +17,7 @@ */ package forge.control; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.KeyEventDispatcher; -import java.awt.KeyboardFocusManager; +import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.InputEvent; @@ -333,9 +330,20 @@ public enum FControl implements KeyEventDispatcher { if (screen.isMatchScreen()) { if (isMatchBackgroundImageVisible()) { - FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH)); + if (screen.getDaytime() == null) + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH), true); + else { + if ("Day".equals(screen.getDaytime())) + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_DAY), true); + else + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_NIGHT), true); + } + } else { + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage((Image)null); } //SOverlayUtils.showTargetingOverlay(); + } else { + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage((Image)null); } Singletons.getView().getNavigationBar().updateSelectedTab(); diff --git a/forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java b/forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java index 059ce836a4a..97000772173 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java +++ b/forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java @@ -188,6 +188,7 @@ public class FScreen { private final boolean allowTabClose; private final FileLocation layoutFile; private final boolean isMatch; + private String daytime = null; private FScreen(final IVTopLevelUI view0, final ICDoc controller0, final String tabCaption0, final SkinImage tabIcon0, @@ -230,6 +231,12 @@ public class FScreen { this.tabCaption = caption; FView.SINGLETON_INSTANCE.getNavigationBar().updateTitle(this); } + public String getDaytime() { + return daytime; + } + public void setDaytime(final String daytime) { + this.daytime = daytime; + } public SkinImage getTabIcon() { return tabIcon; diff --git a/forge-gui-desktop/src/main/java/forge/menus/LayoutMenu.java b/forge-gui-desktop/src/main/java/forge/menus/LayoutMenu.java index 335a692c5f2..731d804bc40 100644 --- a/forge-gui-desktop/src/main/java/forge/menus/LayoutMenu.java +++ b/forge-gui-desktop/src/main/java/forge/menus/LayoutMenu.java @@ -14,6 +14,7 @@ import javax.swing.JRadioButtonMenuItem; import javax.swing.KeyStroke; import forge.Singletons; +import forge.control.FControl; import forge.gui.GuiChoose; import forge.gui.MouseUtil; import forge.gui.framework.FScreen; @@ -119,7 +120,14 @@ public final class LayoutMenu { final boolean isVisible = menuItem.getState(); prefs.setPref(FPref.UI_MATCH_IMAGE_VISIBLE, isVisible); if (isVisible) { - FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH)); + if (FControl.instance.getCurrentScreen().getDaytime() == null) + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH), true); + else { + if ("Day".equals(FControl.instance.getCurrentScreen().getDaytime())) + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_DAY), true); + else + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_NIGHT), true); + } } else { FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage((Image)null); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index f56ced7e01d..abef410190b 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -17,8 +17,7 @@ */ package forge.screens.match; -import java.awt.Component; -import java.awt.Dimension; +import java.awt.*; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; @@ -415,6 +414,19 @@ public final class CMatchUI cCombat.update(); } // showCombat(CombatView) + @Override + public void updateDayTime(String daytime) { + super.updateDayTime(daytime); + if ("Day".equals(daytime)) { + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_DAY), true); + getScreen().setDaytime("Day"); + } else { + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_NIGHT), true); + getScreen().setDaytime("Night"); + } + FView.SINGLETON_INSTANCE.getPnlInsets().repaint(); + } + @Override public void updateZones(final Iterable zonesToUpdate) { for (final PlayerZoneUpdate update : zonesToUpdate) { @@ -1064,6 +1076,12 @@ public final class CMatchUI SDisplayUtil.showTab(EDocID.REPORT_LOG.getDoc()); SOverlayUtils.hideOverlay(); + //reset every match + getScreen().setDaytime(null); + if (FModel.getPreferences().getPrefBoolean(FPref.UI_MATCH_IMAGE_VISIBLE)) + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH), true); + else + FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage((Image)null); } @Override diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java index ff8ed32a868..039563af77d 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java @@ -32,6 +32,7 @@ import java.beans.PropertyChangeListener; import javax.swing.JButton; +import forge.control.FControl; import forge.game.GameView; import forge.game.card.CardView; import forge.gui.FThreads; @@ -42,6 +43,7 @@ import forge.model.FModel; import forge.screens.match.CMatchUI; import forge.screens.match.views.VPrompt; import forge.toolbox.FSkin; +import forge.util.Localizer; /** * Controls the prompt panel in the match UI. @@ -156,7 +158,9 @@ public class CPrompt implements ICDoc { } public void setMessage(final String s0) { - view.getTarMessage().setText(FSkin.encodeSymbols(s0, false)); + String header = FControl.instance.getCurrentScreen().getDaytime() != null ? "[" + Localizer.getInstance().getMessage("lbl"+FControl.instance.getCurrentScreen().getDaytime()) + "]\n\n" : "[---]\n\n"; + header += s0; + view.getTarMessage().setText(FSkin.encodeSymbols(header, false)); view.setCardView(null); } public void setMessage(final String s0, final CardView card) { diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FPanel.java b/forge-gui-desktop/src/main/java/forge/toolbox/FPanel.java index e1eb657532a..a3d5b04f0cf 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FPanel.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FPanel.java @@ -149,7 +149,7 @@ public class FPanel extends FPanelBase implements ILocalRepaint { /** @param img0   {@link java.awt.Image} */ @Override - protected void onSetForegroundImage(final Image img0) { + protected void onSetForegroundImage(final Image img0, boolean stretch) { if (img0 == null) { this.foregroundImage = null; return; @@ -159,6 +159,7 @@ public class FPanel extends FPanelBase implements ILocalRepaint { this.imgW = img0.getWidth(null); this.imgH = img0.getHeight(null); this.iar = (double) imgW / (double) imgH; + this.foregroundStretch = stretch; } /** Aligns NON-STRETCHED foreground image. diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java index 1412034efe3..bb2e069cb51 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java @@ -1305,6 +1305,9 @@ public class FSkin { // Exceptions handled inside method. SkinIcon.setIcon(FSkinProp.BG_TEXTURE, preferredDir + ForgeConstants.TEXTURE_BG_FILE); SkinIcon.setIcon(FSkinProp.BG_MATCH, preferredDir + ForgeConstants.MATCH_BG_FILE); + //daynight bg + SkinIcon.setIcon(FSkinProp.BG_DAY, defaultDir + ForgeConstants.MATCH_BG_DAY_FILE); + SkinIcon.setIcon(FSkinProp.BG_NIGHT, defaultDir + ForgeConstants.MATCH_BG_NIGHT_FILE); // Run through enums and load their coords. Colors.updateAll(); @@ -2258,10 +2261,11 @@ public class FSkin { protected FPanelBase() { super(); } public FPanelBase(final LayoutManager layoutManager) { super(layoutManager); } - protected abstract void onSetForegroundImage(final Image image); - public final void setForegroundImage(final SkinImage skinImage) { onSetForegroundImage(skinImage.image); this.foregroundImage = skinImage; } - public final void setForegroundImage(final Image image) { onSetForegroundImage(image); this.foregroundImage = null; } - public final void setForegroundImage(final ImageIcon imageIcon) { onSetForegroundImage(imageIcon.getImage()); this.foregroundImage = null; } + protected abstract void onSetForegroundImage(final Image image, boolean stretch); + public final void setForegroundImage(final SkinImage skinImage, final boolean stretch) { onSetForegroundImage(skinImage.image, stretch); this.foregroundImage = skinImage; } + public final void setForegroundImage(final SkinImage skinImage) { onSetForegroundImage(skinImage.image, false); this.foregroundImage = skinImage; } + public final void setForegroundImage(final Image image) { onSetForegroundImage(image, false); this.foregroundImage = null; } + public final void setForegroundImage(final ImageIcon imageIcon) { onSetForegroundImage(imageIcon.getImage(), false); this.foregroundImage = null; } protected abstract void onSetBackgroundTexture(final Image image); public final void setBackgroundTexture(final SkinImage skinImage) { onSetBackgroundTexture(skinImage.image); this.backgroundTexture = skinImage; } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 6ac6cba7903..54d7c376fba 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -1720,11 +1720,11 @@ public class GameSimulatorTest extends SimulationTestCase { assertFalse(outlaw.isCloned()); assertTrue(outlaw.isDoubleFaced()); assertTrue(outlaw.hasState(CardStateName.Transformed)); - assertTrue(outlaw.canTransform()); + assertTrue(outlaw.canTransform(null)); assertFalse(outlaw.isBackSide()); assertFalse(giant.isDoubleFaced()); - assertFalse(giant.canTransform()); + assertFalse(giant.canTransform(null)); addCard("Forest", p); addCard("Forest", p); @@ -1756,7 +1756,7 @@ public class GameSimulatorTest extends SimulationTestCase { assertTrue(clonedOutLaw.isCloned()); assertTrue(clonedOutLaw.isDoubleFaced()); assertFalse(clonedOutLaw.hasState(CardStateName.Transformed)); - assertTrue(clonedOutLaw.canTransform()); + assertTrue(clonedOutLaw.canTransform(null)); assertFalse(clonedOutLaw.isBackSide()); assertEquals(clonedOutLaw.getName(), hillGiantName); @@ -1777,7 +1777,7 @@ public class GameSimulatorTest extends SimulationTestCase { assertTrue(transformOutLaw.isCloned()); assertTrue(transformOutLaw.isDoubleFaced()); assertFalse(transformOutLaw.hasState(CardStateName.Transformed)); - assertTrue(transformOutLaw.canTransform()); + assertTrue(transformOutLaw.canTransform(null)); assertTrue(transformOutLaw.isBackSide()); assertEquals(transformOutLaw.getName(), hillGiantName); @@ -1792,7 +1792,7 @@ public class GameSimulatorTest extends SimulationTestCase { assertFalse(transformOutLaw.isCloned()); assertTrue(transformOutLaw.isDoubleFaced()); assertTrue(transformOutLaw.hasState(CardStateName.Transformed)); - assertTrue(transformOutLaw.canTransform()); + assertTrue(transformOutLaw.canTransform(null)); assertTrue(transformOutLaw.isBackSide()); assertEquals(transformOutLaw.getName(), terrorName); diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index fa30139d8cb..9a6f5f8eea1 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -13,6 +13,15 @@ src + + + ${project.basedir} + + **/*.vert + **/*.frag + + + maven-compiler-plugin diff --git a/forge-gui-mobile-dev/shaders/grayscale.frag b/forge-gui-mobile-dev/shaders/grayscale.frag new file mode 100644 index 00000000000..aa5fd3ef8ec --- /dev/null +++ b/forge-gui-mobile-dev/shaders/grayscale.frag @@ -0,0 +1,15 @@ +#ifdef GL_ES +precision mediump float; +#endif + +varying vec4 v_color; +varying vec2 v_texCoords; +uniform sampler2D u_texture; +uniform float u_grayness; + +void main() { + vec4 c = v_color * texture2D(u_texture, v_texCoords); + float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) ); + vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness); + gl_FragColor = vec4(blendedColor.rgb, c.a); +} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/grayscale.vert b/forge-gui-mobile-dev/shaders/grayscale.vert new file mode 100644 index 00000000000..17d96ca8dde --- /dev/null +++ b/forge-gui-mobile-dev/shaders/grayscale.vert @@ -0,0 +1,14 @@ +attribute vec4 a_position; +attribute vec4 a_color; +attribute vec2 a_texCoord0; + +uniform mat4 u_projTrans; + +varying vec4 v_color; +varying vec2 v_texCoords; + +void main() { + v_color = a_color; + v_texCoords = a_texCoord0; + gl_Position = u_projTrans * a_position; +} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/outline.frag b/forge-gui-mobile-dev/shaders/outline.frag new file mode 100644 index 00000000000..738d23c1f71 --- /dev/null +++ b/forge-gui-mobile-dev/shaders/outline.frag @@ -0,0 +1,40 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; +uniform vec2 u_viewportInverse; +uniform vec3 u_color; +uniform float u_offset; +uniform float u_step; + +varying vec4 v_color; +varying vec2 v_texCoord; + +#define ALPHA_VALUE_BORDER 0.5 + +void main() { + vec2 T = v_texCoord.xy; + + float alpha = 0.0; + bool allin = true; + for( float ix = -u_offset; ix < u_offset; ix += u_step ) + { + for( float iy = -u_offset; iy < u_offset; iy += u_step ) + { + float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a; + allin = allin && newAlpha > ALPHA_VALUE_BORDER; + if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha) + { + alpha = newAlpha; + } + } + } + if (allin) + { + alpha = 0.0; + } + + gl_FragColor = vec4(u_color,alpha); +} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/outline.vert b/forge-gui-mobile-dev/shaders/outline.vert new file mode 100644 index 00000000000..1b6e438116d --- /dev/null +++ b/forge-gui-mobile-dev/shaders/outline.vert @@ -0,0 +1,16 @@ +uniform mat4 u_projTrans; + +attribute vec4 a_position; +attribute vec2 a_texCoord0; +attribute vec4 a_color; + +varying vec4 v_color; +varying vec2 v_texCoord; + +uniform vec2 u_viewportInverse; + +void main() { + gl_Position = u_projTrans * a_position; + v_texCoord = a_texCoord0; + v_color = a_color; +} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/underwater.frag b/forge-gui-mobile-dev/shaders/underwater.frag new file mode 100644 index 00000000000..974a1e8ae92 --- /dev/null +++ b/forge-gui-mobile-dev/shaders/underwater.frag @@ -0,0 +1,23 @@ +#ifdef GL_ES +#define PRECISION mediump +precision PRECISION float; +precision PRECISION int; +#else +#define PRECISION +#endif + +varying vec2 v_texCoords; +uniform sampler2D u_texture; +uniform float u_amount; +uniform float u_speed; +uniform float u_time; + +void main () { + vec2 uv = v_texCoords; + + uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount); + + uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount); + + gl_FragColor = texture2D(u_texture, uv); +} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/warp.frag b/forge-gui-mobile-dev/shaders/warp.frag new file mode 100644 index 00000000000..f8a7022fa2c --- /dev/null +++ b/forge-gui-mobile-dev/shaders/warp.frag @@ -0,0 +1,57 @@ +#ifdef GL_ES +precision mediump float; +#endif + +varying vec2 v_texCoords; +uniform sampler2D u_texture; + +uniform float u_time; +uniform float u_speed; +uniform float u_amount; +uniform vec2 u_viewport; +uniform vec2 u_position; + +float random2d(vec2 n) { + return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); +} + +float randomRange (in vec2 seed, in float min, in float max) { + return min + random2d(seed) * (max - min); +} + +float insideRange(float v, float bottom, float top) { + return step(bottom, v) - step(top, v); +} + +void main() +{ + float time = floor(u_time * u_speed * 60.0); + + vec3 outCol = texture2D(u_texture, v_texCoords).rgb; + + float maxOffset = u_amount/2.0; + for (float i = 0.0; i < 2.0; i += 1.0) { + float sliceY = random2d(vec2(time, 2345.0 + float(i))); + float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25; + float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset); + vec2 uvOff = v_texCoords; + uvOff.x += hOffset; + if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){ + outCol = texture2D(u_texture, uvOff).rgb; + } + } + + float maxColOffset = u_amount / 6.0; + float rnd = random2d(vec2(time , 9545.0)); + vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset), + randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset)); + if (rnd < 0.33) { + outCol.r = texture2D(u_texture, v_texCoords + colOffset).r; + } else if (rnd < 0.66) { + outCol.g = texture2D(u_texture, v_texCoords + colOffset).g; + } else { + outCol.b = texture2D(u_texture, v_texCoords + colOffset).b; + } + + gl_FragColor = vec4(outCol, 1.0); +} \ No newline at end of file diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java index fe2475d90f6..f25e19e2824 100644 --- a/forge-gui-mobile/src/forge/Graphics.java +++ b/forge-gui-mobile/src/forge/Graphics.java @@ -39,102 +39,10 @@ public class Graphics { private int failedClipCount; private float alphaComposite = 1; private int transformCount = 0; - private final String sVertex = "uniform mat4 u_projTrans;\n" + - "\n" + - "attribute vec4 a_position;\n" + - "attribute vec2 a_texCoord0;\n" + - "attribute vec4 a_color;\n" + - "\n" + - "varying vec4 v_color;\n" + - "varying vec2 v_texCoord;\n" + - "\n" + - "uniform vec2 u_viewportInverse;\n" + - "\n" + - "void main() {\n" + - " gl_Position = u_projTrans * a_position;\n" + - " v_texCoord = a_texCoord0;\n" + - " v_color = a_color;\n" + - "}"; - private final String sFragment = "#ifdef GL_ES\n" + - "precision mediump float;\n" + - "precision mediump int;\n" + - "#endif\n" + - "\n" + - "uniform sampler2D u_texture;\n" + - "\n" + - "// The inverse of the viewport dimensions along X and Y\n" + - "uniform vec2 u_viewportInverse;\n" + - "\n" + - "// Color of the outline\n" + - "uniform vec3 u_color;\n" + - "\n" + - "// Thickness of the outline\n" + - "uniform float u_offset;\n" + - "\n" + - "// Step to check for neighbors\n" + - "uniform float u_step;\n" + - "\n" + - "varying vec4 v_color;\n" + - "varying vec2 v_texCoord;\n" + - "\n" + - "#define ALPHA_VALUE_BORDER 0.5\n" + - "\n" + - "void main() {\n" + - " vec2 T = v_texCoord.xy;\n" + - "\n" + - " float alpha = 0.0;\n" + - " bool allin = true;\n" + - " for( float ix = -u_offset; ix < u_offset; ix += u_step )\n" + - " {\n" + - " for( float iy = -u_offset; iy < u_offset; iy += u_step )\n" + - " {\n" + - " float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;\n" + - " allin = allin && newAlpha > ALPHA_VALUE_BORDER;\n" + - " if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)\n" + - " {\n" + - " alpha = newAlpha;\n" + - " }\n" + - " }\n" + - " }\n" + - " if (allin)\n" + - " {\n" + - " alpha = 0.0;\n" + - " }\n" + - "\n" + - " gl_FragColor = vec4(u_color,alpha);\n" + - "}"; - private final String vertexShaderGray = "attribute vec4 a_position;\n" + - "attribute vec4 a_color;\n" + - "attribute vec2 a_texCoord0;\n" + - "\n" + - "uniform mat4 u_projTrans;\n" + - "\n" + - "varying vec4 v_color;\n" + - "varying vec2 v_texCoords;\n" + - "\n" + - "void main() {\n" + - " v_color = a_color;\n" + - " v_texCoords = a_texCoord0;\n" + - " gl_Position = u_projTrans * a_position;\n" + - "}"; - private final String fragmentShaderGray = "#ifdef GL_ES\n" + - " precision mediump float;\n" + - "#endif\n" + - "\n" + - "varying vec4 v_color;\n" + - "varying vec2 v_texCoords;\n" + - "uniform sampler2D u_texture;\n" + - "uniform float u_grayness;\n" + - "\n" + - "void main() {\n" + - " vec4 c = v_color * texture2D(u_texture, v_texCoords);\n" + - " float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) );\n" + - " vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness);\n" + - " gl_FragColor = vec4(blendedColor.rgb, c.a);\n" + - "}"; - - private final ShaderProgram shaderOutline = new ShaderProgram(sVertex, sFragment); - private final ShaderProgram shaderGrayscale = new ShaderProgram(vertexShaderGray, fragmentShaderGray); + private final ShaderProgram shaderOutline = new ShaderProgram(Gdx.files.internal("shaders").child("outline.vert"), Gdx.files.internal("shaders").child("outline.frag")); + private final ShaderProgram shaderGrayscale = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("grayscale.frag")); + private final ShaderProgram shaderWarp = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("warp.frag")); + private final ShaderProgram shaderUnderwater = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("underwater.frag")); public Graphics() { ShaderProgram.pedantic = false; @@ -160,6 +68,9 @@ public class Graphics { batch.dispose(); shapeRenderer.dispose(); shaderOutline.dispose(); + shaderGrayscale.dispose(); + shaderUnderwater.dispose(); + shaderWarp.dispose(); } public SpriteBatch getBatch() { @@ -842,6 +753,87 @@ public class Graphics { setAlphaComposite(oldalpha); } } + public void drawWarpImage(Texture image, float x, float y, float w, float h, float time) { + batch.end(); + shaderWarp.bind(); + shaderWarp.setUniformf("u_amount", 0.2f); + shaderWarp.setUniformf("u_speed", 0.6f); + shaderWarp.setUniformf("u_time", time); + batch.setShader(shaderWarp); + batch.begin(); + //draw + batch.draw(image, adjustX(x), adjustY(y, h), w, h); + //reset + batch.end(); + batch.setShader(null); + batch.begin(); + } + public void drawWarpImage(TextureRegion image, float x, float y, float w, float h, float time) { + batch.end(); + shaderWarp.bind(); + shaderWarp.setUniformf("u_amount", 0.2f); + shaderWarp.setUniformf("u_speed", 0.6f); + shaderWarp.setUniformf("u_time", time); + batch.setShader(shaderWarp); + batch.begin(); + //draw + batch.draw(image, adjustX(x), adjustY(y, h), w, h); + //reset + batch.end(); + batch.setShader(null); + batch.begin(); + } + public void drawWarpImage(FImage image, float x, float y, float w, float h, float time) { + batch.end(); + shaderWarp.bind(); + shaderWarp.setUniformf("u_amount", 0.2f); + shaderWarp.setUniformf("u_speed", 0.6f); + shaderWarp.setUniformf("u_time", time); + batch.setShader(shaderWarp); + batch.begin(); + //draw + image.draw(this, x, y, w, h); + //reset + batch.end(); + batch.setShader(null); + batch.begin(); + } + public void drawUnderWaterImage(FImage image, float x, float y, float w, float h, float time, boolean withDarkOverlay) { + batch.end(); + shaderUnderwater.bind(); + shaderUnderwater.setUniformf("u_amount", 10f*time); + shaderUnderwater.setUniformf("u_speed", 0.5f*time); + shaderUnderwater.setUniformf("u_time", time); + batch.setShader(shaderUnderwater); + batch.begin(); + //draw + image.draw(this, x, y, w, h); + //reset + batch.end(); + batch.setShader(null); + batch.begin(); + if(withDarkOverlay){ + float oldalpha = alphaComposite; + setAlphaComposite(0.4f); + fillRect(Color.BLACK, x, y, w, h); + setAlphaComposite(oldalpha); + } + } + public void drawUnderWaterImage(TextureRegion image, float x, float y, float w, float h, float time) { + batch.end(); + shaderUnderwater.bind(); + shaderUnderwater.setUniformf("u_amount", 10f); + shaderUnderwater.setUniformf("u_speed", 0.5f); + shaderUnderwater.setUniformf("u_time", time); + batch.setShader(shaderUnderwater); + batch.begin(); + //draw + batch.draw(image, adjustX(x), adjustY(y, h), w, h); + //reset + batch.end(); + batch.setShader(null); + batch.begin(); + } public void drawImage(FImage image, float x, float y, float w, float h) { drawImage(image, x, y, w, h, false); } diff --git a/forge-gui-mobile/src/forge/assets/FSkinTexture.java b/forge-gui-mobile/src/forge/assets/FSkinTexture.java index a41703e3094..c5998b56e32 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinTexture.java +++ b/forge-gui-mobile/src/forge/assets/FSkinTexture.java @@ -14,6 +14,8 @@ import forge.localinstance.properties.ForgeConstants; public enum FSkinTexture implements FImage { BG_TEXTURE(ForgeConstants.TEXTURE_BG_FILE, true, false), BG_MATCH(ForgeConstants.MATCH_BG_FILE, false, false), + BG_MATCH_DAY(ForgeConstants.MATCH_BG_DAY_FILE, false, false), + BG_MATCH_NIGHT(ForgeConstants.MATCH_BG_NIGHT_FILE, false, false), BG_SPACE(ForgeConstants.SPACE_BG_FILE, false, false), BG_CHAOS_WHEEL(ForgeConstants.CHAOS_WHEEL_IMG_FILE, false, false), Academy_at_Tolaria_West(ForgeConstants.BG_1, false, true), diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index a3f2b6651a0..d84f789991a 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -177,7 +177,8 @@ public class MatchController extends AbstractGuiGame { } actuateMatchPreferences(); - + //reset daytime every match + updateDayTime(null); Forge.openScreen(view); } diff --git a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java index 0593814b155..5956a1fe680 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchScreen.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchScreen.java @@ -667,7 +667,7 @@ public class MatchScreen extends FScreen { private float progress = 0; private boolean finished; - private void drawBackground(Graphics g, FImage image, float x, float y, float w, float h, boolean darkoverlay) { + private void drawBackground(Graphics g, FImage image, float x, float y, float w, float h, boolean darkoverlay, boolean daynightTransition) { float percentage = progress / DURATION; float oldAlpha = g.getfloatAlphaComposite(); if (percentage < 0) { @@ -676,7 +676,10 @@ public class MatchScreen extends FScreen { percentage = 1; } g.setAlphaComposite(percentage); - g.drawGrayTransitionImage(image, x, y, w, h, darkoverlay, 1-(percentage*1)); + if (!daynightTransition) + g.drawGrayTransitionImage(image, x, y, w, h, darkoverlay, 1-(percentage*1)); + else + g.drawUnderWaterImage(image, x, y, w, h, 1-(percentage*1), darkoverlay); g.setAlphaComposite(oldAlpha); } @@ -694,20 +697,42 @@ public class MatchScreen extends FScreen { private class FieldScroller extends FScrollPane { private float extraHeight = 0; private String plane = ""; + private String daytime = ""; @Override public void drawBackground(Graphics g) { super.drawBackground(g); - if (FModel.getPreferences().getPrefBoolean(FPref.UI_MATCH_IMAGE_VISIBLE)) { - boolean isGameFast = MatchController.instance.isGameFast(); - float midField = topPlayerPanel.getBottom(); - float x = topPlayerPanel.getField().getLeft(); - float y = midField - topPlayerPanel.getField().getHeight(); - float w = getWidth() - x; - float bgFullWidth, scaledbgHeight; - int multiplier = playerPanels.keySet().size() - 1; //fix scaling of background when zoomed in multiplayer - float bgHeight = (midField + bottomPlayerPanel.getField().getHeight() * multiplier) - y; + boolean isGameFast = MatchController.instance.isGameFast(); + float midField = topPlayerPanel.getBottom(); + float x = topPlayerPanel.getField().getLeft(); + float y = midField - topPlayerPanel.getField().getHeight(); + float w = getWidth() - x; + float bgFullWidth, scaledbgHeight; + int multiplier = playerPanels.keySet().size() - 1; //fix scaling of background when zoomed in multiplayer + float bgHeight = (midField + bottomPlayerPanel.getField().getHeight() * multiplier) - y; + + if (MatchController.instance.getDayTime() != null) { + //override BG + String dayTime = MatchController.instance.getDayTime(); + if (!daytime.equals(dayTime)) { + bgAnimation = new BGAnimation(); + bgAnimation.start(); + daytime = dayTime; + } + FSkinTexture matchBG = MatchController.instance.getGameView().getGame().isDay() ? FSkinTexture.BG_MATCH_DAY : FSkinTexture.BG_MATCH_NIGHT; + bgFullWidth = bgHeight * matchBG.getWidth() / matchBG.getHeight(); + if (bgFullWidth < w) { + scaledbgHeight = w * (bgHeight / bgFullWidth); + bgFullWidth = w; + bgHeight = scaledbgHeight; + } + if (bgAnimation != null && !isGameFast && !MatchController.instance.getGameView().isMatchOver()) { + bgAnimation.drawBackground(g, matchBG, x + (w - bgFullWidth) / 2, y, bgFullWidth, bgHeight, true, true); + } else { + g.drawImage(matchBG, x + (w - bgFullWidth) / 2, y, bgFullWidth, bgHeight, true); + } + } else if (FModel.getPreferences().getPrefBoolean(FPref.UI_MATCH_IMAGE_VISIBLE)) { if(FModel.getPreferences().getPrefBoolean(FPref.UI_DYNAMIC_PLANECHASE_BG) && hasActivePlane()) { String imageName = getPlaneName() @@ -727,7 +752,7 @@ public class MatchScreen extends FScreen { bgHeight = scaledbgHeight; } if (bgAnimation != null && !isGameFast && !MatchController.instance.getGameView().isMatchOver()) { - bgAnimation.drawBackground(g, FSkinTexture.valueOf(imageName), x + (w - bgFullWidth) / 2, y, bgFullWidth, bgHeight, true); + bgAnimation.drawBackground(g, FSkinTexture.valueOf(imageName), x + (w - bgFullWidth) / 2, y, bgFullWidth, bgHeight, true, false); } else { g.drawImage(FSkinTexture.valueOf(imageName), x + (w - bgFullWidth) / 2, y, bgFullWidth, bgHeight, true); } diff --git a/forge-gui/res/cardsfolder/b/bound_by_moonsilver.txt b/forge-gui/res/cardsfolder/b/bound_by_moonsilver.txt index 7ffc3bd84e8..ccd5134859b 100644 --- a/forge-gui/res/cardsfolder/b/bound_by_moonsilver.txt +++ b/forge-gui/res/cardsfolder/b/bound_by_moonsilver.txt @@ -3,8 +3,8 @@ ManaCost:2 W Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Curse -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. & CARDNAME can't transform | Description$ Enchanted creature can't attack, block, or transform. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Enchanted creature can't attack, block, or transform. +S:Mode$ CantTransform | ValidCard$ Creature.EnchantedBy | Secondary$ True | Description$ Enchanted creature can't transform. A:AB$ Attach | Cost$ Sac<1/Permanent.Other/another permanent> | ValidTgts$ Creature | TgtPrompt$ Select target creature | AILogic$ Curse | SorcerySpeed$ True | ActivationLimit$ 1 | SpellDescription$ Attach CARDNAME to target creature. Activate only as a sorcery and only once each turn. SVar:AIPreference:SacCost$Card.token,Permanent.nonLand+cmcLE2,Land.Basic -SVar:Picture:http://www.wizards.com/global/images/magic/general/bound_by_moonsilver.jpg Oracle:Enchant creature\nEnchanted creature can't attack, block, or transform.\nSacrifice another permanent: Attach Bound by Moonsilver to target creature. Activate only as a sorcery and only once each turn. diff --git a/forge-gui/res/cardsfolder/i/immerwolf.txt b/forge-gui/res/cardsfolder/i/immerwolf.txt index d9b34afb1c5..ad5bf5911fb 100644 --- a/forge-gui/res/cardsfolder/i/immerwolf.txt +++ b/forge-gui/res/cardsfolder/i/immerwolf.txt @@ -4,7 +4,6 @@ Types:Creature Wolf PT:2/2 K:Intimidate S:Mode$ Continuous | Affected$ Creature.Wolf+Other+YouCtrl,Creature.Werewolf+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Each other creature you control that's a Wolf or a Werewolf gets +1/+1. -S:Mode$ Continuous | Affected$ Creature.Werewolf+nonHuman+YouCtrl | AddHiddenKeyword$ CARDNAME can't transform | Description$ Non-Human Werewolf creatures you control can't transform. +S:Mode$ CantTransform | ValidCard$ Creature.Werewolf+nonHuman+YouCtrl | Description$ Non-Human Werewolves you control can't transform. SVar:PlayMain1:TRUE -SVar:Picture:http://www.wizards.com/global/images/magic/general/immerwolf.jpg Oracle:Intimidate (This creature can't be blocked except by artifact creatures and/or creatures that share a color with it.)\nEach other creature you control that's a Wolf or a Werewolf gets +1/+1.\nNon-Human Werewolves you control can't transform. diff --git a/forge-gui/res/cardsfolder/upcoming/arlinn_the_packs_hope_arlinn_the_moons_fury.txt b/forge-gui/res/cardsfolder/upcoming/arlinn_the_packs_hope_arlinn_the_moons_fury.txt new file mode 100644 index 00000000000..031a81f8c4e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/arlinn_the_packs_hope_arlinn_the_moons_fury.txt @@ -0,0 +1,26 @@ +Name:Arlinn, the Pack's Hope +ManaCost:2 R G +Types:Legendary Planeswalker Arlinn +Loyalty:4 +K:Daybound +A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Duration$ UntilYourNextTurn | StaticAbilities$ WithFlash | ReplacementEffects$ ExtraETBCounter | SpellDescription$ Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it. +SVar:WithFlash:Mode$ CastWithFlash | ValidCard$ Creature | ValidSA$ Spell | EffectZone$ Command | Caster$ You | Description$ Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it. +SVar:ExtraETBCounter:Event$ Moved | ActiveZones$ Command | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | ReplaceWith$ AddExtraCounter | Secondary$ True | Description$ Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it. +SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ MoveToBattlefield +SVar:MoveToBattlefield:DB$ ChangeZone | Origin$ All | Destination$ Battlefield | Defined$ ReplacedCard +A:AB$ Token | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | TokenAmount$ 2 | TokenScript$ g_2_2_wolf | SpellDescription$ Create two 2/2 green Wolf creature tokens. +AlternateMode:DoubleFaced +DeckHas:Ability$Token & Ability$Counters & Type$Wolf +Oracle:Daybound (If a player casts no spells during their own turn, it becomes night next turn.)\n[+1]: Until your next turn, you may cast creature spells as though they had flash, and each creature you control enters the battlefield with an additional +1/+1 counter on it.\n[−3]: Create two 2/2 green Wolf creature tokens. + +ALTERNATE + +Name:Arlinn, the Moon's Fury +ManaCost:no cost +Colors:red,green +Types:Legendary Planeswalker Arlinn +Loyalty:4 +K:Nightbound +A:AB$ Mana | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Produced$ R G | SpellDescription$ Add {R}{G}. +A:AB$ Animate | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | Defined$ Self | Power$ 5 | Toughness$ 5 | Keywords$ Trample & Indestructible & Haste | Types$ Creature,Werewolf | StackDescription$ SpellDescription | SpellDescription$ Until end of turn, CARDNAME becomes a 5/5 Werewolf creature with trample, indestructible, and haste. +Oracle:Nightbound (If a player casts at least two spells during their own turn, it becomes day next turn.)\n[+2]: Add {R}{G}.\n[0]: Until end of turn, Arlinn, the Moon's Fury becomes a 5/5 Werewolf creature with trample, indestructible, and haste. diff --git a/forge-gui/res/cardsfolder/upcoming/baneblade_scoundrel_baneclaw_marauder.txt b/forge-gui/res/cardsfolder/upcoming/baneblade_scoundrel_baneclaw_marauder.txt new file mode 100644 index 00000000000..e75195d56e1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/baneblade_scoundrel_baneclaw_marauder.txt @@ -0,0 +1,23 @@ +Name:Baneblade Scoundrel +ManaCost:3 B +Types:Creature Human Rogue Werewolf +PT:4/3 +T:Mode$ AttackerBlocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME becomes blocked, each creature blocking it gets -1/-1 until end of turn. +SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.blockingSource+DefenderCtrl | NumAtt$ -1 | NumDef$ -1 +K:Daybound +AlternateMode:DoubleFaced +Oracle:Whenever Baneblade Scoundrel becomes blocked, each creature blocking it gets -1/-1 until end of turn.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Baneclaw Marauder +ManaCost:no cost +Colors:black +Types:Creature Werewolf +PT:5/4 +T:Mode$ AttackerBlocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME becomes blocked, each creature blocking it gets -1/-1 until end of turn. +SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.blockingSource+DefenderCtrl | NumAtt$ -1 | NumDef$ -1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.blockingSource+DefenderCtrl | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ Whenever a creature blocking CARDNAME dies, that creature's controller loses 1 life. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredCardController | LifeAmount$ 1 +K:Nightbound +Oracle:Whenever Baneclaw Marauder becomes blocked, each creature blocking it gets -1/-1 until end of turn.\nWhenever a creature blocking Baneclaw Marauder dies, that creature's controller loses 1 life.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/bird_admirer_wing_shredder.txt b/forge-gui/res/cardsfolder/upcoming/bird_admirer_wing_shredder.txt new file mode 100644 index 00000000000..aa2ae1808b7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bird_admirer_wing_shredder.txt @@ -0,0 +1,19 @@ +Name:Bird Admirer +ManaCost:2 G +Types:Creature Human Archer Werewolf +PT:1/4 +K:Reach +K:Daybound +AlternateMode:DoubleFaced +Oracle:Reach\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Wing Shredder +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:3/5 +K:Reach +K:Nightbound +Oracle:Reach\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/brimstone_vandal.txt b/forge-gui/res/cardsfolder/upcoming/brimstone_vandal.txt new file mode 100644 index 00000000000..a31d09883a6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/brimstone_vandal.txt @@ -0,0 +1,11 @@ +Name:Brimstone Vandal +ManaCost:2 R +Types:Creature Devil +PT:2/3 +K:Menace +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, CARDNAME deals 1 damage to each opponent. +SVar:TrigDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1 +Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nIf it's neither day nor night, it becomes day as Brimstone Vandal enters the battlefield.\nWhenever day becomes night or night becomes day, Brimstone Vandal deals 1 damage to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/brutal_cathar_moonrage_brute.txt b/forge-gui/res/cardsfolder/upcoming/brutal_cathar_moonrage_brute.txt new file mode 100644 index 00000000000..81983d38cdf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/brutal_cathar_moonrage_brute.txt @@ -0,0 +1,24 @@ +Name:Brutal Cathar +ManaCost:2 W +Types:Creature Human Soldier Werewolf +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When this creature enters the battlefield or transforms into CARDNAME, exile target creature an opponent controls until this creature leaves the battlefield. +T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigExile | Secondary$ True | TriggerDescription$ When this creature enters the battlefield or transforms into CARDNAME, exile target creature an opponent controls until this creature leaves the battlefield. +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | Duration$ UntilHostLeavesPlay +K:Daybound +AlternateMode:DoubleFaced +SVar:PlayMain1:TRUE +SVar:OblivionRing:TRUE +Oracle:When this creature enters the battlefield or transforms into Brutal Cathar, exile target creature an opponent controls until this creature leaves the battlefield.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Moonrage Brute +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:3/3 +K:First strike +K:Ward:PayLife<3> +K:Nightbound +Oracle:First strike\nWard—Pay 3 life.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/burly_breaker_dire_strain_demolisher.txt b/forge-gui/res/cardsfolder/upcoming/burly_breaker_dire_strain_demolisher.txt new file mode 100644 index 00000000000..754a80daa82 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/burly_breaker_dire_strain_demolisher.txt @@ -0,0 +1,19 @@ +Name:Burly Breaker +ManaCost:3 G G +Types:Creature Human Werewolf +PT:6/5 +K:Ward:1 +K:Daybound +AlternateMode:DoubleFaced +Oracle:Ward {1} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {1}.)\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Dire-Strain Demolisher +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:8/7 +K:Ward:3 +K:Nightbound +Oracle:Ward {3} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {3}.)\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/celestus_sanctifier.txt b/forge-gui/res/cardsfolder/upcoming/celestus_sanctifier.txt new file mode 100644 index 00000000000..a37a6bd9326 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/celestus_sanctifier.txt @@ -0,0 +1,11 @@ +Name:Celestus Sanctifier +ManaCost:2 W +Types:Creature Human Cleric +PT:3/2 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ DBDig | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, look at the top two cards of your library. Put one of them into your graveyard. +SVar:DBDig:DB$ Dig | DigNum$ 2 | DestinationZone$ Graveyard | LibraryPosition2$ 0 +DeckHas:Ability$Graveyard +Oracle:If it's neither day nor night, it becomes day as Celestus Sanctifier enters the battlefield.\nWhenever day becomes night or night becomes day, look at the top two cards of your library. Put one of them into your graveyard. diff --git a/forge-gui/res/cardsfolder/upcoming/component_collector.txt b/forge-gui/res/cardsfolder/upcoming/component_collector.txt new file mode 100644 index 00000000000..5a61b3861e4 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/component_collector.txt @@ -0,0 +1,10 @@ +Name:Component Collector +ManaCost:2 U +Types:Creature Homunculus +PT:1/4 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigTapOrUntap | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, you may tap or untap target nonland permanent. +SVar:TrigTapOrUntap:DB$ TapOrUntap | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent +Oracle:If it's neither day nor night, it becomes day as Component Collector enters the battlefield.\nWhenever day becomes night or night becomes day, you may tap or untap target nonland permanent. diff --git a/forge-gui/res/cardsfolder/upcoming/curse_of_leeches_leeching_lurker.txt b/forge-gui/res/cardsfolder/upcoming/curse_of_leeches_leeching_lurker.txt new file mode 100644 index 00000000000..c662e78dd95 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/curse_of_leeches_leeching_lurker.txt @@ -0,0 +1,26 @@ +Name:Curse of Leeches +ManaCost:2 B +Types:Enchantment Aura Curse +K:Enchant player +A:SP$ Attach | ValidTgts$ Player | TgtPrompt$ Select target player to curse | AILogic$ Curse +R:Event$ Transform | ValidCard$ Card.Self | ReplaceWith$ Attach | Description$ As this permanent transforms into CARDNAME, attach it to a player. +SVar:Attach:DB$ Attach | Object$ Self | Chooser$ You | PlayerChoices$ Player | ChoiceTitle$ Select player to curse | AILogic$ Curse +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ At the beginning of enchanted player's upkeep, they lose 1 life and you gain 1 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ TriggeredPlayer | LifeAmount$ 1 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 +K:Daybound +AlternateMode:DoubleFaced +DeckHas:Ability$LifeGain +Oracle:Enchant player\nAs this permanent transforms into Curse of Leeches, attach it to a player.\nAt the beginning of enchanted player's upkeep, they lose 1 life and you gain 1 life.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Leeching Lurker +ManaCost:no cost +Colors:black +Types:Creature Leech Horror +PT:4/4 +K:Lifelink +K:Nightbound +DeckHas:Ability$LifeGain +Oracle:Lifelink\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/fangblade_brigand_fangblade_eviscerator.txt b/forge-gui/res/cardsfolder/upcoming/fangblade_brigand_fangblade_eviscerator.txt new file mode 100644 index 00000000000..56505d10643 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fangblade_brigand_fangblade_eviscerator.txt @@ -0,0 +1,20 @@ +Name:Fangblade Brigand +ManaCost:3 R +Types:Creature Human Werewolf +PT:3/4 +A:AB$ Pump | Cost$ 1 R | Defined$ Self | NumAtt$ +1 | KW$ First Strike | SpellDescription$ CARDNAME gets +1/+0 and gains first strike until end of turn. +K:Daybound +AlternateMode:DoubleFaced +Oracle:{1}{R}: Fangblade Brigand gets +1/+0 and gains first strike until end of turn.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Fangblade Eviscerator +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:4/5 +A:AB$ Pump | Cost$ 1 R | Defined$ Self | NumAtt$ +1 | KW$ First Strike | SpellDescription$ CARDNAME gets +1/+0 and gains first strike until end of turn. +A:AB$ PumpAll | Cost$ 4 R | ValidCards$ Creature.YouCtrl | NumAtt$ +2 | SpellDescription$ Creatures you control get +2/+0 until end of turn. +K:Nightbound +Oracle:{1}{R}: Fangblade Eviscerator gets +1/+0 and gains first strike until end of turn.\n{4}{R}: Creatures you control get +2/+0 until end of turn.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/firmament_sage.txt b/forge-gui/res/cardsfolder/upcoming/firmament_sage.txt new file mode 100644 index 00000000000..6e2a28fd737 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/firmament_sage.txt @@ -0,0 +1,10 @@ +Name:Firmament Sage +ManaCost:3 U +Types:Creature Human Wizard +PT:2/3 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ DBDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, draw a card. +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 +Oracle:If it's neither day nor night, it becomes day as Firmament Sage enters the battlefield.\nWhenever day becomes night or night becomes day, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/gavony_dawnguard.txt b/forge-gui/res/cardsfolder/upcoming/gavony_dawnguard.txt new file mode 100644 index 00000000000..aecacd39f08 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gavony_dawnguard.txt @@ -0,0 +1,11 @@ +Name:Gavony Dawnguard +ManaCost:1 W W +Types:Creature Human Soldier +PT:3/3 +K:Ward:1 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigDig | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, look at the top four cards of your library. You may reveal a creature card with mana value 3 or less from among them and put it into your hand. Put the rest on the bottom of your library in any order. +SVar:TrigDig:DB$ Dig | ForceRevealToController$ True | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Creature.cmcLE3 +Oracle:Ward {1}\nIf it's neither day nor night, it becomes day as Gavony Dawnguard enters the battlefield.\nWhenever day becomes night or night becomes day, look at the top four cards of your library. You may reveal a creature card with mana value 3 or less from among them and put it into your hand. Put the rest on the bottom of your library in any order. diff --git a/forge-gui/res/cardsfolder/upcoming/graveyard_trespasser_graveyard_glutton.txt b/forge-gui/res/cardsfolder/upcoming/graveyard_trespasser_graveyard_glutton.txt new file mode 100644 index 00000000000..c3a2579f1a1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/graveyard_trespasser_graveyard_glutton.txt @@ -0,0 +1,34 @@ +Name:Graveyard Trespasser +ManaCost:2 B +Types:Creature Human Werewolf +PT:3/3 +K:Ward:Discard<1/Card> +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, exile up to one target card from a graveyard. If a creature card was exiled this way, each opponent loses 1 life and you gain 1 life. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigExile | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, exile up to one target card from a graveyard. If a creature card was exiled this way, each opponent loses 1 life and you gain 1 life. +SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Card | TgtPrompt$ Select up to one target card from a graveyard | RememberChanged$ True | SubAbility$ DBDrain +SVar:DBDrain:DB$ LoseLife | ConditionDefined$ Remembered | ConditionPresent$ Creature | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | ConditionDefined$ Remembered | ConditionPresent$ Creature | Defined$ You | LifeAmount$ 1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +K:Daybound +AlternateMode:DoubleFaced +DeckHas:Ability$Graveyard & Ability$LifeGain +Oracle:Ward—Discard a card.\nWhenever Graveyard Trespasser enters the battlefield or attacks, exile up to one target card from a graveyard. If a creature card was exiled this way, each opponent loses 1 life and you gain 1 life.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Graveyard Glutton +ManaCost:no cost +Colors:black +Types:Creature Werewolf +PT:4/4 +K:Ward:Discard<1/Card> +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExileN | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, exile up to two target cards from graveyards. For each creature card exiled this way, each opponent loses 1 life and you gain 1 life. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigExileN | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, exile up to two target cards from graveyards. For each creature card exiled this way, each opponent loses 1 life and you gain 1 life. +SVar:TrigExileN:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from graveyards | RememberChanged$ True | SubAbility$ DBDrainN +SVar:DBDrainN:DB$ LoseLife | ConditionDefined$ Remembered | ConditionPresent$ Creature | Defined$ Player.Opponent | LifeAmount$ X | SubAbility$ DBGainLifeN +SVar:DBGainLifeN:DB$ GainLife | ConditionDefined$ Remembered | ConditionPresent$ Creature | Defined$ You | LifeAmount$ X | SubAbility$ DBCleanupN +SVar:X:Remembered$Valid Creature +SVar:DBCleanupN:DB$ Cleanup | ClearRemembered$ True +K:Nightbound +DeckHas:Ability$Graveyard & Ability$LifeGain +Oracle:Ward—Discard a card.\nWhenever Graveyard Glutton enters the battlefield or attacks, exile up to two target cards from graveyards. For each creature card exiled this way, each opponent loses 1 life and you gain 1 life.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/harvesttide_infiltrator_harvesttide_assailant.txt b/forge-gui/res/cardsfolder/upcoming/harvesttide_infiltrator_harvesttide_assailant.txt new file mode 100644 index 00000000000..34784c77527 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/harvesttide_infiltrator_harvesttide_assailant.txt @@ -0,0 +1,19 @@ +Name:Harvesttide Infiltrator +ManaCost:2 R +Types:Creature Human Werewolf +PT:3/2 +K:Trample +K:Daybound +AlternateMode:DoubleFaced +Oracle:Trample\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Harvesttide Assailant +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:4/4 +K:Trample +K:Nightbound +Oracle:Trample\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/hound_tamer_untamed_pup.txt b/forge-gui/res/cardsfolder/upcoming/hound_tamer_untamed_pup.txt new file mode 100644 index 00000000000..a3bbd5cfca8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hound_tamer_untamed_pup.txt @@ -0,0 +1,23 @@ +Name:Hound Tamer +ManaCost:2 G +Types:Creature Human Werewolf +PT:3/3 +K:Trample +A:AB$ PutCounter | Cost$ 3 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. +K:Daybound +AlternateMode:DoubleFaced +Oracle:Trample\n{3}{G}: Put a +1/+1 counter on target creature.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Untamed Pup +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:4/4 +K:Trample +S:Mode$ Continuous | Affected$ Wolf.Other+YouCtrl,Werewolf.Other+YouCtrl | AddKeyword$ Trample | Description$ Other Wolves and Werewolves you control have trample. +A:AB$ PutCounter | Cost$ 3 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. +K:Nightbound +DeckHints:Type$Wolf & Type$Werewolf +Oracle:Trample\nOther Wolves and Werewolves you control have trample.\n{3}{G}: Put a +1/+1 counter on target creature.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/kessig_naturalist_lord_of_the_ulvenwald.txt b/forge-gui/res/cardsfolder/upcoming/kessig_naturalist_lord_of_the_ulvenwald.txt new file mode 100644 index 00000000000..7240d6b3d3f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kessig_naturalist_lord_of_the_ulvenwald.txt @@ -0,0 +1,24 @@ +Name:Kessig Naturalist +ManaCost:R G +Types:Creature Human Werewolf +PT:2/2 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMana | TriggerDescription$ Whenever CARDNAME attacks, add {R} or {G}. Until end of turn, you don't lose this mana as steps and phases end. +SVar:TrigMana:DB$ Mana | Amount$ 1 | Produced$ Combo R G | PersistentMana$ True +K:Daybound +AlternateMode:DoubleFaced +Oracle:Whenever Kessig Naturalist attacks, add {R} or {G}. Until end of turn, you don't lose this mana as steps and phases end.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Lord of the Ulvenwald +ManaCost:no cost +Colors:red,green +Types:Creature Werewolf +PT:3/3 +S:Mode$ Continuous | Affected$ Wolf.Other+YouCtrl,Werewolf.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Wolves and Werewolves you control get +1/+1. +SVar:PlayMain1:TRUE +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigMana | TriggerDescription$ Whenever CARDNAME attacks, add {R} or {G}. Until end of turn, you don't lose this mana as steps and phases end. +SVar:TrigMana:DB$ Mana | Amount$ 1 | Produced$ Combo R G | PersistentMana$ True +K:Nightbound +DeckHints:Type$Wolf & Type$Werewolf +Oracle:Other Wolves and Werewolves you control get +1/+1.\nWhenever Lord of the Ulvenwald attacks, add {R} or {G}. Until end of turn, you don't lose this mana as steps and phases end.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/moonragers_slash.txt b/forge-gui/res/cardsfolder/upcoming/moonragers_slash.txt new file mode 100644 index 00000000000..f7f58a2d27b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/moonragers_slash.txt @@ -0,0 +1,6 @@ +Name:Moonrager's Slash +ManaCost:2 R +Types:Instant +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | Condition$ Night | Description$ This spell costs {2} less to cast if it's night. +A:SP$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to any target. +Oracle:This spell costs {2} less to cast if it's night.\nMoonrager's Slash deals 3 damage to any target. diff --git a/forge-gui/res/cardsfolder/upcoming/obsessive_astronomer.txt b/forge-gui/res/cardsfolder/upcoming/obsessive_astronomer.txt new file mode 100644 index 00000000000..f52bba4274f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/obsessive_astronomer.txt @@ -0,0 +1,14 @@ +Name:Obsessive Astronomer +ManaCost:1 R +Types:Creature Human Wizard +PT:2/2 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigDiscard | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, discard up to two cards, then draw that many cards. +SVar:TrigDiscard:DB$ Discard | Defined$ You | NumCards$ 2 | Optional$ True | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | NumCards$ Y | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Y:Remembered$Amount +DeckHas:Ability$Discard +Oracle:If it's neither day nor night, it becomes day as Obsessive Astronomer enters the battlefield.\nWhenever day becomes night or night becomes day, discard up to two cards, then draw that many cards. diff --git a/forge-gui/res/cardsfolder/upcoming/olivias_midnight_ambush.txt b/forge-gui/res/cardsfolder/upcoming/olivias_midnight_ambush.txt new file mode 100644 index 00000000000..caaa938630a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/olivias_midnight_ambush.txt @@ -0,0 +1,6 @@ +Name:Olivia's Midnight Ambush +ManaCost:1 B +Types:Instant +A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | SpellDescription$ Target creature gets -2/-2 until end of turn. If it's night, that creature gets -13/-13 until end of turn instead. +SVar:X:Count$Night.13.2 +Oracle:Target creature gets -2/-2 until end of turn. If it's night, that creature gets -13/-13 until end of turn instead. diff --git a/forge-gui/res/cardsfolder/upcoming/outland_liberator_frenzied_trapbreaker.txt b/forge-gui/res/cardsfolder/upcoming/outland_liberator_frenzied_trapbreaker.txt new file mode 100644 index 00000000000..b55430505d1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/outland_liberator_frenzied_trapbreaker.txt @@ -0,0 +1,23 @@ +Name:Outland Liberator +ManaCost:1 G +Types:Creature Human Werewolf +PT:2/2 +A:AB$ Destroy | Cost$ 1 Sac<1/CARDNAME> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +K:Daybound +AlternateMode:DoubleFaced +Oracle:{1}, Sacrifice Outland Liberator: Destroy target artifact or enchantment.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Frenzied Trapbreaker +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:3/3 +A:AB$ Destroy | Cost$ 1 Sac<1/CARDNAME> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ Whenever CARDNAME attacks, destroy target artifact or enchantment defending player controls. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact.ControlledBy TriggeredDefendingPlayer,Enchantment.ControlledBy TriggeredDefendingPlayer | TgtPrompt$ Select target artifact or enchantment defending player controls +K:Nightbound +SVar:HasAttackEffect:TRUE +Oracle:{1}, Sacrifice Frenzied Trapbreaker: Destroy target artifact or enchantment. +Whenever Frenzied Trapbreaker attacks, destroy target artifact or enchantment defending player controls.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/reckless_stormseeker_storm_charged_slasher.txt b/forge-gui/res/cardsfolder/upcoming/reckless_stormseeker_storm_charged_slasher.txt new file mode 100644 index 00000000000..bf5917c1bcf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/reckless_stormseeker_storm_charged_slasher.txt @@ -0,0 +1,21 @@ +Name:Reckless Stormseeker +ManaCost:2 R +Types:Creature Human Werewolf +PT:2/3 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ At the beginning of combat on your turn, target creature you control gets +1/+0 and gains haste until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ 1 | KW$ Haste +K:Daybound +AlternateMode:DoubleFaced +Oracle:At the beginning of combat on your turn, target creature you control gets +1/+0 and gains haste until end of turn.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Storm-Charged Slasher +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:3/4 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ At the beginning of combat on your turn, target creature you control gets +2/+0 and gains trample and haste until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ 2 | KW$ Trample & Haste +K:Nightbound +Oracle:At the beginning of combat on your turn, target creature you control gets +2/+0 and gains trample and haste until end of turn.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/shady_traveler_stalking_predator.txt b/forge-gui/res/cardsfolder/upcoming/shady_traveler_stalking_predator.txt new file mode 100644 index 00000000000..3db14e52cdb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shady_traveler_stalking_predator.txt @@ -0,0 +1,19 @@ +Name:Shady Traveler +ManaCost:2 B +Types:Creature Human Werewolf +PT:2/3 +K:Menace +K:Daybound +AlternateMode:DoubleFaced +Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Stalking Predator +ManaCost:no cost +Colors:black +Types:Creature Werewolf +PT:4/4 +K:Menace +K:Nightbound +Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/spellrune_painter_spellrune_howler.txt b/forge-gui/res/cardsfolder/upcoming/spellrune_painter_spellrune_howler.txt new file mode 100644 index 00000000000..5ba913ab350 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/spellrune_painter_spellrune_howler.txt @@ -0,0 +1,23 @@ +Name:Spellrune Painter +ManaCost:2 R +Types:Creature Human Shaman Werewolf +PT:2/3 +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast an instant or sorcery spell, CARDNAME gets +1/+1 until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | ValidCard$ Card.Self | NumAtt$ 1 | NumDef$ 1 +DeckHints:Type$Instant|Sorcery +K:Daybound +AlternateMode:DoubleFaced +Oracle:Whenever you cast an instant or sorcery spell, Spellrune Painter gets +1/+1 until end of turn.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Spellrune Howler +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:3/4 +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast an instant or sorcery spell, CARDNAME gets +2/+2 until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | ValidCard$ Card.Self | NumAtt$ 2 | NumDef$ 2 +DeckHints:Type$Instant|Sorcery +K:Nightbound +Oracle:Whenever you cast an instant or sorcery spell, Spellrune Howler gets +2/+2 until end of turn.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/sunrise_cavalier.txt b/forge-gui/res/cardsfolder/upcoming/sunrise_cavalier.txt new file mode 100644 index 00000000000..14ce6446a77 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sunrise_cavalier.txt @@ -0,0 +1,13 @@ +Name:Sunrise Cavalier +ManaCost:1 R W +Types:Creature Human Knight +PT:3/3 +K:Trample +K:Haste +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, put a +1/+1 counter on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +Oracle:Trample, haste\nIf it's neither day nor night, it becomes day as Sunrise Cavalier enters the battlefield.\nWhenever day becomes night or night becomes day, put a +1/+1 counter on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/sunstreak_phoenix.txt b/forge-gui/res/cardsfolder/upcoming/sunstreak_phoenix.txt new file mode 100644 index 00000000000..ca3b4d4b8df --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sunstreak_phoenix.txt @@ -0,0 +1,12 @@ +Name:Sunstreak Phoenix +ManaCost:2 R R +Types:Creature Phoenix +PT:4/2 +K:Flying +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +T:Mode$ DayTimeChanges | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped. +SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 R | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True +DeckHas:Ability$Graveyard +Oracle:Flying\nIf it's neither day nor night, it becomes day as Sunstreak Phoenix enters the battlefield.\nWhenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped. diff --git a/forge-gui/res/cardsfolder/upcoming/suspicious_stowaway_seafaring_werewolf.txt b/forge-gui/res/cardsfolder/upcoming/suspicious_stowaway_seafaring_werewolf.txt new file mode 100644 index 00000000000..96077cec45c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/suspicious_stowaway_seafaring_werewolf.txt @@ -0,0 +1,26 @@ +Name:Suspicious Stowaway +ManaCost:1 U +Types:Creature Human Rogue Werewolf +PT:1/1 +K:Unblockable +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card, then discard a card. +SVar:TrigDraw:DB$ Draw | NumCards$ 1 | Defined$ You | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 +K:Daybound +AlternateMode:DoubleFaced +DeckHas:Ability$Discard +Oracle:Suspicious Stowaway can't be blocked.\nWhenever Suspicious Stowaway deals combat damage to a player, draw a card, then discard a card.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Seafaring Werewolf +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:2/1 +K:Unblockable +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDrawN | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card, then discard a card. +SVar:TrigDrawN:DB$ Draw | NumCards$ 1 | Defined$ You | SubAbility$ DBDiscardN +SVar:DBDiscardN:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 +K:Nightbound +Oracle:Seafaring Werewolf can't be blocked.\nWhenever Seafaring Werewolf deals combat damage to a player, draw a card.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/tavern_ruffian_tavern_smasher.txt b/forge-gui/res/cardsfolder/upcoming/tavern_ruffian_tavern_smasher.txt new file mode 100644 index 00000000000..ae4afbe4f1f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tavern_ruffian_tavern_smasher.txt @@ -0,0 +1,17 @@ +Name:Tavern Ruffian +ManaCost:3 R +Types:Creature Human Warrior Werewolf +PT:2/5 +K:Daybound +AlternateMode:DoubleFaced +Oracle:Daybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Tavern Smasher +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:6/5 +K:Nightbound +Oracle:Nightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/the_celestus.txt b/forge-gui/res/cardsfolder/upcoming/the_celestus.txt new file mode 100644 index 00000000000..61021de9005 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_celestus.txt @@ -0,0 +1,15 @@ +Name:The Celestus +ManaCost:3 +Types:Legendary Artifact +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement + +A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. +A:AB$ DayTime | Cost$ 3 T | Value$ Switch | SorcerySpeed$ True | SpellDescription$ If it's night, it becomes day. Otherwise, it becomes night. Activate only as a sorcery. + +T:Mode$ DayTimeChanges | Execute$ DBGainLife | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, you gain 1 life. You may draw a card. If you do, discard a card. +SVar:DBGainLife:DB$ GainLife | LifeAmount$ 1 | SubAbility$ DBDiscard | StackDescription$ {p:You} gain 1 life. +SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 | UnlessCost$ Draw<1/You> | UnlessPayer$ You | UnlessSwitched$ True +DeckHas:Ability$LifeGain +Oracle:If it's neither day nor night, it becomes day as The Celestus enters the battlefield.\n{T}: Add one mana of any color.\n{3}, {T}: If it's night, it becomes day. Otherwise, it becomes night. Activate only as a sorcery.\nWhenever day becomes night or night becomes day, you gain 1 life. You may draw a card. If you do, discard a card. diff --git a/forge-gui/res/cardsfolder/upcoming/tireless_hauler_dire_strain_brawler.txt b/forge-gui/res/cardsfolder/upcoming/tireless_hauler_dire_strain_brawler.txt new file mode 100644 index 00000000000..3c1400d4531 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tireless_hauler_dire_strain_brawler.txt @@ -0,0 +1,19 @@ +Name:Tireless Hauler +ManaCost:4 G +Types:Creature Human Werewolf +PT:4/5 +K:Vigilance +K:Daybound +AlternateMode:DoubleFaced +Oracle:Vigilance\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Dire-Strain Brawler +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:6/6 +K:Vigilance +K:Nightbound +Oracle:Vigilance\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/tovolar_dire_overlord_tovolar_the_midnight_scourge.txt b/forge-gui/res/cardsfolder/upcoming/tovolar_dire_overlord_tovolar_the_midnight_scourge.txt new file mode 100644 index 00000000000..14987fc771d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tovolar_dire_overlord_tovolar_the_midnight_scourge.txt @@ -0,0 +1,32 @@ +Name:Tovolar, Dire Overlord +ManaCost:1 R G +Types:Legendary Creature Human Werewolf +PT:3/3 +T:Mode$ DamageDone | ValidSource$ Wolf.YouCtrl,Werewolf.YouCtrl | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDrawD | TriggerDescription$ Whenever a Wolf or Werewolf you control deals combat damage to a player, draw a card. +SVar:TrigDrawD:DB$ Draw | Defined$ You | NumCards$ 1 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigNight | TriggerDescription$ At the beginning of your upkeep, if you control three or more Wolves and/or Werewolves, it becomes night. Then transform any number of Human Werewolves you control. +SVar:TrigNight:DB$ DayTime | Value$ Night | SubAbility$ DBTransform +SVar:DBTransform:DB$ SetState | MinAmount$ 0 | Amount$ NumHumanWerewolves | Choices$ Human.Werewolf+YouCtrl+DoubleFaced | ChoiceTitle$ Choose any number of Human Werewolves you control | Mode$ Transform +K:Daybound +SVar:NumHumanWerewolves:Count$Valid Human.Werewolf+YouCtrl+DoubleFaced +SVar:Y:Count$Valid Wolf.YouCtrl +SVar:Z:Count$Valid Werewolf.YouCtrl +SVar:X:SVar$Y/Plus.Z +AlternateMode:DoubleFaced +DeckHints:Type$Wolf|Werewolf +Oracle:Whenever a Wolf or Werewolf you control deals combat damage to a player, draw a card.\nAt the beginning of your upkeep, if you control three or more Wolves and/or Werewolves, it becomes night. Then transform any number of Human Werewolves you control.\nDaybound + +ALTERNATE + +Name:Tovolar, the Midnight Scourge +ManaCost:no cost +Colors:red,green +Types:Legendary Creature Werewolf +PT:4/4 +T:Mode$ DamageDone | ValidSource$ Wolf.YouCtrl,Werewolf.YouCtrl | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDrawN | TriggerDescription$ Whenever a Wolf or Werewolf you control deals combat damage to a player, draw a card. +SVar:TrigDrawN:DB$ Draw | Defined$ You | NumCards$ 1 +A:AB$ Pump | Cost$ X R G | ValidTgts$ Wolf.YouCtrl,Werewolf.YouCtrl | TgtPrompt$ Select target Wolf or Werewolf you control | NumAtt$ X | KW$ Trample | SpellDescription$ Target Wolf or Werewolf you control gets +X/+0 and gains trample until end of turn. +K:Nightbound +SVar:X:Count$xPaid +DeckHints:Type$Wolf|Werewolf +Oracle:Whenever a Wolf or Werewolf you control deals combat damage to a player, draw a card.\n{X}{R}{G}: Target Wolf or Werewolf you control gets +X/+0 and gains trample until end of turn.\nNightbound diff --git a/forge-gui/res/cardsfolder/upcoming/tovolars_huntmaster_tovolars_packleader.txt b/forge-gui/res/cardsfolder/upcoming/tovolars_huntmaster_tovolars_packleader.txt new file mode 100644 index 00000000000..87a11aacc06 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tovolars_huntmaster_tovolars_packleader.txt @@ -0,0 +1,27 @@ +Name:Tovolar's Huntmaster +ManaCost:4 G G +Types:Creature Human Werewolf +PT:6/6 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create two 2/2 green Wolf creature tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ g_2_2_wolf +K:Daybound +AlternateMode:DoubleFaced +DeckHas:Ability$Token +Oracle:When Tovolar's Huntmaster enters the battlefield, create two 2/2 green Wolf creature tokens.\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Tovolar's Packleader +ManaCost:no cost +Colors:green +Types:Creature Werewolf +PT:7/7 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, create two 2/2 green Wolf creature tokens. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, create two 2/2 green Wolf creature tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ g_2_2_wolf +A:AB$ Pump | Cost$ 2 G G | ValidTgts$ Wolf.Other+YouCtrl,Werewolf.Other+YouCtrl | TgtPrompt$ Select another target Wolf or Werewolf you control | AILogic$ Fight | SubAbility$ DBFight | StackDescription$ {c:ThisTargetedCard} | SpellDescription$ Another target Wolf or Werewolf you control fights target creature you don't control. +SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | StackDescription$ fights {c:ThisTargetedCard}. +K:Nightbound +DeckHas:Ability$Token +DeckHints:Type$Wolf|Werewolf +Oracle:Whenever Tovolar's Packleader enters the battlefield or attacks, create two 2/2 green Wolf creature tokens.\n{2}{G}{G}: Another target Wolf or Werewolf you control fights target creature you don't control.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/unnatural_moonrise.txt b/forge-gui/res/cardsfolder/upcoming/unnatural_moonrise.txt new file mode 100644 index 00000000000..c4d102d5a1d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/unnatural_moonrise.txt @@ -0,0 +1,10 @@ +Name:Unnatural Moonrise +ManaCost: R G +Types:Sorcery +A:SP$ DayTime | Value$ Night | SubAbility$ DBPump | SpellDescription$ It becomes night. Until end of turn, target creature gets +1/+0 and gains trample and "Whenever this creature deals combat damage to a player, draw a card." +SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ 1 | KW$ Trample | SubAbility$ DBAnimate | StackDescription$ Until end of turn, {c:Targeted} gets +1/+0 and gains trample and "Whenever this creature deals combat damage to a player, draw a card." +SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Triggers$ DamageDraw | StackDescription$ None +SVar:DamageDraw:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever this creature deals combat damage to a player, draw a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 +K:Flashback:2 R G +Oracle:It becomes night. Until end of turn, target creature gets +1/+0 and gains trample and "Whenever this creature deals combat damage to a player, draw a card."\nFlashback {2}{R}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/upcoming/vadrik_astral_archmage.txt b/forge-gui/res/cardsfolder/upcoming/vadrik_astral_archmage.txt new file mode 100644 index 00000000000..d22279f1b86 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vadrik_astral_archmage.txt @@ -0,0 +1,14 @@ +Name:Vadrik, Astral Archmage +ManaCost:1 U R +Types:Legendary Creature Human Wizard +PT:1/2 +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. +SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB +SVar:ETB:DB$ InternalEtbReplacement +S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ X | Description$ Instant and sorcery spells you cast cost {X} less to cast, where X is NICKNAME's power. +SVar:X:Count$CardPower +T:Mode$ DayTimeChanges | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, put a +1/+1 counter on NICKNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHints:Type$Instant|Sorcery +DeckHas:Ability$Counters +Oracle:If it's neither day nor night, it becomes day as Vadrik, Astral Archmage enters the battlefield.\nInstant and sorcery spells you cast cost {X} less to cast, where X is Vadrik’s power.\nWhenever day becomes night or night becomes day, put a +1/+1 counter on Vadrik. diff --git a/forge-gui/res/cardsfolder/upcoming/village_watcher_village_reavers.txt b/forge-gui/res/cardsfolder/upcoming/village_watcher_village_reavers.txt new file mode 100644 index 00000000000..2a0ef447761 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/village_watcher_village_reavers.txt @@ -0,0 +1,20 @@ +Name:Village Watch +ManaCost:4 R +Types:Creature Human Werewolf +PT:4/3 +K:Haste +K:Daybound +AlternateMode:DoubleFaced +Oracle:Haste\nDaybound (If a player casts no spells during their own turn, it becomes night next turn.) + +ALTERNATE + +Name:Village Reavers +ManaCost:no cost +Colors:red +Types:Creature Werewolf +PT:5/4 +S:Mode$ Continuous | Affected$ Wolf.YouCtrl,Werewolf.YouCtrl | AddKeyword$ Haste | Description$ Wolves and Werewolves you control have haste. +K:Nightbound +DeckHints:Type$Wolf|Werewolf +Oracle:Wolves and Werewolves you control have haste.\nNightbound (If a player casts at least two spells during their own turn, it becomes day next turn.) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 04b455e82c4..9371237e14f 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -2714,3 +2714,6 @@ lblWildOpponentNumberError=Anzahl der Wild-Gegner kann nur 0 bis 3 sein lblGauntletProgress=Spießrutenlauf-Fortschritt #SpellAbility.java lblInvalidTargetSpecification=Nicht alle Zielbedingungen sind erfüllt. +#CPrompt.java +lblDay=Tag +lblNight=Nacht diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 28b7795f175..a3eb74727b2 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -2712,3 +2712,6 @@ lblWildOpponentNumberError=Wild Opponents can only be 0 to 3 lblGauntletProgress=Gauntlet Progress #SpellAbility.java lblInvalidTargetSpecification=Not all target requirements are met. +#CPrompt.java +lblDay=Day +lblNight=Night diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 3d5b46f2bf3..9aeff81d514 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -2712,3 +2712,6 @@ lblWildOpponentNumberError=Los oponentes salvajes sólo pueden ser de 0 a 3 lblGauntletProgress=Progreso del desafío #SpellAbility.java lblInvalidTargetSpecification=No se cumplen todos los requisitos de objetivos. +#CPrompt.java +lblDay=Día +lblNight=Noche diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index a51271a382d..80fd72eee0b 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -2711,3 +2711,6 @@ lblWildOpponentNumberError=Gli avversari a sorpresa possono essere da 0 a 3 lblGauntletProgress=Avanzamento della Sfida #SpellAbility.java lblInvalidTargetSpecification=Bersagli non validi. +#CPrompt.java +lblDay=Giorno +lblNight=Notte \ No newline at end of file diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 6f6b1b400e7..aa95771516f 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -2711,3 +2711,6 @@ lblWildOpponentNumberError=ワイルカード相手は 0~3 を設定してくだ lblGauntletProgress=ガントレット進行状況 #SpellAbility.java lblInvalidTargetSpecification=全ての対象制限を満たしていない。 +#CPrompt.java +lblDay=日 +lblNight=夜 diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 185bbb0f8e5..9c8b34c1e04 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -2713,3 +2713,6 @@ lblWildOpponentNumberError=野外对手数只能在0-3之间 lblGauntletProgress=决斗进度 #SpellAbility.java lblInvalidTargetSpecification=并非所有目标的要求都得到了满足。 +#CPrompt.java +lblDay=日 +lblNight=夜晚 diff --git a/forge-gui/res/skins/default/bg_day.jpg b/forge-gui/res/skins/default/bg_day.jpg new file mode 100644 index 00000000000..d3ccdaadd63 Binary files /dev/null and b/forge-gui/res/skins/default/bg_day.jpg differ diff --git a/forge-gui/res/skins/default/bg_night.jpg b/forge-gui/res/skins/default/bg_night.jpg new file mode 100644 index 00000000000..785f16ea66c Binary files /dev/null and b/forge-gui/res/skins/default/bg_night.jpg differ diff --git a/forge-gui/res/sound/daytime.mp3 b/forge-gui/res/sound/daytime.mp3 new file mode 100644 index 00000000000..ae9afd10927 Binary files /dev/null and b/forge-gui/res/sound/daytime.mp3 differ diff --git a/forge-gui/res/sound/nighttime.mp3 b/forge-gui/res/sound/nighttime.mp3 new file mode 100644 index 00000000000..961cf8f8fc7 Binary files /dev/null and b/forge-gui/res/sound/nighttime.mp3 differ diff --git a/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java index ad8514316d0..9458f34b700 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java @@ -44,6 +44,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { private final Map originalGameControllers = Maps.newHashMap(); private boolean gamePause = false; private boolean gameSpeed = false; + private String daytime = null; private boolean ignoreConcedeChain = false; public final boolean hasLocalPlayers() { @@ -62,6 +63,17 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { public final PlayerView getCurrentPlayer() { return currentPlayer; } + + @Override + public String getDayTime() { + return daytime; + } + + @Override + public void updateDayTime(String daytime) { + this.daytime = daytime; + } + @Override public final void setCurrentPlayer(PlayerView player) { player = TrackableTypes.PlayerViewType.lookup(player); //ensure we use the correct player @@ -772,6 +784,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { awaitNextInputTimer.cancel(); awaitNextInputTimer = null; } + daytime = null; } // End of Choice code } diff --git a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java index d516c513d13..d86b05d0d26 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java @@ -317,6 +317,7 @@ public class HostedMatch { humanController.getGui().afterGameEnd(); else if (!GuiBase.getInterface().isLibgdxPort()||!isMatchOver) humanController.getGui().afterGameEnd(); + humanController.getGui().updateDayTime(null); } humanControllers.clear(); } diff --git a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java index e534f17a42d..d1454fa33a5 100644 --- a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java @@ -465,6 +465,12 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { } } + @Override + public Void visit(final GameEventDayTimeChanged event) { + matchController.updateDayTime(event.daytime ? "Day" : "Night"); + return processEvent(); + } + @Override public Void visit(final GameEventManaPool event) { return processPlayer(event.player, manaPoolUpdate); diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index b036c422a60..65470871b60 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -173,8 +173,10 @@ public interface IGuiGame { void clearSelectables(); boolean isSelecting(); boolean isGamePaused(); - public void setgamePause(boolean pause); - public void setGameSpeed(boolean gameSpeed); + void setgamePause(boolean pause); + void setGameSpeed(boolean gameSpeed); + String getDayTime(); + void updateDayTime(String daytime); void awaitNextInput(); void cancelAwaitNextInput(); diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 4290e824b6d..792074b7a8d 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -128,6 +128,8 @@ public final class ForgeConstants { public static final String FONT_FILE = "font1.ttf"; public static final String SPLASH_BG_FILE = "bg_splash.png"; public static final String MATCH_BG_FILE = "bg_match.jpg"; + public static final String MATCH_BG_DAY_FILE = "bg_day.jpg"; + public static final String MATCH_BG_NIGHT_FILE = "bg_night.jpg"; public static final String TEXTURE_BG_FILE = "bg_texture.jpg"; public static final String SPACE_BG_FILE = "bg_space.png"; public static final String CHAOS_WHEEL_IMG_FILE = "bg_chaos_wheel.png"; diff --git a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java index 743bbdc9ab4..381ec7864fa 100644 --- a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java +++ b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java @@ -27,6 +27,8 @@ public enum FSkinProp { BG_SPLASH (null, PropType.BACKGROUND), BG_TEXTURE (null, PropType.BACKGROUND), BG_MATCH (null, PropType.BACKGROUND), + BG_DAY (null, PropType.BACKGROUND), + BG_NIGHT (null, PropType.BACKGROUND), //colors CLR_THEME (new int[] {70, 10}, PropType.COLOR), diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index 2e7bbfc3659..884a8871127 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -17,6 +17,7 @@ import forge.game.event.GameEventCardPhased; import forge.game.event.GameEventCardRegenerated; import forge.game.event.GameEventCardSacrificed; import forge.game.event.GameEventCardTapped; +import forge.game.event.GameEventDayTimeChanged; import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventGameOutcome; import forge.game.event.GameEventGameStarted; @@ -100,6 +101,10 @@ public class EventVisualizer extends IGameEventVisitor.Base imp @Override public SoundEffectType visit(final GameEventTokenCreated event) { return SoundEffectType.Token; } @Override + public SoundEffectType visit(final GameEventDayTimeChanged event) { + return event.daytime ? SoundEffectType.Daytime : SoundEffectType.Nighttime; + } + @Override public SoundEffectType visit(final GameEventBlockersDeclared event) { final boolean isLocalHuman = event.defendingPlayer.getLobbyPlayer() == player; if (isLocalHuman) { diff --git a/forge-gui/src/main/java/forge/sound/SoundEffectType.java b/forge-gui/src/main/java/forge/sound/SoundEffectType.java index a060050f040..b7f057c8645 100644 --- a/forge-gui/src/main/java/forge/sound/SoundEffectType.java +++ b/forge-gui/src/main/java/forge/sound/SoundEffectType.java @@ -50,6 +50,7 @@ public enum SoundEffectType { BlueLand("blue_land.mp3", false), Creature("creature.mp3", false), Damage("damage.mp3", true), + Daytime("daytime.mp3", true), Destroy("destroy.mp3", true), Discard("discard.mp3", false), Draw("draw.mp3", false), @@ -70,6 +71,7 @@ public enum SoundEffectType { LifeLoss("life_loss.mp3", true), LoseDuel("lose_duel.mp3", false), ManaBurn("mana_burn.mp3", false), + Nighttime("nighttime.mp3", true), OtherLand("other_land.mp3", false), Phasing("phasing.mp3", true), Planeswalker("planeswalker.mp3", false),