[CLB] Implemented White Plume Adventurer

This commit is contained in:
Evan Kranzler 2022-05-17 20:18:18 -04:00
parent 4777466b50
commit 86dad5e54f
13 changed files with 524 additions and 9 deletions

View file

@ -0,0 +1,32 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
/**
* @author TheElk801
*/
public class TakeTheInitiativeEffect extends OneShotEffect {
public TakeTheInitiativeEffect() {
super(Outcome.Benefit);
staticText = "you take the initiative";
}
private TakeTheInitiativeEffect(final TakeTheInitiativeEffect effect) {
super(effect);
}
@Override
public TakeTheInitiativeEffect copy() {
return new TakeTheInitiativeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
game.takeInitiative(source, source.getControllerId());
return true;
}
}

View file

@ -26,7 +26,7 @@ public class VentureIntoTheDungeonEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
game.ventureIntoDungeon(source.getControllerId());
game.ventureIntoDungeon(source.getControllerId(), false);
return true;
}
}

View file

@ -1,13 +1,12 @@
package mage.designations;
/**
*
* @author LevelX2
*/
public enum DesignationType {
THE_MONARCH("The Monarch"),
CITYS_BLESSING("City's Blessing");
CITYS_BLESSING("City's Blessing"),
THE_INITIATIVE("The Initiative");
private final String text;

View file

@ -0,0 +1,179 @@
package mage.designations;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.events.DamagedPlayerBatchEvent;
import mage.game.events.GameEvent;
import mage.target.targetpointer.FixedTarget;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
public class Initiative extends Designation {
public Initiative() {
super(DesignationType.THE_INITIATIVE, "CLB");
// Whenever one or more creatures a player controls deals combat damage to you, that player takes the initiative.
this.addAbility(new InitiativeDamageTriggeredAbility());
// Whenever you take the initiative and at the beginning of your upkeep, venture into Undercity.
this.addAbility(new InitiativeVentureTriggeredAbility());
}
private Initiative(final Initiative card) {
super(card);
}
@Override
public Initiative copy() {
return new Initiative(this);
}
}
class InitiativeDamageTriggeredAbility extends TriggeredAbilityImpl {
InitiativeDamageTriggeredAbility() {
super(Zone.ALL, new InitiativeTakeEffect());
}
private InitiativeDamageTriggeredAbility(final InitiativeDamageTriggeredAbility ability) {
super(ability);
}
@Override
public InitiativeDamageTriggeredAbility copy() {
return new InitiativeDamageTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER_BATCH;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getTargetId().equals(game.getInitiativeId())) {
return false;
}
DamagedPlayerBatchEvent dEvent = (DamagedPlayerBatchEvent) event;
UUID playerId = dEvent
.getEvents()
.stream()
.map(GameEvent::getSourceId)
.map(game::getPermanent)
.filter(Objects::nonNull)
.map(Controllable::getControllerId)
.findFirst()
.orElse(null);
if (playerId == null) {
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(playerId));
return true;
}
@Override
public String getRule() {
return "Whenever one or more creatures a player controls deals combat damage to you, that player takes the initiative.";
}
}
class InitiativeTakeEffect extends OneShotEffect {
InitiativeTakeEffect() {
super(Outcome.Benefit);
}
private InitiativeTakeEffect(final InitiativeTakeEffect effect) {
super(effect);
}
@Override
public InitiativeTakeEffect copy() {
return new InitiativeTakeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
game.takeInitiative(source, getTargetPointer().getFirst(game, source));
return true;
}
}
class InitiativeVentureTriggeredAbility extends TriggeredAbilityImpl {
InitiativeVentureTriggeredAbility() {
super(Zone.ALL, new InitiativeUndercityEffect());
}
private InitiativeVentureTriggeredAbility(final InitiativeVentureTriggeredAbility ability) {
super(ability);
}
@Override
public InitiativeVentureTriggeredAbility copy() {
return new InitiativeVentureTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE
|| event.getType() == GameEvent.EventType.TOOK_INITIATIVE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
UUID playerId;
switch (event.getType()) {
case UPKEEP_STEP_PRE:
if (!game.isActivePlayer(game.getInitiativeId())) {
return false;
}
playerId = game.getActivePlayerId();
break;
case TOOK_INITIATIVE:
playerId = event.getPlayerId();
break;
default:
return false;
}
this.getEffects().setTargetPointer(new FixedTarget(playerId));
return true;
}
@Override
public String getRule() {
return "Whenever you take the initiative and at the beginning of your upkeep, venture into Undercity.";
}
}
class InitiativeUndercityEffect extends OneShotEffect {
InitiativeUndercityEffect() {
super(Outcome.Benefit);
}
private InitiativeUndercityEffect(final InitiativeUndercityEffect effect) {
super(effect);
}
@Override
public InitiativeUndercityEffect copy() {
return new InitiativeUndercityEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
game.ventureIntoDungeon(getTargetPointer().getFirst(game, source), true);
return true;
}
}

