diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 386c2156a69..253186a4822 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -31,6 +31,9 @@ import java.awt.event.KeyEvent; import java.util.List; import java.util.UUID; import javax.swing.*; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import mage.cards.decks.Deck; import mage.client.MageFrame; import mage.client.SessionHandler; @@ -47,6 +50,9 @@ import mage.client.util.audio.AudioManager; import mage.client.util.object.SaveObjectUtil; import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; +import mage.remote.ActionData; +import mage.remote.Session; +import mage.remote.SessionImpl; import mage.utils.CompressUtil; import mage.view.*; import mage.view.ChatMessage.MessageType; @@ -102,7 +108,6 @@ public class CallbackClientImpl implements CallbackClient { break; case CHATMESSAGE: { ChatMessage message = (ChatMessage) callback.getData(); - // Drop messages from ignored users if (message.getUsername() != null && IgnoreList.IGNORED_MESSAGE_TYPES.contains(message.getMessageType())) { final String serverAddress = SessionHandler.getSession().getServerHostname().orElseGet(() -> ""); @@ -183,6 +188,7 @@ public class CallbackClientImpl implements CallbackClient { case GAME_INIT: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_INIT", callback.getObjectId(), (GameView) callback.getData()); panel.init((GameView) callback.getData()); } break; @@ -190,6 +196,7 @@ public class CallbackClientImpl implements CallbackClient { case GAME_OVER: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData()); panel.endMessage((String) callback.getData(), callback.getMessageId()); } break; @@ -201,6 +208,7 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_ASK", callback.getObjectId(), message); panel.ask(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions()); } break; @@ -208,8 +216,10 @@ public class CallbackClientImpl implements CallbackClient { case GAME_TARGET: // e.g. Pick triggered ability { GameClientMessage message = (GameClientMessage) callback.getData(); + GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_TARGET", callback.getObjectId(), message); panel.pickTarget(message.getMessage(), message.getCardsView(), message.getGameView(), message.getTargets(), message.isFlag(), message.getOptions(), callback.getMessageId()); } @@ -217,8 +227,10 @@ public class CallbackClientImpl implements CallbackClient { } case GAME_SELECT: { GameClientMessage message = (GameClientMessage) callback.getData(); + GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_SELECT", callback.getObjectId(), message); panel.select(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions()); } break; @@ -226,6 +238,7 @@ public class CallbackClientImpl implements CallbackClient { case GAME_CHOOSE_ABILITY: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), callback.getData()); panel.pickAbility((AbilityPickerView) callback.getData()); } break; @@ -234,15 +247,18 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_CHOOSE_PILE", callback.getObjectId(), message); panel.pickPile(message.getMessage(), message.getPile1(), message.getPile2()); } break; } case GAME_CHOOSE_CHOICE: { GameClientMessage message = (GameClientMessage) callback.getData(); + GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_CHOOSE_CHOICE", callback.getObjectId(), message); panel.getChoice(message.getChoice(), callback.getObjectId()); } break; @@ -251,34 +267,44 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_PLAY_MANA", callback.getObjectId(), message); panel.playMana(message.getMessage(), message.getGameView(), message.getOptions(), callback.getMessageId()); } break; } case GAME_PLAY_XMANA: { GameClientMessage message = (GameClientMessage) callback.getData(); + GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_PLAY_XMANA", callback.getObjectId(), message); panel.playXMana(message.getMessage(), message.getGameView(), callback.getMessageId()); } break; } case GAME_GET_AMOUNT: { GameClientMessage message = (GameClientMessage) callback.getData(); + GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_GET_AMOUNT", callback.getObjectId(), message); + panel.getAmount(message.getMin(), message.getMax(), message.getMessage()); } break; } case GAME_UPDATE: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); + if (panel != null) { + appendJsonEvent("GAME_UPDATE", callback.getObjectId(), callback.getData()); + panel.updateGame((GameView) callback.getData()); } break; } case END_GAME_INFO: + appendJsonEvent("GAME_OVER", callback.getObjectId(), callback.getData()); MageFrame.getInstance().showGameEndDialog((GameEndView) callback.getData()); break; case SHOW_USERMESSAGE: @@ -293,6 +319,7 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { + appendJsonEvent("GAME_INFORM", callback.getObjectId(), message); panel.inform(message.getMessage(), message.getGameView(), callback.getMessageId()); } } @@ -375,7 +402,12 @@ public class CallbackClientImpl implements CallbackClient { } }); } - + private void appendJsonEvent(String name, UUID gameId, Object value) { + Session session = SessionHandler.getSession(); + ActionData actionData = new ActionData(name, gameId); + actionData.value = value; + session.appendJsonLog(actionData); + } private void createChatStartMessage(ChatPanelBasic chatPanel) { chatPanel.setStartMessageDone(true); ChatPanelBasic usedPanel = chatPanel; diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index 27914c196db..17075941d0c 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -25,6 +25,7 @@ jspf-core 0.9.1 + org.jboss.remoting jboss-remoting @@ -50,7 +51,11 @@ trove 1.0.2 - + + com.google.code.gson + gson + 2.8.2 + diff --git a/Mage.Common/src/main/java/mage/remote/ActionData.java b/Mage.Common/src/main/java/mage/remote/ActionData.java new file mode 100644 index 00000000000..4fdd32ef069 --- /dev/null +++ b/Mage.Common/src/main/java/mage/remote/ActionData.java @@ -0,0 +1,133 @@ +/* + * Copyright 2018 nanarpuss_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.remote; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import mage.remote.interfaces.*; + +import java.util.UUID; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +public class ActionData { + public UUID gameId; + public String sessionId; + public String type; + public Object value; + public String message; + + public String toJson() { + GsonBuilder gsonBuilder = new GsonBuilder(); + Gson gson = gsonBuilder.setExclusionStrategies(new CustomExclusionStrategy()).create(); + + return gson.toJson(this); + } + + public ActionData(String type, UUID gameId, String sessionId) { + this.type = type; + this.sessionId = sessionId; + this.gameId = gameId; + } + + public ActionData(String type, UUID gameId) { + this.type = type; + this.gameId = gameId; + } + + public class CustomExclusionStrategy implements ExclusionStrategy { + // FIXME: Very crude way of whitelisting, as it applies to all levels of the JSON tree. + private final java.util.Set KEEP = new java.util.HashSet( + java.util.Arrays.asList( + new String[]{ + "id", + "choice", + "damage", + "abilityType", + "ability", + "abilities", + "method", + "data", + "options", + "life", + "players", + "zone", + "step", + "phase", + "attackers", + "blockers", + "tapped", + "damage", + "combat", + "paid", + "hand", + "stack", + "convertedManaCost", + "gameId", + "canPlayInHand", + "gameView", + "sessionId", + "power", + "choices", + "targets", + "loyalty", + "toughness", + "power", + "type", + "priorityTime", + "manaCost", + "value", + "message", + "cardsView", + "name", + "count", + "counters", + "battlefield", + "parentId" + })); + + public CustomExclusionStrategy() {} + + // This method is called for all fields. if the method returns true the + // field is excluded from serialization + @Override + public boolean shouldSkipField(FieldAttributes f) { + String name = f.getName(); + return !KEEP.contains(name); + } + + // This method is called for all classes. If the method returns true the + // class is excluded. + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + } +} diff --git a/Mage.Common/src/main/java/mage/remote/Session.java b/Mage.Common/src/main/java/mage/remote/Session.java index 8cb062c63ef..67022d59e8d 100644 --- a/Mage.Common/src/main/java/mage/remote/Session.java +++ b/Mage.Common/src/main/java/mage/remote/Session.java @@ -38,6 +38,7 @@ import mage.remote.interfaces.PlayerActions; import mage.remote.interfaces.Replays; import mage.remote.interfaces.ServerState; import mage.remote.interfaces.Testable; +import mage.remote.ActionData; /** * Extracted interface for SessionImpl class. @@ -45,5 +46,5 @@ import mage.remote.interfaces.Testable; * @author noxx */ public interface Session extends ClientData, Connect, GamePlay, GameTypes, ServerState, ChatSession, Feedback, PlayerActions, Replays, Testable { - + public void appendJsonLog(ActionData actionData); } diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index 1f7bca2c0cb..907de603b08 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -32,6 +32,11 @@ import java.lang.reflect.UndeclaredThrowableException; import java.net.*; import java.util.*; import java.util.concurrent.TimeUnit; +import java.io.BufferedWriter; +import java.io.PrintWriter; +import java.io.FileWriter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import mage.MageException; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; @@ -50,6 +55,7 @@ import mage.interfaces.callback.ClientCallback; import mage.players.PlayerType; import mage.players.net.UserData; import mage.utils.CompressUtil; +import mage.remote.ActionData; import mage.view.*; import org.apache.log4j.Logger; import org.jboss.remoting.*; @@ -798,6 +804,9 @@ public class SessionImpl implements Session { public boolean sendPlayerUUID(UUID gameId, UUID data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_UUID", gameId, getSessionId()); + actionData.value = data; + appendJsonLog(actionData); server.sendPlayerUUID(gameId, sessionId, data); return true; } @@ -813,6 +822,10 @@ public class SessionImpl implements Session { public boolean sendPlayerBoolean(UUID gameId, boolean data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_BOOLEAN", gameId, getSessionId()); + actionData.value = data; + appendJsonLog(actionData); + server.sendPlayerBoolean(gameId, sessionId, data); return true; } @@ -828,6 +841,10 @@ public class SessionImpl implements Session { public boolean sendPlayerInteger(UUID gameId, int data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_INTEGER", gameId, getSessionId()); + actionData.value = data; + appendJsonLog(actionData); + server.sendPlayerInteger(gameId, sessionId, data); return true; } @@ -843,6 +860,10 @@ public class SessionImpl implements Session { public boolean sendPlayerString(UUID gameId, String data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_STRING", gameId, getSessionId()); + actionData.value = data; + appendJsonLog(actionData); + server.sendPlayerString(gameId, sessionId, data); return true; } @@ -858,6 +879,9 @@ public class SessionImpl implements Session { public boolean sendPlayerManaType(UUID gameId, UUID playerId, ManaType data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_MANA_TYPE", gameId, getSessionId()); + actionData.value = data; + appendJsonLog(actionData); server.sendPlayerManaType(gameId, playerId, sessionId, data); return true; } @@ -869,6 +893,19 @@ public class SessionImpl implements Session { return false; } + @Override + public void appendJsonLog(ActionData actionData) { + actionData.sessionId = getSessionId(); + + String logFileName = "game-" + actionData.gameId + ".json"; + System.out.println("Logging to " + logFileName); + try(PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(logFileName, true)))) { + out.println(actionData.toJson()); + } catch (IOException e) { + System.err.println(e); + } + } + @Override public DraftPickView sendCardPick(UUID draftId, UUID cardId, Set hiddenCards) { try { @@ -1274,6 +1311,11 @@ public class SessionImpl implements Session { public boolean sendPlayerAction(PlayerAction passPriorityAction, UUID gameId, Object data) { try { if (isConnected()) { + ActionData actionData = new ActionData("SEND_PLAYER_ACTION", gameId, getSessionId()); + + actionData.value = data; + appendJsonLog(actionData); + server.sendPlayerAction(passPriorityAction, gameId, sessionId, data); return true; } diff --git a/Mage.Common/src/main/java/mage/view/GameClientMessage.java b/Mage.Common/src/main/java/mage/view/GameClientMessage.java index 33af5e25e9a..8e8a8b93aa4 100644 --- a/Mage.Common/src/main/java/mage/view/GameClientMessage.java +++ b/Mage.Common/src/main/java/mage/view/GameClientMessage.java @@ -32,6 +32,9 @@ import java.io.Serializable; import java.util.Map; import java.util.Set; import java.util.UUID; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import mage.choices.Choice; /** @@ -155,4 +158,11 @@ public class GameClientMessage implements Serializable { return choice; } + public String toJson() { + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return gson.toJson(this); + } + } diff --git a/Mage.Common/src/main/java/mage/view/GameView.java b/Mage.Common/src/main/java/mage/view/GameView.java index 2e16415ad69..b354503c567 100644 --- a/Mage.Common/src/main/java/mage/view/GameView.java +++ b/Mage.Common/src/main/java/mage/view/GameView.java @@ -28,11 +28,17 @@ package mage.view; import java.io.Serializable; +import java.io.BufferedWriter; +import java.io.PrintWriter; +import java.io.FileWriter; +import java.io.IOException; + import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; + import mage.MageObject; import mage.abilities.costs.Cost; import mage.cards.Card; @@ -54,6 +60,8 @@ import mage.game.stack.StackObject; import mage.players.Player; import mage.watchers.common.CastSpellLastTurnWatcher; import org.apache.log4j.Logger; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; /** * @@ -64,7 +72,6 @@ public class GameView implements Serializable { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(GameView.class); - private final int priorityTime; private final List players = new ArrayList<>(); private CardsView hand; @@ -351,4 +358,8 @@ public class GameView implements Serializable { return rollbackTurnsAllowed; } + public String toJson() { + Gson gson = new GsonBuilder().create(); + return gson.toJson(this); + } }