foul-magics/Mage/src/main/java/mage/game/command/dungeons/TombOfAnnihilation.java
Evan Kranzler bb591dd038
[AFR] Implementing dungeon mechanic (ready for review) (#7937)
* added dungeon and dungeon room class

* [AFR] Implemented Tomb of Annihilation

* [AFR] Implemented Shortcut Seeker

* [AFR] Implemented Gloom Stalker

* [AFR] Implemented Nadaar, Selfless Paladin

* added room triggers

* added more venturing code, currently untested

* fixed error

* moved venture into dungeon from player class to game class

* removed unnecessary sourceobject from dungeon

* fixed npe error

* added dungeon completion

* fixed concurrent modification exception

* added logging

* added proper copy methods

* added views

* updated room text generation

* added some missing code

* finished implementing CompletedDungeonCondition

* [AFR] Implemented Ellywick Tumblestrum

* [AFR] Implemented Lost Mine of Phandelver

* added choice dialog for dungeons

* [AFR] Implemented Dungeon of the Mad Mage

* small text fix

* added initial dungeon test

* [AFR] Implemented Cloister Gargoyle

* [AFR] Implemented Dungeon Crawler

* small text change for dungeon rooms

* added more tests

* some simplification to dungeon props

* updated testing helper functions

* added currently failing test for venturing on separate steps and turns

* added tests for dungeon completion

* fixed missing trigger visual and dungeons not persisting through turns

* some text updates

* added rollback test

* added a test for multiple dungeons at once

* added one more condition test
2021-06-29 06:57:43 -04:00

266 lines
8.9 KiB
Java

package mage.game.command.dungeons;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.CardTypeAssignment;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.LoseLifeAllPlayersEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.command.Dungeon;
import mage.game.command.DungeonRoom;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.TheAtropalToken;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetDiscard;
import java.util.*;
/**
* @author TheElk801
*/
public final class TombOfAnnihilation extends Dungeon {
static final FilterControlledPermanent filter
= new FilterControlledPermanent("an artifact, a creature, or a land");
static {
filter.add(Predicates.or(
CardType.ARTIFACT.getPredicate(),
CardType.CREATURE.getPredicate(),
CardType.LAND.getPredicate()
));
}
public TombOfAnnihilation() {
super("Tomb of Annihilation", "AFR");
// (1) Trapped Entry — Each player loses 1 life. (→ 2a or 2b)
DungeonRoom trappedEntry = new DungeonRoom("Trapped Entry", new LoseLifeAllPlayersEffect(1));
// (2a) Veils of Fear — Each player loses 2 life unless they discard a card. (→ 3)
DungeonRoom veilsOfFear = new DungeonRoom("Veils of Fear", new VeilsOfFearEffect());
// (2b) Oubliette — Discard a card and sacrifice an artifact, a creature, and a land. (→ 4)
DungeonRoom oubliette = new DungeonRoom("Oubliette", new OublietteEffect());
// (3) Sandfall Cell — Each player loses 2 life unless they sacrifice an artifact, a creature, or a land. (→ 4)
DungeonRoom sandfallCell = new DungeonRoom("Sandfall Cell", new SandfallCellEffect());
// (4) Cradle of the Death God — Create The Atropal, a legendary 4/4 black God Horror creature token with deathtouch.
DungeonRoom cradleOfTheDeathGod = new DungeonRoom("Cradle of the Death God", new CreateTokenEffect(new TheAtropalToken()));
trappedEntry.addNextRoom(veilsOfFear);
trappedEntry.addNextRoom(oubliette);
veilsOfFear.addNextRoom(sandfallCell);
oubliette.addNextRoom(sandfallCell);
sandfallCell.addNextRoom(cradleOfTheDeathGod);
this.addRoom(trappedEntry);
this.addRoom(veilsOfFear);
this.addRoom(oubliette);
this.addRoom(sandfallCell);
this.addRoom(cradleOfTheDeathGod);
}
private TombOfAnnihilation(final TombOfAnnihilation dungeon) {
super(dungeon);
}
public TombOfAnnihilation copy() {
return new TombOfAnnihilation(this);
}
}
class VeilsOfFearEffect extends OneShotEffect {
VeilsOfFearEffect() {
super(Outcome.Neutral);
staticText = "each player loses 2 life unless they discard a card";
}
private VeilsOfFearEffect(final VeilsOfFearEffect effect) {
super(effect);
}
@Override
public VeilsOfFearEffect copy() {
return new VeilsOfFearEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Map<UUID, Card> map = new HashMap<>();
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
TargetDiscard target = new TargetDiscard(0, 1, StaticFilters.FILTER_CARD, playerId);
player.choose(Outcome.PreventDamage, target, source.getSourceId(), game);
map.put(playerId, game.getCard(target.getFirstTarget()));
}
for (Map.Entry<UUID, Card> entry : map.entrySet()) {
Player player = game.getPlayer(entry.getKey());
if (player == null) {
continue;
}
if (entry.getValue() != null) {
player.discard(entry.getValue(), false, source, game);
} else {
player.loseLife(2, game, source, false);
}
}
return true;
}
}
class OublietteEffect extends OneShotEffect {
OublietteEffect() {
super(Outcome.Sacrifice);
staticText = "discard a card and sacrifice an artifact, a creature, and a land";
}
private OublietteEffect(final OublietteEffect effect) {
super(effect);
}
@Override
public OublietteEffect copy() {
return new OublietteEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
player.discard(1, false, false, source, game);
int saccable = OublietteTarget.checkTargetCount(source, game);
if (saccable < 1) {
return true;
}
OublietteTarget target = new OublietteTarget(Math.min(saccable, 3));
player.choose(Outcome.Sacrifice, target, source.getSourceId(), game);
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
permanent.sacrifice(source, game);
}
}
return true;
}
}
class OublietteTarget extends TargetControlledPermanent {
private static final CardTypeAssignment cardTypeAssigner = new CardTypeAssignment(
CardType.ARTIFACT,
CardType.CREATURE,
CardType.LAND
);
private static final FilterControlledPermanent filter = TombOfAnnihilation.filter.copy();
static {
filter.setMessage("an artifact, a creature, and a land");
}
OublietteTarget(int numTargets) {
super(numTargets, numTargets, filter, true);
}
private OublietteTarget(final OublietteTarget target) {
super(target);
}
@Override
public OublietteTarget copy() {
return new OublietteTarget(this);
}
@Override
public boolean canTarget(UUID playerId, UUID id, Ability ability, Game game) {
if (!super.canTarget(playerId, id, ability, game)) {
return false;
}
Permanent permanent = game.getPermanent(id);
if (permanent == null) {
return false;
}
if (this.getTargets().isEmpty()) {
return true;
}
Cards cards = new CardsImpl(this.getTargets());
cards.add(permanent);
return cardTypeAssigner.getRoleCount(cards, game) >= cards.size();
}
@Override
public Set<UUID> possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) {
Set<UUID> possibleTargets = super.possibleTargets(sourceId, sourceControllerId, game);
possibleTargets.removeIf(uuid -> !this.canTarget(sourceControllerId, uuid, null, game));
return possibleTargets;
}
static int checkTargetCount(Ability source, Game game) {
List<Permanent> permanents = game
.getBattlefield()
.getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game);
return cardTypeAssigner.getRoleCount(new CardsImpl(permanents), game);
}
}
class SandfallCellEffect extends OneShotEffect {
SandfallCellEffect() {
super(Outcome.Neutral);
staticText = "each player loses 2 life unless they sacrifice an artifact, a creature, or a land";
}
private SandfallCellEffect(final SandfallCellEffect effect) {
super(effect);
}
@Override
public SandfallCellEffect copy() {
return new SandfallCellEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Map<UUID, Permanent> map = new HashMap<>();
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
TargetPermanent target = new TargetPermanent(0, 1, TombOfAnnihilation.filter, true);
player.choose(Outcome.PreventDamage, target, source.getSourceId(), game);
map.put(playerId, game.getPermanent(target.getFirstTarget()));
}
for (Map.Entry<UUID, Permanent> entry : map.entrySet()) {
Player player = game.getPlayer(entry.getKey());
if (player == null) {
continue;
}
if (entry.getValue() != null) {
entry.getValue().sacrifice(source, game);
} else {
player.loseLife(2, game, source, false);
}
}
return true;
}
}