Pull in necessary files from forge-gui to start being able to flesh out gameplay

This commit is contained in:
drdev
2014-03-09 07:16:18 +00:00
parent 5abb581109
commit a79fd3f240
58 changed files with 6458 additions and 33 deletions

50
.gitattributes vendored
View File

@@ -16015,6 +16015,7 @@ forge-m-base/libs/gdx-freetype.jar -text
forge-m-base/libs/gdx-sources.jar -text
forge-m-base/libs/gdx.jar -text
forge-m-base/pom.xml -text
forge-m-base/src/forge/FThreads.java -text
forge-m-base/src/forge/Forge.java -text
forge-m-base/src/forge/assets/FImage.java -text
forge-m-base/src/forge/assets/FSkin.java -text
@@ -16034,6 +16035,21 @@ forge-m-base/src/forge/model/CardCollections.java -text
forge-m-base/src/forge/model/FModel.java -text
forge-m-base/src/forge/model/MetaSet.java -text
forge-m-base/src/forge/model/UnOpenedMeta.java -text
forge-m-base/src/forge/net/FServer.java -text
forge-m-base/src/forge/net/IClientSocket.java -text
forge-m-base/src/forge/net/IConnectionObserver.java -text
forge-m-base/src/forge/net/Lobby.java -text
forge-m-base/src/forge/net/LobbyPlayerRemote.java -text
forge-m-base/src/forge/net/client/INetClient.java -text
forge-m-base/src/forge/net/client/InvalidFieldInPacketException.java -text
forge-m-base/src/forge/net/client/NetClient.java -text
forge-m-base/src/forge/net/client/state/ConnectedClientState.java -text
forge-m-base/src/forge/net/client/state/IClientState.java -text
forge-m-base/src/forge/net/client/state/InLobbyClientState.java -text
forge-m-base/src/forge/net/client/state/UnauthorizedClientState.java -text
forge-m-base/src/forge/player/HumanCostDecision.java -text
forge-m-base/src/forge/player/HumanPlay.java -text
forge-m-base/src/forge/player/HumanPlaySpellAbility.java -text
forge-m-base/src/forge/player/LobbyPlayerHuman.java -text
forge-m-base/src/forge/player/PlayerControllerHuman.java -text
forge-m-base/src/forge/screens/FScreen.java -text
@@ -16044,11 +16060,41 @@ forge-m-base/src/forge/screens/draft/DraftScreen.java -text
forge-m-base/src/forge/screens/guantlet/GuantletScreen.java -text
forge-m-base/src/forge/screens/home/HomeScreen.java -text
forge-m-base/src/forge/screens/match/FControl.java -text
forge-m-base/src/forge/screens/match/FControlGameEventHandler.java -text
forge-m-base/src/forge/screens/match/FControlGamePlayback.java -text
forge-m-base/src/forge/screens/match/MatchScreen.java -text
forge-m-base/src/forge/screens/match/events/IUiEventVisitor.java -text
forge-m-base/src/forge/screens/match/events/UiEvent.java -text
forge-m-base/src/forge/screens/match/events/UiEventAttackerDeclared.java -text
forge-m-base/src/forge/screens/match/events/UiEventBlockerAssigned.java -text
forge-m-base/src/forge/screens/match/input/ButtonUtil.java -text
forge-m-base/src/forge/screens/match/input/Input.java -text
forge-m-base/src/forge/screens/match/input/InputAttack.java -text
forge-m-base/src/forge/screens/match/input/InputBase.java -text
forge-m-base/src/forge/screens/match/input/InputBlock.java -text
forge-m-base/src/forge/screens/match/input/InputConfirm.java -text
forge-m-base/src/forge/screens/match/input/InputConfirmMulligan.java -text
forge-m-base/src/forge/screens/match/input/InputLockUI.java -text
forge-m-base/src/forge/screens/match/input/InputPassPriority.java -text
forge-m-base/src/forge/screens/match/input/InputPayMana.java -text
forge-m-base/src/forge/screens/match/input/InputPayManaOfCostPayment.java -text
forge-m-base/src/forge/screens/match/input/InputPayManaSimple.java -text
forge-m-base/src/forge/screens/match/input/InputPayManaX.java -text
forge-m-base/src/forge/screens/match/input/InputPlaybackControl.java -text
forge-m-base/src/forge/screens/match/input/InputProliferate.java -text
forge-m-base/src/forge/screens/match/input/InputProxy.java -text
forge-m-base/src/forge/screens/match/input/InputQueue.java -text
forge-m-base/src/forge/screens/match/input/InputSelectCardsForConvoke.java -text
forge-m-base/src/forge/screens/match/input/InputSelectCardsFromList.java -text
forge-m-base/src/forge/screens/match/input/InputSelectEntitiesFromList.java -text
forge-m-base/src/forge/screens/match/input/InputSelectManyBase.java -text
forge-m-base/src/forge/screens/match/input/InputSelectTargets.java -text
forge-m-base/src/forge/screens/match/input/InputSynchronized.java -text
forge-m-base/src/forge/screens/match/input/InputSyncronizedBase.java -text
forge-m-base/src/forge/screens/match/views/VAvatar.java -text
forge-m-base/src/forge/screens/match/views/VField.java -text
forge-m-base/src/forge/screens/match/views/VLog.java -text
forge-m-base/src/forge/screens/match/views/VPhases.java -text
forge-m-base/src/forge/screens/match/views/VPhaseIndicator.java -text
forge-m-base/src/forge/screens/match/views/VPlayerPanel.java -text
forge-m-base/src/forge/screens/match/views/VPrompt.java -text
forge-m-base/src/forge/screens/match/views/VStack.java -text
@@ -16068,7 +16114,9 @@ forge-m-base/src/forge/toolbox/FOverlay.java -text
forge-m-base/src/forge/toolbox/FProgressBar.java -text
forge-m-base/src/forge/toolbox/FScrollPane.java -text
forge-m-base/src/forge/toolbox/GuiChoose.java -text
forge-m-base/src/forge/toolbox/GuiDialog.java -text
forge-m-base/src/forge/utils/Constants.java -text
forge-m-base/src/forge/utils/Evaluator.java -text
forge-m-base/src/forge/utils/ForgePreferences.java -text
forge-m-base/src/forge/utils/ForgeProfileProperties.java -text
forge-m-base/src/forge/utils/Preferences.java -text

View File

@@ -0,0 +1,101 @@
package forge;
import java.io.PrintStream;
import forge.util.ThreadUtil;
/**
* TODO: Write javadoc for this type.
*
*/
public class FThreads {
private FThreads() { } //don't allow creating instance
public static void assertExecutedByEdt(final boolean mustBeEDT) {
//TODO
}
public static void invokeInEdtLater(Runnable runnable) {
//SwingUtilities.invokeLater(runnable);
}
public static void invokeInEdtNowOrLater(Runnable proc) {
if (isGuiThread()) {
proc.run();
}
else {
invokeInEdtLater(proc);
}
}
public static void invokeInEdtAndWait(final Runnable proc) {
proc.run();
/*if (SwingUtilities.isEventDispatchThread()) {
// Just run in the current thread.
proc.run();
}
else {
try {
SwingUtilities.invokeAndWait(proc);
}
catch (final InterruptedException exn) {
throw new RuntimeException(exn);
}
catch (final InvocationTargetException exn) {
throw new RuntimeException(exn);
}
}*/
}
public static boolean isGuiThread() {
return true;
//return SwingUtilities.isEventDispatchThread();
}
public static void delayInEDT(int milliseconds, final Runnable inputUpdater) {
Runnable runInEdt = new Runnable() {
@Override
public void run() {
FThreads.invokeInEdtNowOrLater(inputUpdater);
}
};
ThreadUtil.delay(milliseconds, runInEdt);
}
public static String debugGetCurrThreadId() {
return isGuiThread() ? "EDT" : Thread.currentThread().getName();
}
public static String prependThreadId(String message) {
return debugGetCurrThreadId() + " > " + message;
}
public static void dumpStackTrace(PrintStream stream) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
stream.printf("%s > %s called from %s%n", debugGetCurrThreadId(),
trace[2].getClassName() + "." + trace[2].getMethodName(), trace[3].toString());
int i = 0;
for (StackTraceElement se : trace) {
if (i<2) { i++; }
else { stream.println(se.toString()); }
}
}
public static String debugGetStackTraceItem(int depth, boolean shorter) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
String lastItem = trace[depth].toString();
if (shorter) {
int lastPeriod = lastItem.lastIndexOf('.');
lastPeriod = lastItem.lastIndexOf('.', lastPeriod-1);
lastPeriod = lastItem.lastIndexOf('.', lastPeriod-1);
lastItem = lastItem.substring(lastPeriod+1);
return String.format("%s > from %s", debugGetCurrThreadId(), lastItem);
}
return String.format("%s > %s called from %s", debugGetCurrThreadId(),
trace[2].getClassName() + "." + trace[2].getMethodName(), lastItem);
}
public static String debugGetStackTraceItem(int depth) {
return debugGetStackTraceItem(depth, false);
}
}

View File

@@ -45,6 +45,8 @@ import com.badlogic.gdx.Gdx;
* this class must be either private or public static final.
*/
public class FModel {
private FModel() { } //don't allow creating instance
private static StaticData magicDb;
private static PrintStream oldSystemOut;

View File

@@ -0,0 +1,32 @@
package forge.net;
import com.google.common.base.Supplier;
import forge.game.player.LobbyPlayer;
import forge.player.LobbyPlayerHuman;
public class FServer {
private FServer() { } //don't allow creating instance
private static Lobby lobby;
public static Lobby getLobby() {
if (lobby == null) {
//not a very good solution still
lobby = new Lobby(new Supplier<LobbyPlayer>() {
@Override
public LobbyPlayer get() {
return new LobbyPlayerHuman("Human");
}
});
}
return lobby;
}
/*private final NetServer server = new NetServer();
public NetServer getServer() {
// TODO Auto-generated method stub
return server;
}*/
}

View File

@@ -0,0 +1,7 @@
package forge.net;
public interface IClientSocket {
boolean isOpen();
void send(String message);
void close(String farewell);
}

View File

@@ -0,0 +1,9 @@
package forge.net;
public interface IConnectionObserver {
/** Notifies that the client is gone, it's too late to send anything */
public void onConnectionClosed();
/** Notifies of an incoming message */
public void onMessage(String data);
}

View File

@@ -0,0 +1,87 @@
package forge.net;
import com.google.common.base.Supplier;
import forge.ai.AiProfileUtil;
import forge.ai.LobbyPlayerAi;
import forge.assets.FSkin;
import forge.game.player.LobbyPlayer;
import forge.model.FModel;
import forge.net.client.INetClient;
import forge.util.MyRandom;
import forge.utils.ForgePreferences.FPref;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Lobby {
private final LobbyPlayer guiPlayer;
public Lobby(Supplier<LobbyPlayer> humanFactory){
guiPlayer = humanFactory.get();
}
private Map<String, LobbyPlayerRemote> remotePlayers = new ConcurrentHashMap<String, LobbyPlayerRemote>();
//private final LobbyPlayerAi system = new LobbyPlayerAi("System");
public final LobbyPlayer getGuiPlayer() {
return guiPlayer;
}
public final LobbyPlayer getAiPlayer() { return getAiPlayer(getRandomName()); }
public final LobbyPlayer getAiPlayer(String name) {
return getAiPlayer(name, FSkin.isLoaded() ? MyRandom.getRandom().nextInt(FSkin.getAvatars().size()) : 0);
}
public final LobbyPlayer getAiPlayer(String name, int avatarIndex) {
LobbyPlayerAi player = new LobbyPlayerAi(name);
// TODO: implement specific AI profiles for quest mode.
String lastProfileChosen = FModel.getPreferences().getPref(FPref.UI_CURRENT_AI_PROFILE);
player.setRotateProfileEachGame(lastProfileChosen.equals(AiProfileUtil.AI_PROFILE_RANDOM_DUEL));
if(lastProfileChosen.equals(AiProfileUtil.AI_PROFILE_RANDOM_MATCH)) {
lastProfileChosen = AiProfileUtil.getRandomProfile();
System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", lastProfileChosen, player.getName()));
}
player.setAiProfile(lastProfileChosen);
if(FSkin.isLoaded())
player.setAvatarIndex(avatarIndex);
return player;
}
/** Returns a random name from the supplied list. */
private String getRandomName() {
return "Bob";
/*String playerName = GuiDisplayUtil.getPlayerName();
String aiName = NameGenerator.getRandomName("Any", "Generic", playerName);
return aiName;*/
}
public LobbyPlayer getQuestPlayer() {
return guiPlayer;
}
public synchronized LobbyPlayer findOrCreateRemotePlayer(String name, INetClient client) {
if (remotePlayers.containsKey(name))
return remotePlayers.get(name);
LobbyPlayerRemote res = new LobbyPlayerRemote(name, client);
//speak(ChatArea.Room, system, res.getName() + " has joined the server.");
// have to load avatar from remote user's preferences here
remotePlayers.put(name, res);
return res;
}
public void disconnectPlayer(LobbyPlayer player) {
// Should set up a timer here to discard player and all of his games after 20 minutes of being offline
}
/*public void speak(ChatArea room, LobbyPlayer player, String message) {
getGuiPlayer().hear(player, message);
for(LobbyPlayer remote : remotePlayers.values()) {
remote.hear(player, message);
}
}*/
}

View File

@@ -0,0 +1,46 @@
package forge.net;
import forge.game.Game;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.net.client.INetClient;
import forge.net.protocol.toclient.ChatPacketClt;
public class LobbyPlayerRemote extends LobbyPlayer {
private final INetClient connection;
public LobbyPlayerRemote(String name, INetClient netClient) { // This is actually a doubtful idea - this means 1 window per player.
super(name);
connection = netClient;
}
@Override
protected PlayerType getType() {
return PlayerType.REMOTE;
}
/* (non-Javadoc)
* @see forge.game.player.LobbyPlayer#getPlayer(forge.game.GameState)
*/
@Override
public Player createIngamePlayer(Game gameState) {
// Cannot create remote players yet
throw new UnsupportedOperationException("method is not implemented");
}
@Override
public void hear(LobbyPlayer player, String message) {
connection.send(new ChatPacketClt(player.getName(), message));
}
/* (non-Javadoc)
* @see forge.game.player.LobbyPlayer#createControllerFor(forge.game.player.Player)
*/
@Override
public PlayerController createControllerFor(Player p) {
// Cannot create remote players yet
throw new UnsupportedOperationException("method is not implemented");
}
}

View File

@@ -0,0 +1,25 @@
package forge.net.client;
import forge.game.player.LobbyPlayer;
import forge.net.client.state.IClientState;
import forge.net.protocol.toclient.IPacketClt;
/**
* TODO: Write javadoc for this type.
*
*/
public interface INetClient {
/**
* TODO: Write javadoc for this method.
* @param echoMessage
*/
void send(IPacketClt message);
void createPlayer(String playerName);
LobbyPlayer getPlayer();
void replaceState(IClientState old, IClientState newState);
}

View File

@@ -0,0 +1,29 @@
package forge.net.client;
/**
* Indicates incorrect field in an incoming packet
*/
public class InvalidFieldInPacketException extends RuntimeException {
private static final long serialVersionUID = 4505312413627923468L;
/**
* TODO: Write javadoc for Constructor.
* @param message
*/
public InvalidFieldInPacketException(String message) {
super(message);
}
/**
* TODO: Write javadoc for Constructor.
* @param message
* @param cause
*/
public InvalidFieldInPacketException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,98 @@
package forge.net.client;
import forge.game.player.LobbyPlayer;
import forge.net.FServer;
import forge.net.IClientSocket;
import forge.net.IConnectionObserver;
import forge.net.client.state.ConnectedClientState;
import forge.net.client.state.IClientState;
import forge.net.client.state.UnauthorizedClientState;
import forge.net.protocol.ClientProtocol;
import forge.net.protocol.ClientProtocolJson;
import forge.net.protocol.toclient.ErrorIncorrectPacketClt;
import forge.net.protocol.toclient.ErrorNoStateForPacketClt;
import forge.net.protocol.toclient.IPacketClt;
import forge.net.protocol.toclient.WelcomePacketClt;
import forge.net.protocol.toserver.IPacketSrv;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class NetClient implements IConnectionObserver, INetClient{
private final IClientSocket socket;
private final BlockingDeque<IClientState> state = new LinkedBlockingDeque<IClientState>();
private LobbyPlayer player = null;
private final ClientProtocol<IPacketSrv, IPacketClt> protocol;
public NetClient(IClientSocket clientSocket) {
socket = clientSocket;
state.push(new ConnectedClientState(this));
state.push(new UnauthorizedClientState(this));
protocol = new ClientProtocolJson();
send(new WelcomePacketClt("Welcome to server"));
}
public void autorized() {
}
/* (non-Javadoc)
* @see forge.net.client.IConnectionObserver#onConnectionClosed()
*/
@Override
public void onConnectionClosed() {
// Tell the game, the client is gone.
if ( player != null ) {
FServer.getLobby().disconnectPlayer(player);
}
}
@Override
public final LobbyPlayer getPlayer() {
return player;
}
/** Receives input from network client */
@Override
public void onMessage(String data) {
IPacketSrv p = protocol.decodePacket(data);
boolean processed = false;
try{
for(IClientState s : state) {
if ( s.processPacket(p) ) { processed = true; break; }
}
if (!processed)
send(new ErrorNoStateForPacketClt(p.getClass().getSimpleName()));
} catch ( InvalidFieldInPacketException ex ) {
send(new ErrorIncorrectPacketClt(ex.getMessage()));
}
}
@Override
public void send(IPacketClt message) {
String rawData = protocol.encodePacket(message);
socket.send(rawData);
}
/* (non-Javadoc)
* @see forge.net.client.INetClient#setPlayer(forge.game.player.LobbyPlayer)
*/
@Override
public final void createPlayer(String name) {
player = FServer.getLobby().findOrCreateRemotePlayer(name, this);
}
/* (non-Javadoc)
* @see forge.net.client.INetClient#replaceState(forge.net.client.state.IClientState, forge.net.client.state.IClientState)
*/
@Override
public synchronized void replaceState(IClientState old, IClientState newState) {
state.removeFirstOccurrence(old);
state.push(newState);
}
}

View File

@@ -0,0 +1,33 @@
package forge.net.client.state;
import forge.net.client.INetClient;
import forge.net.protocol.toclient.EchoPacketClt;
import forge.net.protocol.toclient.ErrorIncorrectPacketClt;
import forge.net.protocol.toserver.EchoPacketSrv;
import forge.net.protocol.toserver.IPacketSrv;
import forge.net.protocol.toserver.IncorrectPacketSrv;
public class ConnectedClientState implements IClientState {
private final INetClient client;
public ConnectedClientState(INetClient client) {
this.client = client;
}
@Override
public boolean processPacket(IPacketSrv packet ) {
if( packet instanceof EchoPacketSrv) {
client.send(new EchoPacketClt(((EchoPacketSrv)packet).getMessage()));
return true;
}
if( packet instanceof IncorrectPacketSrv) {
client.send(new ErrorIncorrectPacketClt(((IncorrectPacketSrv)packet).getMessage()));
return true;
}
return false;
}
}

View File

@@ -0,0 +1,11 @@
package forge.net.client.state;
import forge.net.protocol.toserver.IPacketSrv;
/**
* TODO: Write javadoc for this type.
*
*/
public interface IClientState {
boolean processPacket(IPacketSrv data);
}

View File

@@ -0,0 +1,26 @@
package forge.net.client.state;
import forge.net.client.INetClient;
import forge.net.protocol.toserver.ChatPacketSrv;
import forge.net.protocol.toserver.IPacketSrv;
public class InLobbyClientState implements IClientState {
private final INetClient client;
protected InLobbyClientState(INetClient client) {
this.client = client;
}
@Override
public boolean processPacket(IPacketSrv data) {
if( data instanceof ChatPacketSrv) {
//ChatPacketSrv cp = (ChatPacketSrv) data;
// if ( not muted )
//FServer.instance.getLobby().speak(ChatArea.Room, client.getPlayer(), cp.getMessage());
// else
// client.send("You are banned and cannot speak");
return true;
}
return false;
}
}

View File

@@ -0,0 +1,47 @@
package forge.net.client.state;
import forge.net.client.INetClient;
import forge.net.client.InvalidFieldInPacketException;
import forge.net.protocol.toclient.AuthResultPacketClt;
import forge.net.protocol.toserver.AuthorizePacketSrv;
import forge.net.protocol.toserver.IPacketSrv;
import org.apache.commons.lang3.StringUtils;
/**
* TODO: Write javadoc for this type.
*
*/
public class UnauthorizedClientState implements IClientState {
/**
* TODO: Write javadoc for Constructor.
* @param client
*/
private final INetClient client;
public UnauthorizedClientState(INetClient client) {
this.client = client;
}
@Override
public boolean processPacket(IPacketSrv packet) {
if( packet instanceof AuthorizePacketSrv ) {
AuthorizePacketSrv p = (AuthorizePacketSrv)packet;
if( StringUtils.isBlank(p.getUsername()))
throw new InvalidFieldInPacketException("username is blank");
// check credentials here!
client.createPlayer(p.getUsername());
client.send(new AuthResultPacketClt(client.getPlayer().getName(), true));
client.replaceState(this, new InLobbyClientState(client));
return true;
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,754 @@
package forge.player;
import com.google.common.base.Predicate;
import forge.FThreads;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.ability.effects.FlipCoinEffect;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.*;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.Ability;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.screens.match.input.InputPayMana;
import forge.screens.match.input.InputPayManaOfCostPayment;
import forge.screens.match.input.InputPayManaSimple;
import forge.screens.match.input.InputPayManaX;
import forge.screens.match.input.InputSelectCardsFromList;
import forge.toolbox.GuiChoose;
import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HumanPlay {
public HumanPlay() {
// TODO Auto-generated constructor stub
}
/**
* <p>
* playSpellAbility.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
public final static void playSpellAbility(Player p, SpellAbility sa) {
FThreads.assertExecutedByEdt(false);
if (sa == Ability.PLAY_LAND_SURROGATE) {
p.playLand(sa.getHostCard(), false);
return;
}
sa.setActivatingPlayer(p);
final Card source = sa.getHostCard();
source.setSplitStateToPlayAbility(sa);
sa = chooseOptionalAdditionalCosts(p, sa);
if (sa == null) {
return;
}
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
if (sa.hasParam("Bestow")) {
source.animateBestow();
}
// Need to check PayCosts, and Ability + All SubAbilities for Target
boolean newAbility = sa.getPayCosts() != null;
SpellAbility ability = sa;
while ((ability != null) && !newAbility) {
final TargetRestrictions tgt = ability.getTargetRestrictions();
newAbility |= tgt != null;
ability = ability.getSubAbility();
}
// System.out.println("Playing:" + sa.getDescription() + " of " + sa.getHostCard() + " new = " + newAbility);
if (newAbility) {
Cost abCost = sa.getPayCosts() == null ? new Cost("0", sa.isAbility()) : sa.getPayCosts();
CostPayment payment = new CostPayment(abCost, sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment);
req.playAbility(true, false, false);
}
else if (payManaCostIfNeeded(p, sa)) {
if (sa.isSpell() && !source.isCopiedSpell()) {
sa.setHostCard(p.getGame().getAction().moveToStack(source));
}
p.getGame().getStack().add(sa);
}
}
/**
* choose optional additional costs. For HUMAN only
* @param activator
*
* @param original
* the original sa
* @return an ArrayList<SpellAbility>.
*/
static SpellAbility chooseOptionalAdditionalCosts(Player p, final SpellAbility original) {
if (!original.isSpell()) {
return original;
}
final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
return p.getController().getAbilityToPlay(abilities);
}
private static boolean payManaCostIfNeeded(final Player p, final SpellAbility sa) {
final ManaCostBeingPaid manaCost;
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
}
else {
manaCost = new ManaCostBeingPaid(sa.getPayCosts().getTotalMana());
ManaCostAdjustment.adjust(manaCost, sa, false);
}
boolean isPaid = manaCost.isPaid();
if (!isPaid) {
InputPayMana inputPay = new InputPayManaSimple(p.getGame(), sa, manaCost);
inputPay.showAndWait();
isPaid = inputPay.isPaid();
}
return isPaid;
}
/**
* <p>
* playSpellAbilityForFree.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*/
public static final void playSaWithoutPayingManaCost(final Game game, final SpellAbility sa, boolean mayChooseNewTargets) {
FThreads.assertExecutedByEdt(false);
final Card source = sa.getHostCard();
source.setSplitStateToPlayAbility(sa);
if (sa.getPayCosts() != null) {
if (sa.getApi() == ApiType.Charm && !sa.isWrapper() && !sa.isCopied()) {
CharmEffect.makeChoices(sa);
}
final CostPayment payment = new CostPayment(sa.getPayCosts(), sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment);
req.playAbility(mayChooseNewTargets, true, false);
}
else {
if (sa.isSpell()) {
final Card c = sa.getHostCard();
if (!c.isCopiedSpell()) {
sa.setHostCard(game.getAction().moveToStack(c));
}
}
game.getStack().add(sa);
}
}
/**
* <p>
* playSpellAbility_NoStack.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param skipTargeting
* a boolean.
*/
public final static void playSpellAbilityNoStack(final Player player, final SpellAbility sa) {
playSpellAbilityNoStack(player, sa, false);
}
public final static void playSpellAbilityNoStack(final Player player, final SpellAbility sa, boolean useOldTargets) {
sa.setActivatingPlayer(player);
if (sa.getPayCosts() != null) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, new CostPayment(sa.getPayCosts(), sa));
req.playAbility(!useOldTargets, false, true);
}
else if (payManaCostIfNeeded(player, sa)) {
AbilityUtils.resolve(sa);
}
}
// ------------------------------------------------------------------------
private static int getAmountFromPart(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : AbilityUtils.calculateAmount(source, amountString, sourceAbility);
}
/**
* TODO: Write javadoc for this method.
* @param part
* @param source
* @param sourceAbility
* @return
*/
private static int getAmountFromPartX(CostPart part, Card source, SpellAbility sourceAbility) {
String amountString = part.getAmount();
return StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : CardFactoryUtil.xCount(source, source.getSVar(amountString));
}
/**
* <p>
* payCostDuringAbilityResolve.
* </p>
*
* @param ability
* a {@link forge.game.spellability.SpellAbility} object.
* @param cost
* a {@link forge.game.cost.Cost} object.
* @param paid
* a {@link forge.UiCommand} object.
* @param unpaid
* a {@link forge.UiCommand} object.
* @param sourceAbility TODO
*/
public static boolean payCostDuringAbilityResolve(final Player p, final Card source, final Cost cost, SpellAbility sourceAbility, String prompt) {
// Only human player pays this way
Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers
if (!source.getRemembered().isEmpty()) {
if (source.getRemembered().get(0) instanceof Card) {
current = (Card) source.getRemembered().get(0);
}
}
if (!source.getImprinted().isEmpty()) {
current = source.getImprinted().get(0);
}
final List<CostPart> parts = cost.getCostParts();
ArrayList<CostPart> remainingParts = new ArrayList<CostPart>(parts);
CostPart costPart = null;
if (!parts.isEmpty()) {
costPart = parts.get(0);
}
String orString = prompt == null ? sourceAbility.getStackDescription().trim() : "";
if (!orString.isEmpty()) {
orString = " (or: " + orString + ")";
}
if (parts.isEmpty() || (costPart.getAmount().equals("0") && parts.size() < 2)) {
return p.getController().confirmPayment(costPart, "Do you want to pay {0}?" + orString);
}
// 0 mana costs were slipping through because CostPart.getAmount returns 1
else if (costPart instanceof CostPartMana && parts.size() < 2) {
if (((CostPartMana) costPart).getManaToPay().isZero()) {
return p.getController().confirmPayment(costPart, "Do you want to pay {0}?" + orString);
}
}
HumanCostDecision hcd = new HumanCostDecision(p, sourceAbility, source);
//the following costs do not need inputs
for (CostPart part : parts) {
boolean mayRemovePart = true;
if (part instanceof CostPayLife) {
final int amount = getAmountFromPart(part, source, sourceAbility);
if (!p.canPayLife(amount)) {
return false;
}
if (!p.getController().confirmPayment(part, "Do you want to pay " + amount + " life?" + orString)) {
return false;
}
p.payLife(amount, null);
}
else if (part instanceof CostDraw) {
final int amount = getAmountFromPart(part, source, sourceAbility);
List<Player> res = new ArrayList<Player>();
String type = part.getType();
for (Player player : p.getGame().getPlayers()) {
if (player.isValid(type, p, source) && player.canDraw()) {
res.add(player);
}
}
if (res.isEmpty()) {
return false;
}
StringBuilder sb = new StringBuilder("Do you want to ");
sb.append(res.contains(p) ? "" : "let that player ");
sb.append("draw " + Lang.nounWithAmount(amount, " card") + "?" + orString);
if (!p.getController().confirmPayment(part, sb.toString())) {
return false;
}
for (Player player : res) {
player.drawCards(amount);
}
}
else if (part instanceof CostGainLife) {
PaymentDecision pd = part.accept(hcd);
if (pd == null)
return false;
else
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostAddMana) {
if (!p.getController().confirmPayment(part, "Do you want to add " + ((CostAddMana) part).toString() + " to your mana pool?" + orString)) {
return false;
}
PaymentDecision pd = part.accept(hcd);
if (pd == null)
return false;
else
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostMill) {
final int amount = getAmountFromPart(part, source, sourceAbility);
final List<Card> list = p.getCardsIn(ZoneType.Library);
if (list.size() < amount) { return false; }
if (!p.getController().confirmPayment(part, "Do you want to mill " + amount + " card" + (amount == 1 ? "" : "s") + "?" + orString)) {
return false;
}
List<Card> listmill = p.getCardsIn(ZoneType.Library, amount);
((CostMill) part).executePayment(sourceAbility, listmill);
}
else if (part instanceof CostFlipCoin) {
final int amount = getAmountFromPart(part, source, sourceAbility);
if (!p.getController().confirmPayment(part, "Do you want to flip " + amount + " coin" + (amount == 1 ? "" : "s") + "?" + orString)) {
return false;
}
final int n = FlipCoinEffect.getFilpMultiplier(p);
for (int i = 0; i < amount; i++) {
FlipCoinEffect.flipCoinCall(p, sourceAbility, n);
}
}
else if (part instanceof CostDamage) {
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!p.canPayLife(amount)) {
return false;
}
if (!p.getController().confirmPayment(part, "Do you want " + source + " to deal " + amount + " damage to you?")) {
return false;
}
p.addDamage(amount, source);
}
else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter();
int amount = getAmountFromPartX(part, source, sourceAbility);
if (part.payCostFromSource()) {
if (!source.canReceiveCounters(counterType)) {
String message = String.format("Won't be able to pay upkeep for %s but it can't have %s counters put on it.", source, counterType.getName());
p.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, message);
return false;
}
if (!p.getController().confirmPayment(part, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + source + "?")) {
return false;
}
source.addCounter(counterType, amount, false);
}
else {
List<Card> list = p.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, part.getType().split(";"), p, source);
if (list.isEmpty()) { return false; }
if (!p.getController().confirmPayment(part, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + part.getTypeDescription() + "?")) {
return false;
}
while (amount > 0) {
InputSelectCardsFromList inp = new InputSelectCardsFromList(1, 1, list);
inp.setMessage("Select a card to add a counter to");
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled()) {
continue;
}
Card selected = inp.getFirstSelected();
selected.addCounter(counterType, 1, false);
amount--;
}
}
}
else if (part instanceof CostRemoveCounter) {
CounterType counterType = ((CostRemoveCounter) part).counter;
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!part.canPay(sourceAbility)) {
return false;
}
if (!p.getController().confirmPayment(part, "Do you want to remove " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " from " + source + "?")) {
return false;
}
source.subtractCounter(counterType, amount);
}
else if (part instanceof CostRemoveAnyCounter) {
int amount = getAmountFromPartX(part, source, sourceAbility);
List<Card> list = new ArrayList<Card>(p.getCardsIn(ZoneType.Battlefield));
int allCounters = 0;
for (Card c : list) {
final Map<CounterType, Integer> tgtCounters = c.getCounters();
for (Integer value : tgtCounters.values()) {
allCounters += value;
}
}
if (allCounters < amount) { return false; }
if (!p.getController().confirmPayment(part, "Do you want to remove counters from " + part.getDescriptiveType() + " ?")) {
return false;
}
list = CardLists.getValidCards(list, ((CostRemoveAnyCounter) part).getType().split(";"), p, source);
while (amount > 0) {
final CounterType counterType;
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card card) {
return card.hasCounters();
}
});
if (list.isEmpty()) { return false; }
InputSelectCardsFromList inp = new InputSelectCardsFromList(1, 1, list);
inp.setMessage("Select a card to remove a counter");
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled()) {
continue;
}
Card selected = inp.getFirstSelected();
final Map<CounterType, Integer> tgtCounters = selected.getCounters();
final ArrayList<CounterType> typeChoices = new ArrayList<CounterType>();
for (CounterType key : tgtCounters.keySet()) {
if (tgtCounters.get(key) > 0) {
typeChoices.add(key);
}
}
if (typeChoices.size() > 1) {
String cprompt = "Select type counters to remove";
counterType = GuiChoose.one(cprompt, typeChoices);
}
else {
counterType = typeChoices.get(0);
}
selected.subtractCounter(counterType, 1);
amount--;
}
}
else if (part instanceof CostExile) {
if ("All".equals(part.getType())) {
if (!p.getController().confirmPayment(part, "Do you want to exile all cards in your graveyard?")) {
return false;
}
List<Card> cards = new ArrayList<Card>(p.getCardsIn(ZoneType.Graveyard));
for (final Card card : cards) {
p.getGame().getAction().exile(card);
}
}
else {
CostExile costExile = (CostExile) part;
ZoneType from = costExile.getFrom();
List<Card> list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source);
final int nNeeded = getAmountFromPart(costPart, source, sourceAbility);
if (list.size() < nNeeded) {
return false;
}
if (from == ZoneType.Library) {
if (!p.getController().confirmPayment(part, "Do you want to exile " + nNeeded +
" card" + (nNeeded == 1 ? "" : "s") + " from your library?")) {
return false;
}
list = list.subList(0, nNeeded);
for (Card c : list) {
p.getGame().getAction().exile(c);
}
return true;
}
// replace this with input
for (int i = 0; i < nNeeded; i++) {
final Card c = GuiChoose.oneOrNone("Exile from " + from, list);
if (c == null) {
return false;
}
list.remove(c);
p.getGame().getAction().exile(c);
}
}
}
else if (part instanceof CostPutCardToLib) {
int amount = Integer.parseInt(((CostPutCardToLib) part).getAmount());
final ZoneType from = ((CostPutCardToLib) part).getFrom();
final boolean sameZone = ((CostPutCardToLib) part).isSameZone();
List<Card> list;
if (sameZone) {
list = p.getGame().getCardsIn(from);
}
else {
list = p.getCardsIn(from);
}
list = CardLists.getValidCards(list, part.getType().split(";"), p, source);
if (sameZone) { // Jotun Grunt
List<Player> players = p.getGame().getPlayers();
List<Player> payableZone = new ArrayList<Player>();
for (Player player : players) {
List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(player));
if (enoughType.size() < amount) {
list.removeAll(enoughType);
} else {
payableZone.add(player);
}
}
Player chosen = GuiChoose.oneOrNone(String.format("Put cards from whose %s?", from), payableZone);
if (chosen == null) {
return false;
}
List<Card> typeList = CardLists.filter(list, CardPredicates.isOwner(chosen));
for (int i = 0; i < amount; i++) {
if (typeList.isEmpty()) {
return false;
}
final Card c = GuiChoose.oneOrNone("Put cards to Library", typeList);
if (c != null) {
typeList.remove(c);
p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos()));
}
else {
return false;
}
}
}
else if (from == ZoneType.Hand) { // Tainted Specter
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "put into library." + orString);
if (!hasPaid) {
return false;
}
}
return true;
}
else if (part instanceof CostSacrifice) {
int amount = Integer.parseInt(((CostSacrifice)part).getAmount());
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "sacrifice." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostGainControl) {
int amount = Integer.parseInt(((CostGainControl)part).getAmount());
List<Card> list = CardLists.getValidCards(p.getGame().getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "gain control." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostReturn) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "return to hand." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostDiscard) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "discard." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostReveal) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "reveal." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostTapType) {
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source);
list = CardLists.filter(list, Presets.UNTAPPED);
int amount = getAmountFromPartX(part, source, sourceAbility);
boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "tap." + orString);
if (!hasPaid) { return false; }
}
else if (part instanceof CostPartMana) {
if (!((CostPartMana) part).getManaToPay().isZero()) { // non-zero costs require input
mayRemovePart = false;
}
}
else {
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost was met: " + part.getClass());
}
if (mayRemovePart) {
remainingParts.remove(part);
}
}
if (remainingParts.isEmpty()) {
return true;
}
if (remainingParts.size() > 1) {
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - Too many payment types - " + source);
}
costPart = remainingParts.get(0);
// check this is a mana cost
if (!(costPart instanceof CostPartMana)) {
throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - The remaining payment type is not Mana.");
}
if (prompt == null) {
String promptCurrent = current == null ? "" : "Current Card: " + current;
prompt = source + "\n" + promptCurrent;
}
if( sourceAbility != null )
sourceAbility.clearManaPaid();
boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false);
if (!paid) {
p.getManaPool().refundManaPaid(sourceAbility);
}
return paid;
}
private static boolean payCostPart(SpellAbility sourceAbility, CostPartWithList cpl, int amount, List<Card> list, String actionName) {
if (list.size() < amount) { return false; } // unable to pay (not enough cards)
InputSelectCardsFromList inp = new InputSelectCardsFromList(amount, amount, list);
inp.setMessage("Select %d " + cpl.getDescriptiveType() + " card(s) to " + actionName);
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled() || inp.getSelected().size() != amount) {
return false;
}
for (Card c : inp.getSelected()) {
cpl.executePayment(sourceAbility, c);
}
if (sourceAbility != null) {
cpl.reportPaidCardsTo(sourceAbility);
}
return true;
}
private static boolean handleOfferingAndConvoke(final SpellAbility ability, boolean manaInputCancelled, boolean isPaid) {
boolean done = !manaInputCancelled && isPaid;
if (ability.isOffering() && ability.getSacrificedAsOffering() != null) {
final Card offering = ability.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (done) {
ability.getHostCard().getGame().getAction().sacrifice(offering, ability);
}
ability.resetSacrificedAsOffering();
}
if (ability.getTappedForConvoke() != null) {
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
if (done) {
c.tap();
}
}
ability.clearTappedForConvoke();
}
return done;
}
public static boolean payManaCost(final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, boolean isActivatedSa) {
final Card source = ability.getHostCard();
ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction());
boolean xWasBilled = false;
String xInCard = source.getSVar("X");
if (mc.getAmountOfX() > 0 && !"Count$xPaid".equals(xInCard)) { // announce X will overwrite whatever was in card script
// this currently only works for things about Targeted object
int xCost = AbilityUtils.calculateAmount(source, "X", ability) * mc.getAmountOfX();
byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1");
toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost);
xWasBilled = true;
}
int timesMultikicked = ability.getHostCard().getKickerMagnitude();
if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) {
ManaCost mkCost = ability.getMultiKickerManaCost();
for(int i = 0; i < timesMultikicked; i++)
toPay.addManaCost(mkCost);
}
if( isActivatedSa )
ManaCostAdjustment.adjust(toPay, ability, false);
InputPayMana inpPayment;
if (ability.isOffering() && ability.getSacrificedAsOffering() == null) {
System.out.println("Sacrifice input for Offering cancelled");
return false;
}
if (!toPay.isPaid()) {
inpPayment = new InputPayManaOfCostPayment(toPay, ability, activator);
inpPayment.setMessagePrefix(prompt);
inpPayment.showAndWait();
if (!inpPayment.isPaid()) {
return handleOfferingAndConvoke(ability, true, false);
}
source.setColorsPaid(toPay.getColorsPaid());
source.setSunburstValue(toPay.getSunburst());
}
if (mc.getAmountOfX() > 0) {
if (!ability.isAnnouncing("X") && !xWasBilled) {
source.setXManaCostPaid(0);
inpPayment = new InputPayManaX(ability, mc.getAmountOfX(), mc.canXbe0());
inpPayment.showAndWait();
if (!inpPayment.isPaid()) {
return false;
}
} else {
int x = AbilityUtils.calculateAmount(source, "X", ability);
source.setXManaCostPaid(x);
}
}
// Handle convoke and offerings
if (ability.isOffering() && ability.getSacrificedAsOffering() != null) {
System.out.println("Finishing up Offering");
final Card offering = ability.getSacrificedAsOffering();
offering.setUsedToPay(false);
activator.getGame().getAction().sacrifice(offering, ability);
ability.resetSacrificedAsOffering();
}
if (ability.getTappedForConvoke() != null) {
for (final Card c : ability.getTappedForConvoke()) {
c.setTapped(false);
c.tap();
}
ability.clearTappedForConvoke();
}
return handleOfferingAndConvoke(ability, false, true);
}
}

View File

@@ -0,0 +1,262 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.player;
import com.google.common.collect.Iterables;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayment;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.Zone;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <p>
* SpellAbility_Requirements class.
* </p>
*
* @author Forge
* @version $Id: HumanPlaySpellAbility.java 24317 2014-01-17 08:32:39Z Max mtg $
*/
public class HumanPlaySpellAbility {
private final SpellAbility ability;
private final CostPayment payment;
public HumanPlaySpellAbility(final SpellAbility sa, final CostPayment cp) {
this.ability = sa;
this.payment = cp;
}
public final void playAbility(boolean mayChooseTargets, boolean isFree, boolean skipStack) {
final Player human = ability.getActivatingPlayer();
final Game game = ability.getActivatingPlayer().getGame();
// used to rollback
Zone fromZone = null;
int zonePosition = 0;
final ManaPool manapool = human.getManaPool();
final Card c = this.ability.getHostCard();
boolean manaConversion = (ability.isSpell() && c.hasKeyword("May spend mana as though it were mana of any color to cast CARDNAME"));
Map<String, String> params = AbilityFactory.getMapParams(c.getSVar("ManaConversionMatrix"));
if (this.ability instanceof Spell && !c.isCopiedSpell()) {
fromZone = game.getZoneOf(c);
if (fromZone != null) {
zonePosition = fromZone.getCards().indexOf(c);
}
this.ability.setHostCard(game.getAction().moveToStack(c));
}
// freeze Stack. No abilities should go onto the stack while I'm filling requirements.
game.getStack().freezeStack();
if (manaConversion) {
AbilityUtils.applyManaColorConversion(human, params);
}
// This line makes use of short-circuit evaluation of boolean values, that is each subsequent argument
// is only executed or evaluated if the first argument does not suffice to determine the value of the expression
boolean prerequisitesMet = this.announceValuesLikeX()
&& this.announceType()
&& (!mayChooseTargets || setupTargets()) // if you can choose targets, then do choose them.
&& (isFree || this.payment.payCost(new HumanCostDecision(human, ability, ability.getHostCard())));
if (!prerequisitesMet) {
if (!ability.isTrigger()) {
rollbackAbility(fromZone, zonePosition);
if (ability.isMadness()) {
// if a player failed to play madness cost, move the card to graveyard
game.getAction().moveToGraveyard(c);
ability.setMadness(false);
} else if (ability.getHostCard().isBestowed()) {
ability.getHostCard().unanimateBestow();
}
}
if (manaConversion) {
manapool.restoreColorReplacements();
}
return;
}
if (isFree || this.payment.isFullyPaid()) {
if (skipStack) {
AbilityUtils.resolve(this.ability);
}
else {
this.enusureAbilityHasDescription(this.ability);
game.getStack().addAndUnfreeze(this.ability);
}
// no worries here. The same thread must resolve, and by this moment ability will have been resolved already
// Triggers haven't resolved yet ??
if (mayChooseTargets) {
clearTargets(ability);
}
if (manaConversion) {
manapool.restoreColorReplacements();
}
}
}
private final boolean setupTargets() {
// Skip to paying if parent ability doesn't target and has no subAbilities.
// (or trigger case where its already targeted)
SpellAbility currentAbility = ability;
final Card source = ability.getHostCard();
do {
TargetRestrictions tgt = currentAbility.getTargetRestrictions();
if (tgt != null && tgt.doesTarget()) {
clearTargets(currentAbility);
Player targetingPlayer;
if (currentAbility.hasParam("TargetingPlayer")) {
List<Player> candidates = AbilityUtils.getDefinedPlayers(source, currentAbility.getParam("TargetingPlayer"), currentAbility);
// activator chooses targeting player
targetingPlayer = ability.getActivatingPlayer().getController().chooseSingleEntityForEffect(
candidates, currentAbility, "Choose the targeting player");
} else {
targetingPlayer = ability.getActivatingPlayer();
}
if (!targetingPlayer.getController().chooseTargetsFor(currentAbility))
return false;
}
final SpellAbility subAbility = currentAbility.getSubAbility();
if (subAbility != null) {
// This is necessary for "TargetsWithDefinedController$ ParentTarget"
((AbilitySub) subAbility).setParent(currentAbility);
}
currentAbility = subAbility;
} while (currentAbility != null);
return true;
}
public final void clearTargets(SpellAbility ability) {
TargetRestrictions tg = ability.getTargetRestrictions();
if (tg != null) {
ability.resetTargets();
tg.calculateStillToDivide(ability.getParam("DividedAsYouChoose"), ability.getHostCard(), ability);
}
}
private void rollbackAbility(Zone fromZone, int zonePosition) {
// cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy
// add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
}
clearTargets(ability);
this.ability.resetOnceResolved();
this.payment.refundPayment();
game.getStack().clearFrozen();
}
private boolean announceValuesLikeX() {
// Announcing Requirements like Choosing X or Multikicker
// SA Params as comma delimited list
String announce = ability.getParam("Announce");
if (announce != null) {
for(String aVar : announce.split(",")) {
String varName = aVar.trim();
boolean isX = "X".equalsIgnoreCase(varName);
CostPartMana manaCost = ability.getPayCosts().getCostMana();
boolean allowZero = !ability.hasParam("XCantBe0") && (!isX || manaCost == null || manaCost.canXbe0());
Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, varName, allowZero);
if (value == null) {
return false;
}
ability.setSVar(varName, value.toString());
if ("Multikicker".equals(varName)) {
ability.getHostCard().setKickerMagnitude(value);
}
else {
ability.getHostCard().setSVar(varName, value.toString());
}
}
}
return true;
}
private boolean announceType() {
// Announcing Requirements like choosing creature type or number
String announce = ability.getParam("AnnounceType");
PlayerController pc = ability.getActivatingPlayer().getController();
if (announce != null) {
for(String aVar : announce.split(",")) {
String varName = aVar.trim();
if ("CreatureType".equals(varName)) {
String choice = pc.chooseSomeType("Creature", ability, CardType.getCreatureTypes(), new ArrayList<String>());
ability.getHostCard().setChosenType(choice);
}
if ("ChooseNumber".equals(varName)) {
int min = Integer.parseInt(ability.getParam("Min"));
int max = Integer.parseInt(ability.getParam("Max"));
int i = ability.getActivatingPlayer().getController().chooseNumber(ability,
"Choose a number", min, max);
ability.getHostCard().setChosenNumber(i);
}
}
}
return true;
}
private void enusureAbilityHasDescription(SpellAbility ability) {
if (!StringUtils.isBlank(ability.getStackDescription())) {
return;
}
// For older abilities that don't setStackDescription set it here
final StringBuilder sb = new StringBuilder();
sb.append(ability.getHostCard().getName());
if (ability.getTargetRestrictions() != null) {
final Iterable<GameObject> targets = ability.getTargets().getTargets();
if (!Iterables.isEmpty(targets)) {
sb.append(" - Targeting ");
for (final GameObject o : targets) {
sb.append(o.toString()).append(" ");
}
}
}
ability.setStackDescription(sb.toString());
}
}

View File

@@ -1,24 +1,54 @@
package forge.screens.match;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import forge.FThreads;
import forge.Forge;
import forge.game.Game;
import forge.game.Match;
import forge.game.card.Card;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.player.LobbyPlayer;
import forge.game.player.Player;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.model.FModel;
import forge.screens.match.events.IUiEventVisitor;
import forge.screens.match.events.UiEvent;
import forge.screens.match.events.UiEventAttackerDeclared;
import forge.screens.match.events.UiEventBlockerAssigned;
import forge.screens.match.input.InputQueue;
import forge.screens.match.views.VPhaseIndicator.PhaseLabel;
import forge.screens.match.views.VPlayerPanel;
import forge.utils.ForgePreferences.FPref;
public class FControl {
private FControl() { } //don't allow creating instance
private static Game game;
private static MatchScreen view;
private static InputQueue inputQueue;
private static List<Player> sortedPlayers;
private static final EventBus uiEvents;
private static MatchUiEventVisitor visitor = new MatchUiEventVisitor();
public static void startGame(final Match match0, final MatchScreen view0) {
game = match0.createGame();
static {
uiEvents = new EventBus("ui events");
//uiEvents.register(Singletons.getControl().getSoundSystem());
uiEvents.register(visitor);
}
public static void startGame(final Match match, final MatchScreen view0) {
game = match.createGame();
view = view0;
/*if (game.getRules().getGameType() == GameType.Quest) {
@@ -30,7 +60,7 @@ public class FControl {
game.subscribeToEvents(qc); // this one listens to player's mulligans ATM
}*/
//inputQueue = new InputQueue();
inputQueue = new InputQueue();
//game.subscribeToEvents(Singletons.getControl().getSoundSystem());
@@ -48,10 +78,36 @@ public class FControl {
});*/
}
public static Game getGame() {
return game;
}
public static MatchScreen getView() {
return view;
}
public static InputQueue getInputQueue() {
return inputQueue;
}
public static boolean stopAtPhase(final Player turn, final PhaseType phase) {
PhaseLabel label = getPlayerPanel(turn).getPhaseIndicator().getLabel(phase);
return label == null || label.getStopAtPhase();
}
public static void setCard(final Card c) {
FThreads.assertExecutedByEdt(true);
setCard(c, false);
}
public static void setCard(final Card c, final boolean showFlipped) {
//TODO
}
private static int getPlayerIndex(Player player) {
return sortedPlayers.indexOf(player);
}
public static void endCurrentGame() {
if (game == null) { return; }
@@ -132,7 +188,152 @@ public class FControl {
return sortedPlayers;
}
public static void resetAllPhaseButtons() {
for (final VPlayerPanel panel : view.getPlayerPanels()) {
panel.getPhaseIndicator().resetPhaseButtons();
}
}
public static void showMessage(final String s0) {
view.getPrompt().setMessage(s0);
}
public static VPlayerPanel getPlayerPanel(Player p) {
int idx = getPlayerIndex(p);
return idx < 0 ? null : view.getPlayerPanels().get(idx);
}
public static boolean mayShowCard(Card c) {
return true;// game == null || !gameHasHumanPlayer || c.canBeShownTo(getCurrentPlayer());
}
public static void showCombat(Combat combat) {
/*if (combat != null && combat.getAttackers().size() > 0 && combat.getAttackingPlayer().getGame().getStack().isEmpty()) {
if (selectedDocBeforeCombat == null) {
IVDoc<? extends ICDoc> combatDoc = EDocID.REPORT_COMBAT.getDoc();
if (combatDoc.getParentCell() != null) {
selectedDocBeforeCombat = combatDoc.getParentCell().getSelected();
if (selectedDocBeforeCombat != combatDoc) {
SDisplayUtil.showTab(combatDoc);
}
else {
selectedDocBeforeCombat = null; //don't need to cache combat doc this way
}
}
}
}
else if (selectedDocBeforeCombat != null) { //re-select doc that was selected before once combat finished
SDisplayUtil.showTab(selectedDocBeforeCombat);
selectedDocBeforeCombat = null;
}
CCombat.SINGLETON_INSTANCE.setModel(combat);
CCombat.SINGLETON_INSTANCE.update();*/
} // showBlockers()
private static Set<Player> highlightedPlayers = new HashSet<Player>();
public static void setHighlighted(Player ge, boolean b) {
if (b) highlightedPlayers.add(ge);
else highlightedPlayers.remove(ge);
}
public static boolean isHighlighted(Player player) {
return highlightedPlayers.contains(player);
}
private static Set<Card> highlightedCards = new HashSet<Card>();
// used to highlight cards in UI
public static void setUsedToPay(Card card, boolean value) {
FThreads.assertExecutedByEdt(true);
boolean hasChanged = value ? highlightedCards.add(card) : highlightedCards.remove(card);
if (hasChanged) { // since we are in UI thread, may redraw the card right now
updateSingleCard(card);
}
}
public static boolean isUsedToPay(Card card) {
return highlightedCards.contains(card);
}
public static void updateZones(List<Pair<Player, ZoneType>> zonesToUpdate) {
//System.out.println("updateZones " + zonesToUpdate);
/*for (Pair<Player, ZoneType> kv : zonesToUpdate) {
Player owner = kv.getKey();
ZoneType zt = kv.getValue();
if (zt == ZoneType.Command)
getCommandFor(owner).getTabletop().setupPlayZone();
else if (zt == ZoneType.Hand) {
VHand vHand = getHandFor(owner);
if (null != vHand)
vHand.getLayoutControl().updateHand();
getFieldViewFor(owner).getDetailsPanel().updateZones();
}
else if (zt == ZoneType.Battlefield) {
getFieldViewFor(owner).getTabletop().setupPlayZone();
} else if (zt == ZoneType.Ante) {
CAntes.SINGLETON_INSTANCE.update();
}
else {
getFieldViewFor(owner).getDetailsPanel().updateZones();
}
}*/
}
// Player's mana pool changes
public static void updateManaPool(List<Player> manaPoolUpdate) {
/*for (Player p : manaPoolUpdate) {
getFieldViewFor(p).getDetailsPanel().updateManaPool();
}*/
}
// Player's lives and poison counters
public static void updateLives(List<Player> livesUpdate) {
/*for (Player p : livesUpdate) {
getFieldViewFor(p).updateDetails();
}*/
}
public static void updateCards(Set<Card> cardsToUpdate) {
for (Card c : cardsToUpdate) {
updateSingleCard(c);
}
}
public static void updateSingleCard(Card c) {
Zone zone = c.getZone();
if (zone != null && zone.getZoneType() == ZoneType.Battlefield) {
/*PlayArea pa = getFieldViewFor(zone.getPlayer()).getTabletop();
pa.updateSingleCard(c);*/
}
}
private final static boolean LOG_UIEVENTS = false;
// UI-related events should arrive here
public static void fireEvent(UiEvent uiEvent) {
if (LOG_UIEVENTS) {
//System.out.println("UI: " + uiEvent.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true));
}
uiEvents.post(uiEvent);
}
private static class MatchUiEventVisitor implements IUiEventVisitor<Void> {
@Override
public Void visit(UiEventBlockerAssigned event) {
updateSingleCard(event.blocker);
return null;
}
@Override
public Void visit(UiEventAttackerDeclared event) {
updateSingleCard(event.attacker);
return null;
}
@Subscribe
public void receiveEvent(UiEvent evt) {
evt.visit(this);
}
}
}

View File

@@ -0,0 +1,385 @@
package forge.screens.match;
import com.google.common.eventbus.Subscribe;
import forge.FThreads;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.event.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.net.FServer;
import forge.screens.match.views.VPhaseIndicator.PhaseLabel;
import forge.toolbox.GuiChoose;
import forge.util.Lang;
import forge.util.maps.MapOfLists;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
public FControlGameEventHandler() {
}
@Subscribe
public void receiveGameEvent(final GameEvent ev) {
ev.visit(this);
}
private final AtomicBoolean phaseUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(final GameEventTurnPhase ev) {
if (phaseUpdPlanned.getAndSet(true)) return null;
FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() {
PhaseHandler pH = FControl.getGame().getPhaseHandler();
Player p = pH.getPlayerTurn();
PhaseType ph = pH.getPhase();
phaseUpdPlanned.set(false);
PhaseLabel lbl = FControl.getPlayerPanel(p).getPhaseIndicator().getLabel(ph);
FControl.resetAllPhaseButtons();
if (lbl != null) { lbl.setActive(true); }
} });
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventPlayerPriority)
*/
private final AtomicBoolean combatUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(GameEventPlayerPriority event) {
if (combatUpdPlanned.getAndSet(true)) { return null; }
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
combatUpdPlanned.set(false);
FControl.showCombat(FControl.getGame().getCombat());
}
});
return null;
}
private final AtomicBoolean turnUpdPlanned = new AtomicBoolean(false);
@Override
public Void visit(final GameEventTurnBegan event) {
if (turnUpdPlanned.getAndSet(true)) { return null; }
final Game game = FControl.getGame(); // to make sure control gets a correct game instance
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
/*VField nextField = FControl.getFieldViewFor(event.turnOwner);
SDisplayUtil.showTab(nextField);*/
turnUpdPlanned.set(false);
FControl.getView().getPrompt().updateText(game);
}
});
return null;
}
@Override
public Void visit(GameEventAnteCardsSelected ev) {
// Require EDT here?
List<Object> options = new ArrayList<Object>();
for (final Entry<Player, Card> kv : ((GameEventAnteCardsSelected) ev).cards.entries()) {
options.add(" -- From " + Lang.getPossesive(kv.getKey().getName()) + " deck --");
options.add(kv.getValue());
}
GuiChoose.one("These cards were chosen to ante", options);
return null;
}
@Override
public Void visit(GameEventPlayerControl ev) {
if (FControl.getGame().isGameOver()) {
return null;
}
FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() {
FControl.initHandViews(FServer.getLobby().getGuiPlayer());
/*SLayoutIO.loadLayout(null);
VMatchUI.SINGLETON_INSTANCE.populate();
for (VHand h : VMatchUI.SINGLETON_INSTANCE.getHands()) {
h.getLayoutControl().updateHand();
}*/
} });
return null;
}
private final Runnable unlockGameThreadOnGameOver = new Runnable() { @Override public void run() {
FControl.getInputQueue().onGameOver(true); // this will unlock any game threads waiting for inputs to complete
} };
@Override
public Void visit(GameEventGameOutcome ev) {
FThreads.invokeInEdtNowOrLater(unlockGameThreadOnGameOver);
return null;
}
@Override
public Void visit(GameEventGameFinished ev) {
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
/*new ViewWinLose(FControl.getGame());
SOverlayUtils.showOverlay();*/
}
});
return null;
}
private final AtomicBoolean stackUpdPlanned = new AtomicBoolean(false);
private final Runnable updStack = new Runnable() {
@Override
public void run() {
stackUpdPlanned.set(false);
FControl.getView().getStack().update();
}
};
@Override
public Void visit(GameEventSpellAbilityCast event) {
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(updStack);
}
return null;
}
@Override
public Void visit(GameEventSpellResolved event) {
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(updStack);
}
return null;
}
@Override
public Void visit(GameEventSpellRemovedFromStack event) {
if (!stackUpdPlanned.getAndSet(true)) {
FThreads.invokeInEdtNowOrLater(updStack);
}
return null;
}
private final List<Pair<Player, ZoneType>> zonesToUpdate = new Vector<Pair<Player, ZoneType>>();
private final Runnable updZones = new Runnable() {
@Override public void run() {
synchronized (zonesToUpdate) {
FControl.updateZones(zonesToUpdate);
zonesToUpdate.clear();
}
}
};
@Override
public Void visit(GameEventZone event) {
if (event.player != null) {
// anything except stack will get here
updateZone(Pair.of(event.player, event.zoneType));
}
return null;
}
@Override
public Void visit(GameEventCardAttachment event) {
// TODO Auto-generated method stub
Game game = event.equipment.getGame();
PlayerZone zEq = (PlayerZone)game.getZoneOf(event.equipment);
if (event.oldEntiy instanceof Card) {
updateZone(game.getZoneOf((Card)event.oldEntiy));
}
if (event.newTarget instanceof Card) {
updateZone(game.getZoneOf((Card)event.newTarget));
}
return updateZone(zEq);
}
private Void updateZone(Zone z) {
return updateZone(Pair.of(z.getPlayer(), z.getZoneType()));
}
private Void updateZone(Pair<Player, ZoneType> kv) {
boolean needUpdate = false;
synchronized (zonesToUpdate) {
needUpdate = zonesToUpdate.isEmpty();
if (!zonesToUpdate.contains(kv)) {
zonesToUpdate.add(kv);
}
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(updZones);
}
return null;
}
private final Set<Card> cardsToUpdate = new HashSet<Card>();
private final Runnable updCards = new Runnable() {
@Override
public void run() {
synchronized (cardsToUpdate) {
FControl.updateCards(cardsToUpdate);
cardsToUpdate.clear();
}
}
};
@Override
public Void visit(GameEventCardTapped event) {
return updateSingleCard(event.card);
}
@Override
public Void visit(GameEventCardPhased event) {
return updateSingleCard(event.card);
}
@Override
public Void visit(GameEventCardDamaged event) {
return updateSingleCard(event.card);
}
@Override
public Void visit(GameEventCardCounters event) {
return updateSingleCard(event.card);
}
@Override
public Void visit(GameEventBlockersDeclared event) { // This is to draw icons on blockers declared by AI
for (MapOfLists<Card, Card> kv : event.blockers.values()) {
for (Collection<Card> blockers : kv.values()) {
updateManyCards(blockers);
}
}
return super.visit(event);
}
@Override
public Void visit(GameEventAttackersDeclared event) {
// Skip redraw for GUI player?
if (event.player.getLobbyPlayer() == FServer.getLobby().getGuiPlayer()) {
return null;
}
// Update all attackers.
// Although they might have been updated when they were apped, there could be someone with vigilance, not redrawn yet.
updateManyCards(event.attackersMap.values());
return super.visit(event);
}
@Override
public Void visit(GameEventCombatEnded event) {
// This should remove sword/shield icons from combatants by the time game moves to M2
updateManyCards(event.attackers);
updateManyCards(event.blockers);
return null;
}
private Void updateSingleCard(Card c) {
boolean needUpdate = false;
synchronized (cardsToUpdate) {
needUpdate = cardsToUpdate.isEmpty();
if (!cardsToUpdate.contains(c)) {
cardsToUpdate.add(c);
}
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(updCards);
}
return null;
}
private Void updateManyCards(Collection<Card> cc) {
boolean needUpdate = false;
synchronized (cardsToUpdate) {
needUpdate = cardsToUpdate.isEmpty();
cardsToUpdate.addAll(cc);
}
if (needUpdate) {
FThreads.invokeInEdtNowOrLater(updCards);
}
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventCardStatsChanged)
*/
@Override
public Void visit(GameEventCardStatsChanged event) {
// TODO Smart partial updates
return updateManyCards(event.cards);
}
// Update manapool
private final List<Player> manaPoolUpdate = new Vector<Player>();
private final Runnable updManaPool = new Runnable() {
@Override public void run() {
synchronized (manaPoolUpdate) {
FControl.updateManaPool(manaPoolUpdate);
manaPoolUpdate.clear();
}
}
};
@Override
public Void visit(GameEventManaPool event) {
boolean invokeUpdate = false;
synchronized (manaPoolUpdate) {
if (!manaPoolUpdate.contains(event.player)) {
invokeUpdate = manaPoolUpdate.isEmpty();
manaPoolUpdate.add(event.player);
}
}
if (invokeUpdate)
FThreads.invokeInEdtNowOrLater(updManaPool);
return null;
}
// Update lives counters
private final List<Player> livesUpdate = new Vector<Player>();
private final Runnable updLives = new Runnable() {
@Override public void run() {
synchronized (livesUpdate) {
FControl.updateLives(livesUpdate);
livesUpdate.clear();
}
}
};
@Override
public Void visit(GameEventPlayerLivesChanged event) {
boolean invokeUpdate = false;
synchronized (livesUpdate) {
if (!livesUpdate.contains(event.player)) {
invokeUpdate = livesUpdate.isEmpty();
livesUpdate.add(event.player);
}
}
if (invokeUpdate)
FThreads.invokeInEdtNowOrLater(updLives);
return null;
}
@Override
public Void visit(GameEventPlayerPoisoned event) {
boolean invokeUpdate = false;
synchronized (livesUpdate) {
if (!livesUpdate.contains(event.receiver)) {
invokeUpdate = livesUpdate.isEmpty();
livesUpdate.add(event.receiver);
}
}
if (invokeUpdate)
FThreads.invokeInEdtNowOrLater(updLives);
return null;
}
}

View File

