mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 10:18:01 +00:00
Merge branch 'Card-Forge:master' into master
This commit is contained in:
@@ -12,16 +12,12 @@ import java.util.Map.Entry;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
@@ -2046,6 +2042,10 @@ public class AbilityUtils {
|
|||||||
return doXMath(ce == null ? 0 : getNumberOfTypes(ce), expr, c, ctb);
|
return doXMath(ce == null ? 0 : getNumberOfTypes(ce), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].contains("CardNumNotedTypes")) {
|
||||||
|
return doXMath(c.getNumNotedTypes(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].contains("CardNumColors")) {
|
if (sq[0].contains("CardNumColors")) {
|
||||||
return doXMath(c.getColor().countColors(), expr, c, ctb);
|
return doXMath(c.getColor().countColors(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -49,7 +50,17 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
|
|||||||
validTypes.addAll(CardType.getAllCardTypes());
|
validTypes.addAll(CardType.getAllCardTypes());
|
||||||
break;
|
break;
|
||||||
case "Creature":
|
case "Creature":
|
||||||
validTypes.addAll(CardType.getAllCreatureTypes());
|
if (sa.hasParam("TypesFromDefined")) {
|
||||||
|
for (final Card c : AbilityUtils.getDefinedCards(card, sa.getParam("TypesFromDefined"), sa)) {
|
||||||
|
for (String t : c.getType()) {
|
||||||
|
if (CardType.isACreatureType(t)) {
|
||||||
|
validTypes.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validTypes.addAll(CardType.getAllCreatureTypes());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "Basic Land":
|
case "Basic Land":
|
||||||
validTypes.addAll(CardType.getBasicTypes());
|
validTypes.addAll(CardType.getBasicTypes());
|
||||||
@@ -75,10 +86,17 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
|
|||||||
for (final String s : invalidTypes) {
|
for (final String s : invalidTypes) {
|
||||||
validTypes.remove(s);
|
validTypes.remove(s);
|
||||||
}
|
}
|
||||||
|
if (sa.hasParam("Note") && card.hasAnyNotedType()) {
|
||||||
|
for (String noted : card.getNotedTypes()) {
|
||||||
|
validTypes.remove(noted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
if (!validTypes.isEmpty()) {
|
if (validTypes.isEmpty() && sa.hasParam("Note")) {
|
||||||
|
// OK to end up with no choices/have nothing new to note
|
||||||
|
} else if (!validTypes.isEmpty()) {
|
||||||
for (final Player p : tgtPlayers) {
|
for (final Player p : tgtPlayers) {
|
||||||
String choice;
|
String choice;
|
||||||
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||||
@@ -89,7 +107,9 @@ public class ChooseTypeEffect extends SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes);
|
choice = p.getController().chooseSomeType(type, sa, validTypes, invalidTypes);
|
||||||
}
|
}
|
||||||
if (!sa.hasParam("ChooseType2")) {
|
if (sa.hasParam("Note")) {
|
||||||
|
card.addNotedType(choice);
|
||||||
|
} else if (!sa.hasParam("ChooseType2")) {
|
||||||
card.setChosenType(choice);
|
card.setChosenType(choice);
|
||||||
} else {
|
} else {
|
||||||
card.setChosenType2(choice);
|
card.setChosenType2(choice);
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import java.util.*;
|
|||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.append(player).append(" drafts a card from ").append(source.getName()).append("'s spellbook");
|
sb.append(player).append(" drafts a card from ").append(source.getName()).append("'s spellbook");
|
||||||
if (zone.equals("Hand")) {
|
if (zone.equals(ZoneType.Hand)) {
|
||||||
sb.append(".");
|
sb.append(".");
|
||||||
} else if (zone.equals("Battlefield")) {
|
} else if (zone.equals(ZoneType.Battlefield)) {
|
||||||
sb.append(" and puts it onto the battlefield.");
|
sb.append(" and puts it onto the battlefield.");
|
||||||
} else if (zone.equals("Exile")) {
|
} else if (zone.equals(ZoneType.Exile)) {
|
||||||
sb.append(", then exiles it.");
|
sb.append(sa.hasParam("ExileFaceDown") ? " and exiles it face down." : ", then exiles it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
@@ -73,6 +73,14 @@ import java.util.*;
|
|||||||
final CardZoneTable triggerList = new CardZoneTable();
|
final CardZoneTable triggerList = new CardZoneTable();
|
||||||
for (final Card c : drafted) {
|
for (final Card c : drafted) {
|
||||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||||
|
if (zone.equals(ZoneType.Exile)) {
|
||||||
|
source.addExiledCard(made);
|
||||||
|
made.setExiledWith(source);
|
||||||
|
made.setExiledBy(source.getController());
|
||||||
|
if (sa.hasParam("ExileFaceDown")) {
|
||||||
|
made.turnFaceDown(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
triggerList.put(ZoneType.None, made.getZone().getZoneType(), made);
|
triggerList.put(ZoneType.None, made.getZone().getZoneType(), made);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
private String originalText = "", text = "";
|
private String originalText = "", text = "";
|
||||||
private String chosenType = "";
|
private String chosenType = "";
|
||||||
private String chosenType2 = "";
|
private String chosenType2 = "";
|
||||||
|
private List<String> notedTypes = new ArrayList<>();
|
||||||
private List<String> chosenColors;
|
private List<String> chosenColors;
|
||||||
private String chosenName = "";
|
private String chosenName = "";
|
||||||
private String chosenName2 = "";
|
private String chosenName2 = "";
|
||||||
@@ -1745,6 +1746,29 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
return chosenType2 != null && !chosenType2.isEmpty();
|
return chosenType2 != null && !chosenType2.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final boolean hasAnyNotedType() {
|
||||||
|
return notedTypes != null && !notedTypes.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void addNotedType(final String type) {
|
||||||
|
notedTypes.add(type);
|
||||||
|
view.updateNotedTypes(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Iterable<String> getNotedTypes() {
|
||||||
|
if (notedTypes == null) {
|
||||||
|
return Lists.newArrayList();
|
||||||
|
}
|
||||||
|
return notedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getNumNotedTypes() {
|
||||||
|
if (notedTypes == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return notedTypes.size();
|
||||||
|
}
|
||||||
|
|
||||||
public final String getChosenColor() {
|
public final String getChosenColor() {
|
||||||
if (hasChosenColor()) {
|
if (hasChosenColor()) {
|
||||||
return chosenColors.get(0);
|
return chosenColors.get(0);
|
||||||
|
|||||||
@@ -359,6 +359,13 @@ public class CardView extends GameEntityView {
|
|||||||
set(TrackableProperty.ChosenType2, c.getChosenType2());
|
set(TrackableProperty.ChosenType2, c.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getNotedTypes() {
|
||||||
|
return get(TrackableProperty.NotedTypes);
|
||||||
|
}
|
||||||
|
void updateNotedTypes(Card c) {
|
||||||
|
set(TrackableProperty.NotedTypes, c.getNotedTypes());
|
||||||
|
}
|
||||||
|
|
||||||
public String getChosenNumber() {
|
public String getChosenNumber() {
|
||||||
return get(TrackableProperty.ChosenNumber);
|
return get(TrackableProperty.ChosenNumber);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public enum TrackableProperty {
|
|||||||
ShieldCount(TrackableTypes.IntegerType),
|
ShieldCount(TrackableTypes.IntegerType),
|
||||||
ChosenType(TrackableTypes.StringType),
|
ChosenType(TrackableTypes.StringType),
|
||||||
ChosenType2(TrackableTypes.StringType),
|
ChosenType2(TrackableTypes.StringType),
|
||||||
|
NotedTypes(TrackableTypes.StringListType),
|
||||||
ChosenColors(TrackableTypes.StringListType),
|
ChosenColors(TrackableTypes.StringListType),
|
||||||
ChosenCards(TrackableTypes.CardViewCollectionType),
|
ChosenCards(TrackableTypes.CardViewCollectionType),
|
||||||
ChosenNumber(TrackableTypes.StringType),
|
ChosenNumber(TrackableTypes.StringType),
|
||||||
|
|||||||
@@ -233,9 +233,9 @@ public class RewardScene extends UIScene {
|
|||||||
} else {
|
} else {
|
||||||
//immersive | no navigation and/or showing cutout cam
|
//immersive | no navigation and/or showing cutout cam
|
||||||
if (fW/fH > 2.3f)
|
if (fW/fH > 2.3f)
|
||||||
mul *= Forge.isLandscapeMode() ? 1.1f : 1.5f;
|
mul *= Forge.isLandscapeMode() ? 1.1f : 1.6f;
|
||||||
else if (fW/fH > 2f)
|
else if (fW/fH > 2f)
|
||||||
mul *= Forge.isLandscapeMode() ? 1.1f : 1.3f;
|
mul *= Forge.isLandscapeMode() ? 1.1f : 1.5f;
|
||||||
|
|
||||||
}
|
}
|
||||||
cardWidth = (cardHeight / CARD_WIDTH_TO_HEIGHT)*mul;
|
cardWidth = (cardHeight / CARD_WIDTH_TO_HEIGHT)*mul;
|
||||||
|
|||||||
@@ -353,21 +353,13 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb
|
|||||||
int y = Forge.getDeviceAdapter().getRealScreenSize(false).getRight();
|
int y = Forge.getDeviceAdapter().getRealScreenSize(false).getRight();
|
||||||
int realX = Forge.getDeviceAdapter().getRealScreenSize(true).getLeft();
|
int realX = Forge.getDeviceAdapter().getRealScreenSize(true).getLeft();
|
||||||
int realY = Forge.getDeviceAdapter().getRealScreenSize(true).getRight();
|
int realY = Forge.getDeviceAdapter().getRealScreenSize(true).getRight();
|
||||||
float fW = x > y ? x : y;
|
|
||||||
float fH = x > y ? y : x;
|
|
||||||
if (realX > x) {
|
if (realX > x) {
|
||||||
x *= 1.1f;
|
x *= 1.1f;
|
||||||
} else if (realY > y) {
|
} else if (realY > y) {
|
||||||
y *= 1.1f;
|
y *= 1.1f;
|
||||||
} else {
|
|
||||||
if (fW/fH > 2f) {
|
|
||||||
//immersive | no navigation and showing cutout cam
|
|
||||||
if (Forge.isLandscapeMode())
|
|
||||||
x *= 1.3f;
|
|
||||||
else
|
|
||||||
y *= 1.5f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
float fW = x > y ? x : y;
|
||||||
|
float fH = x > y ? y : x;
|
||||||
float mul = fW/fH < AR ? AR/(fW/fH) : (fW/fH)/AR;
|
float mul = fW/fH < AR ? AR/(fW/fH) : (fW/fH)/AR;
|
||||||
if (fW/fH >= 2f) {//tall display
|
if (fW/fH >= 2f) {//tall display
|
||||||
mul = (fW/fH) - ((fW/fH)/AR);
|
mul = (fW/fH) - ((fW/fH)/AR);
|
||||||
|
|||||||
10
forge-gui/res/cardsfolder/s/spelldrain_assassin.txt
Normal file
10
forge-gui/res/cardsfolder/s/spelldrain_assassin.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Name:Spelldrain Assassin
|
||||||
|
ManaCost:U B R
|
||||||
|
Types:Creature Vampire Assassin
|
||||||
|
PT:3/3
|
||||||
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, choose an instant or sorcery card in your hand. It perpetually gains casualty 2.
|
||||||
|
SVar:TrigChoose:DB$ ChooseCard | ChoiceZone$ Hand | Choices$ Sorcery.YouOwn,Instant.YouOwn | ChoiceTitle$ Choose an instant or sorcery card in your hand | Amount$ 1 | SubAbility$ DBEffect
|
||||||
|
SVar:DBEffect:DB$ Effect | StaticAbilities$ PerpetualCasualty | Name$ Spelldrain Assassin's Perpetual Effect | Duration$ Permanent | SubAbility$ DBCleanup
|
||||||
|
SVar:PerpetualCasualty:Mode$ Continuous | Affected$ Card.ChosenCard | AddKeyword$ Casualty:2 | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ The chosen card perpetually gains casualty 2.
|
||||||
|
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
|
||||||
|
Oracle:When Spelldrain Assassin enters the battlefield, choose an instant or sorcery card in your hand. It perpetually gains casualty 2.
|
||||||
11
forge-gui/res/cardsfolder/upcoming/celestial_vault.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/celestial_vault.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Name:Celestial Vault
|
||||||
|
ManaCost:1 W
|
||||||
|
Types:Artifact
|
||||||
|
A:AB$ Draft | Cost$ W T | Spellbook$ Angel of Destiny,Resplendent Angel,Angel of Vitality,Righteous Valkyrie,Angel of Invention,Angel of Sanctions,Valkyrie Harbinger,Emancipation Angel,Youthful Valkyrie,Resplendent Marshal,Enduring Angel,Sigardian Savior,Serra Angel,Stalwart Valkyrie,Segovian Angel | Zone$ Exile | ExileFaceDown$ True | RememberDrafted$ True | SpellDescription$ Draft a card from CARDNAME's spellbook and exile it face down.
|
||||||
|
A:AB$ ChangeZoneAll | Cost$ 1 Sac<1/CARDNAME> | Origin$ Exile | Destination$ Hand | ChangeType$ Card.IsRemembered+ExiledWithSource | SpellDescription$ Put each card exiled with CARDNAME into your hand.
|
||||||
|
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered+ExiledWithSource | Execute$ DBForget
|
||||||
|
SVar:DBForget:DB$ Pump | ForgetObjects$ TriggeredCard
|
||||||
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup
|
||||||
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
|
DeckHas:Type$Angel & Ability$LifeGain|Token|Counters
|
||||||
|
Oracle:{W}, {T}: Draft a card from Celestial Vault's spellbook and exile it face down.\n{1}, Sacrifice Celestial Vault: Put each card exiled with Celestial Vault into your hand.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Name:Kenku Artificer
|
Name:Kenku Artificer
|
||||||
ManaCost:2 G
|
ManaCost:2 U
|
||||||
Types:Creature Bird Artificer
|
Types:Creature Bird Artificer
|
||||||
PT:1/1
|
PT:1/1
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ Homunculus Servant — When CARDNAME enters the battlefield, put three +1/+1 counters on up to one target noncreature artifact. That artifact becomes a 0/0 Homunculus artifact creature with flying.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ Homunculus Servant — When CARDNAME enters the battlefield, put three +1/+1 counters on up to one target noncreature artifact. That artifact becomes a 0/0 Homunculus artifact creature with flying.
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Name:Volo, Itinerant Scholar
|
||||||
|
ManaCost:2 U
|
||||||
|
Types:Legendary Creature Human Wizard
|
||||||
|
PT:2/3
|
||||||
|
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When NICKNAME enters the battlefield, create Volo's Journal, a legendary colorless artifact token with hexproof and "Whenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact."
|
||||||
|
SVar:TrigToken:DB$ Token | TokenScript$ volos_journal
|
||||||
|
A:AB$ Pump | Cost$ 2 T | ValidTgts$ Permanent.namedVolo's Journal+YouCtrl | TgtPrompt$ Select target permanent you control named Volo's Journal | SubAbility$ DBDraw | StackDescription$ None | SpellDescription$ Draw a card for each creature type noted for target permanent you control named Volo's Journal.
|
||||||
|
SVar:DBDraw:DB$ Draw | NumCards$ X
|
||||||
|
SVar:X:Targeted$CardNumNotedTypes
|
||||||
|
K:Choose a Background
|
||||||
|
DeckHas:Ability$Token & Type$Artifact
|
||||||
|
Oracle:When Volo enters the battlefield, create Volo's Journal, a legendary colorless artifact token with hexproof and "Whenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact."\n{2}, {T}: Draw a card for each creature type noted for target permanent you control named Volo's Journal.\nChoose a Background
|
||||||
7
forge-gui/res/tokenscripts/volos_journal.txt
Normal file
7
forge-gui/res/tokenscripts/volos_journal.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Name:Volo's Journal
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Legendary Artifact
|
||||||
|
K:Hexproof
|
||||||
|
T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigNoteType | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact.
|
||||||
|
SVar:TrigNoteType:DB$ ChooseType | Type$ Creature | TypesFromDefined$ TriggeredCard | Note$ True
|
||||||
|
Oracle:Hexproof\nWhenever you cast a creature spell, note one of its creature types that hasn't been noted for this artifact.
|
||||||
@@ -419,6 +419,16 @@ public class CardDetailUtil {
|
|||||||
area.append(")");
|
area.append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noted types
|
||||||
|
if (card.getNotedTypes() != null && !card.getNotedTypes().isEmpty()) {
|
||||||
|
if (area.length() != 0) {
|
||||||
|
area.append("\n");
|
||||||
|
}
|
||||||
|
area.append("(noted type").append(card.getNotedTypes().size() == 1 ? ": " : "s: ");
|
||||||
|
area.append(Lang.joinHomogenous(card.getNotedTypes()));
|
||||||
|
area.append(")");
|
||||||
|
}
|
||||||
|
|
||||||
// chosen color
|
// chosen color
|
||||||
if (card.getChosenColors() != null && !card.getChosenColors().isEmpty()) {
|
if (card.getChosenColors() != null && !card.getChosenColors().isEmpty()) {
|
||||||
if (area.length() != 0) {
|
if (area.length() != 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user