mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Add Rod of Absorption
This commit is contained in:
@@ -5,6 +5,7 @@ import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardTypeView;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -26,7 +27,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
|
||||
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Card source = sa.getHostCard();
|
||||
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
|
||||
@@ -95,7 +96,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if ("ReplaySpell".equals(logic)) {
|
||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||
} else if (logic.startsWith("NeedsChosenCard")) {
|
||||
int minCMC = 0;
|
||||
if (sa.getPayCosts().getCostMana() != null) {
|
||||
@@ -103,6 +104,23 @@ public class PlayAi extends SpellAbilityAi {
|
||||
}
|
||||
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
|
||||
} else if ("WithTotalCMC".equals(logic)) {
|
||||
// Try to play only when there are more than three playable cards.
|
||||
if (cards.size() < 3)
|
||||
return false;
|
||||
ManaCost mana = sa.getPayCosts().getTotalMana();
|
||||
if (mana.countX() > 0) {
|
||||
int amount = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
|
||||
return false;
|
||||
int totalCMC = 0;
|
||||
for (Card c : cards) {
|
||||
totalCMC += c.getCMC();
|
||||
}
|
||||
if (amount > totalCMC)
|
||||
amount = totalCMC;
|
||||
sa.setXManaCostPaid(amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
||||
@@ -117,7 +135,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* doTriggerAINoCost
|
||||
@@ -135,7 +153,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
@@ -164,6 +182,11 @@ public class PlayAi extends SpellAbilityAi {
|
||||
// timing restrictions still apply
|
||||
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
||||
continue;
|
||||
if (params != null && params.containsKey("CMCLimit")) {
|
||||
Integer cmcLimit = (Integer) params.get("CMCLimit");
|
||||
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
|
||||
if (!(spell instanceof SpellPermanent)) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -14,6 +17,7 @@ import com.google.common.collect.Lists;
|
||||
import forge.GameCommand;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -69,6 +73,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
int totalCMCLimit = Integer.MAX_VALUE;
|
||||
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
|
||||
}
|
||||
@@ -179,15 +185,46 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
amount = tgtCards.size();
|
||||
}
|
||||
|
||||
if (hasTotalCMCLimit) {
|
||||
totalCMCLimit = AbilityUtils.calculateAmount(source, sa.getParam("WithTotalCMC"), sa);
|
||||
}
|
||||
|
||||
if (controlledByPlayer != null) {
|
||||
activator.addController(controlledByTimeStamp, controlledByPlayer);
|
||||
}
|
||||
|
||||
boolean singleOption = tgtCards.size() == 1 && amount == 1 && optional;
|
||||
Map<String, Object> params = hasTotalCMCLimit ? new HashMap<>() : null;
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0 && totalCMCLimit >= 0) {
|
||||
if (hasTotalCMCLimit) {
|
||||
// filter out cars with mana value greater than limit
|
||||
Iterator<Card> it = tgtCards.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (c.isSplitCard()) {
|
||||
if (c.getState(CardStateName.LeftSplit).getManaCost().getCMC() <= totalCMCLimit)
|
||||
continue;
|
||||
if (c.getState(CardStateName.RightSplit).getManaCost().getCMC() <= totalCMCLimit)
|
||||
continue;
|
||||
it.remove();
|
||||
} else {
|
||||
if (c.getState(CardStateName.Original).getManaCost().getCMC() <= totalCMCLimit)
|
||||
continue;
|
||||
if (c.hasAlternateState() && c.getAlternateState().getManaCost().getCMC() <= totalCMCLimit)
|
||||
continue;
|
||||
// it.remove will only remove item from the list part of CardCollection
|
||||
tgtCards.asSet().remove(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (tgtCards.isEmpty())
|
||||
break;
|
||||
params.put("CMCLimit", totalCMCLimit);
|
||||
}
|
||||
|
||||
while (!tgtCards.isEmpty() && amount > 0) {
|
||||
activator.getController().tempShowCards(showCards);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, null);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), !singleOption && optional, params);
|
||||
activator.getController().endTempShowCards();
|
||||
if (tgtCard == null) {
|
||||
break;
|
||||
@@ -248,6 +285,14 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final String valid[] = {sa.getParam("ValidSA")};
|
||||
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
|
||||
}
|
||||
if (hasTotalCMCLimit) {
|
||||
Iterator<SpellAbility> it = sas.iterator();
|
||||
while (it.hasNext()) {
|
||||
SpellAbility s = it.next();
|
||||
if (s.getPayCosts().getTotalMana().getCMC() > totalCMCLimit)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (sas.isEmpty()) {
|
||||
continue;
|
||||
@@ -276,6 +321,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
tgtSA = tgtSA.copyWithNoManaCost();
|
||||
} else if (sa.hasParam("PlayCost")) {
|
||||
@@ -341,6 +388,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
amount--;
|
||||
totalCMCLimit -= tgtCMC;
|
||||
}
|
||||
|
||||
// Remove controlled by player if any
|
||||
|
||||
@@ -1688,7 +1688,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return exiledWith;
|
||||
}
|
||||
public final void setExiledWith(final Card e) {
|
||||
exiledWith = e;
|
||||
exiledWith = view.setCard(exiledWith, e, TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public final void cleanupExiledWith() {
|
||||
|
||||
@@ -599,6 +599,10 @@ public class CardView extends GameEntityView {
|
||||
return get(TrackableProperty.CloneOrigin);
|
||||
}
|
||||
|
||||
public CardView getExiledWith() {
|
||||
return get(TrackableProperty.ExiledWith);
|
||||
}
|
||||
|
||||
public FCollectionView<CardView> getImprintedCards() {
|
||||
return get(TrackableProperty.ImprintedCards);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ public enum TrackableProperty {
|
||||
UntilLeavesBattlefield(TrackableTypes.CardViewCollectionType),
|
||||
GainControlTargets(TrackableTypes.CardViewCollectionType),
|
||||
CloneOrigin(TrackableTypes.CardViewType),
|
||||
ExiledWith(TrackableTypes.CardViewType),
|
||||
|
||||
ImprintedCards(TrackableTypes.CardViewCollectionType),
|
||||
HauntedBy(TrackableTypes.CardViewCollectionType),
|
||||
|
||||
16
forge-gui/res/cardsfolder/upcoming/rod_of_absorption.txt
Normal file
16
forge-gui/res/cardsfolder/upcoming/rod_of_absorption.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
Name:Rod of Absorption
|
||||
ManaCost:2 U
|
||||
Types:Artifact
|
||||
T:Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Exile | Destination$ Any | TriggerZones$ Battlefield | Execute$ TrigForget | Static$ True
|
||||
SVar:TrigForget:DB$ Pump | ForgetObjects$ TriggeredCard
|
||||
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ DBCleanup | Static$ True
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | Execute$ TrigEffect | TriggerZones$ Battlefield | TriggerDescription$ Whenever a player casts an instant or sorcery spell, exile it instead of putting it into a graveyard as it resolves.
|
||||
SVar:TrigEffect:DB$ Effect | ReplacementEffects$ ReMoved | RememberObjects$ TriggeredCard
|
||||
SVar:ReMoved:Event$ Moved | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Graveyard | Fizzle$ False | ReplaceWith$ DBExile | Description$ Exile it instead of putting it into a graveyard as it resolves.
|
||||
SVar:DBExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile | SubAbility$ DBRemember
|
||||
SVar:DBRemember:DB$ Animate | Defined$ EffectSource | RememberObjects$ ReplacedCard | Duration$ Permanent | SubAbility$ ExileSelf
|
||||
SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self
|
||||
A:AB$ Play | Cost$ X T Sac<1/CARDNAME> | Defined$ ValidExile Card.IsRemembered | ValidSA$ Spell | Amount$ All | WithTotalCMC$ X | WithoutManaCost$ True | Optional$ True | AILogic$ WithTotalCMC | SpellDescription$ You may cast any number of spells from among cards exiled with CARDNAME with total mana value X or less without paying their mana costs.
|
||||
SVar:X:Count$xPaid
|
||||
Oracle:Whenever a player casts an instant or sorcery spell, exile it instead of putting it into a graveyard as it resolves.\n{X}, {T}, Sacrifice Rod of Absorption: You may cast any number of spells from among cards exiled with Rod of Absorption with total mana value X or less without paying their mana costs.
|
||||
Reference in New Issue
Block a user