mirror of
https://github.com/islehorse/HISP.git
synced 2025-04-06 13:15:42 +12:00
338 lines
12 KiB
C#
Executable file
338 lines
12 KiB
C#
Executable file
using System;
|
|
using System.IO;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using HISP.Player;
|
|
using HISP.Game;
|
|
using HISP.Game.Horse;
|
|
|
|
namespace HISP.Server
|
|
{
|
|
class GameClient
|
|
{
|
|
public Socket ClientSocket;
|
|
public string RemoteIp;
|
|
|
|
public bool LoggedIn = false;
|
|
public User LoggedinUser;
|
|
|
|
private Thread recvPackets;
|
|
|
|
private Timer updateTimer;
|
|
private Timer inactivityTimer;
|
|
|
|
private Timer warnTimer;
|
|
private Timer kickTimer;
|
|
private Timer minuteTimer;
|
|
|
|
|
|
private int keepAliveInterval = 60 * 1000;
|
|
private int updateInterval = 60 * 1000;
|
|
|
|
private int totalMinutesElapsed = 0;
|
|
private int oneMinute = 60 * 1000;
|
|
private int warnInterval = GameServer.IdleWarning * 60 * 1000;
|
|
private int kickInterval = GameServer.IdleTimeout * 60 * 1000;
|
|
|
|
private void minuteTimerTick(object state)
|
|
{
|
|
totalMinutesElapsed++;
|
|
if (LoggedIn)
|
|
{
|
|
LoggedinUser.FreeMinutes -= 1;
|
|
if (LoggedinUser.FreeMinutes <= 0)
|
|
{
|
|
LoggedinUser.FreeMinutes = 0;
|
|
if (!LoggedinUser.Subscribed && !LoggedinUser.Moderator && !LoggedinUser.Administrator)
|
|
Kick(Messages.KickReasonNoTime);
|
|
}
|
|
|
|
// unsure of actural timings, would be more or less impossible to know
|
|
// without the original source code :(
|
|
// From testing hunger seemed to go down fastest, then thirst, and finally tiredness.
|
|
|
|
|
|
foreach(HorseInstance horse in LoggedinUser.HorseInventory.HorseList)
|
|
{
|
|
if (totalMinutesElapsed % 2 == 0)
|
|
{
|
|
horse.BasicStats.Thirst--;
|
|
horse.BasicStats.Hunger--;
|
|
}
|
|
if (totalMinutesElapsed % 2 == 0 && (horse.BasicStats.Thirst <= 100 || horse.BasicStats.Thirst <= 100 || horse.BasicStats.Tiredness <= 100))
|
|
horse.BasicStats.Health--;
|
|
|
|
if (totalMinutesElapsed % 60 == 0)
|
|
{
|
|
horse.BasicStats.Mood--;
|
|
horse.BasicStats.Shoes--;
|
|
horse.BasicStats.Tiredness--;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (totalMinutesElapsed % 1 == 0)
|
|
LoggedinUser.Thirst--;
|
|
|
|
if (totalMinutesElapsed % 5 == 0)
|
|
LoggedinUser.Hunger--;
|
|
|
|
if (totalMinutesElapsed % 10 == 0)
|
|
LoggedinUser.Tiredness--;
|
|
}
|
|
|
|
minuteTimer.Change(oneMinute, oneMinute);
|
|
}
|
|
private void keepAliveTimerTick(object state)
|
|
{
|
|
Logger.DebugPrint("Sending keep-alive packet to "+ LoggedinUser.Username);
|
|
byte[] updatePacket = PacketBuilder.CreateKeepAlive();
|
|
SendPacket(updatePacket);
|
|
}
|
|
|
|
private void warnTimerTick(object state)
|
|
{
|
|
Logger.DebugPrint("Sending inactivity warning to: " + RemoteIp);
|
|
byte[] chatPacket = PacketBuilder.CreateChat(Messages.FormatIdleWarningMessage(), PacketBuilder.CHAT_BOTTOM_RIGHT);
|
|
SendPacket(chatPacket);
|
|
if (LoggedIn)
|
|
LoggedinUser.Idle = true;
|
|
warnTimer.Dispose();
|
|
warnTimer = null;
|
|
|
|
}
|
|
|
|
private void kickTimerTick(object state)
|
|
{
|
|
Kick(Messages.FormatIdleKickMessage());
|
|
}
|
|
private void updateTimerTick(object state)
|
|
{
|
|
GameServer.UpdateWorld(this);
|
|
GameServer.UpdatePlayer(this);
|
|
}
|
|
public void Login(int id)
|
|
{
|
|
// Check for duplicate
|
|
foreach(GameClient Client in GameServer.ConnectedClients)
|
|
{
|
|
if(Client.LoggedIn)
|
|
{
|
|
if (Client.LoggedinUser.Id == id)
|
|
Client.Kick(Messages.KickReasonDuplicateLogin);
|
|
}
|
|
}
|
|
|
|
LoggedinUser = new User(this,id);
|
|
LoggedIn = true;
|
|
|
|
updateTimer = new Timer(new TimerCallback(updateTimerTick), null, updateInterval, updateInterval);
|
|
inactivityTimer = new Timer(new TimerCallback(keepAliveTimerTick), null, keepAliveInterval, keepAliveInterval);
|
|
}
|
|
private void receivePackets()
|
|
{
|
|
// HI1 Packets are terminates by 0x00 so we have to read until we receive that terminator
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
while(ClientSocket.Connected)
|
|
{
|
|
try
|
|
{
|
|
if (ClientSocket.Available >= 1)
|
|
{
|
|
byte[] buffer = new byte[ClientSocket.Available];
|
|
ClientSocket.Receive(buffer);
|
|
|
|
|
|
foreach (Byte b in buffer)
|
|
{
|
|
ms.WriteByte(b);
|
|
if (b == 0x00)
|
|
{
|
|
ms.Seek(0x00, SeekOrigin.Begin);
|
|
byte[] fullPacket = ms.ToArray();
|
|
parsePackets(fullPacket);
|
|
|
|
ms.Close();
|
|
ms = new MemoryStream();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
catch(SocketException e)
|
|
{
|
|
Logger.ErrorPrint("Socket exception occured: " + e.Message);
|
|
Disconnect();
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
private void parsePackets(byte[] Packet)
|
|
{
|
|
if (Packet.Length < 1)
|
|
{
|
|
Logger.ErrorPrint("Received an invalid packet (size: "+Packet.Length+")");
|
|
}
|
|
byte identifier = Packet[0];
|
|
|
|
// Reset timers
|
|
|
|
|
|
if (inactivityTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
|
|
{
|
|
if (LoggedIn)
|
|
LoggedinUser.Idle = false;
|
|
inactivityTimer.Change(keepAliveInterval, keepAliveInterval);
|
|
}
|
|
|
|
if (kickTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
|
|
kickTimer = new Timer(new TimerCallback(kickTimerTick), null, kickInterval, kickInterval);
|
|
|
|
if (warnTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
|
|
warnTimer = new Timer(new TimerCallback(warnTimerTick), null, warnInterval, warnInterval);
|
|
|
|
if (!LoggedIn) // Must be either login or policy-file-request
|
|
{
|
|
if (Encoding.UTF8.GetString(Packet).StartsWith("<policy-file-request/>")) // Policy File Request
|
|
{
|
|
GameServer.OnCrossdomainPolicyRequest(this);
|
|
}
|
|
switch (identifier)
|
|
{
|
|
case PacketBuilder.PACKET_LOGIN:
|
|
GameServer.OnLoginRequest(this, Packet);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (identifier)
|
|
{
|
|
case PacketBuilder.PACKET_LOGIN:
|
|
GameServer.OnUserInfoRequest(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_MOVE:
|
|
GameServer.OnMovementPacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_PLAYERINFO:
|
|
GameServer.OnPlayerInfoPacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_PLAYER:
|
|
GameServer.OnProfilePacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_CHAT:
|
|
GameServer.OnChatPacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_CLICK:
|
|
GameServer.OnClickPacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_KEEP_ALIVE:
|
|
GameServer.OnKeepAlive(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_TRANSPORT:
|
|
GameServer.OnTransportUsed(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_INVENTORY:
|
|
GameServer.OnInventoryRequested(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_DYNAMIC_BUTTON:
|
|
GameServer.OnDynamicButtonPressed(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_DYNAMIC_INPUT:
|
|
GameServer.OnDynamicInputReceived(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_ITEM_INTERACTION:
|
|
GameServer.OnItemInteraction(this,Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_QUIT:
|
|
GameServer.OnQuitPacket(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_NPC:
|
|
GameServer.OnNpcInteraction(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_SWFMODULE:
|
|
GameServer.OnSwfModuleCommunication(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_HORSE:
|
|
GameServer.OnHorseInteraction(this, Packet);
|
|
break;
|
|
case PacketBuilder.PACKET_WISH:
|
|
GameServer.OnWish(this, Packet);
|
|
break;
|
|
default:
|
|
Logger.ErrorPrint("Unimplemented Packet: " + BitConverter.ToString(Packet).Replace('-', ' '));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
|
|
if(updateTimer != null)
|
|
updateTimer.Dispose();
|
|
if(inactivityTimer != null)
|
|
inactivityTimer.Dispose();
|
|
if(warnTimer != null)
|
|
warnTimer.Dispose();
|
|
if(kickTimer != null)
|
|
kickTimer.Dispose();
|
|
|
|
GameServer.OnDisconnect(this);
|
|
LoggedIn = false;
|
|
LoggedinUser = null;
|
|
ClientSocket.Close();
|
|
ClientSocket.Dispose();
|
|
//recvPackets.Anort();
|
|
}
|
|
|
|
public void Kick(string Reason)
|
|
{
|
|
byte[] kickPacket = PacketBuilder.CreateKickMessage(Reason);
|
|
SendPacket(kickPacket);
|
|
Disconnect();
|
|
|
|
Logger.InfoPrint("CLIENT: "+RemoteIp+" KICKED for: "+Reason);
|
|
}
|
|
|
|
public void SendPacket(byte[] PacketData)
|
|
{
|
|
try
|
|
{
|
|
ClientSocket.Send(PacketData);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.ErrorPrint("Exception occured: " + e.Message);
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
public GameClient(Socket clientSocket)
|
|
{
|
|
ClientSocket = clientSocket;
|
|
RemoteIp = clientSocket.RemoteEndPoint.ToString();
|
|
|
|
Logger.DebugPrint("Client connected @ " + RemoteIp);
|
|
|
|
kickTimer = new Timer(new TimerCallback(kickTimerTick), null, kickInterval, kickInterval);
|
|
warnTimer = new Timer(new TimerCallback(warnTimerTick), null, warnInterval, warnInterval);
|
|
minuteTimer = new Timer(new TimerCallback(minuteTimerTick), null, oneMinute, oneMinute);
|
|
|
|
recvPackets = new Thread(() =>
|
|
{
|
|
receivePackets();
|
|
});
|
|
recvPackets.Start();
|
|
|
|
}
|
|
}
|
|
}
|