mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
Pull in necessary files from forge-gui to start being able to flesh out gameplay
This commit is contained in:
50
.gitattributes
vendored
50
.gitattributes
vendored
@@ -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
|
||||
|
||||
101
forge-m-base/src/forge/FThreads.java
Normal file
101
forge-m-base/src/forge/FThreads.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
32
forge-m-base/src/forge/net/FServer.java
Normal file
32
forge-m-base/src/forge/net/FServer.java
Normal 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;
|
||||
}*/
|
||||
}
|
||||
7
forge-m-base/src/forge/net/IClientSocket.java
Normal file
7
forge-m-base/src/forge/net/IClientSocket.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package forge.net;
|
||||
|
||||
public interface IClientSocket {
|
||||
boolean isOpen();
|
||||
void send(String message);
|
||||
void close(String farewell);
|
||||
}
|
||||
9
forge-m-base/src/forge/net/IConnectionObserver.java
Normal file
9
forge-m-base/src/forge/net/IConnectionObserver.java
Normal 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);
|
||||
}
|
||||
87
forge-m-base/src/forge/net/Lobby.java
Normal file
87
forge-m-base/src/forge/net/Lobby.java
Normal 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);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
46
forge-m-base/src/forge/net/LobbyPlayerRemote.java
Normal file
46
forge-m-base/src/forge/net/LobbyPlayerRemote.java
Normal 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");
|
||||
}
|
||||
}
|
||||
25
forge-m-base/src/forge/net/client/INetClient.java
Normal file
25
forge-m-base/src/forge/net/client/INetClient.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
98
forge-m-base/src/forge/net/client/NetClient.java
Normal file
98
forge-m-base/src/forge/net/client/NetClient.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
11
forge-m-base/src/forge/net/client/state/IClientState.java
Normal file
11
forge-m-base/src/forge/net/client/state/IClientState.java
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
1125
forge-m-base/src/forge/player/HumanCostDecision.java
Normal file
1125
forge-m-base/src/forge/player/HumanCostDecision.java
Normal file
File diff suppressed because it is too large
Load Diff
754
forge-m-base/src/forge/player/HumanPlay.java
Normal file
754
forge-m-base/src/forge/player/HumanPlay.java
Normal 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);
|
||||
}
|
||||
}
|
||||
262
forge-m-base/src/forge/player/HumanPlaySpellAbility.java
Normal file
262
forge-m-base/src/forge/player/HumanPlaySpellAbility.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
176
forge-m-base/src/forge/screens/match/FControlGamePlayback.java
Normal file
176
forge-m-base/src/forge/screens/match/FControlGamePlayback.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package forge.screens.match.events;
|
||||
|
||||
public interface IUiEventVisitor<T> {
|
||||
T visit(UiEventBlockerAssigned event);
|
||||
T visit(UiEventAttackerDeclared event);
|
||||
}
|
||||
7
forge-m-base/src/forge/screens/match/events/UiEvent.java
Normal file
7
forge-m-base/src/forge/screens/match/events/UiEvent.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package forge.screens.match.events;
|
||||
|
||||
|
||||
public abstract class UiEvent {
|
||||
|
||||
public abstract <T> T visit(IUiEventVisitor<T> visitor);
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
65
forge-m-base/src/forge/screens/match/input/ButtonUtil.java
Normal file
65
forge-m-base/src/forge/screens/match/input/ButtonUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
22
forge-m-base/src/forge/screens/match/input/Input.java
Normal file
22
forge-m-base/src/forge/screens/match/input/Input.java
Normal 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();
|
||||
|
||||
}
|
||||
242
forge-m-base/src/forge/screens/match/input/InputAttack.java
Normal file
242
forge-m-base/src/forge/screens/match/input/InputAttack.java
Normal 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());
|
||||
}
|
||||
}
|
||||
108
forge-m-base/src/forge/screens/match/input/InputBase.java
Normal file
108
forge-m-base/src/forge/screens/match/input/InputBase.java
Normal 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();
|
||||
}
|
||||
}
|
||||
136
forge-m-base/src/forge/screens/match/input/InputBlock.java
Normal file
136
forge-m-base/src/forge/screens/match/input/InputBlock.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
forge-m-base/src/forge/screens/match/input/InputConfirm.java
Normal file
81
forge-m-base/src/forge/screens/match/input/InputConfirm.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
65
forge-m-base/src/forge/screens/match/input/InputLockUI.java
Normal file
65
forge-m-base/src/forge/screens/match/input/InputLockUI.java
Normal 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() {}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
375
forge-m-base/src/forge/screens/match/input/InputPayMana.java
Normal file
375
forge-m-base/src/forge/screens/match/input/InputPayMana.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
129
forge-m-base/src/forge/screens/match/input/InputPayManaX.java
Normal file
129
forge-m-base/src/forge/screens/match/input/InputPayManaX.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
163
forge-m-base/src/forge/screens/match/input/InputProxy.java
Normal file
163
forge-m-base/src/forge/screens/match/input/InputProxy.java
Normal 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();
|
||||
}
|
||||
}
|
||||
102
forge-m-base/src/forge/screens/match/input/InputQueue.java
Normal file
102
forge-m-base/src/forge/screens/match/input/InputQueue.java
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 ; }
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package forge.screens.match.input;
|
||||
|
||||
public interface InputSynchronized extends Input {
|
||||
void awaitLatchRelease();
|
||||
void relaseLatchWhenGameIsOver();
|
||||
}
|
||||
@@ -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() { }
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.screens.match.views;
|
||||
|
||||
import forge.screens.FScreen;
|
||||
import forge.toolbox.FContainer;
|
||||
|
||||
public class VLog extends FContainer {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
forge-m-base/src/forge/toolbox/GuiDialog.java
Normal file
71
forge-m-base/src/forge/toolbox/GuiDialog.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
16
forge-m-base/src/forge/utils/Evaluator.java
Normal file
16
forge-m-base/src/forge/utils/Evaluator.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user