Esempio n. 1
0
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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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
Esempio n. 6
0
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())
Esempio n. 7
0
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)
Esempio n. 8
0
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)
Esempio n. 9
0
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)
Esempio n. 10
0
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
Esempio n. 11
0
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)
Esempio n. 12
0
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
Esempio n. 13
0
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