mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 11:32:00 -08:00
[C21] Implemented Combat Calligrapher
This commit is contained in:
parent
1fad23b9fb
commit
0c3b294527
7 changed files with 193 additions and 41 deletions
|
|
@ -16,16 +16,16 @@ import mage.filter.FilterPermanent;
|
||||||
import mage.filter.common.FilterEquipmentPermanent;
|
import mage.filter.common.FilterEquipmentPermanent;
|
||||||
import mage.filter.predicate.ObjectPlayer;
|
import mage.filter.predicate.ObjectPlayer;
|
||||||
import mage.filter.predicate.ObjectPlayerPredicate;
|
import mage.filter.predicate.ObjectPlayerPredicate;
|
||||||
import mage.filter.predicate.permanent.EquippedPredicate;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.events.DefenderAttackedEvent;
|
||||||
import mage.game.events.GameEvent;
|
import mage.game.events.GameEvent;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.targetpointer.FixedTarget;
|
import mage.target.targetpointer.FixedTarget;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,14 +63,6 @@ public final class AkiriFearlessVoyager extends CardImpl {
|
||||||
|
|
||||||
class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl {
|
class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
|
|
||||||
private static final FilterPermanent filter = new FilterPermanent();
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(EquippedPredicate.instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Set<UUID> attackedPlayerIds = new HashSet<>();
|
|
||||||
|
|
||||||
AkiriFearlessVoyagerTriggeredAbility() {
|
AkiriFearlessVoyagerTriggeredAbility() {
|
||||||
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false);
|
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false);
|
||||||
}
|
}
|
||||||
|
|
@ -86,28 +78,21 @@ class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkEventType(GameEvent event, Game game) {
|
public boolean checkEventType(GameEvent event, Game game) {
|
||||||
return event.getType() == GameEvent.EventType.ATTACKER_DECLARED
|
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
|
||||||
|| event.getType() == GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkTrigger(GameEvent event, Game game) {
|
public boolean checkTrigger(GameEvent event, Game game) {
|
||||||
if (event.getType() == GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST) {
|
return isControlledBy(event.getPlayerId())
|
||||||
attackedPlayerIds.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) {
|
|
||||||
Permanent creature = game.getPermanent(event.getSourceId());
|
|
||||||
if (creature != null
|
|
||||||
&& creature.isControlledBy(controllerId)
|
|
||||||
&& filter.match(creature, game)
|
|
||||||
&& game.getPlayer(event.getTargetId()) != null
|
&& game.getPlayer(event.getTargetId()) != null
|
||||||
&& !attackedPlayerIds.contains(event.getTargetId())) {
|
&& ((DefenderAttackedEvent) event)
|
||||||
attackedPlayerIds.add(event.getTargetId());
|
.getAttackers(game)
|
||||||
return true;
|
.stream()
|
||||||
}
|
.map(Permanent::getAttachments)
|
||||||
}
|
.flatMap(Collection::stream)
|
||||||
return false;
|
.map(game::getPermanent)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.anyMatch(permanent -> permanent.hasSubtype(SubType.EQUIPMENT, game));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
125
Mage.Sets/src/mage/cards/c/CombatCalligrapher.java
Normal file
125
Mage.Sets/src/mage/cards/c/CombatCalligrapher.java
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.TriggeredAbilityImpl;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||||
|
import mage.abilities.effects.RestrictionEffect;
|
||||||
|
import mage.abilities.effects.common.CreateTokenTargetEffect;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.game.permanent.token.SilverquillToken;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class CombatCalligrapher extends CardImpl {
|
||||||
|
|
||||||
|
public CombatCalligrapher(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.BIRD);
|
||||||
|
this.subtype.add(SubType.CLERIC);
|
||||||
|
this.power = new MageInt(3);
|
||||||
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
|
// Flying
|
||||||
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
|
// Inklings can't attack you or planeswalkers you control.
|
||||||
|
this.addAbility(new SimpleStaticAbility(new CombatCalligrapherEffect()));
|
||||||
|
|
||||||
|
// Whenever a player attacks one of your opponents, that attacking player creates a tapped 2/1 white and black Inkling creature token with flying that's attacking that opponent.
|
||||||
|
this.addAbility(new CombatCalligrapherTriggeredAbility());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CombatCalligrapher(final CombatCalligrapher card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CombatCalligrapher copy() {
|
||||||
|
return new CombatCalligrapher(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CombatCalligrapherTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
|
|
||||||
|
CombatCalligrapherTriggeredAbility() {
|
||||||
|
super(Zone.BATTLEFIELD, new CreateTokenTargetEffect(
|
||||||
|
new SilverquillToken(), StaticValue.get(1), true, true
|
||||||
|
), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CombatCalligrapherTriggeredAbility(final CombatCalligrapherTriggeredAbility ability) {
|
||||||
|
super(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CombatCalligrapherTriggeredAbility copy() {
|
||||||
|
return new CombatCalligrapherTriggeredAbility(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkEventType(GameEvent event, Game game) {
|
||||||
|
return event.getType() == GameEvent.EventType.DEFENDER_ATTACKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkTrigger(GameEvent event, Game game) {
|
||||||
|
if (!game.getOpponents(getControllerId()).contains(event.getTargetId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
getEffects().setValue("playerToAttack", event.getPlayerId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRule() {
|
||||||
|
return "Whenever a player attacks one of your opponents, that attacking player creates " +
|
||||||
|
"a tapped 2/1 white and black Inkling creature token with flying that's attacking that opponent.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CombatCalligrapherEffect extends RestrictionEffect {
|
||||||
|
|
||||||
|
public CombatCalligrapherEffect() {
|
||||||
|
super(Duration.WhileOnBattlefield);
|
||||||
|
this.staticText = "Inklings can't attack you or planeswalkers you control";
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombatCalligrapherEffect(final CombatCalligrapherEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||||
|
return permanent.hasSubtype(SubType.INKLING, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||||
|
if (source.isControlledBy(defenderId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Permanent planeswalker = game.getPermanent(defenderId);
|
||||||
|
return planeswalker == null || !planeswalker.isControlledBy(source.getControllerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CombatCalligrapherEffect copy() {
|
||||||
|
return new CombatCalligrapherEffect(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -69,6 +69,7 @@ public final class Commander2021Edition extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Citadel Siege", 85, Rarity.RARE, mage.cards.c.CitadelSiege.class));
|
cards.add(new SetCardInfo("Citadel Siege", 85, Rarity.RARE, mage.cards.c.CitadelSiege.class));
|
||||||
cards.add(new SetCardInfo("Cleansing Nova", 86, Rarity.RARE, mage.cards.c.CleansingNova.class));
|
cards.add(new SetCardInfo("Cleansing Nova", 86, Rarity.RARE, mage.cards.c.CleansingNova.class));
|
||||||
cards.add(new SetCardInfo("Coiling Oracle", 212, Rarity.COMMON, mage.cards.c.CoilingOracle.class));
|
cards.add(new SetCardInfo("Coiling Oracle", 212, Rarity.COMMON, mage.cards.c.CoilingOracle.class));
|
||||||
|
cards.add(new SetCardInfo("Combat Calligrapher", 14, Rarity.RARE, mage.cards.c.CombatCalligrapher.class));
|
||||||
cards.add(new SetCardInfo("Combustible Gearhulk", 163, Rarity.MYTHIC, mage.cards.c.CombustibleGearhulk.class));
|
cards.add(new SetCardInfo("Combustible Gearhulk", 163, Rarity.MYTHIC, mage.cards.c.CombustibleGearhulk.class));
|
||||||
cards.add(new SetCardInfo("Command Tower", 284, Rarity.COMMON, mage.cards.c.CommandTower.class));
|
cards.add(new SetCardInfo("Command Tower", 284, Rarity.COMMON, mage.cards.c.CommandTower.class));
|
||||||
cards.add(new SetCardInfo("Commander's Insight", 23, Rarity.RARE, mage.cards.c.CommandersInsight.class));
|
cards.add(new SetCardInfo("Commander's Insight", 23, Rarity.RARE, mage.cards.c.CommandersInsight.class));
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import mage.game.Game;
|
||||||
import mage.game.permanent.token.Token;
|
import mage.game.permanent.token.Token;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Loki
|
* @author Loki
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,7 +59,7 @@ public class CreateTokenTargetEffect extends OneShotEffect {
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
int value = amount.calculate(game, source, this);
|
int value = amount.calculate(game, source, this);
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
return token.putOntoBattlefield(value, game, source, targetPointer.getFirst(game, source), tapped, attacking);
|
return token.putOntoBattlefield(value, game, source, targetPointer.getFirst(game, source), tapped, attacking, (UUID) getValue("playerToAttack"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
package mage.game.combat;
|
package mage.game.combat;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.*;
|
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
|
import mage.MageObjectReference;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.effects.RequirementEffect;
|
import mage.abilities.effects.RequirementEffect;
|
||||||
import mage.abilities.effects.RestrictionEffect;
|
import mage.abilities.effects.RestrictionEffect;
|
||||||
|
|
@ -23,11 +22,7 @@ import mage.filter.predicate.mageobject.NamePredicate;
|
||||||
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
|
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
|
||||||
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.events.AttackerDeclaredEvent;
|
import mage.game.events.*;
|
||||||
import mage.game.events.DeclareAttackerEvent;
|
|
||||||
import mage.game.events.DeclareBlockerEvent;
|
|
||||||
import mage.game.events.GameEvent;
|
|
||||||
import mage.game.events.GameEvent.EventType;
|
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.players.PlayerList;
|
import mage.players.PlayerList;
|
||||||
|
|
@ -38,6 +33,9 @@ import mage.util.Copyable;
|
||||||
import mage.util.trace.TraceUtil;
|
import mage.util.trace.TraceUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
|
|
@ -269,6 +267,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public void resumeSelectAttackers(Game game) {
|
public void resumeSelectAttackers(Game game) {
|
||||||
|
Map<UUID, Set<MageObjectReference>> morSetMap = new HashMap<>();
|
||||||
for (CombatGroup group : groups) {
|
for (CombatGroup group : groups) {
|
||||||
for (UUID attacker : group.getAttackers()) {
|
for (UUID attacker : group.getAttackers()) {
|
||||||
if (attackersTappedByAttack.contains(attacker)) {
|
if (attackersTappedByAttack.contains(attacker)) {
|
||||||
|
|
@ -282,10 +281,12 @@ public class Combat implements Serializable, Copyable<Combat> {
|
||||||
// This can only be used to modify the event, the attack can't be replaced here
|
// This can only be used to modify the event, the attack can't be replaced here
|
||||||
game.replaceEvent(new AttackerDeclaredEvent(group.defenderId, attacker, attackingPlayerId));
|
game.replaceEvent(new AttackerDeclaredEvent(group.defenderId, attacker, attackingPlayerId));
|
||||||
game.addSimultaneousEvent(new AttackerDeclaredEvent(group.defenderId, attacker, attackingPlayerId));
|
game.addSimultaneousEvent(new AttackerDeclaredEvent(group.defenderId, attacker, attackingPlayerId));
|
||||||
|
morSetMap.computeIfAbsent(group.defenderId, x -> new HashSet<>()).add(new MageObjectReference(attacker, game));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attackersTappedByAttack.clear();
|
attackersTappedByAttack.clear();
|
||||||
|
|
||||||
|
DefenderAttackedEvent.makeAddEvents(morSetMap, attackingPlayerId, game);
|
||||||
game.addSimultaneousEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackingPlayerId, attackingPlayerId));
|
game.addSimultaneousEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackingPlayerId, attackingPlayerId));
|
||||||
if (!game.isSimulation()) {
|
if (!game.isSimulation()) {
|
||||||
Player player = game.getPlayer(attackingPlayerId);
|
Player player = game.getPlayer(attackingPlayerId);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package mage.game.events;
|
||||||
|
|
||||||
|
import mage.MageObjectReference;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public class DefenderAttackedEvent extends GameEvent {
|
||||||
|
|
||||||
|
private final Set<MageObjectReference> morSet = new HashSet<>();
|
||||||
|
|
||||||
|
public DefenderAttackedEvent(UUID targetId, UUID playerId) {
|
||||||
|
super(EventType.DEFENDER_ATTACKED, targetId, null, playerId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void makeAddEvents(Map<UUID, Set<MageObjectReference>> morMapSet, UUID attackingPlayerId, Game game) {
|
||||||
|
for (Map.Entry<UUID, Set<MageObjectReference>> entry : morMapSet.entrySet()) {
|
||||||
|
DefenderAttackedEvent event = new DefenderAttackedEvent(entry.getKey(), attackingPlayerId);
|
||||||
|
event.morSet.addAll(entry.getValue());
|
||||||
|
game.addSimultaneousEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Permanent> getAttackers(Game game) {
|
||||||
|
return morSet
|
||||||
|
.stream()
|
||||||
|
.map(mor -> mor.getPermanent(game))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -275,6 +275,7 @@ public class GameEvent implements Serializable {
|
||||||
amount not used for this event
|
amount not used for this event
|
||||||
flag not used for this event
|
flag not used for this event
|
||||||
*/
|
*/
|
||||||
|
DEFENDER_ATTACKED,
|
||||||
DECLARING_BLOCKERS,
|
DECLARING_BLOCKERS,
|
||||||
DECLARED_BLOCKERS,
|
DECLARED_BLOCKERS,
|
||||||
DECLARE_BLOCKER,
|
DECLARE_BLOCKER,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue