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

com.threerings.parlor.server.ParlorManager 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.parlor.server;

import com.google.inject.Inject;

import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;

import com.threerings.util.Name;

import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;

import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.server.BodyLocator;
import com.threerings.crowd.server.PlaceRegistry;

import com.threerings.parlor.client.ParlorService;
import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.data.ParlorMarshaller;
import com.threerings.parlor.game.data.GameConfig;

import static com.threerings.parlor.Log.log;

/**
 * The parlor manager is responsible for the parlor services in aggregate. This includes
 * maintaining the registry of active games, handling the necessary coordination for the
 * matchmaking services and anything else that falls outside the scope of an actual in-progress
 * game.
 */
public class ParlorManager
    implements ParlorCodes, ParlorProvider
{
    @Inject public ParlorManager (InvocationManager invmgr)
    {
        invmgr.registerProvider(this, ParlorMarshaller.class, PARLOR_GROUP);
    }

    // from interface ParlorProvider
    public void invite (ClientObject caller, Name invitee, GameConfig config,
                        ParlorService.InviteListener listener)
        throws InvocationException
    {
//         Log.info("Handling invite request [source=" + source + ", invitee=" + invitee +
//                  ", config=" + config + "].");

        BodyObject source = (BodyObject)caller;

        // ensure that the invitee is online at present
        BodyObject target = _locator.lookupBody(invitee);
        if (target == null) {
            throw new InvocationException(INVITEE_NOT_ONLINE);
        }

        int inviteId = invite(source, target, config);
        listener.inviteReceived(inviteId);
    }

    // from interface ParlorProvider
    public void respond (ClientObject caller, int inviteId, int code, Object arg,
                         ParlorService.InvocationListener listener)
    {
        // pass this on to the parlor manager
        respondToInvite((BodyObject)caller, inviteId, code, arg);
    }

    // from interface ParlorProvider
    public void cancel (ClientObject caller, int inviteId,
                        ParlorService.InvocationListener listener)
    {
        cancelInvite((BodyObject)caller, inviteId);
    }

    // from interface ParlorProvider
    public void startSolitaire (ClientObject caller, GameConfig config,
                                ParlorService.ConfirmListener listener)
        throws InvocationException
    {
        BodyObject user = (BodyObject)caller;

        log.debug("Processing start solitaire [caller=" + user.who() + ", config=" + config + "].");

        try {
            // just this fellow will be playing
            if (config.players == null || config.players.length == 0) {
                config.players = new Name[] { user.getVisibleName() };
            }

            // create the game manager and begin its initialization process
            createGameManager(config);

            // the game manager will notify the player that their game is
            // "ready", but tell the caller that we processed their request
            listener.requestProcessed();

        } catch (InstantiationException ie) {
            log.warning("Error instantiating game manager " +
                        "[for=" + caller.who() + ", config=" + config + "].", ie);
            throw new InvocationException(INTERNAL_ERROR);
        }
    }

    /**
     * Issues an invitation from the inviter to the invitee for a game as
     * described by the supplied config object.
     *
     * @param inviter the player initiating the invitation.
     * @param invitee the player being invited.
     * @param config the configuration of the game being proposed.
     *
     * @return the invitation identifier for the newly created invitation record.
     *
     * @exception InvocationException thrown if the invitation was not able to be processed for
     * some reason (like the invited player has requested not to be disturbed). The explanation
     * will be provided in the message data of the exception.
     */
    public int invite (BodyObject inviter, BodyObject invitee, GameConfig config)
        throws InvocationException
    {
//          Log.info("Received invitation request [inviter=" + inviter +
//                   ", invitee=" + invitee + ", config=" + config + "].");

        // here we should check to make sure the invitee hasn't muted the inviter, and that the
        // inviter isn't shunned and all that other access control type stuff

        // create a new invitation record for this invitation
        Invitation invite = new Invitation(inviter, invitee, config);

        // stick it in the pending invites table
        _invites.put(invite.inviteId, invite);

        // deliver an invite notification to the invitee
        ParlorSender.sendInvite(invitee, invite.inviteId, inviter.getVisibleName(), config);

        // and let the caller know the invite id we assigned
        return invite.inviteId;
    }

    /**
     * Effects a response to an invitation (accept, refuse or counter), made by the specified
     * source user with the specified arguments.
     *
     * @param source the body object of the user that is issuing this response.
     * @param inviteId the identifier of the invitation to which we are responding.
     * @param code the response code (either {@link #INVITATION_ACCEPTED}, {@link
     * #INVITATION_REFUSED} or {@link #INVITATION_COUNTERED}).
     * @param arg the argument that goes along with the response: an explanatory message in the
     * case of a refusal (the empty string, not null, if no message was provided) or the new game
     * configuration in the case of a counter-invitation.
     */
    public void respondToInvite (BodyObject source, int inviteId, int code, Object arg)
    {
        // look up the invitation
        Invitation invite = _invites.get(inviteId);
        if (invite == null) {
            log.warning("Requested to respond to non-existent invitation " +
                        "[source=" + source + ", inviteId=" + inviteId +
                        ", code=" + code + ", arg=" + arg + "].");
            return;
        }

        // make sure this response came from the proper person
        if (source != invite.invitee) {
            log.warning("Got response from non-invitee [source=" + source +
                        ", invite=" + invite + ", code=" + code +
                        ", arg=" + arg + "].");
            return;
        }

        // let the other user know that a response was made to this invitation
        ParlorSender.sendInviteResponse(
            invite.inviter, invite.inviteId, code, arg);

        switch (code) {
        case INVITATION_ACCEPTED:
            // the invitation was accepted, so we'll need to start up the game and get the
            // necessary balls rolling
            processAcceptedInvitation(invite);
            // and remove the invitation from the pending table
            _invites.remove(inviteId);
            break;

        case INVITATION_REFUSED:
            // remove the invitation record from the pending table as it is no longer pending
            _invites.remove(inviteId);
            break;

        case INVITATION_COUNTERED:
            // swap control of the invitation to the invitee
            invite.swapControl();
            break;

        default:
            log.warning("Requested to respond to invitation with unknown response code " +
                        "[source=" + source + ", invite=" + invite + ", code=" + code +
                        ", arg=" + arg + "].");
            break;
        }
    }

    /**
     * Requests that an outstanding invitation be cancelled.
     *
     * @param source the body object of the user that is making the request.
     * @param inviteId the unique id of the invitation to be cancelled.
     */
    public void cancelInvite (BodyObject source, int inviteId)
    {
        // TBD
    }

    /**
     * Starts up and configures the game manager for an accepted invitation.
     */
    protected void processAcceptedInvitation (Invitation invite)
    {
        try {
            log.info("Creating game manager [invite=" + invite + "].");

            // configure the game config with the player info
            invite.config.players = new Name[] { invite.invitee.getVisibleName(),
                                                 invite.inviter.getVisibleName() };

            // create the game manager and begin its initialization process; the game manager will
            // take care of notifying the players that the game has been created
            createGameManager(invite.config);

        } catch (Exception e) {
            log.warning("Unable to create game manager [invite=" + invite + "].", e);
        }
    }

    /**
     * Called to create our game managers.
     */
    protected void createGameManager (GameConfig config)
        throws InstantiationException, InvocationException
    {
        _plreg.createPlace(config);
    }

    /**
     * The invitation record is used by the parlor manager to keep track of pending invitations.
     */
    protected static class Invitation
    {
        /** The unique identifier for this invitation. */
        public int inviteId = _nextInviteId++;

        /** The person proposing the invitation. */
        public BodyObject inviter;

        /** The person to whom the invitation is proposed. */
        public BodyObject invitee;

        /** The configuration of the game being proposed. */
        public GameConfig config;

        /**
         * Constructs a new invitation with the specified participants and configuration.
         */
        public Invitation (BodyObject inviter, BodyObject invitee, GameConfig config)
        {
            this.inviter = inviter;
            this.invitee = invitee;
            this.config = config;
        }

        /**
         * Swaps the inviter and invitee which is necessary when the invitee responds with a
         * counter-invitation.
         */
        public void swapControl ()
        {
            BodyObject tmp = inviter;
            inviter = invitee;
            invitee = tmp;
        }
    }

    /** The place registry with which we operate. */
    @Inject protected PlaceRegistry _plreg;

    /** Used to look body objects up by name. */
    @Inject protected BodyLocator _locator;

    /** The table of pending invitations. */
    protected IntMap _invites = IntMaps.newHashIntMap();

    /** A counter used to generate unique identifiers for invitation records. */
    protected static int _nextInviteId = 0;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy