Clash adjustments (#10616)

* adjust clash effect

* Make clash not a singleton

* Add unit test for Clash effect

* fix test (skip init shuffling)

* Fix CLASHED event flag logic and add to unit test

* Additional test and comments

* comments in GameEvent

* param name typo
This commit is contained in:
xenohedron 2023-07-14 23:15:01 -04:00 committed by GitHub
parent 0d4c73b385
commit ee29c38413
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 187 additions and 67 deletions

View file

@ -2,8 +2,6 @@ package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
@ -13,11 +11,10 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.players.PlayerList;
import mage.target.Target;
import mage.target.common.TargetOpponent;
import java.io.ObjectStreamException;
import java.util.UUID;
/**
* 1. The controller of the spell or ability chooses an opponent. (This doesn't
@ -54,24 +51,14 @@ import java.io.ObjectStreamException;
*
* @author LevelX2
*/
public class ClashEffect extends OneShotEffect implements MageSingleton {
public class ClashEffect extends OneShotEffect {
private static final ClashEffect instance = new ClashEffect();
private Object readResolve() throws ObjectStreamException {
return instance;
}
private ClashEffect() {
public ClashEffect() {
super(Outcome.Benefit);
this.staticText = "Clash with an opponent";
}
public static ClashEffect getInstance() {
return instance;
}
public ClashEffect(final ClashEffect effect) {
protected ClashEffect(final ClashEffect effect) {
super(effect);
}
@ -94,7 +81,6 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
// choose opponent
Target target = new TargetOpponent(true);
target.setTargetName("an opponent to clash with");
target.setNotTarget(true);
if (!controller.choose(Outcome.Benefit, target, source, game)) {
return false;
}
@ -133,32 +119,26 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
message.append(" no card");
}
message.append(" - ");
if (!game.isSimulation()) {
if (cmcController > cmcOpponent) {
message.append(controller.getLogName()).append(" won the clash");
game.informPlayer(controller, "You won the clash!");
} else if (cmcController < cmcOpponent) {
message.append(opponent.getLogName()).append(" won the clash");
game.informPlayer(controller, opponent.getName() + " won the clash!");
} else {
message.append(" no winner ");
}
game.informPlayers(message.toString());
if (cmcController > cmcOpponent) {
message.append(controller.getLogName()).append(" won the clash");
} else if (cmcController < cmcOpponent) {
message.append(opponent.getLogName()).append(" won the clash");
} else {
message.append(" no winner ");
}
// decide to put the cards on top or on the buttom of library in turn order beginning with the active player in turn order
PlayerList playerList = game.getPlayerList().copy();
playerList.setCurrent(game.getActivePlayerId());
Player nextPlayer;
do {
Player current = playerList.getCurrent(game);
if (cardController != null && current.getId().equals(controller.getId())) {
topController = current.chooseUse(Outcome.Detriment, "Put " + cardController.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
game.informPlayers(message.toString());
// decide to put the cards on top or on the bottom of library in turn order beginning with the active player in turn order
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;
}
if (cardOpponent != null && current.getId().equals(opponent.getId())) {
topOpponent = current.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
if (cardController != null && player.getId().equals(controller.getId())) {
topController = player.chooseUse(Outcome.Detriment, "Put " + cardController.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
} else if (cardOpponent != null && player.getId().equals(opponent.getId())) {
topOpponent = player.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
}
nextPlayer = playerList.getNext(game, false);
} while (nextPlayer != null && !nextPlayer.getId().equals(game.getActivePlayerId()));
}
// put the cards back to library
if (cardController != null) {
controller.moveCardToLibraryWithInfo(cardController, source, game, Zone.LIBRARY, topController, true);
@ -166,14 +146,14 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
if (cardOpponent != null) {
opponent.moveCardToLibraryWithInfo(cardOpponent, source, game, Zone.LIBRARY, topOpponent, true);
}
// fire CLASHED event with info about who won
game.fireEvent(new GameEvent(
GameEvent.EventType.CLASHED, controller.getId(), source,
opponent.getId(), 0, cmcController > cmcOpponent
));
// fire CLASHED events with info about winner (flag is true if playerId won; other player is targetId)
game.fireEvent(new GameEvent(
GameEvent.EventType.CLASHED, opponent.getId(), source,
controller.getId(), 0, cmcOpponent > cmcController
controller.getId(), 0, cmcController > cmcOpponent
));
game.fireEvent(new GameEvent(
GameEvent.EventType.CLASHED, controller.getId(), source,
opponent.getId(), 0, cmcOpponent > cmcController
));
// set opponent to DoIfClashWonEffect

View file

@ -54,7 +54,7 @@ public class DoIfClashWonEffect extends OneShotEffect {
}
if (chooseUseText == null || player.chooseUse(executingEffect.getOutcome(), message, source, game)) {
if (ClashEffect.getInstance().apply(game, source)) {
if (new ClashEffect().apply(game, source)) {
if (setTargetPointerToClashedOpponent) {
Object opponent = getValue("clashOpponent");
if (opponent instanceof Player) {

View file

@ -99,6 +99,11 @@ public class GameEvent implements Serializable {
DISCARDED_CARD,
DISCARDED_CARDS,
CYCLE_CARD, CYCLED_CARD, CYCLE_DRAW,
/* CLASHED (one event fired for each player involved)
playerId the id of the clashing player
flag true = playerId won the clash
targetId the id of the other player in the clash
*/
CLASH, CLASHED,
DAMAGE_PLAYER,
MILL_CARDS,
@ -107,9 +112,9 @@ public class GameEvent implements Serializable {
/* DAMAGED_PLAYER
targetId the id of the damaged player
sourceId sourceId of the ability which caused the damage
playerId the id of the damged player
playerId the id of the damaged player
amount amount of damage
flag true = comabat damage - other damage = false
flag true = combat damage - other damage = false
*/
DAMAGED_PLAYER,
@ -121,7 +126,7 @@ public class GameEvent implements Serializable {
/* DAMAGE_CAUSES_LIFE_LOSS,
targetId the id of the damaged player
sourceId sourceId of the ability which caused the damage, can be null for default events like combat
playerId the id of the damged player
playerId the id of the damaged player
amount amount of damage
flag is it combat damage
*/
@ -134,7 +139,7 @@ public class GameEvent implements Serializable {
sourceId sourceId of the ability which caused the lose
playerId the id of the player loosing life
amount amount of life loss
flag true = from comabat damage - other from non combat damage
flag true = from combat damage - other from non combat damage
*/
PLAY_LAND, LAND_PLAYED,
CREATURE_CHAMPIONED,
@ -333,7 +338,7 @@ public class GameEvent implements Serializable {
TAP,
/* TAPPED,
targetId tapped permanent
sourceId id of the abilitity's source (can be null for standard tap actions like combat)
sourceId id of the ability's source (can be null for standard tap actions like combat)
playerId controller of the tapped permanent
amount not used for this event
flag is it tapped for combat
@ -441,7 +446,7 @@ public class GameEvent implements Serializable {
/* LOST_CONTROL
targetId id of the creature that lost control
sourceId null
playerId player that controlles the creature before
playerId player that controls the creature before
amount not used for this event
flag not used for this event
*/

View file

@ -567,11 +567,11 @@ public interface Player extends MageItem, Copyable<Player> {
void revealCards(Ability source, Cards cards, Game game);
void revealCards(String titelSuffix, Cards cards, Game game);
void revealCards(String titleSuffix, Cards cards, Game game);
void revealCards(Ability source, String titelSuffix, Cards cards, Game game);
void revealCards(Ability source, String titleSuffix, Cards cards, Game game);
void revealCards(String titelSuffix, Cards cards, Game game, boolean postToLog);
void revealCards(String titleSuffix, Cards cards, Game game, boolean postToLog);
/**
* Adds the cards to the reveal window and adds the source object's id name