/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.networking; import java.awt.geom.Area; import java.util.List; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import com.t3.MD5Key; import com.t3.client.TabletopTool; import com.t3.guid.GUID; import com.t3.model.Asset; import com.t3.model.AssetManager; import com.t3.model.ExposedAreaMetaData; import com.t3.model.Label; import com.t3.model.MacroButtonProperties; import com.t3.model.Pointer; import com.t3.model.Token; import com.t3.model.Zone; import com.t3.model.Zone.VisionType; import com.t3.model.ZonePoint; import com.t3.model.campaign.Campaign; import com.t3.model.campaign.CampaignProperties; import com.t3.model.chat.TextMessage; import com.t3.model.drawing.Drawable; import com.t3.model.drawing.Pen; import com.t3.model.initiative.InitiativeList; import com.t3.model.initiative.InitiativeValue; public class ServerCommandClientImpl implements ServerCommand { private final TimedEventQueue movementUpdateQueue = new TimedEventQueue(100); private final LinkedBlockingQueue<MD5Key> assetRetrieveQueue = new LinkedBlockingQueue<MD5Key>(); public ServerCommandClientImpl() { movementUpdateQueue.start(); // new AssetRetrievalThread().start(); } @Override public void heartbeat(String data) { makeServerCall(NetworkCommand.heartbeat, data); } @Override public void movePointer(String player, int x, int y) { makeServerCall(NetworkCommand.movePointer, player, x, y); } @Override public void bootPlayer(String player) { makeServerCall(NetworkCommand.bootPlayer, player); } @Override public void setCampaign(Campaign campaign) { try { campaign.setBeingSerialized(true); makeServerCall(NetworkCommand.setCampaign, campaign); } finally { campaign.setBeingSerialized(false); } } @Override public void setVisionType(GUID zoneGUID, VisionType visionType) { makeServerCall(NetworkCommand.setVisionType, zoneGUID, visionType); } @Override public void updateCampaign(CampaignProperties properties) { makeServerCall(NetworkCommand.updateCampaign, properties); } @Override public void getZone(GUID zoneGUID) { makeServerCall(NetworkCommand.getZone, zoneGUID); } @Override public void putZone(Zone zone) { makeServerCall(NetworkCommand.putZone, zone); } @Override public void removeZone(GUID zoneGUID) { makeServerCall(NetworkCommand.removeZone, zoneGUID); } @Override public void renameZone(GUID zoneGUID, String name) { makeServerCall(NetworkCommand.renameZone, zoneGUID, name); } @Override public void putAsset(Asset asset) { makeServerCall(NetworkCommand.putAsset, asset); } @Override public void getAsset(MD5Key assetID) { makeServerCall(NetworkCommand.getAsset, assetID); } @Override public void removeAsset(MD5Key assetID) { makeServerCall(NetworkCommand.removeAsset, assetID); } @Override public void enforceZoneView(GUID zoneGUID, int x, int y, double scale, int width, int height) { makeServerCall(NetworkCommand.enforceZoneView, zoneGUID, x, y, scale, width, height); } @Override 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. TabletopTool.getCampaign().getZone(zoneGUID).putToken(token); makeServerCall(NetworkCommand.putToken, zoneGUID, token); } @Override public void removeToken(GUID zoneGUID, GUID tokenGUID) { makeServerCall(NetworkCommand.removeToken, zoneGUID, tokenGUID); } @Override public void putLabel(GUID zoneGUID, Label label) { makeServerCall(NetworkCommand.putLabel, zoneGUID, label); } @Override public void removeLabel(GUID zoneGUID, GUID labelGUID) { makeServerCall(NetworkCommand.removeLabel, zoneGUID, labelGUID); } @Override public void draw(GUID zoneGUID, Pen pen, Drawable drawable) { makeServerCall(NetworkCommand.draw, zoneGUID, pen, drawable); } @Override public void clearAllDrawings(GUID zoneGUID, Zone.Layer layer) { makeServerCall(NetworkCommand.clearAllDrawings, zoneGUID, layer); } @Override public void undoDraw(GUID zoneGUID, GUID drawableGUID) { makeServerCall(NetworkCommand.undoDraw, zoneGUID, drawableGUID); } @Override public void setZoneGridSize(GUID zoneGUID, int xOffset, int yOffset, int size, int color) { makeServerCall(NetworkCommand.setZoneGridSize, zoneGUID, xOffset, yOffset, size, color); } @Override public void setZoneVisibility(GUID zoneGUID, boolean visible) { makeServerCall(NetworkCommand.setZoneVisibility, zoneGUID, visible); } @Override public void message(TextMessage message) { makeServerCall(NetworkCommand.message, message); } @Override public void showPointer(String player, Pointer pointer) { makeServerCall(NetworkCommand.showPointer, player, pointer); } @Override public void hidePointer(String player) { makeServerCall(NetworkCommand.hidePointer, player); } @Override public void setLiveTypingLabel(String label, boolean show) { makeServerCall(NetworkCommand.setLiveTypingLabel, label, show); } @Override public void enforceNotification(Boolean enforce) { // TabletopTool.showInformation(enforce.toString()); makeServerCall(NetworkCommand.enforceNotification, enforce); } @Override public void startTokenMove(String playerId, GUID zoneGUID, GUID tokenGUID, Set<GUID> tokenList) { makeServerCall(NetworkCommand.startTokenMove, playerId, zoneGUID, tokenGUID, tokenList); } @Override public void stopTokenMove(GUID zoneGUID, GUID tokenGUID) { movementUpdateQueue.flush(); makeServerCall(NetworkCommand.stopTokenMove, zoneGUID, tokenGUID); } @Override public void updateTokenMove(GUID zoneGUID, GUID tokenGUID, int x, int y) { movementUpdateQueue.enqueue(NetworkCommand.updateTokenMove, zoneGUID, tokenGUID, x, y); } @Override public void toggleTokenMoveWaypoint(GUID zoneGUID, GUID tokenGUID, ZonePoint cp) { movementUpdateQueue.flush(); makeServerCall(NetworkCommand.toggleTokenMoveWaypoint, zoneGUID, tokenGUID, cp); } @Override public void addTopology(GUID zoneGUID, Area area) { makeServerCall(NetworkCommand.addTopology, zoneGUID, area); } @Override public void removeTopology(GUID zoneGUID, Area area) { makeServerCall(NetworkCommand.removeTopology, zoneGUID, area); } @Override public void exposePCArea(GUID zoneGUID) { makeServerCall(NetworkCommand.exposePCArea, zoneGUID); } @Override public void exposeFoW(GUID zoneGUID, Area area, Set<GUID> selectedToks) { makeServerCall(NetworkCommand.exposeFoW, zoneGUID, area, selectedToks); } @Override public void setFoW(GUID zoneGUID, Area area, Set<GUID> selectedToks) { makeServerCall(NetworkCommand.setFoW, zoneGUID, area, selectedToks); } @Override public void hideFoW(GUID zoneGUID, Area area, Set<GUID> selectedToks) { makeServerCall(NetworkCommand.hideFoW, zoneGUID, area, selectedToks); } @Override public void setZoneHasFoW(GUID zoneGUID, boolean hasFog) { makeServerCall(NetworkCommand.setZoneHasFoW, zoneGUID, hasFog); } @Override public void bringTokensToFront(GUID zoneGUID, Set<GUID> tokenList) { makeServerCall(NetworkCommand.bringTokensToFront, zoneGUID, tokenList); } @Override public void sendTokensToBack(GUID zoneGUID, Set<GUID> tokenList) { makeServerCall(NetworkCommand.sendTokensToBack, zoneGUID, tokenList); } @Override public void enforceZone(GUID zoneGUID) { makeServerCall(NetworkCommand.enforceZone, zoneGUID); } @Override public void setServerPolicy(ServerPolicy policy) { makeServerCall(NetworkCommand.setServerPolicy, policy); } @Override public void updateInitiative(InitiativeList list, Boolean ownerPermission) { makeServerCall(NetworkCommand.updateInitiative, list, ownerPermission); } @Override public void updateTokenInitiative(GUID zone, GUID token, Boolean holding, InitiativeValue state, Integer index) { makeServerCall(NetworkCommand.updateTokenInitiative, zone, token, holding, state, index); } @Override public void updateCampaignMacros(List<MacroButtonProperties> properties) { makeServerCall(NetworkCommand.updateCampaignMacros, properties); } private static void makeServerCall(NetworkCommand command, Object... params) { if (TabletopTool.getConnection() != null) { TabletopTool.getConnection().callMethod(command, params); } } @Override public void setBoard(GUID zoneGUID, MD5Key mapAssetId, int x, int y) { // First, ensure that the possibly new map texture is available on the client // note: This may not be the optimal solution... can't tell from available documentation. // it may send a texture that is already sent // it might be better to do it in the background(?) // there seem to be other ways to upload textures (?) (e.g. in T3Util) putAsset(AssetManager.getAsset(mapAssetId)); // Second, tell the client to change the zone's board info makeServerCall(NetworkCommand.setBoard, zoneGUID, mapAssetId, x, y); } /* * (non-Javadoc) * * @see com.t3.networking.ServerNetworkCommand#updateExposedAreaMeta(com.t3.GUID, * com.t3.GUID, com.t3.model.ExposedAreaMetaData) */ @Override public void updateExposedAreaMeta(GUID zoneGUID, GUID tokenExposedAreaGUID, ExposedAreaMetaData meta) { makeServerCall(NetworkCommand.updateExposedAreaMeta, zoneGUID, tokenExposedAreaGUID, meta); } /** * Some events become obsolete very quickly, such as dragging a token around. This queue always has exactly one * element, the more current version of the event. The event is then dispatched at 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 static class TimedEventQueue extends Thread { NetworkCommand command; Object[] params; long delay; Object sleepSemaphore = new Object(); public TimedEventQueue(long millidelay) { delay = millidelay; } public synchronized void enqueue(NetworkCommand command, Object... params) { this.command = command; this.params = params; } public synchronized void flush() { if (command != null) { makeServerCall(command, params); } command = null; params = null; } @Override public void run() { while (true) { flush(); synchronized (sleepSemaphore) { try { Thread.sleep(delay); } catch (InterruptedException ie) { // nothing to do } } } } } }