* Added new game mode: Oathbreaker (#5678);

This commit is contained in:
Oleg Agafonov 2019-05-27 16:17:15 +04:00
parent adb666587b
commit 07cf5201ba
17 changed files with 1100 additions and 236 deletions

View file

@ -0,0 +1,61 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import java.util.UUID;
/**
* For oathbreaker game mode
*
* @author JayDi85
*/
public class SignatureSpellCastOnlyWithOathbreakerEffect extends ContinuousRuleModifyingEffectImpl {
private final Condition condition;
private final UUID signatureSpell;
public SignatureSpellCastOnlyWithOathbreakerEffect(Condition condition, UUID signatureSpell) {
super(Duration.EndOfGame, Outcome.Detriment);
this.condition = condition;
this.signatureSpell = signatureSpell;
staticText = setText();
}
private SignatureSpellCastOnlyWithOathbreakerEffect(final SignatureSpellCastOnlyWithOathbreakerEffect effect) {
super(effect);
this.condition = effect.condition;
this.signatureSpell = effect.signatureSpell;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CAST_SPELL;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getSourceId().equals(signatureSpell)) {
return condition != null && !condition.apply(game, source);
}
return false; // cast not prevented by this effect
}
@Override
public SignatureSpellCastOnlyWithOathbreakerEffect copy() {
return new SignatureSpellCastOnlyWithOathbreakerEffect(this);
}
private String setText() {
StringBuilder sb = new StringBuilder("cast this spell only ");
if (condition != null) {
sb.append(' ').append(condition.toString());
}
return sb.toString();
}
}

View file

@ -0,0 +1,51 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* For Oathbreaker game mode
*
* @author JayDi85
*/
public class OathbreakerOnBattlefieldCondition implements Condition {
private UUID playerId;
private FilterControlledPermanent filter;
public OathbreakerOnBattlefieldCondition(UUID playerId, List<UUID> oathbreakersToSearch) {
this.playerId = playerId;
this.filter = new FilterControlledPermanent("oathbreaker on battlefield");
if (oathbreakersToSearch != null && !oathbreakersToSearch.isEmpty()) {
// any commander on battlefield
List<PermanentIdPredicate> idsList = new ArrayList<>();
for (UUID id : oathbreakersToSearch) {
idsList.add(new PermanentIdPredicate(id));
}
this.filter.add(Predicates.or(idsList));
} else {
// random id to disable condition
this.filter.add(new PermanentIdPredicate(UUID.randomUUID()));
}
}
@Override
public boolean apply(Game game, Ability source) {
// source.getSourceId() is null for commander's effects
int permanentsOnBattlefield = game.getBattlefield().count(this.filter, source.getSourceId(), playerId, game);
return permanentsOnBattlefield > 0;
}
@Override
public String toString() {
return filter.getMessage();
}
}

View file

