class BravoFactory(Factory): """ A ``Factory`` that creates ``BravoProtocol`` objects when connected to. """ implements(IPushProducer) protocol = BravoProtocol timestamp = None time = 0 day = 0 handshake_hook = None login_hook = None interface = "" def __init__(self, name): """ Create a factory and world. ``name`` is the string used to look up factory-specific settings from the configuration. :param str name: internal name of this factory """ log.msg("Initializing factory for world '%s'..." % name) self.name = name self.config_name = "world %s" % name self.port = configuration.getint(self.config_name, "port") self.interface = configuration.getdefault(self.config_name, "host", "") self.world = World(name) self.world.factory = self if configuration.has_option(self.config_name, "perm_cache"): cache_level = configuration.getint(self.config_name, "perm_cache") self.world.enable_cache(cache_level) self.protocols = dict() self.eid = 1 self.time_loop = LoopingCall(self.update_time) self.time_loop.start(2) authenticator = configuration.get(self.config_name, "authenticator") selected = retrieve_named_plugins(IAuthenticator, [authenticator])[0] log.msg("Using authenticator %s" % selected.name) self.handshake_hook = selected.handshake self.login_hook = selected.login generators = configuration.getlist("bravo", "generators") generators = retrieve_sorted_plugins(ITerrainGenerator, generators) log.msg("Using generators %s" % ", ".join(i.name for i in generators)) self.world.pipeline = generators self.chat_consumers = set() log.msg("Factory successfully initialized for world '%s'!" % name) def buildProtocol(self, addr): """ Create a protocol. This overriden method provides early player entity registration, as a solution to the username/entity race that occurs on login. """ banned = self.world.serializer.load_plugin_data("banned_ips") for ip in banned.split(): if addr.host == ip: # Use BannedProtocol with extreme prejudice. log.msg("Kicking banned IP %s" % addr) p = BannedProtocol() p.factory = self return p log.msg("Starting connection for %s" % addr) p = self.protocol() p.factory = self self.register_entity(p) return p def create_entity(self, x, y, z, name, **kwargs): """ Spawn an entirely new entity. Handles entity registration as well as instantiation. """ location = Location() location.x = x location.y = y location.z = z entity = entities[name](eid=0, location=location, **kwargs) self.register_entity(entity) bigx = entity.location.x // 16 bigz = entity.location.z // 16 chunk = self.world.load_chunk(bigx, bigz) chunk.entities.add(entity) log.msg("Created entity %s" % entity) return entity def register_entity(self, entity): """ Registers an entity with this factory. Registration is perhaps too fancy of a name; this method merely makes sure that the entity has a unique and usable entity ID. """ if not entity.eid: self.eid += 1 entity.eid = self.eid log.msg("Registered entity %s" % entity) def destroy_entity(self, entity): """ Destroy an entity. The factory doesn't have to know about entities, but it is a good place to put this logic. """ bigx = entity.location.x // 16 bigz = entity.location.z // 16 chunk = self.world.load_chunk(bigx, bigz) chunk.entities.discard(entity) log.msg("Destroyed entity %s" % entity) def update_time(self): """ Update the in-game timer. The timer goes from 0 to 24000, both of which are high noon. The clock increments by 20 every second. Days are 20 minutes long. The day clock is incremented every in-game day, which is every 20 minutes. The day clock goes from 0 to 360, which works out to a reset once every 5 days. This is a Babylonian in-game year. """ if self.timestamp is None: # First run since the start of the factory; re-init everything. self.timestamp = time() self.update_season() t = time() self.time += 20 * (t - self.timestamp) self.timestamp = t while self.time > 24000: self.time -= 24000 self.day += 1 while self.day > 360: self.day -= 360 self.update_season() def update_season(self): """ Update the world's season. """ plugins = configuration.getlistdefault(self.config_name, "seasons", []) for plugin in retrieve_named_plugins(ISeason, plugins): if plugin.day == self.day: self.world.season = plugin def chat(self, message): """ Relay chat messages. Chat messages are sent to all connected clients, as well as to anybody consuming this factory. """ for consumer in self.chat_consumers: consumer.write((self, message)) # Prepare the message for chat packeting. for user in self.protocols: message = message.replace(user, chat_name(user)) message = sanitize_chat(message) packet = make_packet("chat", message=message) self.broadcast(packet) def broadcast(self, packet): """ Broadcast a packet to all connected players. """ for player in self.protocols.itervalues(): player.transport.write(packet) def broadcast_for_others(self, packet, protocol): """ Broadcast a packet to all players except the originating player. Useful for certain packets like player entity spawns which should never be reflexive. """ for player in self.protocols.itervalues(): if player is not protocol: player.transport.write(packet) def broadcast_for_chunk(self, packet, x, z): """ Broadcast a packet to all players that have a certain chunk loaded. `x` and `z` are chunk coordinates, not block coordinates. """ for player in self.protocols.itervalues(): if (x, z) in player.chunks: player.transport.write(packet) def flush_chunk(self, chunk): """ Flush a damaged chunk to all players that have it loaded. """ if chunk.is_damaged(): packet = chunk.get_damage_packet() for player in self.protocols.itervalues(): if (chunk.x, chunk.z) in player.chunks: player.transport.write(packet) chunk.clear_damage() def give(self, coords, block, quantity): """ Spawn a pickup at the specified coordinates. The coordinates need to be in pixels, not blocks. :param tuple coords: coordinates, in pixels :param tuple block: key of block or item to drop :param int quantity: number of blocks to drop in the stack """ x, y, z = coords entity = self.create_entity(x // 32, y // 32, z // 32, "Item", item=block, quantity=quantity) packet = entity.save_to_packet() self.broadcast(packet) packet = make_packet("create", eid=entity.eid) self.broadcast(packet) def pauseProducing(self): pass def resumeProducing(self): pass def stopProducing(self): pass
os.makedirs(target) print "Making map of %dx%d chunks in %s" % (size, size, target) print "Using pipeline: %s" % ", ".join(plugin.name for plugin in pipeline) world = World(target) world.pipeline = pipeline world.season = None counts = [1, 2, 4, 5, 8] count = 0 total = size ** 2 cpu = 0 before = time.time() for i, j in product(xrange(size), repeat=2): start = time.time() chunk = world.load_chunk(i, j) cpu += (time.time() - start) world.save_chunk(chunk) count += 1 if count >= counts[0]: print "Status: %d/%d (%.2f%%)" % (count, total, count * 100 / total) counts.append(counts.pop(0) * 10) taken = time.time() - before print "Finished!" print "Took %.2f seconds to generate (%dms/chunk)" % (taken, taken * 1000 / size) print "Spent %.2f seconds on CPU (%dms/chunk)" % (cpu, cpu * 1000 / size)
os.makedirs(target) print "Making map of %dx%d chunks in %s" % (size, size, target) print "Using pipeline: %s" % ", ".join(plugin.name for plugin in pipeline) world = World(target) world.pipeline = pipeline world.season = None counts = [1, 2, 4, 5, 8] count = 0 total = size**2 cpu = 0 before = time.time() for i, j in product(xrange(size), repeat=2): start = time.time() chunk = world.load_chunk(i, j) cpu += (time.time() - start) world.save_chunk(chunk) count += 1 if count >= counts[0]: print "Status: %d/%d (%.2f%%)" % (count, total, count * 100 / total) counts.append(counts.pop(0) * 10) taken = time.time() - before print "Finished!" print "Took %.2f seconds to generate (%dms/chunk)" % (taken, taken * 1000 / size) print "Spent %.2f seconds on CPU (%dms/chunk)" % (cpu, cpu * 1000 / size)
class BravoFactory(Factory): """ A ``Factory`` that creates ``BravoProtocol`` objects when connected to. """ implements(IPushProducer) protocol = BravoProtocol timestamp = None time = 0 day = 0 handshake_hook = None login_hook = None interface = "" def __init__(self, name): """ Create a factory and world. ``name`` is the string used to look up factory-specific settings from the configuration. :param str name: internal name of this factory """ log.msg("Initializing factory for world '%s'..." % name) self.name = name self.config_name = "world %s" % name self.port = configuration.getint(self.config_name, "port") self.interface = configuration.getdefault(self.config_name, "host", "") self.world = World(name) self.world.factory = self if configuration.has_option(self.config_name, "perm_cache"): cache_level = configuration.getint(self.config_name, "perm_cache") self.world.enable_cache(cache_level) self.protocols = dict() self.eid = 1 self.time = self.world.time self.time_loop = LoopingCall(self.update_time) self.time_loop.start(2) authenticator = configuration.get(self.config_name, "authenticator") selected = retrieve_named_plugins(IAuthenticator, [authenticator])[0] log.msg("Using authenticator %s" % selected.name) self.handshake_hook = selected.handshake self.login_hook = selected.login generators = configuration.getlist(self.config_name, "generators") generators = retrieve_sorted_plugins(ITerrainGenerator, generators) log.msg("Using generators %s" % ", ".join(i.name for i in generators)) self.world.pipeline = generators self.chat_consumers = set() log.msg("Factory successfully initialized for world '%s'!" % name) def buildProtocol(self, addr): """ Create a protocol. This overriden method provides early player entity registration, as a solution to the username/entity race that occurs on login. """ banned = self.world.serializer.load_plugin_data("banned_ips") for ip in banned.split(): if addr.host == ip: # Use BannedProtocol with extreme prejudice. log.msg("Kicking banned IP %s" % addr) p = BannedProtocol() p.factory = self return p log.msg("Starting connection for %s" % addr) p = self.protocol(self.name) p.factory = self self.register_entity(p) return p def create_entity(self, x, y, z, name, **kwargs): """ Spawn an entirely new entity. Handles entity registration as well as instantiation. """ location = Location() location.x = x location.y = y location.z = z entity = entities[name](eid=0, location=location, **kwargs) self.register_entity(entity) bigx = entity.location.x // 16 bigz = entity.location.z // 16 chunk = self.world.load_chunk(bigx, bigz) chunk.entities.add(entity) log.msg("Created entity %s" % entity) return entity def register_entity(self, entity): """ Registers an entity with this factory. Registration is perhaps too fancy of a name; this method merely makes sure that the entity has a unique and usable entity ID. """ if not entity.eid: self.eid += 1 entity.eid = self.eid log.msg("Registered entity %s" % entity) def destroy_entity(self, entity): """ Destroy an entity. The factory doesn't have to know about entities, but it is a good place to put this logic. """ bigx = entity.location.x // 16 bigz = entity.location.z // 16 chunk = self.world.load_chunk(bigx, bigz) chunk.entities.discard(entity) log.msg("Destroyed entity %s" % entity) def update_time(self): """ Update the in-game timer. The timer goes from 0 to 24000, both of which are high noon. The clock increments by 20 every second. Days are 20 minutes long. The day clock is incremented every in-game day, which is every 20 minutes. The day clock goes from 0 to 360, which works out to a reset once every 5 days. This is a Babylonian in-game year. """ if self.timestamp is None: # First run since the start of the factory; re-init everything. self.timestamp = time() self.update_season() t = time() self.time += 20 * (t - self.timestamp) self.timestamp = t while self.time > 24000: self.time -= 24000 self.day += 1 while self.day > 360: self.day -= 360 self.update_season() def update_season(self): """ Update the world's season. """ plugins = configuration.getlistdefault(self.config_name, "seasons", []) for plugin in retrieve_named_plugins(ISeason, plugins): if plugin.day == self.day: self.world.season = plugin def chat(self, message): """ Relay chat messages. Chat messages are sent to all connected clients, as well as to anybody consuming this factory. """ for consumer in self.chat_consumers: consumer.write((self, message)) # Prepare the message for chat packeting. for user in self.protocols: message = message.replace(user, chat_name(user)) message = sanitize_chat(message) packet = make_packet("chat", message=message) self.broadcast(packet) def broadcast(self, packet): """ Broadcast a packet to all connected players. """ for player in self.protocols.itervalues(): player.transport.write(packet) def broadcast_for_others(self, packet, protocol): """ Broadcast a packet to all players except the originating player. Useful for certain packets like player entity spawns which should never be reflexive. """ for player in self.protocols.itervalues(): if player is not protocol: player.transport.write(packet) def broadcast_for_chunk(self, packet, x, z): """ Broadcast a packet to all players that have a certain chunk loaded. `x` and `z` are chunk coordinates, not block coordinates. """ for player in self.protocols.itervalues(): if (x, z) in player.chunks: player.transport.write(packet) def flush_chunk(self, chunk): """ Flush a damaged chunk to all players that have it loaded. """ if chunk.is_damaged(): packet = chunk.get_damage_packet() for player in self.protocols.itervalues(): if (chunk.x, chunk.z) in player.chunks: player.transport.write(packet) chunk.clear_damage() def give(self, coords, block, quantity): """ Spawn a pickup at the specified coordinates. The coordinates need to be in pixels, not blocks. If the size of the stack is too big, multiple stacks will be dropped. :param tuple coords: coordinates, in pixels :param tuple block: key of block or item to drop :param int quantity: number of blocks to drop in the stack """ x, y, z = coords while quantity > 0: entity = self.create_entity(x // 32, y // 32, z // 32, "Item", item=block, quantity=min(quantity, 64)) packet = entity.save_to_packet() packet += make_packet("create", eid=entity.eid) self.broadcast(packet) quantity -= 64 def players_near(self, player, radius): """ Obtain other players within a radius of a given player. Radius is measured in blocks. """ for i in (p for p in self.protocols.itervalues() if player.location.distance(p.location) <= radius and p != player): yield i def stopFactory(self): """ Called before factory stops listening on ports. Used to perform shutdown tasks. """ if not self.world.saving: return log.msg("Shutting down; flushing world data...") # Flush all dirty chunks to disk. for chunk in self.world.dirty_chunk_cache.itervalues(): self.world.save_chunk(chunk) # Write back current world time. self.world.time = self.time self.world.serializer.save_level(self.world) log.msg("World data saved!") def pauseProducing(self): pass def resumeProducing(self): pass def stopProducing(self): pass