def __init__(self, *args, **kwargs): socketserver.ThreadingTCPServer.__init__(self, *args, **kwargs) self._stop = threading.Event() self.world = WorldServer(self) self.players = {} # Dict of all players connected. {ipaddress: requesthandler,} self.player_ids = [] # List of all players this session, indexes are their ID's [0: first requesthandler,] self.command_parser = CommandParser()
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): inventory = "\0"*(4*40) # Currently, is serialized to be 4 bytes * (27 inv + 9 quickbar + 4 armor) = 160 bytes command_parser = CommandParser() operator = False def sendpacket(self, size, packet): self.request.sendall(struct.pack("i", 5+size)+packet) def sendchat(self, txt, color=(255,255,255,255)): txt = txt.encode('utf-8') self.sendpacket(len(txt) + 4, "\5" + txt + struct.pack("BBBB", *color)) def sendinfo(self, info, color=(255,255,255,255)): info = info.encode('utf-8') self.sendpacket(len(info) + 4, "\5" + info + struct.pack("BBBB", *color)) def broadcast(self, txt): for player in self.server.players.itervalues(): player.sendchat(txt) def sendpos(self, pos_bytes, mom_bytes): self.sendpacket(38, "\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) def lookup_player(self, playername): # find player by name for player in self.server.players.values(): if player.username == playername: return player return None def handle(self): self.username = str(self.client_address) print "Client connecting...", self.client_address self.server.players[self.client_address] = self self.server.player_ids.append(self) self.id = len(self.server.player_ids) - 1 try: self.loop() except socket.error as e: if self.server._stop.isSet(): return # Socket error while shutting down doesn't matter if e[0] in (10053, 10054): print "Client %s %s crashed." % (self.username, self.client_address) else: raise e def loop(self): world, players = self.server.world, self.server.players while 1: byte = self.request.recv(1) if not byte: return # The client has disconnected intentionally packettype = struct.unpack("B", byte)[0] # Client Packet Type if packettype == 1: # Sector request sector = struct.unpack("iii", self.request.recv(4*3)) if sector not in world.sectors: with world.server_lock: world.open_sector(sector) if not world.sectors[sector]: #Empty sector, send packet 2 self.sendpacket(12, "\2" + struct.pack("iii",*sector)) else: msg = struct.pack("iii",*sector) + save_sector_to_string(world, sector) + world.get_exposed_sector(sector) self.sendpacket(len(msg), "\1" + msg) elif packettype == 3: # Add block positionbytes = self.request.recv(4*3) blockbytes = self.request.recv(2) position = struct.unpack("iii", positionbytes) blockid = G.BLOCKS_DIR[struct.unpack("BB", blockbytes)] with world.server_lock: world.add_block(position, blockid, sync=False) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him players[address].sendpacket(14, "\3" + positionbytes + blockbytes) elif packettype == 4: # Remove block positionbytes = self.request.recv(4*3) with world.server_lock: world.remove_block(struct.unpack("iii", positionbytes), sync=False) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him players[address].sendpacket(12, "\4" + positionbytes) elif packettype == 5: # Receive chat text txtlen = struct.unpack("i", self.request.recv(4))[0] raw_txt = self.request.recv(txtlen).decode('utf-8') txt = "%s: %s" % (self.username, raw_txt) try: if raw_txt[0] == '/': ex = self.command_parser.execute(raw_txt, user=self, world=world) if ex != COMMAND_HANDLED: self.sendchat('$$rUnknown command.') else: # Not a command, send the chat to all players for address in players: players[address].sendchat(txt) print txt # May as well let console see it too except CommandException, e: self.sendchat(str(e), COMMAND_ERROR_COLOR) elif packettype == 6: # Player Inventory Update self.inventory = self.request.recv(4*40) #TODO: All player's inventories should be autosaved at a regular interval. elif packettype == 8: # Player Movement mom_bytes, pos_bytes = self.request.recv(4*3), self.request.recv(8*3) self.momentum = struct.unpack("fff", mom_bytes) self.position = struct.unpack("ddd", pos_bytes) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him #TODO: Only send to nearby players players[address].sendpacket(38, "\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes)
class ServerPlayer(socketserver.BaseRequestHandler): inventory = b"\0" * ( 4 * 40 ) # Currently, is serialized to be 4 bytes * (27 inv + 9 quickbar + 4 armor) = 160 bytes command_parser = CommandParser() operator = False def sendpacket(self, size: int, packet: bytes): self.request.sendall(struct.pack("i", 5 + size) + packet) def sendchat(self, txt: str, color=(255, 255, 255, 255)): txt_bytes = txt.encode('utf-8') self.sendpacket( len(txt_bytes) + 4, b"\5" + txt_bytes + struct.pack("BBBB", *color)) def sendinfo(self, info: str, color=(255, 255, 255, 255)): info_bytes = info.encode('utf-8') self.sendpacket( len(info_bytes) + 4, b"\5" + info_bytes + struct.pack("BBBB", *color)) def broadcast(self, txt: str): for player in self.server.players.values(): player.sendchat(txt) def sendpos(self, pos_bytes, mom_bytes): self.sendpacket( 38, b"\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) def lookup_player(self, playername): # find player by name for player in list(self.server.players.values()): if player.username == playername: return player return None def handle(self): self.username = str(self.client_address) print("Client connecting...", self.client_address) self.server.players[self.client_address] = self self.server.player_ids.append(self) self.id = len(self.server.player_ids) - 1 try: self.loop() except socket.error as e: if self.server._stop.isSet(): return # Socket error while shutting down doesn't matter if e.errno in (10053, 10054): print("Client %s %s crashed." % (self.username, self.client_address)) else: raise e def loop(self): world, players = self.server.world, self.server.players while 1: byte = self.request.recv(1) if not byte: return # The client has disconnected intentionally packettype = struct.unpack("B", byte)[0] # Client Packet Type if packettype == 1: # Sector request sector = struct.unpack("iii", self.request.recv(4 * 3)) if sector not in world.sectors: with world.server_lock: world.open_sector(sector) if not world.sectors[sector]: # Empty sector, send packet 2 self.sendpacket(12, b"\2" + struct.pack("iii", *sector)) else: msg = struct.pack("iii", *sector) + save_sector_to_bytes( world, sector) + world.get_exposed_sector(sector) self.sendpacket(len(msg), b"\1" + msg) elif packettype == 3: # Add block positionbytes = self.request.recv(4 * 3) blockbytes = self.request.recv(2) position = struct.unpack("iii", positionbytes) blockid = G.BLOCKS_DIR[struct.unpack("BB", blockbytes)] with world.server_lock: world.add_block(position, blockid, sync=False) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him players[address].sendpacket( 14, b"\3" + positionbytes + blockbytes) elif packettype == 4: # Remove block positionbytes = self.request.recv(4 * 3) with world.server_lock: world.remove_block(struct.unpack("iii", positionbytes), sync=False) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him players[address].sendpacket(12, b"\4" + positionbytes) elif packettype == 5: # Receive chat text txtlen = struct.unpack("i", self.request.recv(4))[0] raw_txt = self.request.recv(txtlen).decode('utf-8') txt = "%s: %s" % (self.username, raw_txt) try: if raw_txt[0] == '/': ex = self.command_parser.execute(raw_txt, user=self, world=world) if ex != COMMAND_HANDLED: self.sendchat('$$rUnknown command.') else: # Not a command, send the chat to all players for address in players: players[address].sendchat(txt) print(txt) # May as well let console see it too except CommandException as e: self.sendchat(str(e), COMMAND_ERROR_COLOR) elif packettype == 6: # Player Inventory Update self.inventory = self.request.recv(4 * 40) #TODO: All player's inventories should be autosaved at a regular interval. elif packettype == 8: # Player Movement mom_bytes, pos_bytes = self.request.recv( 4 * 3), self.request.recv(8 * 3) self.momentum = struct.unpack("fff", mom_bytes) self.position = struct.unpack("ddd", pos_bytes) for address in players: if address is self.client_address: continue # He told us, we don't need to tell him #TODO: Only send to nearby players players[address].sendpacket( 38, b"\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) elif packettype == 9: # Player Jump for address in players: if address is self.client_address: continue # He told us, we don't need to tell him #TODO: Only send to nearby players players[address].sendpacket( 2, b"\x09" + struct.pack("H", self.id)) elif packettype == 10: # Update Tile Entity block_pos = struct.unpack("iii", self.request.recv(4 * 3)) ent_size = struct.unpack("i", self.request.recv(4))[0] world[block_pos].update_tile_entity( self.request.recv(ent_size)) elif packettype == 255: # Initial Login txtlen = struct.unpack("i", self.request.recv(4))[0] self.username = self.request.recv(txtlen).decode('utf-8') self.position = None load_player(self, "world") for player in self.server.players.values(): player.sendchat("$$y%s has connected." % self.username) print("%s's username is %s" % (self.client_address, self.username)) position = (0, self.server.world.terraingen.get_height(0, 0) + 2, 0) if self.position is None: self.position = position # New player, set initial position # Send list of current players to the newcomer for player in self.server.players.values(): if player is self: continue name = player.username.encode('utf-8') self.sendpacket(2 + len(name), b'\7' + struct.pack("H", player.id) + name) # Send the newcomer's name to all current players name = self.username.encode('utf-8') for player in self.server.players.values(): if player is self: continue player.sendpacket(2 + len(name), b'\7' + struct.pack("H", self.id) + name) #Send them the sector under their feet first so they don't fall sector = sectorize(position) if sector not in world.sectors: with world.server_lock: world.open_sector(sector) msg = struct.pack("iii", *sector) + save_sector_to_bytes( world, sector) + world.get_exposed_sector(sector) self.sendpacket(len(msg), b"\1" + msg) # Send them their spawn position and world seed(for client side biome generator) seed_packet = make_string_packet(str(G.SEED)) self.sendpacket( 12 + len(seed_packet), struct.pack("B", 255) + struct.pack("iii", *position) + seed_packet) self.sendpacket(4 * 40, b"\6" + self.inventory) else: print("Received unknown packettype", packettype) def finish(self): print("Client disconnected,", self.client_address, self.username) try: del self.server.players[self.client_address] except KeyError: pass for player in self.server.players.values(): player.sendchat("%s has disconnected." % self.username) # Send user list for player in self.server.players.values(): player.sendpacket(2 + 1, b'\7' + struct.pack("H", self.id) + b'\0') save_player(self, "world")