Amonket Aftermath ability and card frame changes Completed

* Aftermath Ability implementation complete (At least until we see comprehensive rules that contradict the way I assumed it will work)
* Aftermath Card Frame rendering complete
* Normal Split and Fuse Split card frame rendering complete
* Amonket Split card CMC changes NOT made, but left for a separate commit
This commit is contained in:
Mark Langen 2017-04-04 00:29:54 -06:00
parent a96a7f89f5
commit 18663f0a7a
11 changed files with 278 additions and 41 deletions

View file

@ -27,23 +27,19 @@
*/
package mage.abilities.keyword;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.*;
import mage.cards.Card;
import mage.cards.SplitCard;
import mage.cards.SplitCardHalf;
import mage.cards.SplitCardHalfImpl;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import org.junit.After;
import java.util.UUID;
/**
@ -57,8 +53,9 @@ import java.util.UUID;
*/
public class AftermathAbility extends SimpleStaticAbility {
public AftermathAbility() {
super(Zone.ALL, new AftermathCantCastFromHand());
addEffect(new AftermathCastFromGraveyard());
super(Zone.ALL, new AftermathCastFromGraveyard());
addEffect(new AftermathCantCastFromHand());
addEffect(new AftermathExileAsResolvesFromGraveyard());
}
public AftermathAbility(final AftermathAbility ability) {
@ -101,9 +98,13 @@ class AftermathCastFromGraveyard extends AsThoughEffectImpl {
return new AftermathCastFromGraveyard(this);
}
private static String msb(UUID id) {
return Integer.toUnsignedString((int)id.getMostSignificantBits(), 16);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (objectId.equals(source.getSourceId()) &&
if (objectId.equals(source.getSourceId()) &
affectedControllerId.equals(source.getControllerId())) {
Card card = game.getCard(source.getSourceId());
if (card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) {
@ -151,4 +152,76 @@ class AftermathCantCastFromHand extends ContinuousRuleModifyingEffectImpl {
}
return false;
}
}
class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
AftermathExileAsResolvesFromGraveyard() {
super(Duration.WhileOnStack, Outcome.Detriment);
this.staticText = "Exile it afterwards.";
}
AftermathExileAsResolvesFromGraveyard(AftermathExileAsResolvesFromGraveyard effect) {
super(effect);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean applies(GameEvent evt, Ability source, Game game) {
ZoneChangeEvent event = (ZoneChangeEvent) evt;
if (event.getFromZone() == Zone.STACK && event.getToZone() != Zone.EXILED) {
// Moving something from stack to somewhere else
// Get the source id, getting the whole split card's ID, because
// that's the card that is changing zones in the event, but
// source.getSourceId is only the split card half.
// If branch so that we also support putting Aftermath on
// non-split cards for... whatever reason, in case somebody
// wants to do that in the future.
UUID sourceId = source.getSourceId();
Card sourceCard = game.getCard(source.getSourceId());
if (sourceCard != null && sourceCard instanceof SplitCardHalf) {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (event.getTargetId() == sourceId) {
// Moving this spell from stack to yard
Spell spell = game.getStack().getSpell(source.getSourceId());
if (spell != null && spell.getFromZone() == Zone.GRAVEYARD) {
// And this spell was cast from the graveyard, so we need to exile it
return true;
}
}
}
return false;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
UUID sourceId = source.getSourceId();
Card sourceCard = game.getCard(source.getSourceId());
if (sourceCard != null && sourceCard instanceof SplitCardHalf) {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (sourceCard != null) {
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {
return player.moveCardToExileWithInfo(sourceCard, null, "", sourceId, game, ((ZoneChangeEvent)event).getFromZone(), true);
}
}
return false;
}
@Override
public AftermathExileAsResolvesFromGraveyard copy() {
return new AftermathExileAsResolvesFromGraveyard(this);
}
}

View file

@ -28,8 +28,11 @@
package mage.cards;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import com.sun.deploy.util.ArrayUtil;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
@ -49,10 +52,14 @@ public abstract class SplitCard extends CardImpl {
protected Card rightHalfCard;
public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costsLeft, String costsRight, boolean fused) {
super(ownerId, setInfo, cardTypes, costsLeft + costsRight, (fused ? SpellAbilityType.SPLIT_FUSED : SpellAbilityType.SPLIT));
this(ownerId, setInfo, cardTypes, cardTypes, costsLeft, costsRight, fused);
}
public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] typesLeft, CardType[] typesRight, String costsLeft, String costsRight, boolean fused) {
super(ownerId, setInfo, CardType.mergeTypes(typesLeft, typesRight), costsLeft + costsRight, (fused ? SpellAbilityType.SPLIT_FUSED : SpellAbilityType.SPLIT));
String[] names = setInfo.getName().split(" // ");
leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), cardTypes, costsLeft, this, SpellAbilityType.SPLIT_LEFT);
rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), cardTypes, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesLeft, costsLeft, this, SpellAbilityType.SPLIT_LEFT);
rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesRight, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
this.splitCard = true;
}
@ -139,6 +146,14 @@ public abstract class SplitCard extends CardImpl {
return allAbilites;
}
/**
* Currently only gets the fuse SpellAbility if there is one, but generally gets
* any abilities on a split card as a whole, and not on either half individually.
**/
public Abilities<Ability> getSharedAbilities() {
return super.getAbilities();
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();

View file

@ -15,4 +15,6 @@ public interface SplitCardHalf extends Card {
SplitCardHalf copy();
void setParentCard(SplitCard card);
SplitCard getParentCard();
}

View file

@ -82,4 +82,8 @@ public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf {
this.splitCardParent = card;
}
@Override
public SplitCard getParentCard() {
return this.splitCardParent;
}
}

