forked from External/mage
Rework Ring-bearer implementation. Add GUI + gamelogs. (#10596)
* Fix Ring-bearer choosing & add some GUI + logs * use a ring svg in a separate gold panel * use a fontawesome svg * add a couple null checks, group icon with commander * rework rinbearer logic according to review * fix typo in game log * small fixes
This commit is contained in:
parent
4065e2e935
commit
14235b6320
20 changed files with 177 additions and 86 deletions
|
|
@ -19,7 +19,7 @@ public enum SourceIsRingBearerCondition implements Condition {
|
|||
.ofNullable(source.getSourcePermanentIfItStillExists(game))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(permanent -> permanent.isControlledBy(source.getControllerId()))
|
||||
.map(permanent -> permanent.isRingBearer(game))
|
||||
.map(permanent -> permanent.isRingBearer())
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ public enum CardIconCategory {
|
|||
ABILITY, // example: flying (on left side)
|
||||
PLAYABLE_COUNT, // on bottom left corner
|
||||
SYSTEM, // example: too many icons combines in the one icon (on left side)
|
||||
COMMANDER // example: commander (on top center icon)
|
||||
COMMANDER, // example: commander (on top center icon)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ public enum CardIconColor {
|
|||
|
||||
DEFAULT(),
|
||||
YELLOW(new Color(231, 203, 18), new Color(76, 76, 76), new Color(0, 0, 0)),
|
||||
RED(new Color(255, 31, 75), new Color(76, 76, 76), new Color(229, 228, 228));
|
||||
RED(new Color(255, 31, 75), new Color(76, 76, 76), new Color(229, 228, 228)),
|
||||
GOLD(new Color(186, 105, 19), new Color(76, 76, 76), new Color(229, 228, 228));
|
||||
|
||||
private final Color fillColor;
|
||||
private final Color strokeColor;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public class CardIconImpl implements CardIcon, Serializable {
|
|||
// Utility Icons
|
||||
public static final CardIconImpl FACE_DOWN = new CardIconImpl(CardIconType.OTHER_FACEDOWN, "Card is face down");
|
||||
public static final CardIconImpl COMMANDER = new CardIconImpl(CardIconType.COMMANDER, "Card is commander");
|
||||
public static final CardIconImpl RINGBEARER = new CardIconImpl(CardIconType.RINGBEARER, "Ring-bearer");
|
||||
|
||||
// Ability Icons
|
||||
public static final CardIconImpl ABILITY_CREW = new CardIconImpl(CardIconType.ABILITY_CREW,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ public enum CardIconType {
|
|||
OTHER_FACEDOWN("prepared/reply-fill.svg", CardIconCategory.ABILITY, 100),
|
||||
OTHER_COST_X("prepared/square-fill.svg", CardIconCategory.ABILITY, 100),
|
||||
//
|
||||
RINGBEARER("prepared/ring.svg", CardIconCategory.COMMANDER, 100),
|
||||
COMMANDER("prepared/crown.svg", CardIconCategory.COMMANDER, 100), // TODO: fix big size, see CardIconsPanel
|
||||
//
|
||||
SYSTEM_COMBINED("prepared/square-fill.svg", CardIconCategory.SYSTEM, 1000), // inner usage, must use last order
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import mage.filter.predicate.Predicates;
|
|||
import mage.filter.predicate.mageobject.*;
|
||||
import mage.filter.predicate.other.AnotherTargetPredicate;
|
||||
import mage.filter.predicate.permanent.AttachedOrShareCreatureTypePredicate;
|
||||
import mage.filter.predicate.permanent.RingBearerPredicate;
|
||||
import mage.filter.predicate.permanent.TappedPredicate;
|
||||
import mage.filter.predicate.permanent.TokenPredicate;
|
||||
|
||||
|
|
@ -664,6 +665,13 @@ public final class StaticFilters {
|
|||
FILTER_CONTROLLED_PERMANENT_NON_LAND.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterControlledPermanent FILTER_CONTROLLED_RINGBEARER = new FilterControlledPermanent("the controlled Ring-bearer");
|
||||
|
||||
static {
|
||||
FILTER_CONTROLLED_RINGBEARER.add(RingBearerPredicate.instance);
|
||||
FILTER_CONTROLLED_RINGBEARER.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterLandPermanent FILTER_LAND = new FilterLandPermanent();
|
||||
|
||||
static {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ public enum RingBearerPredicate implements Predicate<Permanent> {
|
|||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return input.isRingBearer(game);
|
||||
return input.isRingBearer();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -589,7 +589,10 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
player.chooseRingBearer(this);
|
||||
getOrCreateTheRing(playerId).addNextAbility(this);
|
||||
fireEvent(GameEvent.getEvent(GameEvent.EventType.TEMPTED_BY_RING, player.getRingBearerId(), null, playerId));
|
||||
|
||||
Permanent ringbearer = player.getRingBearer(this);
|
||||
UUID ringbearerId = ringbearer == null ? null : ringbearer.getId();
|
||||
fireEvent(GameEvent.getEvent(GameEvent.EventType.TEMPTED_BY_RING, ringbearerId, null, playerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import mage.game.Game;
|
|||
import mage.game.command.Emblem;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.common.TemptedByTheRingWatcher;
|
||||
|
||||
|
|
@ -53,15 +54,16 @@ public final class TheRingEmblem extends Emblem {
|
|||
}
|
||||
|
||||
public void addNextAbility(Game game) {
|
||||
String logText;
|
||||
Ability ability;
|
||||
switch (TemptedByTheRingWatcher.getCount(this.getControllerId(), game)) {
|
||||
case 0:
|
||||
// Your Ring-bearer is legendary and can't be blocked by creatures with greater power.
|
||||
logText = "Your Ring-bearer is legendary and can't be blocked by creatures with greater power.";
|
||||
ability = new SimpleStaticAbility(Zone.COMMAND, new TheRingEmblemLegendaryEffect());
|
||||
ability.addEffect(new TheRingEmblemEvasionEffect());
|
||||
break;
|
||||
case 1:
|
||||
// Whenever your Ring-bearer attacks, draw a card, then discard a card.
|
||||
logText = "Whenever your Ring-bearer attacks, draw a card, then discard a card.";
|
||||
ability = new AttacksCreatureYouControlTriggeredAbility(
|
||||
Zone.COMMAND,
|
||||
new DrawDiscardControllerEffect(1, 1),
|
||||
|
|
@ -69,11 +71,11 @@ public final class TheRingEmblem extends Emblem {
|
|||
).setTriggerPhrase("Whenever your Ring-bearer attacks, ");
|
||||
break;
|
||||
case 2:
|
||||
// Whenever your Ring-bearer becomes blocked by a creature, that creature's controller sacrifices it at end of combat.
|
||||
logText ="Whenever your Ring-bearer becomes blocked by a creature, that creature's controller sacrifices it at end of combat.";
|
||||
ability = new TheRingEmblemTriggeredAbility();
|
||||
break;
|
||||
case 3:
|
||||
// Whenever your Ring-bearer deals combat damage to a player, each opponent loses 3 life.
|
||||
logText = "Whenever your Ring-bearer deals combat damage to a player, each opponent loses 3 life.";
|
||||
ability = new DealsDamageToAPlayerAllTriggeredAbility(
|
||||
Zone.COMMAND, new LoseLifeOpponentsEffect(3), filter, false,
|
||||
SetTargetPointer.NONE, true, false
|
||||
|
|
@ -82,10 +84,20 @@ public final class TheRingEmblem extends Emblem {
|
|||
default:
|
||||
return;
|
||||
}
|
||||
UUID controllerId = this.getControllerId();
|
||||
this.getAbilities().add(ability);
|
||||
ability.setSourceId(this.getId());
|
||||
ability.setControllerId(this.getControllerId());
|
||||
ability.setControllerId(controllerId);
|
||||
game.getState().addAbility(ability, this);
|
||||
|
||||
String name = "";
|
||||
if(controllerId != null){
|
||||
Player player = game.getPlayer(controllerId);
|
||||
if(player != null){
|
||||
name = player.getLogName();
|
||||
}
|
||||
}
|
||||
game.informPlayers(name + " gains a new Ring ability: \"" + logText + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +106,7 @@ enum TheRingEmblemPredicate implements Predicate<Permanent> {
|
|||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
return input.isRingBearer(game);
|
||||
return input.isRingBearer();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +160,7 @@ class TheRingEmblemEvasionEffect extends RestrictionEffect {
|
|||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
return permanent.isControlledBy(source.getControllerId())
|
||||
&& permanent.isRingBearer(game);
|
||||
&& permanent.isRingBearer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -184,7 +196,7 @@ class TheRingEmblemTriggeredAbility extends TriggeredAbilityImpl {
|
|||
if (attacker == null
|
||||
|| blocker == null
|
||||
|| attacker.isControlledBy(getControllerId())
|
||||
|| !attacker.isRingBearer(game)) {
|
||||
|| !attacker.isRingBearer()) {
|
||||
return false;
|
||||
}
|
||||
this.getEffects().setTargetPointer(new FixedTarget(blocker, game));
|
||||
|
|
|
|||
|
|
@ -420,7 +420,9 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
boolean isManifested();
|
||||
|
||||
boolean isRingBearer(Game game);
|
||||
boolean isRingBearer();
|
||||
|
||||
void setRingBearer(Game game, boolean value);
|
||||
|
||||
@Override
|
||||
Permanent copy();
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean renowned;
|
||||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected boolean ringBearerFlag = false;
|
||||
protected int classLevel = 1;
|
||||
protected final Set<UUID> goadingPlayers = new HashSet<>();
|
||||
protected UUID originalControllerId;
|
||||
|
|
@ -165,6 +166,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.transformed = permanent.transformed;
|
||||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.ringBearerFlag = permanent.ringBearerFlag;
|
||||
this.classLevel = permanent.classLevel;
|
||||
this.goadingPlayers.addAll(permanent.goadingPlayers);
|
||||
this.pairedPermanent = permanent.pairedPermanent;
|
||||
|
|
@ -801,11 +803,24 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Losing control of a ring bearer clear its status.
|
||||
public void removeUncontrolledRingBearer(Game game){
|
||||
if(isRingBearer()) {
|
||||
UUID controllerId = beforeResetControllerId;
|
||||
Player controller = controllerId == null ? null : game.getPlayer(controllerId);
|
||||
String controllerName = controller == null ? "" : controller.getLogName();
|
||||
game.informPlayers(controllerName + " has lost control of " + getLogName() + ". It is no longer a Ring-bearer.");
|
||||
|
||||
this.setRingBearer(game, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkControlChanged(Game game) {
|
||||
if (!controllerId.equals(beforeResetControllerId)) {
|
||||
this.removeFromCombat(game);
|
||||
this.controlledFromStartOfControllerTurn = false;
|
||||
this.removeUncontrolledRingBearer(game);
|
||||
|
||||
this.getAbilities(game).setControllerId(controllerId);
|
||||
game.getContinuousEffects().setController(objectId, controllerId);
|
||||
|
|
@ -1656,6 +1671,41 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.renowned = value;
|
||||
}
|
||||
|
||||
// Used as key for the ring bearer info.
|
||||
private static final String ringbearerInfoKey = "IS_RINGBEARER";
|
||||
|
||||
@Override
|
||||
public void setRingBearer(Game game, boolean value) {
|
||||
if(value == this.ringBearerFlag){
|
||||
return;
|
||||
}
|
||||
|
||||
if(value) {
|
||||
// The player may have another Ringbearer. We need to clear it if so.
|
||||
//
|
||||
// 701.52a Certain spells and abilities have the text “the Ring tempts you.”
|
||||
// Each time the Ring tempts you, choose a creature you control.
|
||||
// That creature becomes your Ring-bearer until another creature
|
||||
// becomes your Ring-bearer or another player gains control of it.
|
||||
Player player = game.getPlayer(getControllerId());
|
||||
String playername = "";
|
||||
if(player != null){
|
||||
playername = player.getLogName();
|
||||
Permanent existingRingbearer = player.getRingBearer(game);
|
||||
if(existingRingbearer != null && existingRingbearer.getId() != this.getId()){
|
||||
existingRingbearer.setRingBearer(game, false);
|
||||
}
|
||||
}
|
||||
|
||||
addInfo(ringbearerInfoKey, CardUtil.addToolTipMarkTags("Is " + playername + "'s Ring-bearer"), game);
|
||||
}
|
||||
else {
|
||||
addInfo(ringbearerInfoKey, null, game);
|
||||
}
|
||||
|
||||
this.ringBearerFlag = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClassLevel() {
|
||||
return classLevel;
|
||||
|
|
@ -1811,10 +1861,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isRingBearer(Game game) {
|
||||
Player player = game.getPlayer(getControllerId());
|
||||
return player != null && this.equals(player.getRingBearer(game));
|
||||
}
|
||||
public boolean isRingBearer() { return ringBearerFlag; }
|
||||
|
||||
@Override
|
||||
public boolean fight(Permanent fightTarget, Ability source, Game game) {
|
||||
|
|
|
|||
|
|
@ -1080,8 +1080,6 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
*/
|
||||
FilterMana getPhyrexianColors();
|
||||
|
||||
UUID getRingBearerId();
|
||||
|
||||
Permanent getRingBearer(Game game);
|
||||
|
||||
void chooseRingBearer(Game game);
|
||||
|
|
|
|||
|
|
@ -177,8 +177,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// mana colors the player can handle like Phyrexian mana
|
||||
protected FilterMana phyrexianColors;
|
||||
|
||||
protected UUID ringBearerId = null;
|
||||
|
||||
// Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy)
|
||||
protected final List<List<Mana>> availableTriggeredManaList = new ArrayList<>();
|
||||
|
||||
|
|
@ -288,7 +286,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
this.payManaMode = player.payManaMode;
|
||||
this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null;
|
||||
this.ringBearerId = player.ringBearerId;
|
||||
for (Designation object : player.designations) {
|
||||
this.designations.add(object.copy());
|
||||
}
|
||||
|
|
@ -376,8 +373,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
this.phyrexianColors = player.getPhyrexianColors() != null ? player.getPhyrexianColors().copy() : null;
|
||||
|
||||
this.ringBearerId = player.getRingBearerId();
|
||||
|
||||
this.designations.clear();
|
||||
for (Designation object : player.getDesignations()) {
|
||||
this.designations.add(object.copy());
|
||||
|
|
@ -5140,59 +5135,87 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return this.phyrexianColors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getRingBearerId() {
|
||||
return ringBearerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permanent getRingBearer(Game game) {
|
||||
if (ringBearerId == null) {
|
||||
return null;
|
||||
}
|
||||
Permanent bearer = game.getPermanent(ringBearerId);
|
||||
if (bearer != null && bearer.isControlledBy(getId())) {
|
||||
return bearer;
|
||||
}
|
||||
ringBearerId = null;
|
||||
return null;
|
||||
return game.getBattlefield()
|
||||
.getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_RINGBEARER,
|
||||
getId(),null, game)
|
||||
.stream()
|
||||
.filter(p -> p != null)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// 701.52a Certain spells and abilities have the text “the Ring tempts you.” Each time the Ring tempts
|
||||
// you, choose a creature you control. That creature becomes your Ring-bearer until another
|
||||
// creature becomes your Ring-bearer or another player gains control of it.
|
||||
@Override
|
||||
public void chooseRingBearer(Game game) {
|
||||
Permanent currentBearer = getRingBearer(game);
|
||||
int creatureCount = game.getBattlefield().count(
|
||||
StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game
|
||||
);
|
||||
boolean mustChoose;
|
||||
if (currentBearer == null) {
|
||||
if (creatureCount > 0) {
|
||||
mustChoose = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (currentBearer.isCreature(game)) {
|
||||
if (creatureCount > 1) {
|
||||
mustChoose = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (creatureCount > 0) {
|
||||
mustChoose = false;
|
||||
UUID currentBearerId = currentBearer == null ? null : currentBearer.getId();
|
||||
|
||||
List<UUID> ids = game.getBattlefield()
|
||||
.getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game)
|
||||
.stream()
|
||||
.filter(p -> p != null)
|
||||
.map(p -> p.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if(ids.isEmpty()) {
|
||||
game.informPlayers(getLogName() + " has no creature to be Ring-bearer.");
|
||||
return;
|
||||
}
|
||||
|
||||
// There should always be a creature at the end.
|
||||
UUID newBearerId;
|
||||
if(ids.size() == 1){
|
||||
// Only one creature, it will be the Ring-bearer.
|
||||
// The player does not have to make any choice.
|
||||
newBearerId = ids.get(0);
|
||||
} else {
|
||||
return;
|
||||
// Multiple possible Ring-bearer.
|
||||
// Asking first if the player wants to change Ring-bearer.
|
||||
boolean mustChoose = currentBearer == null || !(currentBearer.isCreature(game));
|
||||
boolean choosing = mustChoose;
|
||||
if (!mustChoose) {
|
||||
choosing = chooseUse(Outcome.Neutral, "Choose a new Ring-bearer?", null, game);
|
||||
}
|
||||
|
||||
if (choosing) {
|
||||
TargetPermanent target = new TargetControlledCreaturePermanent();
|
||||
target.setNotTarget(true);
|
||||
target.withChooseHint("to be your Ring-bearer");
|
||||
choose(Outcome.Neutral, target, null, game);
|
||||
|
||||
newBearerId = target.getFirstTarget();
|
||||
}
|
||||
else {
|
||||
newBearerId = currentBearerId;
|
||||
}
|
||||
}
|
||||
if (!mustChoose && !chooseUse(Outcome.Neutral, "Choose a new Ring-bearer?", null, game)) {
|
||||
return;
|
||||
}
|
||||
TargetPermanent target = new TargetControlledCreaturePermanent();
|
||||
target.setNotTarget(true);
|
||||
target.withChooseHint("to be your Ring-bearer");
|
||||
choose(Outcome.Neutral, target, null, game);
|
||||
UUID newBearerId = target.getFirstTarget();
|
||||
if (game.getPermanent(newBearerId) != null) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, newBearerId, null, getId()));
|
||||
this.ringBearerId = newBearerId;
|
||||
|
||||
if(currentBearerId != null && currentBearerId == newBearerId) {
|
||||
// Oracle Ruling for Call of the Ring
|
||||
//
|
||||
// If the creature you choose as your Ring-bearer was already your Ring-bearer,
|
||||
// that still counts as choosing that creature as your Ring-bearer for the purpose
|
||||
// f abilities that trigger "whenever you choose a creature as your Ring-bearer"
|
||||
// or abilities that care about which creature was chosen as your Ring-bearer.
|
||||
// (2023-06-16)
|
||||
game.informPlayers(getLogName() + " did not choose a new Ring-bearer. " +
|
||||
"It is still " + (currentBearer == null ? "" : currentBearer.getLogName()) + ".");
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, currentBearerId, null, getId()));
|
||||
} else {
|
||||
Permanent ringBearer = game.getPermanent(newBearerId);
|
||||
if(ringBearer != null){
|
||||
// The setRingBearer method is taking care of removing
|
||||
// the status from the current ring bearer, if existing.
|
||||
ringBearer.setRingBearer(game, true);
|
||||
|
||||
game.informPlayers(getLogName() + " has chosen " + ringBearer.getLogName() + " as Ring-bearer.");
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, newBearerId, null, getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue