- Implemented <SubType> Offering ability from BOK.

- Added Patron of the Akki, Patron of the Kitsune, Patron of the Moon and Patron of the Orochi
This commit is contained in:
moomarc
2013-05-31 17:20:41 +00:00
parent 913ba50b58
commit ebce943f38
21 changed files with 249 additions and 16 deletions

4
.gitattributes vendored
View File

@@ -7827,6 +7827,10 @@ res/cardsfolder/p/patriarchs_desire.txt -text svneol=unset#text/plain
res/cardsfolder/p/patricians_scorn.txt -text svneol=unset#text/plain
res/cardsfolder/p/patrol_hound.txt -text
res/cardsfolder/p/patrol_signaler.txt svneol=native#text/plain
res/cardsfolder/p/patron_of_the_akki.txt -text
res/cardsfolder/p/patron_of_the_kitsune.txt -text
res/cardsfolder/p/patron_of_the_moon.txt -text
res/cardsfolder/p/patron_of_the_orochi.txt -text
res/cardsfolder/p/patron_of_the_wild.txt svneol=native#text/plain
res/cardsfolder/p/patron_wizard.txt svneol=native#text/plain
res/cardsfolder/p/pattern_of_rebirth.txt svneol=native#text/plain

View File

@@ -0,0 +1,11 @@
Name:Patron of the Akki
ManaCost:4 R R
Types:Legendary Creature Spirit
PT:5/5
K:Goblin offering
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, creatures you control get +2/+0 until end of turn.
SVar:TrigPump:AB$ PumpAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | NumAtt$ 2
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/patron_of_the_akki.jpg
Oracle:Goblin offering (You may cast this card any time you could cast an instant by sacrificing a Goblin and paying the difference in mana costs between this and the sacrificed Goblin. Mana cost includes color.)\nWhenever Patron of the Akki attacks, creatures you control get +2/+0 until end of turn.
SetInfo:BOK Rare

View File

@@ -0,0 +1,11 @@
Name:Patron of the Kitsune
ManaCost:4 W W
Types:Legendary Creature Spirit
PT:5/6
K:Fox offering
T:Mode$ Attacks | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ PatronLife | OptionalDecider$ You | TriggerDescription$ Whenever a creature attacks, you gain 1 life.
SVar:PatronLife:AB$ GainLife | Cost$ 0 | LifeAmount$ 1
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/patron_of_the_kitsune.jpg
Oracle:Fox offering (You may cast this card any time you could cast an instant by sacrificing a Fox and paying the difference in mana costs between this and the sacrificed Fox. Mana cost includes color.)\nWhenever a creature attacks, you may gain 1 life.
SetInfo:BOK Rare

View File

@@ -0,0 +1,11 @@
Name:Patron of the Moon
ManaCost:5 U U
Types:Legendary Creature Spirit
PT:5/4
K:Moonfolk offering
K:Flying
A:AB$ ChangeZone | Cost$ 1 | ChangeNum$ 2 | Origin$ Hand | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.YouCtrl | SpellDescription$ Put up to two land cards from your hand onto the battlefield tapped.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/patron_of_the_moon.jpg
Oracle:Moonfolk offering (You may cast this card any time you could cast an instant by sacrificing a Moonfolk and paying the difference in mana costs between this and the sacrificed Moonfolk. Mana cost includes color.)\nFlying\n{1}: Put up to two land cards from your hand onto the battlefield tapped.
SetInfo:BOK Rare

View File

@@ -0,0 +1,10 @@
Name:Patron of the Orochi
ManaCost:6 G G
Types:Legendary Creature Spirit
PT:7/7
K:Snake offering
A:AB$ UntapAll | Cost$ T | ValidCards$ Forest,Creature.Green | ActivationLimit$ 1 | SpellDescription$ Untap all Forests and all green creatures. Activate this ability only once each turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/patron_of_the_orochi.jpg
Oracle:Snake offering (You may cast this card any time you could cast an instant by sacrificing a Snake and paying the difference in mana costs between this and the sacrificed Snake. Mana cost includes color.)\n{T}: Untap all Forests and all green creatures. Activate this ability only once each turn.
SetInfo:BOK Rare

View File

@@ -2149,6 +2149,17 @@ public class Card extends GameEntity implements Comparable<Card> {
sb.append("\r\n");
}
sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)");
} else if (keyword.endsWith(" offering")) {
String offeringType = keyword.split(" ")[0];
if (sb.length() != 0) {
sb.append("\r\n");
}
sbLong.append(keywords.get(i));
sbLong.append(" (You may cast this card any time you could cast an instant by sacrificing a ");
sbLong.append(offeringType);
sbLong.append("and paying the difference in mana costs between this and the sacrificed ");
sbLong.append(offeringType);
sbLong.append(". Mana cost includes color.)");
} else if (keyword.startsWith("Soulbond")) {
sbLong.append(keywords.get(i));
sbLong.append(" (You may pair this creature ");
@@ -2429,6 +2440,17 @@ public class Card extends GameEntity implements Comparable<Card> {
sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3);
}
sb.append("Convoke (Each creature you tap while casting this spell reduces its cost by 1 or by one mana of that creature's color.)\r\n");
} else if (keyword.endsWith(" offering")) {
if (sb.toString().endsWith("\r\n\r\n")) {
sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3);
}
String offeringType = keyword.split(" ")[0];
sb.append(keyword);
sb.append(" (You may cast this card any time you could cast an instant by sacrificing a ");
sb.append(offeringType);
sb.append("and paying the difference in mana costs between this and the sacrificed ");
sb.append(offeringType);
sb.append(". Mana cost includes color.)");
} else if (keyword.equals("Remove CARDNAME from your deck before playing if you're not playing for ante.")) {
if (sb.toString().endsWith("\r\n\r\n")) {
sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3);

View File

@@ -61,7 +61,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()));
} else {
boolean isOptional = sa.hasParam("Optional");
choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, valid, amount, sa, destroy, isOptional);
choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, valid, amount, sa, destroy, isOptional, false);
}
for(Card sac : choosenToSacrifice) {

View File

@@ -129,6 +129,10 @@ public class CostPartMana extends CostPart {
InputPayMana inpPayment;
toPay.applySpellCostChange(ability);
if (ability.isOffering() && ability.getSacrificedAsOffering() == null) {
System.out.println("Sacrifice input for Offering cancelled");
return false;
}
if (!toPay.isPaid()) {
inpPayment = new InputPayManaOfCostPayment(toPay, ability);
Singletons.getControl().getInputQueue().setInputAndWait(inpPayment);
@@ -139,19 +143,36 @@ public class CostPartMana extends CostPart {
source.setSunburstValue(toPay.getSunburst());
}
if (this.getAmountOfX() > 0) {
if( !ability.isAnnouncing("X") && !xWasBilled) {
if (!ability.isAnnouncing("X") && !xWasBilled) {
source.setXManaCostPaid(0);
inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0());
Singletons.getControl().getInputQueue().setInputAndWait(inpPayment);
if(!inpPayment.isPaid())
if (!inpPayment.isPaid()) {
return false;
}
} else {
int x = AbilityUtils.calculateAmount(source, "X", ability);
source.setXManaCostPaid(x);
}
}
// Handle convoke and offerings if InputPayManaOfCostPayment was skipped because cost was reduced to 0
if (ability.isOffering() && ability.getSacrificedAsOffering() != null) {
System.out.println("Finishing up Offering");
final Card offering = ability.getSacrificedAsOffering();
offering.setUsedToPay(false);
game.getAction().sacrifice(offering, ability);
ability.resetSacrificedAsOffering();
}
if (ability.getTappedForConvoke() != null) {
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
c.tap();
}
ability.clearTappedForConvoke();
}
return true;
}
/* (non-Javadoc)

View File

@@ -398,6 +398,19 @@ public class ManaCostBeingPaid {
increaseColorlessMana(extra.getGenericCost());
}
public final void determineManaCostDifference(final ManaCost subThisManaCost) {
for (ManaCostShard shard : subThisManaCost.getShards()) {
if (shard == ManaCostShard.X) {
cntX--;
} else if (unpaidShards.containsKey(shard)) {
decreaseShard(shard, 1);
} else {
decreaseColorlessMana(1);
}
}
decreaseColorlessMana(subThisManaCost.getGenericCost());
}
/**
* To string.
*
@@ -554,6 +567,9 @@ public class ManaCostBeingPaid {
for (final StaticAbility stAb : reduceAbilities) {
stAb.applyAbility("ReduceCost", spell, this);
}
if (spell.isSpell() && spell.isOffering()) { // cost reduction from offerings
adjustCostByOffering(sa, spell);
}
// Set cost (only used by Trinisphere) is applied last
for (final StaticAbility stAb : setAbilities) {
@@ -562,7 +578,7 @@ public class ManaCostBeingPaid {
} // GetSpellCostChange
private void adjustCostByConvoke(final SpellAbility sa, final SpellAbility spell) {
List<Card> untappedCreats = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED);
@@ -636,6 +652,33 @@ public class ManaCostBeingPaid {
return usableColors;
}
private void adjustCostByOffering(final SpellAbility sa, final SpellAbility spell) {
String offeringType = "";
for (String kw : sa.getSourceCard().getKeyword()) {
if (kw.endsWith(" offering")) {
offeringType = kw.split(" ")[0];
break;
}
}
Card toSac = null;
List<Card> canOffer = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield),
CardPredicates.isType(offeringType));
final List<Card> toSacList = sa.getSourceCard().getController().getController().choosePermanentsToSacrifice(canOffer,
offeringType, 1, spell, false, false, true);
if (!toSacList.isEmpty()) {
toSac = toSacList.get(0);
} else {
return;
}
determineManaCostDifference(toSac.getManaCost());
sa.setSacrificedAsOffering(toSac);
toSac.setUsedToPay(true); //stop it from interfering with mana input
}
public String getSourceRestriction() {
return sourceRestriction;
}

View File

@@ -76,6 +76,7 @@ public abstract class SpellAbility implements ISpellAbility {
private boolean replicate = false;
private boolean cycling = false;
private boolean delve = false;
private boolean offering = false;
private Card targetCard;
/** The chosen target. */
@@ -100,6 +101,7 @@ public abstract class SpellAbility implements ISpellAbility {
private HashMap<String, Object> replacingObjects = new HashMap<String, Object>();
private List<Card> tappedForConvoke = new ArrayList<Card>();
private Card sacrificedAsOffering = null;
private HashMap<String, String> sVars = new HashMap<String, String>();
@@ -1309,6 +1311,47 @@ public abstract class SpellAbility implements ISpellAbility {
}
}
/**
* Returns whether the SA is a patron offering.
*/
public boolean isOffering() {
return this.offering;
}
/**
* Sets the SA as a patron offering.
*
* @param c card sacrificed for a patron offering
*/
public void setIsOffering(final boolean bOffering) {
this.offering = bOffering;
}
/**
* Sets the card sacrificed for a patron offering.
*
* @param c card sacrificed for a patron offering
*/
public void setSacrificedAsOffering(final Card c) {
this.sacrificedAsOffering = c;
}
/**
* Gets the card sacrificed for a patron offering.
*
* @return the card sacrificed for a patron offering
*/
public Card getSacrificedAsOffering() {
return this.sacrificedAsOffering;
}
/**
* Clear the card sacrificed for a patron offering.
*/
public void resetSacrificedAsOffering() {
this.sacrificedAsOffering = null;
}
/**
* @return the splicedCards
*/

View File

@@ -292,7 +292,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
* @return a boolean.
*/
public final boolean canPlay(final Card c, final SpellAbility sa) {
if (c.isPhasedOut()) {
if (c.isPhasedOut() || c.isUsedToPay()) {
return false;
}

View File

@@ -792,6 +792,26 @@ public final class GameActionUtil {
newSA.setDescription(sa.getDescription() + " (by paying " + actualcost.toSimpleString() + " instead of its mana cost)");
alternatives.add(newSA);
}
if (sa.isSpell() && keyword.endsWith(" offering")) {
final String offeringType = keyword.split(" ")[0];
List<Card> canOffer = CardLists.filter(sa.getSourceCard().getController().getCardsIn(ZoneType.Battlefield),
CardPredicates.isType(offeringType));
if (source.getController().hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
canOffer = CardLists.getNotType(canOffer, "Creature");
}
if (!canOffer.isEmpty()) {
final SpellAbility newSA = sa.copy();
SpellAbilityRestriction sar = new SpellAbilityRestriction();
sar.setVariables(sa.getRestrictions());
sar.setInstantSpeed(true);
newSA.setRestrictions(sar);
newSA.setBasicSpell(false);
newSA.setIsOffering(true);
newSA.setPayCosts(sa.getPayCosts());
newSA.setDescription(sa.getDescription() + " (" + offeringType + " offering)");
alternatives.add(newSA);
}
}
if (sa.hasParam("Equip") && sa instanceof AbilityActivated && keyword.equals("EquipInstantSpeed")) {
final SpellAbility newSA = ((AbilityActivated) sa).getCopy();
SpellAbilityRestriction sar = new SpellAbilityRestriction();

View File

@@ -194,6 +194,16 @@ public class ComputerUtilMana {
manapool.clearManaPaid(sa, test);
// handle Offerings for AI
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (cost.isPaid() && !test) {
sa.getSourceCard().getController().getGame().getAction().sacrifice(offering, sa);
}
sa.resetSacrificedAsOffering();
}
if( DEBUG_MANA_PAYMENT )
System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n", FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost, extraMana, sa.getSourceCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t") );

View File

@@ -1048,7 +1048,7 @@ public class CombatUtil {
final Player opponent = game.getCombat().getDefendingPlayerRelatedTo(c).get(0);
//List<Card> list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this);
final List<Card> list = opponent.getCardsIn(ZoneType.Battlefield);
List<Card> toSac = opponent.getController().choosePermanentsToSacrifice(list, "Card", a, this, false, false);
List<Card> toSac = opponent.getController().choosePermanentsToSacrifice(list, "Card", a, this, false, false, false);
for(Card sacd : toSac) {
game.getAction().sacrifice(sacd, this);

View File

@@ -455,7 +455,7 @@ public class Upkeep extends Phase {
lowest.add(c);
}
List<Card> toSac = player.getController().choosePermanentsToSacrifice(lowest, "Select creature with power: " + power + " to destroy.", 1, this, true, false);
List<Card> toSac = player.getController().choosePermanentsToSacrifice(lowest, "Select creature with power: " + power + " to destroy.", 1, this, true, false, false);
game.getAction().destroyNoRegeneration(toSac.get(0), this);
}
} // resolve

View File

@@ -90,7 +90,7 @@ public abstract class PlayerController {
public abstract Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender);
public abstract Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero);
public abstract List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional);
public abstract List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional, boolean canCancel);
public abstract Target chooseTargets(SpellAbility ability);
public Card chooseSingleCardForEffect(List<Card> sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); }

