Regeneration Rework:

- Regenerate or RegenerateAll does create an Effect
in Command which does replace Destroy if possible
- Trigger Regenerated will be added to the Effect
if something does care about "regenerated that way"
- new Regeneration Api is the internal effect
that does handle the actual regeneration
- ReplaceDestroy has Section to handle if Regeneration is possible
- CardShields are removed with the option in PlayerController
This commit is contained in:
Hanmac
2018-02-06 12:56:02 +01:00
parent 8b26deb477
commit ff7a0f1ae6
23 changed files with 319 additions and 229 deletions

View File

@@ -1402,26 +1402,13 @@ public class GameAction {
return false;
}
if (c.canBeShielded() && (!c.isCreature() || c.getNetToughness() > 0)
&& (c.getShieldCount() > 0 || c.hasKeyword("If CARDNAME would be destroyed, regenerate it."))) {
c.subtractShield(c.getController().getController().chooseRegenerationShield(c));
c.setDamage(0);
c.setHasBeenDealtDeathtouchDamage(false);
c.tap();
c.addRegeneratedThisTurn();
if (game.getCombat() != null) {
game.getCombat().removeFromCombat(c);
}
// Play the Regen sound
game.fireEvent(new GameEventCardRegenerated());
return false;
}
return destroyNoRegeneration(c, sa);
return destroy(c, sa, true);
}
public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) {
return destroy(c, sa, false);
}
public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) {
public final boolean destroy(final Card c, final SpellAbility sa, final boolean regenerate) {
Player activator = null;
if (!c.canBeDestroyed()) {
return false;
@@ -1433,6 +1420,7 @@ public class GameAction {
repRunParams.put("Source", sa);
repRunParams.put("Card", c);
repRunParams.put("Affected", c);
repRunParams.put("Regeneration", regenerate);
if (game.getReplacementHandler().run(repRunParams) != ReplacementResult.NotReplaced) {
return false;

View File

@@ -115,6 +115,7 @@ public enum ApiType {
RearrangeTopOfLibrary (RearrangeTopOfLibraryEffect.class),
Regenerate (RegenerateEffect.class),
RegenerateAll (RegenerateAllEffect.class),
Regeneration (RegenerationEffect.class),
RemoveCounter (CountersRemoveEffect.class),
RemoveCounterAll (CountersRemoveAllEffect.class),
RemoveFromCombat (RemoveFromCombatEffect.class),

View File

@@ -292,9 +292,16 @@ public abstract class SpellAbilityEffect {
protected static void addForgetOnMovedTrigger(final Card card, final String zone) {
String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | Destination$ Any | TriggerZones$ Command | Static$ True";
String effect = "DB$ Pump | ForgetObjects$ TriggeredCard";
String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
+ " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, card);
AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card);
saForget.setSubAbility(saExile);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
parsedTrigger.setOverridingAbility(saForget);
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
}

View File

@@ -1,22 +1,27 @@
package forge.game.ability.effects;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardShields;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class RegenerateAllEffect extends SpellAbilityEffect {
public class RegenerateAllEffect extends RegenerateBaseEffect {
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility)
*/
@Override
protected String getStackDescription(SpellAbility sa) {
return "Regenerate all valid cards.";
}
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
final Card hostCard = sa.getHostCard();
@@ -30,21 +35,8 @@ public class RegenerateAllEffect extends SpellAbilityEffect {
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa);
for (final Card c : list) {
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = 259368227093961103L;
@Override
public void run() {
c.resetShield();
}
};
if (c.isInPlay()) {
c.addShield(new CardShields(sa, null));
game.getEndOfTurn().addUntil(untilEOT);
}
}
// create Effect for Regeneration
createRengenerationEffect(sa, list);
} // regenerateAllResolve
}

View File

@@ -0,0 +1,85 @@
package forge.game.ability.effects;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
public abstract class RegenerateBaseEffect extends SpellAbilityEffect {
public void createRengenerationEffect(SpellAbility sa, final Iterable<Card> list) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
// create Effect for Regeneration
Card eff = createEffect(
hostCard, sa.getActivatingPlayer(), hostCard.getName() + "'s Regeneration", hostCard.getImageKey());
eff.addRemembered(list);
addForgetOnMovedTrigger(eff, "Battlefield");
// build ReplacementEffect
String repeffstr = "Event$ Destroy | ActiveZones$ Command | ValidCard$ Card.IsRemembered | Regeneration$ True"
+ " | Description$ Regeneration (if creature would be destroyed, regenerate it instead)";
String effect = "DB$ Regeneration | Defined$ ReplacedCard";
String exileEff = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
+ " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
SpellAbility saReg = AbilityFactory.getAbility(effect, eff);
AbilitySub saExile = (AbilitySub)AbilityFactory.getAbility(exileEff, eff);
saReg.setSubAbility(saExile);
re.setOverridingAbility(saReg);
eff.addReplacementEffect(re);
// add extra Remembered
if (sa.hasParam("RememberObjects")) {
eff.addRemembered(AbilityUtils.getDefinedObjects(hostCard, sa.getParam("RememberObjects"), sa));
}
if (sa.hasParam("RegenerationTrigger")) {
final String str = sa.getSVar(sa.getParam("RegenerationTrigger"));
SpellAbility trigSA = AbilityFactory.getAbility(str, eff);
final String trigStr = "Mode$ Regenerated | ValidCause$ Effect.Self | TriggerZones$ Command "
+ " | TriggerDescription$ " + trigSA.getDescription();
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, eff, true);
trigger.setOverridingAbility(trigSA);
eff.moveTrigger(trigger);
}
// Copy text changes
if (sa.isIntrinsic()) {
eff.copyChangedTextFrom(hostCard);
}
eff.updateStateForView();
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = 259368227093961103L;
@Override
public void run() {
game.getAction().exile(eff, null);
}
};
game.getEndOfTurn().addUntil(untilEOT);
}
}

View File

@@ -1,24 +1,16 @@
package forge.game.ability.effects;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardShields;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.TextUtil;
import java.util.Iterator;
import java.util.List;
public class RegenerateEffect extends SpellAbilityEffect {
public class RegenerateEffect extends RegenerateBaseEffect {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility)
*/
@Override
protected String getStackDescription(SpellAbility sa) {
@@ -47,51 +39,14 @@ public class RegenerateEffect extends SpellAbilityEffect {
return sb.toString();
}
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = sa.getActivatingPlayer().getGame();
final Card sourceCard = sa.getHostCard();
for (final Card tgtC : getTargetCards(sa)) {
final GameCommand untilEOT = new GameCommand() {
private static final long serialVersionUID = 1922050611313909200L;
@Override
public void run() {
tgtC.resetShield();
}
};
if (tgtC.isInPlay() && (tgt == null || tgt.canTgtPlayer() || tgtC.canBeTargetedBy(sa))) {
SpellAbility triggerSA = null;
if (sa.hasParam("RegenerationTrigger")) {
String abString = sa.getHostCard().getSVar(sa.getParam("RegenerationTrigger"));
if (sa.hasParam("ReplacePlayerName")) { // Soldevi Sentry
String def = sa.getParam("ReplacePlayerName");
List<Player> replaced = AbilityUtils.getDefinedPlayers(sourceCard, def, sa);
if(replaced.isEmpty())
abString = TextUtil.fastReplace(abString, def, "");
else
abString = TextUtil.fastReplace(abString, def, replaced.get(0).getName());
} else if (sa.hasParam("ReplaceCardUID")) { // Debt of Loyalty
String def = sa.getParam("ReplaceCardUID");
List<Card> replaced = AbilityUtils.getDefinedCards(sourceCard, def, sa);
if(replaced.isEmpty())
abString = TextUtil.fastReplace(abString, def, "");
else
abString = TextUtil.fastReplace(abString, def, Integer.toString(replaced.get(0).getId()));
}
triggerSA = AbilityFactory.getAbility(abString, sourceCard);
triggerSA.setActivatingPlayer(sa.getActivatingPlayer());
triggerSA.setTrigger(true);
triggerSA.setHostCard(sourceCard);
}
CardShields shield = new CardShields(sa, triggerSA);
tgtC.addShield(shield);
game.getEndOfTurn().addUntil(untilEOT);
}
}
// create Effect for Regeneration
createRengenerationEffect(sa, getTargetCards(sa));
} // regenerateResolve
}

View File

@@ -0,0 +1,55 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.event.GameEventCardRegenerated;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import java.util.Map;
import com.google.common.collect.Maps;
public class RegenerationEffect extends SpellAbilityEffect {
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
for (Card c : getTargetCards(sa)) {
if (!c.canBeShielded() || !c.isInPlay()) {
continue;
}
c.setDamage(0);
c.setHasBeenDealtDeathtouchDamage(false);
c.tap();
c.addRegeneratedThisTurn();
if (game.getCombat() != null) {
game.getCombat().removeFromCombat(c);
}
// Play the Regen sound
game.fireEvent(new GameEventCardRegenerated());
if (host.getType().hasStringType("Effect")) {
c.subtractShield(host);
host.removeRemembered(c);
}
// Run triggers
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Card", c);
runParams.put("Cause", host);
game.getTriggerHandler().runTrigger(TriggerType.Regenerated, runParams, false);
}
}
}

