Imported Upstream version 2008.1+svn1648
[opeanno-debian-packaging.git] / game / network.py
blob1d69847492312ca58dcb3084d114698856832cc0
1 # ###################################################
2 # Copyright (C) 2008 The OpenAnno Team
3 # team@openanno.org
4 # This file is part of OpenAnno.
6 # OpenAnno is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the
18 # Free Software Foundation, Inc.,
19 # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 # ###################################################
22 import time
23 import socket
24 import select
25 import pickle
26 import struct
27 import sys
28 from game.packets import *
30 # TODO: make networking robust
31 # (i.e. GUI freezes sometimes when waiting for timeout)
33 if sys.argv[0].lower().endswith('openanno.py'):
34 import game.main
36 class Socket(object):
37 """A socket which handles network communication, it sends and receives packets (packets=Objects of (sub)type Packet)
38 @param port: the port to listen on or 0 for auto choosing a port
39 """
40 def __init__(self, port = 0):
41 ## TODO: use ext_scheduler for socket
42 if sys.argv[0].lower().endswith('openanno.py'):
43 game.main.fife.pump.append(self._pump)
44 self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP)
45 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
46 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
47 self._socket.bind(('', port))
48 self.port = self._socket.getsockname()[1]
49 self.buffers = {}
51 def __del__(self):
52 self._socket.close()
54 def end(self):
55 if sys.argv[0].lower().endswith('openanno.py'):
56 game.main.fife.pump.remove(self._pump)
58 def _pump(self, forever = False):
59 """internal function which gets regularly called and checks for incoming packets
60 @param forever: used internally in masterserver, to not waste resources
61 """
62 #a packet is: OA<len><data>
63 while 1:
64 read, write, error = select.select([self._socket], [], [], *([] if forever else [0]))
65 if len(read) == 0:
66 break
67 try:
68 data, address = self._socket.recvfrom(1024)
69 except socket.error:
70 continue
71 if len(data) == 0:
72 continue
73 self.buffers[address] = (self.buffers[address] + data) if address in self.buffers else data
74 while 1:
75 if self.buffers[address][0:2] != 'OA':
76 del self.buffers[address]
77 break
78 if len(self.buffers[address]) < 6:
79 break
80 length = struct.unpack('I', self.buffers[address][2:6])[0]
81 if length + 6 > len(self.buffers[address]):
82 break
83 data = self.buffers[address][6:6 + length]
84 self.buffers[address] = self.buffers[address][6 + length:]
85 try:
86 packet = pickle.loads(data)
87 except pickle.PickleError:
88 continue
89 packet.address, packet.port = address
90 self.receive(packet)
91 if len(self.buffers[address]) == 0:
92 del self.buffers[address]
93 break
95 def send(self, packet):
96 """Send a packet
97 @param packet: the packet to send (packet = object of (sub)type Packet) (see packet.py
98 """
99 data = pickle.dumps(packet)
100 #print 'SEND', packet, 'TO', packet.address, packet.port
101 self._socket.sendto('OA' + struct.pack('I',len(data)) + data, (packet.address, packet.port))
103 def receive(self, packet):
104 """Hook this function to receive packets
105 @param packet: the incoming packet
107 pass
109 class MPPlayer(object):
111 @param name:
113 def __init__(self, address = None, port = None):
114 self.address, self.port = address, port
115 self.name, self.color, self.team = "unknown player", None, None
116 self.ready = False
118 def __str__(self):
119 playerstr = self.name
120 if self.color is not None:
121 playerstr += "(%s)" % self.color.name
122 return playerstr
124 class Connection(object):
125 """ Base Class for network connection
127 def __init__(self, port = 0):
128 self._socket = Socket(port)
129 self._socket.receive = self.onPacket
131 self.local_player = None
132 self.mpoptions = {}
133 self.mpoptions['players'] = []
134 self.mpoptions['slots'] = None
135 self.mpoptions['bots'] = None
136 self.mpoptions['maps'] = {}
137 self.mpoptions['selected_map'] = -1
139 def onPacket(self, packet):
140 """Called on packet receive
142 pass
144 def send(self, packet):
145 # if no address, send to all players
146 if packet.address is None and packet.port is None:
147 for player in self.mpoptions['players']:
148 packet.address, packet.port = player.address, player.port
149 if packet.address is None and packet.port is None:
150 self.onPacket(packet)
151 else:
152 self._socket.send(packet)
153 else:
154 self._socket.send(packet)
156 class ClientConnection(Connection):
157 """ Connection for a client
159 Use an instance of this class for
160 game.main.connectin on a client machine
163 keepAliveInterval = 1.5
165 STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED = range(0,3)
167 def __init__(self):
168 super(ClientConnection, self).__init__()
170 self.local_player = MPPlayer()
172 self.state = self.__class__.STATE_DISCONNECTED
174 def join(self, address, port):
175 self.address, self.port = address, port
176 self.sendToServer(LobbyJoinPacket(self.address, self.port, self.local_player))
178 game.main.ext_scheduler.add_new_object(self.sendKeepAlive, self, self.keepAliveInterval, -1)
180 def onPacket(self, packet):
181 #print 'RECV', packet,'FROM',packet.address,packet.port
182 packet.handleOnClient()
184 def sendKeepAlive(self):
185 """Telling server that i'm still there"""
186 self.sendToServer(LobbyKeepAlivePacket())
188 def reconnect(self):
189 if self.state not in (self.__class__.STATE_CONNECTING, self.__class__.STATE_DISCONNECTED):
190 self.doDisconnect()
191 if self._pump not in game.main.fife.pump:
192 game.main.fife.pump.append(self._pump)
193 self.send(ConnectPacket(self.address, self.port))
194 self.connectTime = time.time()
195 self.state = self.__class__.STATE_CONNECTING
197 def sendToServer(self, packet):
198 packet.address, packet.port = self.address, self.port
199 self.send(packet)
201 def end(self):
202 self._socket.receive = lambda a: None
203 game.main.ext_scheduler.rem_all_classinst_calls(self)
204 self.sendToServer(LeaveServerPacket())
206 def doChat(self, text):
208 @param text:
210 self.send(ChatPacket(text))
212 def doDisconnect(self):
213 self.send(DisconnectPacket(self.address, self.port))
215 def doPlayerModify(self, **settings):
217 @param **settings:
219 for name, value in settings.items():
220 self.send(PlayerModify(name, value))
222 def onTimeout(self):
223 pass
225 def onConnected(self):
226 """Called when connection to server is confirmed
228 pass
230 def onDisconnect(self):
231 pass
233 def onChat(self, player, text):
235 @param player:
236 @param text:
238 pass
240 def onPlayerPart(self, player):
242 @param player:
244 pass
246 def onPlayerModify(self, player):
248 @param player:
250 pass
252 def onServerSetting(self, settings):
254 @param settings:
256 pass
258 def onTickPacket(self, tick, commands):
260 @param tick:
261 @param commands:
263 pass
265 class ServerConnection(Connection):
266 """ Connection on a server
268 Use an instance of this class for
269 game.main.connectin on a game server
272 clientUpdateInterval = 2
273 clientTimeout = 5
274 registerTimeout = 120
276 def __init__(self, port = None):
277 super(ServerConnection, self).__init__(game.main.settings.network.port)
279 self.local_player = MPPlayer("127.0.0.1", port)
281 # here we save the time of the arrival of keep-alive packets
282 # last_client_message[(ip, port)] = timestamp
283 self.last_client_message = {}
285 self.register()
287 game.main.ext_scheduler.add_new_object(self.register, self, self.registerTimeout, -1)
288 game.main.ext_scheduler.add_new_object(self.check_client_timeout, self, self.clientTimeout, -1)
289 game.main.ext_scheduler.add_new_object(self.notifyClients, self, self.clientUpdateInterval, -1)
291 def end(self):
292 game.main.ext_scheduler.rem_all_classinst_calls(self)
293 self._socket.receive = lambda a: None
295 def notifyClients(self):
296 self.send(LobbyServerInfoPacket(self.mpoptions))
298 def check_client_timeout(self):
299 for client, last_reception in self.last_client_message.items():
300 if last_reception + self.clientTimeout < time.time():
301 for player in self.mpoptions['players']:
302 if player.address == client[0] and player.port == client[1]:
303 print "PLAYER TIMEOUT", client[0], client[1]
304 self.remove_player(player)
305 self.notifyClients()
306 break
308 def remove_player(self, player):
309 self.mpoptions['players'].remove(player)
310 del self.last_client_message[(player.address, player.port)]
312 def register(self):
313 """ Registers game server on master game server
315 self._socket.send(MasterRegisterPacket(self._socket.port))
316 self.registerTime = time.time()
318 def onPacket(self, packet):
319 #print 'RECV', packet,'FROM',packet.address,packet.port
320 packet.handleOnServer()