QTfrontend/netclient.cpp
author unc0rr
Thu, 28 Sep 2006 16:34:42 +0000
changeset 176 533d03041dcd
parent 127 ca70467bd0a8
child 183 57c2ef19f719
permissions -rw-r--r--
'h' marks out current hedgehog

/*
 * Hedgewars, a worms-like game
 * Copyright (c) 2005, 2006 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * Distributed under the terms of the BSD-modified licence:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * with the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <QMessageBox>
#include <QUuid>
#include "netclient.h"
#include "game.h"
#include "gameuiconfig.h"

HWNet::HWNet(GameUIConfig * config)
	: QObject()
{
	this->config = config;
	state = nsDisconnected;
	IRCmsg_cmd_text = new QRegExp("^[A-Z]+ :.+$");
	IRCmsg_number_param = new QRegExp("^:\\S+ [0-9]{3} .+$");
	IRCmsg_who_cmd_target = new QRegExp("^:\\S+ [A-Z]+ \\S+$"); // last \\S should mean 'the 1st char is not ":"'
	IRCmsg_who_cmd_text = new QRegExp("^:\\S+ [A-Z]+ :.+$");
	IRCmsg_who_cmd_target_text = new QRegExp("^:\\S+ [A-Z]+ \\S+ :.+$");
	isOp = false;
	teamsCount = 0;

	connect(&NetSocket, SIGNAL(readyRead()), this, SLOT(ClientRead()));
	connect(&NetSocket, SIGNAL(connected()), this, SLOT(OnConnect()));
	connect(&NetSocket, SIGNAL(disconnected()), this, SLOT(OnDisconnect()));
	connect(&NetSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
			SLOT(displayError(QAbstractSocket::SocketError)));
}

void HWNet::ClientRead()
{
	while (NetSocket.canReadLine())
	{
		ParseLine(NetSocket.readLine().trimmed());
	}
}

void HWNet::displayError(QAbstractSocket::SocketError socketError)
{
	switch (socketError)
	{
		case QAbstractSocket::RemoteHostClosedError:
			break;
		case QAbstractSocket::HostNotFoundError:
			QMessageBox::information(0, tr("Error"),
					tr("The host was not found. Please check the host name and port settings."));
			break;
		case QAbstractSocket::ConnectionRefusedError:
			QMessageBox::information(0, tr("Error"),
					tr("Connection refused"));
			break;
		default:
			QMessageBox::information(0, tr("Error"),
					NetSocket.errorString());
    }
}

void HWNet::Connect(const QString & hostName, quint16 port, const QString & nick)
{
	state = nsConnecting;
	NetSocket.connectToHost(hostName, port);
	mynick = nick;
	opnick = "";
	opCount = 0;
}


void HWNet::OnConnect()
{
	state = nsConnected;
	RawSendNet(QString("USER hwgame 1 2 Hedgewars game"));
	RawSendNet(QString("NICK %1").arg(mynick));
}

void HWNet::OnDisconnect()
{
	state = nsDisconnected;
}

void HWNet::Perform()
{

}

void HWNet::JoinGame(const QString & game)
{
	state = nsJoining;
	RawSendNet(QString("JOIN %1").arg(game));
}

void HWNet::Disconnect()
{
	switch (state)
	{
		case nsDisconnected:
		{
			break;
		}
		case nsConnecting:
		case nsQuitting:
		{
			NetSocket.disconnect();
			break;
		}
		default:
		{
			state = nsQuitting;
			RawSendNet(QString("QUIT :oops"));
		}
	}
}

void HWNet::RawSendNet(const QString & str)
{
	RawSendNet(str.toUtf8());
}

void HWNet::RawSendNet(const QByteArray & buf)
{
	if (buf.size() > 510) return;
	NetSocket.write(buf);
	NetSocket.write("\x0d\x0a", 2);
}

void HWNet::SendNet(const QByteArray & buf)
{
	if ((state == nsGaming) || (state == nsStarting))
	{
		QString msg = QString(buf.toBase64());
		if ((msg == "AUM=") && (mynick == opnick))
		{
			ConfigAsked();
		} else
		if (msg == "AT8=")
		{
			// its ping ("?")
		} else
		{
			if (state == nsGaming)
			{
				NetBuffer += buf;
			} else
			{
				RawSendNet(QString("PRIVMSG %1 :"MAGIC_CHAR MAGIC_CHAR"%2").arg(channel, msg));
			}
		}
	}
}

void HWNet::FlushNetBuf()
{
	if (NetBuffer.size() > 0)
	{
		RawSendNet(QString("PRIVMSG %1 :"MAGIC_CHAR MAGIC_CHAR"%2").arg(channel, QString(NetBuffer.toBase64())));
		NetBuffer.clear();
	}
}

void HWNet::ParseLine(const QByteArray & line)
{
	QString msg = QString::fromUtf8 (line.data(), line.size());
	//QMessageBox::information(0, "", msg);
	if (IRCmsg_cmd_text->exactMatch(msg))
	{
		msgcmd_textHandler(msg);
	} else
	if (IRCmsg_number_param->exactMatch(msg))
	{
		msgnumber_paramHandler(msg);
	} else
	if (IRCmsg_who_cmd_text->exactMatch(msg))
	{
		msgwho_cmd_textHandler(msg);
	} else
	if (IRCmsg_who_cmd_target->exactMatch(msg))
	{
		msgwho_cmd_targetHandler(msg);
	} else
	if (IRCmsg_who_cmd_target_text->exactMatch(msg))
	{
		msgwho_cmd_target_textHandler(msg);
	}
}

void HWNet::msgcmd_textHandler(const QString & msg)
{
	QStringList list = msg.split(" :");
	if (list[0] == "PING")
	{
		RawSendNet(QString("PONG %1").arg(list[1]));
	}
}

void HWNet::msgnumber_paramHandler(const QString & msg)
{
	int pos = msg.indexOf(" :");
	QString text = msg.mid(pos + 2);
	QStringList list = msg.mid(0, pos).split(" ");
	bool ok;
	quint16 number = list[1].toInt(&ok);
	if (!ok)
		return ;
	switch (number)
	{
		case 001 :
		{
			Perform();
			emit Connected();
			break;
		}
		case 322 : // RPL_LIST
		{
			emit AddGame(list[3]);
			break;
		}
		case 353 : // RPL_NAMREPLY
		{
			QStringList ops = text.split(" ").filter(QRegExp("^@\\S+$"));
			opCount += ops.size();
			if (ops.size() == 1)
			{
				opnick = ops[0].mid(1);
			}
			break;
		}
		case 366 : // RPL_ENDOFNAMES
		{
			if (opCount != 1)
			{
				opnick = "";
			}
			opCount = 0;
			break;
		}
		case 432 : // ERR_ERRONEUSNICKNAME
		case 433 : // ERR_NICKNAMEINUSE
		{
			QMessageBox::information(0, "Your net nickname is in use or cannot be used", msg);
			// ask for another nick
		}
	}
}

void HWNet::msgwho_cmd_targetHandler(const QString & msg)
{
	QStringList list = msg.split(" ");
	QString who = list[0].mid(1).split("!")[0];
	if (list[1] == "NICK")
	{
		if (mynick == who)
			mynick = list[2];
		if (opnick == who)
			opnick = list[2];
	}
}

void HWNet::msgwho_cmd_textHandler(const QString & msg)
{
	int pos = msg.indexOf(" :");
	QString text = msg.mid(pos + 2);
	QStringList list = msg.mid(0, pos).split(" ");
	QString who = list[0].mid(1).split("!")[0];
	if (list[1] == "JOIN")
	{
		if (who == mynick)
		{
			channel = text;
			state = nsJoined;
			emit EnteredGame();
			RawSendNet(QString("PRIVMSG %1 :Hello!").arg(channel));
		}
	}
}
void HWNet::msgwho_cmd_target_textHandler(const QString & msg)
{
	int pos = msg.indexOf(" :");
	QString text = msg.mid(pos + 2);
	QStringList list = msg.mid(0, pos).split(" ");
	QString who = list[0].mid(1).split("!")[0];
	if (list[1] == "PRIVMSG")
	{
		if (list[2] == opnick)
		{
			hwp_opmsg(who, text);
		} else
		if (list[2] == channel)
		{
			hwp_chanmsg(who, text);
		}
	}
}

void HWNet::hwp_opmsg(const QString & who, const QString & msg)
{
	if (state != nsJoined)
		return ;
	if (!msg.startsWith(MAGIC_CHAR))
		return ;
	QStringList list = msg.split(MAGIC_CHAR, QString::SkipEmptyParts);
	if (list[0] == "A")
	{
		list.removeFirst();
		if (list.size() != 9)
			return ;
		if (teamsCount < 5)
		{
			teams[teamsCount].nick = who;
			teams[teamsCount].hhs = list;
			teamsCount++;
			QString teamnames;
			for(int i = 0; i < teamsCount; i++)
			{
				teamnames += MAGIC_CHAR;
				teamnames += teams[i].hhs[0];
			}
			QString tmsg = QString(MAGIC_CHAR"=%2").arg(teamnames);
			RawSendNet(QString("PRIVMSG %1 :").arg(channel) + tmsg);
			hwp_chanmsg(mynick, tmsg);
		}
	}
}

void HWNet::ConfigAsked()
{
	configasks++;
	if (configasks == playerscnt)
	{
		quint32 color = 65535;
			#define ADD(a) { \
							QByteArray strmsg; \
							strmsg.append(a); \
							quint8 sz = strmsg.size(); \
							cache.append(QByteArray((char *)&sz, 1)); \
							cache.append(strmsg); \
							}
		{
			QByteArray cache;
			ADD("eseed " + seed);
			ADD("e$gmflags 0");
			ADD(QString("etheme %1").arg(config->GetRandomTheme()));
			QString _msg = MAGIC_CHAR MAGIC_CHAR + QString(cache.toBase64());
			RawSendNet(QString("PRIVMSG %1 :%2").arg(channel, _msg));
			hwp_chanmsg(mynick, _msg);
		}
		for (int i = 0; i < teamsCount; i++)
		{
			QString msg;
			msg = MAGIC_CHAR "T" MAGIC_CHAR + teams[i].nick + MAGIC_CHAR + teams[i].hhs.join(MAGIC_CHAR);
			RawSendNet(QString("PRIVMSG %1 :%2").arg(channel, msg));
			hwp_chanmsg(mynick, msg);
			QByteArray cache;
			ADD(QString("ecolor %1").arg(color));
			ADD("eadd hh0 0");
			ADD("eadd hh1 0");
			ADD("eadd hh2 0");
			ADD("eadd hh3 0");
			ADD("eadd hh4 0");
			#undef ADD
			QString _msg = MAGIC_CHAR MAGIC_CHAR + QString(cache.toBase64());
			RawSendNet(QString("PRIVMSG %1 :%2").arg(channel, _msg));
			hwp_chanmsg(mynick, _msg);
			color <<= 8;
		}
		SENDCFGSTRNET("!");
	}
}

void HWNet::hwp_chanmsg(const QString & who, const QString & msg)
{
	if ((state < nsJoined) || (state > nsGaming))
	{
		return ;
	}
	if (state == nsJoined)
	{
		if (msg.startsWith(MAGIC_CHAR"Start!") && (who == opnick))
		{
			state = nsStarting;
			RunGame();
			return ;
		}
		if (msg.startsWith(MAGIC_CHAR"="MAGIC_CHAR) && (who == opnick))
		{
			emit ChangeInTeams(msg.mid(3).split(MAGIC_CHAR));
		}
	}
	if (state == nsStarting)
	{
		if (msg == MAGIC_CHAR MAGIC_CHAR "AUM=")
		{
			if (mynick == opnick) ConfigAsked();
			return ;
		}
		if (msg == MAGIC_CHAR MAGIC_CHAR "ASE=")
		{
			state = nsGaming;
			TimerFlusher = new QTimer();
			connect(TimerFlusher, SIGNAL(timeout()), this, SLOT(FlushNetBuf()));
			TimerFlusher->start(2000);
		}
		if (msg.startsWith(MAGIC_CHAR"T"MAGIC_CHAR))
		{
			NetTeamAdded(msg.mid(3));
		}
	}
	if ((state < nsStarting) || (state > nsGaming))
	{
		return;
	}
	if (msg.startsWith(MAGIC_CHAR MAGIC_CHAR)) // HWP message
	{
		QByteArray em = QByteArray::fromBase64(msg.mid(2).toAscii());
		emit FromNet(em);
	} else // smth other
	{

	}
}

void HWNet::NetTeamAdded(const QString & msg)
{
	QStringList list = msg.split(MAGIC_CHAR, QString::SkipEmptyParts);
	if (list.size() != 10)
		return ;
	SENDCFGSTRLOC("eaddteam");
	if (list[0] == mynick)
	{
		emit LocalCFG(list[1]);
	} else
	{
		SENDCFGSTRLOC("erdriven");
		SENDCFGSTRLOC(QString("ename team %1").arg(list[1]));
		for (int i = 0; i < 8; i++)
		{
			SENDCFGSTRLOC(QString("ename hh%1 ").arg(i) + list[i + 2]);
		}
	}
}

void HWNet::AddTeam(const HWTeam & team)
{
	if (state != nsJoined)
	{
		return ;
	}
	RawSendNet(QString("PRIVMSG %1 :").arg(opnick) + MAGIC_CHAR "A" MAGIC_CHAR +
			team.TeamName + MAGIC_CHAR + team.HHName[0] + MAGIC_CHAR + team.HHName[1] + MAGIC_CHAR +
			team.HHName[2] + MAGIC_CHAR + team.HHName[3] + MAGIC_CHAR + team.HHName[4] + MAGIC_CHAR +
			team.HHName[5] + MAGIC_CHAR + team.HHName[6] + MAGIC_CHAR + team.HHName[7]);
}

void HWNet::StartGame()
{
	if ((opnick != mynick) || (state != nsJoined))
	{
		return ;
	}
	QStringList players;
	for (int i = 0; i < teamsCount; i++)
	{
		if (!players.contains(teams[i].nick))
		{
			players.append(teams[i].nick);
		}
	}
	playerscnt = players.size();
	configasks = 0;

	seed = QUuid::createUuid().toString();
	QString msg = QString(MAGIC_CHAR"Start!");
	RawSendNet(QString("PRIVMSG %1 :%2").arg(channel, msg));
	hwp_chanmsg(mynick, msg);
}

void HWNet::RunGame()
{
	HWGame * game = new HWGame(config, 0);
	connect(game, SIGNAL(SendNet(const QByteArray &)), this, SLOT(SendNet(const QByteArray &)));
	connect(this, SIGNAL(FromNet(const QByteArray &)), game, SLOT(FromNet(const QByteArray &)));
	connect(this, SIGNAL(LocalCFG(const QString &)), game, SLOT(LocalCFG(const QString &)));
	game->StartNet();
}