queue = outQueueList.remove(0);
+
+ if (queue.isEmpty()) return null;
+
+ byte[] message = queue.remove(0);
+ if (!queue.isEmpty()) {
+ outQueueList.add(queue);
+ }
+ return message;
}
public final void addMessageHandler(MessageHandler handler) {
@@ -99,10 +113,9 @@ public final void removeMessageHandler(MessageHandler handler) {
messageHandlers.remove(handler);
}
- protected void dispatchMessage(byte[] message) {
- var id = getId();
- if (messageHandlers.isEmpty()) {
- log.warn("message received but not messageHandlers registered for {}.", id);
+ private void dispatchMessage(String id, byte[] message) {
+ if (messageHandlers.size() == 0) {
+ log.warn("message received but not messageHandlers registered.");
}
for (MessageHandler handler : messageHandlers) {
@@ -110,9 +123,9 @@ protected void dispatchMessage(byte[] message) {
}
}
- protected final void dispatchCompressedMessage(byte[] compressedMessage) {
+ protected final void dispatchCompressedMessage(String id, byte[] compressedMessage) {
var message = inflate(compressedMessage);
- dispatchMessage(message);
+ dispatchMessage(id, message);
}
protected final void writeMessage(OutputStream out, byte[] message) throws IOException {
@@ -226,7 +239,7 @@ public final void removeActivityListener(ActivityListener listener) {
listeners.remove(listener);
}
- protected void notifyListeners(
+ private void notifyListeners(
ActivityListener.Direction direction,
ActivityListener.State state,
int totalTransferSize,
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/connection/Connection.java b/src/main/java/net/rptools/clientserver/simple/connection/Connection.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/connection/Connection.java
rename to src/main/java/net/rptools/clientserver/simple/connection/Connection.java
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java b/src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java
similarity index 51%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java
rename to src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java
index 42124691c5..418c8d7625 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java
+++ b/src/main/java/net/rptools/clientserver/simple/connection/SocketConnection.java
@@ -16,12 +16,13 @@
import java.io.*;
import java.net.Socket;
-import java.net.SocketTimeoutException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* @author drice
+ * TODO To change the template for this generated type comment go to Window - Preferences -
+ * Java - Code Style - Code Templates
*/
public class SocketConnection extends AbstractConnection implements Connection {
/** Instance used for log messages. */
@@ -34,56 +35,61 @@ public class SocketConnection extends AbstractConnection implements Connection {
private String hostName;
private int port;
- public SocketConnection(String id, String hostName, int port) {
+ public SocketConnection(String id, String hostName, int port) throws IOException {
this.id = id;
this.hostName = hostName;
this.port = port;
}
- public SocketConnection(String id, Socket socket) {
+ public SocketConnection(String id, Socket socket) throws IOException {
this.id = id;
- this.socket = socket;
-
+ this.hostName = socket.getInetAddress().getHostName();
+ this.port = socket.getPort();
initialize(socket);
}
- @Override
- public String getId() {
- return id;
- }
-
- private void initialize(Socket socket) {
+ private void initialize(Socket socket) throws IOException {
this.socket = socket;
- this.send = new SendThread(socket);
- this.receive = new ReceiveThread(socket);
-
+ this.send = new SendThread(new BufferedOutputStream(socket.getOutputStream()));
+ this.receive = new ReceiveThread(this, socket.getInputStream());
this.send.start();
this.receive.start();
}
+ public String getId() {
+ return id;
+ }
+
@Override
public void open() throws IOException {
initialize(new Socket(hostName, port));
}
- @Override
public void sendMessage(Object channel, byte[] message) {
addMessage(channel, message);
+ synchronized (send) {
+ send.notify();
+ }
}
- @Override
- protected void onClose() {
- receive.interrupt();
- send.interrupt();
+ protected boolean isStopRequested() {
+ return send.stopRequested;
+ }
+
+ public synchronized void close() {
+ if (isStopRequested()) {
+ return;
+ }
+ send.requestStop();
+ receive.requestStop();
try {
socket.close();
} catch (IOException e) {
- log.warn("Failed to close socket", e);
+ log.warn(e.toString());
}
}
- @Override
public boolean isAlive() {
return !socket.isClosed();
}
@@ -97,41 +103,49 @@ public String getError() {
// send thread
// /////////////////////////////////////////////////////////////////////////
private class SendThread extends Thread {
- private final Socket socket;
+ private final OutputStream out;
+ private boolean stopRequested = false;
- public SendThread(Socket socket) {
+ public SendThread(OutputStream out) {
setName("SocketConnection.SendThread");
- this.socket = socket;
+ this.out = out;
+ }
+
+ public void requestStop() {
+ this.stopRequested = true;
+ synchronized (this) {
+ this.notify();
+ }
}
@Override
public void run() {
try {
- final OutputStream out;
- try {
- out = new BufferedOutputStream(socket.getOutputStream());
- } catch (IOException e) {
- log.error("Unable to get socket output stream", e);
- return;
- }
-
- while (!SocketConnection.this.isClosed() && SocketConnection.this.isAlive()) {
- // Blocks for a time until a message is received.
- byte[] message = SocketConnection.this.nextMessage();
- if (message == null) {
- // No message available. Thread may also have been interrupted as part of stopping.
- continue;
- }
-
+ while (!stopRequested && SocketConnection.this.isAlive()) {
try {
- SocketConnection.this.writeMessage(out, message);
- } catch (IOException e) {
- log.error("Error while writing message. Closing connection.", e);
- return;
+ while (SocketConnection.this.hasMoreMessages()) {
+ try {
+ byte[] message = SocketConnection.this.nextMessage();
+ if (message == null) {
+ continue;
+ }
+ SocketConnection.this.writeMessage(out, message);
+ } catch (IndexOutOfBoundsException e) {
+ // just ignore and wait
+ }
+ }
+ synchronized (this) {
+ if (!stopRequested) {
+ this.wait();
+ }
+ }
+ } catch (InterruptedException e) {
+ // do nothing
}
}
- } finally {
- SocketConnection.this.close();
+ } catch (IOException e) {
+ log.error(e);
+ fireDisconnect();
}
}
}
@@ -140,42 +154,35 @@ public void run() {
// receive thread
// /////////////////////////////////////////////////////////////////////////
private class ReceiveThread extends Thread {
- private final Socket socket;
+ private final SocketConnection conn;
+ private final InputStream in;
+ private boolean stopRequested = false;
- public ReceiveThread(Socket socket) {
+ public ReceiveThread(SocketConnection conn, InputStream in) {
setName("SocketConnection.ReceiveThread");
- this.socket = socket;
+ this.conn = conn;
+ this.in = in;
+ }
+
+ public void requestStop() {
+ stopRequested = true;
}
@Override
public void run() {
- try {
- final InputStream in;
+ while (!stopRequested && conn.isAlive()) {
try {
- in = socket.getInputStream();
+ byte[] message = conn.readMessage(in);
+ conn.dispatchCompressedMessage(conn.id, message);
} catch (IOException e) {
- log.error("Unable to get socket input stream", e);
- return;
+ log.error(e);
+ fireDisconnect();
+ break;
+ } catch (Throwable t) {
+ log.error(t);
+ // don't let anything kill this thread via exception
+ t.printStackTrace();
}
-
- while (!SocketConnection.this.isClosed() && SocketConnection.this.isAlive()) {
- try {
- byte[] message = SocketConnection.this.readMessage(in);
- SocketConnection.this.dispatchCompressedMessage(message);
- } catch (SocketTimeoutException e) {
- log.warn("Lost client {}", SocketConnection.this.getId(), e);
- return;
- } catch (IOException e) {
- log.error(e);
- return;
- } catch (Throwable t) {
- // don't let anything kill this thread via exception
- log.error("Unexpected error", t);
- }
- }
- } finally {
- SocketConnection.this.close();
- fireDisconnect();
}
}
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java b/src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java
similarity index 56%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java
rename to src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java
index 124c6d732b..3c71a080bb 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java
+++ b/src/main/java/net/rptools/clientserver/simple/connection/WebRTCConnection.java
@@ -20,28 +20,23 @@
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
import net.rptools.clientserver.simple.server.WebRTCServer;
import net.rptools.clientserver.simple.webrtc.*;
+import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.server.ServerConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
-public class WebRTCConnection extends AbstractConnection implements Connection {
- public interface Listener {
- void onLoginError();
- }
-
+public class WebRTCConnection extends AbstractConnection
+ implements Connection, PeerConnectionObserver, RTCDataChannelObserver {
private static final Logger log = LogManager.getLogger(WebRTCConnection.class);
- private final PeerConnectionObserver peerConnectionObserver = new PeerConnectionObserverImpl();
- private final RTCDataChannelObserver rtcDataChannelObserver = new RTCDataChannelObserverImpl();
private final PeerConnectionFactory factory = new PeerConnectionFactory();
- private final String serverName;
+ private final ServerConfig config;
private final String id;
private final Gson gson = new Gson();
- private final Listener listener;
private WebSocketClient signalingClient;
// only set on server side
private WebRTCServer server;
@@ -53,13 +48,10 @@ public interface Listener {
private final SendThread sendThread = new SendThread();
private Thread handleDisconnect;
- private final AtomicBoolean closed = new AtomicBoolean(false);
-
// used from client side
- public WebRTCConnection(String id, String serverName, Listener listener) {
+ public WebRTCConnection(String id, ServerConfig config) {
this.id = id;
- this.serverName = serverName;
- this.listener = listener;
+ this.config = config;
init();
}
@@ -67,12 +59,11 @@ public WebRTCConnection(String id, String serverName, Listener listener) {
public WebRTCConnection(OfferMessageDto message, WebRTCServer webRTCServer) {
this.id = message.source;
this.server = webRTCServer;
- this.serverName = server.getName();
- this.listener = () -> {};
+ this.config = server.getConfig();
this.signalingClient = server.getSignalingClient();
init();
- peerConnection = factory.createPeerConnection(rtcConfig, peerConnectionObserver);
+ peerConnection = factory.createPeerConnection(rtcConfig, this);
peerConnection.setRemoteDescription(
message.offer,
new SetSessionDescriptionObserver() {
@@ -99,7 +90,7 @@ public void onSuccess(RTCSessionDescription description) {
@Override
public void onSuccess() {
var msg = new AnswerMessageDto();
- msg.source = serverName;
+ msg.source = server.getConfig().getServerName();
msg.destination = getId();
msg.answer = description;
sendSignalingMessage(gson.toJson(msg));
@@ -125,11 +116,9 @@ private boolean isServerSide() {
private String getSource() {
// on server side the id is already user@server
- if (isServerSide()) {
- return getId();
- }
+ if (isServerSide()) return getId();
- return getId() + "@" + serverName;
+ return getId() + "@" + config.getServerName();
}
private void startSignaling() {
@@ -158,9 +147,7 @@ public void onMessage(String message) {
public void onClose(int code, String reason, boolean remote) {
lastError = "WebSocket closed: (" + code + ") " + reason;
log.info(prefix() + lastError);
- if (!isAlive()) {
- fireDisconnectAsync();
- }
+ if (!isAlive()) fireDisconnectAsync();
}
@Override
@@ -211,13 +198,17 @@ private void init() {
public void sendMessage(Object channel, byte[] message) {
log.debug(prefix() + "added message");
addMessage(channel, message);
+ if (peerConnection != null
+ && peerConnection.getConnectionState() == RTCPeerConnectionState.CONNECTED) {
+ synchronized (sendThread) {
+ sendThread.notify();
+ }
+ }
}
@Override
public boolean isAlive() {
- if (peerConnection == null) {
- return false;
- }
+ if (peerConnection == null) return false;
return switch (peerConnection.getConnectionState()) {
case CONNECTED, DISCONNECTED -> true;
@@ -278,15 +269,15 @@ public void onFailure(String error) {
private void onLogin(LoginMessageDto message) {
if (!message.success) {
- listener.onLoginError();
+ MapTool.showError("Handshake.msg.playerAlreadyConnected");
return;
}
- peerConnection = factory.createPeerConnection(rtcConfig, peerConnectionObserver);
+ peerConnection = factory.createPeerConnection(rtcConfig, this);
var initDict = new RTCDataChannelInit();
localDataChannel = peerConnection.createDataChannel("myDataChannel", initDict);
- localDataChannel.registerObserver(rtcDataChannelObserver);
+ localDataChannel.registerObserver(this);
var offerOptions = new RTCOfferOptions();
peerConnection.createOffer(
@@ -302,7 +293,7 @@ public void onSuccess() {
var msg = new OfferMessageDto();
msg.offer = description;
msg.source = getSource();
- msg.destination = serverName;
+ msg.destination = config.getServerName();
sendSignalingMessage(gson.toJson(msg));
}
@@ -324,253 +315,274 @@ private String prefix() {
return isServerSide() ? "S " : "C ";
}
- public void addIceCandidate(RTCIceCandidate candidate) {
- log.info(prefix() + "PeerConnection.addIceCandidate: " + candidate.toString());
- peerConnection.addIceCandidate(candidate);
+ @Override
+ public void onSignalingChange(RTCSignalingState state) {
+ // set thread name for better logs.
+ Thread.currentThread().setName("WebRTCConnection.WebRTCThread_" + getId());
+ log.info(prefix() + "PeerConnection.onSignalingChange: " + state);
}
- private void fireDisconnectAsync() {
- handleDisconnect =
- new Thread(
- () -> {
- fireDisconnect();
- },
- "WebRTCConnection.handleDisconnect");
- handleDisconnect.start();
+ @Override
+ public void onConnectionChange(RTCPeerConnectionState state) {
+ log.info(prefix() + "PeerConnection.onConnectionChange " + state);
+ switch (state) {
+ case FAILED -> {
+ lastError = "PeerConnection failed";
+ peerConnection = null;
+ fireDisconnectAsync();
+ }
+ case CONNECTED -> {
+ if (hasMoreMessages()) {
+ synchronized (sendThread) {
+ sendThread.notify();
+ }
+ }
+ }
+ }
}
@Override
- protected void onClose() {
- // signalingClient should be closed if connection was established
- if (!isServerSide() && signalingClient.isOpen()) {
- signalingClient.close();
- }
+ public void onIceConnectionChange(RTCIceConnectionState state) {
+ log.info(prefix() + "PeerConnection.onIceConnectionChange " + state);
+ }
- sendThread.interrupt();
- if (peerConnection != null) {
- peerConnection.close();
- peerConnection = null;
- }
+ @Override
+ public void onStandardizedIceConnectionChange(RTCIceConnectionState state) {
+ log.info(prefix() + "PeerConnection.onStandardizedIceConnectionChange " + state);
}
@Override
- public String getError() {
- return lastError;
+ public void onIceConnectionReceivingChange(boolean receiving) {
+ log.info(prefix() + "PeerConnection.onIceConnectionReceivingChange " + receiving);
}
- private class SendThread extends Thread {
- public SendThread() {
- super("WebRTCConnection.SendThread_" + WebRTCConnection.this.getId());
- }
+ @Override
+ public void onIceGatheringChange(RTCIceGatheringState state) {
+ log.info(prefix() + "PeerConnection.onIceGatheringChange " + state);
+ }
- @Override
- public void run() {
- log.debug(prefix() + " sendThread started");
+ @Override
+ public void onIceCandidate(RTCIceCandidate candidate) {
+ var msg = new CandidateMessageDto();
- while (!WebRTCConnection.this.isClosed() && WebRTCConnection.this.isAlive()) {
- // Blocks for a time until a message is received.
- byte[] message = WebRTCConnection.this.nextMessage();
- if (message == null) {
- // No message available. Thread may also have been interrupted as part of stopping.
- continue;
- }
+ if (isServerSide()) {
+ msg.source = config.getServerName();
+ msg.destination = getSource();
+ } else {
+ msg.destination = config.getServerName();
+ msg.source = getSource();
+ }
+ msg.candidate = candidate;
+ sendSignalingMessage(gson.toJson(msg));
+ }
- ByteBuffer buffer = ByteBuffer.allocate(message.length + Integer.BYTES);
- buffer.putInt(message.length).put(message).rewind();
+ @Override
+ public void onIceCandidateError(RTCPeerConnectionIceErrorEvent event) {
+ log.debug(
+ prefix()
+ + "PeerConnection.onIceCandidateError: code:"
+ + event.getErrorCode()
+ + " url: "
+ + event.getUrl()
+ + " address/port: "
+ + event.getAddress()
+ + ":"
+ + event.getPort()
+ + " text: "
+ + event.getErrorText());
+ }
- int chunkSize = 16 * 1024;
+ @Override
+ public void onIceCandidatesRemoved(RTCIceCandidate[] candidates) {
+ log.info(prefix() + "PeerConnection.onIceCandidatesRemoved");
+ }
- while (buffer.remaining() > 0) {
- var amountToSend = Math.min(buffer.remaining(), chunkSize);
- ByteBuffer part = buffer;
+ @Override
+ public void onAddStream(MediaStream stream) {
+ log.info(prefix() + "PeerConnection.onAddStream");
+ }
- if (amountToSend != buffer.capacity()) {
- // we need to allocation a new ByteBuffer because send calls ByteBuffer.array()
- // which would return
- // the whole byte[] and not only the slice. But the lib doesn't use
- // ByteBuffer.arrayOffset().
- var slice = buffer.slice(buffer.position(), amountToSend);
- part = ByteBuffer.allocate(amountToSend);
- part.put(slice);
- }
+ @Override
+ public void onRemoveStream(MediaStream stream) {
+ log.info(prefix() + "PeerConnection.onRemoveStream");
+ }
- buffer.position(buffer.position() + amountToSend);
- try {
- localDataChannel.send(new RTCDataChannelBuffer(part, true));
- } catch (Exception e) {
- log.error(prefix() + e);
- fireDisconnect();
- return;
- }
- log.debug(prefix() + " sent " + part.capacity() + " bytes");
- }
- }
+ @Override
+ public void onDataChannel(RTCDataChannel newDataChannel) {
+ log.info(prefix() + "PeerConnection.onDataChannel");
+ this.localDataChannel = newDataChannel;
+ localDataChannel.registerObserver(this);
- log.debug(prefix() + " sendThread ended");
+ if (isServerSide()) {
+ server.onDataChannelOpened(this);
}
}
- private final class PeerConnectionObserverImpl implements PeerConnectionObserver {
- @Override
- public void onIceCandidate(RTCIceCandidate candidate) {
- var msg = new CandidateMessageDto();
-
- if (isServerSide()) {
- msg.source = serverName;
- msg.destination = getSource();
- } else {
- msg.destination = serverName;
- msg.source = getSource();
- }
- msg.candidate = candidate;
- sendSignalingMessage(gson.toJson(msg));
- }
+ @Override
+ public void onRenegotiationNeeded() {
+ // set thread name for better logs
+ Thread.currentThread().setName("WebRTCConnection.WebRTCThread_" + getId());
+ log.info(prefix() + "PeerConnection.onRenegotiationNeeded");
+ }
- @Override
- public void onAddStream(MediaStream stream) {
- log.info(prefix() + "PeerConnection.onAddStream");
- }
+ @Override
+ public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) {
+ log.info(prefix() + "PeerConnection.onTrack(multiple Streams)");
+ }
- @Override
- public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) {
- log.info(prefix() + "PeerConnection.onTrack(multiple Streams)");
- }
+ @Override
+ public void onRemoveTrack(RTCRtpReceiver receiver) {
+ log.info(prefix() + "PeerConnection.onRemoveTrack");
+ }
- @Override
- public void onConnectionChange(RTCPeerConnectionState state) {
- log.info(prefix() + "PeerConnection.onConnectionChange " + state);
- switch (state) {
- case FAILED -> {
- lastError = "PeerConnection failed";
- peerConnection = null;
- fireDisconnectAsync();
- }
- }
- }
+ @Override
+ public void onTrack(RTCRtpTransceiver transceiver) {
+ log.info(prefix() + "PeerConnection.onTrack");
+ }
- @Override
- public void onDataChannel(RTCDataChannel newDataChannel) {
- log.info(prefix() + "PeerConnection.onDataChannel");
- localDataChannel = newDataChannel;
- localDataChannel.registerObserver(rtcDataChannelObserver);
+ public void addIceCandidate(RTCIceCandidate candidate) {
+ log.info(prefix() + "PeerConnection.addIceCandidate: " + candidate.toString());
+ peerConnection.addIceCandidate(candidate);
+ }
- if (isServerSide()) {
- server.onDataChannelOpened(WebRTCConnection.this);
+ // dataChannel
+ @Override
+ public void onBufferedAmountChange(long previousAmount) {
+ log.info(prefix() + "dataChannel onBufferedAmountChange " + previousAmount);
+ }
+
+ // dataChannel
+ @Override
+ public void onStateChange() {
+ var state = localDataChannel.getState();
+ log.info(prefix() + "localDataChannel onStateChange " + state);
+ switch (state) {
+ case OPEN -> {
+ // connection established we don't need the signaling server anymore
+ // for now disabled. We may get additional ice candidates.
+ if (!isServerSide() && signalingClient.isOpen()) signalingClient.close();
+
+ sendThread.start();
+ }
+ case CLOSED -> {
+ close();
+ fireDisconnectAsync();
}
}
+ }
- @Override
- public void onIceCandidateError(RTCPeerConnectionIceErrorEvent event) {
- log.debug(
- prefix()
- + "PeerConnection.onIceCandidateError: code:"
- + event.getErrorCode()
- + " url: "
- + event.getUrl()
- + " address/port: "
- + event.getAddress()
- + ":"
- + event.getPort()
- + " text: "
- + event.getErrorText());
- }
+ // dataChannel
+ @Override
+ public void onMessage(RTCDataChannelBuffer channelBuffer) {
+ log.debug(
+ prefix() + "localDataChannel onMessage: got " + channelBuffer.data.capacity() + " bytes");
- @Override
- public void onIceCandidatesRemoved(RTCIceCandidate[] candidates) {
- log.info(prefix() + "PeerConnection.onIceCandidatesRemoved");
+ if (Thread.currentThread().getContextClassLoader() == null) {
+ ClassLoader cl = ClassLoader.getSystemClassLoader();
+ Thread.currentThread().setContextClassLoader(cl);
}
- @Override
- public void onIceConnectionChange(RTCIceConnectionState state) {
- log.info(prefix() + "PeerConnection.onIceConnectionChange " + state);
- }
+ var message = readMessage(channelBuffer.data);
+ if (message != null) dispatchCompressedMessage(id, message);
+ }
- @Override
- public void onIceConnectionReceivingChange(boolean receiving) {
- log.info(prefix() + "PeerConnection.onIceConnectionReceivingChange " + receiving);
- }
+ private void fireDisconnectAsync() {
+ handleDisconnect =
+ new Thread(
+ () -> {
+ fireDisconnect();
+ if (isServerSide()) server.clearClients();
+ },
+ "WebRTCConnection.handleDisconnect");
+ handleDisconnect.start();
+ }
- @Override
- public void onIceGatheringChange(RTCIceGatheringState state) {
- log.info(prefix() + "PeerConnection.onIceGatheringChange " + state);
- }
+ @Override
+ public void close() {
+ // signalingClient should be closed if connection was established
+ if (!isServerSide() && signalingClient.isOpen()) signalingClient.close();
- @Override
- public void onRemoveStream(MediaStream stream) {
- log.info(prefix() + "PeerConnection.onRemoveStream");
- }
+ if (sendThread.stopRequested) return;
- @Override
- public void onRemoveTrack(RTCRtpReceiver receiver) {
- log.info(prefix() + "PeerConnection.onRemoveTrack");
+ sendThread.requestStop();
+ if (peerConnection != null) {
+ peerConnection.close();
+ peerConnection = null;
}
+ }
- @Override
- public void onRenegotiationNeeded() {
- // set thread name for better logs
- Thread.currentThread().setName("WebRTCConnection.WebRTCThread_" + getId());
- log.info(prefix() + "PeerConnection.onRenegotiationNeeded");
- }
+ @Override
+ public String getError() {
+ return lastError;
+ }
- @Override
- public void onSignalingChange(RTCSignalingState state) {
- // set thread name for better logs.
- Thread.currentThread().setName("WebRTCConnection.WebRTCThread_" + getId());
- log.info(prefix() + "PeerConnection.onSignalingChange: " + state);
- }
+ private class SendThread extends Thread {
+ private boolean stopRequested = false;
- @Override
- public void onStandardizedIceConnectionChange(RTCIceConnectionState state) {
- log.info(prefix() + "PeerConnection.onStandardizedIceConnectionChange " + state);
+ public SendThread() {
+ super("WebRTCConnection.SendThread_" + WebRTCConnection.this.getId());
}
- @Override
- public void onTrack(RTCRtpTransceiver transceiver) {
- log.info(prefix() + "PeerConnection.onTrack");
+ public void requestStop() {
+ this.stopRequested = true;
+ synchronized (this) {
+ this.notify();
+ }
}
- }
- private final class RTCDataChannelObserverImpl implements RTCDataChannelObserver {
@Override
- public void onBufferedAmountChange(long previousAmount) {
- log.info(prefix() + "dataChannel onBufferedAmountChange " + previousAmount);
- }
+ public void run() {
+ log.debug(prefix() + " sendThread started");
+ try {
+ while (!stopRequested && WebRTCConnection.this.isAlive()) {
+ while (WebRTCConnection.this.hasMoreMessages()
+ && peerConnection.getConnectionState() == RTCPeerConnectionState.CONNECTED) {
+ byte[] message = WebRTCConnection.this.nextMessage();
+ if (message == null) {
+ continue;
+ }
- @Override
- public void onStateChange() {
- var state = localDataChannel.getState();
- log.info(prefix() + "localDataChannel onStateChange " + state);
- switch (state) {
- case OPEN -> {
- // connection established we don't need the signaling server anymore
- // for now disabled. We may get additional ice candidates.
- if (!isServerSide() && signalingClient.isOpen()) {
- signalingClient.close();
- }
+ ByteBuffer buffer = ByteBuffer.allocate(message.length + Integer.BYTES);
+ buffer.putInt(message.length).put(message).rewind();
- sendThread.start();
- }
- case CLOSED -> {
- close();
- fireDisconnectAsync();
- }
- }
- }
+ int chunkSize = 16 * 1024;
- @Override
- public void onMessage(RTCDataChannelBuffer channelBuffer) {
- log.debug(
- prefix() + "localDataChannel onMessage: got " + channelBuffer.data.capacity() + " bytes");
+ while (buffer.remaining() > 0) {
+ var amountToSend = buffer.remaining() <= chunkSize ? buffer.remaining() : chunkSize;
+ ByteBuffer part = buffer;
- if (Thread.currentThread().getContextClassLoader() == null) {
- ClassLoader cl = ClassLoader.getSystemClassLoader();
- Thread.currentThread().setContextClassLoader(cl);
- }
+ if (amountToSend != buffer.capacity()) {
+ // we need to allocation a new ByteBuffer because send calls ByteBuffer.array()
+ // which would return
+ // the whole byte[] and not only the slice. But the lib doesn't use
+ // ByteBuffer.arrayOffset().
+ var slice = buffer.slice(buffer.position(), amountToSend);
+ part = ByteBuffer.allocate(amountToSend);
+ part.put(slice);
+ }
- var message = readMessage(channelBuffer.data);
- if (message != null) {
- dispatchCompressedMessage(message);
+ buffer.position(buffer.position() + amountToSend);
+ localDataChannel.send(new RTCDataChannelBuffer(part, true));
+ log.debug(prefix() + " sent " + part.capacity() + " bytes");
+ }
+ }
+ synchronized (this) {
+ if (!stopRequested) {
+ try {
+ log.debug(prefix() + "sendThread -> sleep");
+ this.wait();
+ log.debug(prefix() + "sendThread -> woke up");
+ } catch (InterruptedException e) {
+ log.debug(prefix() + "sendThread -> interrupted");
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error(prefix() + e);
+ fireDisconnect();
}
+ log.debug(prefix() + " sendThread ended");
}
}
}
diff --git a/src/main/java/net/rptools/clientserver/simple/server/AbstractServer.java b/src/main/java/net/rptools/clientserver/simple/server/AbstractServer.java
new file mode 100644
index 0000000000..b966028f2a
--- /dev/null
+++ b/src/main/java/net/rptools/clientserver/simple/server/AbstractServer.java
@@ -0,0 +1,162 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.clientserver.simple.server;
+
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+import net.rptools.clientserver.simple.DisconnectHandler;
+import net.rptools.clientserver.simple.MessageHandler;
+import net.rptools.clientserver.simple.connection.Connection;
+import net.rptools.maptool.server.Handshake;
+import net.rptools.maptool.server.HandshakeObserver;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public abstract class AbstractServer implements DisconnectHandler, Server, HandshakeObserver {
+
+ private static final Logger log = LogManager.getLogger(AbstractServer.class);
+ // private final ReaperThread reaperThread;
+
+ private final Map clients =
+ Collections.synchronizedMap(new HashMap());
+ private final List observerList =
+ Collections.synchronizedList(new ArrayList());
+
+ private final HandshakeProvider handshakeProvider;
+ private final MessageHandler messageHandler;
+
+ public AbstractServer(HandshakeProvider handshakeProvider, MessageHandler messageHandler) {
+ this.handshakeProvider = handshakeProvider;
+ this.messageHandler = messageHandler;
+ }
+
+ public void addObserver(ServerObserver observer) {
+ observerList.add(observer);
+ }
+
+ public void removeObserver(ServerObserver observer) {
+ observerList.remove(observer);
+ }
+
+ public void broadcastMessage(byte[] message) {
+ synchronized (clients) {
+ for (Connection conn : clients.values()) {
+ conn.sendMessage(message);
+ }
+ }
+ }
+
+ public void broadcastMessage(String[] exclude, byte[] message) {
+ Set excludeSet = new HashSet();
+ for (String e : exclude) {
+ excludeSet.add(e);
+ }
+ synchronized (clients) {
+ for (Map.Entry entry : clients.entrySet()) {
+ if (!excludeSet.contains(entry.getKey())) {
+ entry.getValue().sendMessage(message);
+ }
+ }
+ }
+ }
+
+ public void sendMessage(String id, Object channel, byte[] message) {
+ Connection client = clients.get(id);
+ client.sendMessage(channel, message);
+ }
+
+ public void close() {
+ synchronized (clients) {
+ for (Connection conn : clients.values()) {
+ conn.close();
+ }
+ }
+ }
+
+ protected void reapClients() {
+ log.debug("About to reap clients");
+ synchronized (clients) {
+ log.debug("Reaping clients");
+
+ for (Iterator> i = clients.entrySet().iterator();
+ i.hasNext(); ) {
+ Map.Entry entry = i.next();
+ Connection conn = entry.getValue();
+ if (!conn.isAlive()) {
+ log.debug("\tReaping: " + conn.getId());
+ i.remove();
+ try {
+ fireClientDisconnect(conn);
+ conn.close();
+ } catch (Exception e) {
+ // Don't want to raise an error if notification of removing a dead connection failed
+ }
+ }
+ }
+ }
+ }
+
+ protected void fireClientConnect(Connection conn) {
+ log.debug("Firing: clientConnect: " + conn.getId());
+ for (ServerObserver observer : observerList) {
+ observer.connectionAdded(conn);
+ }
+ }
+
+ protected void fireClientDisconnect(Connection conn) {
+ log.debug("Firing: clientDisconnect: " + conn.getId());
+ for (ServerObserver observer : observerList) {
+ observer.connectionRemoved(conn);
+ }
+ }
+
+ ////
+ // DISCONNECT HANDLER
+ public void handleDisconnect(Connection conn) {
+ log.debug("HandleDisconnect: " + conn.getId());
+ fireClientDisconnect(conn);
+ }
+
+ protected void handleConnection(Connection conn) throws ExecutionException, InterruptedException {
+ var handshake = handshakeProvider.getConnectionHandshake(conn);
+ handshake.addObserver(this);
+ // Make sure the client is allowed
+ handshake.startHandshake();
+ }
+
+ public void onCompleted(Handshake handshake) {
+ handshake.removeObserver(this);
+ var conn = handshake.getConnection();
+ handshakeProvider.releaseHandshake(conn);
+ if (handshake.isSuccessful()) {
+ conn.addMessageHandler(messageHandler);
+ conn.addDisconnectHandler(this);
+
+ log.debug("About to add new client");
+ synchronized (clients) {
+ reapClients();
+
+ log.debug("Adding new client");
+ clients.put(conn.getId(), conn);
+ fireClientConnect(conn);
+ // System.out.println("new client " + conn.getId() + " added, " + server.clients.size()
+ // + " total");
+ }
+ } else {
+ log.debug("Client closing: bad handshake");
+ conn.close();
+ }
+ }
+}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/server/NilServer.java b/src/main/java/net/rptools/clientserver/simple/server/HandshakeProvider.java
similarity index 73%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/server/NilServer.java
rename to src/main/java/net/rptools/clientserver/simple/server/HandshakeProvider.java
index ad9b76bec3..e9444833fb 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/server/NilServer.java
+++ b/src/main/java/net/rptools/clientserver/simple/server/HandshakeProvider.java
@@ -14,11 +14,11 @@
*/
package net.rptools.clientserver.simple.server;
-/** A server implementation that never receives connections */
-public class NilServer extends AbstractServer {
- @Override
- public void start() {}
+import net.rptools.clientserver.simple.connection.Connection;
+import net.rptools.maptool.server.Handshake;
- @Override
- public void close() {}
+public interface HandshakeProvider {
+ Handshake getConnectionHandshake(Connection conn);
+
+ void releaseHandshake(Connection conn);
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/server/Server.java b/src/main/java/net/rptools/clientserver/simple/server/Server.java
similarity index 77%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/server/Server.java
rename to src/main/java/net/rptools/clientserver/simple/server/Server.java
index 00b379a7d9..171e8a57e6 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/server/Server.java
+++ b/src/main/java/net/rptools/clientserver/simple/server/Server.java
@@ -24,4 +24,14 @@ public interface Server extends AutoCloseable {
void addObserver(ServerObserver observer);
void removeObserver(ServerObserver observer);
+
+ void broadcastMessage(byte[] message);
+
+ void broadcastMessage(String[] exclude, byte[] message);
+
+ default void sendMessage(String id, byte[] message) {
+ sendMessage(id, null, message);
+ }
+
+ void sendMessage(String id, Object channel, byte[] message);
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java b/src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java
similarity index 94%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java
rename to src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java
index 7dcdcdee8c..7ea52d9cc4 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java
+++ b/src/main/java/net/rptools/clientserver/simple/server/ServerObserver.java
@@ -20,4 +20,6 @@
public interface ServerObserver {
public void connectionAdded(Connection conn);
+
+ public void connectionRemoved(Connection conn);
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/server/SocketServer.java b/src/main/java/net/rptools/clientserver/simple/server/SocketServer.java
similarity index 84%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/server/SocketServer.java
rename to src/main/java/net/rptools/clientserver/simple/server/SocketServer.java
index 2b4c0ac9c5..633a61ebb3 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/server/SocketServer.java
+++ b/src/main/java/net/rptools/clientserver/simple/server/SocketServer.java
@@ -17,7 +17,8 @@
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutionException;
+import net.rptools.clientserver.simple.MessageHandler;
import net.rptools.clientserver.simple.connection.SocketConnection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -32,22 +33,21 @@ public class SocketServer extends AbstractServer {
private ServerSocket socket;
private ListeningThread listeningThread;
- public SocketServer(int port) {
+ public SocketServer(int port, HandshakeProvider handshake, MessageHandler messageHandler) {
+ super(handshake, messageHandler);
this.port = port;
}
@Override
public void start() throws IOException {
- var serverSocket = new ServerSocket(port);
- // If the above throws, it will be as though we never started.
-
- socket = serverSocket;
+ socket = new ServerSocket(port);
listeningThread = new ListeningThread(this, socket);
listeningThread.start();
}
@Override
public void close() {
+ super.close();
listeningThread.suppressErrors();
log.debug("Server closing down");
@@ -104,15 +104,12 @@ public void run() {
while (!stopRequested) {
try {
Socket s = socket.accept();
- // Client heartbeat frequency is 20 seconds, so a minute should permit two or three
- // heartbeats to come in if still connected.
- s.setSoTimeout((int) TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES));
log.debug("Client connecting ...");
String id = nextClientId(s);
SocketConnection conn = new SocketConnection(id, s);
- server.fireClientConnect(conn);
- } catch (IOException e) {
+ server.handleConnection(conn);
+ } catch (IOException | ExecutionException | InterruptedException e) {
if (!suppressErrors) {
log.error(e.getMessage(), e);
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java b/src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java
similarity index 87%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java
rename to src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java
index 32ad0b8c7b..5954661f99 100644
--- a/clientserver/src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java
+++ b/src/main/java/net/rptools/clientserver/simple/server/WebRTCServer.java
@@ -19,11 +19,14 @@
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
+import net.rptools.clientserver.simple.MessageHandler;
import net.rptools.clientserver.simple.connection.WebRTCConnection;
import net.rptools.clientserver.simple.webrtc.CandidateMessageDto;
import net.rptools.clientserver.simple.webrtc.LoginMessageDto;
import net.rptools.clientserver.simple.webrtc.MessageDto;
import net.rptools.clientserver.simple.webrtc.OfferMessageDto;
+import net.rptools.maptool.client.MapTool;
+import net.rptools.maptool.server.ServerConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.java_websocket.client.WebSocketClient;
@@ -32,15 +35,8 @@
public class WebRTCServer extends AbstractServer {
private static final Logger log = LogManager.getLogger(WebRTCServer.class);
- public interface Listener {
- void onLoginError();
-
- void onUnexpectedClose();
- }
-
- private final Listener listener;
private WebSocketClient signalingClient;
- private final String serverName;
+ private final ServerConfig config;
private final Gson gson = new Gson();
private String lastError = null;
private URI webSocketUri = null;
@@ -51,9 +47,10 @@ public interface Listener {
public static String WebSocketUrl = "ws://webrtc1.rptools.net:8080";
private boolean disconnectExpected;
- public WebRTCServer(String serverName, Listener listener) {
- this.listener = listener;
- this.serverName = serverName;
+ public WebRTCServer(
+ ServerConfig config, HandshakeProvider handshake, MessageHandler messageHandler) {
+ super(handshake, messageHandler);
+ this.config = config;
try {
webSocketUri = new URI(WebSocketUrl);
@@ -75,7 +72,7 @@ public void onOpen(ServerHandshake handshakeData) {
reconnectCounter = 30;
log.info("S WebSocket connected\n");
var msg = new LoginMessageDto();
- msg.source = serverName;
+ msg.source = config.getServerName();
sendSignalingMessage(gson.toJson(msg));
}
@@ -89,17 +86,12 @@ public void onMessage(String message) {
public void onClose(int code, String reason, boolean remote) {
lastError = "WebSocket closed: remote:" + remote + " (" + code + ") " + reason;
log.info("S " + lastError);
- if (disconnectExpected) {
- return;
- }
+ if (disconnectExpected) return;
// if the connection get closed remotely the rptools.net server disconnected. Try to
// reconnect.
- if (reconnectCounter > 0) {
- retryConnect();
- } else {
- listener.onUnexpectedClose();
- }
+ if (reconnectCounter > 0) retryConnect();
+ else MapTool.stopServer();
}
@Override
@@ -119,7 +111,7 @@ private void handleSignalingMessage(String message) {
case "login" -> {
var loginMsg = gson.fromJson(message, LoginMessageDto.class);
if (!loginMsg.success) {
- listener.onLoginError();
+ MapTool.showError("ServerDialog.error.serverAlreadyExists");
}
}
case "offer" -> {
@@ -139,13 +131,13 @@ public WebSocketClient getSignalingClient() {
return signalingClient;
}
- public String getName() {
- return serverName;
+ public ServerConfig getConfig() {
+ return config;
}
public void onDataChannelOpened(WebRTCConnection connection) {
try {
- fireClientConnect(connection);
+ handleConnection(connection);
} catch (Exception e) {
log.error(e);
}
@@ -162,6 +154,7 @@ public void start() throws IOException {
@Override
public void close() {
+ super.close();
disconnectExpected = true;
reconnectCounter = -1;
signalingClient.close();
@@ -186,4 +179,8 @@ void retryConnect() {
reconnectThread.setName("WebRTCServer.reconnectThread");
reconnectThread.start();
}
+
+ public void clearClients() {
+ reapClients();
+ }
}
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/AnswerMessageDto.java b/src/main/java/net/rptools/clientserver/simple/webrtc/AnswerMessageDto.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/AnswerMessageDto.java
rename to src/main/java/net/rptools/clientserver/simple/webrtc/AnswerMessageDto.java
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/CandidateMessageDto.java b/src/main/java/net/rptools/clientserver/simple/webrtc/CandidateMessageDto.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/CandidateMessageDto.java
rename to src/main/java/net/rptools/clientserver/simple/webrtc/CandidateMessageDto.java
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/LoginMessageDto.java b/src/main/java/net/rptools/clientserver/simple/webrtc/LoginMessageDto.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/LoginMessageDto.java
rename to src/main/java/net/rptools/clientserver/simple/webrtc/LoginMessageDto.java
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/MessageDto.java b/src/main/java/net/rptools/clientserver/simple/webrtc/MessageDto.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/MessageDto.java
rename to src/main/java/net/rptools/clientserver/simple/webrtc/MessageDto.java
diff --git a/clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/OfferMessageDto.java b/src/main/java/net/rptools/clientserver/simple/webrtc/OfferMessageDto.java
similarity index 100%
rename from clientserver/src/main/java/net/rptools/clientserver/simple/webrtc/OfferMessageDto.java
rename to src/main/java/net/rptools/clientserver/simple/webrtc/OfferMessageDto.java
diff --git a/src/main/java/net/rptools/maptool/api/ApiData.java b/src/main/java/net/rptools/maptool/api/ApiData.java
new file mode 100644
index 0000000000..41a4768678
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/ApiData.java
@@ -0,0 +1,26 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+public interface ApiData {
+
+ default JsonObject asJsonObject() {
+ Gson gson = new Gson();
+ return gson.toJsonTree(this).getAsJsonObject();
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/client/ui/themes/AarkLaF.java b/src/main/java/net/rptools/maptool/api/ApiException.java
similarity index 62%
rename from src/main/java/net/rptools/maptool/client/ui/themes/AarkLaF.java
rename to src/main/java/net/rptools/maptool/api/ApiException.java
index 2eb8e4ec55..61f69718a6 100644
--- a/src/main/java/net/rptools/maptool/client/ui/themes/AarkLaF.java
+++ b/src/main/java/net/rptools/maptool/api/ApiException.java
@@ -12,23 +12,15 @@
* and specifically the Affero license
* text at .
*/
-package net.rptools.maptool.client.ui.themes;
+package net.rptools.maptool.api;
-import com.formdev.flatlaf.FlatDarkLaf;
+public class ApiException extends Exception {
-public class AarkLaF extends FlatDarkLaf {
- public static final String NAME = "Aark";
-
- public static boolean setup() {
- return setup(new AarkLaF());
- }
-
- public static void installLafInfo() {
- installLafInfo(NAME, AarkLaF.class);
+ public ApiException(String message) {
+ super(message);
}
- @Override
- public String getName() {
- return NAME;
+ public ApiException(String message, Throwable cause) {
+ super(message, cause);
}
}
diff --git a/src/main/java/net/rptools/maptool/api/maptool/MapToolApi.java b/src/main/java/net/rptools/maptool/api/maptool/MapToolApi.java
new file mode 100644
index 0000000000..e995a1285d
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/maptool/MapToolApi.java
@@ -0,0 +1,25 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.maptool;
+
+import java.util.concurrent.CompletableFuture;
+import net.rptools.maptool.api.util.ApiResult;
+
+public class MapToolApi {
+
+ public CompletableFuture> getVersion() {
+ return CompletableFuture.completedFuture(new ApiResult(new MapToolInfo()));
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/maptool/MapToolInfo.java b/src/main/java/net/rptools/maptool/api/maptool/MapToolInfo.java
new file mode 100644
index 0000000000..937902ce50
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/maptool/MapToolInfo.java
@@ -0,0 +1,28 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.maptool;
+
+import net.rptools.maptool.api.ApiData;
+import net.rptools.maptool.client.MapTool;
+
+// @XmlRootElement
+public record MapToolInfo(
+ String mapToolVersion, String webEndpointVersion, boolean developmentVersion)
+ implements ApiData {
+
+ public MapToolInfo() {
+ this(MapTool.getVersion(), "unavailable", MapTool.isDevelopment());
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/ApiCall.java b/src/main/java/net/rptools/maptool/api/util/ApiCall.java
new file mode 100644
index 0000000000..54ad4bebed
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/ApiCall.java
@@ -0,0 +1,51 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import javax.swing.SwingUtilities;
+import net.rptools.maptool.api.ApiData;
+import net.rptools.maptool.api.ApiException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class ApiCall {
+
+ private static final Logger log = LogManager.getLogger(ApiCall.class);
+
+ public CompletableFuture> runOnSwingThread(Callable callable) {
+ try {
+ if (SwingUtilities.isEventDispatchThread()) {
+ return CompletableFuture.completedFuture(doCall(callable));
+ } else {
+ return CompletableFuture.supplyAsync(() -> doCall(callable));
+ }
+ } catch (Exception e) {
+ log.error(e);
+ return CompletableFuture.completedFuture(
+ new ApiResult<>(new ApiException("err.internal", e)));
+ }
+ }
+
+ private ApiResult doCall(Callable callable) {
+ try {
+ return new ApiResult(callable.call());
+ } catch (Exception e) {
+ log.error(e);
+ return new ApiResult<>(new ApiException("err.internal", e));
+ }
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/ApiListResult.java b/src/main/java/net/rptools/maptool/api/util/ApiListResult.java
new file mode 100644
index 0000000000..e6f28dcba6
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/ApiListResult.java
@@ -0,0 +1,77 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.util.List;
+import net.rptools.maptool.api.ApiData;
+import net.rptools.maptool.api.ApiException;
+
+public class ApiListResult {
+
+ private final List data;
+ private final ApiResultStatus status;
+ private final ApiException exception;
+
+ public static ApiListResult INTERNAL_ERROR_RESULT =
+ new ApiListResult<>(List.of(new NoData()));
+
+ public ApiListResult(List data) {
+ this.data = List.copyOf(data);
+ this.exception = null;
+ this.status = data != null ? ApiResultStatus.OK : ApiResultStatus.NONE;
+ }
+
+ public ApiListResult(ApiException e) {
+ this.data = null;
+ this.status = ApiResultStatus.ERROR;
+ this.exception = e;
+ }
+
+ public List getData() {
+ return data;
+ }
+
+ public ApiResultStatus getStatus() {
+ return status;
+ }
+
+ public String getStatusMessage() {
+ if (exception == null) {
+ return "";
+ } else {
+ return exception.getMessage();
+ }
+ }
+
+ public JsonObject asJsonObject() {
+ JsonObject json = new JsonObject();
+ if (data != null) {
+ JsonArray objList =
+ data.stream()
+ .map(ApiData::asJsonObject)
+ .collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
+ json.add("data", objList);
+ }
+ json.addProperty("status", status.getTextValue());
+ String msg = getStatusMessage();
+ if (msg != null && msg.length() > 0) {
+ json.addProperty("message", msg);
+ }
+
+ return json;
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/ApiNoResult.java b/src/main/java/net/rptools/maptool/api/util/ApiNoResult.java
new file mode 100644
index 0000000000..055c0e032d
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/ApiNoResult.java
@@ -0,0 +1,68 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+import com.google.gson.JsonObject;
+import net.rptools.maptool.api.ApiData;
+import net.rptools.maptool.api.ApiException;
+
+public class ApiNoResult {
+
+ private final ApiResultStatus status;
+ private final ApiException exception;
+ private final T data;
+
+ // public static ApiNoResult NOT_FOUND = new ApiNoResult<>(new NoData());
+
+ public ApiNoResult() {
+ this(null);
+ }
+
+ public ApiNoResult(ApiException e) {
+ this.data = null;
+ this.status = ApiResultStatus.ERROR;
+ this.exception = e;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public ApiResultStatus getStatus() {
+ return status;
+ }
+
+ public String getStatusMessage() {
+ if (exception == null) {
+ return "";
+ } else {
+ return exception.getMessage();
+ }
+ }
+
+ public JsonObject asJsonObject() {
+ JsonObject json = new JsonObject();
+ if (data != null) {
+ json.add("data", data.asJsonObject());
+ }
+ json.addProperty("status", status.getTextValue());
+ String msg = getStatusMessage();
+ if (msg != null && msg.length() > 0) {
+ json.addProperty("message", msg);
+ }
+
+ return json;
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/ApiResult.java b/src/main/java/net/rptools/maptool/api/util/ApiResult.java
new file mode 100644
index 0000000000..dbac10c2db
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/ApiResult.java
@@ -0,0 +1,70 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+import com.google.gson.JsonObject;
+import net.rptools.maptool.api.ApiData;
+import net.rptools.maptool.api.ApiException;
+
+public class ApiResult {
+
+ private final T data;
+ private final ApiResultStatus status;
+ private final ApiException exception;
+
+ public static ApiResult NOT_FOUND = new ApiResult<>(new NoData());
+
+ public ApiResult(T data) {
+ this.data = data;
+ this.exception = null;
+ this.status = data != null ? ApiResultStatus.OK : ApiResultStatus.NONE;
+ }
+
+ public ApiResult(ApiException e) {
+ this.data = null;
+ this.status = ApiResultStatus.ERROR;
+ this.exception = e;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public ApiResultStatus getStatus() {
+ return status;
+ }
+
+ public String getStatusMessage() {
+ if (exception == null) {
+ return "";
+ } else {
+ return exception.getMessage();
+ }
+ }
+
+ public JsonObject asJsonObject() {
+ JsonObject json = new JsonObject();
+ if (data != null) {
+ json.add("data", data.asJsonObject());
+ }
+ json.addProperty("status", status.getTextValue());
+ String msg = getStatusMessage();
+ if (msg != null && msg.length() > 0) {
+ json.addProperty("message", msg);
+ }
+
+ return json;
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/ApiResultStatus.java b/src/main/java/net/rptools/maptool/api/util/ApiResultStatus.java
new file mode 100644
index 0000000000..2ead9c24c2
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/ApiResultStatus.java
@@ -0,0 +1,37 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+public enum ApiResultStatus {
+ OK("ok", ""),
+ ERROR("error", "Internal API Error"),
+ NONE("none", "Not Found");
+
+ private final String textValue;
+ private final String defaultMessage;
+
+ ApiResultStatus(String val, String defaultMsg) {
+ textValue = val;
+ defaultMessage = defaultMsg;
+ }
+
+ public String getTextValue() {
+ return textValue;
+ }
+
+ public String getDefaultMessage() {
+ return defaultMessage;
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/api/util/NoData.java b/src/main/java/net/rptools/maptool/api/util/NoData.java
new file mode 100644
index 0000000000..bd9bda933f
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/api/util/NoData.java
@@ -0,0 +1,19 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.api.util;
+
+import net.rptools.maptool.api.ApiData;
+
+public record NoData() implements ApiData {}
diff --git a/src/main/java/net/rptools/maptool/client/AppActions.java b/src/main/java/net/rptools/maptool/client/AppActions.java
index c49300c1b7..6c56d03f69 100644
--- a/src/main/java/net/rptools/maptool/client/AppActions.java
+++ b/src/main/java/net/rptools/maptool/client/AppActions.java
@@ -31,6 +31,7 @@
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
@@ -77,6 +78,7 @@
import net.rptools.maptool.model.drawing.DrawableTexturePaint;
import net.rptools.maptool.model.player.*;
import net.rptools.maptool.model.player.Player.Role;
+import net.rptools.maptool.model.player.PlayerDatabaseFactory.PlayerDatabaseType;
import net.rptools.maptool.server.ServerConfig;
import net.rptools.maptool.server.ServerPolicy;
import net.rptools.maptool.util.*;
@@ -141,7 +143,7 @@ private static int getMenuShortcutKeyMask() {
public static final Action NEXT_TOKEN =
new ZoneClientAction() {
{
- init("action.nextToken");
+ init("menu.nextToken");
}
@Override
@@ -638,11 +640,13 @@ public boolean isAvailable() {
@Override
protected void executeAction() {
- final var server = MapTool.getServer();
- if (server != null) {
- ConnectionInfoDialog dialog = new ConnectionInfoDialog(server);
- dialog.setVisible(true);
+
+ if (MapTool.getServer() == null) {
+ return;
}
+
+ ConnectionInfoDialog dialog = new ConnectionInfoDialog(MapTool.getServer());
+ dialog.setVisible(true);
}
};
@@ -1276,7 +1280,7 @@ protected void executeAction() {
MapTool.showError("msg.error.cantBootSelf");
return;
}
- if (MapTool.getClient().isPlayerConnected(selectedPlayer.getName())) {
+ if (MapTool.isPlayerConnected(selectedPlayer.getName())) {
String msg = I18N.getText("msg.confirm.bootPlayer", selectedPlayer.getName());
if (MapTool.confirm(msg)) {
MapTool.serverCommand().bootPlayer(selectedPlayer.getName());
@@ -2104,13 +2108,11 @@ public boolean isSelected() {
@Override
protected void executeAction() {
- var client = MapTool.getClient();
- ServerPolicy policy = client.getServerPolicy();
+ ServerPolicy policy = MapTool.getServerPolicy();
policy.setIsMovementLocked(!policy.isMovementLocked());
- client.setServerPolicy(policy);
- client.getServerCommand().setServerPolicy(policy);
+ MapTool.updateServerPolicy(policy);
}
};
@@ -2128,13 +2130,11 @@ public boolean isSelected() {
@Override
protected void executeAction() {
- var client = MapTool.getClient();
- ServerPolicy policy = client.getServerPolicy();
+ ServerPolicy policy = MapTool.getServerPolicy();
policy.setIsTokenEditorLocked(!policy.isTokenEditorLocked());
- client.setServerPolicy(policy);
- client.getServerCommand().setServerPolicy(policy);
+ MapTool.updateServerPolicy(policy);
}
};
@@ -2162,9 +2162,8 @@ protected void executeAction() {
StartServerDialog dialog = new StartServerDialog();
dialog.showDialog();
- if (!dialog.accepted()) { // Results stored in Preferences.userRoot()
- return;
- }
+ if (!dialog.accepted()) // Results stored in Preferences.userRoot()
+ return;
StartServerDialogPreferences serverProps =
new StartServerDialogPreferences(); // data retrieved from
@@ -2228,19 +2227,26 @@ protected void executeAction() {
boolean failed = false;
try {
- MapTool.disconnect();
+ ServerDisconnectHandler.disconnectExpected = true;
MapTool.stopServer();
+ // Use UPnP to open port in router
+ if (serverProps.getUseUPnP()) {
+ UPnPUtil.openPort(serverProps.getPort());
+ }
// Right now set this is set to whatever the last server settings were. If we
// wanted to turn it on and
// leave it turned on, the line would change to:
// campaign.setHasUsedFogToolbar(useIF || campaign.hasUsedFogToolbar());
campaign.setHasUsedFogToolbar(useIF);
- ServerSidePlayerDatabase playerDatabase;
+ PlayerDatabaseFactory.setServerConfig(config);
if (serverProps.getUsePasswordFile()) {
+ PlayerDatabaseFactory.setCurrentPlayerDatabase(
+ PlayerDatabaseType.PASSWORD_FILE);
PasswordFilePlayerDatabase db =
- PlayerDatabaseFactory.getPasswordFilePlayerDatabase();
+ (PasswordFilePlayerDatabase)
+ PlayerDatabaseFactory.getCurrentPlayerDatabase();
db.initialize();
if (serverProps.getRole() == Role.GM) {
db.addTemporaryPlayer(
@@ -2249,22 +2255,13 @@ protected void executeAction() {
db.addTemporaryPlayer(
dialog.getUsernameTextField().getText(), Role.PLAYER, playerPassword);
}
- playerDatabase = db;
} else {
- playerDatabase =
- PlayerDatabaseFactory.getDefaultPlayerDatabase(
- config.getPlayerPassword(), config.getGmPassword());
+ PlayerDatabaseFactory.setCurrentPlayerDatabase(PlayerDatabaseType.DEFAULT);
}
+ PlayerDatabase playerDatabase = PlayerDatabaseFactory.getCurrentPlayerDatabase();
// Make a copy of the campaign since we don't coordinate local changes well ...
// yet
- Player.Role playerType = (Player.Role) dialog.getRoleCombo().getSelectedItem();
- final var player =
- new LocalPlayer(
- dialog.getUsernameTextField().getText(),
- playerType,
- (playerType == Role.GM) ? gmPassword : playerPassword);
-
/*
* JFJ 2010-10-27 The below creates a NEW campaign with a copy of the existing campaign. However, this is NOT a full copy. In the constructor called below, each zone from the
* previous campaign(ie, the one passed in) is recreated. This means that only some items for that campaign, zone(s), and token's are copied over when you start a new server
@@ -2275,11 +2272,37 @@ protected void executeAction() {
MapTool.startServer(
dialog.getUsernameTextField().getText(),
config,
- serverProps.getUseUPnP(),
policy,
campaign,
playerDatabase,
- player);
+ true);
+
+ // Connect to server
+ Player.Role playerType = (Player.Role) dialog.getRoleCombo().getSelectedItem();
+ Runnable onConnected =
+ () -> {
+ // connecting
+ MapTool.getFrame()
+ .getConnectionStatusPanel()
+ .setStatus(ConnectionStatusPanel.Status.server);
+ MapTool.addLocalMessage(
+ MessageUtil.getFormattedSystemMsg(
+ I18N.getText("msg.info.startServer")));
+ };
+
+ if (playerType == Player.Role.GM) {
+ MapTool.createConnection(
+ config,
+ new LocalPlayer(
+ dialog.getUsernameTextField().getText(), playerType, gmPassword),
+ onConnected);
+ } else {
+ MapTool.createConnection(
+ config,
+ new LocalPlayer(
+ dialog.getUsernameTextField().getText(), playerType, playerPassword),
+ onConnected);
+ }
} catch (UnknownHostException uh) {
MapTool.showError("msg.error.invalidLocalhost", uh);
failed = true;
@@ -2290,7 +2313,9 @@ protected void executeAction() {
| InvalidAlgorithmParameterException
| InvalidKeySpecException
| NoSuchPaddingException
- | InvalidKeyException e) {
+ | InvalidKeyException
+ | ExecutionException
+ | InterruptedException e) {
MapTool.showError("msg.error.initializeCrypto", e);
failed = true;
} catch (PasswordDatabaseException pwde) {
@@ -2301,7 +2326,11 @@ protected void executeAction() {
if (failed) {
try {
MapTool.startPersonalServer(campaign);
- } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+ } catch (IOException
+ | NoSuchAlgorithmException
+ | InvalidKeySpecException
+ | ExecutionException
+ | InterruptedException e) {
MapTool.showError("msg.error.failedStartPersonalServer", e);
}
}
@@ -2330,9 +2359,8 @@ protected void executeAction() {
return;
}
+ ServerDisconnectHandler.disconnectExpected = true;
LOAD_MAP.setSeenWarning(false);
-
- MapTool.disconnect();
MapTool.stopServer();
// Install a temporary gimped campaign until we get the one from the
@@ -2345,8 +2373,9 @@ protected void executeAction() {
.getConnectionStatusPanel()
.setStatus(ConnectionStatusPanel.Status.connected);
- // Show the user something interesting while we're connecting. Look below for the
- // corresponding hideGlassPane
+ // Show the user something interesting until we've got the campaign
+ // Look in ClientMethodHandler.setCampaign() for the corresponding
+ // hideGlassPane
StaticMessageDialog progressDialog =
new StaticMessageDialog(I18N.getText("msg.info.connecting"));
MapTool.getFrame().showFilledGlassPane(progressDialog);
@@ -2364,30 +2393,20 @@ protected void executeAction() {
dialog.getPort(),
prefs.getServerName(),
dialog.getServer(),
- false,
dialog.getUseWebRTC());
String password =
prefs.getUsePublicKey()
? new PasswordGenerator().getPassword()
: prefs.getPassword();
- MapTool.connectToRemoteServer(
+ MapTool.createConnection(
config,
new LocalPlayer(prefs.getUsername(), prefs.getRole(), password),
- (success) -> {
- EventQueue.invokeLater(
- () -> {
- MapTool.getFrame().hideGlassPane();
- if (success) {
- // Show the user something interesting until we've got the campaign
- // Look in ClientMethodHandler.setCampaign() for the corresponding
- // hideGlassPane
- MapTool.getFrame()
- .showFilledGlassPane(
- new StaticMessageDialog(
- I18N.getText("msg.info.campaignLoading")));
- }
- });
+ () -> {
+ MapTool.getFrame().hideGlassPane();
+ MapTool.getFrame()
+ .showFilledGlassPane(
+ new StaticMessageDialog(I18N.getText("msg.info.campaignLoading")));
});
} catch (UnknownHostException e1) {
@@ -2396,7 +2415,10 @@ protected void executeAction() {
} catch (IOException e1) {
MapTool.showError("msg.error.failedLoadCampaign", e1);
failed = true;
- } catch (NoSuchAlgorithmException | InvalidKeySpecException e1) {
+ } catch (NoSuchAlgorithmException
+ | InvalidKeySpecException
+ | ExecutionException
+ | InterruptedException e1) {
MapTool.showError("msg.error.initializeCrypto", e1);
failed = true;
}
@@ -2404,7 +2426,11 @@ protected void executeAction() {
MapTool.getFrame().hideGlassPane();
try {
MapTool.startPersonalServer(oldCampaign);
- } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+ } catch (IOException
+ | NoSuchAlgorithmException
+ | InvalidKeySpecException
+ | ExecutionException
+ | InterruptedException e) {
MapTool.showError("msg.error.failedStartPersonalServer", e);
}
}
@@ -2431,16 +2457,7 @@ protected void executeAction() {
}
};
- /**
- * Disconnects the client and starts a personal server.
- *
- * If we are hosting the server, the personal server will have the same campaign as the server.
- * Otherwise a new basic campaign will be created.
- */
public static void disconnectFromServer() {
- // hide map so player doesn't get a brief GM view
- MapTool.getFrame().setCurrentZoneRenderer(null);
-
Campaign campaign;
if (MapTool.isHostingServer()) {
campaign = MapTool.getCampaign();
@@ -2448,18 +2465,20 @@ public static void disconnectFromServer() {
campaign = CampaignFactory.createBasicCampaign();
new CampaignManager().clearCampaignData();
}
-
+ ServerDisconnectHandler.disconnectExpected = true;
LOAD_MAP.setSeenWarning(false);
-
- MapTool.disconnect();
MapTool.stopServer();
-
+ MapTool.disconnect();
MapTool.getFrame().getToolbarPanel().getMapselect().setVisible(true);
MapTool.getFrame().getToolbarPanel().setTokenSelectionGroupEnabled(true);
try {
MapTool.startPersonalServer(campaign);
- } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+ } catch (IOException
+ | NoSuchAlgorithmException
+ | InvalidKeySpecException
+ | ExecutionException
+ | InterruptedException e) {
MapTool.showError("msg.error.failedStartPersonalServer", e);
}
}
@@ -2472,7 +2491,8 @@ public static void disconnectFromServer() {
@Override
public boolean isAvailable() {
- return MapTool.getClient().getPlayerDatabase() instanceof PersistedPlayerDatabase;
+ return PlayerDatabaseFactory.getCurrentPlayerDatabase()
+ instanceof PersistedPlayerDatabase;
}
@Override
@@ -3339,7 +3359,7 @@ public ToggleWindowAction(MTFrame mtFrame) {
@Override
public boolean isSelected() {
- return !MapTool.getFrame().getFrame(mtFrame).isHidden();
+ return MapTool.getFrame().getFrame(mtFrame).isShowing();
}
@Override
@@ -3350,7 +3370,7 @@ public boolean isAvailable() {
@Override
protected void executeAction() {
DockableFrame frame = MapTool.getFrame().getFrame(mtFrame);
- if (!frame.isHidden()) {
+ if (frame.isShowing()) {
MapTool.getFrame().getDockingManager().hideFrame(mtFrame.name());
} else {
MapTool.getFrame().getDockingManager().showFrame(mtFrame.name());
diff --git a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java
index 9b66a3a4e0..3e0d16246d 100644
--- a/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java
+++ b/src/main/java/net/rptools/maptool/client/ClientMessageHandler.java
@@ -18,7 +18,6 @@
import java.awt.Point;
import java.awt.geom.Area;
import java.io.IOException;
-import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -74,7 +73,6 @@
import net.rptools.maptool.server.proto.*;
import net.rptools.maptool.transfer.AssetConsumer;
import net.rptools.maptool.transfer.AssetHeader;
-import net.rptools.maptool.util.MessageUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -87,18 +85,14 @@
public class ClientMessageHandler implements MessageHandler {
private static final Logger log = LogManager.getLogger(ClientMessageHandler.class);
- private final MapToolClient client;
-
- public ClientMessageHandler(MapToolClient client) {
- this.client = client;
- }
+ public ClientMessageHandler() {}
@Override
public void handleMessage(String id, byte[] message) {
try {
var msg = Message.parseFrom(message);
var msgType = msg.getMessageTypeCase();
- log.debug("{} got: {}", id, msgType);
+ log.debug(id + " got: " + msgType);
switch (msgType) {
case ADD_TOPOLOGY_MSG -> handle(msg.getAddTopologyMsg());
@@ -195,7 +189,7 @@ private void handle(UpdateExposedAreaMetaMsg msg) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
var tokenGUID = msg.hasTokenGuid() ? GUID.valueOf(msg.getTokenGuid().getValue()) : null;
ExposedAreaMetaData meta = new ExposedAreaMetaData(Mapper.map(msg.getArea()));
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.setExposedAreaMetaData(tokenGUID, meta);
});
}
@@ -207,7 +201,7 @@ private void handle(UpdateGmMacrosMsg msg) {
msg.getMacrosList().stream()
.map(MacroButtonProperties::fromDto)
.collect(Collectors.toList());
- client.getCampaign().setGmMacroButtonPropertiesArray(macros);
+ MapTool.getCampaign().setGmMacroButtonPropertiesArray(macros);
MapTool.getFrame().getGmPanel().reset();
});
}
@@ -219,7 +213,7 @@ private void handle(UpdateCampaignMacrosMsg msg) {
msg.getMacrosList().stream()
.map(MacroButtonProperties::fromDto)
.collect(Collectors.toList());
- client.getCampaign().setMacroButtonPropertiesArray(macros);
+ MapTool.getCampaign().setMacroButtonPropertiesArray(macros);
MapTool.getFrame().getCampaignPanel().reset();
});
}
@@ -229,7 +223,7 @@ private void handle(UpdateTokenInitiativeMsg msg) {
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
var tokenGUID = GUID.valueOf(msg.getTokenGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var list = zone.getInitiativeList();
TokenInitiative ti = list.getTokenInitiative(msg.getIndex());
if (!ti.getId().equals(tokenGUID)) {
@@ -266,7 +260,7 @@ private void handle(UpdateCampaignMsg msg) {
() -> {
CampaignProperties properties = CampaignProperties.fromDto(msg.getProperties());
- client.getCampaign().replaceCampaignProperties(properties);
+ MapTool.getCampaign().replaceCampaignProperties(properties);
MapToolFrame frame = MapTool.getFrame();
ZoneRenderer zr = frame.getCurrentZoneRenderer();
if (zr != null) {
@@ -289,7 +283,7 @@ private void handle(SetServerPolicyMsg msg) {
EventQueue.invokeLater(
() -> {
ServerPolicy policy = ServerPolicy.fromDto(msg.getPolicy());
- client.setServerPolicy(policy);
+ MapTool.setServerPolicy(policy);
MapTool.getFrame().getToolbox().updateTools();
});
}
@@ -347,11 +341,11 @@ private void handle(SetZoneVisibilityMsg msg) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
boolean visible = msg.getIsVisible();
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.setVisible(visible);
ZoneRenderer currentRenderer = MapTool.getFrame().getCurrentZoneRenderer();
if (!visible
- && !client.getPlayer().isGM()
+ && !MapTool.getPlayer().isGM()
&& currentRenderer != null
&& currentRenderer.getZone().getId().equals(zoneGUID)) {
Collection AllTokenIDs = new ArrayList<>();
@@ -375,7 +369,7 @@ private void handle(UndoDrawMsg msg) {
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
GUID drawableId = GUID.valueOf(msg.getDrawableGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
if (zone == null) {
return;
}
@@ -393,7 +387,7 @@ private void handle(UpdateDrawingMsg msg) {
Pen p = Pen.fromDto(msg.getPen());
DrawnElement de = DrawnElement.fromDto(msg.getDrawing());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.updateDrawable(de, p);
MapTool.getFrame().refresh();
});
@@ -403,7 +397,7 @@ private void handle(UpdateTokenPropertyMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var tokenGUID = GUID.valueOf(msg.getTokenGuid());
var token = zone.getToken(tokenGUID);
if (token != null) {
@@ -524,7 +518,7 @@ private void handle(SetZoneHasFowMsg msg) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
boolean hasFog = msg.getHasFow();
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.setHasFog(hasFog);
// In case we're looking at the zone
@@ -541,14 +535,10 @@ private void handle(SetZoneGridSizeMsg msg) {
int size = msg.getSize();
int color = msg.getColor();
- var zone = client.getCampaign().getZone(zoneGUID);
- // Sometimes these messages can come in as a zone is being removed, so we can't rely on
- // its existence
- if (zone != null) {
- zone.getGrid().setSize(size);
- zone.getGrid().setOffset(xOffset, yOffset);
- zone.setGridColor(color);
- }
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
+ zone.getGrid().setSize(size);
+ zone.getGrid().setOffset(xOffset, yOffset);
+ zone.setGridColor(color);
MapTool.getFrame().refresh();
});
@@ -559,7 +549,7 @@ private void handle(SetVisionTypeMsg msg) {
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
VisionType visionType = VisionType.valueOf(msg.getVision().name());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
if (zone != null) {
zone.setVisionType(visionType);
if (MapTool.getFrame().getCurrentZoneRenderer() != null) {
@@ -575,7 +565,7 @@ private void handle(SetTokenLocationMsg msg) {
EventQueue.invokeLater(
() -> {
// Only the table should process this
- if (client.getPlayer().getName().equalsIgnoreCase("Table")) {
+ if (MapTool.getPlayer().getName().equalsIgnoreCase("Table")) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
var keyToken = GUID.valueOf(msg.getTokenGuid());
@@ -586,7 +576,7 @@ private void handle(SetTokenLocationMsg msg) {
var y = msg.getLocation().getY();
// Get the zone
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
// Get the token
var token = zone.getToken(keyToken);
@@ -599,7 +589,7 @@ private void handle(SetTokenLocationMsg msg) {
token.setX(zp2.x);
token.setY(zp2.y);
- client.getServerCommand().putToken(zoneGUID, token);
+ MapTool.serverCommand().putToken(zoneGUID, token);
}
});
}
@@ -625,7 +615,7 @@ private void handle(SetFowMsg msg) {
var selectedTokens =
msg.getSelectedTokensList().stream().map(GUID::valueOf).collect(Collectors.toSet());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.setFogArea(area, selectedTokens);
MapTool.getFrame().refresh();
});
@@ -634,7 +624,7 @@ private void handle(SetFowMsg msg) {
private void handle(SetCampaignNameMsg msg) {
EventQueue.invokeLater(
() -> {
- client.getCampaign().setName(msg.getName());
+ MapTool.getCampaign().setName(msg.getName());
MapTool.getFrame().setTitle();
});
}
@@ -654,7 +644,7 @@ private void handle(SetBoardMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
Point boardXY = Mapper.map(msg.getPoint());
var assetId = new MD5Key(msg.getAssetId());
@@ -676,7 +666,7 @@ private void handle(RenameZoneMsg msg) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
String name = msg.getName();
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
if (zone != null) {
zone.setName(name);
}
@@ -690,7 +680,7 @@ private void handle(RemoveZoneMsg msg) {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
final var renderer = MapTool.getFrame().getZoneRenderer(zoneGUID);
final var zone = renderer.getZone();
- client.getCampaign().removeZone(zoneGUID);
+ MapTool.getCampaign().removeZone(zoneGUID);
MapTool.getFrame().removeZoneRenderer(renderer);
// Now we have fire off adding the tokens in the zone
@@ -708,7 +698,7 @@ private void handle(RemoveTopologyMsg msg) {
var area = Mapper.map(msg.getArea());
var topologyType = Zone.TopologyType.valueOf(msg.getType().name());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.removeTopology(area, topologyType);
MapTool.getFrame().getZoneRenderer(zoneGUID).repaint();
@@ -719,7 +709,7 @@ private void handle(RemoveTokensMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var tokenGUIDs =
msg.getTokenGuidList().stream().map(GUID::valueOf).collect(Collectors.toList());
zone.removeTokens(tokenGUIDs);
@@ -731,7 +721,7 @@ private void handle(RemoveTokenMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var tokenGUID = GUID.valueOf(msg.getTokenGuid());
zone.removeToken(tokenGUID);
MapTool.getFrame().refresh();
@@ -742,7 +732,7 @@ private void handle(RemoveLabelMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
GUID labelGUID = GUID.valueOf(msg.getLabelGuid());
zone.removeLabel(labelGUID);
MapTool.getFrame().refresh();
@@ -753,7 +743,7 @@ private void handle(PutZoneMsg msg) {
EventQueue.invokeLater(
() -> {
Zone zone = Zone.fromDto(msg.getZone());
- client.getCampaign().putZone(zone);
+ MapTool.getCampaign().putZone(zone);
// TODO: combine this with MapTool.addZone()
var renderer = ZoneRendererFactory.newRenderer(zone);
@@ -772,7 +762,7 @@ private void handle(PutLabelMsg msg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(msg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
Label label = Label.fromDto(msg.getLabel());
zone.putLabel(label);
MapTool.getFrame().refresh();
@@ -791,16 +781,7 @@ private void handle(PutAssetMsg msg) {
private void handle(PlayerDisconnectedMsg msg) {
EventQueue.invokeLater(
() -> {
- var player = Player.fromDto(msg.getPlayer());
- client.removePlayer(player);
-
- if (!player.equals(client.getPlayer())) {
- MapTool.addLocalMessage(
- MessageUtil.getFormattedSystemMsg(
- MessageFormat.format(
- I18N.getText("msg.info.playerDisconnected"), player.getName())));
- }
-
+ MapTool.removePlayer(Player.fromDto(msg.getPlayer()));
MapTool.getFrame().refresh();
});
}
@@ -808,16 +789,7 @@ private void handle(PlayerDisconnectedMsg msg) {
private void handle(PlayerConnectedMsg msg) {
EventQueue.invokeLater(
() -> {
- var player = Player.fromDto(msg.getPlayer());
- client.addPlayer(player);
-
- if (!player.equals(client.getPlayer())) {
- MapTool.addLocalMessage(
- MessageUtil.getFormattedSystemMsg(
- MessageFormat.format(
- I18N.getText("msg.info.playerConnected"), player.getName())));
- }
-
+ MapTool.addPlayer(Player.fromDto(msg.getPlayer()));
MapTool.getFrame().refresh();
});
}
@@ -860,7 +832,7 @@ private void handle(HideFowMsg msg) {
var selectedTokens =
msg.getTokenGuidList().stream().map(GUID::valueOf).collect(Collectors.toSet());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.hideArea(area, selectedTokens);
MapTool.getFrame().refresh();
});
@@ -882,7 +854,7 @@ private void handle(ExposeFowMsg msg) {
Area area = Mapper.map(msg.getArea());
var selectedTokens =
msg.getTokenGuidList().stream().map(GUID::valueOf).collect(Collectors.toSet());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.exposeArea(area, selectedTokens);
MapTool.getFrame().refresh();
});
@@ -934,7 +906,7 @@ private void handle(EnforceZoneMsg msg) {
if (renderer != null
&& renderer != MapTool.getFrame().getCurrentZoneRenderer()
- && (renderer.getZone().isVisible() || client.getPlayer().isGM())) {
+ && (renderer.getZone().isVisible() || MapTool.getPlayer().isGM())) {
MapTool.getFrame().setCurrentZoneRenderer(renderer);
}
});
@@ -949,7 +921,7 @@ private void handle(PutTokenMsg putTokenMsg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(putTokenMsg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var token = Token.fromDto(putTokenMsg.getToken());
zone.putToken(token);
MapTool.getFrame().refresh();
@@ -960,7 +932,7 @@ private void handle(EditTokenMsg editTokenMsg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(editTokenMsg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
var token = Token.fromDto(editTokenMsg.getToken());
zone.editToken(token);
MapTool.getFrame().refresh();
@@ -974,7 +946,7 @@ private void handle(DrawMsg drawMsg) {
Pen pen = Pen.fromDto(drawMsg.getPen());
Drawable drawable = Drawable.fromDto(drawMsg.getDrawable());
- var zone = client.getCampaign().getZone(zoneGuid);
+ var zone = MapTool.getCampaign().getZone(zoneGuid);
zone.addDrawable(new DrawnElement(drawable, pen));
MapTool.getFrame().refresh();
});
@@ -984,7 +956,7 @@ private void handle(ClearExposedAreaMsg clearExposedAreaMsg) {
EventQueue.invokeLater(
() -> {
var zoneGUID = GUID.valueOf(clearExposedAreaMsg.getZoneGuid());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.clearExposedArea(clearExposedAreaMsg.getGlobalOnly());
});
}
@@ -994,7 +966,7 @@ private void handle(ClearAllDrawingsMsg clearAllDrawingsMsg) {
() -> {
var zoneGUID = GUID.valueOf(clearAllDrawingsMsg.getZoneGuid());
var layer = Zone.Layer.valueOf(clearAllDrawingsMsg.getLayer());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.clearDrawables(layer);
MapTool.getFrame().refresh();
});
@@ -1006,7 +978,7 @@ private void handle(ChangeZoneDisplayNameMsg changeZoneDisplayNameMsg) {
var zoneGUID = GUID.valueOf(changeZoneDisplayNameMsg.getZoneGuid());
String displayName = changeZoneDisplayNameMsg.getName();
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
if (zone != null) {
zone.setPlayerAlias(displayName);
}
@@ -1025,7 +997,7 @@ private void handle(AddTopologyMsg addTopologyMsg) {
var area = Mapper.map(addTopologyMsg.getArea());
var topologyType = Zone.TopologyType.valueOf(addTopologyMsg.getType().name());
- var zone = client.getCampaign().getZone(zoneGUID);
+ var zone = MapTool.getCampaign().getZone(zoneGUID);
zone.addTopology(area, topologyType);
MapTool.getFrame().getZoneRenderer(zoneGUID).repaint();
@@ -1034,9 +1006,10 @@ private void handle(AddTopologyMsg addTopologyMsg) {
private void handle(BootPlayerMsg bootPlayerMsg) {
String playerName = bootPlayerMsg.getPlayerName();
- if (client.getPlayer().getName().equals(playerName))
+ if (MapTool.getPlayer().getName().equals(playerName))
EventQueue.invokeLater(
() -> {
+ ServerDisconnectHandler.disconnectExpected = true;
AppActions.disconnectFromServer();
MapTool.showInformation("You have been booted from the server.");
});
@@ -1048,7 +1021,7 @@ private void handle(UpdatePlayerStatusMsg updatePlayerStatusMsg) {
var loaded = updatePlayerStatusMsg.getLoaded();
Player player =
- client.getPlayerList().stream()
+ MapTool.getPlayerList().stream()
.filter(x -> x.getName().equals(playerName))
.findFirst()
.orElse(null);
diff --git a/src/main/java/net/rptools/maptool/client/MapTool.java b/src/main/java/net/rptools/maptool/client/MapTool.java
index 55fb505a48..e0f3a378d7 100644
--- a/src/main/java/net/rptools/maptool/client/MapTool.java
+++ b/src/main/java/net/rptools/maptool/client/MapTool.java
@@ -14,6 +14,8 @@
*/
package net.rptools.maptool.client;
+import static net.rptools.maptool.model.player.PlayerDatabaseFactory.PlayerDatabaseType.PERSONAL_SERVER;
+
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.plaf.basic.ThemePainter;
@@ -41,14 +43,12 @@
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
+import java.text.MessageFormat;
import java.util.*;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
-import net.rptools.clientserver.ConnectionFactory;
-import net.rptools.clientserver.simple.connection.DirectConnection;
import net.rptools.lib.BackupManager;
import net.rptools.lib.DebugStream;
import net.rptools.lib.FileUtil;
@@ -56,9 +56,10 @@
import net.rptools.lib.image.ThumbnailManager;
import net.rptools.lib.net.RPTURLStreamHandlerFactory;
import net.rptools.lib.sound.SoundManager;
-import net.rptools.maptool.client.MapToolConnection.HandshakeCompletionObserver;
import net.rptools.maptool.client.events.ChatMessageAdded;
-import net.rptools.maptool.client.events.ServerDisconnected;
+import net.rptools.maptool.client.events.PlayerConnected;
+import net.rptools.maptool.client.events.PlayerDisconnected;
+import net.rptools.maptool.client.events.ServerStopped;
import net.rptools.maptool.client.functions.UserDefinedMacroFunctions;
import net.rptools.maptool.client.swing.MapToolEventQueue;
import net.rptools.maptool.client.swing.NoteFrame;
@@ -70,6 +71,7 @@
import net.rptools.maptool.client.ui.OSXAdapter;
import net.rptools.maptool.client.ui.logger.LogConsoleFrame;
import net.rptools.maptool.client.ui.sheet.stats.StatSheetListener;
+import net.rptools.maptool.client.ui.startserverdialog.StartServerDialogPreferences;
import net.rptools.maptool.client.ui.theme.Icons;
import net.rptools.maptool.client.ui.theme.RessourceManager;
import net.rptools.maptool.client.ui.theme.ThemeSupport;
@@ -87,14 +89,13 @@
import net.rptools.maptool.model.TextMessage;
import net.rptools.maptool.model.Zone;
import net.rptools.maptool.model.ZoneFactory;
-import net.rptools.maptool.model.library.LibraryManager;
import net.rptools.maptool.model.library.url.LibraryURLStreamHandler;
import net.rptools.maptool.model.player.LocalPlayer;
-import net.rptools.maptool.model.player.PersonalServerPlayerDatabase;
import net.rptools.maptool.model.player.Player;
+import net.rptools.maptool.model.player.PlayerDatabase;
import net.rptools.maptool.model.player.PlayerDatabaseFactory;
import net.rptools.maptool.model.player.PlayerZoneListener;
-import net.rptools.maptool.model.player.ServerSidePlayerDatabase;
+import net.rptools.maptool.model.player.Players;
import net.rptools.maptool.model.zones.TokensAdded;
import net.rptools.maptool.model.zones.TokensRemoved;
import net.rptools.maptool.model.zones.ZoneAdded;
@@ -107,8 +108,11 @@
import net.rptools.maptool.transfer.AssetTransferManager;
import net.rptools.maptool.util.MessageUtil;
import net.rptools.maptool.util.StringUtil;
+import net.rptools.maptool.util.UPnPUtil;
import net.rptools.maptool.util.UserJvmOptions;
+import net.rptools.maptool.webapi.MTWebAppServer;
import net.rptools.parser.ParserException;
+import net.tsc.servicediscovery.ServiceAnnouncer;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
@@ -150,23 +154,33 @@ public class MapTool {
private static String vendor = "RPTools!"; // Default, will get from JAR Manifest during normal
// runtime
+ private static Campaign campaign;
+
+ private static List playerList;
+ private static LocalPlayer player;
private static PlayerZoneListener playerZoneListener;
private static ZoneLoadedListener zoneLoadedListener;
+ private static MapToolConnection conn;
+ private static ClientMessageHandler handler;
private static JMenuBar menuBar;
private static MapToolFrame clientFrame;
private static NoteFrame profilingNoteFrame;
private static LogConsoleFrame logConsoleFrame;
private static MapToolServer server;
- private static MapToolClient client;
+ private static ServerCommand serverCommand;
+ private static ServerPolicy serverPolicy;
private static BackupManager backupManager;
private static AssetTransferManager assetTransferManager;
+ private static ServiceAnnouncer announcer;
private static AutoSaveManager autoSaveManager;
private static TaskBarFlasher taskbarFlasher;
private static MapToolLineParser parser = new MapToolLineParser();
private static String lastWhisperer;
+ private static final MTWebAppServer webAppServer = new MTWebAppServer();
+
// Jamz: To support new command line parameters for multi-monitor support & enhanced PrintStream
private static boolean debug = false;
private static int graphicsMonitor = -1;
@@ -177,20 +191,6 @@ public class MapTool {
private static int windowY = -1;
private static String loadCampaignOnStartPath = "";
- static {
- try {
- var connections = DirectConnection.create("local");
- var playerDB = new PersonalServerPlayerDatabase(new LocalPlayer());
- var campaign = CampaignFactory.createBasicCampaign();
- var policy = new ServerPolicy();
-
- server = new MapToolServer("", new Campaign(campaign), null, false, policy, playerDB);
- client = new MapToolClient(server, campaign, playerDB.getPlayer(), connections.clientSide());
- } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new RuntimeException("Unable to create default personal server", e);
- }
- }
-
public static Dimension getThumbnailSize() {
return THUMBNAIL_SIZE;
}
@@ -536,6 +536,19 @@ public static void playSound(String eventId) {
}
}
+ public static void updateServerPolicy() {
+ updateServerPolicy(serverPolicy);
+ }
+
+ public static void updateServerPolicy(ServerPolicy policy) {
+ setServerPolicy(policy);
+
+ // Give everyone the new policy
+ if (serverCommand != null) {
+ serverCommand.setServerPolicy(policy);
+ }
+ }
+
public static boolean isInFocus() {
// TODO: This should probably also check owned windows
return getFrame().isFocused();
@@ -640,7 +653,6 @@ private static void moveToMonitor(JFrame frame, int monitor, boolean maximize) {
private static void initialize() {
// First time
AppSetup.install();
- LibraryManager.init();
// Clean up after ourselves
FileUtil.delete(AppUtil.getAppHome("tmp"), 2);
@@ -658,26 +670,22 @@ private static void initialize() {
assetTransferManager = new AssetTransferManager();
assetTransferManager.addConsumerListener(new AssetTransferHandler());
+ playerList = new ArrayList<>();
+
+ handler = new ClientMessageHandler();
+
setClientFrame(new MapToolFrame(menuBar));
- taskbarFlasher = new TaskBarFlasher(clientFrame);
- // Make sure the user sees something right away so that they aren't staring at a black screen.
- // Technically this call does too much, but since it is a blank campaign it's okay.
- setCampaign(client.getCampaign());
+ serverCommand = new ServerCommandClientImpl();
try {
+ player = new LocalPlayer("", Player.Role.GM, ServerConfig.getPersonalServerGMPassword());
playerZoneListener = new PlayerZoneListener();
zoneLoadedListener = new ZoneLoadedListener();
-
Campaign cmpgn = CampaignFactory.createBasicCampaign();
- // Set the Topology drawing mode to the last mode used for convenience
- // Should only be one zone, but let's cover our bases.
- cmpgn.getZones().forEach(zone -> zone.setTopologyTypes(AppPreferences.getTopologyTypes()));
-
- // Stop the pre-init client/server.
- disconnect();
- stopServer();
-
+ // This was previously being done in the server thread and didn't always get done
+ // before the campaign was accessed by the postInitialize() method below.
+ setCampaign(cmpgn);
startPersonalServer(cmpgn);
} catch (Exception e) {
MapTool.showError("While starting personal server", e);
@@ -737,11 +745,11 @@ public static boolean isDevelopment() {
}
public static ServerPolicy getServerPolicy() {
- return client.getServerPolicy();
+ return serverPolicy;
}
- public static @Nonnull ServerCommand serverCommand() {
- return client.getServerCommand();
+ public static ServerCommand serverCommand() {
+ return serverCommand;
}
/**
@@ -751,6 +759,39 @@ public static MapToolServer getServer() {
return server;
}
+ public static void addPlayer(Player player) {
+ if (!playerList.contains(player)) {
+ playerList.add(player);
+ new MapToolEventBus().getMainEventBus().post(new PlayerConnected(player));
+ new Players().playerSignedIn(player);
+
+ // LATER: Make this non-anonymous
+ playerList.sort((arg0, arg1) -> arg0.getName().compareToIgnoreCase(arg1.getName()));
+
+ if (!player.equals(MapTool.getPlayer())) {
+ String msg =
+ MessageFormat.format(I18N.getText("msg.info.playerConnected"), player.getName());
+ addLocalMessage(MessageUtil.getFormattedSystemMsg(msg));
+ }
+ }
+ }
+
+ public static void removePlayer(Player player) {
+ if (player == null) {
+ return;
+ }
+ playerList.remove(player);
+ new MapToolEventBus().getMainEventBus().post(new PlayerDisconnected(player));
+
+ new Players().playerSignedOut(player);
+
+ if (MapTool.getPlayer() != null && !player.equals(MapTool.getPlayer())) {
+ String msg =
+ MessageFormat.format(I18N.getText("msg.info.playerDisconnected"), player.getName());
+ addLocalMessage(MessageUtil.getFormattedSystemMsg(msg));
+ }
+ }
+
/**
* These are the messages that originate from the server
*
@@ -875,7 +916,10 @@ public static void addGlobalMessage(String message, List targets) {
}
public static Campaign getCampaign() {
- return client.getCampaign();
+ if (campaign == null) {
+ campaign = CampaignFactory.createBasicCampaign();
+ }
+ return campaign;
}
public static MapToolLineParser getParser() {
@@ -887,15 +931,17 @@ public static void setCampaign(Campaign campaign) {
}
public static void setCampaign(Campaign campaign, GUID defaultRendererId) {
- campaign = Objects.requireNonNullElseGet(campaign, Campaign::new);
-
// Load up the new
- client.setCampaign(campaign);
+ MapTool.campaign = campaign;
ZoneRenderer currRenderer = null;
clientFrame.clearZoneRendererList();
clientFrame.getInitiativePanel().setZone(null);
clientFrame.clearTokenTree();
+ if (campaign == null) {
+ clientFrame.setCurrentZoneRenderer(null);
+ return;
+ }
// Install new campaign
for (Zone zone : campaign.getZones()) {
@@ -924,6 +970,10 @@ public static void setCampaign(Campaign campaign, GUID defaultRendererId) {
UserDefinedMacroFunctions.getInstance().handleCampaignLoadMacroEvent();
}
+ public static void setServerPolicy(ServerPolicy policy) {
+ serverPolicy = policy;
+ }
+
public static AssetTransferManager getAssetTransferManager() {
return assetTransferManager;
}
@@ -932,80 +982,69 @@ public static AssetTransferManager getAssetTransferManager() {
* Start the server from a campaign file and various settings.
*
* @param id the id of the server for announcement.
- * @param config the server configuration. Set to null only for a personal server.
+ * @param config the server configuration.
* @param policy the server policy configuration to use.
* @param campaign the campaign.
* @param playerDatabase the player database to use for the connection.
- * @throws IOException if we fail to start the new server. In this case, the new client and server
- * will be available via {@link #getServer()} and {@link #getClient()}, but neither will be in
- * a started state.
+ * @param copyCampaign should the campaign be a copy of the one provided.
+ * @throws IOException if new MapToolServer fails.
*/
public static void startServer(
String id,
- @Nullable ServerConfig config,
- boolean useUPnP,
+ ServerConfig config,
ServerPolicy policy,
Campaign campaign,
- ServerSidePlayerDatabase playerDatabase,
- LocalPlayer player)
+ PlayerDatabase playerDatabase,
+ boolean copyCampaign)
throws IOException {
- if (server != null && server.getState() == MapToolServer.State.Started) {
- log.error("A server is already running.", new Exception());
+ if (server != null) {
+ Thread.dumpStack();
showError("msg.error.alreadyRunningServer");
return;
}
assetTransferManager.flush();
- var connections = DirectConnection.create("local");
- server = new MapToolServer(id, new Campaign(campaign), config, useUPnP, policy, playerDatabase);
- client = new MapToolClient(server, campaign, player, connections.clientSide());
+ // TODO: the client and server campaign MUST be different objects.
+ // Figure out a better init method
+ server = new MapToolServer(config, policy, playerDatabase);
- if (!server.isPersonalServer()) {
- getFrame().getConnectionPanel().startHosting();
+ serverPolicy = server.getPolicy();
+ if (copyCampaign) {
+ server.setCampaign(new Campaign(campaign)); // copy of FoW depends on server policies
+ } else {
+ server.setCampaign(campaign);
}
- setUpClient(client);
- client
- .getConnection()
- .onCompleted(
- (success) -> {
- if (success) {
- // connected
- EventQueue.invokeLater(
- () -> {
- MapTool.getFrame()
- .getConnectionStatusPanel()
- .setStatus(ConnectionStatusPanel.Status.server);
- });
- } else {
- // This should never happen. But if it does, just tear everything back down.
- server.stop();
- }
- });
-
- server.start();
- try {
- client.start();
- } catch (IOException e) {
- // Oof. Server started but client can't.
- server.stop();
- throw e;
+ if (announcer != null) {
+ announcer.stop();
}
- if (!server.isPersonalServer()) {
- MapTool.addLocalMessage(
- MessageUtil.getFormattedSystemMsg(I18N.getText("msg.info.startServer")));
+ // Don't announce personal servers
+ if (!config.isPersonalServer()) {
+ announcer =
+ new ServiceAnnouncer(id, server.getConfig().getPort(), AppConstants.SERVICE_GROUP);
+ announcer.start();
+ }
+
+ // Registered ?
+ if (config.isServerRegistered() && !config.isPersonalServer()) {
+ try {
+ MapToolRegistry.RegisterResponse result =
+ MapToolRegistry.getInstance()
+ .registerInstance(config.getServerName(), config.getPort(), config.getUseWebRTC());
+ if (result == MapToolRegistry.RegisterResponse.NAME_EXISTS) {
+ MapTool.showError("msg.error.alreadyRegistered");
+ }
+ // TODO: I don't like this
+ } catch (Exception e) {
+ MapTool.showError("msg.error.failedCannotRegisterServer", e);
+ }
}
- // Adopt the local connection, no handshake required.
- connections.serverSide().open();
- server.addLocalConnection(connections.serverSide(), player);
- // Update the client, including running onCampaignLoad.
- setCampaign(
- client.getCampaign(),
- Optional.ofNullable(clientFrame.getCurrentZoneRenderer())
- .map(zr -> zr.getZone().getId())
- .orElse(null));
+ if (MapTool.isHostingServer()) {
+ getFrame().getConnectionPanel().startHosting();
+ }
+ server.start();
}
public static ThumbnailManager getThumbnailManager() {
@@ -1016,27 +1055,23 @@ public static ThumbnailManager getThumbnailManager() {
return thumbnailManager;
}
- /**
- * Shutdown the current server.
- *
- * The client must have already been disconnected if necessary.
- */
public static void stopServer() {
if (server == null) {
return;
}
+ disconnect();
server.stop();
+ server = null;
getFrame().getConnectionPanel().stopHosting();
}
public static List getPlayerList() {
- return client.getPlayerList();
+ return playerList;
}
/** Returns the list of non-gm names. */
public static List getNonGMs() {
- var playerList = client.getPlayerList();
List nonGMs = new ArrayList<>(playerList.size());
playerList.forEach(
player -> {
@@ -1049,7 +1084,6 @@ public static List getNonGMs() {
/** Returns the list of gm names. */
public static List getGMs() {
- var playerList = client.getPlayerList();
List gms = new ArrayList<>(playerList.size());
playerList.forEach(
player -> {
@@ -1060,6 +1094,22 @@ public static List getGMs() {
return gms;
}
+ /**
+ * checks if a specific player is connected to the game.
+ *
+ * @param player The name of the player to check.
+ * @return {@code true} if the player is connected otherwise {@code false}.
+ */
+ public static boolean isPlayerConnected(String player) {
+ for (int i = 0; i < playerList.size(); i++) {
+ Player p = playerList.get(i);
+ if (p.getName().equalsIgnoreCase(player)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static void removeZone(Zone zone) {
MapTool.serverCommand().removeZone(zone.getId());
MapTool.getFrame().removeZoneRenderer(MapTool.getFrame().getZoneRenderer(zone.getId()));
@@ -1105,59 +1155,69 @@ public static void addZone(Zone zone, boolean changeZone) {
}
}
- public static MapToolClient getClient() {
- return client;
- }
-
public static LocalPlayer getPlayer() {
- return client.getPlayer();
+ return player;
}
public static void startPersonalServer(Campaign campaign)
- throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
- var player = new LocalPlayer();
- startServer(
- "",
- null,
- false,
- new ServerPolicy(),
- campaign,
- PlayerDatabaseFactory.getPersonalServerPlayerDatabase(player),
- player);
+ throws IOException,
+ NoSuchAlgorithmException,
+ InvalidKeySpecException,
+ ExecutionException,
+ InterruptedException {
+ ServerConfig config = ServerConfig.createPersonalServerConfig();
+
+ PlayerDatabaseFactory.setCurrentPlayerDatabase(PERSONAL_SERVER);
+ PlayerDatabase playerDatabase = PlayerDatabaseFactory.getCurrentPlayerDatabase();
+ MapTool.startServer(null, config, new ServerPolicy(), campaign, playerDatabase, false);
+
+ String username = AppPreferences.getDefaultUserName();
+ LocalPlayer localPlayer = (LocalPlayer) playerDatabase.getPlayer(username);
+ // Connect to server
+ MapTool.createConnection(
+ config,
+ localPlayer,
+ () -> {
+ // connecting
+ MapTool.getFrame()
+ .getConnectionStatusPanel()
+ .setStatus(ConnectionStatusPanel.Status.server);
+ });
}
- private static void setUpClient(MapToolClient client) {
+ public static void createConnection(ServerConfig config, LocalPlayer player, Runnable onCompleted)
+ throws IOException, ExecutionException, InterruptedException {
+ MapTool.player = player;
MapTool.getFrame().getCommandPanel().clearAllIdentities();
- MapToolConnection clientConn = client.getConnection();
+ MapToolConnection clientConn = new MapToolConnection(config, player);
+
clientConn.addActivityListener(clientFrame.getActivityMonitor());
- clientConn.onCompleted(
- (success) -> {
- EventQueue.invokeLater(
- () -> {
- clientFrame.getLookupTablePanel().updateView();
- clientFrame.getInitiativePanel().updateView();
- });
+ clientConn.addDisconnectHandler(new ServerDisconnectHandler());
+
+ clientConn.setOnCompleted(
+ () -> {
+ clientConn.addMessageHandler(handler);
+ // LATER: I really, really, really don't like this startup pattern
+ if (clientConn.isAlive()) {
+ conn = clientConn;
+ }
+ clientFrame.getLookupTablePanel().updateView();
+ clientFrame.getInitiativePanel().updateView();
+ onCompleted.run();
});
+
+ clientConn.start();
}
- public static void connectToRemoteServer(
- ServerConfig config, LocalPlayer player, HandshakeCompletionObserver onCompleted)
- throws IOException {
- if (server != null && server.getState() == MapToolServer.State.Started) {
- log.error("A local server is still running.", new Exception());
- showError("msg.error.stillRunningServer");
- return;
+ public static void closeConnection() throws IOException {
+ if (conn != null) {
+ conn.close();
}
+ }
- var connection = ConnectionFactory.getInstance().createConnection(player.getName(), config);
-
- server = null;
- client = new MapToolClient(player, connection);
- setUpClient(client);
- client.getConnection().onCompleted(onCompleted);
-
- client.start();
+ public static MapToolConnection getConnection() {
+ return conn;
}
/** returns the current locale code. */
@@ -1167,23 +1227,54 @@ public static String getLanguage() {
/** returns whether the player is using a personal server. */
public static boolean isPersonalServer() {
- return server != null && server.isPersonalServer();
+ return server != null && server.getConfig().isPersonalServer();
}
/** returns whether the player is hosting a server - personal servers do not count. */
public static boolean isHostingServer() {
- return server != null && !server.isPersonalServer();
+ return server != null && !server.getConfig().isPersonalServer();
}
public static void disconnect() {
- client.close();
- new MapToolEventBus().getMainEventBus().post(new ServerDisconnected());
+ // Close UPnP port mapping if used
+ StartServerDialogPreferences serverProps = new StartServerDialogPreferences();
+ if (serverProps.getUseUPnP()) {
+ int port = serverProps.getPort();
+ UPnPUtil.closePort(port);
+ }
+ boolean isPersonalServer = isPersonalServer();
+
+ if (announcer != null) {
+ announcer.stop();
+ announcer = null;
+ }
+
+ // Unregister ourselves
+ if (server != null && server.getConfig().isServerRegistered() && !isPersonalServer) {
+ try {
+ MapToolRegistry.getInstance().unregisterInstance();
+ } catch (Throwable t) {
+ MapTool.showError("While unregistering server instance", t);
+ }
+ }
+
+ try {
+ if (conn != null && conn.isAlive()) {
+ conn.close();
+ }
+ } catch (IOException ioe) {
+ // This isn't critical, we're closing it anyway
+ log.debug("While closing connection", ioe);
+ }
+
+ new MapToolEventBus().getMainEventBus().post(new ServerStopped());
+ playerList.clear();
MapTool.getFrame()
.getConnectionStatusPanel()
.setStatus(ConnectionStatusPanel.Status.disconnected);
- if (!isPersonalServer()) {
+ if (!isPersonalServer) {
addLocalMessage(MessageUtil.getFormattedSystemMsg(I18N.getText("msg.info.disconnected")));
}
}
@@ -1274,10 +1365,18 @@ private static void postInitialize() {
// fire up autosaves
getAutoSaveManager().start();
+ taskbarFlasher = new TaskBarFlasher(clientFrame);
+
// Jamz: After preferences are loaded, Asset Tree and ImagePanel are out of sync,
// so after frame is all done loading we sync them back up.
MapTool.getFrame().getAssetPanel().getAssetTree().initialize();
+ // Set the Topology drawing mode to the last mode used for convenience
+ MapTool.getFrame()
+ .getCurrentZoneRenderer()
+ .getZone()
+ .setTopologyTypes(AppPreferences.getTopologyTypes());
+
// Register the instance that will listen for token hover events and create a stat sheet.
new MapToolEventBus().getMainEventBus().register(new StatSheetListener());
new MapToolEventBus().getMainEventBus().register(new TokenHoverListener());
@@ -1343,6 +1442,26 @@ public static boolean useToolTipsForUnformatedRolls() {
}
}
+ public static MTWebAppServer getWebAppServer() {
+ return webAppServer;
+ }
+
+ public static void startWebAppServer(final int port) {
+ try {
+ Thread webAppThread =
+ new Thread(
+ () -> {
+ webAppServer.setPort(port);
+ webAppServer.startServer();
+ });
+
+ webAppThread.start();
+ } catch (Exception e) { // TODO: This needs to be logged
+ System.out.println("Unable to start web server");
+ e.printStackTrace();
+ }
+ }
+
public static String getClientId() {
return clientId;
}
@@ -1364,8 +1483,10 @@ public void run() {
e.printStackTrace();
}
- ServerCommand command = client.getServerCommand();
- command.heartbeat(getPlayer().getName());
+ ServerCommand command = serverCommand;
+ if (command != null) {
+ command.heartbeat(getPlayer().getName());
+ }
}
}
}
@@ -1717,7 +1838,6 @@ public static void main(String[] args) {
EventQueue.invokeLater(
() -> {
initialize();
-
EventQueue.invokeLater(
() -> {
clientFrame.setVisible(true);
diff --git a/src/main/java/net/rptools/maptool/client/MapToolClient.java b/src/main/java/net/rptools/maptool/client/MapToolClient.java
deleted file mode 100644
index 62c3e4a837..0000000000
--- a/src/main/java/net/rptools/maptool/client/MapToolClient.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * This software Copyright by the RPTools.net development team, and
- * licensed under the Affero GPL Version 3 or, at your option, any later
- * version.
- *
- * MapTool Source Code 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.
- *
- * You should have received a copy of the GNU Affero General Public
- * License * along with this source Code. If not, please visit
- * and specifically the Affero license
- * text at .
- */
-package net.rptools.maptool.client;
-
-import java.awt.EventQueue;
-import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import javax.annotation.Nullable;
-import net.rptools.clientserver.simple.connection.Connection;
-import net.rptools.maptool.client.events.PlayerConnected;
-import net.rptools.maptool.client.events.PlayerDisconnected;
-import net.rptools.maptool.events.MapToolEventBus;
-import net.rptools.maptool.language.I18N;
-import net.rptools.maptool.model.Campaign;
-import net.rptools.maptool.model.CampaignFactory;
-import net.rptools.maptool.model.campaign.CampaignManager;
-import net.rptools.maptool.model.player.LocalPlayer;
-import net.rptools.maptool.model.player.Player;
-import net.rptools.maptool.model.player.PlayerDatabase;
-import net.rptools.maptool.model.player.PlayerDatabaseFactory;
-import net.rptools.maptool.server.ClientHandshake;
-import net.rptools.maptool.server.MapToolServer;
-import net.rptools.maptool.server.ServerCommand;
-import net.rptools.maptool.server.ServerPolicy;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * The client side of a client-server channel.
- *
- * This has nothing to do with the GUI, but represents those parts of the client that are needed
- * to interact with a server. Most of this used to exist as global state in {@link
- * net.rptools.maptool.client.MapTool} and elsewhere.
- */
-public class MapToolClient {
- private static final Logger log = LogManager.getLogger(MapToolClient.class);
-
- public enum State {
- New,
- Started,
- Connected,
- Closed
- }
-
- private final MapToolServer localServer;
- private final LocalPlayer player;
- private final PlayerDatabase playerDatabase;
-
- /** Case-insensitive ordered set of player names. */
- private final List playerList;
-
- private final MapToolConnection conn;
- private Campaign campaign;
- private ServerPolicy serverPolicy;
- private final ServerCommand serverCommand;
- private State currentState = State.New;
-
- private MapToolClient(
- @Nullable MapToolServer localServer,
- Campaign campaign,
- LocalPlayer player,
- Connection connection,
- ServerPolicy policy,
- PlayerDatabase playerDatabase) {
- this.localServer = localServer;
- this.campaign = campaign;
- this.player = player;
- this.playerDatabase = playerDatabase;
- this.playerList = new ArrayList<>();
- this.serverPolicy = new ServerPolicy(policy);
-
- this.conn =
- new MapToolConnection(
- connection, player, localServer == null ? new ClientHandshake(this, connection) : null);
-
- this.serverCommand = new ServerCommandClientImpl(this);
-
- this.conn.addDisconnectHandler(this::onDisconnect);
- this.conn.onCompleted(
- (success) -> {
- if (!success) {
- // Failed handshake. Disconnect from the server, but treat it as unexpected.
- this.conn.close();
- return;
- }
-
- if (transitionToState(State.Started, State.Connected)) {
- this.conn.addMessageHandler(new ClientMessageHandler(this));
- }
- });
- }
-
- /** Creates a client for a local server, whether personal or hosted. */
- public MapToolClient(
- MapToolServer localServer, Campaign campaign, LocalPlayer player, Connection connection) {
- this(
- localServer,
- campaign,
- player,
- connection,
- localServer.getPolicy(),
- localServer.getPlayerDatabase());
- }
-
- /**
- * Creates a client for use with a remote hosted server.
- *
- * @param player The player connecting to the server.
- */
- public MapToolClient(LocalPlayer player, Connection connection) {
- this(
- null,
- new Campaign(),
- player,
- connection,
- new ServerPolicy(),
- PlayerDatabaseFactory.getLocalPlayerDatabase(player));
- }
-
- /**
- * Transition from any state except {@code newState} to {@code newState}.
- *
- * @param newState The new state to set.
- */
- private boolean transitionToState(State newState) {
- if (currentState == newState) {
- log.warn(
- "Failed to transition to state {} because that is already the current state", newState);
- return false;
- } else {
- currentState = newState;
- return true;
- }
- }
-
- /**
- * Transition from {@code expectedState} to {@code newState}.
- *
- * @param expectedState The state to transition from
- * @param newState The new state to set.
- */
- private boolean transitionToState(State expectedState, State newState) {
- if (currentState != expectedState) {
- log.warn(
- "Failed to transition from state {} to state {} because the current state is actually {}",
- expectedState,
- newState,
- currentState);
- return false;
- } else {
- currentState = newState;
- return true;
- }
- }
-
- public State getState() {
- return currentState;
- }
-
- public void start() throws IOException {
- if (transitionToState(State.New, State.Started)) {
- try {
- conn.start();
- } catch (IOException e) {
- // Make sure we're in a reasonable state before propagating.
- log.error("Failed to start client", e);
- transitionToState(State.Closed);
- throw e;
- }
- }
- }
-
- public void close() {
- if (transitionToState(State.Closed)) {
- if (conn.isAlive()) {
- conn.close();
- }
-
- playerList.clear();
- }
- }
-
- public ServerCommand getServerCommand() {
- return serverCommand;
- }
-
- public LocalPlayer getPlayer() {
- return player;
- }
-
- public List getPlayerList() {
- return Collections.unmodifiableList(playerList);
- }
-
- public void addPlayer(Player player) {
- if (!playerList.contains(player)) {
- playerList.add(player);
- new MapToolEventBus().getMainEventBus().post(new PlayerConnected(player));
- playerDatabase.playerSignedIn(player);
-
- playerList.sort((arg0, arg1) -> arg0.getName().compareToIgnoreCase(arg1.getName()));
- }
- }
-
- public void removePlayer(Player player) {
- playerList.remove(player);
- new MapToolEventBus().getMainEventBus().post(new PlayerDisconnected(player));
- playerDatabase.playerSignedOut(player);
- }
-
- public boolean isPlayerConnected(String playerName) {
- return playerList.stream().anyMatch(p -> p.getName().equalsIgnoreCase(playerName));
- }
-
- public PlayerDatabase getPlayerDatabase() {
- return playerDatabase;
- }
-
- public MapToolConnection getConnection() {
- return conn;
- }
-
- /**
- * @return A copy of the client's server policy.
- */
- public ServerPolicy getServerPolicy() {
- return new ServerPolicy(serverPolicy);
- }
-
- /**
- * Sets the client's server policy.
- *
- * If this also needs to be updated remotely, call {@link
- * net.rptools.maptool.server.ServerCommand#setServerPolicy(net.rptools.maptool.server.ServerPolicy)}
- * as well.
- *
- * @param serverPolicy The new policy to set.
- */
- public void setServerPolicy(ServerPolicy serverPolicy) {
- this.serverPolicy = new ServerPolicy(serverPolicy);
- }
-
- public Campaign getCampaign() {
- return this.campaign;
- }
-
- public void setCampaign(Campaign campaign) {
- this.campaign = campaign;
- }
-
- private void onDisconnect(Connection connection) {
- /*
- * Three main cases:
- * 1. Expected disconnect. This will be part of a broader shutdown sequence and we don't need to
- * do anything to clean up client or server state.
- * 2. Unexpected disconnect for remote server. Common case due to remote server shutdown or
- * other lost connection. We need to clean up the connection, show an error to the user, and
- * start a new personal server with a blank campaign.
- * 3. Unexpected disconnect for local server. A rare case where we lost connection without
- * shutting down the server. We need to clean up the connection, stop the server, show an
- * error to the user, and start a new personal server with the current campaign.
- */
- var disconnectExpected = currentState == State.Closed;
-
- if (!disconnectExpected) {
- // Keep any local server campaign around in the new personal server.
- final var newPersonalServerCampaign =
- localServer == null ? CampaignFactory.createBasicCampaign() : localServer.getCampaign();
-
- // Make sure the connection state is cleaned up since we can't count on it having been done.
- MapTool.disconnect();
- MapTool.stopServer();
-
- EventQueue.invokeLater(
- () -> {
- var errorText = I18N.getText("msg.error.server.disconnected");
- var connectionError = connection.getError();
- var errorMessage =
- errorText + (connectionError != null ? (": " + connectionError) : "");
- MapTool.showError(errorMessage);
-
- // hide map so player doesn't get a brief GM view
- MapTool.getFrame().setCurrentZoneRenderer(null);
- MapTool.getFrame().getToolbarPanel().getMapselect().setVisible(true);
- MapTool.getFrame().getAssetPanel().enableAssets();
- new CampaignManager().clearCampaignData();
- MapTool.getFrame().getToolbarPanel().setTokenSelectionGroupEnabled(true);
-
- try {
- MapTool.startPersonalServer(newPersonalServerCampaign);
- } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
- MapTool.showError(I18N.getText("msg.error.server.cantrestart"), e);
- }
- });
- }
- }
-}
diff --git a/src/main/java/net/rptools/maptool/client/MapToolConnection.java b/src/main/java/net/rptools/maptool/client/MapToolConnection.java
index f73ec26dd0..320f5a0369 100644
--- a/src/main/java/net/rptools/maptool/client/MapToolConnection.java
+++ b/src/main/java/net/rptools/maptool/client/MapToolConnection.java
@@ -15,13 +15,14 @@
package net.rptools.maptool.client;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import net.rptools.clientserver.simple.DisconnectHandler;
+import java.util.concurrent.ExecutionException;
+import net.rptools.clientserver.ConnectionFactory;
import net.rptools.clientserver.simple.connection.Connection;
import net.rptools.maptool.client.ui.ActivityMonitorPanel;
import net.rptools.maptool.model.player.LocalPlayer;
+import net.rptools.maptool.server.ClientHandshake;
import net.rptools.maptool.server.Handshake;
+import net.rptools.maptool.server.ServerConfig;
import net.rptools.maptool.server.proto.Message;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -30,59 +31,51 @@
* @author trevor
*/
public class MapToolConnection {
- public interface HandshakeCompletionObserver {
- void onComplete(boolean success);
- }
/** Instance used for log messages. */
private static final Logger log = LogManager.getLogger(MapToolConnection.class);
private final LocalPlayer player;
- private final Connection connection;
- private final Handshake handshake;
- private final List onCompleted;
+ private Connection connection;
+ private Handshake handshake;
+ private Runnable onCompleted;
+
+ public MapToolConnection(ServerConfig config, LocalPlayer player) throws IOException {
- public MapToolConnection(Connection connection, LocalPlayer player, Handshake handshake) {
- this.connection = connection;
+ this.connection = ConnectionFactory.getInstance().createConnection(player.getName(), config);
this.player = player;
- this.handshake = handshake;
- onCompleted = new ArrayList<>();
+ this.handshake = new ClientHandshake(connection, player);
+ onCompleted = () -> {};
}
- public void onCompleted(HandshakeCompletionObserver onCompleted) {
- this.onCompleted.add(onCompleted);
+ public void setOnCompleted(Runnable onCompleted) {
+ if (onCompleted == null) this.onCompleted = () -> {};
+ else this.onCompleted = onCompleted;
}
- public void start() throws IOException {
- if (handshake == null) {
- // No handshake required. Transition immediately to connected.
- connection.open();
- for (final var callback : onCompleted) {
- callback.onComplete(true);
- }
- } else {
- handshake.whenComplete(
- (result, exception) -> {
+ public void start() throws IOException, ExecutionException, InterruptedException {
+ connection.addMessageHandler(handshake);
+ handshake.addObserver(
+ (ignore) -> {
+ connection.removeMessageHandler(handshake);
+ if (handshake.isSuccessful()) {
+ onCompleted.run();
+ } else {
+ // For client side only show the error message as its more likely to make sense
+ // for players, the exception is logged just in case more info is required
+ var exception = handshake.getException();
if (exception != null) {
- // For client side only show the error message as its more likely to make sense
- // for players, the exception is logged just in case more info is required
log.warn(exception);
- MapTool.showError(exception.getMessage());
- connection.close();
- for (final var callback : onCompleted) {
- callback.onComplete(false);
- }
- } else {
- for (final var callback : onCompleted) {
- callback.onComplete(true);
- }
}
- });
-
- // this triggers the handshake from the server side
- connection.open();
- handshake.startHandshake();
- }
+ MapTool.showError(handshake.getErrorMessage());
+ connection.close();
+ onCompleted.run();
+ AppActions.disconnectFromServer();
+ }
+ });
+ // this triggers the handshake from the server side
+ connection.open();
+ handshake.startHandshake();
}
public void addMessageHandler(ClientMessageHandler handler) {
@@ -93,7 +86,7 @@ public void addActivityListener(ActivityMonitorPanel activityMonitor) {
connection.addActivityListener(activityMonitor);
}
- public void addDisconnectHandler(DisconnectHandler serverDisconnectHandler) {
+ public void addDisconnectHandler(ServerDisconnectHandler serverDisconnectHandler) {
connection.addDisconnectHandler(serverDisconnectHandler);
}
@@ -101,12 +94,12 @@ public boolean isAlive() {
return connection.isAlive();
}
- public void close() {
+ public void close() throws IOException {
connection.close();
}
public void sendMessage(Message msg) {
- log.debug("{} sent {}", player.getName(), msg.getMessageTypeCase());
+ log.debug(player.getName() + " sent " + msg.getMessageTypeCase());
connection.sendMessage(msg.toByteArray());
}
}
diff --git a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java
index 3c93a0aedd..fc5c7d508a 100644
--- a/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java
+++ b/src/main/java/net/rptools/maptool/client/MapToolVariableResolver.java
@@ -343,8 +343,10 @@ public Set getVariables() {
protected void updateTokenProperty(Token token, String varname, String value) {
// this logic allows unit tests to execute MT script that changes token properties
// there should be no other context where we have no server of any kind
- MapTool.serverCommand()
- .updateTokenProperty(tokenInContext, Token.Update.setProperty, varname, value);
+ if (MapTool.serverCommand() != null)
+ MapTool.serverCommand()
+ .updateTokenProperty(tokenInContext, Token.Update.setProperty, varname, value);
+ else token.setProperty(varname, value);
}
@Override
diff --git a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java
index e1dbb46f86..33722cd2e4 100644
--- a/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java
+++ b/src/main/java/net/rptools/maptool/client/ServerCommandClientImpl.java
@@ -21,6 +21,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;
import net.rptools.lib.MD5Key;
import net.rptools.maptool.client.functions.ExecFunction;
@@ -50,14 +51,14 @@
* the {@link ServerMessageHandler ServerMessageHandler}
*/
public class ServerCommandClientImpl implements ServerCommand {
- private static final Logger log = LogManager.getLogger(ServerCommandClientImpl.class);
- private final MapToolClient client;
private final TimedEventQueue movementUpdateQueue = new TimedEventQueue(100);
+ private final LinkedBlockingQueue assetRetrieveQueue = new LinkedBlockingQueue();
+ private static final Logger log = LogManager.getLogger(ServerCommandClientImpl.class);
- public ServerCommandClientImpl(MapToolClient client) {
- this.client = client;
+ public ServerCommandClientImpl() {
movementUpdateQueue.start();
+ // new AssetRetrievalThread().start();
}
public void heartbeat(String data) {
@@ -87,7 +88,7 @@ public void setCampaign(Campaign campaign) {
}
public void setCampaignName(String name) {
- client.getCampaign().setName(name);
+ MapTool.getCampaign().setName(name);
MapTool.getFrame().setTitle();
var msg = SetCampaignNameMsg.newBuilder().setName(name);
makeServerCall(Message.newBuilder().setSetCampaignNameMsg(msg).build());
@@ -165,7 +166,7 @@ public void restoreZoneView(GUID zoneGUID) {
}
public void editToken(GUID zoneGUID, Token token) {
- client.getCampaign().getZone(zoneGUID).editToken(token);
+ MapTool.getCampaign().getZone(zoneGUID).editToken(token);
var msg = EditTokenMsg.newBuilder().setZoneGuid(zoneGUID.toString()).setToken(token.toDto());
makeServerCall(Message.newBuilder().setEditTokenMsg(msg).build());
}
@@ -174,7 +175,7 @@ public void putToken(GUID zoneGUID, Token token) {
// Hack to generate zone event. All functions that update tokens call this method
// after changing the token. But they don't tell the zone about it so classes
// waiting for the zone change event don't get it.
- client.getCampaign().getZone(zoneGUID).putToken(token);
+ MapTool.getCampaign().getZone(zoneGUID).putToken(token);
var msg = PutTokenMsg.newBuilder().setZoneGuid(zoneGUID.toString()).setToken(token.toDto());
makeServerCall(Message.newBuilder().setPutTokenMsg(msg).build());
}
@@ -182,7 +183,7 @@ public void putToken(GUID zoneGUID, Token token) {
@Override
public void removeToken(GUID zoneGUID, GUID tokenGUID) {
// delete local token immediately
- client.getCampaign().getZone(zoneGUID).removeToken(tokenGUID);
+ MapTool.getCampaign().getZone(zoneGUID).removeToken(tokenGUID);
var msg =
RemoveTokenMsg.newBuilder()
.setZoneGuid(zoneGUID.toString())
@@ -193,7 +194,7 @@ public void removeToken(GUID zoneGUID, GUID tokenGUID) {
@Override
public void removeTokens(GUID zoneGUID, List tokenGUIDs) {
// delete local tokens immediately
- client.getCampaign().getZone(zoneGUID).removeTokens(tokenGUIDs);
+ MapTool.getCampaign().getZone(zoneGUID).removeTokens(tokenGUIDs);
var msg = RemoveTokensMsg.newBuilder().setZoneGuid(zoneGUID.toString());
msg.addAllTokenGuid(tokenGUIDs.stream().map(t -> t.toString()).collect(Collectors.toList()));
makeServerCall(Message.newBuilder().setRemoveTokensMsg(msg).build());
@@ -418,7 +419,7 @@ public void exposePCArea(GUID zoneGUID) {
public void exposeFoW(GUID zoneGUID, Area area, Set selectedToks) {
// Expose locally right away.
- client.getCampaign().getZone(zoneGUID).exposeArea(area, selectedToks);
+ MapTool.getCampaign().getZone(zoneGUID).exposeArea(area, selectedToks);
var msg = ExposeFowMsg.newBuilder().setZoneGuid(zoneGUID.toString()).setArea(Mapper.map(area));
msg.addAllTokenGuid(selectedToks.stream().map(g -> g.toString()).collect(Collectors.toList()));
makeServerCall(Message.newBuilder().setExposeFowMsg(msg).build());
@@ -517,15 +518,9 @@ public void clearExposedArea(GUID zoneGUID, boolean globalOnly) {
makeServerCall(Message.newBuilder().setClearExposedAreaMsg(msg).build());
}
- private void makeServerCall(Message msg) {
- log.debug(
- "{} making server call {}; state is {}",
- client.getPlayer().getName(),
- msg.getMessageTypeCase(),
- client.getState());
-
- if (client.getState() == MapToolClient.State.Connected) {
- client.getConnection().sendMessage(msg);
+ private static void makeServerCall(Message msg) {
+ if (MapTool.getConnection() != null) {
+ MapTool.getConnection().sendMessage(msg);
}
}
@@ -802,7 +797,7 @@ public void updatePlayerStatus(Player player) {
* some time interval. If a new event arrives before the time interval elapses, it is replaced. In
* this way, only the most current version of the event is released.
*/
- private class TimedEventQueue extends Thread {
+ private static class TimedEventQueue extends Thread {
Message msg;
long delay;
diff --git a/src/main/java/net/rptools/maptool/client/ServerDisconnectHandler.java b/src/main/java/net/rptools/maptool/client/ServerDisconnectHandler.java
new file mode 100644
index 0000000000..a0345c0a35
--- /dev/null
+++ b/src/main/java/net/rptools/maptool/client/ServerDisconnectHandler.java
@@ -0,0 +1,65 @@
+/*
+ * This software Copyright by the RPTools.net development team, and
+ * licensed under the Affero GPL Version 3 or, at your option, any later
+ * version.
+ *
+ * MapTool Source Code 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.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License * along with this source Code. If not, please visit
+ * and specifically the Affero license
+ * text at .
+ */
+package net.rptools.maptool.client;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.concurrent.ExecutionException;
+import net.rptools.clientserver.simple.DisconnectHandler;
+import net.rptools.clientserver.simple.connection.Connection;
+import net.rptools.maptool.language.I18N;
+import net.rptools.maptool.model.CampaignFactory;
+import net.rptools.maptool.model.campaign.CampaignManager;
+
+/** This class handles when the server inexplicably disconnects */
+public class ServerDisconnectHandler implements DisconnectHandler {
+ // TODO: This is a temporary hack until I can come up with a cleaner mechanism
+ public static boolean disconnectExpected;
+
+ public void handleDisconnect(Connection connection) {
+ // Update internal state
+ MapTool.disconnect();
+
+ // TODO: attempt to reconnect if this was unexpected
+ if (!disconnectExpected) {
+ var errorText = I18N.getText("msg.error.server.disconnected");
+ var connectionError = connection.getError();
+ var errorMessage = errorText + (connectionError != null ? (": " + connectionError) : "");
+ MapTool.showError(errorMessage);
+
+ // hide map so player doesn't get a brief GM view
+ MapTool.getFrame().setCurrentZoneRenderer(null);
+ MapTool.getFrame().getToolbarPanel().getMapselect().setVisible(true);
+ MapTool.getFrame().getAssetPanel().enableAssets();
+ new CampaignManager().clearCampaignData();
+ MapTool.getFrame().getToolbarPanel().setTokenSelectionGroupEnabled(true);
+ try {
+ MapTool.startPersonalServer(CampaignFactory.createBasicCampaign());
+ } catch (IOException
+ | NoSuchAlgorithmException
+ | InvalidKeySpecException
+ | ExecutionException
+ | InterruptedException e) {
+ MapTool.showError(I18N.getText("msg.error.server.cantrestart"), e);
+ }
+ } else if (!MapTool.isPersonalServer() && !MapTool.isHostingServer()) {
+ // expected disconnect from someone else's server
+ // hide map so player doesn't get a brief GM view
+ MapTool.getFrame().setCurrentZoneRenderer(null);
+ }
+ disconnectExpected = false;
+ }
+}
diff --git a/src/main/java/net/rptools/maptool/client/events/ServerDisconnected.java b/src/main/java/net/rptools/maptool/client/events/ServerStopped.java
similarity index 94%
rename from src/main/java/net/rptools/maptool/client/events/ServerDisconnected.java
rename to src/main/java/net/rptools/maptool/client/events/ServerStopped.java
index 65eb33514a..1b21cdb367 100644
--- a/src/main/java/net/rptools/maptool/client/events/ServerDisconnected.java
+++ b/src/main/java/net/rptools/maptool/client/events/ServerStopped.java
@@ -14,4 +14,4 @@
*/
package net.rptools.maptool.client.events;
-public record ServerDisconnected() {}
+public record ServerStopped() {}
diff --git a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java
index f60cfafb37..ce14761020 100644
--- a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java
+++ b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java
@@ -251,7 +251,7 @@ protected JsonObject getDrawingJSONInfo(String functionName, Zone map, GUID guid
dinfo.addProperty("name", d.getName());
dinfo.addProperty("layer", el.getDrawable().getLayer().name());
dinfo.addProperty("type", getDrawbleType(d));
- dinfo.add("bounds", boundsToJSON(map, d));
+ dinfo.add("bounds", boundsToJSON(d));
dinfo.addProperty("penColor", paintToString(el.getPen().getPaint()));
dinfo.addProperty("fillColor", paintToString(el.getPen().getBackgroundPaint()));
dinfo.addProperty("opacity", el.getPen().getOpacity());
@@ -262,12 +262,12 @@ protected JsonObject getDrawingJSONInfo(String functionName, Zone map, GUID guid
return dinfo;
}
- private JsonObject boundsToJSON(Zone map, AbstractDrawing d) {
+ private JsonObject boundsToJSON(AbstractDrawing d) {
JsonObject binfo = new JsonObject();
- binfo.addProperty("x", d.getBounds(map).x);
- binfo.addProperty("y", d.getBounds(map).y);
- binfo.addProperty("width", d.getBounds(map).width);
- binfo.addProperty("height", d.getBounds(map).height);
+ binfo.addProperty("x", d.getBounds().x);
+ binfo.addProperty("y", d.getBounds().y);
+ binfo.addProperty("width", d.getBounds().width);
+ binfo.addProperty("height", d.getBounds().height);
return binfo;
}
diff --git a/src/main/java/net/rptools/maptool/client/functions/DrawingMiscFunctions.java b/src/main/java/net/rptools/maptool/client/functions/DrawingMiscFunctions.java
index 03dfee4c3e..2312355706 100644
--- a/src/main/java/net/rptools/maptool/client/functions/DrawingMiscFunctions.java
+++ b/src/main/java/net/rptools/maptool/client/functions/DrawingMiscFunctions.java
@@ -108,7 +108,7 @@ public Object childEvaluate(
private JsonArray getCrossedPoints(final Zone map, final DrawnElement de, final String pathStr) {
List