@@ -0,0 +1,176 @@
package forge.screens.match;
import com.google.common.eventbus.Subscribe;
import forge.FThreads;
import forge.game.event.*;
import forge.screens.match.input.InputPlaybackControl;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
public class FControlGamePlayback extends IGameEventVisitor.Base<Void> {
private final InputPlaybackControl inputPlayback = new InputPlaybackControl(this);
private final AtomicBoolean paused = new AtomicBoolean(false);
private final CyclicBarrier gameThreadPauser = new CyclicBarrier(2);
public FControlGamePlayback() {
}
@Subscribe
public void receiveGameEvent(final GameEvent ev) { ev.visit(this); }
private int phasesDelay = 200;
private int combatDelay = 400;
private int castDelay = 400;
private int resolveDelay = 400;
private boolean fasterPlayback = false;
private void pauseForEvent(int delay) {
try {
Thread.sleep(fasterPlayback ? delay / 10 : delay);
} catch (InterruptedException e) {
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
e.printStackTrace();
}
}
@Override
public Void visit(GameEventBlockersDeclared event) {
pauseForEvent(combatDelay);
return super.visit(event);
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventTurnPhase)
*/
@Override
public Void visit(GameEventTurnPhase ev) {
boolean isUiToStop = FControl.stopAtPhase(ev.playerTurn, ev.phase);
switch(ev.phase) {
case COMBAT_END:
case COMBAT_DECLARE_ATTACKERS:
case COMBAT_DECLARE_BLOCKERS:
if (FControl.getGame().getPhaseHandler().inCombat()) {
pauseForEvent(combatDelay);
}
break;
default:
if (isUiToStop) {
pauseForEvent(phasesDelay);
}
break;
}
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventDuelFinished)
*/
@Override
public Void visit(GameEventGameFinished event) {
FControl.getInputQueue().removeInput(inputPlayback);
return null;
}
@Override
public Void visit(GameEventGameStarted event) {
FControl.getInputQueue().setInput(inputPlayback);
return null;
}
@Override
public Void visit(GameEventLandPlayed event) {
pauseForEvent(resolveDelay);
return super.visit(event);
}
@Override
public Void visit(final GameEventSpellResolved event) {
FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() { FControl.setCard(event.spell.getHostCard()); } });
pauseForEvent(resolveDelay);
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventSpellAbilityCast)
*/
@Override
public Void visit(final GameEventSpellAbilityCast event) {
FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() { FControl.setCard(event.sa.getHostCard()); } });
pauseForEvent(castDelay);
return null;
}
/* (non-Javadoc)
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventPlayerPriority)
*/
@Override
public Void visit(GameEventPlayerPriority event) {
if (paused.get()) {
try {
inputPlayback.onGamePaused();
gameThreadPauser.await();
gameThreadPauser.reset();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
return null;
}
public void onGameStopRequested() {
paused.set(false);
if (gameThreadPauser.getNumberWaiting() != 0) {
releaseGameThread();
}
}
private void releaseGameThread() {
// just need to run another thread through the barrier... not edt preferrably :)
FControl.getGame().getAction().invoke(new Runnable() {
@Override
public void run() {
try {
gameThreadPauser.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
e.printStackTrace();
}
}
});
}
public void resume() {
paused.set(false);
releaseGameThread();
}
public void pause() {
paused.set(true);
}
public void singleStep() {
releaseGameThread();
}
/**
* TODO: Write javadoc for this method.
* @param isFast
*/
public void setSpeed(boolean isFast) {
fasterPlayback = isFast;
}
}

View File

@@ -1,8 +1,7 @@
package forge.screens.match;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import forge.screens.FScreen;
import forge.screens.match.views.VAvatar;
import forge.screens.match.views.VPlayerPanel;
@@ -20,7 +19,7 @@ public class MatchScreen extends FScreen {
public static FSkinColor BORDER_COLOR = FSkinColor.get(Colors.CLR_BORDERS);
private final Match match;
private final Map<RegisteredPlayer, VPlayerPanel> playerPanels;
private final List<VPlayerPanel> playerPanels;
//private final VLog log;
private final VStack stack;
private final VPrompt prompt;
@@ -31,12 +30,12 @@ public class MatchScreen extends FScreen {
super(true, "Game", true);
match = match0;
playerPanels = new HashMap<RegisteredPlayer, VPlayerPanel>();
playerPanels = new ArrayList<VPlayerPanel>();
for (RegisteredPlayer player : match.getPlayers()) {
playerPanels.put(player, add(new VPlayerPanel(player)));
playerPanels.add(add(new VPlayerPanel(player)));
}
bottomPlayerPanel = playerPanels.get(match.getPlayers().get(0));
topPlayerPanel = playerPanels.get(match.getPlayers().get(1));
bottomPlayerPanel = playerPanels.get(0);
topPlayerPanel = playerPanels.get(1);
topPlayerPanel.setFlipped(true);
bottomPlayerPanel.setSelectedZone(ZoneType.Hand);
@@ -47,6 +46,18 @@ public class MatchScreen extends FScreen {
FControl.startGame(match0, this);
}
public VPrompt getPrompt() {
return prompt;
}
public VStack getStack() {
return stack;
}
public List<VPlayerPanel> getPlayerPanels() {
return playerPanels;
}
@Override
public void drawBackground(Graphics g) {
super.drawBackground(g);

View File

@@ -0,0 +1,6 @@
package forge.screens.match.events;
public interface IUiEventVisitor<T> {
T visit(UiEventBlockerAssigned event);
T visit(UiEventAttackerDeclared event);
}

View File

@@ -0,0 +1,7 @@
package forge.screens.match.events;
public abstract class UiEvent {
public abstract <T> T visit(IUiEventVisitor<T> visitor);
}

View File

@@ -0,0 +1,32 @@
package forge.screens.match.events;
import forge.game.GameEntity;
import forge.game.card.Card;
/**
* TODO: Write javadoc for this type.
*
*/
public class UiEventAttackerDeclared extends UiEvent {
public final Card attacker;
public final GameEntity defender;
public UiEventAttackerDeclared(Card card, GameEntity currentDefender) {
attacker = card;
defender = currentDefender;
}
@Override
public <T> T visit(IUiEventVisitor<T> visitor) {
return visitor.visit(this);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return attacker.toString() + ( defender == null ? " removed from combat" : " declared to attack " + defender.getName() );
}
}

View File

@@ -0,0 +1,21 @@
package forge.screens.match.events;
import forge.game.card.Card;
public class UiEventBlockerAssigned extends UiEvent {
public final Card blocker;
public final Card attackerBeingBlocked;
public UiEventBlockerAssigned(Card card, Card currentAttacker) {
blocker = card;
attackerBeingBlocked = currentAttacker;
}
@Override
public <T> T visit(IUiEventVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.screens.match.FControl;
import forge.toolbox.FButton;
/**
* Manages match UI OK/Cancel button enabling and focus
*/
public class ButtonUtil {
public static void setButtonText(String okLabel, String cancelLabel) {
getOk().setText(okLabel);
getCancel().setText(cancelLabel);
}
public static void reset() {
disableAll();
getOk().setText("OK");
getCancel().setText("Cancel");
}
public static void enableAll() {
getOk().setEnabled(true);
getCancel().setEnabled(true);
}
public static void disableAll() {
getOk().setEnabled(false);
getCancel().setEnabled(false);
}
public static void enableOnlyOk() {
getOk().setEnabled(true);
getCancel().setEnabled(false);
}
public static void enableOnlyCancel() {
getOk().setEnabled(false);
getCancel().setEnabled(true);
}
private static FButton getOk() {
return FControl.getView().getPrompt().getBtnOk();
}
private static FButton getCancel() {
return FControl.getView().getPrompt().getBtnCancel();
}
}

View File

@@ -0,0 +1,22 @@
package forge.screens.match.input;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public interface Input {
// showMessage() is always the first method called
void showMessageInitial();
void selectCard(Card c);
void selectAbility(SpellAbility ab);
void selectPlayer(Player player);
void selectButtonOK();
void selectButtonCancel();
}

View File

@@ -0,0 +1,242 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import com.google.common.collect.Iterables;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.screens.match.FControl;
import forge.screens.match.events.UiEventAttackerDeclared;
import java.util.List;
/**
* <p>
* InputAttack class.
* </p>
*
* @author Forge
* @version $Id: InputAttack.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputAttack extends InputSyncronizedBase {
/** Constant <code>serialVersionUID=7849903731842214245L</code>. */
private static final long serialVersionUID = 7849903731842214245L;
private final Combat combat;
private final List<GameEntity> defenders;
private GameEntity currentDefender;
private final Player playerAttacks;
private final Player playerDeclares;
private AttackingBand activeBand = null;
public InputAttack(Player attacks, Player declares, Combat combat) {
this.playerAttacks = attacks;
this.playerDeclares = declares;
this.combat = combat;
this.defenders = combat.getDefenders();
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
// TODO still seems to have some issues with multiple planeswalkers
ButtonUtil.enableOnlyOk();
setCurrentDefender(defenders.isEmpty() ? null : defenders.get(0));
if ( null == currentDefender ) {
System.err.println("InputAttack has no potential defenders!");
return; // should even throw here!
}
List<Card> possibleAttackers = playerAttacks.getCardsIn(ZoneType.Battlefield);
for (Card c : Iterables.filter(possibleAttackers, CardPredicates.Presets.CREATURES)) {
if (c.hasKeyword("CARDNAME attacks each turn if able.")) {
for(GameEntity def : defenders ) {
if( CombatUtil.canAttack(c, def, combat) ) {
combat.addAttacker(c, currentDefender);
FControl.fireEvent(new UiEventAttackerDeclared(c, currentDefender));
break;
}
}
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
final int i = c.getKeywordPosition("CARDNAME attacks specific player each combat if able");
final String defined = c.getKeyword().get(i).split(":")[1];
final Player player = AbilityUtils.getDefinedPlayers(c, defined, null).get(0);
if (player != null && CombatUtil.canAttack(c, player, combat)) {
combat.addAttacker(c, player);
FControl.fireEvent(new UiEventAttackerDeclared(c, player));
}
}
}
}
private void showCombat() {
// redraw sword icons
FControl.showCombat(combat);
}
/** {@inheritDoc} */
@Override
protected final void onOk() {
// TODO Add check to see if each must attack creature is attacking
// Propaganda costs could have been paid here.
setCurrentDefender(null); // remove highlights
activateBand(null);
stop();
}
@Override
protected final void onPlayerSelected(Player selected) {
if (defenders.contains(selected)) {
setCurrentDefender(selected);
}
else {
flashIncorrectAction(); // cannot attack that player
}
}
/** {@inheritDoc} */
@Override
protected final void onCardSelected(final Card card) {
final List<Card> att = combat.getAttackers();
if (/*triggerEvent.getButton() == 3 && */att.contains(card) && !card.hasKeyword("CARDNAME attacks each turn if able.")
&& !card.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
// TODO Is there no way to attacks each turn cards to attack Planeswalkers?
combat.removeFromCombat(card);
FControl.setUsedToPay(card, false);
showCombat();
// When removing an attacker clear the attacking band
this.activateBand(null);
FControl.fireEvent(new UiEventAttackerDeclared(card, null));
return;
}
if (combat.isAttacking(card, currentDefender)) {
// Activate band by selecting/deselecting a band member
if (this.activeBand == null) {
this.activateBand(combat.getBandOfAttacker(card));
} else if (this.activeBand.getAttackers().contains(card)) {
this.activateBand(null);
} else { // Join a band by selecting a non-active band member after activating a band
if (this.activeBand.canJoinBand(card)) {
combat.removeFromCombat(card);
declareAttacker(card);
} else {
flashIncorrectAction();
}
}
updateMessage();
return;
}
if ( card.getController().isOpponentOf(playerAttacks) ) {
if ( defenders.contains(card) ) { // planeswalker?
setCurrentDefender(card);
return;
}
}
if (playerAttacks.getZone(ZoneType.Battlefield).contains(card) && CombatUtil.canAttack(card, currentDefender, combat)) {
if (this.activeBand != null && !this.activeBand.canJoinBand(card)) {
this.activateBand(null);
updateMessage();
flashIncorrectAction();
return;
}
if(combat.isAttacking(card)) {
combat.removeFromCombat(card);
}
declareAttacker(card);
showCombat();
}
else {
flashIncorrectAction();
}
} // selectCard()
/**
* TODO: Write javadoc for this method.
* @param card
*/
private void declareAttacker(final Card card) {
combat.addAttacker(card, currentDefender, this.activeBand);
this.activateBand(this.activeBand);
updateMessage();
FControl.fireEvent(new UiEventAttackerDeclared(card, currentDefender));
}
private final void setCurrentDefender(GameEntity def) {
currentDefender = def;
for( GameEntity ge: defenders ) {
if ( ge instanceof Card) {
FControl.setUsedToPay((Card)ge, ge == def);
}
else if (ge instanceof Player) {
FControl.setHighlighted((Player) ge, ge == def);
}
}
updateMessage();
// update UI
}
private final void activateBand(AttackingBand band) {
if (this.activeBand != null) {
for(Card card : this.activeBand.getAttackers()) {
FControl.setUsedToPay(card, false);
}
}
this.activeBand = band;
if (this.activeBand != null) {
for(Card card : this.activeBand.getAttackers()) {
FControl.setUsedToPay(card, true);
}
}
// update UI
}
private void updateMessage() {
StringBuilder sb = new StringBuilder();
sb.append(playerDeclares.getName()).append(", ");
sb.append(playerAttacks == playerDeclares ? "declare attackers." : "declare attackers for " + playerAttacks.getName()).append("\n");
sb.append("Selecting Creatures to Attack ").append(currentDefender).append("\n\n");
sb.append("To change the current defender, click on the player or planeswalker you wish to attack.\n");
sb.append("To attack as a band, click an attacking creature to activate its 'band', select another to join the band.");
showMessage(sb.toString());
}
}

View File

@@ -0,0 +1,108 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.screens.match.FControl;
/**
* <p>
* Abstract Input class.
* </p>
*
* @author Forge
* @version $Id: InputBase.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public abstract class InputBase implements java.io.Serializable, Input {
/** Constant <code>serialVersionUID=-6539552513871194081L</code>. */
private static final long serialVersionUID = -6539552513871194081L;
private boolean finished = false;
protected final boolean isFinished() { return finished; }
protected final void setFinished() { finished = true; }
// showMessage() is always the first method called
@Override
public final void showMessageInitial() {
finished = false;
showMessage();
}
protected abstract void showMessage();
@Override
public final void selectPlayer(final Player player) {
if (isFinished()) { return; }
onPlayerSelected(player);
}
@Override
public void selectAbility(SpellAbility ab) { }
@Override
public final void selectButtonCancel() {
if (isFinished()) { return; }
onCancel();
}
@Override
public final void selectButtonOK() {
if (isFinished()) { return; }
onOk();
}
@Override
public final void selectCard(final Card c) {
if (isFinished()) { return; }
onCardSelected(c);
}
protected void onCardSelected(final Card c) {}
protected void onPlayerSelected(final Player p) {}
protected void onCancel() {}
protected void onOk() {}
// to remove need for CMatchUI dependence
protected final void showMessage(String message) {
FControl.showMessage(message);
}
protected final void flashIncorrectAction() {
FControl.getView().getPrompt().remind();
}
protected String getTurnPhasePriorityMessage(Game game) {
final PhaseHandler ph = game.getPhaseHandler();
final StringBuilder sb = new StringBuilder();
sb.append("Priority: ").append(ph.getPriorityPlayer()).append("\n");
sb.append("Turn ").append(ph.getTurn()).append(" (").append(ph.getPlayerTurn()).append(")\n");
sb.append("Phase: ").append(ph.getPhase().nameForUi).append("\n");
sb.append("Stack: ");
if (!game.getStack().isEmpty()) {
sb.append(game.getStack().size()).append(" to Resolve.");
} else {
sb.append("Empty");
}
sb.append("\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,136 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.game.card.Card;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.screens.match.FControl;
import forge.screens.match.events.UiEventBlockerAssigned;
import forge.toolbox.FOptionPane;
/**
* <p>
* Input_Block class.
* </p>
*
* @author Forge
* @version $Id: InputBlock.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputBlock extends InputSyncronizedBase {
/** Constant <code>serialVersionUID=6120743598368928128L</code>. */
private static final long serialVersionUID = 6120743598368928128L;
private Card currentAttacker = null;
// some cards may block several creatures at a time. (ex: Two-Headed Dragon, Vanguard's Shield)
private final Combat combat;
private final Player defender;
private final Player declarer;
/**
* TODO: Write javadoc for Constructor.
* @param priority
*/
public InputBlock(Player whoDeclares, Player whoDefends, Combat combat) {
defender = whoDefends;
declarer = whoDeclares;
this.combat = combat;
}
/** {@inheritDoc} */
@Override
protected final void showMessage() {
// could add "Reset Blockers" button
ButtonUtil.enableOnlyOk();
String prompt = declarer == defender ? "declare blockers." : "declare blockers for " + defender.getName();
final StringBuilder sb = new StringBuilder(declarer.getName());
sb.append(", ").append(prompt).append("\n\n");
if (this.currentAttacker == null) {
sb.append("To Block, click on your opponent's attacker first, then your blocker(s).\n");
sb.append("To cancel a block right-click on your blocker");
}
else {
final String attackerName = this.currentAttacker.isFaceDown() ? "Morph" : this.currentAttacker.getName();
sb.append("Select a creature to block ").append(attackerName).append(" (");
sb.append(this.currentAttacker.getUniqueNumber()).append("). ");
sb.append("To cancel a block right-click on your blocker");
}
showMessage(sb.toString());
FControl.showCombat(combat);
}
/** {@inheritDoc} */
@Override
public final void onOk() {
String blockErrors = CombatUtil.validateBlocks(combat, defender);
if( null == blockErrors ) {
// Done blocking
ButtonUtil.reset();
setCurrentAttacker(null);
stop();
}
else {
FOptionPane.showMessageDialog(blockErrors);
}
}
/** {@inheritDoc} */
@Override
public final void onCardSelected(final Card card) {
if (/*triggerEvent.getButton() == 3 &&*/ card.getController() == defender) {
combat.removeFromCombat(card);
FControl.fireEvent(new UiEventBlockerAssigned(card, (Card)null));
} else {
// is attacking?
boolean isCorrectAction = false;
if (combat.isAttacking(card)) {
setCurrentAttacker(card);
isCorrectAction = true;
} else {
// Make sure this card is valid to even be a blocker
if (this.currentAttacker != null && card.isCreature() && defender.getZone(ZoneType.Battlefield).contains(card)) {
isCorrectAction = CombatUtil.canBlock(this.currentAttacker, card, combat);
if ( isCorrectAction ) {
combat.addBlocker(this.currentAttacker, card);
FControl.fireEvent(new UiEventBlockerAssigned(card, currentAttacker));
}
}
}
if (!isCorrectAction) {
flashIncorrectAction();
}
}
this.showMessage();
} // selectCard()
private void setCurrentAttacker(Card card) {
currentAttacker = card;
for(Card c : combat.getAttackers()) {
FControl.setUsedToPay(c, card == c);
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
/**
* <p>
* InputConfirm class.
* </p>
*
* @author Forge
* @version $Id: InputConfirm.java 21647 2013-05-24 22:31:11Z Max mtg $
*/
public class InputConfirm extends InputSyncronizedBase {
private static final long serialVersionUID = -3591794991788531626L;
private final String message;
private final String yesButtonText;
private final String noButtonText;
private boolean result;
public InputConfirm(String message0) {
this(message0, "Yes", "No", true);
}
public InputConfirm(String message0, String yesButtonText0, String noButtonText0) {
this(message0, yesButtonText0, noButtonText0, true);
}
public InputConfirm(String message0, String yesButtonText0, String noButtonText0, boolean defaultYes0) {
this.message = message0;
this.yesButtonText = yesButtonText0;
this.noButtonText = noButtonText0;
result = defaultYes0;
}
/** {@inheritDoc} */
@Override
protected final void showMessage() {
ButtonUtil.setButtonText(this.yesButtonText, this.noButtonText);
ButtonUtil.enableAll();
showMessage(this.message);
}
/** {@inheritDoc} */
@Override
protected final void onOk() {
this.result = true;
done();
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
this.result = false;
done();
}
private void done() {
ButtonUtil.reset();
stop();
}
public final boolean getResult() {
return this.result;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.screens.match.FControl;
import forge.toolbox.GuiDialog;
import forge.util.Lang;
import forge.util.ThreadUtil;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* InputConfirmMulligan class.
* </p>
*
* @author Forge
* @version $Id: InputConfirmMulligan.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputConfirmMulligan extends InputSyncronizedBase {
/** Constant <code>serialVersionUID=-8112954303001155622L</code>. */
private static final long serialVersionUID = -8112954303001155622L;
boolean keepHand = false;
final boolean isCommander;
private final List<Card> selected = new ArrayList<Card>();
private final Player player;
private final Player startingPlayer;
public InputConfirmMulligan(Player humanPlayer, Player startsGame, boolean commander) {
player = humanPlayer;
isCommander = commander;
startingPlayer = startsGame;
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
Game game = player.getGame();
StringBuilder sb = new StringBuilder();
if (startingPlayer == player) {
sb.append(player).append(", you are going first!\n\n");
}
else {
sb.append(startingPlayer.getName()).append(" is going first.\n");
sb.append(player).append(", you are going ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n\n");
}
if (isCommander) {
ButtonUtil.setButtonText("Keep", "Exile");
ButtonUtil.enableOnlyOk();
sb.append("Will you keep your hand or choose some cards to exile those and draw one less card?");
}
else {
ButtonUtil.setButtonText("Keep", "Mulligan");
ButtonUtil.enableAll();
sb.append("Do you want to keep your hand?");
}
showMessage(sb.toString());
}
/** {@inheritDoc} */
@Override
protected final void onOk() {
keepHand = true;
done();
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
keepHand = false;
done();
}
private void done() {
ButtonUtil.reset();
if (isCommander) {
// Clear the "selected" icon after clicking the done button
for (Card c : this.selected) {
FControl.setUsedToPay(c, false);
}
}
stop();
}
volatile boolean cardSelectLocked = false;
@Override
protected void onCardSelected(final Card c0) { // the only place that would cause troubles - input is supposed only to confirm, not to fire abilities
boolean fromHand = player.getZone(ZoneType.Hand).contains(c0);
boolean isSerumPowder = c0.getName().equals("Serum Powder");
boolean isLegalChoice = fromHand && (isCommander || isSerumPowder);
if (!isLegalChoice || cardSelectLocked) {
flashIncorrectAction();
return;
}
if (isSerumPowder && GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) {
cardSelectLocked = true;
ThreadUtil.invokeInGameThread(new Runnable() {
public void run() {
List<Card> hand = new ArrayList<Card>(c0.getController().getCardsIn(ZoneType.Hand));
for (Card c : hand) {
player.getGame().getAction().exile(c);
}
c0.getController().drawCards(hand.size());
cardSelectLocked = false;
}
});
return;
}
if (isCommander) { // allow to choose cards for partial paris
if (selected.contains(c0)) {
FControl.setUsedToPay(c0, false);
selected.remove(c0);
}
else {
FControl.setUsedToPay(c0, true);
selected.add(c0);
}
if (selected.isEmpty()) {
ButtonUtil.enableOnlyOk();
}
else {
ButtonUtil.enableAll();
}
}
}
public final boolean isKeepHand() {
return keepHand;
}
public List<Card> getSelectedCards() {
return selected;
}
}

View File

@@ -0,0 +1,65 @@
package forge.screens.match.input;
import forge.FThreads;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.screens.match.FControl;
import forge.util.ThreadUtil;
import java.util.concurrent.atomic.AtomicInteger;
public class InputLockUI implements Input {
private final AtomicInteger iCall = new AtomicInteger();
public InputLockUI(InputQueue inputQueue) {
}
public void showMessageInitial() {
int ixCall = 1 + iCall.getAndIncrement();
ThreadUtil.delay(500, new InputUpdater(ixCall));
}
@Override
public String toString() {
return "lockUI";
}
private class InputUpdater implements Runnable {
final int ixCall;
public InputUpdater(final int idxCall) {
ixCall = idxCall;
}
@Override
public void run() {
if ( ixCall != iCall.get() || !isActive()) // cancel the message if it's not from latest call or input is gone already
return;
FThreads.invokeInEdtLater(showMessageFromEdt);
}
};
private final Runnable showMessageFromEdt = new Runnable() {
@Override
public void run() {
ButtonUtil.disableAll();
showMessage("Waiting for actions...");
}
};
protected final boolean isActive() {
return FControl.getInputQueue().getInput() == this;
}
protected void showMessage(String message) {
FControl.showMessage(message);
}
@Override public void selectCard(Card c) {}
@Override public void selectAbility(SpellAbility ab) {}
@Override public void selectPlayer(Player player) {}
@Override public void selectButtonOK() {}
@Override public void selectButtonCancel() {}
}

View File

@@ -0,0 +1,79 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
/**
* <p>
* Input_PassPriority class.
* </p>
*
* @author Forge
* @version $Id: InputPassPriority.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputPassPriority extends InputSyncronizedBase {
/** Constant <code>serialVersionUID=-581477682214137181L</code>. */
private static final long serialVersionUID = -581477682214137181L;
private final Player player;
private SpellAbility chosenSa;
public InputPassPriority(Player human) {
player = human;
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
showMessage(getTurnPhasePriorityMessage(player.getGame()));
chosenSa = null;
ButtonUtil.enableOnlyOk();
}
/** {@inheritDoc} */
@Override
protected final void onOk() {
stop();
}
public SpellAbility getChosenSa() { return chosenSa; }
@Override
protected void onCardSelected(final Card card) {
List<SpellAbility> abilities = card.getAllPossibleAbilities(player, false);
if (abilities.isEmpty()) {
flashIncorrectAction();
return;
}
selectAbility(player.getController().getAbilityToPlay(abilities));
}
@Override
public void selectAbility(final SpellAbility ab) {
if (ab != null) {
chosenSa = ab;
stop();
}
}
}

View File

@@ -0,0 +1,375 @@
package forge.screens.match.input;
import forge.FThreads;
import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.game.Game;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
import forge.player.HumanPlay;
import forge.toolbox.GuiChoose;
import forge.utils.Evaluator;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public abstract class InputPayMana extends InputSyncronizedBase {
private static final long serialVersionUID = -9133423708688480255L;
protected int phyLifeToLose = 0;
protected final Player player;
protected final Game game;
protected ManaCostBeingPaid manaCost;
protected final SpellAbility saPaidFor;
private boolean bPaid = false;
private Boolean canPayManaCost = null;
private boolean locked = false;
protected InputPayMana(SpellAbility saToPayFor, Player payer) {
this.player = payer;
this.game = player.getGame();
this.saPaidFor = saToPayFor;
}
@Override
protected void onCardSelected(final Card card) {
if (card.getManaAbility().isEmpty()) {
flashIncorrectAction();
return;
}
// only tap card if the mana is needed
activateManaAbility(card, this.manaCost);
}
public void useManaFromPool(byte colorCode) {
// find the matching mana in pool.
player.getManaPool().tryPayCostWithColor(colorCode, saPaidFor, manaCost);
onManaAbilityPaid();
showMessage();
}
/**
* <p>
* activateManaAbility.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param card
* a {@link forge.game.card.Card} object.
* @param manaCost
* a {@link forge.game.mana.ManaCostBeingPaid} object.
* @return a {@link forge.game.mana.ManaCostBeingPaid} object.
*/
protected void activateManaAbility(final Card card, ManaCostBeingPaid manaCost) {
if ( locked ) {
System.err.print("Should wait till previous call to playAbility finishes.");
return;
}
// make sure computer's lands aren't selected
if (card.getController() != player) {
return;
}
byte colorCanUse = 0;
byte colorNeeded = 0;
for (final byte color : MagicColor.WUBRG) {
if (manaCost.isAnyPartPayableWith(color, player.getManaPool())) { colorCanUse |= color; }
if (manaCost.needsColor(color, player.getManaPool())) { colorNeeded |= color; }
}
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.COLORLESS, player.getManaPool()))
colorCanUse |= ManaAtom.COLORLESS;
if ( 0 == colorCanUse ) // no mana cost or something
return;
List<SpellAbility> abilities = new ArrayList<SpellAbility>();
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
final String typeRes = manaCost.getSourceRestriction();
if (StringUtils.isNotBlank(typeRes) && !card.isType(typeRes)) {
return;
}
boolean guessAbilityWithRequiredColors = true;
for (SpellAbility ma : card.getManaAbility()) {
ma.setActivatingPlayer(player);
AbilityManaPart m = ma.getManaPartRecursive();
if (m == null || !ma.canPlay()) { continue; }
if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; }
if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; }
if (!m.meetsManaRestrictions(saPaidFor)) { continue; }
abilities.add(ma);
// skip express mana if the ability is not undoable or reusable
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null) {
guessAbilityWithRequiredColors = false;
}
}
if (abilities.isEmpty()) {
return;
}
// Store some information about color costs to help with any mana choices
if (colorNeeded == 0) { // only colorless left
if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) {
String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(",");
for (String negColor : negEffects) {
byte col = MagicColor.fromName(negColor);
colorCanUse |= col;
}
}
}
// If the card has any ability that tracks mana spent, skip express Mana choice
if (saPaidFor.tracksManaSpent()) {
colorCanUse = MagicColor.ALL_COLORS;
guessAbilityWithRequiredColors = false;
}
boolean choice = true;
if (guessAbilityWithRequiredColors) {
// express Mana Choice
if (colorNeeded == 0) {
choice = false;
//avoid unnecessary prompt by pretending we need White
//for the sake of "Add one mana of any color" effects
colorNeeded = MagicColor.WHITE;
}
else {
final ArrayList<SpellAbility> colorMatches = new ArrayList<SpellAbility>();
for (SpellAbility sa : abilities) {
if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) {
colorMatches.add(sa);
}
}
if (colorMatches.isEmpty()) {
// can only match colorless just grab the first and move on.
// This is wrong. Sometimes all abilities aren't created equal
choice = false;
}
else if (colorMatches.size() < abilities.size()) {
// leave behind only color matches
abilities = colorMatches;
}
}
}
final SpellAbility chosen = abilities.size() > 1 && choice ? GuiChoose.one("Choose mana ability", abilities) : abilities.get(0);
ColorSet colors = ColorSet.fromMask(0 == colorNeeded ? colorCanUse : colorNeeded);
chosen.getManaPartRecursive().setExpressChoice(colors);
// System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana");
Runnable proc = new Runnable() {
@Override
public void run() {
HumanPlay.playSpellAbility(chosen.getActivatingPlayer(), chosen);
player.getManaPool().payManaFromAbility(saPaidFor, InputPayMana.this.manaCost, chosen);
onManaAbilityPaid();
onStateChanged();
}
};
locked = true;
game.getAction().invoke(proc);
}
/**
* <p>
* canMake. color is like "G", returns "Green".
* </p>
*
* @param am
* a {@link forge.card.spellability.AbilityMana} object.
* @param mana
* a {@link java.lang.String} object.
* @return a boolean.
*/
private static boolean abilityProducesManaColor(final SpellAbility am, AbilityManaPart m, final byte neededColor) {
if (0 != (neededColor & MagicColor.COLORLESS)) {
return true;
}
if (m.isAnyMana()) {
return true;
}
// check for produce mana replacement effects - they mess this up, so just use the mana ability
final Card source = am.getHostCard();
final Player activator = am.getActivatingPlayer();
final Game g = source.getGame();
final HashMap<String, Object> repParams = new HashMap<String, Object>();
repParams.put("Event", "ProduceMana");
repParams.put("Mana", m.getOrigProduced());
repParams.put("Affected", source);
repParams.put("Player", activator);
repParams.put("AbilityMana", am);
for (final Player p : g.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect.requirementsCheck(g)
&& replacementEffect.canReplace(repParams)
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
&& replacementEffect.zonesCheck(g.getZoneOf(crd))) {
return true;
}
}
}
}
if (am.getApi() == ApiType.ManaReflected) {
final Iterable<String> reflectableColors = CardUtil.getReflectableManaColors(am);
for (final String color : reflectableColors) {
if (0 != (neededColor & MagicColor.fromName(color))) {
return true;
}
}
}
else {
String colorsProduced = m.isComboMana() ? m.getComboColors() : m.getOrigProduced();
for (final String color : colorsProduced.split(" ")) {
if (0 != (neededColor & MagicColor.fromName(color))) {
return true;
}
if( (neededColor & ManaAtom.COLORLESS) != 0)
return true;
}
}
return false;
}
protected boolean isAlreadyPaid() {
if (manaCost.isPaid()) {
bPaid = true;
}
return bPaid;
}
protected boolean supportAutoPay() {
return true;
}
private void runAsAi(Runnable proc) {
this.player.runWithController(proc, new PlayerControllerAi(this.game, this.player, this.player.getOriginalLobbyPlayer()));
}
/** {@inheritDoc} */
@Override
protected void onOk() {
if (supportAutoPay()) {
//use AI utility to automatically pay mana cost if possible
final Runnable proc = new Runnable() {
@Override
public void run() {
ComputerUtilMana.payManaCost(manaCost, saPaidFor, player);
}
};
//must run in game thread as certain payment actions can only be automated there
game.getAction().invoke(new Runnable() {
@Override
public void run() {
runAsAi(proc);
onStateChanged();
}
});
}
}
protected void updateButtons() {
if (supportAutoPay()) {
ButtonUtil.setButtonText("Auto", "Cancel");
}
ButtonUtil.enableOnlyCancel();
}
protected final void updateMessage() {
locked = false;
if (supportAutoPay()) {
if (canPayManaCost == null) {
//use AI utility to determine if mana cost can be paid if that hasn't been determined yet
Evaluator<Boolean> proc = new Evaluator<Boolean>() {
@Override
public Boolean evaluate() {
return ComputerUtilMana.canPayManaCost(manaCost, saPaidFor, player);
}
};
runAsAi(proc);
canPayManaCost = proc.getResult();
}
if (canPayManaCost) {
ButtonUtil.enableAll(); //enabled Auto button if mana cost can be paid
}
}
showMessage(getMessage());
}
/** {@inheritDoc} */
@Override
protected final void onStop() {
if (supportAutoPay()) {
ButtonUtil.reset();
}
}
/** {@inheritDoc} */
@Override
public void showMessage() {
if (isFinished()) { return; }
updateButtons();
onStateChanged();
}
protected void onStateChanged() {
if (isAlreadyPaid()) {
done();
stop();
}
else {
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
updateMessage();
}
});
}
}
protected void onManaAbilityPaid() {} // some inputs overload it
protected abstract void done();
protected abstract String getMessage();
@Override
public String toString() {
return String.format("PayManaBase %s left", manaCost.toString());
}
public boolean isPaid() { return bPaid; }
protected String messagePrefix;
public void setMessagePrefix(String prompt) {
// TODO Auto-generated method stub
messagePrefix = prompt;
}
}

View File

@@ -0,0 +1,61 @@
package forge.screens.match.input;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class InputPayManaOfCostPayment extends InputPayMana {
public InputPayManaOfCostPayment(ManaCostBeingPaid cost, SpellAbility spellAbility, Player payer) {
super(spellAbility, payer);
manaCost = cost;
}
private static final long serialVersionUID = 3467312982164195091L;
private int phyLifeToLose = 0;
@Override
protected final void onPlayerSelected(Player selected) {
if (player == selected) {
if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) {
this.phyLifeToLose += 2;
}
this.showMessage();
}
}
@Override
protected void done() {
final Card source = saPaidFor.getHostCard();
if (this.phyLifeToLose > 0) {
player.payLife(this.phyLifeToLose, source);
}
}
@Override
protected void onCancel() {
stop();
}
@Override
protected String getMessage() {
final String displayMana = manaCost.toString(false);
final StringBuilder msg = new StringBuilder();
if( messagePrefix != null )
msg.append(messagePrefix).append("\n");
msg.append("Pay Mana Cost: ").append(displayMana);
if (this.phyLifeToLose > 0) {
msg.append(" (");
msg.append(this.phyLifeToLose);
msg.append(" life paid for phyrexian mana)");
}
if (manaCost.containsPhyrexianMana()) {
msg.append("\n(Click on your life total to pay life for phyrexian mana.)");
}
return msg.toString();
}
}

View File

@@ -0,0 +1,133 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
//pays the cost of a card played from the player's hand
//the card is removed from the players hand if the cost is paid
//CANNOT be used for ABILITIES
public class InputPayManaSimple extends InputPayMana {
// anything that uses this should be converted to Ability_Cost
/** Constant <code>serialVersionUID=3467312982164195091L</code>. */
private static final long serialVersionUID = 3467312982164195091L;
private final Card originalCard;
private final ManaCost originalManaCost;
public InputPayManaSimple(final Game game, final SpellAbility sa, final ManaCostBeingPaid manaCostToPay) {
super(sa, sa.getActivatingPlayer());
this.originalManaCost = manaCostToPay.toManaCost();
this.originalCard = sa.getHostCard();
if (sa.getHostCard().isCopiedSpell() && sa.isSpell()) {
this.manaCost = new ManaCostBeingPaid(ManaCost.ZERO);
game.getStack().add(this.saPaidFor);
}
else {
this.manaCost = manaCostToPay;
}
}
protected void onManaAbilityPaid() {
if (this.manaCost.isPaid()) {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
}
}
/** {@inheritDoc} */
@Override
protected final void onPlayerSelected(Player selected) {
if (player == selected) {
if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) {
this.phyLifeToLose += 2;
}
this.showMessage();
}
}
/**
* <p>
* done.
* </p>
*/
@Override
protected void done() {
this.originalCard.setSunburstValue(this.manaCost.getSunburst());
if (this.phyLifeToLose > 0) {
player.payLife(this.phyLifeToLose, this.originalCard);
}
if (!this.saPaidFor.getHostCard().isCopiedSpell()) {
if (this.saPaidFor.isSpell()) {
this.saPaidFor.setHostCard(game.getAction().moveToStack(this.originalCard));
}
}
}
/** {@inheritDoc} */
@Override
protected final void onCancel() {
player.getManaPool().refundManaPaid(this.saPaidFor);
// Update UI
this.stop();
}
/** {@inheritDoc} */
@Override
public final void showMessage() {
if (isFinished()) { return; }
updateButtons();
if (this.manaCost.isPaid() && !new ManaCostBeingPaid(this.originalManaCost).isPaid()) {
this.done();
this.stop();
}
else {
updateMessage();
}
}
/* (non-Javadoc)
* @see forge.control.input.InputPayManaBase#updateMessage()
*/
@Override
protected String getMessage() {
final StringBuilder msg = new StringBuilder("Pay Mana Cost: " + this.manaCost.toString());
if (this.phyLifeToLose > 0) {
msg.append(" (");
msg.append(this.phyLifeToLose);
msg.append(" life paid for phyrexian mana)");
}
if (this.manaCost.containsPhyrexianMana()) {
msg.append("\n(Click on your life total to pay life for phyrexian mana.)");
}
// has its own variant of checkIfPaid
return msg.toString();
}
}

View File

@@ -0,0 +1,129 @@
package forge.screens.match.input;
import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.card.Card;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.spellability.SpellAbility;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class InputPayManaX extends InputPayMana {
private static final long serialVersionUID = -6900234444347364050L;
private int xPaid = 0;
private ArrayList<Mana> xPaidByColor = new ArrayList<>();
private byte colorsPaid;
private final ManaCost manaCostPerX;
private final boolean xCanBe0;
private boolean canceled = false;
public InputPayManaX(final SpellAbility sa0, final int amountX, final boolean xCanBe0) {
super(sa0, sa0.getActivatingPlayer());
xPaid = 0;
if (saPaidFor.hasParam("XColor")) {
String xColor = saPaidFor.getParam("XColor");
if (amountX == 1) {
manaCostPerX = new ManaCost(new ManaCostParser(xColor));
}
else {
List<String> list = new ArrayList<String>(amountX);
for (int i = 0; i < amountX; i++) {
list.add(xColor);
}
manaCostPerX = new ManaCost(new ManaCostParser(StringUtils.join(list, ' ')));
}
}
else {
manaCostPerX = ManaCost.get(amountX);
}
manaCost = new ManaCostBeingPaid(manaCostPerX);
this.xCanBe0 = xCanBe0;
colorsPaid = saPaidFor.getHostCard().getColorsPaid(); // for effects like sunburst
}
/* (non-Javadoc)
* @see forge.control.input.InputPayManaBase#isPaid()
*/
@Override
public boolean isPaid() {
//return !( xPaid == 0 && !costMana.canXbe0() || this.colorX.equals("") && !this.manaCost.toString().equals(strX) );
// return !( xPaid == 0 && !costMana.canXbe0()) && !(this.colorX.equals("") && !this.manaCost.toString().equals(strX));
return !canceled && (xPaid > 0 || xCanBe0);
}
@Override
protected boolean supportAutoPay() {
return false;
}
@Override
public void showMessage() {
if (isFinished()) { return; }
updateMessage();
}
@Override
protected String getMessage() {
StringBuilder msg = new StringBuilder("Pay X Mana Cost for ");
msg.append(saPaidFor.getHostCard().getName()).append("\n").append(this.xPaid);
msg.append(" Paid so far.");
if (!xCanBe0) {
msg.append(" X Can't be 0.");
}
// Enable just cancel is full X value hasn't been paid for multiple X values
// or X is 0, and x can't be 0
if (!isPaid()) {
ButtonUtil.enableOnlyCancel();
}
else {
ButtonUtil.enableAll();
}
return msg.toString();
}
@Override
protected void onCardSelected(final Card card) {
// don't allow here the cards that produce only wrong colors
activateManaAbility(card, this.manaCost);
}
@Override
protected void onManaAbilityPaid() {
if (this.manaCost.isPaid()) {
this.colorsPaid |= manaCost.getColorsPaid();
this.manaCost = new ManaCostBeingPaid(manaCostPerX);
this.xPaid++;
this.xPaidByColor.add(saPaidFor.getPayingMana().get(0));
}
}
@Override
protected final void onCancel() {
// If you hit cancel, isPaid needs to return false
this.canceled = true;
this.stop();
}
@Override
protected final void onOk() {
done();
this.stop();
}
@Override
protected void done() {
final Card card = saPaidFor.getHostCard();
card.setXManaCostPaid(this.xPaid);
card.setXManaCostPaidByColor(this.xPaidByColor);
card.setColorsPaid(this.colorsPaid);
card.setSunburstValue(ColorSet.fromMask(this.colorsPaid).countColors());
}
}