View File

@@ -118,7 +118,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) {
public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional, boolean canCancel) {
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, amount, sa, destroy, isOptional);
}

View File

@@ -210,7 +210,7 @@ public class PlayerControllerHuman extends PlayerController {
* @see forge.game.player.PlayerController#choosePermanentsToSacrifice(java.util.List, int, forge.card.spellability.SpellAbility, boolean, boolean)
*/
@Override
public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) {
public List<Card> choosePermanentsToSacrifice(List<Card> validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional, boolean canCancel) {
int max = Math.min(amount, validTargets.size());
if (max <= 0)
return new ArrayList<Card>();
@@ -223,10 +223,12 @@ public class PlayerControllerHuman extends PlayerController {
InputSelectCards inp = new InputSelectCardsFromList(min, max, validTargets);
// TODO: Either compose a message here, or pass it as parameter from caller.
inp.setMessage("Select %d " + validMessage + "(s) to sacrifice");
inp.setCancelAllowed(canCancel);
Singletons.getControl().getInputQueue().setInputAndWait(inp);
if( inp.hasCancelled() )
if (inp.hasCancelled()) {
return new ArrayList<Card>();
}
else return inp.getSelected();
}

View File

@@ -239,7 +239,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
player.getZone(ZoneType.Battlefield).updateObservers();
}
protected boolean isAlredyPaid() {
protected boolean isAlreadyPaid() {
if (manaCost.isPaid()) {
bPaid = true;
}
@@ -256,7 +256,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
protected void onStateChanged() {
if( isAlredyPaid() ) {
if( isAlreadyPaid() ) {
done();
stop();
} else
@@ -282,6 +282,17 @@ public abstract class InputPayMana extends InputSyncronizedBase {
saPaidFor.clearTappedForConvoke();
}
}
protected void handleOfferings(boolean isCancelled) {
final Card offering = saPaidFor.getSacrificedAsOffering();
if (offering != null) {
offering.setUsedToPay(false);
if (!isCancelled) {
game.getAction().sacrifice(offering, saPaidFor);
}
}
saPaidFor.resetSacrificedAsOffering();
}
public boolean isPaid() { return bPaid; }
}

View File

@@ -39,11 +39,19 @@ public class InputPayManaOfCostPayment extends InputPayMana {
// any mana tapabilities can't be used in payment as well as being tapped for convoke)
handleConvokedCards(false);
if (saPaidFor.isOffering()) {
handleOfferings(false);
}
}
@Override
protected void onCancel() {
handleConvokedCards(true);
if (saPaidFor.isOffering()) {
handleOfferings(true);
}
stop();
}

View File

@@ -92,12 +92,18 @@ public class InputPayManaSimple extends InputPayMana {
handleConvokedCards(false);
}
if (!this.saPaidFor.isOffering()) {
handleOfferings(false);
}
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
handleConvokedCards(true);
if (!this.saPaidFor.isOffering()) {
handleOfferings(true);
}
player.getManaPool().refundManaPaid(this.saPaidFor, true);
player.getZone(ZoneType.Battlefield).updateObservers(); // DO