Compare commits

...

1 Commits

Author SHA1 Message Date
Hans Mackowiak
e635f811ff Bestow as Static Effect 2025-02-09 17:13:45 +01:00
8 changed files with 70 additions and 62 deletions

View File

@@ -4,6 +4,8 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -51,13 +53,18 @@ public abstract class SpellAbilityAi {
* Handles the AI decision to play a "main" SpellAbility * Handles the AI decision to play a "main" SpellAbility
*/ */
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard(); Card source = sa.getHostCard();
try {
source = ObjectUtils.firstNonNull(sa.getAlternateHost(source), source);
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) { if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
return false; return false;
}
return canPlayWithoutRestrict(ai, sa);
} finally {
sa.undoAlternateHost(source);
} }
return canPlayWithoutRestrict(ai, sa);
} }
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) { protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {

View File

@@ -262,6 +262,8 @@ public final class GameActionUtil {
// reset static abilities // reset static abilities
if (lkicheck) { if (lkicheck) {
// unanimate host again
sa.undoAlternateHost(newHost);
game.getAction().checkStaticAbilities(false); game.getAction().checkStaticAbilities(false);
// clear delayed changes, this check should not have updated the view // clear delayed changes, this check should not have updated the view
game.getTracker().clearDelayed(); game.getTracker().clearDelayed();
@@ -468,6 +470,7 @@ public final class GameActionUtil {
// reset static abilities // reset static abilities
if (lkicheck) { if (lkicheck) {
sa.undoAlternateHost(newHost);
game.getAction().checkStaticAbilities(false); game.getAction().checkStaticAbilities(false);
// clear delayed changes, this check should not have updated the view // clear delayed changes, this check should not have updated the view
game.getTracker().clearDelayed(); game.getTracker().clearDelayed();

View File

@@ -250,7 +250,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private int exertThisTurn = 0; private int exertThisTurn = 0;
private PlayerCollection exertedByPlayer = new PlayerCollection(); private PlayerCollection exertedByPlayer = new PlayerCollection();
private long bestowTimestamp = -1; private Card bestowEffect = null;
private long transformedTimestamp = 0; private long transformedTimestamp = 0;
private long prototypeTimestamp = -1; private long prototypeTimestamp = -1;
private long mutatedTimestamp = -1; private long mutatedTimestamp = -1;
@@ -6827,43 +6827,40 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
} }
public final void animateBestow() { public final void animateBestow() {
animateBestow(true);
}
public final void animateBestow(final boolean updateView) {
if (isBestowed()) { if (isBestowed()) {
return; return;
} }
bestowTimestamp = getGame().getNextTimestamp(); bestowEffect = SpellAbilityEffect.createEffect(null, this, this.getController(), "Bestow Effect", getImageKey(), getGame().getNextTimestamp());
addChangedCardTypes(new CardType(Collections.singletonList("Aura"), true), bestowEffect.setRenderForUI(false);
new CardType(Collections.singletonList("Creature"), true), bestowEffect.addRemembered(this);
false, EnumSet.of(RemoveType.EnchantmentTypes), bestowTimestamp, 0, updateView, false);
addChangedCardKeywords(Collections.singletonList("Enchant creature"), Lists.newArrayList(), String s = "Mode$ Continuous | AffectedDefined$ RememberedCard | EffectZone$ Command "
false, bestowTimestamp, null, updateView); + " | AddType$ Aura | RemoveType$ Creature | RemoveEnchantmentTypes$ True | AddKeyword$ Enchant creature";
bestowEffect.addStaticAbility(s);
GameCommand until = SpellAbilityEffect.exileEffectCommand(getGame(), bestowEffect);
addLeavesPlayCommand(until);
getGame().getAction().moveToCommand(bestowEffect, null);
} }
public final void unanimateBestow() { public final void unanimateBestow() {
unanimateBestow(true);
}
public final void unanimateBestow(final boolean updateView) {
if (!isBestowed()) { if (!isBestowed()) {
return; return;
} }
getGame().getAction().exileEffect(bestowEffect);
removeChangedCardKeywords(bestowTimestamp, 0, updateView); bestowEffect = null;
removeChangedCardTypes(bestowTimestamp, 0, updateView);
bestowTimestamp = -1;
} }
public final boolean isBestowed() { public final boolean isBestowed() {
return bestowTimestamp != -1; return bestowEffect != null;
} }
public final long getBestowTimestamp() { public final Card getBestowEffect() {
return bestowTimestamp; return this.bestowEffect;
} }
public final void setBestowTimestamp(final long t) { public void setBestowEffect(final Card effect) {
bestowTimestamp = t; this.bestowEffect = effect;
} }
public final long getGameTimestamp() { public final long getGameTimestamp() {

View File

@@ -314,6 +314,9 @@ public class CardCopyService {
if (copyFrom.isSuspected()) { if (copyFrom.isSuspected()) {
newCopy.setSuspectedEffect(getLKICopy(copyFrom.getSuspectedEffect(), cachedMap)); newCopy.setSuspectedEffect(getLKICopy(copyFrom.getSuspectedEffect(), cachedMap));
} }
if (copyFrom.isBestowed()) {
newCopy.setBestowEffect(getLKICopy(copyFrom.getBestowEffect(), cachedMap));
}
newCopy.setColor(copyFrom.getColor().getColor()); newCopy.setColor(copyFrom.getColor().getColor());
newCopy.setPhasedOut(copyFrom.getPhasedOut()); newCopy.setPhasedOut(copyFrom.getPhasedOut());
@@ -369,8 +372,6 @@ public class CardCopyService {
newCopy.setGameTimestamp(copyFrom.getGameTimestamp()); newCopy.setGameTimestamp(copyFrom.getGameTimestamp());
newCopy.setLayerTimestamp(copyFrom.getLayerTimestamp()); newCopy.setLayerTimestamp(copyFrom.getLayerTimestamp());
newCopy.setBestowTimestamp(copyFrom.getBestowTimestamp());
newCopy.setForetold(copyFrom.isForetold()); newCopy.setForetold(copyFrom.isForetold());
newCopy.setTurnInZone(copyFrom.getTurnInZone()); newCopy.setTurnInZone(copyFrom.getTurnInZone());
newCopy.setForetoldCostByEffect(copyFrom.isForetoldCostByEffect()); newCopy.setForetoldCostByEffect(copyFrom.isForetoldCostByEffect());

View File

@@ -96,15 +96,19 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
card.setController(activator, 0); card.setController(activator, 0);
} }
card = ObjectUtils.firstNonNull(getAlternateHost(card), card); try {
card = ObjectUtils.firstNonNull(getAlternateHost(card), card);
if (!this.getRestrictions().canPlay(card, this)) { if (!this.getRestrictions().canPlay(card, this)) {
return false; return false;
} }
if (!activator.getController().isFullControl(FullControlFlag.AllowPaymentStartWithMissingResources) && if (!activator.getController().isFullControl(FullControlFlag.AllowPaymentStartWithMissingResources) &&
!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this, false)) { !CostPayment.canPayAdditionalCosts(this.getPayCosts(), this, false)) {
return false; return false;
}
} finally {
undoAlternateHost(card);
} }
return true; return true;
@@ -165,7 +169,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
source = CardCopyService.getLKICopy(source); source = CardCopyService.getLKICopy(source);
} }
source.animateBestow(false); source.animateBestow();
lkicheck = true; lkicheck = true;
} else if (isCastFaceDown()) { } else if (isCastFaceDown()) {
// need a copy of the card to turn facedown without trigger anything // need a copy of the card to turn facedown without trigger anything
@@ -205,6 +209,12 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
return lkicheck ? source : null; return lkicheck ? source : null;
} }
public void undoAlternateHost(Card source) {
if (isBestow()) {
source.unanimateBestow();
}
}
public boolean isCounterableBy(final SpellAbility sa) { public boolean isCounterableBy(final SpellAbility sa) {
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(getHostCard()); final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(getHostCard());
repParams.put(AbilityKey.SpellAbility, this); repParams.put(AbilityKey.SpellAbility, this);

View File

@@ -2595,6 +2595,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return null; return null;
} }
public void undoAlternateHost(Card source) {
}
public boolean hasOptionalKeywordAmount(KeywordInterface kw) { public boolean hasOptionalKeywordAmount(KeywordInterface kw) {
long staticId = kw.getStatic() == null ? 0 : kw.getStatic().getId(); long staticId = kw.getStatic() == null ? 0 : kw.getStatic().getId();
return this.optionalKeywordAmount.contains(kw.getKeyword(), Pair.of(kw.getIdx(), staticId)); return this.optionalKeywordAmount.contains(kw.getKeyword(), Pair.of(kw.getIdx(), staticId));

View File

@@ -208,24 +208,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) { public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) {
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Zone cardZone = c.getLastKnownZone(); final Zone cardZone = c.getLastKnownZone();
Card cp = c;
// for Bestow need to check the animated State
if (sa.isSpell() && sa.isBestow()) {
// already bestowed or in battlefield, no need to check for spell
if (c.isInPlay()) {
return false;
}
// if card is lki and bestowed, then do nothing there, it got already animated
if (!(c.isLKI() && c.isBestowed())) {
if (!c.isLKI()) {
cp = CardCopyService.getLKICopy(c);
}
cp.animateBestow(!cp.isLKI());
}
}
if (cardZone == null || this.getZone() == null || !cardZone.is(this.getZone())) { if (cardZone == null || this.getZone() == null || !cardZone.is(this.getZone())) {
// If Card is not in the default activating zone, do some additional checks // If Card is not in the default activating zone, do some additional checks
@@ -277,7 +259,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
} }
if (params.containsKey("Affected")) { if (params.containsKey("Affected")) {
if (!cp.isValid(params.get("Affected").split(","), activator, o.getHost(), o.getAbility())) { if (!c.isValid(params.get("Affected").split(","), activator, o.getHost(), o.getAbility())) {
return false; return false;
} }
} }

View File

@@ -104,6 +104,8 @@ public class HumanPlay {
if (rollback.isInZone(ZoneType.Exile)) { if (rollback.isInZone(ZoneType.Exile)) {
rollback.addMayLookTemp(p); rollback.addMayLookTemp(p);
} }
} else {
sa.undoAlternateHost(rollback);
} }
} }
@@ -151,7 +153,9 @@ public class HumanPlay {
source.setSplitStateToPlayAbility(sa); source.setSplitStateToPlayAbility(sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false); if (!req.playAbility(mayChooseNewTargets, true, false)) {
sa.undoAlternateHost(source);
}
} }
/** /**