All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.whirled.spot.client.SpotSceneDirector Maven / Gradle / Ivy

The newest version!
//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.whirled.spot.client;

import com.samskivert.util.ResultListener;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.ConfirmListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;

import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.client.LocationAdapter;
import com.threerings.crowd.client.LocationDirector;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;

import com.threerings.whirled.client.SceneDirector;
import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.data.ScenePlace;
import com.threerings.whirled.data.SceneUpdate;
import com.threerings.whirled.spot.data.ClusteredBodyObject;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
import com.threerings.whirled.spot.data.SpotCodes;
import com.threerings.whirled.spot.data.SpotScene;
import com.threerings.whirled.util.WhirledContext;

import static com.threerings.whirled.spot.Log.log;

/**
 * Extends the standard scene director with facilities to move between locations within a scene.
 */
public class SpotSceneDirector extends BasicDirector
    implements SpotCodes, Subscriber, AttributeChangeListener
{
    /**
     * Creates a new spot scene director with the specified context and which will cooperate with
     * the supplied scene director.
     *
     * @param ctx the active client context.
     * @param locdir the location director with which we will be cooperating.
     * @param scdir the scene director with which we will be cooperating.
     */
    public SpotSceneDirector (WhirledContext ctx, LocationDirector locdir, SceneDirector scdir)
    {
        super(ctx);

        _ctx = ctx;
        _scdir = scdir;

        // wire ourselves up to hear about leave place notifications
        locdir.addLocationObserver(new LocationAdapter() {
            @Override
            public void locationDidChange (PlaceObject place) {
                // we need to clear some things out when we leave a place
                handleDeparture();
            }
        });
    }

    /**
     * Configures this spot scene director with a chat director, with which it will coordinate to
     * implement cluster chatting.
     */
    public void setChatDirector (ChatDirector chatdir)
    {
        _chatdir = chatdir;
    }

    /**
     * Returns our current location unless we have a location change pending, in which case our
     * pending location is returned.
     */
    public Location getIntendedLocation ()
    {
        return (_pendingLoc != null) ? _pendingLoc : _location;
    }

    /**
     * Requests that this client move to the location specified by the supplied portal id. A
     * request will be made and when the response is received, the location observers will be
     * notified of success or failure.
     *
     * @return true if the request was issued, false if it was rejected by a location observer or
     * because we have another request outstanding.
     */
    public boolean traversePortal (int portalId)
    {
        return traversePortal(portalId, null);
    }

    /**
     * Requests that this client move to the location specified by the supplied portal id. A
     * request will be made and when the response is received, the location observers will be
     * notified of success or failure.
     */
    public boolean traversePortal (int portalId, ResultListener rl)
    {
        // look up the destination scene and location
        SpotScene scene = (SpotScene)_scdir.getScene();
        if (scene == null) {
            log.warning("Requested to traverse portal when we have no scene", "portalId", portalId);
            return false;
        }

        // sanity check the server's notion of what scene we're in with our notion of it
        int sceneId = _scdir.getScene().getId();
        int clSceneId = ScenePlace.getSceneId((BodyObject)_ctx.getClient().getClientObject());
        if (sceneId != clSceneId) {
            log.warning("Client and server differ in opinion of what scene we're in",
                "sSceneId", clSceneId, "cSceneId", sceneId);
            return false;
        }

        // find the portal they're talking about
        Portal dest = scene.getPortal(portalId);
        if (dest == null) {
            log.warning("Requested to traverse non-existent portal",
                "portalId", portalId, "portals", scene.getPortals());
            return false;
        }

        // prepare to move to this scene (sets up pending data)
        if (!_scdir.prepareMoveTo(dest.targetSceneId, rl)) {
            log.info("Portal traversal vetoed by scene director", "portalId", portalId);
            return false;
        }

        // check the version of our cached copy of the scene to which we're requesting to move; if
        // we were unable to load it, assume a cached version of zero
        int sceneVer = 0;
        SceneModel pendingModel = _scdir.getPendingModel();
        if (pendingModel != null) {
            sceneVer = pendingModel.version;
        }

        // issue a traversePortal request
        log.info("Issuing traversePortal(" + sceneId + ", " + dest + ", " + sceneVer + ").");
        _sservice.traversePortal(sceneId, portalId, sceneVer,
            new SpotService.SpotSceneMoveListener() {
                public void requestFailed (String cause) {
                    _scdir.requestFailed(cause);
                }

                public void moveSucceeded (int placeId, PlaceConfig config) {
                    _scdir.moveSucceeded(placeId, config);
                }

                public void moveSucceededWithUpdates (
                        int placeId, PlaceConfig config, SceneUpdate[] updates) {
                    _scdir.moveSucceededWithUpdates(placeId, config, updates);
                }

                public void moveSucceededWithScene (int placeId, PlaceConfig config,
                        SceneModel model) {
                    _scdir.moveSucceededWithScene(placeId, config, model);
                }

                public void moveRequiresServerSwitch (String hostname, int[] ports) {
                    _scdir.moveRequiresServerSwitch(hostname, ports);
                }

                public void requestCancelled () {
                    _scdir.cancelMoveRequest();
                }
            });
        return true;
    }

    /**
     * Issues a request to change our location within the scene to the specified location.
     *
     * @param loc the new location to which to move.
     * @param listener will be notified of success or failure. Most client entities find out about
     * location changes via changes to the occupant info data, but the initiator of a location
     * change request can be notified of its success or failure, primarily so that it can act in
     * anticipation of a successful location change (like by starting a sprite moving toward the
     * new location), but backtrack if it finds out that the location change failed.
     */
    public void changeLocation (Location loc, final ResultListener listener)
    {
        // refuse if there's a pending location change or if we're already at the specified
        // location
        if (loc.equivalent(_location)) {
            log.info("Not going to " + loc + "; we're at " + _location + " and we're headed to " +
                     _pendingLoc + ".");
            if (listener != null) {
                // This isn't really a failure, it's just a no-op.
                listener.requestCompleted(_location);
            }
            return;
        }

        if (_pendingLoc != null) {
            log.info("Not going to " + loc + "; we're at " + _location + " and we're headed to " +
                     _pendingLoc + ".");
            if (listener != null) {
                // Already moving, best thing to do is ignore it.
                listener.requestCompleted(_pendingLoc);
            }
            return;
        }

        SpotScene scene = (SpotScene)_scdir.getScene();
        if (scene == null) {
            log.warning("Requested to change locations, but we're not currently in any scene",
                "loc", loc);
            if (listener != null) {
                listener.requestFailed(new Exception("m.cant_get_there"));
            }
            return;
        }

        int sceneId = _scdir.getScene().getId();
        log.info("Sending changeLocation request", "scid", sceneId, "loc", loc);

        _pendingLoc = loc.clone();
        ConfirmListener clist = new ConfirmListener() {
            public void requestProcessed () {
                _location = _pendingLoc;
                _pendingLoc = null;
                if (listener != null) {
                    listener.requestCompleted(_location);
                }
            }

            public void requestFailed (String reason) {
                _pendingLoc = null;
                if (listener != null) {
                    listener.requestFailed(new Exception(reason));
                }
            }
        };
        _sservice.changeLocation(sceneId, loc, clist);
    }

    /**
     * Issues a request to join the cluster associated with the specified user (starting one if
     * necessary).
     *
     * @param froid the bodyOid of another user; the calling user will be made to join the target
     * user's cluster.
     * @param listener will be notified of success or failure.
     */
    public void joinCluster (int froid, final ResultListener listener)
    {
        SpotScene scene = (SpotScene)_scdir.getScene();
        if (scene == null) {
            log.warning("Requested to join cluster, but we're not currently in any scene",
                "froid", froid);
            if (listener != null) {
                listener.requestFailed(new Exception("m.cant_get_there"));
            }
            return;
        }

        log.info("Joining cluster", "friend", froid);

        _sservice.joinCluster(froid, new ConfirmListener() {
            public void requestProcessed () {
                if (listener != null) {
                    listener.requestCompleted(null);
                }
            }
            public void requestFailed (String reason) {
                if (listener != null) {
                    listener.requestFailed(new Exception(reason));
                }
            }
        });
    }

    /**
     * Sends a chat message to the other users in the cluster to which the location that we
     * currently occupy belongs.
     *
     * @return true if a cluster speak message was delivered, false if we are not in a valid
     * cluster and refused to deliver the request.
     */
    public boolean requestClusterSpeak (String message)
    {
        return requestClusterSpeak(message, ChatCodes.DEFAULT_MODE);
    }

    /**
     * Sends a chat message to the other users in the cluster to which the location that we
     * currently occupy belongs.
     *
     * @return true if a cluster speak message was delivered, false if we are not in a valid
     * cluster and refused to deliver the request.
     */
    public boolean requestClusterSpeak (String message, byte mode)
    {
        // make sure we're currently in a scene
        SpotScene scene = (SpotScene)_scdir.getScene();
        if (scene == null) {
            log.warning("Requested to speak to cluster, but we're not currently in any scene",
                "message", message);
            return false;
        }

        // make sure we're part of a cluster
        if (_self.getClusterOid() <= 0) {
            log.info("Ignoring cluster speak as we're not in a cluster",
                "cloid", _self.getClusterOid());
            return false;
        }

        message = _chatdir.filter(message, null, true);
        if (message != null) {
            _sservice.clusterSpeak(message, mode);
        }
        return true;
    }

    // documentation inherited from interface
    public void objectAvailable (DObject object)
    {
        clearCluster(false);
        int oid = object.getOid();
        if (oid != _self.getClusterOid()) {
            // we got it too late, just unsubscribe
            _ctx.getDObjectManager().unsubscribeFromObject(oid, this);

        } else {
            // it's our new cluster!
            _clobj = object;
            if (_chatdir != null) {
                _chatdir.addAuxiliarySource(object, CLUSTER_CHAT_TYPE);
            }
        }
    }

    // documentation inherited from interface
    public void requestFailed (int oid, ObjectAccessException cause)
    {
        log.warning("Unable to subscribe to cluster chat object", "oid", oid, "cause", cause);
    }

    // documentation inherited from interface
    public void attributeChanged (AttributeChangedEvent event)
    {
        if (event.getName().equals(_self.getClusterField()) &&
            !event.getValue().equals(event.getOldValue())) {
            maybeUpdateCluster();
        }
    }

    @Override
    public void clientDidLogon (Client client)
    {
        super.clientDidLogon(client);

        ClientObject clientObj = client.getClientObject();
        if (clientObj instanceof ClusteredBodyObject) {
            // listen to the client object
            clientObj.addListener(this);
            _self = (ClusteredBodyObject) clientObj;

            // we may need to subscribe to a cluster due to session resumption
            maybeUpdateCluster();
        }
    }

    @Override
    public void clientObjectDidChange (Client client)
    {
        super.clientObjectDidChange(client);

        // listen to the client object
        ClientObject clientObj = client.getClientObject();
        clientObj.addListener(this);
        _self = (ClusteredBodyObject) clientObj;
    }

    @Override
    public void clientDidLogoff (Client client)
    {
        super.clientDidLogoff(client);

        // clear out our business
        _location = null;
        _pendingLoc = null;
        _sservice = null;
        clearCluster(true);

        // stop listening to the client object
        client.getClientObject().removeListener(this);
        _self = null;
    }

    @Override
    protected void fetchServices (Client client)
    {
        _sservice = client.requireService(SpotService.class);
    }

    /**
     * Clean up after a few things when we depart from a scene.
     */
    protected void handleDeparture ()
    {
        // clear out our last known location id
        _location = null;
    }

    /**
     * Checks to see if our cluster has changed and does the necessary subscription machinations if
     * necessary.
     */
    protected void maybeUpdateCluster ()
    {
        int cloid = _self.getClusterOid();
        if ((_clobj == null && cloid <= 0) || (_clobj != null && cloid == _clobj.getOid())) {
            // our cluster didn't change, we can stop now
            return;
        }

        // clear out any old cluster object
        clearCluster(false);

        // if there's a new cluster object, subscribe to it
        if (_chatdir != null && cloid > 0) {
            // we'll wire up to the chat director when this completes
            _ctx.getDObjectManager().subscribeToObject(cloid, this);
        }
    }

    /**
     * Convenience routine to unwire chat for and unsubscribe from our current cluster, if any.
     *
     * @param force clear the cluster even if we're still apparently in it.
     */
    protected void clearCluster (boolean force)
    {
        if (_clobj != null && (force || (_clobj.getOid() != _self.getClusterOid()))) {
            if (_chatdir != null) {
                _chatdir.removeAuxiliarySource(_clobj);
            }
            _ctx.getDObjectManager().unsubscribeFromObject(_clobj.getOid(), this);
            _clobj = null;
        }
    }

    /** The active client context. */
    protected WhirledContext _ctx;

    /** Access to spot scene services. */
    protected SpotService _sservice;

    /** The scene director with which we are cooperating. */
    protected SceneDirector _scdir;

    /** A casted reference to our clustered body object. */
    protected ClusteredBodyObject _self;

    /** A reference to the chat director with which we coordinate. */
    protected ChatDirector _chatdir;

    /** The location we currently occupy. */
    protected Location _location;

    /** The location to which we have an outstanding change location request. */
    protected Location _pendingLoc;

    /** The cluster chat object for the cluster we currently occupy. */
    protected DObject _clobj;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy