1 # ###################################################
2 # Copyright (C) 2008 The OpenAnno Team
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 # ###################################################
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'):
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
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]
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
62 #a packet is: OA<len><data>
64 read
, write
, error
= select
.select([self
._socket
], [], [], *([] if forever
else [0]))
68 data
, address
= self
._socket
.recvfrom(1024)
73 self
.buffers
[address
] = (self
.buffers
[address
] + data
) if address
in self
.buffers
else data
75 if self
.buffers
[address
][0:2] != 'OA':
76 del self
.buffers
[address
]
78 if len(self
.buffers
[address
]) < 6:
80 length
= struct
.unpack('I', self
.buffers
[address
][2:6])[0]
81 if length
+ 6 > len(self
.buffers
[address
]):
83 data
= self
.buffers
[address
][6:6 + length
]
84 self
.buffers
[address
] = self
.buffers
[address
][6 + length
:]
86 packet
= pickle
.loads(data
)
87 except pickle
.PickleError
:
89 packet
.address
, packet
.port
= address
91 if len(self
.buffers
[address
]) == 0:
92 del self
.buffers
[address
]
95 def send(self
, packet
):
97 @param packet: the packet to send (packet = object of (sub)type Packet) (see packet.py
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
109 class MPPlayer(object):
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
119 playerstr
= self
.name
120 if self
.color
is not None:
121 playerstr
+= "(%s)" % self
.color
.name
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
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
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
)
152 self
._socket
.send(packet
)
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)
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())
189 if self
.state
not in (self
.__class
__.STATE_CONNECTING
, self
.__class
__.STATE_DISCONNECTED
):
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
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
):
210 self
.send(ChatPacket(text
))
212 def doDisconnect(self
):
213 self
.send(DisconnectPacket(self
.address
, self
.port
))
215 def doPlayerModify(self
, **settings
):
219 for name
, value
in settings
.items():
220 self
.send(PlayerModify(name
, value
))
225 def onConnected(self
):
226 """Called when connection to server is confirmed
230 def onDisconnect(self
):
233 def onChat(self
, player
, text
):
240 def onPlayerPart(self
, player
):
246 def onPlayerModify(self
, player
):
252 def onServerSetting(self
, settings
):
258 def onTickPacket(self
, tick
, commands
):
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
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
= {}
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)
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
)
308 def remove_player(self
, player
):
309 self
.mpoptions
['players'].remove(player
)
310 del self
.last_client_message
[(player
.address
, player
.port
)]
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()