/*
* Copyright (c) 2009, Hamish Morgan. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the University of Sussex nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package locusts.client;
import java.io.IOException;
import locusts.client.renderer.RenderPanel;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import locusts.client.diamondtouch.DTBoundingBoxEvent;
import locusts.client.diamondtouch.DTHandler;
import locusts.client.diamondtouch.DTListener;
import locusts.client.diamondtouch.DTSegmentsEvent;
import locusts.common.ObservableList;
import locusts.common.Player;
import locusts.common.World;
import locusts.common.net.FullUpdateRequest;
import locusts.common.net.InputMessage;
import locusts.common.net.PlayerMessage;
import locusts.common.net.PlayerMessage.NewPlayerRequest;
import locusts.common.net.PlayerMessage.NewPlayerResponse;
import locusts.common.net.PlayerMessage.PlayerDetailsUpdate;
import locusts.common.net.WorldUpdateMessage;
import locusts.common.vibro.VTMessage;
import locusts.common.vibro.VTMessageHandler;
import org.apache.mina.common.IoSession;
import org.apache.mina.handler.demux.MessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that controls a single client view of the game.
*
* TODO: there can be multiple players per client.
*
*
* @author Hamish Morgan
*/
public class Client {
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
private final RenderPanel panel;
private final ClientNetHandler net;
private DTHandler dt;
private final ClientConfig config;
private List<ClientConfig> allConfigs;
/**
* Hold a list of all players using this client. Since there can be
* multiple mice and a touch table which can support up to 8 players,
* there can be an unbounded number of players.
*/
private HashMap<String, Player> clientPlayers;
private ObservableList<Player> allPlayers;
public Client(ClientConfig config) {
allPlayers = new ObservableList<Player>();
this.config = config;
allConfigs = new ArrayList<ClientConfig>();
allConfigs.add(config);
clientPlayers = new HashMap<String, Player>();
panel = new RenderPanel(config, allPlayers);
panel.start();
panel.getOvRenderer().setClientConfigs(allConfigs);
net = new ClientNetHandler(config);
net.addMessageHandler(WorldUpdateMessage.class,
new WorldMessageHandler());
net.addMessageHandler(String.class, new TextHandler());
net.addMessageHandler(PlayerMessage.class, new PlayerMessageHandler());
try {
net.addMessageHandler(VTMessage.class, new VTMessageHandler());
} catch (Exception e) {
net.addMessageHandler(VTMessage.class, MessageHandler.NOOP);
LOG.error(null, e);
}
net.addMessageHandler(ClientConfig.class, new ClientConfigHandler());
net.addMessageHandler(ObservableList.class, new MessageHandler<ObservableList>() {
public void messageReceived(IoSession session,
ObservableList message) throws Exception {
if (message.isEmpty() || !(message.get(0) instanceof Player))
return;
allPlayers.clear();
allPlayers.addAll(message);
// throw new UnsupportedOperationException("Not supported yet.");
}
});
final MouseInput mouseinput = new MouseInput();
panel.addMouseListener(mouseinput);
panel.addMouseMotionListener(mouseinput);
panel.addMouseWheelListener(mouseinput);
panel.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_U) {
net.write(new FullUpdateRequest());
LOG.info("Full update requested....");
}
}
});
if (config.isTouchEnabled()) {
System.out.println("Starting diamond touch handler...");
try {
dt = new DTHandler();
dt.addListener(new TableInput());
dt.getTransform().watchComponent(panel);
dt.start();
} catch (IOException ex) {
dt = null;
LOG.error(null, ex);
}
} else {
dt = null;
}
}
private class ClientConfigHandler implements MessageHandler<ClientConfig> {
public void messageReceived(IoSession session, ClientConfig message)
throws Exception {
// System.out.println("ClientConfig recieved");
if (config.getId() == message.getId())
config.set(message);
else {
final ListIterator<ClientConfig> it =
allConfigs.listIterator();
boolean found = false;
while (!found && it.hasNext()) {
final ClientConfig cfg = it.next();
if (cfg.getId() == message.getId()) {
cfg.set(message);
found = true;
}
}
if (!found)
allConfigs.add(message);
}
}
}
public RenderPanel getPanel() {
return panel;
}
/**
* Handle events produced by a diamond touch table handler (DTHander)
* class, convert them to game space, and sent them on the network.
*/
private class TableInput implements DTListener {
private DTBoundingBoxEvent convertToWorldSpace(DTBoundingBoxEvent e)
throws NoninvertibleTransformException {
// final AffineTransform at = panel.keyList.getTransform();
// at.concatenate(config.getTransform());
AffineTransform at = config.getTransform();
;
// at.concatenate(panel.keyList.getTransform());
// at.concatenate(at);
Point2D origin =
new Point2D.Double(e.getXOrigin(), e.getYOrigin());
Point2D corner =
new Point2D.Double(e.getXCorner(), e.getYCorner());
at.inverseTransform(origin, origin);
at.inverseTransform(corner, corner);
return new DTBoundingBoxEvent(e.getSource(), e.getUserId(),
e.getTime(), e.getAction(),
(int) origin.getX(), (int) origin.getY(),
(int) corner.getX(), (int) corner.getY());
}
public void segmentsReceived(DTSegmentsEvent msg) {
LOG.warn("Ignoring DTSegments event.");
}
private void sendInputMessage(DTBoundingBoxEvent msg,
InputMessage.Type type) {
if (panel.getWorld().getState() == World.State.GAME_OVER)
return;
try {
final String localId = "DT-" + msg.getUserId();
if (!clientPlayers.containsKey(localId)) {
net.write(new NewPlayerRequest(localId));
// if (!net.containsPlayer(localId)) {
// request a new player data.
clientPlayers.put(localId, null);
} else if (clientPlayers.get(localId) == null) {
// request was sent but response not yet received
} else {
DTBoundingBoxEvent wmsg = convertToWorldSpace(msg);
InputMessage im = new InputMessage.Touch(
wmsg.getCenterX(), wmsg.getCenterY(),
wmsg.getWidth(), wmsg.getHeight(),
clientPlayers.get(localId),
type);
net.write(im);
}
} catch (NoninvertibleTransformException ex) {
LOG.warn("Failed to send touch event packet.", ex);
}
}
public void boundingBoxDown(DTBoundingBoxEvent msg) {
sendInputMessage(msg, InputMessage.Type.PRESS);
}
public void boundingBoxMoved(DTBoundingBoxEvent msg) {
sendInputMessage(msg, InputMessage.Type.DRAG);
}
public void boundingBoxUp(DTBoundingBoxEvent msg) {
sendInputMessage(msg, InputMessage.Type.RELEASE);
}
}
/**
* Handle mouse input, translate messages to game space, and sent them on
* the network.
*/
private class MouseInput implements MouseListener, MouseMotionListener,
MouseWheelListener {
private MouseEvent convertEventToWorldspace(MouseEvent e) {
Point2D p = new Point2D.Double(e.getX(), e.getY());
// AffineTransform at = panel.keyList.getTransform();
// at.concatenate(config.getTransform());
AffineTransform at = config.getTransform();
// at.concatenate(panel.keyList.getTransform());
try {
final Point2D q = at.inverseTransform(p, null);
if (e instanceof MouseWheelEvent) {
MouseWheelEvent f = (MouseWheelEvent) e;
return new MouseWheelEvent(f.getComponent(), f.getID(), f.
getWhen(), f.getModifiers(), (int) q.getX(),
(int) q.getY(), f.getClickCount(), f.
isPopupTrigger(), f.getScrollType(), f.
getScrollAmount(),
f.getWheelRotation());
} else {
return new MouseEvent(e.getComponent(), e.getID(), e.
getWhen(),
e.getModifiers(), (int) q.getX(), (int) q.getY(),
e.getClickCount(), e.isPopupTrigger(),
e.getButton());
}
} catch (NoninvertibleTransformException ex) {
LOG.error("Failed to convert click to world space.", ex);
return e;
}
}
public void mousePressed(MouseEvent e) {
if (panel.getWorld().getState() == World.State.GAME_OVER)
return;
final String localId = "Mouse-0";
if (!clientPlayers.containsKey(localId)) {
net.write(new NewPlayerRequest(localId));
clientPlayers.put(localId, null);
} else if (clientPlayers.get(localId) == null) {
// request was sent but response not yet received
} else {
MouseEvent c = convertEventToWorldspace(e);
net.write(new InputMessage.Mouse(c.getX(), c.getY(),
clientPlayers.get(localId),
InputMessage.Type.PRESS));
}
}
public void mouseClicked(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
if (panel.getWorld().getState() == World.State.GAME_OVER)
return;
final String localId = "Mouse-0";
if (!clientPlayers.containsKey(localId)) {
net.write(new NewPlayerRequest(localId));
clientPlayers.put(localId, null);
} else if (clientPlayers.get(localId) == null) {
// request was sent but response not yet received
} else {
MouseEvent c = convertEventToWorldspace(e);
net.write(new InputMessage.Mouse(c.getX(), c.getY(),
clientPlayers.get(localId), InputMessage.Type.RELEASE));
}
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
if (panel.getWorld().getState() == World.State.GAME_OVER)
return;
final String localId = "Mouse-0";
if (!clientPlayers.containsKey(localId)) {
net.write(new NewPlayerRequest(localId));
clientPlayers.put(localId, null);
} else if (clientPlayers.get(localId) == null) {
// request was sent but response not yet received
} else {
final MouseEvent c = convertEventToWorldspace(e);
net.write(new InputMessage.Mouse(c.getX(), c.getY(),
clientPlayers.get(localId), InputMessage.Type.DRAG));
}
}
public void mouseMoved(MouseEvent e) {
}
public void mouseWheelMoved(MouseWheelEvent e) {
}
}
class WorldMessageHandler implements MessageHandler<WorldUpdateMessage> {
public void messageReceived(IoSession session, WorldUpdateMessage msg)
throws Exception {
final World world = panel.getWorld();
msg.applyTo(world);
}
}
private class PlayerMessageHandler implements
MessageHandler<PlayerMessage> {
public void messageReceived(IoSession session, PlayerMessage message)
throws Exception {
if (message.getClass().equals(NewPlayerRequest.class)) {
handleMessage((NewPlayerRequest) message);
} else if (message.getClass().equals(NewPlayerResponse.class)) {
handleMessage((NewPlayerResponse) message);
} else if (message.getClass().equals(PlayerDetailsUpdate.class)) {
handleMessage((PlayerDetailsUpdate) message);
}
}
public void handleMessage(NewPlayerRequest message) {
// should not be recieved by a client ever!
}
public void handleMessage(NewPlayerResponse message) {
clientPlayers.put(message.getLocalId(), message.getPlayer());
}
public void handleMessage(PlayerDetailsUpdate message) {
for (Player player : clientPlayers.values()) {
if (player.getId() == message.getPlayer().getId()) {
message.applyTo(player);
return;
} else {
// the player info applies to someone else
}
}
LOG.warn("Player not found for update message.");
}
}
private class TextHandler implements MessageHandler<String> {
public void messageReceived(IoSession session, String message)
throws Exception {
System.out.println("Recieved Message From Server:: " + message);
}
}
}
|