forked from External/mage
Merge origin/master
Conflicts: Mage/src/main/java/mage/players/PlayerImpl.java
This commit is contained in:
commit
b7b3bc4474
51 changed files with 979 additions and 396 deletions
|
|
@ -159,9 +159,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
protected boolean reachedNextTurnAfterLeaving = false;
|
||||
|
||||
// indicates that the spell with the set sourceId can be cast with an alternate mana costs (can also be no mana costs)
|
||||
protected UUID castSourceIdWithAlternateMana;
|
||||
protected ManaCosts<ManaCost> castSourceIdManaCosts;
|
||||
protected Costs<Cost> castSourceIdCosts;
|
||||
// support multiple cards with alternative mana cost
|
||||
protected Set<UUID> castSourceIdWithAlternateMana = new HashSet<>();
|
||||
protected Map<UUID, ManaCosts<ManaCost>> castSourceIdManaCosts = new HashMap<>();
|
||||
protected Map<UUID, Costs<Cost>> castSourceIdCosts = new HashMap<>();
|
||||
|
||||
// indicates that the player is in mana payment phase
|
||||
protected boolean payManaMode = false;
|
||||
|
|
@ -270,9 +271,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.priorityTimeLeft = player.getPriorityTimeLeft();
|
||||
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
|
||||
|
||||
this.castSourceIdWithAlternateMana = player.castSourceIdWithAlternateMana;
|
||||
this.castSourceIdManaCosts = player.castSourceIdManaCosts;
|
||||
this.castSourceIdCosts = player.castSourceIdCosts;
|
||||
this.castSourceIdWithAlternateMana.addAll(player.castSourceIdWithAlternateMana);
|
||||
this.castSourceIdManaCosts.putAll(player.castSourceIdManaCosts);
|
||||
this.castSourceIdCosts.putAll(player.castSourceIdCosts);
|
||||
|
||||
this.payManaMode = player.payManaMode;
|
||||
this.phyrexianColors = player.phyrexianColors.copy();
|
||||
|
||||
|
|
@ -339,9 +341,12 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.turnControllers.clear();
|
||||
this.turnControllers.addAll(player.getTurnControllers());
|
||||
this.reachedNextTurnAfterLeaving = player.hasReachedNextTurnAfterLeaving();
|
||||
this.castSourceIdWithAlternateMana = player.getCastSourceIdWithAlternateMana();
|
||||
this.castSourceIdManaCosts = player.getCastSourceIdManaCosts();
|
||||
this.castSourceIdCosts = player.getCastSourceIdCosts();
|
||||
|
||||
this.clearCastSourceIdManaCosts();
|
||||
this.castSourceIdWithAlternateMana.addAll(player.getCastSourceIdWithAlternateMana());
|
||||
this.castSourceIdManaCosts.putAll(player.getCastSourceIdManaCosts());
|
||||
this.castSourceIdCosts.putAll(player.getCastSourceIdCosts());
|
||||
|
||||
this.phyrexianColors = player.getPhyrexianColors().copy();
|
||||
|
||||
this.designations.clear();
|
||||
|
|
@ -416,9 +421,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.setLife(game.getLife(), game, (UUID) null);
|
||||
this.setReachedNextTurnAfterLeaving(false);
|
||||
|
||||
this.castSourceIdWithAlternateMana = null;
|
||||
this.castSourceIdManaCosts = null;
|
||||
this.castSourceIdCosts = null;
|
||||
this.clearCastSourceIdManaCosts();
|
||||
|
||||
this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left
|
||||
this.phyrexianColors = new FilterMana();
|
||||
|
||||
|
|
@ -443,9 +447,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.canPlayCardsFromGraveyard = false;
|
||||
this.topCardRevealed = false;
|
||||
this.alternativeSourceCosts.clear();
|
||||
this.castSourceIdWithAlternateMana = null;
|
||||
this.castSourceIdManaCosts = null;
|
||||
this.castSourceIdCosts = null;
|
||||
this.clearCastSourceIdManaCosts();
|
||||
this.getManaPool().clearEmptyManaPoolRules();
|
||||
this.phyrexianColors = new FilterMana();
|
||||
}
|
||||
|
|
@ -1060,26 +1062,33 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts<ManaCost> manaCosts, Costs<Cost> costs) {
|
||||
castSourceIdWithAlternateMana = sourceId;
|
||||
castSourceIdManaCosts = manaCosts;
|
||||
castSourceIdCosts = costs;
|
||||
castSourceIdWithAlternateMana.add(sourceId);
|
||||
castSourceIdManaCosts.put(sourceId, manaCosts);
|
||||
castSourceIdCosts.put(sourceId, costs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getCastSourceIdWithAlternateMana() {
|
||||
public Set<UUID> getCastSourceIdWithAlternateMana() {
|
||||
return castSourceIdWithAlternateMana;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Costs<Cost> getCastSourceIdCosts() {
|
||||
public Map<UUID, Costs<Cost>> getCastSourceIdCosts() {
|
||||
return castSourceIdCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManaCosts getCastSourceIdManaCosts() {
|
||||
public Map<UUID, ManaCosts<ManaCost>> getCastSourceIdManaCosts() {
|
||||
return castSourceIdManaCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCastSourceIdManaCosts() {
|
||||
this.castSourceIdCosts.clear();
|
||||
this.castSourceIdManaCosts.clear();
|
||||
this.castSourceIdWithAlternateMana.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPayManaMode(boolean payManaMode) {
|
||||
this.payManaMode = payManaMode;
|
||||
|
|
@ -1143,11 +1152,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
// Update the zcc to the stack
|
||||
ability.setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(ability.getSourceId()));
|
||||
|
||||
// ALTERNATIVE COST from dynamic effects
|
||||
// some effects set sourceId to cast without paying mana costs or other costs
|
||||
if (ability.getSourceId().equals(getCastSourceIdWithAlternateMana())) {
|
||||
if (getCastSourceIdWithAlternateMana().contains(ability.getSourceId())) {
|
||||
Ability spellAbility = spell.getSpellAbility();
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts();
|
||||
Costs<Cost> costs = getCastSourceIdCosts();
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(ability.getSourceId());
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(ability.getSourceId());
|
||||
if (alternateCosts == null) {
|
||||
noMana = true;
|
||||
} else {
|
||||
|
|
@ -1161,7 +1172,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
spellAbility.getCosts().addAll(costs);
|
||||
}
|
||||
}
|
||||
setCastSourceIdWithAlternateMana(null, null, null);
|
||||
clearCastSourceIdManaCosts(); // TODO: test multiple alternative cost for different cards as same time
|
||||
|
||||
GameEvent event = GameEvent.getEvent(GameEvent.EventType.CAST_SPELL,
|
||||
spell.getSpellAbility().getId(), spell.getSpellAbility().getSourceId(), playerId, permittingObject);
|
||||
game.fireEvent(event);
|
||||
|
|
@ -3059,10 +3071,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (available == null) {
|
||||
return true;
|
||||
}
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(),
|
||||
AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game);
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(copy.getSourceId(),
|
||||
AsThoughEffectType.SPEND_OTHER_MANA, copy, copy.getControllerId(), game);
|
||||
for (Mana mana : abilityOptions) {
|
||||
for (Mana avail : available) {
|
||||
// TODO: SPEND_OTHER_MANA effects with getAsThoughManaType can change mana type to pay,
|
||||
// but that code processing it as any color, need to test and fix another use cases
|
||||
// (example: Sunglasses of Urza - may spend white mana as though it were red mana)
|
||||
|
||||
//
|
||||
// add tests for non any color like Sunglasses of Urza
|
||||
if (permittingObject != null && mana.count() <= avail.count()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3074,14 +3092,35 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
// ALTERNATIVE COST from source card (AlternativeCostSourceAbility)
|
||||
for (Ability objectAbility : sourceObject.getAbilities()) {
|
||||
if (objectAbility instanceof AlternativeCostSourceAbility) {
|
||||
if (objectAbility.getCosts().canPay(ability, ability.getSourceId(), playerId, game)) {
|
||||
if (objectAbility.getCosts().canPay(copy, copy.getSourceId(), playerId, game)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canPlayCardByAlternateCost(game.getCard(ability.getSourceId()), available, ability, game);
|
||||
|
||||
// ALTERNATIVE COST FROM dynamic effects
|
||||
if (getCastSourceIdWithAlternateMana().contains(copy.getSourceId())) {
|
||||
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId());
|
||||
Costs<Cost> costs = getCastSourceIdCosts().get(copy.getSourceId());
|
||||
|
||||
boolean canPutToPlay = true;
|
||||
if (alternateCosts != null && !alternateCosts.canPay(copy, copy.getSourceId(), playerId, game)) {
|
||||
canPutToPlay = false;
|
||||
}
|
||||
if (costs != null && !costs.canPay(copy, copy.getSourceId(), playerId, game)) {
|
||||
canPutToPlay = false;
|
||||
}
|
||||
|
||||
if (canPutToPlay) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ALTERNATIVE COST from source card (any AlternativeSourceCosts)
|
||||
return canPlayCardByAlternateCost(game.getCard(ability.getSourceId()), available, copy, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3202,35 +3241,85 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Ability> cardPlayableAbilities(Game game, Card card, boolean setControllerId) {
|
||||
List<Ability> playable = new ArrayList();
|
||||
if (card != null) {
|
||||
for (ActivatedAbility ability : card.getAbilities().getActivatedAbilities(Zone.HAND)) {
|
||||
if (!ability.canActivate(playerId, game).canActivate()) {
|
||||
continue;
|
||||
}
|
||||
private void getPlayableFromNonHandCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<Ability> output) {
|
||||
if (fromZone == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID savedControllerId = null;
|
||||
if (setControllerId) {
|
||||
// For when owner != caster, e.g. with Psychic Intrusion and similar effects.
|
||||
savedControllerId = getId();
|
||||
ability.setControllerId(getId());
|
||||
}
|
||||
if (ability instanceof SpellAbility
|
||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, ability, getId(), game)) {
|
||||
playable.add(ability);
|
||||
} else if (ability instanceof PlayLandAbility
|
||||
&& null != game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), getId(), game)) {
|
||||
playable.add(ability);
|
||||
}
|
||||
if (setControllerId) {
|
||||
ability.setControllerId(savedControllerId);
|
||||
}
|
||||
// BASIC abilities
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCard splitCard = (SplitCard) card;
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(), availableMana, output);
|
||||
} else if (card instanceof AdventureCard) {
|
||||
// adventure must use different card characteristics for different spells (main or adventure)
|
||||
AdventureCard adventureCard = (AdventureCard) card;
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output);
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(), availableMana, output);
|
||||
} else {
|
||||
getPlayableFromNonHandCardSingle(game, fromZone, card, card.getAbilities(), availableMana, output);
|
||||
}
|
||||
|
||||
// DYNAMIC ADDED abilities
|
||||
if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities
|
||||
// Other activated abilities (added dynamic by effects)
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable;
|
||||
if (card instanceof AdventureCard) {
|
||||
// adventure cards (contains two different cards: main and adventure spell)
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
} else {
|
||||
// all other cards (TODO: check split cards with dynamic added abilities)
|
||||
useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
|
||||
output.addAll(useable.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getPlayableFromNonHandCardSingle(Game game, Zone fromZone, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) {
|
||||
|
||||
// check "can play from hand" condition as original controller (effects checks affected controller with source controller)
|
||||
// TODO: remove card.getSpellAbility() ?
|
||||
MageObjectReference permittingObject = game.getContinuousEffects().asThough(card.getId(),
|
||||
AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, card.getSpellAbility(), this.getId(), game);
|
||||
boolean canActivateAsHandZone = permittingObject != null
|
||||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
|
||||
// check "can play" condition as affected controller
|
||||
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
boolean possibleToPlay = false;
|
||||
|
||||
// spell/hand abilities (play from all zones)
|
||||
// need permitingObject or canPlayCardsFromGraveyard
|
||||
if (canActivateAsHandZone
|
||||
&& ability.getZone().match(Zone.HAND)
|
||||
&& (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) {
|
||||
possibleToPlay = true;
|
||||
}
|
||||
|
||||
// zone's abilities (play from specific zone)
|
||||
// no need in permitingObject
|
||||
if (fromZone != Zone.ALL && ability.getZone().match(fromZone)) {
|
||||
possibleToPlay = true;
|
||||
}
|
||||
|
||||
if (possibleToPlay && canPlay(ability, availableMana, card, game)) {
|
||||
output.add(ability);
|
||||
}
|
||||
} finally {
|
||||
ability.setControllerId(savedControllerId);
|
||||
}
|
||||
}
|
||||
return playable;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3249,11 +3338,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
boolean fromAll = fromZone.equals(Zone.ALL);
|
||||
Collection<Card> cards;
|
||||
|
||||
if (hidden && (fromAll || fromZone == Zone.HAND)) {
|
||||
cards = hideDuplicatedAbilities ? hand.getUniqueCards(game) : hand.getCards(game);
|
||||
for (Card card : cards) {
|
||||
for (Card card : hand.getCards(game)) {
|
||||
for (Ability ability : card.getAbilities(game)) { // gets this activated ability from hand? (Morph?)
|
||||
if (ability.getZone().match(Zone.HAND)) {
|
||||
if (ability instanceof ActivatedAbility) {
|
||||
|
|
@ -3284,37 +3371,15 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.GRAVEYARD) {
|
||||
cards = hideDuplicatedAbilities ? graveyard.getUniqueCards(game) : graveyard.getCards(game);
|
||||
for (Card card : cards) {
|
||||
// Handle split cards in graveyard to support Aftermath
|
||||
if (card instanceof SplitCard) {
|
||||
SplitCard splitCard = (SplitCard) card;
|
||||
getPlayableFromGraveyardCard(game, splitCard.getLeftHalfCard(),
|
||||
splitCard.getLeftHalfCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, splitCard.getRightHalfCard(),
|
||||
splitCard.getRightHalfCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, splitCard, splitCard.getSharedAbilities(),
|
||||
availableMana, playable);
|
||||
} else if (card instanceof AdventureCard) {
|
||||
AdventureCard adventureCard = (AdventureCard) card;
|
||||
getPlayableFromGraveyardCard(game, adventureCard.getSpellCard(),
|
||||
adventureCard.getSpellCard().getAbilities(), availableMana, playable);
|
||||
getPlayableFromGraveyardCard(game, adventureCard, adventureCard.getAbilities(), availableMana, playable);
|
||||
} else {
|
||||
getPlayableFromGraveyardCard(game, card, card.getAbilities(), availableMana, playable);
|
||||
}
|
||||
|
||||
// Other activated abilities
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
|
||||
getOtherUseableActivatedAbilities(card, Zone.GRAVEYARD, game, useable);
|
||||
playable.addAll(useable.values());
|
||||
for (Card card : graveyard.getCards(game)) {
|
||||
getPlayableFromNonHandCardAll(game, Zone.GRAVEYARD, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
|
||||
if (fromAll || fromZone == Zone.EXILED) {
|
||||
for (ExileZone exile : game.getExile().getExileZones()) {
|
||||
for (Card card : exile.getCards(game)) {
|
||||
playable.addAll(cardPlayableAbilities(game, card, true));
|
||||
getPlayableFromNonHandCardAll(game, Zone.EXILED, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3323,7 +3388,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (fromAll) {
|
||||
for (Cards revealedCards : game.getState().getRevealed().values()) {
|
||||
for (Card card : revealedCards.getCards(game)) {
|
||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
||||
// revealed cards can be from any zones
|
||||
getPlayableFromNonHandCardAll(game, game.getState().getZone(card.getId()), card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3335,7 +3401,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (player != null) {
|
||||
if (/*player.isTopCardRevealed() &&*/player.getLibrary().hasCards()) {
|
||||
Card card = player.getLibrary().getFromTop(game);
|
||||
playable.addAll(cardPlayableAbilities(game, card, false));
|
||||
getPlayableFromNonHandCardAll(game, Zone.LIBRARY, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue