diff options
Diffstat (limited to 'chicken-wranglers/src/models/playermodel.cpp')
-rw-r--r-- | chicken-wranglers/src/models/playermodel.cpp | 466 |
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); +} |