@ -41,6 +41,7 @@ public abstract class GameCommanderImpl extends GameImpl {
@Override
protected void init(UUID choosingPlayerId) {
// Karn Liberated calls it to restart game, all data and commanders must be re-initialized
// plays watcher
state.addWatcher(new CommanderPlaysCountWatcher());
@ -49,20 +50,19 @@ public abstract class GameCommanderImpl extends GameImpl {
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
Player player = getPlayer(playerId);
if (player != null) {
if (player.getSideboard().isEmpty()) { // needed for restart game of e.g. Karn Liberated
for (UUID commanderId : player.getCommandersIds()) {
Card commander = this.getCard(commanderId);
if (commander != null) {
initCommander(commander, player);
}
// add new commanders
for (UUID id : player.getSideboard()) {
Card commander = this.getCard(id);
if (commander != null) {
addCommander(commander, player);
}
} else {
while (!player.getSideboard().isEmpty()) {
Card commander = this.getCard(player.getSideboard().iterator().next());
if (commander != null) {
player.addCommanderId(commander.getId());
initCommander(commander, player);
}
}
// init commanders
for (UUID commanderId : player.getCommandersIds()) {
Card commander = this.getCard(commanderId);
if (commander != null) {
initCommander(commander, player);
}
}
}
@ -75,17 +75,27 @@ public abstract class GameCommanderImpl extends GameImpl {
}
public void initCommander(Card commander, Player player) {
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
commander.moveToZone(Zone.COMMAND, null, this, true);
commander.getAbilities().setControllerId(player.getId());
ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
ability.addEffect(new CommanderCostModification(commander.getId()));
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), checkCommanderDamage);
Ability ability = new SimpleStaticAbility(Zone.COMMAND, new InfoEffect("Commander effects"));
initCommanderEffects(commander, player, ability);
CommanderInfoWatcher watcher = initCommanderWatcher(commander, checkCommanderDamage);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);
this.getState().addAbility(ability, null);
}
public CommanderInfoWatcher initCommanderWatcher(Card commander, boolean checkCommanderDamage) {
return new CommanderInfoWatcher("Commander", commander.getId(), checkCommanderDamage);
}
public void initCommanderEffects(Card commander, Player player, Ability commanderAbility) {
// all commander effects must be independent from sourceId or controllerId
commanderAbility.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary));
commanderAbility.addEffect(new CommanderCostModification(commander.getId()));
}
//20130711
/*903.8. The Commander variant uses an alternate mulligan rule.
* Each time a player takes a mulligan, rather than shuffling their entire hand of cards into their library, that player exiles any number of cards from their hand face down.
@ -207,4 +217,8 @@ public abstract class GameCommanderImpl extends GameImpl {
this.checkCommanderDamage = checkCommanderDamage;
}
public void addCommander(Card card, Player player) {
player.addCommanderId(card.getId());
}
}

View file

@ -2871,7 +2871,7 @@ public abstract class GameImpl implements Game, Serializable {
// as commander (only commander games, look at init code in GameCommanderImpl)
if (this instanceof GameCommanderImpl) {
for (Card card : command) {
player.addCommanderId(card.getId());
((GameCommanderImpl) this).addCommander(card, player);
// no needs in initCommander call -- it's uses on game startup (init)
}
} else if (!command.isEmpty()) {

View file

@ -62,7 +62,7 @@ public abstract class GameTinyLeadersImpl extends GameImpl {
ability.addEffect(new CommanderCostModification(commander.getId()));
// Commander rule #4 was removed Jan. 18, 2016
// ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander)));
CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), false);
CommanderInfoWatcher watcher = new CommanderInfoWatcher("Commander", commander.getId(), false);
getState().addWatcher(watcher);
watcher.addCardInfoToCommander(this);
this.getState().addAbility(ability, null);

View file

@ -26,17 +26,20 @@ public class CommanderInfoWatcher extends Watcher {
private final Map<UUID, Integer> damageToPlayer = new HashMap<>();
private final boolean checkCommanderDamage;
private final String commanderTypeName;
public CommanderInfoWatcher(UUID commander, boolean checkCommanderDamage) {
public CommanderInfoWatcher(String commanderTypeName, UUID commander, boolean checkCommanderDamage) {
super(WatcherScope.CARD);
this.sourceId = commander;
this.checkCommanderDamage = checkCommanderDamage;
this.commanderTypeName = commanderTypeName;
}
public CommanderInfoWatcher(final CommanderInfoWatcher watcher) {
super(watcher);
this.damageToPlayer.putAll(watcher.damageToPlayer);
this.checkCommanderDamage = watcher.checkCommanderDamage;
this.commanderTypeName = watcher.commanderTypeName;
}
@Override
@ -78,7 +81,7 @@ public class CommanderInfoWatcher extends Watcher {
}
if (object != null) {
StringBuilder sb = new StringBuilder();
sb.append("<b>Commander</b>");
sb.append("<b>" + commanderTypeName + "</b>");
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int playsCount = watcher.getPlaysCount(sourceId);
if (playsCount > 0) {
@ -89,9 +92,9 @@ public class CommanderInfoWatcher extends Watcher {
if (checkCommanderDamage) {
for (Map.Entry<UUID, Integer> entry : damageToPlayer.entrySet()) {
Player damagedPlayer = game.getPlayer(entry.getKey());
sb.append("<b>Commander</b> did ").append(entry.getValue()).append(" combat damage to player ").append(damagedPlayer.getLogName()).append('.');
sb.append("<b>" + commanderTypeName + "</b> did ").append(entry.getValue()).append(" combat damage to player ").append(damagedPlayer.getLogName()).append('.');
this.addInfo(object, "Commander" + entry.getKey(),
"<b>Commander</b> did " + entry.getValue() + " combat damage to player " + damagedPlayer.getLogName() + '.', game);
"<b>" + commanderTypeName + "</b> did " + entry.getValue() + " combat damage to player " + damagedPlayer.getLogName() + '.', game);
}
}
}