mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge branch 'surveil' into 'master'
Surveil Closes #681 and #684 See merge request core-developers/forge!904
This commit is contained in:
@@ -292,8 +292,26 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return ImmutablePair.of(toTop, toBottom);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#arrangeForSurveil(forge.game.card.CardCollection)
|
||||
*/
|
||||
@Override
|
||||
public ImmutablePair<CardCollection, CardCollection> arrangeForSurveil(CardCollection topN) {
|
||||
CardCollection toGraveyard = new CardCollection();
|
||||
CardCollection toTop = new CardCollection();
|
||||
|
||||
// TODO add AI logic there, similar to Scry
|
||||
|
||||
toTop.addAll(topN);
|
||||
|
||||
Collections.shuffle(toTop, MyRandom.getRandom());
|
||||
return ImmutablePair.of(toTop, toGraveyard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willPutCardOnTop(Card c) {
|
||||
// TODO add Logic there similar to Scry. this is used for Clash
|
||||
|
||||
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Surveil, SurveilAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||
|
||||
@@ -11,7 +11,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
@@ -22,9 +21,8 @@ public class ScryAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null) { // It doesn't appear that Scry ever targets
|
||||
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
|
||||
// ability is targeted
|
||||
sa.resetTargets();
|
||||
|
||||
|
||||
44
forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
Normal file
44
forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class SurveilAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player, forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
|
||||
// ability is targeted
|
||||
sa.resetTargets();
|
||||
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return doTriggerAINoCost(ai, sa, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.game.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1848,7 +1848,7 @@ public class GameAction {
|
||||
//Vancouver Mulligan
|
||||
for(Player p : whoCanMulligan) {
|
||||
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
|
||||
p.scry(1);
|
||||
p.scry(1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.game.event.GameEventPlayerPoisoned;
|
||||
import forge.game.event.GameEventScry;
|
||||
import forge.game.event.GameEventSpellAbilityCast;
|
||||
import forge.game.event.GameEventSpellResolved;
|
||||
import forge.game.event.GameEventSurveil;
|
||||
import forge.game.event.GameEventTurnBegan;
|
||||
import forge.game.event.GameEventTurnPhase;
|
||||
import forge.game.event.IGameEventVisitor;
|
||||
@@ -65,7 +66,24 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventSurveil ev) {
|
||||
String scryOutcome = "";
|
||||
String toLibrary = Lang.nounWithAmount(ev.toLibrary, "card") + " to the top of the library";
|
||||
String toGraveyard = Lang.nounWithAmount(ev.toGraveyard, "card") + " to the graveyard";
|
||||
|
||||
if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
|
||||
scryOutcome = ev.player.toString() + " surveil " + toLibrary + " and " + toGraveyard;
|
||||
} else if (ev.toGraveyard == 0) {
|
||||
scryOutcome = ev.player.toString() + " surveil " + toLibrary;
|
||||
} else {
|
||||
scryOutcome = ev.player.toString() + " surveil " + toGraveyard;
|
||||
}
|
||||
|
||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventSpellResolved ev) {
|
||||
String messageForLog = ev.hasFizzled ? ev.spell.getHostCard().getName() + " ability fizzles." : ev.spell.getStackDescription();
|
||||
|
||||
@@ -144,6 +144,7 @@ public enum ApiType {
|
||||
SkipTurn (SkipTurnEffect.class),
|
||||
StoreSVar (StoreSVarEffect.class),
|
||||
StoreMap (StoreMapEffect.class),
|
||||
Surveil (SurveilEffect.class),
|
||||
Tap (TapEffect.class),
|
||||
TapAll (TapAllEffect.class),
|
||||
TapOrUntap (TapOrUntapEffect.class),
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ScryEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
p.scry(num);
|
||||
p.scry(num, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class SurveilEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(Lang.joinHomogenous(getTargetPlayers(sa)));
|
||||
|
||||
int num = 1;
|
||||
if (sa.hasParam("Amount")) {
|
||||
num = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
|
||||
}
|
||||
|
||||
sb.append(" surveil (").append(num).append(").");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
int num = 1;
|
||||
if (sa.hasParam("Amount")) {
|
||||
num = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
|
||||
}
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
if (isOptional && !p.getController().confirmAction(sa, null, "Do you want to surveil?")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p.surveil(num, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package forge.game.event;
|
||||
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class GameEventSurveil extends GameEvent {
|
||||
|
||||
public final Player player;
|
||||
public final int toLibrary, toGraveyard;
|
||||
|
||||
public GameEventSurveil(Player player, int toLibrary, int toGraveyard) {
|
||||
this.player = player;
|
||||
this.toLibrary = toLibrary;
|
||||
this.toGraveyard = toGraveyard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T visit(IGameEventVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public interface IGameEventVisitor<T> {
|
||||
T visit(GameEventSpellAbilityCast gameEventSpellAbilityCast);
|
||||
T visit(GameEventSpellResolved event);
|
||||
T visit(GameEventSpellRemovedFromStack event);
|
||||
T visit(GameEventSurveil event);
|
||||
T visit(GameEventTokenCreated event);
|
||||
T visit(GameEventTurnBegan gameEventTurnBegan);
|
||||
T visit(GameEventTurnEnded event);
|
||||
@@ -87,6 +88,7 @@ public interface IGameEventVisitor<T> {
|
||||
public T visit(GameEventSpellResolved event) { return null; }
|
||||
public T visit(GameEventSpellAbilityCast event) { return null; }
|
||||
public T visit(GameEventSpellRemovedFromStack event) { return null; }
|
||||
public T visit(GameEventSurveil event) { return null; }
|
||||
public T visit(GameEventTokenCreated event) { return null; }
|
||||
public T visit(GameEventTurnBegan event) { return null; }
|
||||
public T visit(GameEventTurnEnded event) { return null; }
|
||||
|
||||
@@ -1248,15 +1248,11 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return drawCards(1);
|
||||
}
|
||||
|
||||
public void scry(final int numScry) {
|
||||
final CardCollection topN = new CardCollection();
|
||||
final PlayerZone library = getZone(ZoneType.Library);
|
||||
final int actualNumScry = Math.min(numScry, library.size());
|
||||
public void scry(final int numScry, SpellAbility cause) {
|
||||
final CardCollection topN = new CardCollection(this.getCardsIn(ZoneType.Library, numScry));
|
||||
|
||||
if (actualNumScry == 0) { return; }
|
||||
|
||||
for (int i = 0; i < actualNumScry; i++) {
|
||||
topN.add(library.get(i));
|
||||
if (topN.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ImmutablePair<CardCollection, CardCollection> lists = getController().arrangeForScry(topN);
|
||||
@@ -1268,7 +1264,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
if (toBottom != null) {
|
||||
for(Card c : toBottom) {
|
||||
getGame().getAction().moveToBottomOfLibrary(c, null, null);
|
||||
getGame().getAction().moveToBottomOfLibrary(c, cause, null);
|
||||
numToBottom++;
|
||||
}
|
||||
}
|
||||
@@ -1276,7 +1272,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (toTop != null) {
|
||||
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
|
||||
for(Card c : toTop) {
|
||||
getGame().getAction().moveToLibrary(c, null, null);
|
||||
getGame().getAction().moveToLibrary(c, cause, null);
|
||||
numToTop++;
|
||||
}
|
||||
}
|
||||
@@ -1288,6 +1284,42 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
|
||||
public void surveil(final int num, SpellAbility cause) {
|
||||
final CardCollection topN = new CardCollection(this.getCardsIn(ZoneType.Library, num));
|
||||
|
||||
if (topN.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ImmutablePair<CardCollection, CardCollection> lists = getController().arrangeForSurveil(topN);
|
||||
final CardCollection toTop = lists.getLeft();
|
||||
final CardCollection toGrave = lists.getRight();
|
||||
|
||||
int numToGrave = 0;
|
||||
int numToTop = 0;
|
||||
|
||||
if (toGrave != null) {
|
||||
for(Card c : toGrave) {
|
||||
getGame().getAction().moveToGraveyard(c, cause, null);
|
||||
numToGrave++;
|
||||
}
|
||||
}
|
||||
|
||||
if (toTop != null) {
|
||||
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
|
||||
for(Card c : toTop) {
|
||||
getGame().getAction().moveToLibrary(c, cause, null);
|
||||
numToTop++;
|
||||
}
|
||||
}
|
||||
|
||||
getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave));
|
||||
|
||||
//final Map<String, Object> runParams = Maps.newHashMap();
|
||||
//runParams.put("Player", this);
|
||||
//getGame().getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
|
||||
public boolean canMulligan() {
|
||||
return !getZone(ZoneType.Hand).isEmpty();
|
||||
}
|
||||
|
||||
@@ -140,6 +140,8 @@ public abstract class PlayerController {
|
||||
/** Shows message to player to reveal chosen cardName, creatureType, number etc. AI must analyze API to understand what that is */
|
||||
public abstract void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value);
|
||||
public abstract ImmutablePair<CardCollection, CardCollection> arrangeForScry(CardCollection topN);
|
||||
public abstract ImmutablePair<CardCollection, CardCollection> arrangeForSurveil(CardCollection topN);
|
||||
|
||||
public abstract boolean willPutCardOnTop(Card c);
|
||||
public final CardCollectionView orderMoveToZoneList(CardCollectionView cards, ZoneType destinationZone) {
|
||||
return orderMoveToZoneList(cards, destinationZone, null);
|
||||
|
||||
@@ -246,6 +246,11 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
return ImmutablePair.of(topN, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutablePair<CardCollection, CardCollection> arrangeForSurveil(CardCollection topN) {
|
||||
return ImmutablePair.of(topN, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willPutCardOnTop(Card c) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Deadly Visit
|
||||
ManaCost:3 B B
|
||||
Types:Sorcery
|
||||
A:SP$ Destroy | Cost$ 3 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Subability$ DBSurveil | SpellDescription$ Destroy target creature.
|
||||
SVar:DBSurveil:DB$ Surveil | SurveilNum$ 2
|
||||
A:SP$ Destroy | Cost$ 3 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Subability$ DBSurveil | SpellDescription$ Destroy target creature. Surveil 2 (Look at the top two cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.)
|
||||
SVar:DBSurveil:DB$ Surveil | Amount$ 2
|
||||
Oracle:Destroy target creature.\nSurveil 2. (Look at the top two cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Sinister Sabotage
|
||||
ManaCost:1 U U
|
||||
Types:Instant
|
||||
A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | Subability$ DBSurveil | SpellDescription$ Counter target spell.1 (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
SVar:DBSurveil:DB$ Surveil | SurveilNum$ 1
|
||||
A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | Subability$ DBSurveil | SpellDescription$ Counter target spell. Surveil 1 (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
SVar:DBSurveil:DB$ Surveil | Amount$ 1
|
||||
Oracle:Counter target spell.\nSurveil 1. (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Thought Erasure
|
||||
ManaCost:U B
|
||||
Types:Sorcery
|
||||
A:SP$ Discard | Cost$ U B | ValidTgts$ Opponent | DiscardValid$ Card.nonLand | NumCards$ 1 | Mode$ RevealYouChoose | Subability$ DBSurveil | SpellDescription$ Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.
|
||||
SVar:DBSurveil:DB$ Surveil | SurveilNum$ 1
|
||||
A:SP$ Discard | Cost$ U B | ValidTgts$ Opponent | DiscardValid$ Card.nonLand | NumCards$ 1 | Mode$ RevealYouChoose | Subability$ DBSurveil | SpellDescription$ Target opponent reveals their hand. You choose a nonland card from it. That player discards that card. Surveil 1 (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
SVar:DBSurveil:DB$ Surveil | Amount$ 1
|
||||
Oracle:Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.\nSurveil 1. (Look at the top card of your library. You may put it into your graveyard.)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Unexplained Disappearance
|
||||
ManaCost:1 U
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Hand | Subability$ DBSurveil | SpellDescription$ Return target creature to its owner's hand.
|
||||
SVar:DBSurveil:DB$ Surveil | SurveilNum$ 1
|
||||
A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Hand | Subability$ DBSurveil | SpellDescription$ Return target creature to its owner's hand. Surveil 1 (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
SVar:DBSurveil:DB$ Surveil | Amount$ 1
|
||||
Oracle:Return target creature to its owner's hand.\nSurveil 1. (Look at the top card of your library. You may put that card into your graveyard.)
|
||||
|
||||
@@ -769,6 +769,43 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
return ImmutablePair.of(toTop, toBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutablePair<CardCollection, CardCollection> arrangeForSurveil(final CardCollection topN) {
|
||||
CardCollection toGrave = null;
|
||||
CardCollection toTop = null;
|
||||
|
||||
tempShowCards(topN);
|
||||
if (topN.size() == 1) {
|
||||
final Card c = topN.getFirst();
|
||||
final CardView view = CardView.get(c);
|
||||
|
||||
tempShowCard(c);
|
||||
getGui().setCard(view);
|
||||
boolean result = false;
|
||||
result = InputConfirm.confirm(this, view, TextUtil.concatNoSpace("Put ", view.toString(), " on the top of library or graveyard?"),
|
||||
true, ImmutableList.of("Library", "Graveyard"));
|
||||
if (result) {
|
||||
toTop = topN;
|
||||
} else {
|
||||
toGrave = topN;
|
||||
}
|
||||
} else {
|
||||
toGrave = game.getCardList(getGui().many("Select cards to be put into the graveyard",
|
||||
"Cards to put in the graveyard", -1, CardView.getCollection(topN), null));
|
||||
topN.removeAll((Collection<?>) toGrave);
|
||||
if (topN.isEmpty()) {
|
||||
toTop = null;
|
||||
} else if (topN.size() == 1) {
|
||||
toTop = topN;
|
||||
} else {
|
||||
toTop = game.getCardList(getGui().order("Arrange cards to be put on top of your library",
|
||||
"Top of Library", CardView.getCollection(topN), null));
|
||||
}
|
||||
}
|
||||
endTempShowCards();
|
||||
return ImmutablePair.of(toTop, toGrave);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willPutCardOnTop(final Card c) {
|
||||
final CardView view = CardView.get(c);
|
||||
|
||||
Reference in New Issue
Block a user