View File

@@ -0,0 +1,72 @@
package forge.screens.match.input;
import forge.screens.match.FControl;
import forge.screens.match.FControlGamePlayback;
/**
* TODO: Write javadoc for this type.
*
*/
public class InputPlaybackControl extends InputSyncronizedBase implements InputSynchronized {
private static final long serialVersionUID = 7979208993306642072L;
FControlGamePlayback control;
private boolean isPaused = false;
private boolean isFast = false;
/**
* TODO: Write javadoc for Constructor.
* @param fControlGamePlayback
*/
public InputPlaybackControl(FControlGamePlayback fControlGamePlayback) {
control = fControlGamePlayback;
}
/* (non-Javadoc)
* @see forge.gui.input.InputBase#showMessage()
*/
@Override
protected void showMessage() {
setPause(false);
ButtonUtil.enableAll();
}
private void setPause(boolean pause) {
isPaused = pause;
if ( isPaused )
ButtonUtil.setButtonText("Resume", "Step");
else {
ButtonUtil.setButtonText("Pause", isFast ? "1x Speed" : "10x Faster");
showMessage("Press pause to pause game.");
}
}
public void onGamePaused() {
showMessage(getTurnPhasePriorityMessage(FControl.getGame()));
}
@Override
protected void onOk() {
if ( isPaused ) {
control.resume();
setPause(false);
} else {
control.pause();
setPause(true);
}
}
@Override
protected void onCancel() {
if ( isPaused ) {
control.singleStep();
} else {
isFast = !isFast;
control.setSpeed(isFast);
setPause(isPaused); // update message
}
}
}

View File

@@ -0,0 +1,99 @@
package forge.screens.match.input;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.toolbox.GuiChoose;
import java.util.*;
import java.util.Map.Entry;
public final class InputProliferate extends InputSelectManyBase<GameEntity> {
private static final long serialVersionUID = -1779224307654698954L;
private Map<GameEntity, CounterType> chosenCounters = new HashMap<GameEntity, CounterType>();
public InputProliferate() {
super(1, Integer.MAX_VALUE);
allowUnselect = true;
}
protected String getMessage() {
StringBuilder sb = new StringBuilder("Choose permanents and/or players with counters on them to add one more counter of that type.");
sb.append("\n\nYou've selected so far:\n");
if (chosenCounters.isEmpty()) {
sb.append("(none)");
}
else {
for (Entry<GameEntity, CounterType> ge : chosenCounters.entrySet()) {
if (ge.getKey() instanceof Player) {
sb.append("* A poison counter to player ").append(ge.getKey()).append("\n");
}
else {
sb.append("* ").append(ge.getKey()).append(" -> ").append(ge.getValue()).append("counter\n");
}
}
}
return sb.toString();
}
@Override
protected void onCardSelected(final Card card) {
if (!card.hasCounters()) {
return;
}
boolean entityWasSelected = chosenCounters.containsKey(card);
if (entityWasSelected) {
this.chosenCounters.remove(card);
}
else {
final List<CounterType> choices = new ArrayList<CounterType>();
for (final CounterType ct : CounterType.values()) {
if (card.getCounters(ct) > 0) {
choices.add(ct);
}
}
CounterType toAdd = choices.size() == 1 ? choices.get(0) : GuiChoose.one("Select counter type", choices);
chosenCounters.put(card, toAdd);
}
refresh();
}
@Override
protected final void onPlayerSelected(Player player) {
if (player.getPoisonCounters() == 0 || player.hasKeyword("You can't get poison counters")) {
return;
}
boolean entityWasSelected = chosenCounters.containsKey(player);
if (entityWasSelected) {
this.chosenCounters.remove(player);
} else
this.chosenCounters.put(player, null /* POISON counter is meant */);
refresh();
}
public Map<GameEntity, CounterType> getProliferationMap() {
return chosenCounters;
}
@Override
protected boolean hasEnoughTargets() { return true; }
@Override
protected boolean hasAllTargets() { return false; }
@Override
public Collection<GameEntity> getSelected() {
// TODO Auto-generated method stub
return chosenCounters.keySet();
}
}

View File

@@ -0,0 +1,163 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.FThreads;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.screens.match.FControl;
import forge.toolbox.FOptionPane;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.atomic.AtomicReference;
/**
* <p>
* GuiInput class.
* </p>
*
* @author Forge
* @version $Id: InputProxy.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputProxy implements Observer {
/** The input. */
private AtomicReference<Input> input = new AtomicReference<Input>();
private Game game = null;
// private static final boolean DEBUG_INPUT = true; // false;
public void setGame(Game game0) {
game = game0;
FControl.getInputQueue().addObserver(this);
}
public boolean passPriority() {
Input inp = getInput();
if (inp != null && inp instanceof InputPassPriority) {
inp.selectButtonOK();
return true;
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
FOptionPane.showMessageDialog("Cannot pass priority at this time.");
}
});
return false;
}
@Override
public final void update(final Observable observable, final Object obj) {
final Input nextInput = FControl.getInputQueue().getActualInput(game);
/* if(DEBUG_INPUT)
System.out.printf("%s ... \t%s on %s, \tstack = %s%n",
FThreads.debugGetStackTraceItem(6, true), nextInput == null ? "null" : nextInput.getClass().getSimpleName(),
game.getPhaseHandler().debugPrintState(), Singletons.getControl().getInputQueue().printInputStack());
*/
this.input.set(nextInput);
Runnable showMessage = new Runnable() {
@Override public void run() {
Input current = getInput();
FControl.getInputQueue().syncPoint();
//System.out.printf("\t%s > showMessage @ %s/%s during %s%n", FThreads.debugGetCurrThreadId(), nextInput.getClass().getSimpleName(), current.getClass().getSimpleName(), game.getPhaseHandler().debugPrintState());
current.showMessageInitial();
}
};
FThreads.invokeInEdtLater(showMessage);
}
/**
* <p>
* selectButtonOK.
* </p>
*/
public final void selectButtonOK() {
Input inp = getInput();
if (inp != null) {
inp.selectButtonOK();
}
}
/**
* <p>
* selectButtonCancel.
* </p>
*/
public final void selectButtonCancel() {
Input inp = getInput();
if (inp != null) {
inp.selectButtonCancel();
}
}
/**
* <p>
* selectPlayer.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
*/
public final void selectPlayer(final Player player) {
Input inp = getInput();
if (inp != null) {
inp.selectPlayer(player);
}
}
/**
* <p>
* selectCard.
* </p>
*
* @param card
* a {@link forge.game.card.Card} object.
* @param triggerEvent
*/
public final void selectCard(final Card card) {
Input inp = getInput();
if (inp != null) {
inp.selectCard(card);
}
}
public final void selectAbility(SpellAbility ab) {
Input inp = getInput();
if (inp != null) {
inp.selectAbility(ab);
}
}
/** {@inheritDoc} */
@Override
public final String toString() {
Input inp = getInput();
return null == inp ? "(null)" : inp.toString();
}
/** @return {@link forge.gui.InputProxy.InputBase} */
private Input getInput() {
return this.input.get();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.screens.match.input;
import forge.game.Game;
import java.util.Observable;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
/**
* <p>
* InputControl class.
* </p>
*
* @author Forge
* @version $Id: InputQueue.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class InputQueue extends Observable {
private final BlockingDeque<InputSynchronized> inputStack = new LinkedBlockingDeque<InputSynchronized>();
private final InputLockUI inputLock;
public InputQueue() {
inputLock = new InputLockUI(this);
}
public final void updateObservers() {
this.setChanged();
this.notifyObservers();
}
public final Input getInput() {
return inputStack.isEmpty() ? null : this.inputStack.peek();
}
public final void removeInput(Input inp) {
Input topMostInput = inputStack.isEmpty() ? null : inputStack.pop();
if (topMostInput != inp) {
throw new RuntimeException("Cannot remove input " + inp.getClass().getSimpleName() + " because it's not on top of stack. Stack = " + inputStack );
}
updateObservers();
}
/**
* <p>
* updateInput.
* </p>
*
* @return a {@link forge.gui.input.InputBase} object.
*/
public final Input getActualInput(Game game) {
Input topMost = inputStack.peek(); // incoming input to Control
if (topMost != null && !game.isGameOver()) {
return topMost;
}
return inputLock;
} // getInput()
// only for debug purposes
public String printInputStack() {
return inputStack.toString();
}
public void setInput(InputSynchronized input) {
this.inputStack.push(input);
syncPoint();
this.updateObservers();
}
public void syncPoint() {
synchronized (inputLock) {
// acquire and release lock, so that actions from Game thread happen before EDT reads their results
}
}
/**
* TODO: Write javadoc for this method.
*/
public void onGameOver(boolean releaseAllInputs) {
for (InputSynchronized inp : inputStack) {
inp.relaseLatchWhenGameIsOver();
if (!releaseAllInputs) {
break;
}
}
}
} // InputControl

View File

@@ -0,0 +1,98 @@
package forge.screens.match.input;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public final class InputSelectCardsForConvoke extends InputSelectManyBase<Card> {
private static final long serialVersionUID = -1779224307654698954L;
private final Map<Card, ImmutablePair<Byte, ManaCostShard>> chosenCards = new HashMap<Card, ImmutablePair<Byte, ManaCostShard>>();
private final ManaCostBeingPaid remainingCost;
private final Player player;
public InputSelectCardsForConvoke(Player p, ManaCost cost, List<Card> untapped) {
super(1, Math.min(cost.getCMC(), untapped.size()));
remainingCost = new ManaCostBeingPaid(cost);
player = p;
allowUnselect = true;
}
protected String getMessage() {
return "Choose creatures to tap for convoke.\nRemaining mana cost is " + remainingCost.toString();
}
@Override
protected void onCardSelected(final Card card) {
boolean entityWasSelected = chosenCards.containsKey(card);
if (entityWasSelected) {
ImmutablePair<Byte, ManaCostShard> color = this.chosenCards.remove(card);
remainingCost.increaseShard(color.right, 1);
onSelectStateChanged(card, false);
}
else {
byte chosenColor = player.getController().chooseColorAllowColorless("Convoke " + card.toString() + " for which color?", card, CardUtil.getColors(card));
if (remainingCost.getColorlessManaAmount() > 0 && (chosenColor == 0 || !remainingCost.needsColor(chosenColor, player.getManaPool()))) {
registerConvoked(card, ManaCostShard.COLORLESS, chosenColor);
} else {
for (ManaCostShard shard : remainingCost.getDistinctShards()) {
if (shard.canBePaidWithManaOfColor(chosenColor)) {
registerConvoked(card, shard, chosenColor);
return;
}
}
showMessage("The colors provided by " + card.toString() + " you've chosen cannot be used to decrease the manacost of " + remainingCost.toString());
flashIncorrectAction();
}
}
refresh();
}
private void registerConvoked(Card card, ManaCostShard shard, byte chosenColor) {
remainingCost.decreaseShard(shard, 1);
chosenCards.put(card, ImmutablePair.of(chosenColor, shard));
onSelectStateChanged(card, true);
}
@Override
protected final void onPlayerSelected(Player player) {
}
public Map<Card, ManaCostShard> getConvokeMap() {
Map<Card, ManaCostShard> result = new HashMap<Card, ManaCostShard>();
if( !hasCancelled() )
for(Entry<Card, ImmutablePair<Byte, ManaCostShard>> c : chosenCards.entrySet())
result.put(c.getKey(), c.getValue().right);
return result;
}
@Override
protected boolean hasEnoughTargets() { return true; }
@Override
protected boolean hasAllTargets() { return false; }
@Override
public Collection<Card> getSelected() {
// TODO Auto-generated method stub
return chosenCards.keySet();
}
}

View File

@@ -0,0 +1,21 @@
package forge.screens.match.input;
import forge.game.card.Card;
import java.util.Collection;
public class InputSelectCardsFromList extends InputSelectEntitiesFromList<Card> {
private static final long serialVersionUID = 6230360322294805986L;
public InputSelectCardsFromList(int cnt, Collection<Card> validCards) {
super(cnt, cnt, validCards); // to avoid hangs
}
public InputSelectCardsFromList(int min, int max, Collection<Card> validCards) {
super(min, max, validCards); // to avoid hangs
}
public InputSelectCardsFromList(Collection<Card> validCards) {
super(1, 1, validCards); // to avoid hangs
}
}

View File

@@ -0,0 +1,75 @@
package forge.screens.match.input;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.player.Player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSelectManyBase<T> {
private static final long serialVersionUID = -6609493252672573139L;
private final Collection<T> validChoices;
protected final List<T> selected = new ArrayList<T>();
public InputSelectEntitiesFromList(int min, int max, Collection<T> validChoices) {
super(Math.min(min, validChoices.size()), Math.min(max, validChoices.size()));
this.validChoices = validChoices;
if ( min > validChoices.size() )
System.out.println(String.format("Trying to choose at least %d cards from a list with only %d cards!", min, validChoices.size()));
}
@Override
protected void onCardSelected(final Card c) {
if (!selectEntity(c)) {
return;
}
refresh();
}
@Override
protected void onPlayerSelected(final Player p) {
if (!selectEntity(p)) {
return;
}
refresh();
}
public final Collection<T> getSelected() {
return selected;
}
@SuppressWarnings("unchecked")
protected boolean selectEntity(GameEntity c) {
if (!validChoices.contains(c)) {
return false;
}
boolean entityWasSelected = selected.contains(c);
if (entityWasSelected) {
if (!allowUnselect)
return false;
this.selected.remove(c);
}
else {
this.selected.add((T)c);
}
onSelectStateChanged(c, !entityWasSelected);
return true;
}
// might re-define later
protected boolean hasEnoughTargets() { return selected.size() >= min; }
protected boolean hasAllTargets() { return selected.size() >= max; }
protected String getMessage() {
return max == Integer.MAX_VALUE
? String.format(message, selected.size())
: String.format(message, max - selected.size());
}
}

View File

@@ -0,0 +1,101 @@
package forge.screens.match.input;
import com.google.common.collect.Iterables;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.screens.match.FControl;
import java.util.Collection;
public abstract class InputSelectManyBase<T extends GameEntity> extends InputSyncronizedBase {
private static final long serialVersionUID = -2305549394512889450L;
protected boolean bCancelled = false;
protected final int min;
protected final int max;
protected boolean allowUnselect = false;
protected boolean allowCancel = false;
protected String message = "Source-Card-Name - Select %d more card(s)";
protected InputSelectManyBase(int min, int max) {
if (min > max) {
throw new IllegalArgumentException("Min must not be greater than Max");
}
this.min = min;
this.max = max;
}
protected void refresh() {
if (hasAllTargets()) {
selectButtonOK();
}
else {
this.showMessage();
}
}
protected abstract boolean hasEnoughTargets();
protected abstract boolean hasAllTargets();
protected abstract String getMessage();
@Override
public final void showMessage() {
showMessage(getMessage());
boolean canCancel = allowCancel;
boolean canOk = hasEnoughTargets();
if (canOk && canCancel) { ButtonUtil.enableAll(); }
if (!canOk && canCancel) { ButtonUtil.enableOnlyCancel(); }
if (canOk && !canCancel) { ButtonUtil.enableOnlyOk(); }
if (!canOk && !canCancel) { ButtonUtil.disableAll(); }
}
@Override
protected final void onCancel() {
bCancelled = true;
this.getSelected().clear();
this.stop();
afterStop();
}
public final boolean hasCancelled() {
return bCancelled;
}
public abstract Collection<T> getSelected();
public T getFirstSelected() { return Iterables.getFirst(getSelected(), null); }
@Override
protected final void onOk() {
this.stop();
afterStop();
}
public void setMessage(String message0) {
this.message = message0;
}
protected void onSelectStateChanged(GameEntity c, boolean newState) {
if (c instanceof Card) {
FControl.setUsedToPay((Card)c, newState); // UI supports card highlighting though this abstraction-breaking mechanism
}
}
protected void afterStop() {
for (GameEntity c : getSelected()) {
if (c instanceof Card) {
FControl.setUsedToPay((Card)c, false);
}
}
}
public final boolean isUnselectAllowed() { return allowUnselect; }
public final void setUnselectAllowed(boolean allow) { this.allowUnselect = allow; }
public final void setCancelAllowed(boolean allow) { this.allowCancel = allow ; }
}

