mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 10:18:01 +00:00
- Added Chisei, Heart of Oceans , Dream Leash, Spinning Darkness, and Thought Lash
This commit is contained in:
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1764,6 +1764,7 @@ res/cardsfolder/c/chimeric_mass.txt svneol=native#text/plain
|
||||
res/cardsfolder/c/chimeric_sphere.txt -text
|
||||
res/cardsfolder/c/chimeric_staff.txt svneol=native#text/plain
|
||||
res/cardsfolder/c/chimney_imp.txt svneol=native#text/plain
|
||||
res/cardsfolder/c/chisei_heart_of_oceans.txt -text
|
||||
res/cardsfolder/c/chittering_rats.txt svneol=native#text/plain
|
||||
res/cardsfolder/c/chlorophant.txt -text
|
||||
res/cardsfolder/c/cho_arrim_alchemist.txt -text
|
||||
@@ -2923,6 +2924,7 @@ res/cardsfolder/d/dream_coat.txt -text svneol=unset#text/plain
|
||||
res/cardsfolder/d/dream_fighter.txt -text
|
||||
res/cardsfolder/d/dream_fracture.txt svneol=native#text/plain
|
||||
res/cardsfolder/d/dream_halls.txt -text
|
||||
res/cardsfolder/d/dream_leash.txt -text
|
||||
res/cardsfolder/d/dream_prowler.txt svneol=native#text/plain
|
||||
res/cardsfolder/d/dream_salvage.txt -text
|
||||
res/cardsfolder/d/dream_stalker.txt svneol=native#text/plain
|
||||
@@ -10399,6 +10401,7 @@ res/cardsfolder/s/spined_wurm.txt svneol=native#text/plain
|
||||
res/cardsfolder/s/spineless_thug.txt svneol=native#text/plain
|
||||
res/cardsfolder/s/spinerock_knoll.txt -text
|
||||
res/cardsfolder/s/spinneret_sliver.txt svneol=native#text/plain
|
||||
res/cardsfolder/s/spinning_darkness.txt -text
|
||||
res/cardsfolder/s/spiny_starfish.txt -text
|
||||
res/cardsfolder/s/spiraling_duelist.txt svneol=native#text/plain
|
||||
res/cardsfolder/s/spiraling_embers.txt svneol=native#text/plain
|
||||
@@ -11233,6 +11236,7 @@ res/cardsfolder/t/thought_devourer.txt svneol=native#text/plain
|
||||
res/cardsfolder/t/thought_eater.txt svneol=native#text/plain
|
||||
res/cardsfolder/t/thought_gorger.txt -text
|
||||
res/cardsfolder/t/thought_hemorrhage.txt -text svneol=unset#text/plain
|
||||
res/cardsfolder/t/thought_lash.txt -text
|
||||
res/cardsfolder/t/thought_nibbler.txt svneol=native#text/plain
|
||||
res/cardsfolder/t/thought_prison.txt -text svneol=unset#text/plain
|
||||
res/cardsfolder/t/thought_reflection.txt -text
|
||||
|
||||
11
res/cardsfolder/c/chisei_heart_of_oceans.txt
Normal file
11
res/cardsfolder/c/chisei_heart_of_oceans.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Chisei, Heart of Oceans
|
||||
ManaCost:2 U U
|
||||
Types:Legendary Creature Spirit
|
||||
PT:4/4
|
||||
K:Flying
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you remove a counter from a permanent you control.
|
||||
SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self | UnlessPayer$ You | UnlessCost$ RemoveAnyCounter<1/Permanent.YouCtrl/a permanent you control>
|
||||
#AI only removes negative counters
|
||||
SVar:RemAIDeck:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/chisei_heart_of_oceans.jpg
|
||||
Oracle:Flying\nAt the beginning of your upkeep, sacrifice Chisei, Heart of Oceans unless you remove a counter from a permanent you control.
|
||||
11
res/cardsfolder/d/dream_leash.txt
Normal file
11
res/cardsfolder/d/dream_leash.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Dream Leash
|
||||
ManaCost:3 U U
|
||||
Types:Enchantment Aura
|
||||
Text:You can't choose an untapped permanent as CARDNAME's target as you cast CARDNAME.
|
||||
K:Enchant permanent
|
||||
K:SpellCantTarget:Permanent.untapped
|
||||
A:SP$ Attach | Cost$ 3 U U | ValidTgts$ Permanent | AILogic$ GainControl
|
||||
S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted creature.
|
||||
SVar:PlayMain1:TRUE
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/dream_leash.jpg
|
||||
Oracle:Enchant permanent\nYou can't choose an untapped permanent as Dream Leash's target as you cast Dream Leash.\nYou control enchanted permanent.
|
||||
9
res/cardsfolder/s/spinning_darkness.txt
Normal file
9
res/cardsfolder/s/spinning_darkness.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Spinning Darkness
|
||||
ManaCost:4 B B
|
||||
Types:Instant
|
||||
SVar:AltCost:Cost$ ExileFromGrave<3/Card.Black+FromTopGrave> | Description$ You may exile the top three black cards of your graveyard rather than pay CARDNAME's mana cost.
|
||||
A:SP$ DealDamage | Cost$ 4 B B | ValidTgts$ Creature.nonBlack | TgtPrompt$ Select target nonblack creature | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to target nonblack creature. You gain 3 life.
|
||||
SVar:DBGainLife:DB$ GainLife | LifeAmount$ 3
|
||||
SVar:RemAIDeck:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/spinning_darkness.jpg
|
||||
Oracle:You may exile the top three black cards of your graveyard rather than pay Spinning Darkness's mana cost.\nSpinning Darkness deals 3 damage to target nonblack creature. You gain 3 life.
|
||||
11
res/cardsfolder/t/thought_lash.txt
Normal file
11
res/cardsfolder/t/thought_lash.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Thought Lash
|
||||
ManaCost:2 U U
|
||||
Types:Enchantment
|
||||
K:Cumulative upkeep:ExileFromTop<1/Card>:Exile the top card of your library.
|
||||
T:Mode$ PayCumulativeUpkeep | ValidCard$ Card.Self | Paid$ False | Execute$ TrigExileAll | TriggerDescription$ When a player doesn't pay CARDNAME's cumulative upkeep, that player exiles all cards from his or her library.
|
||||
SVar:TrigExileAll:AB$ ChangeZoneAll | Cost$ 0 | Origin$ Library | Destination$ Exile | ChangeType$ Card.YouCtrl
|
||||
A:AB$ PreventDamage | Cost$ ExileFromTop<1/Card> | Defined$ You | Amount$ 1 | SpellDescription$ Prevent the next 1 damage that would be dealt to you this turn.
|
||||
SVar:RemAIDeck:True
|
||||
SVar:RemRandomDeck:True
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/thought_lash.jpg
|
||||
Oracle:Cumulative upkeep-Exile the top card of your library. (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.)\nWhen a player doesn't pay Thought Lash's cumulative upkeep, that player exiles all cards from his or her library.\nExile the top card of your library: Prevent the next 1 damage that would be dealt to you this turn.
|
||||
@@ -2013,20 +2013,11 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|| keyword.startsWith("PreventAllDamageBy")
|
||||
|| keyword.startsWith("CantBlock")
|
||||
|| keyword.startsWith("CantBeBlockedBy")
|
||||
|| keyword.startsWith("CantEquip")) {
|
||||
|| keyword.startsWith("CantEquip")
|
||||
|| keyword.startsWith("SpellCantTarget")) {
|
||||
continue;
|
||||
}
|
||||
if (keyword.startsWith("CostChange")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (k.length > 8) {
|
||||
sbLong.append(k[8]).append("\r\n");
|
||||
}
|
||||
/*} else if (keyword.startsWith("AdjustLandPlays")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (k.length > 3) {
|
||||
sbLong.append(k[3]).append("\r\n");
|
||||
}*/
|
||||
} else if (keyword.startsWith("etbCounter")) {
|
||||
if (keyword.startsWith("etbCounter")) {
|
||||
final String[] p = keyword.split(":");
|
||||
final StringBuilder s = new StringBuilder();
|
||||
if (p.length > 4) {
|
||||
@@ -2345,18 +2336,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
// keyword descriptions
|
||||
for (int i = 0; i < kw.size(); i++) {
|
||||
final String keyword = kw.get(i);
|
||||
if (keyword.startsWith("CostChange")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (k.length > 8) {
|
||||
sb.append(k[8]).append("\r\n");
|
||||
}
|
||||
}
|
||||
/*if (keyword.startsWith("AdjustLandPlays")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (k.length > 3) {
|
||||
sb.append(k[3]).append("\r\n");
|
||||
}
|
||||
}*/
|
||||
if ((keyword.startsWith("Ripple") && !sb.toString().contains("Ripple"))
|
||||
|| (keyword.startsWith("Dredge") && !sb.toString().contains("Dredge"))
|
||||
|| (keyword.startsWith("Madness") && !sb.toString().contains("Madness"))
|
||||
@@ -7881,10 +7860,10 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
if (this.isPhasedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getSourceCard();
|
||||
|
||||
if (this.getKeyword() != null) {
|
||||
final Card source = sa.getSourceCard();
|
||||
|
||||
for (String kw : this.getKeyword()) {
|
||||
if (kw.equals("Shroud")) {
|
||||
return false;
|
||||
@@ -7941,6 +7920,15 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.isSpell() && source.hasStartOfKeyword("SpellCantTarget")) {
|
||||
final int keywordPosition = source.getKeywordPosition("SpellCantTarget");
|
||||
final String parse = source.getKeyword().get(keywordPosition).toString();
|
||||
final String[] k = parse.split(":");
|
||||
final String[] restrictions = k[1].split(",");
|
||||
if (this.isValid(restrictions, source.getController(), source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package forge.card.cost;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import forge.Card;
|
||||
@@ -155,10 +156,13 @@ public class CostExile extends CostPartWithList {
|
||||
final Player activator = ability.getActivatingPlayer();
|
||||
final Card source = ability.getSourceCard();
|
||||
final Game game = activator.getGame();
|
||||
String type = this.getType();
|
||||
|
||||
List<Card> typeList = new ArrayList<Card>();
|
||||
if (this.getType().equals("All")) {
|
||||
if (type.equals("All")) {
|
||||
return true; // this will always work
|
||||
} else if (type.contains("FromTopGrave")) {
|
||||
type = type.replace("FromTopGrave", "");
|
||||
}
|
||||
if (this.getFrom().equals(ZoneType.Stack)) {
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
@@ -172,7 +176,7 @@ public class CostExile extends CostPartWithList {
|
||||
}
|
||||
}
|
||||
if (!this.payCostFromSource()) {
|
||||
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source);
|
||||
typeList = CardLists.getValidCards(typeList, type.split(";"), activator, source);
|
||||
|
||||
final Integer amount = this.convertAmount();
|
||||
if ((amount != null) && (typeList.size() < amount)) {
|
||||
@@ -212,6 +216,13 @@ public class CostExile extends CostPartWithList {
|
||||
final Card source = ability.getSourceCard();
|
||||
Integer c = this.convertAmount();
|
||||
final Player activator = ability.getActivatingPlayer();
|
||||
String type = this.getType();
|
||||
boolean fromTopGrave = false;
|
||||
if (type.contains("FromTopGrave")) {
|
||||
type = type.replace("FromTopGrave", "");
|
||||
fromTopGrave = true;
|
||||
}
|
||||
|
||||
List<Card> list;
|
||||
|
||||
if (this.sameZone) {
|
||||
@@ -220,13 +231,13 @@ public class CostExile extends CostPartWithList {
|
||||
list = new ArrayList<Card>(activator.getCardsIn(this.getFrom()));
|
||||
}
|
||||
|
||||
if (this.getType().equals("All")) {
|
||||
if (type.equals("All")) {
|
||||
for (final Card card : list) {
|
||||
executePayment(ability, card);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
list = CardLists.getValidCards(list, this.getType().split(";"), activator, source);
|
||||
list = CardLists.getValidCards(list, type.split(";"), activator, source);
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
// Generalize this
|
||||
@@ -236,12 +247,11 @@ public class CostExile extends CostPartWithList {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (this.payCostFromSource())
|
||||
return activator.getZone(from).contains(source) && GuiDialog.confirm(source, source.getName() + " - Exile?") && executePayment(ability, source);
|
||||
|
||||
List<Card> validCards = CardLists.getValidCards(activator.getCardsIn(from), getType().split(";"), activator, source);
|
||||
List<Card> validCards = CardLists.getValidCards(activator.getCardsIn(from), type.split(";"), activator, source);
|
||||
if (this.from == ZoneType.Battlefield || this.from == ZoneType.Hand) {
|
||||
InputSelectCards inp = new InputSelectCardsFromList(c, c, validCards);
|
||||
inp.setMessage("Exile %d card(s) from your" + from );
|
||||
@@ -252,6 +262,7 @@ public class CostExile extends CostPartWithList {
|
||||
|
||||
if (this.from == ZoneType.Stack) return exileFromStack(ability, c);
|
||||
if (this.from == ZoneType.Library) return exileFromTop(ability, c);
|
||||
if (fromTopGrave) return exileFromTopGraveType(ability, c, validCards);
|
||||
if (!this.sameZone) return exileFromMiscZone(ability, c, validCards);
|
||||
|
||||
|
||||
@@ -404,11 +415,8 @@ public class CostExile extends CostPartWithList {
|
||||
}
|
||||
}
|
||||
|
||||
// Exile<Num/Type{/TypeDescription}>
|
||||
// ExileFromHand<Num/Type{/TypeDescription}>
|
||||
|
||||
// ExileFromGrave<Num/Type{/TypeDescription}>
|
||||
// ExileFromTop<Num/Type{/TypeDescription}> (of library)
|
||||
// ExileSameGrave<Num/Type{/TypeDescription}>
|
||||
|
||||
private boolean exileFromMiscZone(SpellAbility sa, int nNeeded, List<Card> typeList) {
|
||||
for (int i = 0; i < nNeeded; i++) {
|
||||
@@ -428,6 +436,18 @@ public class CostExile extends CostPartWithList {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean exileFromTopGraveType(SpellAbility sa, int nNeeded, List<Card> typeList) {
|
||||
Collections.reverse(typeList);
|
||||
for (int i = 0; i < nNeeded; i++) {
|
||||
if (typeList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final Card c = typeList.get(0);
|
||||
typeList.remove(c);
|
||||
executePayment(sa, c);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
|
||||
*/
|
||||
@@ -456,9 +476,10 @@ public class CostExile extends CostPartWithList {
|
||||
|
||||
if (this.getType().equals("All")) {
|
||||
return new PaymentDecision(new ArrayList<Card>(ai.getCardsIn(this.getFrom())));
|
||||
} else if (this.getType().contains("FromTopGrave")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Integer c = this.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(this.getAmount());
|
||||
|
||||
@@ -2,9 +2,12 @@ package forge.game.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CardPredicates.Presets;
|
||||
@@ -30,6 +33,7 @@ import forge.card.cost.CostPayLife;
|
||||
import forge.card.cost.CostPayment;
|
||||
import forge.card.cost.CostPutCardToLib;
|
||||
import forge.card.cost.CostPutCounter;
|
||||
import forge.card.cost.CostRemoveAnyCounter;
|
||||
import forge.card.cost.CostRemoveCounter;
|
||||
import forge.card.cost.CostReturn;
|
||||
import forge.card.cost.CostReveal;
|
||||
@@ -375,17 +379,23 @@ public class HumanPlay {
|
||||
else if (part instanceof CostPutCounter) {
|
||||
CounterType counterType = ((CostPutCounter) part).getCounter();
|
||||
int amount = getAmountFromPartX(part, source, sourceAbility);
|
||||
|
||||
if (false == source.canReceiveCounters(counterType)) {
|
||||
String message = String.format("Won't be able to pay upkeep for %s but it can't have %s counters put on it.", source, counterType.getName());
|
||||
p.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, message);
|
||||
return false;
|
||||
if (part.payCostFromSource()) {
|
||||
if (!source.canReceiveCounters(counterType)) {
|
||||
String message = String.format("Won't be able to pay upkeep for %s but it can't have %s counters put on it.", source, counterType.getName());
|
||||
p.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GuiDialog.confirm(source, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + source + "?"))
|
||||
return false;
|
||||
|
||||
source.addCounter(counterType, amount, false);
|
||||
} else {
|
||||
List<Card> list = p.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, part.getType().split(";"), p, source);
|
||||
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "add a counter." + orString);
|
||||
if(!hasPaid) return false;
|
||||
}
|
||||
|
||||
if (false == GuiDialog.confirm(source, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + source + "?"))
|
||||
return false;
|
||||
|
||||
source.addCounter(counterType, amount, false);
|
||||
}
|
||||
|
||||
else if (part instanceof CostRemoveCounter) {
|
||||
@@ -400,7 +410,57 @@ public class HumanPlay {
|
||||
|
||||
source.subtractCounter(counterType, amount);
|
||||
}
|
||||
|
||||
|
||||
else if (part instanceof CostRemoveAnyCounter) {
|
||||
int amount = getAmountFromPartX(part, source, sourceAbility);
|
||||
List<Card> list = new ArrayList<Card>(p.getCardsIn(ZoneType.Battlefield));
|
||||
int allCounters = 0;
|
||||
for (Card c : list) {
|
||||
final Map<CounterType, Integer> tgtCounters = c.getCounters();
|
||||
for (Integer value : tgtCounters.values()) {
|
||||
allCounters += value;
|
||||
}
|
||||
}
|
||||
if (allCounters < amount) return false;
|
||||
if (!GuiDialog.confirm(source, "Do you want to remove counters from " + part.getDescriptiveType() + " ?")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, ((CostRemoveAnyCounter) part).getType().split(";"), p, source);
|
||||
while (amount > 0) {
|
||||
final CounterType counterType;
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card card) {
|
||||
return card.hasCounters();
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) return false;
|
||||
InputSelectCards inp = new InputSelectCardsFromList(1, 1, list);
|
||||
inp.setMessage("Select a card to remove a counter");
|
||||
inp.setCancelAllowed(true);
|
||||
Singletons.getControl().getInputQueue().setInputAndWait(inp);
|
||||
if (inp.hasCancelled())
|
||||
continue;
|
||||
Card selected = inp.getSelected().get(0);
|
||||
final Map<CounterType, Integer> tgtCounters = selected.getCounters();
|
||||
final ArrayList<CounterType> typeChoices = new ArrayList<CounterType>();
|
||||
for (CounterType key : tgtCounters.keySet()) {
|
||||
if (tgtCounters.get(key) > 0) {
|
||||
typeChoices.add(key);
|
||||
}
|
||||
}
|
||||
if (typeChoices.size() > 1) {
|
||||
String prompt = "Select type counters to remove";
|
||||
counterType = GuiChoose.one(prompt, typeChoices);
|
||||
} else {
|
||||
counterType = typeChoices.get(0);
|
||||
}
|
||||
selected.subtractCounter(counterType, 1);
|
||||
amount--;
|
||||
}
|
||||
}
|
||||
|
||||
else if (part instanceof CostExile) {
|
||||
if ("All".equals(part.getType())) {
|
||||
if (false == GuiDialog.confirm(source, "Do you want to exile all cards in your graveyard?"))
|
||||
@@ -417,7 +477,16 @@ public class HumanPlay {
|
||||
final int nNeeded = getAmountFromPart(costPart, source, sourceAbility);
|
||||
if (list.size() < nNeeded)
|
||||
return false;
|
||||
|
||||
if (from == ZoneType.Library) {
|
||||
if (!GuiDialog.confirm(source, "Do you want to exile card(s) from you library?")) {
|
||||
return false;
|
||||
}
|
||||
list = list.subList(0, nNeeded);
|
||||
for (Card c : list) {
|
||||
p.getGame().getAction().exile(c);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// replace this with input
|
||||
for (int i = 0; i < nNeeded; i++) {
|
||||
final Card c = GuiChoose.oneOrNone("Exile from " + from, list);
|
||||
|
||||
Reference in New Issue
Block a user