From 8fe39bca9b436b5bb40acdf6fc67deee412749ea Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Sat, 20 Apr 2024 13:32:27 -0400 Subject: [PATCH] Discord, Lord of Disharmony + Support (#5073) * Discord, Lord of Disharmony + Support * Prevent AI use for now. * Include chosen name's description in text box * Clean up DescriptionFromChosenName * Remove blank lines from Discord card script --------- Co-authored-by: Jetz --- .../src/main/java/forge/game/ForgeScript.java | 29 +++++++++++++++++++ .../game/ability/effects/PlayEffect.java | 14 +++++++-- .../src/main/java/forge/game/card/Card.java | 15 ++++++++++ .../d/discord_lord_of_disharmony.txt | 19 ++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 forge-gui/res/cardsfolder/d/discord_lord_of_disharmony.txt diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 089047a5607..7f12761047f 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -19,6 +19,7 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.TargetChoices; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityCastWithFlash; import forge.game.trigger.Trigger; import forge.game.zone.ZoneType; import forge.util.Expressions; @@ -388,6 +389,34 @@ public class ForgeScript { if (sa.isManaAbilityFor(paidFor, colorCanUse)) { return false; } + } else if(property.equals("NamedSpell")) { + boolean found = false; + for (String name : source.getNamedCards()) { + if (sa.cardState.getName().equals(name)) { + found = true; + break; + } + } + return found; + } + else if (property.equals("CouldCastTiming")) { + Card host = sa.getHostCard(); + Game game = host.getGame(); + if (game.getStack().isSplitSecondOnStack()) { + return false; + } + // Adapted from SpellAbility.canCastTiming, to determine if the SA could be cast at the current timing (assuming the controller had priority). + + if (sourceController.canCastSorcery() || sa.getRestrictions().isInstantSpeed()) { + return true; + } + if (sa.isSpell()) { + return host.isInstant() || host.hasKeyword(Keyword.FLASH) || StaticAbilityCastWithFlash.anyWithFlash(sa, host, sourceController); + } + if (sa.isActivatedAbility()) { + return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed(); + } + return true; } else if (sa.getHostCard() != null) { return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 8a93fcfccf6..cf24167532d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -160,16 +160,23 @@ public class PlayEffect extends SpellAbilityEffect { return; } } else if (sa.hasParam("CopyFromChosenName")) { - String name = controller.getNamedCard(); - if (name.trim().isEmpty()) return; + String name = source.getNamedCard(); + if (name.trim().isEmpty()) { + name = controller.getNamedCard(); + if(name.trim().isEmpty()) { + return; + } + } Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), controller); // so it gets added to stack card.setCopiedPermanent(card); + // Keeps adventures from leaving the recast effect + card.setCopiedSpell(true); card.setToken(true); tgtCards = new CardCollection(card); } else { tgtCards = new CardCollection(); - // filter only cards that didn't changed zones + // filter only cards that didn't change zones for (Card c : getTargetCards(sa)) { Card gameCard = game.getCardState(c, null); if (c.equalsWithGameTimestamp(gameCard)) { @@ -280,6 +287,7 @@ public class PlayEffect extends SpellAbilityEffect { tgtCard.setZone(zone); // to fix the CMC tgtCard.setCopiedPermanent(original); + tgtCard.setCopiedSpell(true); if (zone != null) { zone.add(tgtCard); } 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 ba4db6ea0ec..ba523475fa0 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2824,6 +2824,21 @@ public class Card extends GameEntity implements Comparable, IHasSVars { sAbility = sbSA.toString(); } else if (sa.isSpell() && sa.isBasicSpell()) { continue; + } else if (sa.hasParam("DescriptionFromChosenName") && !getNamedCard().isEmpty()) { + String name = getNamedCard(); + ICardFace namedFace = StaticData.instance().getCommonCards().getFaceByName(name); + StringBuilder sbSA = new StringBuilder(sAbility); + sbSA.append(linebreak); + sbSA.append(Localizer.getInstance().getMessage("lblSpell")); + sbSA.append(" — "); + if(!namedFace.getManaCost().isNoCost()) { + sbSA.append(namedFace.getManaCost().getSimpleString()).append(": "); + } + sbSA.append(namedFace.getName()).append("\r\n"); + sbSA.append(namedFace.getType()).append("\r\n"); + sbSA.append(namedFace.getOracleText().replaceAll("\\\\n", "\r\n")); + sbSA.append(linebreak); + sAbility = sbSA.toString(); } if (sa.getManaPart() != null) { diff --git a/forge-gui/res/cardsfolder/d/discord_lord_of_disharmony.txt b/forge-gui/res/cardsfolder/d/discord_lord_of_disharmony.txt new file mode 100644 index 00000000000..597f47ff5db --- /dev/null +++ b/forge-gui/res/cardsfolder/d/discord_lord_of_disharmony.txt @@ -0,0 +1,19 @@ +Name:Discord, Lord of Disharmony +ManaCost:2 B R +Types:Legendary Creature Chimera +PT:3/5 +K:Flying +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ ChooseName | TriggerDescription$ At the beginning of your end step, choose a random nonland Magic card name. Until your next end step, you may cast a copy of a spell with that name, and mana of any type can be spent to cast it. If you cast a spell this way, copy this ability if NICKNAME is on the battlefield. +SVar:ChooseName:DB$ NameCard | ValidCards$ Card.nonLand | Defined$ You | AtRandom$ True | SubAbility$ CreateAbility +SVar:CreateAbility:DB$ Effect | Abilities$ DiscordCast | Triggers$ TrigHostLeaves | Duration$ UntilYourNextEndStep | ImprintCards$ OriginalHost.inZoneBattlefield | SubAbility$ CleanupName +SVar:CleanupName:DB$ Cleanup | ClearNamedCard$ True +SVar:DiscordCast:ST$ Play | ActivationZone$ Command | Cost$ 0 | CopyFromChosenName$ True | ManaConversion$ AnyType->AnyColor | ValidSA$ Spell.CouldCastTiming+NamedSpell | Optional$ True | RememberPlayed$ True | SubAbility$ CheckCast | DescriptionFromChosenName$ True | SpellDescription$ Cast a copy of the spell with the chosen name. If NICKNAME is on the battlefield, copy its ability. +SVar:WasCast:Remembered$Valid Card +SVar:CheckCast:DB$ Branch | BranchConditionSVar$ WasCast | TrueSubAbility$ ExileAbility +SVar:ExileAbility:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile | SubAbility$ CleanupMemory +SVar:CleanupMemory:DB$ Cleanup | ClearRemembered$ True | ClearNamedCard$ True | SubAbility$ MaybeRepeat +SVar:MaybeRepeat:DB$ ImmediateTrigger | ConditionPresent$ Card | ConditionDefined$ Imprinted | Execute$ ChooseName | TriggerDescription$ Choose a random nonland Magic card name. Until your next end step, you may cast a copy of a spell with that name, and mana of any type can be spent to cast it. If you cast a spell this way, copy this ability if NICKNAME is on the battlefield. +SVar:TrigHostLeaves:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.IsImprinted | TriggerZones$ Command | Execute$ OnHostLost +SVar:OnHostLost:DB$ Cleanup | ClearImprinted$ True +AI:RemoveDeck:All +Oracle:Flying\nAt the beginning of your end step, choose a random nonland Magic card name. Until your next end step, you may cast a copy of a spell with that name, and mana of any type can be spent to cast it. If you cast a spell this way, copy this ability if Discord is on the battlefield.