View File

@@ -194,7 +194,7 @@ public class Card extends GameEntity implements Comparable<Card> {
private boolean hasBeenDealtDeathtouchDamage = false;
// regeneration
private List<CardShields> shields = Lists.newArrayList();
private FCollection<Card> shields = new FCollection<>();
private int regeneratedThisTurn = 0;
private int turnInZone;
@@ -2275,22 +2275,20 @@ public class Card extends GameEntity implements Comparable<Card> {
}
// shield = regeneration
public final Iterable<CardShields> getShields() {
public final Iterable<Card> getShields() {
return shields;
}
public final int getShieldCount() {
return shields.size();
}
public final void addShield(final CardShields shield) {
shields.add(shield);
view.updateShieldCount(this);
public final void addShield(final Card shield) {
if (shields.add(shield)) {
view.updateShieldCount(this);
}
}
public final void subtractShield(CardShields shield) {
if (shield != null && shield.hasTrigger()) {
getGame().getStack().addSimultaneousStackEntry(shield.getTriggerSA());
}
public final void subtractShield(final Card shield) {
if (shields.remove(shield)) {
view.updateShieldCount(this);
}

View File

@@ -3346,6 +3346,15 @@ public class CardFactoryUtil {
final ReplacementEffect re = makeEtbCounter(sb.toString(), card, intrinsic);
inst.addReplacement(re);
} else if (keyword.equals("If CARDNAME would be destroyed, regenerate it.")) {
String repeffstr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self"
+ " | Secondary$ True | Regeneration$ True | Description$ " + keyword;
String effect = "DB$ Regeneration | Defined$ ReplacedCard";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card, intrinsic);
SpellAbility sa = AbilityFactory.getAbility(effect, card);
re.setOverridingAbility(sa);
inst.addReplacement(re);
}

View File

@@ -1,79 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.card;
import forge.game.spellability.SpellAbility;
/**
* <p>
* Card_Shields class.
* </p>
*
* @author Forge
* @version $Id: CardShields.java 23786 2013-11-24 06:59:42Z Max mtg $
*/
public class CardShields {
// restore the regeneration shields
private final SpellAbility sourceSA;
private final SpellAbility triggerSA;
/**
* Instantiates a new CardShields.
*
* @param sourceSA
* a SpellAbility
*/
public CardShields(final SpellAbility sourceSA, final SpellAbility triggerSA) {
this.sourceSA = sourceSA;
this.triggerSA = triggerSA;
}
/**
*
* getSourceSA.
*
* @return sourceSA
*/
public final SpellAbility getSourceSA() {
return this.sourceSA;
}
/**
*
* getTriggerSA.
*
* @return triggerSA
*/
public final SpellAbility getTriggerSA() {
return this.triggerSA;
}
public final boolean hasTrigger() {
return this.triggerSA != null;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String suffix = this.triggerSA != null ? " - " + triggerSA.getDescription() : "";
return this.sourceSA.getHostCard().getName() + suffix;
}
}

View File

@@ -196,7 +196,6 @@ public abstract class PlayerController {
public abstract boolean confirmPayment(CostPart costPart, String string, SpellAbility sa);
public abstract ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, Map<String, Object> runParams);
public abstract String chooseProtectionType(String string, SpellAbility sa, List<String> choices);
public abstract CardShields chooseRegenerationShield(Card c);
// these 4 need some refining.
public abstract boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, FCollectionView<Player> allPayers);

View File

@@ -46,18 +46,35 @@ public class ReplaceDestroy extends ReplacementEffect {
if (!runParams.get("Event").equals("Destroy")) {
return false;
}
if (this.getMapParams().containsKey("ValidPlayer")) {
if (!matchesValid(runParams.get("Affected"), this.getMapParams().get("ValidPlayer").split(","), this.getHostCard())) {
if (hasParam("ValidPlayer")) {
if (!matchesValid(runParams.get("Affected"), getParam("ValidPlayer").split(","), getHostCard())) {
return false;
}
}
if (this.getMapParams().containsKey("ValidCard")) {
if (!matchesValid(runParams.get("Card"), this.getMapParams().get("ValidCard").split(","), this.getHostCard())) {
if (hasParam("ValidCard")) {
Card card = (Card)runParams.get("Card");
if (!matchesValid(card, getParam("ValidCard").split(","), getHostCard())) {
return false;
}
// extra check for Regeneration
if (hasParam("Regeneration")) {
if (!runParams.containsKey("Regeneration")) {
return false;
}
if (!(Boolean)runParams.get("Regeneration")) {
return false;
}
if (!card.canBeShielded()) {
return false;
}
if (card.isCreature()) {
if (card.getNetToughness() <= 0)
return false;
}
}
}
if (this.getMapParams().containsKey("ValidSource")) {
if (!matchesValid(runParams.get("Source"), this.getMapParams().get("ValidSource").split(","), this.getHostCard())) {
if (hasParam("ValidSource")) {
if (!matchesValid(runParams.get("Source"), getParam("ValidSource").split(","), getHostCard())) {
return false;
}
}

View File

@@ -17,6 +17,8 @@
*/
package forge.game.trigger;
import java.util.Map;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
@@ -42,22 +44,20 @@ public class TriggerDestroyed extends Trigger {
* @param intrinsic
* the intrinsic
*/
public TriggerDestroyed(final java.util.Map<String, String> params, final Card host, final boolean intrinsic) {
public TriggerDestroyed(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/** {@inheritDoc} */
@Override
public final boolean performTest(final java.util.Map<String, Object> runParams2) {
if (this.mapParams.containsKey("ValidCauser")) {
if (!matchesValid(runParams2.get("Causer"), this.mapParams.get("ValidCauser").split(","),
this.getHostCard())) {
public final boolean performTest(final Map<String, Object> runParams2) {
if (hasParam("ValidCauser")) {
if (!matchesValid(runParams2.get("Causer"), getParam("ValidCauser").split(","), getHostCard())) {
return false;
}
}
if (this.mapParams.containsKey("ValidCard")) {
if (!matchesValid(runParams2.get("Card"), this.mapParams.get("ValidCard").split(","),
this.getHostCard())) {
if (hasParam("ValidCard")) {
if (!matchesValid(runParams2.get("Card"), getParam("ValidCard").split(","), getHostCard())) {
return false;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.trigger;
import java.util.Map;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
/**
* <p>
* Trigger_Destroyed class.
* </p>
*
* @author Forge
* @version $Id: TriggerDestroyed.java 17802 2012-10-31 08:05:14Z Max mtg $
*/
public class TriggerRegenerated extends Trigger {
/**
* <p>
* Constructor for Trigger_Destroyed.
* </p>
*
* @param params
* a {@link java.util.HashMap} object.
* @param host
* a {@link forge.game.card.Card} object.
* @param intrinsic
* the intrinsic
*/
public TriggerRegenerated(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/** {@inheritDoc} */
@Override
public final boolean performTest(final Map<String, Object> runParams2) {
if (hasParam("ValidCause")) {
if (!matchesValid(runParams2.get("Cause"), getParam("ValidCause").split(","), getHostCard())) {
return false;
}
}
if (hasParam("ValidCard")) {
if (!matchesValid(runParams2.get("Card"), getParam("ValidCard").split(","), getHostCard())) {
return false;
}
}
return true;
}
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa) {
sa.setTriggeringObject("Card", this.getRunParams().get("Card"));
sa.setTriggeringObject("Cause", this.getRunParams().get("Cause"));
}
@Override
public String getImportantStackObjects(SpellAbility sa) {
StringBuilder sb = new StringBuilder();
sb.append("Regenerated: ").append(sa.getTriggeringObject("Card"));
//sb.append("Destroyer: ").append(sa.getTriggeringObject("Causer"));
return sb.toString();
}
}

View File

@@ -67,6 +67,7 @@ public enum TriggerType {
PlanarDice(TriggerPlanarDice.class),
PlaneswalkedFrom(TriggerPlaneswalkedFrom.class),
PlaneswalkedTo(TriggerPlaneswalkedTo.class),
Regenerated(TriggerRegenerated.class),
Revealed(TriggerRevealed.class),
Sacrificed(TriggerSacrificed.class),
Scry(TriggerScry.class),