mirror of
https://github.com/magefree/mage.git
synced 2025-12-29 23:12:10 -08:00
Implementing "collect evidence" mechanic (#11671)
* [MKM] Implement Axebane Ferox * add exile to cost, fix text * add targeting message copied from crew ability * [MKM] Implement Forensic Researcher * [MKM] Implement Izoni, Center of the Web * implement requested changes * merge fix * [MKM] Implement Sample Collector * [MKM] Implement Evidence Examiner * [MKM] Implement Surveillance Monitor * [MKM] Implement Vitu-Ghazi Inspector * [MKM] Implement Crimestopper Sprite * [MKM] Implement Urgent Necropsy * [MKM] Implement Analyze the Pollen * implement requested changes * add can pay cost check to counter unless pays effect * fix test failure * add tests * fix prompt message
This commit is contained in:
parent
322c49e37f
commit
99c2ffa231
21 changed files with 1162 additions and 4 deletions
|
|
@ -0,0 +1,37 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CollectEvidenceTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
public CollectEvidenceTriggeredAbility(Effect effect, boolean optional) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
setTriggerPhrase("Whenever you collect evidence, ");
|
||||
}
|
||||
|
||||
private CollectEvidenceTriggeredAbility(final CollectEvidenceTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectEvidenceTriggeredAbility copy() {
|
||||
return new CollectEvidenceTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.EVIDENCE_COLLECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return isControlledBy(event.getPlayerId());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.keyword.CollectEvidenceAbility;
|
||||
import mage.game.Game;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* Checks if the spell was cast with the alternate collect evidence cost
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public enum CollectedEvidenceCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return CardUtil.checkSourceCostsTagExists(game, source, CollectEvidenceAbility.COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Evidence was collected";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package mage.abilities.costs.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.hint.HintUtils;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetCardInYourGraveyard;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CollectEvidenceCost extends CostImpl {
|
||||
|
||||
private final int amount;
|
||||
|
||||
public CollectEvidenceCost(int amount) {
|
||||
super();
|
||||
this.amount = amount;
|
||||
this.text = "collect evidence " + amount;
|
||||
}
|
||||
|
||||
private CollectEvidenceCost(final CollectEvidenceCost cost) {
|
||||
super(cost);
|
||||
this.amount = cost.amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
Player player = game.getPlayer(controllerId);
|
||||
return player != null && player
|
||||
.getGraveyard()
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(MageObject::getManaValue)
|
||||
.sum() >= amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
Player player = game.getPlayer(controllerId);
|
||||
if (player == null) {
|
||||
paid = false;
|
||||
return paid;
|
||||
}
|
||||
// TODO: require target to have minimum selected total mana value (requires refactor)
|
||||
Target target = new TargetCardInYourGraveyard(1, Integer.MAX_VALUE) {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
// shows selected mana value
|
||||
int totalMV = this
|
||||
.getTargets()
|
||||
.stream()
|
||||
.map(game::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(MageObject::getManaValue)
|
||||
.sum();
|
||||
return super.getMessage() + HintUtils.prepareText(
|
||||
" (selected mana value " + totalMV + " of " + amount + ")",
|
||||
totalMV >= amount ? Color.GREEN : Color.RED
|
||||
);
|
||||
}
|
||||
}.withNotTarget(true);
|
||||
player.choose(Outcome.Exile, target, source, game);
|
||||
Cards cards = new CardsImpl(target.getTargets());
|
||||
paid = cards
|
||||
.getCards(game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(MageObject::getManaValue)
|
||||
.sum() >= amount;
|
||||
if (paid) {
|
||||
player.moveCards(cards, Zone.EXILED, source, game);
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.EVIDENCE_COLLECTED,
|
||||
source.getSourceId(), source, source.getControllerId(), amount
|
||||
));
|
||||
}
|
||||
return paid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectEvidenceCost copy() {
|
||||
return new CollectEvidenceCost(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +84,8 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
|
|||
message += costValueMessage + '?';
|
||||
|
||||
costToPay.clearPaid();
|
||||
if (!(player.chooseUse(Outcome.Benefit, message, source, game)
|
||||
if (!(costToPay.canPay(source, source, player.getId(), game)
|
||||
&& player.chooseUse(Outcome.Benefit, message, source, game)
|
||||
&& costToPay.pay(source, game, source, spell.getControllerId(), false, null))) {
|
||||
game.informPlayers(player.getLogName() + " chooses not to pay " + costValueMessage + " to prevent the counter effect");
|
||||
game.getStack().counter(spell.getId(), source, game, exile ? PutCards.EXILED : PutCards.GRAVEYARD);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.condition.common.CollectedEvidenceCondition;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.common.CollectEvidenceCost;
|
||||
import mage.abilities.hint.ConditionTrueHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CollectEvidenceAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
|
||||
|
||||
private static final String promptString = "Collect evidence ";
|
||||
private static final String keywordText = "As an additional cost to cast this spell, you may collect evidence ";
|
||||
private static final String reminderText = "Exile cards with total mana value $$$ or greater from your graveyard";
|
||||
private final String rule;
|
||||
private final int amount;
|
||||
|
||||
public static final String COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY = "collectEvidenceActivation";
|
||||
|
||||
protected OptionalAdditionalCost additionalCost;
|
||||
|
||||
private static final Hint hint = new ConditionTrueHint(CollectedEvidenceCondition.instance, "evidence was collected");
|
||||
|
||||
public static OptionalAdditionalCost makeCost(int amount) {
|
||||
OptionalAdditionalCost cost = new OptionalAdditionalCostImpl(
|
||||
keywordText + amount,
|
||||
reminderText.replace("$$$", "" + amount),
|
||||
new CollectEvidenceCost(amount)
|
||||
);
|
||||
cost.setRepeatable(false);
|
||||
return cost;
|
||||
}
|
||||
|
||||
public CollectEvidenceAbility(int amount) {
|
||||
super(Zone.STACK, null);
|
||||
this.additionalCost = makeCost(amount);
|
||||
this.rule = additionalCost.getName() + ' ' + additionalCost.getReminderText();
|
||||
this.setRuleAtTheTop(true);
|
||||
this.addHint(hint);
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
private CollectEvidenceAbility(final CollectEvidenceAbility ability) {
|
||||
super(ability);
|
||||
this.rule = ability.rule;
|
||||
this.additionalCost = ability.additionalCost.copy();
|
||||
this.amount = ability.amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectEvidenceAbility copy() {
|
||||
return new CollectEvidenceAbility(this);
|
||||
}
|
||||
|
||||
public void resetCost() {
|
||||
if (additionalCost != null) {
|
||||
additionalCost.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetCost();
|
||||
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
|
||||
if (!canPay || !player.chooseUse(Outcome.Exile, promptString + amount + '?', ability, game)) {
|
||||
return;
|
||||
}
|
||||
|
||||
additionalCost.activate();
|
||||
for (Cost cost : ((Costs<Cost>) additionalCost)) {
|
||||
ability.getCosts().add(cost.copy());
|
||||
}
|
||||
ability.setCostsTag(COLLECT_EVIDENCE_ACTIVATION_VALUE_KEY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCastMessageSuffix() {
|
||||
return additionalCost.getCastSuffixMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
|
@ -573,6 +573,12 @@ public class GameEvent implements Serializable {
|
|||
playerId the player suspecting
|
||||
*/
|
||||
BECOME_SUSPECTED,
|
||||
/* Evidence collected
|
||||
targetId same as sourceId
|
||||
sourceId of the ability for the cost
|
||||
playerId the player paying the cost
|
||||
*/
|
||||
EVIDENCE_COLLECTED,
|
||||
//custom events
|
||||
CUSTOM_EVENT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.keyword.MenaceAbility;
|
||||
import mage.abilities.keyword.ReachAbility;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class IzoniSpiderToken extends TokenImpl {
|
||||
|
||||
public IzoniSpiderToken() {
|
||||
super("Spider Token", "2/1 black and green Spider creature token with menace and reach");
|
||||
cardType.add(CardType.CREATURE);
|
||||
color.setBlack(true);
|
||||
color.setGreen(true);
|
||||
subtype.add(SubType.SPIDER);
|
||||
power = new MageInt(2);
|
||||
toughness = new MageInt(1);
|
||||
|
||||
// Menace
|
||||
this.addAbility(new MenaceAbility());
|
||||
|
||||
// Reach
|
||||
this.addAbility(ReachAbility.getInstance());
|
||||
}
|
||||
|
||||
private IzoniSpiderToken(final IzoniSpiderToken token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
public IzoniSpiderToken copy() {
|
||||
return new IzoniSpiderToken(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ public final class CardUtil {
|
|||
public static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
|
||||
private static final List<String> costWords = Arrays.asList(
|
||||
"put", "return", "exile", "discard", "sacrifice", "remove", "tap", "reveal", "pay"
|
||||
"put", "return", "exile", "discard", "sacrifice", "remove", "tap", "reveal", "pay", "collect"
|
||||
);
|
||||
|
||||
public static final int TESTS_SET_CODE_LOOKUP_LENGTH = 6; // search set code in commands like "set_code-card_name"
|
||||
|
|
@ -1764,8 +1764,8 @@ public final class CardUtil {
|
|||
* Warning, don't use self reference objects because it will raise StackOverflowError
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> T deepCopyObject(T value) {
|
||||
if (isImmutableObject(value)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue