summaryrefslogtreecommitdiffstats
path: root/chicken-wranglers/src/models/playermodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'chicken-wranglers/src/models/playermodel.cpp')
-rw-r--r--chicken-wranglers/src/models/playermodel.cpp466
1 files changed, 466 insertions, 0 deletions
diff --git a/chicken-wranglers/src/models/playermodel.cpp b/chicken-wranglers/src/models/playermodel.cpp
new file mode 100644
index 0000000..f1ccab0
--- /dev/null
+++ b/chicken-wranglers/src/models/playermodel.cpp
@@ -0,0 +1,466 @@
+/****************************************************************************
+**
+** This file is a part of QtChickenWranglers.
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).*
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+**
+** * 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.
+**
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) 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 OWNER 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."
+**
+****************************************************************************/
+
+#include "playermodel.h"
+
+#include "charactermodel.h"
+#include "networkconnection.h"
+
+#include <QtDeclarative>
+
+PlayerModel::PlayerModel(QObject *parent)
+ : QObject(parent)
+{
+}
+
+PlayerModel::PlayerModel(NetworkConnection *connection, QObject *parent)
+ : QObject(parent), m_character(0), m_connection(connection),
+ m_status(NoCharacter), m_direction(Global::DirectionStop),
+ m_isLeader(false), m_score(0)
+{
+ connect(connection, SIGNAL(received(NetworkMessage)),
+ this, SLOT(onNetworkMessageReceived(NetworkMessage)));
+}
+
+PlayerModel::~PlayerModel()
+{
+ if (!characterName().isEmpty())
+ emit characterReleased(characterName());
+}
+
+QString PlayerModel::characterPath() const
+{
+ if (m_character)
+ return m_character->path();
+ else
+ return QString();
+}
+
+QString PlayerModel::characterName() const
+{
+ if (m_character)
+ return m_character->name();
+ else
+ return QString();
+}
+
+bool PlayerModel::setCharacter(const QString &characterName)
+{
+ PlayerListModel *listModel = qobject_cast<PlayerListModel *>(parent());
+ if (!listModel) {
+ qWarning() << "Invalid PlayerListModel";
+ return false;
+ }
+
+ if (!listModel->availableCharacters().contains(characterName))
+ return false;
+
+ // TODO: Check if it is safe to set a new character when
+ // the player has already selected one.
+ if (m_character && m_character->name() == characterName) {
+ qWarning() << "Player has already an associated character";
+ return false;
+ }
+
+ m_character = new CharacterModel(characterName, this);
+ m_status = Ready;
+
+ emit characterPathChanged();
+ emit characterSelected(characterName);
+
+ return true;
+}
+
+bool PlayerModel::unsetCharacter(const QString &characterName)
+{
+ if (!m_character) {
+ qWarning() << "Player has no associated character";
+ return false;
+ }
+
+ m_character->deleteLater();
+ m_character = 0;
+
+ m_status = NoCharacter;
+
+ emit characterPathChanged();
+ emit characterReleased(characterName);
+
+ return true;
+}
+
+void PlayerModel::setLeader(bool isLeader)
+{
+ if (m_isLeader == isLeader)
+ return;
+
+ m_isLeader = isLeader;
+
+ notifyPlayerLeader(m_isLeader);
+}
+
+void PlayerModel::setScore(int score)
+{
+ if (m_score == score)
+ return;
+
+ m_score = score;
+
+ emit scoreChanged();
+}
+
+void PlayerModel::reset()
+{
+ // TODO: Check what we need to cleanup at this point
+ m_score = 0;
+ m_direction = Global::DirectionStop;
+}
+
+void PlayerModel::setDirection(Global::Direction direction)
+{
+ if (m_direction == direction)
+ return;
+
+ m_direction = direction;
+
+ emit directionChanged();
+}
+
+void PlayerModel::notifyCharacterAvailableList(const QStringList &availableCharacters)
+{
+ QString availableCharactersString = availableCharacters.join("|");
+
+ NetworkMessage message(CW::CharacterMessageType, CW::CharacterAvailableList);
+ message.setData(availableCharactersString.toLatin1());
+
+ m_connection->send(message);
+}
+
+void PlayerModel::notifyCharacterIsAvailable(const QString &characterName, bool available)
+{
+ QByteArray data;
+ data.append(characterName);
+ data.append("|");
+
+ if (available)
+ data.append("1");
+ else
+ data.append("0");
+
+ NetworkMessage message(CW::CharacterMessageType, CW::CharacterIsAvailable);
+ message.setData(data);
+
+ m_connection->send(message);
+}
+
+void PlayerModel::notifyPlayerLeader(bool isLeader)
+{
+ QByteArray data;
+
+ if (isLeader)
+ data.append("1");
+ else
+ data.append("0");
+
+ NetworkMessage message(CW::PlayerInfoMessageType, CW::PlayerInfoLeader);
+ message.setData(data);
+
+ m_connection->send(message);
+}
+
+void PlayerModel::onNetworkMessageReceived(const NetworkMessage &message)
+{
+ switch (message.type()) {
+ case CW::CharacterMessageType:
+ handleCharacterMessage(message);
+ break;
+ case CW::PlayerControlMessageType:
+ handlePlayerControlMessage(message);
+ break;
+ default:
+ qWarning("Invalid network message type");
+ }
+}
+
+void PlayerModel::handleCharacterMessage(const NetworkMessage &message)
+{
+ switch (message.content()) {
+ case CW::CharacterSelected:
+ setCharacter(QString(message.data()));
+ break;
+
+ case CW::CharacterReleased:
+ unsetCharacter(characterName());
+ break;
+
+ default:
+ qWarning("Invalid character message");
+ }
+}
+
+// FIXME: Move the two static functions below to a better place
+static Global::Direction playerControlToDirection(CW::PlayerControl control)
+{
+ QMap<CW::PlayerControl, Global::Direction> direction;
+
+ direction[CW::PlayerControlUp] = Global::DirectionUp;
+ direction[CW::PlayerControlDown] = Global::DirectionDown;
+ direction[CW::PlayerControlRight] = Global::DirectionRight;
+ direction[CW::PlayerControlLeft] = Global::DirectionLeft;
+ direction[CW::PlayerControlStop] = Global::DirectionStop;
+
+ return direction[control];
+}
+
+static Global::LaserDirection playerControlToLaserDirection(CW::PlayerControl control)
+{
+ QMap<CW::PlayerControl, Global::LaserDirection> laserDirection;
+
+ laserDirection[CW::PlayerControlLaserUp] = Global::LaserUp;
+ laserDirection[CW::PlayerControlLaserDown] = Global::LaserDown;
+ laserDirection[CW::PlayerControlLaserRight] = Global::LaserRight;
+ laserDirection[CW::PlayerControlLaserLeft] = Global::LaserLeft;
+
+ return laserDirection[control];
+}
+
+void PlayerModel::handlePlayerControlMessage(const NetworkMessage &message)
+{
+ CW::PlayerControl control = static_cast<CW::PlayerControl>(message.content());
+
+ switch (control) {
+ case CW::PlayerControlLaserUp:
+ case CW::PlayerControlLaserDown:
+ case CW::PlayerControlLaserLeft:
+ case CW::PlayerControlLaserRight:
+ requestLaserToggle(playerControlToLaserDirection(control));
+ break;
+ case CW::PlayerControlUp:
+ case CW::PlayerControlDown:
+ case CW::PlayerControlLeft:
+ case CW::PlayerControlRight:
+ case CW::PlayerControlStop:
+ setDirection(playerControlToDirection(control));
+ break;
+ default:
+ qWarning("Invalid player control message");
+ }
+}
+
+void PlayerModel::requestLaserToggle(Global::LaserDirection laserDirection)
+{
+ emit laserToggleRequested(laserDirection);
+}
+
+PlayerListModel::PlayerListModel(QObject *parent)
+ : QAbstractListModel(parent), m_playersReady(false)
+{
+ QHash<int, QByteArray> roles;
+
+ roles[StatusRole] = "status";
+ roles[CharacterPathRole] = "characterPath";
+
+ setRoleNames(roles);
+
+ qmlRegisterType<PlayerModel>("game.types", 1, 0, "PlayerModel");
+
+ m_availableCharacters = CharacterListModel::characterNameList();
+}
+
+void PlayerListModel::newPlayer(NetworkConnection *connection)
+{
+ PlayerModel *player = new PlayerModel(connection, this);
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ m_players << player;
+ endInsertRows();
+
+ connect(player, SIGNAL(characterPathChanged()),
+ this, SLOT(onCharacterPathChanged()));
+ connect(player, SIGNAL(characterSelected(QString)),
+ this, SLOT(onCharacterSelected(QString)));
+ connect(player, SIGNAL(characterReleased(QString)),
+ this, SLOT(onCharacterReleased(QString)));
+
+ // A new player has joined, so he is not yet until he
+ // has chosen a character.
+ setPlayersReady(false);
+
+ // Check if player is the first one connected, which
+ // makes him the leader. This enables some special
+ // abilities for his client, such as start/end a match.
+ if (m_players.size() == 1)
+ player->setLeader(true);
+
+ player->notifyCharacterAvailableList(m_availableCharacters);
+}
+
+void PlayerListModel::removePlayer(NetworkConnection *connection)
+{
+ foreach (PlayerModel *model, m_players) {
+ if (connection == model->connection()) {
+ int index = m_players.indexOf(model);
+
+ beginRemoveRows(QModelIndex(), index, index);
+ m_players.removeAt(index);
+ endRemoveRows();
+
+ model->deleteLater();
+ break;
+ }
+ }
+
+ checkPlayersReady();
+}
+
+int PlayerListModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+
+ return m_players.count();
+}
+
+QVariant PlayerListModel::data(const QModelIndex &index, int role) const
+{
+ if (index.row() < 0 || index.row() >= m_players.count())
+ return QVariant();
+
+ PlayerModel *player = m_players[index.row()];
+
+ if (role == StatusRole)
+ return player->status();
+ if (role == CharacterPathRole)
+ return player->characterPath();
+
+ return QVariant();
+}
+
+void PlayerListModel::reset()
+{
+ foreach (PlayerModel *player, m_players)
+ player->reset();
+}
+
+void PlayerListModel::setPlayersReady(bool playersReady)
+{
+ if (m_playersReady == playersReady)
+ return;
+
+ m_playersReady = playersReady;
+
+ emit playersReadyChanged();
+}
+
+void PlayerListModel::checkPlayersReady()
+{
+ if (m_players.size() == 0) {
+ setPlayersReady(false);
+ return;
+ }
+
+ foreach(PlayerModel *player, m_players) {
+ if (player->status() != PlayerModel::Ready) {
+ setPlayersReady(false);
+ return;
+ }
+ }
+
+ setPlayersReady(true);
+}
+
+PlayerModel *PlayerListModel::get(int index) const
+{
+ if (index < 0 || index >= m_players.count())
+ return 0;
+
+ return m_players.at(index);
+}
+
+void PlayerListModel::onCharacterPathChanged()
+{
+ PlayerModel *playerModel = qobject_cast<PlayerModel *>(sender());
+
+ int playerModelIndex = m_players.indexOf(playerModel);
+
+ emit dataChanged(index(playerModelIndex), index(playerModelIndex));
+}
+
+void PlayerListModel::onCharacterSelected(const QString &characterName)
+{
+ m_availableCharacters.removeAll(characterName);
+
+ PlayerModel *senderModel = qobject_cast<PlayerModel *>(sender());
+
+ foreach (PlayerModel *model, m_players)
+ if (senderModel != model)
+ model->notifyCharacterIsAvailable(characterName, false);
+
+ checkPlayersReady();
+}
+
+void PlayerListModel::onCharacterReleased(const QString &characterName)
+{
+ if (m_availableCharacters.contains(characterName))
+ return;
+
+ m_availableCharacters.append(characterName);
+
+ foreach (PlayerModel *model, m_players)
+ model->notifyCharacterIsAvailable(characterName, true);
+
+ checkPlayersReady();
+}
+
+void PlayerListModel::notifyMatchReady()
+{
+ NetworkMessage message(CW::MatchMessageType, CW::MatchReady);
+
+ foreach (PlayerModel *model, m_players)
+ model->connection()->send(message);
+}
+
+void PlayerListModel::notifyMatchFinished()
+{
+ NetworkMessage message(CW::MatchMessageType, CW::MatchFinished);
+
+ foreach (PlayerModel *model, m_players)
+ model->connection()->send(message);
+}