class CubeWorldFactory(Factory): def __init__(self): import config self.config = config self.connections = MultikeyDict() self.entities = {} self.entity_ids = IDPool(1) self.scripts = [] for script in config.scripts: self.load_script(script) print 'cuwo running on port 12345' def broadcast_packet(self, packet): data = write_packet(packet) for connection in self.connections.values(): connection.transport.write(data) def format(self, value): format_dict = {'server_name' : self.config.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines def load_script(self, name): path = './scripts/%s.py' % name try: mod = imp.load_source(name, path) except IOError: return None script = mod.get_class()(self) return script def call_scripts(self, name, *arg, **kw): for script in self.scripts: if call_handler(script, name, *arg, **kw) is False: return False return True def buildProtocol(self, addr): return CubeWorldProtocol(self)
class Channel(object): def __init__(self, settings): self.name = settings['name'] self.default = settings['default'] self.password = settings['password'] self.silent_join = settings['silent_join'] self.can_leave = settings['can_leave'] self.players = MultikeyDict() def add_player(self, player, suppress=False): entity_id = player.entity_id if entity_id not in self.players: self.players[(entity_id, )] = player if suppress: return if not self.silent_join: self.send_message('%s has joined the channel %s' % (player.name, self.name)) else: player.send_chat('You\'ve joined the channel %s' % self.name) def remove_player(self, player): if player in self.players: del self.players[player] if self.default: return if not self.silent_join: self.send_message('%s has left the channel %s' % (player.name, self.name)) else: player.send_chat('You\'ve left the channel %s' % self.name) def send_message(self, message, entity=None): if not message or len(message) < 1: return entity_id = 0 if entity: entity_id = entity.entity_id message = '<%s> %s' % (self.name, message) data = create_message(message, entity_id) for player in self.players.values(): player.transport.write(data) print '%s: %s' % (entity.name, message)
class Channel(object): def __init__(self, settings): self.name = settings['name'] self.default = settings['default'] self.password = settings['password'] self.silent_join = settings['silent_join'] self.can_leave = settings['can_leave'] self.players = MultikeyDict() def add_player(self, player, suppress=False): entity_id = player.entity_id if entity_id not in self.players: self.players[(entity_id,)] = player if suppress: return if not self.silent_join: self.send_message('%s has joined the channel %s' % ( player.name, self.name)) else: player.send_chat('You\'ve joined the channel %s' % self.name) def remove_player(self, player): if player in self.players: del self.players[player] if self.default: return if not self.silent_join: self.send_message('%s has left the channel %s' % ( player.name, self.name)) else: player.send_chat('You\'ve left the channel %s' % self.name) def send_message(self, message, entity=None): if not message or len(message) < 1: return entity_id = 0 if entity: entity_id = entity.entity_id message = '<%s> %s' % (self.name, message) data = create_message(message, entity_id) for player in self.players.values(): player.transport.write(data) print '%s: %s' % (entity.name, message)
class CubeWorldServer: exit_code = None world = None old_time = None skip_index = 0 def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.updated_chunks = set() use_same_loop = base.update_fps == base.network_fps if use_same_loop: def update_callback(): self.update() self.send_update() else: update_callback = self.update self.update_loop = LoopingCall(update_callback) self.update_loop.start(1.0 / base.update_fps, now=False) if use_same_loop: self.send_loop = self.update_loop else: self.send_loop = LoopingCall(self.send_update) self.send_loop.start(1.0 / base.network_fps, now=False) self.mission_loop = LoopingCall(self.update_missions) self.mission_loop.start(base.mission_update_rate, now=False) # world self.world = World(self, self.loop, base.seed, use_tgen=base.use_tgen, use_entities=base.use_entities, chunk_retire_time=base.chunk_retire_time, debug=base.world_debug_info) if base.world_debug_file is not None: debug_fp = open(base.world_debug_file, 'wb') self.world.set_debug(debug_fp) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening self.loop.set_exception_handler(self.exception_handler) self.loop.create_task(self.create_server(self.build_protocol, port=base.port, family=socket.AF_INET)) def exception_handler(self, loop, context): exception = context.get('exception') if isinstance(exception, TimeoutError): pass else: loop.default_exception_handler(context) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def add_packet_list(self, items, l, size): for item in iterate_packet_list(l): items.append(item.data.copy()) def handle_tgen_packets(self, in_queue): if in_queue is None: return p = self.update_packet self.add_packet_list(p.player_hits, in_queue.player_hits, in_queue.player_hits_size) self.add_packet_list(p.sound_actions, in_queue.sound_actions, in_queue.sound_actions_size) self.add_packet_list(p.particles, in_queue.particles, in_queue.particles_size) self.add_packet_list(p.block_actions, in_queue.block_actions, in_queue.block_actions_size) self.add_packet_list(p.shoot_actions, in_queue.shoot_packets, in_queue.shoot_packets_size) self.add_packet_list(p.kill_actions, in_queue.kill_actions, in_queue.kill_actions_size) self.add_packet_list(p.damage_actions, in_queue.damage_actions, in_queue.damage_actions_size) self.add_packet_list(p.passive_actions, in_queue.passive_packets, in_queue.passive_packets_size) self.add_packet_list(p.missions, in_queue.missions, in_queue.missions_size) def update_missions(self): max_dist = self.config.base.mission_max_distance p = self.update_packet added = set() for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue min_pos = (player_entity.pos - max_dist) // constants.MISSION_SCALE max_pos = (player_entity.pos + max_dist) // constants.MISSION_SCALE for x in range(min_pos.x, max_pos.x): for y in range(min_pos.y, max_pos.y): if (x, y) in added: continue added.add((x, y)) reg_x = x // constants.MISSIONS_IN_REGION reg_y = y // constants.MISSIONS_IN_REGION try: reg = self.world.get_region((reg_x, reg_y)) except KeyError: continue local_x = x % constants.MISSIONS_IN_REGION local_y = y % constants.MISSIONS_IN_REGION try: m = reg.get_mission((local_x, local_y)) except (IndexError, ValueError): continue mission_packet = packets.MissionPacket() mission_packet.x = x mission_packet.y = y mission_packet.something1 = 0 mission_packet.something2 = 0 mission_packet.info = m.info p.missions.append(mission_packet) def send_entity_data(self, entity): base = self.config.base # full entity packet for new, close players entity_packet.set_entity(entity, entity.entity_id) full = packets.write_packet(entity_packet) # pos entity packet if not entity.is_tgen: entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) only_pos = packets.write_packet(entity_packet) # reduced rate packet skip_reduced = self.skip_index != 0 entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) reduced = packets.write_packet(entity_packet) max_distance = base.max_distance max_reduce_distance = base.max_reduce_distance old_close_players = entity.close_players new_close_players = {} for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue if entity is player_entity: continue if entity.full_update: connection.send_data(full) new_close_players[connection] = entity.copy() continue dist = (player_entity.pos - entity.pos).length if dist > max_distance: if not entity.is_tgen: connection.send_data(only_pos) continue old_ref = old_close_players.get(connection, None) if old_ref is None: connection.send_data(full) new_close_players[connection] = entity.copy() continue if dist > max_reduce_distance and skip_reduced: connection.send_data(reduced) new_close_players[connection] = old_ref continue new_mask = entitydata.get_mask(old_ref, entity) entity_packet.set_entity(entity, entity.entity_id, new_mask) connection.send_packet(entity_packet) new_close_players[connection] = entity.copy() entity.close_players = new_close_players entity.full_update = False def update(self): self.scripts.call('update') out_packets = self.world.update(self.update_loop.dt) self.handle_tgen_packets(out_packets) def send_update(self): self.skip_index = (self.skip_index + 1) % self.config.base.reduce_skip for entity in self.world.entities.values(): self.send_entity_data(entity) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) if not update_packet.is_empty(): self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update new_time = (self.get_time(), self.get_day()) if new_time != self.old_time: time_packet.time = new_time[0] time_packet.day = new_time[1] self.broadcast_packet(time_packet) self.old_time = new_time def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.send_data(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = os.path.join(self.config.base.save_path, f'{name}.dat') try: with open(path, 'r', newline=None) as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): os.makedirs(self.config.base.save_path, exist_ok=True) path = os.path.join(self.config.base.save_path, f'{name}.dat') data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)
class CubeWorldServer(Factory): items_changed = False exit_code = None def __init__(self, config): self.config = config base = config.base # game-related self.update_packet = ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.chunk_items = collections.defaultdict(list) self.entities = {} self.entity_ids = IDPool(1) self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / base.update_fps, False) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.iteritems(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = reactor.seconds() self.set_clock('12:00') # start listening self.listen_tcp(base.port, self) def buildProtocol(self, addr): # return None here to refuse the connection. # will use this later to hardban e.g. DoS ret = self.scripts.call('on_connection_attempt', address=addr).result if ret is False: return None elif ret is not None: return BanProtocol(ret) return CubeWorldConnection(self, addr) def remove_item(self, chunk, index): items = self.chunk_items[chunk] ret = items.pop(index) self.items_changed = True return ret.item_data def drop_item(self, item_data, pos): item = ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.chunk_items[get_chunk(pos)].append(item) self.items_changed = True def update(self): self.scripts.call('update') # entity updates for entity_id, entity in self.entities.iteritems(): entity_packet.set_entity(entity, entity_id, entity.mask) entity.mask = 0 self.broadcast_packet(entity_packet) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet if self.items_changed: for chunk, items in self.chunk_items.iteritems(): item_list = ChunkItems() item_list.chunk_x, item_list.chunk_y = chunk item_list.items = items update_packet.chunk_items.append(item_list) self.broadcast_packet(update_packet) update_packet.reset() # reset drop times if self.items_changed: for items in self.chunk_items.values(): for item in items: item.drop_time = 0 self.items_changed = False # time update time_packet.time = self.get_time() time_packet.day = self.get_day() self.broadcast_packet(time_packet) def send_chat(self, value): packet = ServerChatMessage() packet.entity_id = 0 packet.value = value self.broadcast_packet(packet) def broadcast_packet(self, packet): data = write_packet(packet) for player in self.players.values(): player.transport.write(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name): try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) except ImportError, e: traceback.print_exc(e) return None script = mod.get_class()(self) print 'Loaded script %r' % name return script
class CubeWorldServer(Factory): items_changed = False def __init__(self, config): self.config = config base = config.base # game-related self.update_packet = ServerUpdate() self.update_packet.reset() self.connections = MultikeyDict() self.chunk_items = collections.defaultdict(list) self.entities = {} self.entity_ids = IDPool(1) self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / constants.UPDATE_FPS, False) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.iteritems(): self.passwords[k.lower()] = v self.scripts = [] for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = reactor.seconds() self.set_clock('12:00') def buildProtocol(self, addr): # return None here to refuse the connection. # will use this later to hardban e.g. DoS message = self.call_scripts('on_connection_attempt', addr) if message is not None: return BanProtocol(message) return CubeWorldConnection(self, addr) def remove_item(self, chunk, index): items = self.chunk_items[chunk] ret = items.pop(index) self.items_changed = True return ret.item_data def drop_item(self, item_data, pos): item = ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.chunk_items[get_chunk(pos)].append(item) self.items_changed = True def update(self): self.call_scripts('update') # entity updates for entity_id, entity in self.entities.iteritems(): entity_packet.set_entity(entity, entity_id, entity.mask) entity.mask = 0 self.broadcast_packet(entity_packet) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet if self.items_changed: for chunk, items in self.chunk_items.iteritems(): item_list = ChunkItems() item_list.chunk_x, item_list.chunk_y = chunk item_list.items = items update_packet.chunk_items.append(item_list) self.broadcast_packet(update_packet) update_packet.reset() # reset drop times if self.items_changed: for items in self.chunk_items.values(): for item in items: item.drop_time = 0 self.items_changed = False # time update time_packet.time = self.get_time() time_packet.day = self.get_day() self.broadcast_packet(time_packet) def send_chat(self, value): packet = ServerChatMessage() packet.entity_id = 0 packet.value = value self.broadcast_packet(packet) def broadcast_packet(self, packet): data = write_packet(packet) for connection in self.connections.values(): connection.transport.write(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name): path = './scripts/%s.py' % name try: mod = imp.load_source(name, path) except IOError: return None script = mod.get_class()(self) print 'Loaded script %r' % name return script def call_scripts(self, name, *arg, **kw): return call_scripts(self.scripts, name, *arg, **kw) # data store methods def load_data(self, name, default=None): path = './%s.dat' % name try: with open(path, 'rU') as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): path = './%s.dat' % name data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = reactor.seconds() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = reactor.seconds() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time())
class CubeWorldFactory(Factory): def __init__(self, config): self.config = config self.passwords = {} for k, v in config.passwords.iteritems(): self.passwords[k.lower()] = v self.connections = MultikeyDict() self.entities = {} self.entity_ids = IDPool(1) self.scripts = [] for script in config.scripts: self.load_script(script) self.start_time = reactor.seconds() self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / constants.UPDATE_FPS) print 'cuwo running on port 12345' def update(self): pass def send_chat(self, value): packet = ServerChatMessage() packet.entity_id = 0 packet.value = value self.broadcast_packet(packet) def broadcast_packet(self, packet): data = write_packet(packet) for connection in self.connections.values(): connection.transport.write(data) def format(self, value): format_dict = {'server_name' : self.config.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines def load_script(self, name): path = './scripts/%s.py' % name try: mod = imp.load_source(name, path) except IOError: return None script = mod.get_class()(self) return script def call_scripts(self, name, *arg, **kw): for script in self.scripts: if call_handler(script, name, *arg, **kw) is False: return False return True def buildProtocol(self, addr): return CubeWorldProtocol(self)
class CubeWorldServer: exit_code = None world = None def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.chunks = {} self.updated_chunks = set() self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / base.update_fps, now=False) # world self.world = World(self, self.loop, base.seed, base.use_tgen, base.use_entities) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening asyncio.Task(self.create_server(self.build_protocol, port=base.port)) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def update(self): self.scripts.call('update') # entity updates for entity_id, entity in self.world.entities.items(): entity_packet.set_entity(entity, entity_id, entity.mask) entity.mask = 0 self.broadcast_packet(entity_packet) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update time_packet.time = self.get_time() time_packet.day = self.get_day() self.broadcast_packet(time_packet) def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.transport.write(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = './save/%s.dat' % name try: with open(path, 'rU') as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): path = './save/%s.dat' % name data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)
class CubeWorldServer(Factory): items_changed = False exit_code = 0 last_secondly_check = None updates_since_last_second = 0 updates_per_second = 0 current_change_index = 0 world = None def __init__(self, config): self.config = config base = config.base # GAME RELATED self.update_packet = ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.entities = {} self.entity_ids = IDPool(1) # DATABASE self.db_con = database.get_connection() database.create_structure(self.db_con) # Initialize default world self.world = World(self) self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / constants.UPDATE_FPS, False) # SERVER RELATED self.git_rev = base.get('git_rev', None) self.ranks = {} for k, v in base.ranks.iteritems(): self.ranks[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # INGAME TIME self.extra_elapsed_time = 0.0 self.start_time = reactor.seconds() self.set_clock('12:00') # START LISTENING self.listen_tcp(base.port, self) def buildProtocol(self, addr): con_remain = self.config.base.max_connections_per_ip for connection in self.connections: if connection.address.host == addr.host: con_remain -= 1 if con_remain <= 0: if con_remain == 0: print '[WARNING] Too many connections from %s, closing...' % addr.host connection.disconnect() if con_remain <= 0: return self.db_con = database.get_connection() if database.is_banned_ip(self.db_con, addr.host): print '[INFO] Banned client %s tried to join.' % addr.host return 'You are banned from this server.' if self.scripts.call('on_connection_attempt', address=addr).result is False: print '[WARNING] Connection attempt for %s blocked by script!' % addr.host return False return CubeWorldConnection(self, addr) def remove_item(self, chunk_x, chunk_y, index): print '[DEBUG] Removing item #%s from chunk %s,%s' % (index, chunk_x, chunk_y) chunk = self.world.get_chunk_unscaled(chunk_x, chunk_y) ret = chunk.item_list.pop(index) self.items_changed = True return ret.item_data def drop_item(self, item_data, pos): print '[DEBUG] Dropping item at %s,%s' % (pos.x, pos.y) chunk = self.world.get_chunk_scaled(pos.x, pos.y) if len(chunk.item_list) > constants.MAX_ITEMS_PER_CHUNK: print '[WARNING] To many items at Chunk(%s,%s)!' % (math.floor(pos.x / constants.CHUNK_SCALE), math.floor(pos.y / constants.CHUNK_SCALE)) return False item = ChunkItemData() item.drop_time = 750 item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data chunk.item_list.append(item) self.items_changed = True def update(self): self.scripts.call('update') uxtime = reactor.seconds() if self.last_secondly_check: update_seconds_delta = uxtime - self.last_secondly_check else: update_seconds_delta = 0 if update_seconds_delta < 1: self.updates_since_last_second += 1 else: ups = math.floor((self.updates_per_second + (self.updates_since_last_second / update_seconds_delta)) / 2) self.updates_since_last_second = 0 if ups != self.updates_per_second: dus = ups - self.updates_per_second self.updates_per_second = ups if dus > 0: print "\rUpdates/s: %s (+%s)" % (ups, dus) elif dus < 0: print "\rUpdates/s: %s (-%s)" % (ups, dus) for player in self.players.values(): if player.packet_count > 0: ppr = math.ceil( ( player.packet_rate + ( player.packet_count / update_seconds_delta ) ) / 2 ) player.packet_count = 0 if ppr != player.packet_rate: dpr = ppr - player.packet_rate player.packet_rate = ppr if dpr > 0: print "\rPackets/s for %s: %s (+%s)" % (player.name, player.packet_rate, dpr) elif dpr < 0: print "\rPackets/s for %s: %s (-%s)" % (player.name, player.packet_rate, dpr) # entity updates for entity_id, entity in self.entities.iteritems(): entity_packet.set_entity(entity, entity_id, entity.mask) entity.mask = 0 self.broadcast_packet(entity_packet) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet if self.items_changed: for chunk in self.world.chunks.values(): item_list = ChunkItems() item_list.chunk_x = chunk.chunk_x item_list.chunk_y = chunk.chunk_y item_list.items = chunk.item_list update_packet.chunk_items.append(item_list) for item in chunk.item_list: item.drop_time = 0 self.items_changed = False self.broadcast_packet(update_packet) update_packet.reset() if update_seconds_delta != 0: for player in self.players.values(): if player.time_last_packet >= (uxtime - constants.CLIENT_RECV_TIMEOUT): if player.entity_data.changed: player.entity_data.changed = False ret = player.do_anticheat_actions() if (not ret) and player.login_id: database.update_player(self.db_con, player.login_id, player.name) else: print '[WARNING] Connection timed out for Player %s #%s' % (player.entity_data.name, player.entity_id) player.kick('Connection timed out') self.broadcast_time() def hit_entity_id(self, id): return True def send_chat(self, value): packet = ServerChatMessage() packet.entity_id = 0 packet.value = value self.broadcast_packet(packet) def broadcast_packet(self, packet): data = write_packet(packet) for player in self.players.values(): player.transport.write(data) def broadcast_time(self): time_packet.time = self.get_time() time_packet.day = self.get_day() self.broadcast_packet(time_packet) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name): try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) except ImportError, e: traceback.print_exc(e) return None script = mod.get_class()(self) print '[INFO] Loaded script %r' % name return script
class CubeWorldServer: exit_code = None world = None old_time = None skip_index = 0 def __init__(self, loop, config): self.loop = loop self.config = config base = config.base # game-related self.update_packet = packets.ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.updated_chunks = set() use_same_loop = base.update_fps == base.network_fps if use_same_loop: def update_callback(): self.update() self.send_update() else: update_callback = self.update self.update_loop = LoopingCall(update_callback) self.update_loop.start(1.0 / base.update_fps, now=False) if use_same_loop: self.send_loop = self.update_loop else: self.send_loop = LoopingCall(self.send_update) self.send_loop.start(1.0 / base.network_fps, now=False) self.mission_loop = LoopingCall(self.update_missions) self.mission_loop.start(base.mission_update_rate, now=False) # world self.world = World(self, self.loop, base.seed, use_tgen=base.use_tgen, use_entities=base.use_entities, chunk_retire_time=base.chunk_retire_time, debug=base.world_debug_info) if base.world_debug_file is not None: debug_fp = open(base.world_debug_file, 'wb') self.world.set_debug(debug_fp) # server-related self.git_rev = base.get('git_rev', None) self.passwords = {} for k, v in base.passwords.items(): self.passwords[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.extra_elapsed_time = 0.0 self.start_time = loop.time() self.set_clock('12:00') # start listening self.loop.set_exception_handler(self.exception_handler) self.loop.create_task( self.create_server(self.build_protocol, port=base.port, family=socket.AF_INET)) def exception_handler(self, loop, context): exception = context.get('exception') if isinstance(exception, TimeoutError): pass else: loop.default_exception_handler(context) def build_protocol(self): return CubeWorldConnection(self) def drop_item(self, item_data, pos): item = packets.ChunkItemData() item.drop_time = 750 # XXX provide sane values for these item.scale = 0.1 item.rotation = 185.0 item.something3 = item.something5 = item.something6 = 0 item.pos = pos item.item_data = item_data self.world.get_chunk(get_chunk(pos)).add_item(item) def add_packet_list(self, items, l, size): for item in iterate_packet_list(l): items.append(item.data.copy()) def handle_tgen_packets(self, in_queue): if in_queue is None: return p = self.update_packet self.add_packet_list(p.player_hits, in_queue.player_hits, in_queue.player_hits_size) self.add_packet_list(p.sound_actions, in_queue.sound_actions, in_queue.sound_actions_size) self.add_packet_list(p.particles, in_queue.particles, in_queue.particles_size) self.add_packet_list(p.block_actions, in_queue.block_actions, in_queue.block_actions_size) self.add_packet_list(p.shoot_actions, in_queue.shoot_packets, in_queue.shoot_packets_size) self.add_packet_list(p.kill_actions, in_queue.kill_actions, in_queue.kill_actions_size) self.add_packet_list(p.damage_actions, in_queue.damage_actions, in_queue.damage_actions_size) self.add_packet_list(p.passive_actions, in_queue.passive_packets, in_queue.passive_packets_size) self.add_packet_list(p.missions, in_queue.missions, in_queue.missions_size) def update_missions(self): max_dist = self.config.base.mission_max_distance p = self.update_packet added = set() for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue min_pos = (player_entity.pos - max_dist) // constants.MISSION_SCALE max_pos = (player_entity.pos + max_dist) // constants.MISSION_SCALE for x in range(min_pos.x, max_pos.x): for y in range(min_pos.y, max_pos.y): if (x, y) in added: continue added.add((x, y)) reg_x = x // constants.MISSIONS_IN_REGION reg_y = y // constants.MISSIONS_IN_REGION try: reg = self.world.get_region((reg_x, reg_y)) except KeyError: continue local_x = x % constants.MISSIONS_IN_REGION local_y = y % constants.MISSIONS_IN_REGION try: m = reg.get_mission((local_x, local_y)) except (IndexError, ValueError): continue mission_packet = packets.MissionPacket() mission_packet.x = x mission_packet.y = y mission_packet.something1 = 0 mission_packet.something2 = 0 mission_packet.info = m.info p.missions.append(mission_packet) def send_entity_data(self, entity): base = self.config.base # full entity packet for new, close players entity_packet.set_entity(entity, entity.entity_id) full = packets.write_packet(entity_packet) # pos entity packet if not entity.is_tgen: entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) only_pos = packets.write_packet(entity_packet) # reduced rate packet skip_reduced = self.skip_index != 0 entity_packet.set_entity(entity, entity.entity_id, entitydata.POS_FLAG) reduced = packets.write_packet(entity_packet) max_distance = base.max_distance max_reduce_distance = base.max_reduce_distance old_close_players = entity.close_players new_close_players = {} for connection in self.players.values(): player_entity = connection.entity if player_entity is None: continue if entity is player_entity: continue if entity.full_update: connection.send_data(full) new_close_players[connection] = entity.copy() continue dist = (player_entity.pos - entity.pos).length if dist > max_distance: if not entity.is_tgen: connection.send_data(only_pos) continue old_ref = old_close_players.get(connection, None) if old_ref is None: connection.send_data(full) new_close_players[connection] = entity.copy() continue if dist > max_reduce_distance and skip_reduced: connection.send_data(reduced) new_close_players[connection] = old_ref continue new_mask = entitydata.get_mask(old_ref, entity) entity_packet.set_entity(entity, entity.entity_id, new_mask) connection.send_packet(entity_packet) new_close_players[connection] = entity.copy() entity.close_players = new_close_players entity.full_update = False def update(self): self.scripts.call('update') out_packets = self.world.update(self.update_loop.dt) self.handle_tgen_packets(out_packets) def send_update(self): self.skip_index = (self.skip_index + 1) % self.config.base.reduce_skip for entity in self.world.entities.values(): self.send_entity_data(entity) self.broadcast_packet(update_finished_packet) # other updates update_packet = self.update_packet for chunk in self.updated_chunks: chunk.on_update(update_packet) if not update_packet.is_empty(): self.broadcast_packet(update_packet) update_packet.reset() # reset drop times for chunk in self.updated_chunks: chunk.on_post_update() self.updated_chunks.clear() # time update new_time = (self.get_time(), self.get_day()) if new_time != self.old_time: time_packet.time = new_time[0] time_packet.day = new_time[1] self.broadcast_packet(time_packet) self.old_time = new_time def send_chat(self, value): chat_packet.entity_id = 0 chat_packet.value = value self.broadcast_packet(chat_packet) def play_sound(self, name, pos=None, pitch=1.0, volume=1.0): sound = packets.SoundAction() sound.set_name(name) sound.pitch = pitch sound.volume = volume if pos is not None: sound.pos = pos self.update_packet.sound_action.append(sound) return extra_server_update.reset() for player in self.players.values(): sound.pos = player.entity.pos extra_server_update.sound_actions = [sound] player.send_packet(extra_server_update) def broadcast_packet(self, packet): data = packets.write_packet(packet) for player in self.players.values(): player.send_data(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name, update=False): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) if update: importlib.reload(mod) except ImportError as e: traceback.print_exc() return None script = mod.get_class()(self) print('Loaded script %r' % name) return script def unload_script(self, name): try: self.scripts[name].unload() except KeyError: return False print('Unloaded script %r' % name) return True def call_command(self, user, command, args): """ Calls a command from an external interface, e.g. IRC, console """ return self.scripts.call('on_command', user=user, command=command, args=args).result def get_mode(self): return self.scripts.call('get_mode').result # command convenience methods (for /help) def get_commands(self): for script in self.scripts.get(): if script.commands is None: continue for command in script.commands.values(): yield command def get_command(self, name): for script in self.scripts.get(): if script.commands is None: continue name = script.aliases.get(name, name) command = script.commands.get(name, None) if command: return command # binary data store methods def load_data(self, name, default=None): path = os.path.join(self.config.base.save_path, f'{name}.dat') try: with open(path, 'r', newline=None) as fp: data = fp.read() except IOError: return default return eval(data) def save_data(self, name, value): os.makedirs(self.config.base.save_path, exist_ok=True) path = os.path.join(self.config.base.save_path, f'{name}.dat') data = pprint.pformat(value, width=1) with open(path, 'w') as fp: fp.write(data) # time methods def set_clock(self, value): day = self.get_day() time = parse_clock(value) self.start_time = self.loop.time() self.extra_elapsed_time = day * constants.MAX_TIME + time def get_elapsed_time(self): dt = self.loop.time() - self.start_time dt *= self.config.base.time_modifier * constants.NORMAL_TIME_SPEED return dt * 1000 + self.extra_elapsed_time def get_time(self): return int(self.get_elapsed_time() % constants.MAX_TIME) def get_day(self): return int(self.get_elapsed_time() / constants.MAX_TIME) def get_clock(self): return get_clock_string(self.get_time()) # stop/restart def stop(self, code=None): print('Stopping...') self.exit_code = code if self.world: self.world.stop() self.scripts.unload() self.loop.stop() # asyncio wrappers def get_interface(self): return self.config.base.network_interface def create_datagram_endpoint(self, *arg, port=0, **kw): host = self.get_interface() addr = (host, port) return self.loop.create_datagram_endpoint(*arg, local_addr=addr, **kw) def create_server(self, *arg, **kw): return self.loop.create_server(*arg, host=self.get_interface(), **kw) def connect_connection(self, *arg, **kw): host = self.get_interface() return self.loop.create_connection(*arg, local_addr=(host, 0), **kw)
class CubeWorldServer(Factory): items_changed = False exit_code = None world = None def __init__(self, config): self.config = config base = config.base # game-related self.update_packet = ServerUpdate() self.update_packet.reset() self.connections = set() self.players = MultikeyDict() self.entities = {} # DATABASE self.db_con = database.get_connection() database.create_structure(self.db_con) self.update_loop = LoopingCall(self.update) self.update_loop.start(1.0 / base.update_fps, False) # server-related self.git_rev = base.get('git_rev', None) self.ranks = {} for k, v in base.ranks.iteritems(): self.ranks[k.lower()] = v self.scripts = ScriptManager() for script in base.scripts: self.load_script(script) # time self.start_time = reactor.seconds() self.next_secondly_check = self.start_time+1 # start listening self.listen_tcp(base.port, self) def buildProtocol(self, addr): # return None here to refuse the connection. # will use this later to hardban e.g. DoS ret = self.scripts.call('on_connection_attempt', address=addr).result if ret is False: print '[WARNING] Connection attempt for %s blocked by script!' % addr.host return None elif ret is not None: return BanProtocol(ret) if database.is_banned_ip(self.db_con, addr.host): print '[INFO] Banned client %s tried to join.' % addr.host return BanProtocol('You are banned on this server.') return CubeWorldConnection(self, addr) def update(self): self.scripts.call('update') # entity updates for entity_id, entity in self.entities.iteritems(): entity.mask = 0 def send_chat(self, value): packet = ServerChatMessage() packet.entity_id = 0 packet.value = value self.broadcast_packet(packet) def broadcast_packet(self, packet): data = write_packet(packet) for player in self.players.values(): player.transport.write(data) # line/string formatting options based on config def format(self, value): format_dict = {'server_name': self.config.base.server_name} return value % format_dict def format_lines(self, value): lines = [] for line in value: lines.append(self.format(line)) return lines # script methods def load_script(self, name): try: return self.scripts[name] except KeyError: pass try: mod = __import__('scripts.%s' % name, globals(), locals(), [name]) except ImportError, e: traceback.print_exc(e) return None script = mod.get_class()(self) print 'Loaded script %r' % name return script