View file

@ -386,7 +386,7 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
Dungeon addDungeon(Dungeon dungeon, UUID playerId);
void ventureIntoDungeon(UUID playerId);
void ventureIntoDungeon(UUID playerId, boolean undercity);
/**
* Tells whether the current game has day or night, defaults to false
@ -519,6 +519,10 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
void setMonarchId(Ability source, UUID monarchId);
UUID getInitiativeId();
void takeInitiative(Ability source, UUID initiativeId);
int damagePlayerOrPlaneswalker(UUID playerOrWalker, int damage, UUID attackerId, Ability source, Game game, boolean combatDamage, boolean preventable);
int damagePlayerOrPlaneswalker(UUID playerOrWalker, int damage, UUID attackerId, Ability source, Game game, boolean combatDamage, boolean preventable, List<UUID> appliedEffects);

View file

@ -26,6 +26,7 @@ import mage.constants.*;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.Initiative;
import mage.designations.Monarch;
import mage.filter.Filter;
import mage.filter.FilterCard;
@ -36,6 +37,7 @@ import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.combat.Combat;
import mage.game.command.*;
import mage.game.command.dungeons.UndercityDungeon;
import mage.game.events.*;
import mage.game.events.TableEvent.EventType;
import mage.game.mulligan.Mulligan;
@ -536,21 +538,24 @@ public abstract class GameImpl implements Game {
));
}
private Dungeon getOrCreateDungeon(UUID playerId) {
private Dungeon getOrCreateDungeon(UUID playerId, boolean undercity) {
Dungeon dungeon = this.getPlayerDungeon(playerId);
if (dungeon != null && dungeon.hasNextRoom()) {
return dungeon;
}
removeDungeon(dungeon);
return this.addDungeon(Dungeon.selectDungeon(playerId, this), playerId);
return this.addDungeon(undercity ? new UndercityDungeon() : Dungeon.selectDungeon(playerId, this), playerId);
}
@Override
public void ventureIntoDungeon(UUID playerId) {
public void ventureIntoDungeon(UUID playerId, boolean undercity) {
if (playerId == null) {
return;
}
if (replaceEvent(GameEvent.getEvent(GameEvent.EventType.VENTURE, playerId, null, playerId))) {
return;
}
this.getOrCreateDungeon(playerId).moveToNextRoom(playerId, this);
this.getOrCreateDungeon(playerId, undercity).moveToNextRoom(playerId, this);
fireEvent(GameEvent.getEvent(GameEvent.EventType.VENTURED, playerId, null, playerId));
}
@ -3691,6 +3696,22 @@ public abstract class GameImpl implements Game {
}
}
@Override
public UUID getInitiativeId() {
return getState().getInitiativeId();
}
@Override
public void takeInitiative(Ability source, UUID initiativeId) {
if (getInitiativeId() == null) {
getState().addDesignation(new Initiative(), this, initiativeId);
} else if (!getInitiativeId().equals(initiativeId)) {
getState().setInitiativeId(initiativeId);
}
informPlayers(getPlayer(initiativeId).getLogName() + " takes the initiative");
fireEvent(new GameEvent(GameEvent.EventType.TOOK_INITIATIVE, initiativeId, source, initiativeId));
}
@Override
public int damagePlayerOrPlaneswalker(UUID playerOrWalker, int damage, UUID attackerId, Ability source, Game game, boolean combatDamage, boolean preventable) {
return damagePlayerOrPlaneswalker(playerOrWalker, damage, attackerId, source, game, combatDamage, preventable, null);

View file

@ -76,6 +76,7 @@ public class GameState implements Serializable, Copyable<GameState> {
private UUID priorityPlayerId; // player that has currently priority
private UUID playerByOrderId; // player that has currently priority
private UUID monarchId; // player that is the monarch
private UUID initiativeId; // player that has the initiative
private SpellStack stack;
private Command command;
private boolean isPlaneChase;
@ -144,6 +145,7 @@ public class GameState implements Serializable, Copyable<GameState> {
this.priorityPlayerId = state.priorityPlayerId;
this.playerByOrderId = state.playerByOrderId;
this.monarchId = state.monarchId;
this.initiativeId = state.initiativeId;
this.turn = state.turn.copy();
this.stack = state.stack.copy();
@ -249,6 +251,7 @@ public class GameState implements Serializable, Copyable<GameState> {
this.playerByOrderId = state.playerByOrderId;
this.priorityPlayerId = state.priorityPlayerId;
this.monarchId = state.monarchId;
this.initiativeId = state.initiativeId;
this.stack = state.stack;
this.command = state.command;
this.isPlaneChase = state.isPlaneChase;
@ -482,6 +485,14 @@ public class GameState implements Serializable, Copyable<GameState> {
this.monarchId = monarchId;
}
public UUID getInitiativeId() {
return initiativeId;
}
public void setInitiativeId(UUID initiativeId) {
this.initiativeId = initiativeId;
}
public UUID getChoosingPlayerId() {
return choosingPlayerId;
}

View file

@ -0,0 +1,152 @@
package mage.game.command.dungeons;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.HexproofAbility;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.command.Dungeon;
import mage.game.command.DungeonRoom;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.SkeletonToken;
import mage.game.permanent.token.TreasureToken;
import mage.players.Player;
import mage.target.TargetPlayer;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import mage.util.RandomUtil;
/**
* @author TheElk801
*/
public class UndercityDungeon extends Dungeon {
public UndercityDungeon() {
super("Undercity", "CLB");
DungeonRoom secretEntrance = new DungeonRoom(
"Secret Entrance",
new SearchLibraryPutInHandEffect(
new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND)
)
);
DungeonRoom forge = new DungeonRoom(
"Forge", new AddCountersTargetEffect(CounterType.P1P1.createInstance())
);
forge.addTarget(new TargetCreaturePermanent());
DungeonRoom lostWell = new DungeonRoom(
"Lost Well", new ScryEffect(2, false)
);
DungeonRoom trap = new DungeonRoom("Trap!", new LoseLifeTargetEffect(5));
trap.addTarget(new TargetPlayer());
DungeonRoom arena = new DungeonRoom("Arena", new GoadTargetEffect());
arena.addTarget(new TargetCreaturePermanent());
DungeonRoom stash = new DungeonRoom("Stash", new CreateTokenEffect(new TreasureToken()));
DungeonRoom archives = new DungeonRoom("Archives", new DrawCardSourceControllerEffect(1));
DungeonRoom catacombs = new DungeonRoom("Catacombs", new CreateTokenEffect(new SkeletonToken()));
DungeonRoom throneOfTheDeadThree = new DungeonRoom("Throne of the Dead Three", new ThroneOfTheDeadThreeEffect());
secretEntrance.addNextRoom(forge);
secretEntrance.addNextRoom(lostWell);
forge.addNextRoom(trap);
forge.addNextRoom(arena);
lostWell.addNextRoom(arena);
lostWell.addNextRoom(stash);
trap.addNextRoom(archives);
arena.addNextRoom(archives);
arena.addNextRoom(catacombs);
archives.addNextRoom(throneOfTheDeadThree);
catacombs.addNextRoom(throneOfTheDeadThree);
this.addRoom(secretEntrance);
this.addRoom(forge);
this.addRoom(lostWell);
this.addRoom(trap);
this.addRoom(arena);
this.addRoom(stash);
this.addRoom(archives);
this.addRoom(catacombs);
this.addRoom(throneOfTheDeadThree);
}
private UndercityDungeon(final UndercityDungeon dungeon) {
super(dungeon);
}
public UndercityDungeon copy() {
return new UndercityDungeon(this);
}
}
class ThroneOfTheDeadThreeEffect extends OneShotEffect {
ThroneOfTheDeadThreeEffect() {
super(Outcome.Benefit);
staticText = "Reveal the top ten cards of your library. Put a creature card from among them onto the " +
"battlefield with three +1/+1 counters on it. It gains hexproof until your next turn. Then shuffle.";
}
private ThroneOfTheDeadThreeEffect(final ThroneOfTheDeadThreeEffect effect) {
super(effect);
}
@Override
public ThroneOfTheDeadThreeEffect copy() {
return new ThroneOfTheDeadThreeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 10));
player.revealCards(source, cards, game);
Card card;
switch (cards.count(StaticFilters.FILTER_CARD_CREATURE, game)) {
case 0:
card = null;
break;
case 1:
card = RandomUtil.randomFromCollection(cards.getCards(StaticFilters.FILTER_CARD_CREATURE, game));
break;
default:
TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_CREATURE);
player.choose(outcome, cards, target, game);
card = cards.get(target.getFirstTarget(), game);
}
if (card != null) {
player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId());
permanent.addCounters(CounterType.P1P1.createInstance(), source, game);
game.addEffect(new GainAbilityTargetEffect(HexproofAbility.getInstance())
.setTargetPointer(new FixedTarget(permanent, game)), source);
}
player.shuffleLibrary(source, game);
return true;
}
}

View file

@ -362,6 +362,7 @@ public class GameEvent implements Serializable {
*/
BECOME_MONARCH,
BECOMES_MONARCH,
TOOK_INITIATIVE,
BECOMES_DAY_NIGHT,
MEDITATED,
PHASE_OUT, PHASED_OUT,