mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Can't be activated effects - fixed that some restricted effects show abilities as playable (example: Sharkey, Tyrant of the Shire and Karakas, fixed #10642)
This commit is contained in:
parent
21ad11dbdc
commit
393dbc4047
9 changed files with 136 additions and 44 deletions
|
|
@ -22,7 +22,7 @@ public class SimpleCardView implements Serializable, SelectableObjectView {
|
||||||
|
|
||||||
protected boolean isChoosable;
|
protected boolean isChoosable;
|
||||||
protected boolean isSelected;
|
protected boolean isSelected;
|
||||||
protected PlayableObjectStats playableStats = new PlayableObjectStats();
|
protected PlayableObjectStats playableStats = new PlayableObjectStats(); // filled on client side from GameView
|
||||||
|
|
||||||
public SimpleCardView(final SimpleCardView view) {
|
public SimpleCardView(final SimpleCardView view) {
|
||||||
this.id = view.id;
|
this.id = view.id;
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ public final class Karakas extends CardImpl {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
|
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
|
||||||
this.supertype.add(SuperType.LEGENDARY);
|
this.supertype.add(SuperType.LEGENDARY);
|
||||||
|
|
||||||
// {tap}: Add {W}.
|
// {T}: Add {W}.
|
||||||
this.addAbility(new WhiteManaAbility());
|
this.addAbility(new WhiteManaAbility());
|
||||||
// {tap}: Return target legendary creature to its owner's hand.
|
// {T}: Return target legendary creature to its owner's hand.
|
||||||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new TapSourceCost());
|
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new TapSourceCost());
|
||||||
ability.addTarget(new TargetCreaturePermanent(filter));
|
ability.addTarget(new TargetCreaturePermanent(filter));
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,23 @@ import mage.abilities.Abilities;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.cards.repository.CardInfo;
|
import mage.cards.repository.CardInfo;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
import mage.game.permanent.PermanentCard;
|
import mage.game.permanent.PermanentCard;
|
||||||
import mage.game.permanent.PermanentImpl;
|
import mage.game.permanent.PermanentImpl;
|
||||||
import mage.view.AbilityPickerView;
|
import mage.view.AbilityPickerView;
|
||||||
import mage.view.GameView;
|
import mage.view.GameView;
|
||||||
|
import mage.view.SimpleCardView;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author JayDi85
|
* @author JayDi85
|
||||||
*/
|
*/
|
||||||
|
|
@ -89,4 +98,74 @@ public class AbilityPickerTest extends CardTestPlayerBase {
|
||||||
PermanentImpl permanent = new PermanentCard(info.createCard(), playerA.getId(), currentGame);
|
PermanentImpl permanent = new PermanentCard(info.createCard(), playerA.getId(), currentGame);
|
||||||
return permanent.getAbilities(currentGame);
|
return permanent.getAbilities(currentGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_RealGame_ActivatedAbilities_All() {
|
||||||
|
// possible bug: wrongly enabled ability, see #10642
|
||||||
|
|
||||||
|
// Activated abilities of lands your opponents control can't be activated unless they're mana abilities.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Sharkey, Tyrant of the Shire", 1);
|
||||||
|
// {T}: Add {W}.
|
||||||
|
// {T}: Return target legendary creature to its owner's hand.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Karakas", 1);
|
||||||
|
|
||||||
|
// have all abilities
|
||||||
|
runCode("all available", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
List<String> needList = new ArrayList<>(Arrays.asList(
|
||||||
|
"{T}: Add {W}.",
|
||||||
|
"{T}: Return target legendary creature to its owner's hand."
|
||||||
|
));
|
||||||
|
Collections.sort(needList);
|
||||||
|
assertPlayableAbilities(needList);
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_RealGame_ActivatedAbilities_Restricted() {
|
||||||
|
// possible bug: wrongly enabled ability, see #10642
|
||||||
|
|
||||||
|
// Activated abilities of lands your opponents control can't be activated unless they're mana abilities.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Sharkey, Tyrant of the Shire", 1);
|
||||||
|
// {T}: Add {W}.
|
||||||
|
// {T}: Return target legendary creature to its owner's hand.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Karakas", 1);
|
||||||
|
|
||||||
|
// have mana abilities only
|
||||||
|
runCode("non-mana ability disabled", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> {
|
||||||
|
List<String> needList = new ArrayList<>(Collections.singletonList(
|
||||||
|
"{T}: Add {W}."
|
||||||
|
));
|
||||||
|
Collections.sort(needList);
|
||||||
|
assertPlayableAbilities(needList);
|
||||||
|
});
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlayableAbilities(List<String> need) {
|
||||||
|
// server side
|
||||||
|
List<String> realList = playerA.getPlayable(currentGame, true).stream()
|
||||||
|
.map(Ability::getRule)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Assert.assertEquals("wrong server side playable list", need.toString(), realList.toString());
|
||||||
|
|
||||||
|
// client side as game data
|
||||||
|
GameView gameView = getGameView(playerA);
|
||||||
|
realList.clear();
|
||||||
|
gameView.getCanPlayObjects().getObjects().forEach((objectId, stats) -> {
|
||||||
|
stats.getPlayableAbilityIds().forEach(abilityId -> {
|
||||||
|
Ability ability = currentGame.getAbility(abilityId, objectId).orElse(null);
|
||||||
|
realList.add(ability == null ? "null" : ability.getRule());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Collections.sort(realList);
|
||||||
|
Assert.assertEquals("wrong client side playable list", need.toString(), realList.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import mage.cards.Card;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.command.CommandObject;
|
import mage.game.command.CommandObject;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
@ -138,6 +139,13 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
|
||||||
return ActivationStatus.getFalse();
|
return ActivationStatus.getFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// activate restrictions by replacement effects (example: Sharkey, Tyrant of the Shire)
|
||||||
|
if (this.isActivatedAbility()) {
|
||||||
|
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY, this.getId(), this, playerId))) {
|
||||||
|
return ActivationStatus.getFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// all fine, can be activated
|
// all fine, can be activated
|
||||||
// TODO: WTF, must be rework to remove data change in canActivate call
|
// TODO: WTF, must be rework to remove data change in canActivate call
|
||||||
// (it can be called from any place by any player or card).
|
// (it can be called from any place by any player or card).
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ public final class PlayableCountIcon extends CardIconImpl {
|
||||||
private static String getHint(PlayableObjectStats objectStats) {
|
private static String getHint(PlayableObjectStats objectStats) {
|
||||||
String res = "Playable abilities: " + objectStats.getPlayableAmount();
|
String res = "Playable abilities: " + objectStats.getPlayableAmount();
|
||||||
// abilities list already sorted
|
// abilities list already sorted
|
||||||
List<String> list = objectStats.getPlayableAbilities();
|
List<String> list = objectStats.getPlayableAbilityNames();
|
||||||
final int[] counter = {0};
|
final int[] counter = {0};
|
||||||
if (list.size() > 0) {
|
if (!list.isEmpty()) {
|
||||||
res += "<br>" + list
|
res += "<br>" + list
|
||||||
.stream()
|
.stream()
|
||||||
.map(s -> {
|
.map(s -> {
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ public class GameEvent implements Serializable {
|
||||||
*/
|
*/
|
||||||
ACTIVATE_ABILITY, ACTIVATED_ABILITY,
|
ACTIVATE_ABILITY, ACTIVATED_ABILITY,
|
||||||
/* ACTIVATE_ABILITY, ACTIVATED_ABILITY,
|
/* ACTIVATE_ABILITY, ACTIVATED_ABILITY,
|
||||||
|
WARNING, do not use choose dialogs inside, can be calls multiple types, e.g. on playable checking
|
||||||
targetId id of the ability to activate / use
|
targetId id of the ability to activate / use
|
||||||
sourceId sourceId of the object with that ability
|
sourceId sourceId of the object with that ability
|
||||||
playerId player that tries to use this ability
|
playerId player that tries to use this ability
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,11 @@ public class PlayableObjectStats implements Serializable, Copyable<PlayableObjec
|
||||||
// collect info about abilities for card icons popup, must be simple online text (html symbols are possible)
|
// collect info about abilities for card icons popup, must be simple online text (html symbols are possible)
|
||||||
// some long html tags can be miss (example: ability extra hint) -- that's ok
|
// some long html tags can be miss (example: ability extra hint) -- that's ok
|
||||||
String shortInfo = ability.toString();
|
String shortInfo = ability.toString();
|
||||||
|
shortInfo = shortInfo.replace("<br>", " ");
|
||||||
|
shortInfo = shortInfo.replace("\n", " ");
|
||||||
if (shortInfo.length() > 50) {
|
if (shortInfo.length() > 50) {
|
||||||
shortInfo = shortInfo.substring(0, 50 - 1) + "...";
|
shortInfo = shortInfo.substring(0, 50 - 1) + "...";
|
||||||
}
|
}
|
||||||
shortInfo = shortInfo.replace("<br>", " ");
|
|
||||||
shortInfo = shortInfo.replace("\n", " ");
|
|
||||||
dest.add(new PlayableObjectRecord(ability.getId(), shortInfo));
|
dest.add(new PlayableObjectRecord(ability.getId(), shortInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ public class PlayableObjectStats implements Serializable, Copyable<PlayableObjec
|
||||||
+ this.other.size();
|
+ this.other.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getPlayableAbilities() {
|
public List<String> getPlayableAbilityNames() {
|
||||||
List<String> all = new ArrayList<>();
|
List<String> all = new ArrayList<>();
|
||||||
all.addAll(this.basicManaAbilities.stream().map(PlayableObjectRecord::getValue).sorted().collect(Collectors.toList()));
|
all.addAll(this.basicManaAbilities.stream().map(PlayableObjectRecord::getValue).sorted().collect(Collectors.toList()));
|
||||||
all.addAll(this.basicPlayAbilities.stream().map(PlayableObjectRecord::getValue).sorted().collect(Collectors.toList()));
|
all.addAll(this.basicPlayAbilities.stream().map(PlayableObjectRecord::getValue).sorted().collect(Collectors.toList()));
|
||||||
|
|
@ -100,6 +100,15 @@ public class PlayableObjectStats implements Serializable, Copyable<PlayableObjec
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<UUID> getPlayableAbilityIds() {
|
||||||
|
List<UUID> all = new ArrayList<>();
|
||||||
|
all.addAll(this.basicManaAbilities.stream().map(PlayableObjectRecord::getId).collect(Collectors.toList()));
|
||||||
|
all.addAll(this.basicPlayAbilities.stream().map(PlayableObjectRecord::getId).collect(Collectors.toList()));
|
||||||
|
all.addAll(this.basicCastAbilities.stream().map(PlayableObjectRecord::getId).collect(Collectors.toList()));
|
||||||
|
all.addAll(this.other.stream().map(PlayableObjectRecord::getId).collect(Collectors.toList()));
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
public int getPlayableImportantAmount() {
|
public int getPlayableImportantAmount() {
|
||||||
// return only important abilities (e.g. show it as card icons)
|
// return only important abilities (e.g. show it as card icons)
|
||||||
return this.other.size();
|
return this.other.size();
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@ import mage.abilities.ActivatedAbility;
|
||||||
import mage.util.Copyable;
|
import mage.util.Copyable;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains stats with all playable cards for the player
|
* Contains stats with all playable cards for the player
|
||||||
|
|
@ -66,4 +63,8 @@ public class PlayableObjectsList implements Serializable, Copyable<PlayableObjec
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<UUID, PlayableObjectStats> getObjects() {
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1447,58 +1447,52 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean playManaAbility(ActivatedManaAbilityImpl ability, Game game) {
|
protected boolean playManaAbility(ActivatedManaAbilityImpl ability, Game game) {
|
||||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY,
|
int bookmark = game.bookmarkState();
|
||||||
ability.getId(), ability, playerId))) {
|
if (ability.activate(game, false) && ability.resolve(game)) {
|
||||||
int bookmark = game.bookmarkState();
|
if (ability.isUndoPossible()) {
|
||||||
if (ability.activate(game, false) && ability.resolve(game)) {
|
if (storedBookmark == -1 || storedBookmark > bookmark) { // e.g. useful for undo Nykthos, Shrine to Nyx
|
||||||
if (ability.isUndoPossible()) {
|
setStoredBookmark(bookmark);
|
||||||
if (storedBookmark == -1 || storedBookmark > bookmark) { // e.g. useful for undo Nykthos, Shrine to Nyx
|
|
||||||
setStoredBookmark(bookmark);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resetStoredBookmark(game);
|
|
||||||
}
|
}
|
||||||
return true;
|
} else {
|
||||||
|
resetStoredBookmark(game);
|
||||||
}
|
}
|
||||||
restoreState(bookmark, ability.getRule(), game);
|
return true;
|
||||||
}
|
}
|
||||||
|
restoreState(bookmark, ability.getRule(), game);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean playAbility(ActivatedAbility ability, Game game) {
|
protected boolean playAbility(ActivatedAbility ability, Game game) {
|
||||||
//20091005 - 602.2a
|
//20091005 - 602.2a
|
||||||
|
int bookmark = game.bookmarkState();
|
||||||
if (ability.isUsesStack()) {
|
if (ability.isUsesStack()) {
|
||||||
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATE_ABILITY,
|
// put to stack
|
||||||
ability.getId(), ability, playerId))) {
|
setStoredBookmark(bookmark); // move global bookmark to current state (if you activated mana before then you can't rollback it)
|
||||||
int bookmark = game.bookmarkState();
|
ability.newId();
|
||||||
setStoredBookmark(bookmark); // move global bookmark to current state (if you activated mana before then you can't rollback it)
|
ability.setControllerId(playerId);
|
||||||
ability.newId();
|
game.getStack().push(new StackAbility(ability, playerId));
|
||||||
ability.setControllerId(playerId);
|
if (ability.activate(game, false)) {
|
||||||
game.getStack().push(new StackAbility(ability, playerId));
|
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY,
|
||||||
if (ability.activate(game, false)) {
|
ability.getId(), ability, playerId));
|
||||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ACTIVATED_ABILITY,
|
if (!game.isSimulation()) {
|
||||||
ability.getId(), ability, playerId));
|
game.informPlayers(getLogName() + ability.getGameLogMessage(game));
|
||||||
if (!game.isSimulation()) {
|
|
||||||
game.informPlayers(getLogName() + ability.getGameLogMessage(game));
|
|
||||||
}
|
|
||||||
game.removeBookmark(bookmark);
|
|
||||||
resetStoredBookmark(game);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
restoreState(bookmark, ability.getRule(), game);
|
game.removeBookmark(bookmark);
|
||||||
|
resetStoredBookmark(game);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
restoreState(bookmark, ability.getRule(), game);
|
||||||
} else {
|
} else {
|
||||||
int bookmark = game.bookmarkState();
|
// resolve without stack
|
||||||
if (ability.activate(game, false)) {
|
if (ability.activate(game, false)) {
|
||||||
ability.resolve(game);
|
ability.resolve(game);
|
||||||
game.removeBookmark(bookmark);
|
game.removeBookmark(bookmark);
|
||||||
resetStoredBookmark(game);
|
resetStoredBookmark(game);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
restoreState(bookmark, ability.getRule(), game);
|
|
||||||
}
|
}
|
||||||
|
restoreState(bookmark, ability.getRule(), game);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue