[OTJ] Implementing "spree" mechanic (#12018)

* [OTJ] Implement Unfortunate Accident

* fix errors

* a few more things

* [OTJ] Implement Three Steps Ahead

* [OTJ] Implement Caught in the Crossfire

* [OTJ] Implement Insatiable Avarice

* add test

* [OTJ] Implement Explosive Derailment

* [OTJ] Implement Requisition Raid

* [OTJ] Implement Rustler Rampage

* add comment to test

* [OTJ] Implement Metamorphic Blast

* [OTJ] Implement Final Showdown

* rework cost addition, add test

* move cost application to its own loop
This commit is contained in:
Evan Kranzler 2024-03-31 12:11:34 -04:00 committed by GitHub
parent fa67b0450f
commit ba20e97b71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 740 additions and 2 deletions

View file

@ -514,6 +514,14 @@ public interface Ability extends Controllable, Serializable {
*/
Ability withFirstModeFlavorWord(String flavorWord);
/**
* Sets cost word for first mode
*
* @param cost
* @return
*/
Ability withFirstModeCost(Cost cost);
/**
* Creates the message about the ability casting/triggering/activating to
* post in the game log before the ability resolves.

View file

@ -311,6 +311,16 @@ public abstract class AbilityImpl implements Ability {
return false;
}
// apply mode costs if they have them
for (UUID modeId : this.getModes().getSelectedModes()) {
Cost cost = this.getModes().get(modeId).getCost();
if (cost instanceof ManaCost) {
this.addManaCostsToPay((ManaCost) cost.copy());
} else if (cost != null) {
this.costs.add(cost.copy());
}
}
// unit tests only: it allows to add targets/choices by two ways:
// 1. From cast/activate command params (process it here)
// 2. From single addTarget/setChoice, it's a preffered method for tests (process it in normal choose dialogs like human player)
@ -1141,6 +1151,12 @@ public abstract class AbilityImpl implements Ability {
return this;
}
@Override
public Ability withFirstModeCost(Cost cost) {
this.modes.getMode().withCost(cost);
return this;
}
@Override
public String getGameLogMessage(Game game) {
if (game.isSimulation()) {

View file

@ -1,5 +1,6 @@
package mage.abilities;
import mage.abilities.costs.Cost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.target.Target;
@ -17,6 +18,7 @@ public class Mode implements Serializable {
protected final Targets targets;
protected final Effects effects;
protected String flavorWord;
protected Cost cost = null;
/**
* Optional Tag to distinguish this mode from others.
* In the case of modes that players can only choose once,
@ -39,6 +41,7 @@ public class Mode implements Serializable {
this.effects = mode.effects.copy();
this.flavorWord = mode.flavorWord;
this.modeTag = mode.modeTag;
this.cost = mode.cost != null ? mode.cost.copy() : null;
}
public UUID setRandomId() {
@ -107,4 +110,13 @@ public class Mode implements Serializable {
this.flavorWord = flavorWord;
return this;
}
public Mode withCost(Cost cost) {
this.cost = cost;
return this;
}
public Cost getCost() {
return cost;
}
}

View file

@ -584,7 +584,14 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
sb.append("<br>");
for (Mode mode : this.values()) {
sb.append("&bull ");
if (mode.getCost() != null) {
// for Spree
sb.append("+ ");
sb.append(mode.getCost().getText());
sb.append(" &mdash; ");
} else {
sb.append("&bull ");
}
sb.append(mode.getEffects().getTextStartingUpperCase(mode));
sb.append("<br>");
}

View file

@ -0,0 +1,28 @@
package mage.abilities.keyword;
import mage.abilities.StaticAbility;
import mage.cards.Card;
import mage.constants.Zone;
/**
* @author TheElk801
*/
public class SpreeAbility extends StaticAbility {
public SpreeAbility(Card card) {
super(Zone.ALL, null);
this.setRuleVisible(false);
card.getSpellAbility().getModes().setChooseText("Spree <i>(Choose one or more additional costs.)</i>");
card.getSpellAbility().getModes().setMinModes(1);
card.getSpellAbility().getModes().setMaxModes(Integer.MAX_VALUE);
}
private SpreeAbility(final SpreeAbility ability) {
super(ability);
}
@Override
public SpreeAbility copy() {
return new SpreeAbility(this);
}
}

View file

@ -414,14 +414,17 @@ public class StackAbility extends StackObjectImpl implements Ability {
public void addManaCostsToPay(ManaCost manaCost) {
// Do nothing
}
@Override
public Map<String, Object> getCostsTagMap() {
return ability.getCostsTagMap();
}
@Override
public void setCostsTag(String tag, Object value){
public void setCostsTag(String tag, Object value) {
ability.setCostsTag(tag, value);
}
@Override
public AbilityType getAbilityType() {
return ability.getAbilityType();
@ -539,6 +542,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Ability withFirstModeCost(Cost cost) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game) {
throw new UnsupportedOperationException("Not supported yet.");