View File

@@ -0,0 +1,229 @@
package forge.screens.match.input;
import forge.game.GameEntity;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.screens.match.FControl;
import forge.toolbox.GuiChoose;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public final class InputSelectTargets extends InputSyncronizedBase {
private final List<Card> choices;
// some cards can be targeted several times (eg: distribute damage as you choose)
private final Map<GameEntity, Integer> targetDepth = new HashMap<GameEntity, Integer>();
private final TargetRestrictions tgt;
private final SpellAbility sa;
private boolean bCancel = false;
private boolean bOk = false;
private final boolean mandatory;
private static final long serialVersionUID = -1091595663541356356L;
public final boolean hasCancelled() { return bCancel; }
public final boolean hasPressedOk() { return bOk; }
/**
* TODO: Write javadoc for Constructor.
* @param select
* @param choices
* @param req
* @param alreadyTargeted
* @param targeted
* @param tgt
* @param sa
* @param mandatory
*/
public InputSelectTargets(List<Card> choices, SpellAbility sa, boolean mandatory) {
this.choices = choices;
this.tgt = sa.getTargetRestrictions();
this.sa = sa;
this.mandatory = mandatory;
}
@Override
public void showMessage() {
final StringBuilder sb = new StringBuilder();
sb.append("Targeted:\n");
for (final Entry<GameEntity, Integer> o : targetDepth.entrySet()) {
sb.append(o.getKey());
if( o.getValue() > 1 )
sb.append(" (").append(o.getValue()).append(" times)");
sb.append("\n");
}
if (!sa.getUniqueTargets().isEmpty()) {
sb.append("Parent Targeted:");
sb.append(sa.getUniqueTargets()).append("\n");
}
sb.append(sa.getHostCard() + " - " + tgt.getVTSelection());
int maxTargets = tgt.getMaxTargets(sa.getHostCard(), sa);
int targeted = sa.getTargets().getNumTargeted();
if(maxTargets > 1)
sb.append("\n(").append(maxTargets - targeted).append(" more can be targeted)");
showMessage(sb.toString());
// If reached Minimum targets, enable OK button
if (!tgt.isMinTargetsChosen(sa.getHostCard(), sa) || tgt.isDividedAsYouChoose()) {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target
ButtonUtil.disableAll();
} else {
ButtonUtil.enableOnlyCancel();
}
} else {
if (mandatory && tgt.hasCandidates(sa, true)) {
// Player has to click on a target or ok
ButtonUtil.enableOnlyOk();
} else {
ButtonUtil.enableAll();
}
}
}
@Override
protected final void onCancel() {
bCancel = true;
this.done();
}
@Override
protected final void onOk() {
bOk = true;
this.done();
}
@Override
protected final void onCardSelected(final Card card) {
if (!tgt.isUniqueTargets() && targetDepth.containsKey(card)) {
return;
}
// leave this in temporarily, there some seriously wrong things going on here
// Can be targeted doesn't check if the target is a valid type, only if a card is generally "targetable"
if (!card.canBeTargetedBy(sa)) {
showMessage(sa.getHostCard() + " - Cannot target this card (Shroud? Protection? Restrictions).");
return;
}
if (!choices.contains(card)) {
if (card.isPlaneswalker() && sa.getApi() == ApiType.DealDamage) {
showMessage(sa.getHostCard() + " - To deal an opposing Planeswalker direct damage, target its controller and then redirect the damage on resolution.");
} else {
showMessage(sa.getHostCard() + " - The selected card is not a valid choice to be targeted.");
}
return;
}
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((sa.getTargets().getNumTargeted() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa))
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
} else if (sa.getApi() == ApiType.PutCounter) {
apiBasedMessage = "Select how many counters to distribute to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(card.toString());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(card, allocatedPortion);
}
addTarget(card);
} // selectCard()
@Override
protected final void onPlayerSelected(Player player) {
if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) {
return;
}
if (!sa.canTarget(player)) {
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
return;
}
if (tgt.isDividedAsYouChoose()) {
final int stillToDivide = tgt.getStillToDivide();
int allocatedPortion = 0;
// allow allocation only if the max targets isn't reached and there are more candidates
if ((sa.getTargets().getNumTargeted() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
final Integer[] choices = new Integer[stillToDivide];
for (int i = 1; i <= stillToDivide; i++) {
choices[i - 1] = i;
}
String apiBasedMessage = "Distribute how much to ";
if (sa.getApi() == ApiType.DealDamage) {
apiBasedMessage = "Select how much damage to deal to ";
} else if (sa.getApi() == ApiType.PreventDamage) {
apiBasedMessage = "Select how much damage to prevent to ";
}
final StringBuilder sb = new StringBuilder();
sb.append(apiBasedMessage);
sb.append(player.getName());
Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices);
if (null == chosen) {
return;
}
allocatedPortion = chosen;
} else { // otherwise assign the rest of the damage/protection
allocatedPortion = stillToDivide;
}
tgt.setStillToDivide(stillToDivide - allocatedPortion);
tgt.addDividedAllocation(player, allocatedPortion);
}
addTarget(player);
}
private void addTarget(GameEntity ge) {
sa.getTargets().add(ge);
if (ge instanceof Card) {
FControl.setUsedToPay((Card) ge, true);
}
Integer val = targetDepth.get(ge);
targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) );
if (hasAllTargets()) {
bOk = true;
this.done();
}
else {
this.showMessage();
}
}
private void done() {
for (GameEntity c : targetDepth.keySet()) {
if (c instanceof Card) {
FControl.setUsedToPay((Card)c, false);
}
}
this.stop();
}
private boolean hasAllTargets() {
return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose());
}
}

View File

@@ -0,0 +1,6 @@
package forge.screens.match.input;
public interface InputSynchronized extends Input {
void awaitLatchRelease();
void relaseLatchWhenGameIsOver();
}

View File

@@ -0,0 +1,52 @@
package forge.screens.match.input;
import forge.FThreads;
import forge.error.BugReporter;
import forge.screens.match.FControl;
import java.util.concurrent.CountDownLatch;
public abstract class InputSyncronizedBase extends InputBase implements InputSynchronized {
private static final long serialVersionUID = 8756177361251703052L;
private final CountDownLatch cdlDone;
public InputSyncronizedBase() {
cdlDone = new CountDownLatch(1);
}
public void awaitLatchRelease() {
FThreads.assertExecutedByEdt(false);
try{
cdlDone.await();
} catch (InterruptedException e) {
BugReporter.reportException(e);
}
}
public final void relaseLatchWhenGameIsOver() {
cdlDone.countDown();
}
public void showAndWait() {
FControl.getInputQueue().setInput(this);
awaitLatchRelease();
}
protected final void stop() {
onStop();
// ensure input won't accept any user actions.
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
setFinished();
}
});
// thread irrelevant
FControl.getInputQueue().removeInput(InputSyncronizedBase.this);
cdlDone.countDown();
}
protected void onStop() { }
}

View File

@@ -1,6 +1,5 @@
package forge.screens.match.views;
import forge.screens.FScreen;
import forge.toolbox.FContainer;
public class VLog extends FContainer {

View File

@@ -15,13 +15,13 @@ import forge.toolbox.FContainer;
import forge.toolbox.FDisplayObject;
import forge.utils.Utils;
public class VPhases extends FContainer {
public class VPhaseIndicator extends FContainer {
public static final float HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.3f;
private static final FSkinFont labelFont = FSkinFont.get(11);
private final Map<PhaseType, PhaseLabel> phaseLabels = new HashMap<PhaseType, PhaseLabel>();
public VPhases() {
public VPhaseIndicator() {
addPhaseLabel("UP", PhaseType.UPKEEP);
addPhaseLabel("DR", PhaseType.DRAW);
addPhaseLabel("M1", PhaseType.MAIN1);
@@ -40,6 +40,10 @@ public class VPhases extends FContainer {
phaseLabels.put(phaseType, add(new PhaseLabel(caption, phaseType)));
}
public PhaseLabel getLabel(PhaseType phaseType) {
return phaseLabels.get(phaseType);
}
public void resetPhaseButtons() {
for (PhaseLabel lbl : phaseLabels.values()) {
lbl.setActive(false);
@@ -60,7 +64,7 @@ public class VPhases extends FContainer {
}
}
private class PhaseLabel extends FDisplayObject {
public class PhaseLabel extends FDisplayObject {
private final String caption;
private final PhaseType phaseType;
private boolean stopAtPhase = false;
@@ -78,6 +82,10 @@ public class VPhases extends FContainer {
active = active0;
}
public PhaseType getPhaseType() {
return phaseType;
}
public boolean getStopAtPhase() {
return stopAtPhase;
}

View File

@@ -24,7 +24,7 @@ public class VPlayerPanel extends FContainer {
private static final FSkinColor ZONE_BACK_COLOR = FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f);
private final RegisteredPlayer player;
private final VPhases phases;
private final VPhaseIndicator phaseIndicator;
private final VField field;
private final VAvatar avatar;
private final List<InfoLabel> infoLabels = new ArrayList<InfoLabel>();
@@ -33,7 +33,7 @@ public class VPlayerPanel extends FContainer {
public VPlayerPanel(RegisteredPlayer player0) {
player = player0;
phases = add(new VPhases());
phaseIndicator = add(new VPhaseIndicator());
field = add(new VField());
avatar = add(new VAvatar(player.getPlayer().getAvatarIndex()));
infoLabels.add(add(new LifeLabel()));
@@ -96,11 +96,19 @@ public class VPlayerPanel extends FContainer {
return field;
}
public VPhaseIndicator getPhaseIndicator() {
return phaseIndicator;
}
public VAvatar getAvatar() {
return avatar;
}
@Override
protected void doLayout(float width, float height) {
//layout for bottom panel by default
float x = VAvatar.WIDTH;
phases.setBounds(x, height - VPhases.HEIGHT, width - VAvatar.WIDTH, VPhases.HEIGHT);
phaseIndicator.setBounds(x, height - VPhaseIndicator.HEIGHT, width - VAvatar.WIDTH, VPhaseIndicator.HEIGHT);
float y, zoneHeight;
if (selectedZone != null) {
@@ -118,7 +126,7 @@ public class VPlayerPanel extends FContainer {
y = height - VAvatar.HEIGHT;
avatar.setPosition(0, y);
float infoLabelWidth;
float infoLabelHeight = VAvatar.HEIGHT - VPhases.HEIGHT;
float infoLabelHeight = VAvatar.HEIGHT - VPhaseIndicator.HEIGHT;
for (InfoLabel infoLabel : infoLabels) {
infoLabelWidth = infoLabel.getPreferredWidth();
infoLabel.setBounds(x, y, infoLabelWidth, infoLabelHeight);

View File

@@ -1,11 +1,18 @@
package forge.screens.match.views;
import org.apache.commons.lang3.StringUtils;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import forge.FThreads;
import forge.Forge.Graphics;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinColor.Colors;
import forge.game.Game;
import forge.game.GameRules;
import forge.game.Match;
import forge.screens.match.input.InputProxy;
import forge.toolbox.FButton;
import forge.toolbox.FContainer;
import forge.utils.Utils;
@@ -19,15 +26,59 @@ public class VPrompt extends FContainer {
private static final FSkinFont font = FSkinFont.get(11);
private final FButton btnOk, btnCancel;
private String message = "This is where the prompt would be.\nLine 2 of the prompt.\nLine 3 of the prompt.";
private final InputProxy inputProxy = new InputProxy();
private String message;
public VPrompt() {
btnOk = add(new FButton("Yes"));
btnCancel = add(new FButton("No"));
btnOk = add(new FButton("Yes", new Runnable() {
@Override
public void run() {
inputProxy.selectButtonOK();
}
}));
btnCancel = add(new FButton("No", new Runnable() {
@Override
public void run() {
inputProxy.selectButtonCancel();
}
}));
btnOk.setSize(BTN_WIDTH, HEIGHT);
btnCancel.setSize(BTN_WIDTH, HEIGHT);
}
public FButton getBtnOk() {
return btnOk;
}
public FButton getBtnCancel() {
return btnCancel;
}
public String getMessage() {
return message;
}
public void setMessage(String message0) {
message = message0;
}
public InputProxy getInputProxy() {
return inputProxy;
}
/** Flashes animation on input panel if play is currently waiting on input. */
public void remind() {
//SDisplayUtil.remind(view);
}
public void updateText(Game game) {
//FThreads.assertExecutedByEdt(true);
//final Match match = game.getMatch();
//final GameRules rules = game.getRules();
//final String text = String.format("T:%d G:%d/%d [%s]", game.getPhaseHandler().getTurn(), match.getPlayedGames().size() + 1, rules.getGamesPerMatch(), rules.getGameType());
//view.getLblGames().setText(text);
//view.getLblGames().setToolTipText(String.format("%s: Game #%d of %d, turn %d", rules.getGameType(), match.getPlayedGames().size() + 1, rules.getGamesPerMatch(), game.getPhaseHandler().getTurn()));
}
@Override
protected void doLayout(float width, float height) {
btnCancel.setLeft(width - BTN_WIDTH);
@@ -39,7 +90,9 @@ public class VPrompt extends FContainer {
float h = getHeight();
g.fillRect(backColor, 0, 0, w, h);
if (!StringUtils.isEmpty(message)) {
g.drawText(message, font, foreColor, BTN_WIDTH, 0, w - 2 * BTN_WIDTH, h,
true, HAlignment.CENTER, true);
}
}
}

View File

@@ -13,6 +13,10 @@ public class VStack extends FContainer {
setSize(WIDTH, HEIGHT);
}
public void update() {
//TODO
}
@Override
protected void doLayout(float width, float height) {
// TODO Auto-generated method stub

View File

@@ -1,5 +1,7 @@
package forge.toolbox;
import org.apache.commons.lang3.StringUtils;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import forge.Forge.Graphics;
@@ -13,7 +15,7 @@ public class FButton extends FDisplayObject {
private static final FSkinColor foreColor = FSkinColor.get(Colors.CLR_TEXT);
private FSkinImage imgL, imgM, imgR;
private String caption;
private String text;
private FSkinFont font;
private boolean toggled = false;
private Runnable command;
@@ -25,12 +27,12 @@ public class FButton extends FDisplayObject {
this("", null);
}
public FButton(final String caption0) {
this(caption0, null);
public FButton(final String text0) {
this(text0, null);
}
public FButton(final String caption0, Runnable command0) {
caption = caption0;
public FButton(final String text0, Runnable command0) {
text = text0;
command = command0;
font = FSkinFont.get(14);
resetImg();
@@ -42,6 +44,13 @@ public class FButton extends FDisplayObject {
imgR = FSkinImage.BTN_UP_RIGHT;
}
public String getText() {
return text;
}
public void setText(String text0) {
text = text0;
}
@Override
public void setEnabled(boolean b0) {
if (isEnabled() == b0) { return; }
@@ -127,8 +136,8 @@ public class FButton extends FDisplayObject {
g.drawImage(imgL, 0, 0, buttonWidth, h);
g.drawImage(imgR, buttonWidth, 0, w - buttonWidth, h);
}
if (!caption.isEmpty()) {
g.drawText(caption, font, foreColor, insetX, 0, w - 2 * insetX, h, false, HAlignment.CENTER, true);
if (!StringUtils.isEmpty(text)) {
g.drawText(text, font, foreColor, insetX, 0, w - 2 * insetX, h, false, HAlignment.CENTER, true);
}
}
}

View File

@@ -0,0 +1,71 @@
package forge.toolbox;
import forge.FThreads;
import forge.game.card.Card;
import org.apache.commons.lang3.StringUtils;
import javax.swing.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Holds player interactions using standard windows
*
*/
public class GuiDialog {
private static final String[] defaultConfirmOptions = { "Yes", "No" };
public static boolean confirm(final Card c, final String question) {
return GuiDialog.confirm(c, question, true, null);
}
public static boolean confirm(final Card c, final String question, final boolean defaultChoice) {
return GuiDialog.confirm(c, question, defaultChoice, null);
}
public static boolean confirm(final Card c, final String question, String[] options) {
return GuiDialog.confirm(c, question, true, options);
}
public static boolean confirm(final Card c, final String question, final boolean defaultIsYes, final String[] options) {
Callable<Boolean> confirmTask = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
final String title = c == null ? "Question" : c.getName() + " - Ability";
String questionToUse = StringUtils.isBlank(question) ? "Activate card's ability?" : question;
String[] opts = options == null ? defaultConfirmOptions : options;
int answer = FOptionPane.showOptionDialog(questionToUse, title, FOptionPane.QUESTION_ICON, opts, defaultIsYes ? 0 : 1);
return answer == 0;
}};
FutureTask<Boolean> future = new FutureTask<Boolean>(confirmTask);
FThreads.invokeInEdtAndWait(future);
try {
return future.get().booleanValue();
}
catch (Exception e) { // should be no exception here
e.printStackTrace();
}
return false;
}
/**
* <p>
* showInfoDialg.
* </p>
*
* @param message
* a {@link java.lang.String} object.
*/
public static void message(final String message) {
message(message, UIManager.getString("OptionPane.messageDialogTitle"));
}
public static void message(final String message, final String title) {
FThreads.invokeInEdtAndWait(new Runnable() {
@Override
public void run() {
FOptionPane.showMessageDialog(message, title, null);
}
});
}
}

View File

@@ -0,0 +1,16 @@
package forge.utils;
public abstract class Evaluator<T> implements Runnable {
private T result;
@Override
public final void run() {
result = evaluate();
}
public abstract T evaluate();
public T getResult() {
return result;
}
}