# TOFU # Copyright (C) 2005 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """tofu.server This module implements a Tofu server. To lauch a server, you just have to import this module, and then call serve_forever(). """ from twisted.internet.protocol import DatagramProtocol, Protocol, Factory from twisted.protocols.basic import LineReceiver, NetstringReceiver from twisted.internet import reactor import sys, struct import tofu class UDP(DatagramProtocol): def datagramReceived(self, data, (host, port)): print "UDP received %r from %s:%d" % (data, host, port) self.transport.write(data, (host, port)) #reactor.listenUDP(6900, UDP()) class SecurityError(StandardError): pass MAX_LENGTH = 99999 class PlayerNotifier(NetstringReceiver): def __init__(self): self.player = None self.version_checked = 0 self.MAX_LENGTH = MAX_LENGTH def stringReceived(self, data): #print "TCP received %r" % (data,) try: code = data[0] #print "* Tofu * receiving code %s..." % code if code == tofu.CODE_LOGIN_PLAYER : self.login_player (*data[1:].split("\n", 2)) elif code == tofu.CODE_LOGOUT_PLAYER: self.logout_player() elif code == tofu.CODE_ASK_UNIQUE : #XXX Verify if the player has the rigth to see the asked unique. unique = tofu.Unique.undumpuid(data[1:]) #print "TCP send:", tofu.CODE_DATA_UNIQUE + repr(unique.dump()) #print len(tofu.CODE_DATA_UNIQUE + unique.dump()) self.send_unique(unique) elif code == tofu.CODE_DATA_ACTION : uid = struct.unpack("!i", data[1:5])[0] if tofu.Unique.hasuid(uid): mobile = tofu.Unique.undumpuid(data[1:5]) if not mobile in self.player.mobiles: # XXX optimize this (use Set) raise SecurityError("Player %s does not have the right to send action for mobile %s !" % (self.player.filename, mobile.uid)) mobile.controller.push(tofu.YourAction.undump(data[5:])) elif code == tofu.CODE_CHECK_VERSION: if data[1:] != tofu.VERSION: raise ValueError("Server and client use incompatible version (server: %s, client %s)" % (VERSION, data[1:])) self.version_checked = 1 except StandardError, e: print "* Tofu * Error occured:" sys.excepthook(*sys.exc_info()) self.sendString(tofu.CODE_ERROR + "%s: %s" % (e.__class__.__name__, str(e))) def login_player(self, filename, password, client_side_data): try: player = tofu.YourPlayer.get(filename) except ValueError: player = tofu.YourPlayer(filename, password, client_side_data) if password != player.password: raise ValueError("Password is wrong !") # XXX delay that player.login(self, self.transport.socket.getpeername(), client_side_data) self.player = player def logout_player(self): self.player.logout() self.player = None def connectionLost(self, reason): Protocol.connectionLost(self, reason) if self.player: print "* Tofu * Connection lost with player %s:" % self.player.filename, reason.getErrorMessage() self.logout_player() def send_unique(self, unique): print "* Tofu * Sending unique %s..." % unique.uid self.sendString(tofu.CODE_DATA_UNIQUE + unique.dump()) def notify_state(self, mobile, state): self.sendString(tofu.CODE_DATA_STATE + "%s%s" % (mobile.dumpuid(), state.dump())) def notify_add_mobile(self, mobile): self.sendString(tofu.CODE_ADD_MOBILE + mobile.dumpuid() + mobile.level.dumpuid()) self.send_unique(mobile) def notify_remove_mobile(self, mobile): self.sendString(tofu.CODE_REMOVE_MOBILE + mobile.dumpuid()) def notify_enter_level(self, level): self.sendString(tofu.CODE_ENTER_LEVEL + level.dumpuid()) self.send_unique(level) def notify_own_control(self, mobile): self.sendString(tofu.CODE_OWN_CONTROL + mobile.dumpuid()) class Notifier(tofu.Notifier): def notify_action(self, mobile, action): #raise AssertionError("Server does not send action !") pass def notify_state(self, mobile, state): for player in tofu.YourPlayer._alls2.values(): # XXX optimize this (maintain a list of player for each level) if player.notifier: for m in player.mobiles: if m.level is mobile.level: player.notifier.notify_state(mobile, state) break def notify_add_mobile(self, mobile): for player in tofu.YourPlayer._alls2.values(): # XXX optimize this if player.notifier: for m in player.mobiles: if (not m is mobile) and (m.level is mobile.level): if mobile in player.mobiles: player.notifier.notify_own_control(mobile) player.notifier.notify_add_mobile(mobile) #def doit(): # if mobile in player.mobiles: # print "SEND OWN_CONTROL" # player.notifier.notify_own_control(mobile) # print "SEND ADD_MOBILE" # player.notifier.notify_add_mobile(mobile) # #tofu.IDLER.next_round_tasks.append(doit) break else: if mobile in player.mobiles: # XXX optimize this # Not notified, because the player doesn't have the level yet # => send ENTER_LEVEL and OWN_CONTROL #player.notifier.notify_own_control(mobile) mobile.send_control_to(player) player.notifier.notify_enter_level(mobile.level) def notify_remove_mobile(self, mobile): for player in tofu.YourPlayer._alls2.values(): # XXX optimize this if player.notifier: for m in player.mobiles: if m.level is mobile.level: player.notifier.notify_remove_mobile(mobile) break def check_level_activity(self, level): for mobile in level.mobiles: if mobile.controller.remote: level.set_active(1) return # No remote user for this level => we can inactive it level.set_active(0) def notify_discard(self, unique): # The server is ALWAYS responsible for saving data if isinstance(unique, tofu.SavedInAPath): unique.save() def game_ended(self): for player in tofu.Player._alls2.values(): if player.notifier: player.notifier.logout_player() def serve_forever(port = 6900): """serve_forever(port = 6900) Starts a game server on TCP port PORT.""" tofu.NOTIFIER = Notifier() f = Factory() f.protocol = PlayerNotifier reactor.listenTCP(6900, f) print "* Tofu * Server ready !" try: tofu.IDLER.idle() except: sys.excepthook(*sys.exc_info()) tofu.NOTIFIER.game_ended()