mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-11 16:26:22 +00:00
@@ -6,10 +6,12 @@ import java.util.Map;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class PermanentEffect extends SpellAbilityEffect {
|
||||
@@ -28,24 +30,37 @@ public class PermanentEffect extends SpellAbilityEffect {
|
||||
final Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
final CardZoneTable table = AbilityKey.addCardZoneTableParams(moveParams, sa);
|
||||
|
||||
if ((sa.isIntrinsic() || host.wasCast()) && sa.isSneak()) {
|
||||
host.setTapped(true);
|
||||
}
|
||||
|
||||
final Card c = game.getAction().moveToPlay(host, sa, moveParams);
|
||||
sa.setHostCard(c);
|
||||
|
||||
// CR 608.3g
|
||||
if (sa.isIntrinsic() || c.wasCast()) {
|
||||
if (sa.isDash() && c.isInPlay()) {
|
||||
if ((sa.isIntrinsic() || c.wasCast()) && c.isInPlay()) {
|
||||
if (sa.isDash()) {
|
||||
registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c));
|
||||
// add AI hint
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Dash"), c.getGame().getNextTimestamp(), 0);
|
||||
}
|
||||
if (sa.isBlitz() && c.isInPlay()) {
|
||||
if (sa.isBlitz()) {
|
||||
registerDelayedTrigger(sa, "Sacrifice", Lists.newArrayList(c));
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Blitz"), c.getGame().getNextTimestamp(), 0);
|
||||
}
|
||||
if (sa.isWarp() && c.isInPlay()) {
|
||||
if (sa.isWarp()) {
|
||||
registerDelayedTrigger(sa, "Exile", Lists.newArrayList(c));
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Warp"), c.getGame().getNextTimestamp(), 0);
|
||||
}
|
||||
if (sa.isSneak() && c.isCreature()) {
|
||||
final Card returned = sa.getPaidList("Returned", true).getFirst();
|
||||
final GameEntity defender = game.getCombat().getDefenderByAttacker(returned);
|
||||
game.getCombat().addAttacker(c, defender);
|
||||
game.getCombat().getBandOfAttacker(c).setBlocked(false);
|
||||
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
}
|
||||
}
|
||||
|
||||
table.triggerChangesZoneAll(game, sa);
|
||||
|
||||
@@ -3288,19 +3288,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
} else if (keyword.startsWith("Starting intensity")) {
|
||||
sbAfter.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Escalate") || keyword.startsWith("Buyback")
|
||||
|| keyword.startsWith("Freerunning") || keyword.startsWith("Prowl")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
final Cost cost = new Cost(manacost, false);
|
||||
|
||||
StringBuilder sbCost = new StringBuilder(k[0]);
|
||||
if (!cost.isOnlyManaCost()) {
|
||||
sbCost.append("—");
|
||||
} else {
|
||||
sbCost.append(" ");
|
||||
}
|
||||
sbCost.append(cost.toSimpleString());
|
||||
sbBefore.append(sbCost).append(" (").append(inst.getReminderText()).append(")");
|
||||
|| keyword.startsWith("Freerunning") || keyword.startsWith("Prowl")
|
||||
|| keyword.startsWith("Sneak") || keyword.startsWith("Cleave")) {
|
||||
sbBefore.append(formatKeywordWithCost(inst));
|
||||
sbBefore.append("\r\n");
|
||||
} else if (keyword.startsWith("Multikicker")) {
|
||||
final String[] n = keyword.split(":");
|
||||
@@ -3329,22 +3319,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Disturb") || keyword.startsWith("Overload")
|
||||
|| keyword.startsWith("Plot") || keyword.startsWith("Mayhem")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost mCost;
|
||||
if (k.length < 2 || "ManaCost".equals(k[1])) {
|
||||
mCost = new Cost(getManaCost(), false);
|
||||
} else {
|
||||
mCost = new Cost(k[1], false);
|
||||
}
|
||||
|
||||
StringBuilder sbCost = new StringBuilder(k[0]);
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbCost.append("—");
|
||||
} else {
|
||||
sbCost.append(" ");
|
||||
}
|
||||
sbCost.append(mCost.toSimpleString());
|
||||
sbAfter.append(sbCost).append(" (").append(inst.getReminderText()).append(")");
|
||||
sbAfter.append(formatKeywordWithCost(inst));
|
||||
sbAfter.append("\r\n");
|
||||
} else if (keyword.equals("Gift")) {
|
||||
sbBefore.append(keyword);
|
||||
@@ -3456,6 +3431,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return sb;
|
||||
}
|
||||
|
||||
private String formatKeywordWithCost(final KeywordInterface inst) {
|
||||
final String keyword = inst.getOriginal();
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost mCost;
|
||||
if (k.length < 2 || "ManaCost".equals(k[1])) {
|
||||
mCost = new Cost(getManaCost(), false);
|
||||
} else {
|
||||
mCost = new Cost(k[1], false);
|
||||
}
|
||||
|
||||
StringBuilder sbCost = new StringBuilder(k[0]);
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbCost.append("—");
|
||||
} else {
|
||||
sbCost.append(" ");
|
||||
}
|
||||
sbCost.append(mCost.toSimpleString());
|
||||
return sbCost + " (" + inst.getReminderText() + ")";
|
||||
}
|
||||
|
||||
private String formatSpellAbility(final SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(sa.toString()).append("\r\n\r\n");
|
||||
|
||||
@@ -3621,6 +3621,31 @@ public class CardFactoryUtil {
|
||||
sa.setSVar("ScavengeX", "Exiled$CardPower");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Sneak")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manaCost = k[1];
|
||||
final Cost webCost = new Cost(manaCost + " Return<1/Creature.attacking+unblocked/unblocked attacker>", false);
|
||||
|
||||
final SpellAbility newSA = card.getFirstSpellAbilityWithFallback().copyWithManaCostReplaced(host.getController(), webCost);
|
||||
|
||||
if (k.length > 2) {
|
||||
newSA.getMapParams().put("ValidAfterStack", k[2]);
|
||||
}
|
||||
|
||||
final StringBuilder desc = new StringBuilder();
|
||||
desc.append("Sneak ").append(ManaCostParser.parse(manaCost)).append(" (");
|
||||
desc.append(inst.getReminderText());
|
||||
desc.append(")");
|
||||
|
||||
newSA.setDescription(desc.toString());
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(card.getName()).append(" (Sneak)");
|
||||
newSA.setStackDescription(sb.toString());
|
||||
newSA.putParam("Secondary", "True");
|
||||
newSA.setAlternativeCost(AlternativeCost.Sneak);
|
||||
newSA.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(newSA);
|
||||
} else if (keyword.startsWith("Station")) {
|
||||
String effect = "AB$ PutCounter | Cost$ tapXType<1/Creature.Other> | Defined$ Self " +
|
||||
"| CounterType$ CHARGE | CounterNum$ StationX | SorcerySpeed$ True " +
|
||||
|
||||
@@ -168,10 +168,11 @@ public enum Keyword {
|
||||
RIOT("Riot", SimpleKeyword.class, false, "This creature enters with your choice of a +1/+1 counter or haste."),
|
||||
RIPPLE("Ripple", KeywordWithAmount.class, false, "When you cast this spell, you may reveal the top {%d:card} of your library. You may cast any of those cards with the same name as this spell without paying their mana costs. Put the rest on the bottom of your library in any order."),
|
||||
SADDLE("Saddle", KeywordWithAmount.class, false, "Tap any number of other creatures you control with total power %1$d or more: This Mount becomes saddled until end of turn. Saddle only as a sorcery."),
|
||||
SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."),
|
||||
SHADOW("Shadow", SimpleKeyword.class, true, "This creature can block or be blocked by only creatures with shadow."),
|
||||
SHROUD("Shroud", SimpleKeyword.class, true, "This can't be the target of spells or abilities."),
|
||||
SKULK("Skulk", SimpleKeyword.class, true, "This creature can't be blocked by creatures with greater power."),
|
||||
SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."),
|
||||
SNEAK("Sneak", KeywordWithCost.class, false, "You may cast this spell for %s if you also return an unblocked attacker you control to hand during the declare blockers step."),
|
||||
SOULBOND("Soulbond", SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters. They remain paired for as long as you control both of them."),
|
||||
SOULSHIFT("Soulshift", KeywordWithAmount.class, false, "When this creature dies, you may return target Spirit card with mana value %d or less from your graveyard to your hand."),
|
||||
SPACE_SCULPTOR("Space sculptor", SimpleKeyword.class, true, "CARDNAME divides the battlefield into alpha, beta, and gamma sectors. If a creature isn't assigned to a sector, its controller assigns it to one. Opponents assign first."),
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum AlternativeCost {
|
||||
Overload,
|
||||
Prowl,
|
||||
Plotted,
|
||||
Sneak,
|
||||
Spectacle,
|
||||
Surge,
|
||||
Warp,
|
||||
|
||||
@@ -686,6 +686,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return isAlternativeCost(AlternativeCost.Prowl);
|
||||
}
|
||||
|
||||
public final boolean isSneak() {
|
||||
return isAlternativeCost(AlternativeCost.Sneak);
|
||||
}
|
||||
|
||||
public final boolean isSurged() {
|
||||
return isAlternativeCost(AlternativeCost.Surge);
|
||||
}
|
||||
|
||||
@@ -337,6 +337,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (sa.isSneak()) {
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user