View file

@ -55,4 +55,9 @@ public class MockSplitCardHalf extends MockCard implements SplitCardHalf {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public SplitCard getParentCard() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}

View file

@ -61,7 +61,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 50;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 71;
private static final long CARD_CONTENT_VERSION = 74;
private final TreeSet<String> landTypes = new TreeSet<>();
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;

View file

@ -1,5 +1,7 @@
package mage.constants;
import java.util.HashSet;
/**
*
* @author North
@ -26,4 +28,19 @@ public enum CardType {
return text;
}
/**
* Returns all of the card types from two lists of card types.
* Duplicates are eliminated.
*/
public static CardType[] mergeTypes(CardType[] a, CardType[] b) {
HashSet<CardType> cardTypes = new HashSet<>();
for (CardType t: a) {
cardTypes.add(t);
}
for (CardType t: b) {
cardTypes.add(t);
}
return cardTypes.toArray(new CardType[0]);
}
}

View file

@ -1245,29 +1245,30 @@ public abstract class PlayerImpl implements Player, Serializable {
return useable;
}
@Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
// Get the usable activated abilities for a *single card object*, that is, either a card or half of a split card.
// Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities
// as candidates.
private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities<Ability> candidateAbilites, LinkedHashMap<UUID, ActivatedAbility> output) {
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
ManaOptions availableMana = null;
// ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly
// availableMana.addMana(manaPool.getMana());
for (Ability ability : object.getAbilities()) {
// ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly
// availableMana.addMana(manaPool.getMana());
for (Ability ability : candidateAbilites) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
if (ability.getZone().match(zone)) {
if (ability instanceof ActivatedAbility) {
if (ability instanceof ActivatedManaAbilityImpl) {
if (((ActivatedAbility) ability).canActivate(playerId, game)) {
useable.put(ability.getId(), (ActivatedAbility) ability);
output.put(ability.getId(), (ActivatedAbility) ability);
}
} else if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) {
useable.put(ability.getId(), (ActivatedAbility) ability);
output.put(ability.getId(), (ActivatedAbility) ability);
}
} else if (ability instanceof AlternativeSourceCosts) {
if (object.isLand()) {
for (Ability ability2 : object.getAbilities().copy()) {
if (ability2 instanceof PlayLandAbility) {
useable.put(ability2.getId(), (ActivatedAbility) ability2);
output.put(ability2.getId(), (ActivatedAbility) ability2);
}
}
}
@ -1277,19 +1278,19 @@ public abstract class PlayerImpl implements Player, Serializable {
}
if (zone != Zone.HAND) {
if (Zone.GRAVEYARD == zone && canPlayCardsFromGraveyard()) {
for (ActivatedAbility ability : object.getAbilities().getPlayableAbilities(Zone.HAND)) {
for (ActivatedAbility ability : candidateAbilites.getPlayableAbilities(Zone.HAND)) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility) {
continue; // You can't play spells from graveyard that have no costs
}
if (ability.canActivate(playerId, game)) {
useable.put(ability.getId(), ability);
output.put(ability.getId(), ability);
}
}
}
}
if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) {
for (Ability ability : object.getAbilities()) {
for (Ability ability : candidateAbilites) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility && !(Objects.equals(ability.getSourceId(), getCastSourceIdWithAlternateMana()))) {
continue; // You can't play spells that have no costs, unless you can play them without paying their mana costs
@ -1297,12 +1298,25 @@ public abstract class PlayerImpl implements Player, Serializable {
ability.setControllerId(this.getId());
if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND)
&& ((ActivatedAbility) ability).canActivate(playerId, game)) {
useable.put(ability.getId(), (ActivatedAbility) ability);
output.put(ability.getId(), (ActivatedAbility) ability);
}
}
}
}
}
}
@Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
if (object instanceof SplitCard) {
SplitCard splitCard = (SplitCard) object;
getUseableActivatedAbilitiesHalfImpl(splitCard.getLeftHalfCard(), zone, game, splitCard.getLeftHalfCard().getAbilities(), useable);
getUseableActivatedAbilitiesHalfImpl(splitCard.getRightHalfCard(), zone, game, splitCard.getRightHalfCard().getAbilities(), useable);
getUseableActivatedAbilitiesHalfImpl(splitCard, zone, game, splitCard.getSharedAbilities(), useable);
} else {
getUseableActivatedAbilitiesHalfImpl(object, zone, game, object.getAbilities(), useable);
}
getOtherUseableActivatedAbilities(object, zone, game, useable);
return useable;