def load_config(self): try: self.colors = DotDict(self.config.config.colors) self.logger.info("Configuration loaded!") except: self.logger.warning( "Failed to load from config! Initiating with default values") self.colors = DotDict({ "Admin": "^#F7434C", "SuperAdmin": "^#E23800;", "Admin": "^#C443F7;", "Moderator": "^#4385F7", "Registered": "^#A0F743;", "default": "^green;" })
def get_plugin_config(self, plugin_name): if plugin_name not in self.config.plugins: storage = DotDict({}) self.config.plugins[plugin_name] = storage else: storage = self.config.plugins[plugin_name] return storage
def get_storage(self, caller): """ Collect the storage for caller. :param caller: Entity requesting its storage :return: Storage shelf for caller. If called doesn't have anything in storage, return an empty shelf. """ name = caller.name if name not in self.plugin_shelf: self.plugin_shelf[name] = DotDict({}) return self.plugin_shelf[name]
def load_config(self, path, default=False): if not isinstance(path, Path): path = Path(path) if default: self.load_defaults(path) try: with path.open() as f: self._raw_config = f.read() except FileNotFoundError: path.touch() with path.open("w") as f: f.write("{}") self._raw_config = "{}" self._path = path recursive_dictionary_update(self._config, json.loads(self._raw_config)) if "plugins" not in self._config: self._config['plugins'] = DotDict({})
def load_config(self, path, default=False): if not isinstance(path, Path): path = Path(path) if default: self.load_defaults(path) try: with path.open() as f: self._raw_config = f.read() except FileNotFoundError: path.touch() with path.open("w") as f: f.write("{}") self._raw_config = "{}" self._path = path try: recursive_dictionary_update(self._config, json.loads(self._raw_config)) except ValueError as e: logger.error("Error while loading config.json file:\n\t" "{}".format(e)) sys.exit(1) if "plugins" not in self._config: self._config['plugins'] = DotDict({})
class ChatEnhancements(StorageCommandPlugin): name = "chat_enhancements" depends = ["player_manager", "command_dispatcher"] default_config = {"chat_timestamps": True, "timestamp_color": "^gray;", "colors": DotDict({ "Owner": "^#F7434C;", "SuperAdmin": "^#E23800;", "Admin": "^#C443F7;", "Moderator": "^#4385F7;", "Registered": "^#A0F743;", "default": "^yellow;" })} def __init__(self): super().__init__() self.command_dispatcher = None self.colors = None self.cts = None self.cts_color = None self.last_whisper = {} def activate(self): super().activate() self.command_dispatcher = self.plugins.command_dispatcher.plugin_config self.colors = self.config.get_plugin_config(self.name)["colors"] self.cts = self.config.get_plugin_config(self.name)["chat_timestamps"] self.cts_color = self.config.get_plugin_config(self.name)[ "timestamp_color"] self.last_whisper = {} link_plugin_if_available(self, "irc_bot") if "ignores" not in self.storage: self.storage["ignores"] = {} # Packet hooks - look for these packets and act on them def on_connect_success(self, data, connection): """ Catch when a successful connection is established. Set the player's chat style to be the server default. :param data: :param connection: :return: Boolean: True. Must be true, so that packet get passed on. """ return True def on_chat_received(self, data, connection): sender = "" if data["parsed"]["name"]: if data["parsed"]["name"] != "server": sender = self.plugins['player_manager'].get_player_by_name( data["parsed"]["name"]) if connection.player.uuid not in self.storage["ignores"]: self.storage["ignores"][connection.player.uuid] = [] if sender.uuid in self.storage["ignores"][ connection.player.uuid]: return False try: sender = self.decorate_line(sender.connection) except AttributeError: self.logger.warning("Sender {} is sending a message that " "the wrapper isn't handling correctly" "".format(data["parsed"]["name"])) sender = data["parsed"]["name"] yield from send_message(connection, data["parsed"]["message"], mode=data["parsed"]["header"]["mode"], client_id=data["parsed"]["header"]["client_id"], name=sender, channel=data["parsed"]["header"]["channel"]) def on_chat_sent(self, data, connection): """ Catch when someone sends a message. Add a timestamp to the message (if that feature is turned on). Colorize the player's name based on their role. :param data: The packet containing the message. :param connection: The connection from which the packet came. :return: Boolean. True if an error occurred while generating a colored name (so that we don't stop the packet from flowing). Null if the message came from the server (since it doesn't need colors) or if the message is a command. """ message = data['parsed']['message'] if message.startswith(self.command_dispatcher.command_prefix): return True if self.plugins['chat_manager'].mute_check(connection.player): return False return True # Helper functions - Used by commands def decorate_line(self, connection): if self.cts: now = datetime.now() timestamp = "{}{}{}> <".format(self.cts_color, now.strftime("%H:%M"), "^reset;") else: timestamp = "" try: sender = timestamp + self._colored_name(connection.player) except AttributeError as e: self.logger.warning( "AttributeError in colored_name: {}".format(str(e))) sender = connection.player.alias return sender def _colored_name(self, data): """ Generate colored name based on target's role. :param data: target to check against :return: DotDict. Name of target will be colorized. """ if "Owner" in data.roles: color = self.colors.Owner elif "SuperAdmin" in data.roles: color = self.colors.SuperAdmin elif "Admin" in data.roles: color = self.colors.Admin elif "Moderator" in data.roles: color = self.colors.Moderator elif "Registered" in data.roles: color = self.colors.Registered else: color = self.colors.default return color + data.alias + "^reset;" @asyncio.coroutine def _send_to_server(self, message, mode, connection): msg_base = data_parser.ChatSent.build(dict(message=" ".join(message), send_mode=mode)) msg_packet = pparser.build_packet(packets.packets['chat_sent'], msg_base) yield from connection.client_raw_write(msg_packet) # Commands - In-game actions that can be performed @Command("l", doc="Send message only to people on same world.", syntax="(message)") def _local(self, data, connection): """ Local chat. Sends a message only to characters who are on the same planet. :param data: The packet containing the command. :param connection: The connection from which the packet came. :return: Null """ if self.plugins['chat_manager'].mute_check(connection.player): send_message(connection, "You are muted and cannot chat.") return False if data: yield from self._send_to_server(data, ChatSendMode.LOCAL, connection) return True @Command("u", doc="Send message to the entire universe.", syntax="(message)") def _universe(self, data, connection): """ Universal chat. Sends a message that everyone can see. :param data: The packet containing the command. :param connection: The connection from which the packet came. :return: Null """ if self.plugins['chat_manager'].mute_check(connection.player): send_message(connection, "You are muted and cannot chat.") return False if data: yield from self._send_to_server(data, ChatSendMode.UNIVERSE, connection) if link_plugin_if_available(self, "irc_bot"): # Try sending it to IRC if we have that available. asyncio.ensure_future( self.plugins["irc_bot"].bot_write( "<{}> {}".format(connection.player.alias, " ".join(data)))) if link_plugin_if_available(self, "discord_bot"): discord = self.plugins['discord_bot'] asyncio.ensure_future(discord.bot.send_message( discord.bot.get_channel(discord.channel), "**<{}>** {}".format(connection.player.alias, " ".join( data)))) return True @Command("p", doc="Send message to only party members.") def _party(self, data, connection): """ Party chat. Sends a message to only members of your party. :param data: The packet containing the command. :param connection: The connection from which the packet came. :return: Null """ if self.plugins['chat_manager'].mute_check(connection.player): send_message(connection, "You are muted and cannot chat.") return False if data: yield from self._send_to_server(data, ChatSendMode.PARTY, connection) return True @Command("whisper", "w", doc="Send message privately to a person.") def _whisper(self, data, connection): """ Whisper. Sends a message to only one person. This command shadows the built-in whisper. :param data: The packet containing the command. :param connection: The connection from which the packet came. :return: Null """ try: name = data[0] except IndexError: raise SyntaxWarning("No target provided.") recipient = self.plugins.player_manager.find_player(name) if recipient is not None: if not recipient.logged_in: send_message(connection, "Player {} is not currently logged in." "".format(recipient.alias)) return False if connection.player.uuid in self.storage["ignores"][recipient.uuid]: send_message(connection, "Player {} is currently ignoring you." .format(recipient.alias)) return False if recipient.uuid in self.storage["ignores"][ connection.player.uuid]: send_message(connection, "Cannot send message to player {} " "as you are currently ignoring " "them.".format(recipient.alias)) return False self.last_whisper[recipient.uuid] = connection.player.alias self.last_whisper[connection.player.uuid] = recipient.alias message = " ".join(data[1:]) client_id = connection.player.client_id sender = self.decorate_line(connection) send_mode = ChatReceiveMode.WHISPER channel = "Private" yield from send_message(recipient.connection, message, client_id=client_id, name=sender, mode=send_mode, channel=channel) yield from send_message(connection, message, client_id=client_id, name=sender, mode=send_mode, channel=channel) else: yield from send_message(connection, "Couldn't find a player with name {}" "".format(name)) @Command("reply", "r", doc="Send message privately to the last person who privately " \ "messaged you.") def _reply(self, data, connection): """ Reply. Sends a message to the last person who whispered you. :param data: The packet containing the command. :param connection: The connection from which the packet came. :return: Null """ if connection.player.uuid in self.last_whisper: name = self.last_whisper[connection.player.uuid] recipient = self.plugins.player_manager.find_player(name) else: recipient = None if recipient is not None: if not recipient.logged_in: send_message(connection, "Player {} is not currently logged in." "".format(recipient.alias)) return False if connection.player.uuid in self.storage["ignores"][recipient.uuid]: send_message(connection, "Player {} is currently ignoring you." .format(recipient.alias)) return False if recipient.uuid in self.storage["ignores"][ connection.player.uuid]: send_message(connection, "Cannot send message to player {} " "as you are currently ignoring " "them.".format(recipient.alias)) return False self.last_whisper[recipient.uuid] = connection.player.alias self.last_whisper[connection.player.uuid] = recipient.alias message = " ".join(data) client_id = connection.player.client_id sender = self.decorate_line(connection) send_mode = ChatReceiveMode.WHISPER channel = "Private" yield from send_message(recipient.connection, message, client_id=client_id, name=sender, mode=send_mode, channel=channel) yield from send_message(connection, message, client_id=client_id, name=sender, mode=send_mode, channel=channel) else: yield from send_message(connection, "You haven't been messaged by anyone.") @Command("ignore", doc="Ignores a player, preventing you from seeing their " "messages. Use /ignore again to toggle.") def _ignore(self, data, connection): user = connection.player.uuid try: name = data[0] except IndexError: raise SyntaxWarning("No target provided.") target = self.plugins.player_manager.find_player(name) if target is not None: if target == connection.player: send_message(connection, "Can't ignore yourself!") return False if not self.storage["ignores"][user]: self.storage["ignores"][user] = [] if target.uuid in self.storage["ignores"][user]: self.storage["ignores"][user].remove(target.uuid) yield from send_message(connection, "User {} removed from " "ignores list.".format(target.alias)) else: self.storage["ignores"][user].append(target.uuid) yield from send_message(connection, "User {} added to ignores " "list.".format(target.alias))
class BasePlugin(metaclass=BaseMeta): """ Defines an interface for all plugins to inherit from. Note that the init method should generally not be overrode; all setup work should be done in activate() if possible. If you do override __init__, remember to super()! Note that only one instance of each plugin will be instantiated for *all* connected clients. self.connection will be changed by the plugin manager to the current connection. You may access the factory if necessary via self.factory.connections to access other clients, but this "Is Not A Very Good Idea" (tm) `name` *must* be defined in child classes or else the plugin manager will complain quite thoroughly. """ name = "Base Plugin" description = "The common class for all plugins to inherit from." version = ".1" depends = () default_config = None plugins = DotDict({}) auto_activate = True def __init__(self): self.loop = asyncio.get_event_loop() self.plugin_config = self.config.get_plugin_config(self.name) if isinstance(self.default_config, collections.Mapping): temp = recursive_dictionary_update(self.default_config, self.plugin_config) self.plugin_config.update(temp) else: self.plugin_config = self.default_config def activate(self): pass def deactivate(self): pass def on_protocol_request(self, data, connection): """Packet type: 0 """ return True def on_protocol_response(self, data, connection): """Packet type: 1 """ return True def on_server_disconnect(self, data, connection): """Packet type: 2 """ return True def on_connect_success(self, data, connection): """Packet type: 3 """ return True def on_connect_failure(self, data, connection): """Packet type: 4 """ return True def on_handshake_challenge(self, data, connection): """Packet type: 5 """ return True def on_chat_received(self, data, connection): """Packet type: 6 """ return True def on_universe_time_update(self, data, connection): """Packet type: 7 """ return True def on_celestial_response(self, data, connection): """Packet type: 8 """ return True def on_player_warp_result(self, data, connection): """Packet type: 9 """ return True def on_client_connect(self, data, connection): """Packet type: 10 """ return True def on_client_disconnect_request(self, data, connection): """Packet type: 11 """ return True def on_handshake_response(self, data, connection): """Packet type: 12 """ return True def on_player_warp(self, data, connection): """Packet type: 13 """ return True def on_fly_ship(self, data, connection): """Packet type: 14 """ return True def on_chat_sent(self, data, connection): """Packet type: 15 """ return True def on_celestial_request(self, data, connection): """Packet type: 16 """ return True def on_client_context_update(self, data, connection): """Packet type: 17 """ return True def on_world_start(self, data, connection): """Packet type: 18 """ return True def on_world_stop(self, data, connection): """Packet type: 19 """ return True def on_central_structure_update(self, data, connection): """Packet type: 20 """ return True def on_tile_array_update(self, data, connection): """Packet type: 21 """ return True def on_tile_update(self, data, connection): """Packet type: 22 """ return True def on_tile_liquid_update(self, data, connection): """Packet type: 23 """ return True def on_tile_damage_update(self, data, connection): """Packet type: 24 """ return True def on_tile_modification_failure(self, data, connection): """Packet type: 25 """ return True def on_give_item(self, data, connection): """Packet type: 26 """ return True def on_environment_update(self, data, connection): """Packet type: 27 """ return True def on_entity_interact_result(self, data, connection): """Packet type: 28 """ return True def on_update_tile_protection(self, data, connection): """Packet type: 29 """ return True def on_set_player_start(self, data, connection): """Packet type: 30 """ return True def on_find_unique_entity_response(self, data, connection): """Packet type: 31 """ return True def on_modify_tile_list(self, data, connection): """Packet type: 32 """ return True def on_damage_tile_group(self, data, connection): """Packet type: 33 """ return True def on_collect_liquid(self, data, connection): """Packet type: 34 """ return True def on_request_drop(self, data, connection): """Packet type: 35 """ return True def on_spawn_entity(self, data, connection): """Packet type: 36 """ return True def on_entity_interact(self, data, connection): """Packet type: 37 """ return True def on_connect_wire(self, data, connection): """Packet type: 38 """ return True def on_disconnect_all_wires(self, data, connection): """Packet type: 39 """ return True def on_world_client_state_update(self, data, connection): """Packet type: 40 """ return True def on_find_unique_entity(self, data, connection): """Packet type: 41 """ return True def on_entity_create(self, data, connection): """Packet type: 42 """ return True def on_entity_update(self, data, connection): """Packet type: 43 """ return True def on_entity_destroy(self, data, connection): """Packet type: 44 """ return True def on_hit_request(self, data, connection): """Packet type: 45 """ return True def on_damage_request(self, data, connection): """Packet type: 46 """ return True def on_damage_notification(self, data, connection): """Packet type: 47 """ return True def on_entity_message(self, data, connection): """Packet type: 48 """ return True def on_entity_message_response(self, data, connection): """Packet type: 49 """ return True def on_update_world_properties(self, data, connection): """Packet type: 50 """ return True def on_step_update(self, data, connection): """Packet type: 51 """ return True def __repr__(self): return "<Plugin instance: %s (version %s)>" % (self.name, self.version)
def config(self): if self._dot_dict is None: self._dot_dict = DotDict(self._config) return self._dot_dict
class BasePlugin(metaclass=BaseMeta): """ Defines an interface for all plugins to inherit from. Note that the __init__ method should generally not be overrode; all setup work should be done in activate() if possible. If you do override __init__, remember to super()! Note that only one instance of each plugin will be instantiated for *all* connected clients. self.protocol will be changed by the plugin manager to the current protocol. You may access the factory if necessary via self.factory.protocols to access other clients, but this "Is Not A Very Good Idea" (tm) `name` *must* be defined in child classes or else the plugin manager will complain quite thoroughly. """ name = "Base Plugin" description = "The common class for all plugins to inherit from." version = ".1" depends = [] plugins = DotDict({}) auto_activate = True def __init__(self): self.loop = asyncio.get_event_loop() def activate(self): pass def deactivate(self): pass def on_protocol_version(self, data, protocol): return True def on_server_disconnect(self, data, protocol): return True def on_handshake_challenge(self, data, protocol): return True def on_chat_received(self, data, protocol): return True def on_universe_time_update(self, data, protocol): return True def on_handshake_response(self, data, protocol): return True def on_client_context_update(self, data, protocol): return True def on_world_start(self, data, protocol): return True def on_world_stop(self, data, protocol): return True def on_tile_array_update(self, data, protocol): return True def on_tile_update(self, data, protocol): return True def on_tile_liquid_update(self, data, protocol): return True def on_tile_damage_update(self, data, protocol): return True def on_tile_modification_failure(self, data, protocol): return True def on_give_item(self, data, protocol): return True def on_swap_in_container_result(self, data, protocol): return True def on_environment_update(self, data, protocol): return True def on_entity_interact_result(self, data, protocol): return True def on_modify_tile_list(self, data, protocol): return True def on_damage_tile(self, data, protocol): return True def on_damage_tile_group(self, data, protocol): return True def on_request_drop(self, data, protocol): return True def on_spawn_entity(self, data, protocol): return True def on_entity_interact(self, data, protocol): return True def on_connect_wire(self, data, protocol): return True def on_disconnect_all_wires(self, data, protocol): return True def on_open_container(self, data, protocol): return True def on_close_container(self, data, protocol): return True def on_swap_in_container(self, data, protocol): return True def on_item_apply_in_container(self, data, protocol): return True def on_start_crafting_in_container(self, data, protocol): return True def on_stop_crafting_in_container(self, data, protocol): return True def on_burn_container(self, data, protocol): return True def on_clear_container(self, data, protocol): return True def on_world_update(self, data, protocol): return True def on_entity_create(self, data, protocol): return True def on_entity_update(self, data, protocol): return True def on_entity_destroy(self, data, protocol): return True def on_status_effect_request(self, data, protocol): return True def on_update_world_properties(self, data, protocol): return True def on_heartbeat(self, data, protocol): return True def on_connect_response(self, data, protocol): return True def on_chat_sent(self, data, protocol): return True def on_damage_notification(self, data, protocol): return True def on_client_connect(self, data, protocol): return True def on_client_disconnect(self, data, protocol): return True def on_warp_command(self, data, protocol): return True def __repr__(self): return "<Plugin instance: %s (version %s)>" % (self.name, self.version)
def __init__(self): super().__init__() self.greeting = None self.gifts = DotDict({})
class NewPlayerGreeter(SimpleCommandPlugin): name = "new_player_greeters" depends = ["player_manager"] default_config = {"greeting": "Why hello there. You look like you're " "new here. Here, take this. It should " "help you on your way.", "gifts": DotDict({ })} def __init__(self): super().__init__() self.greeting = None self.gifts = DotDict({}) def activate(self): super().activate() self.greeting = self.config.get_plugin_config(self.name)["greeting"] self.gifts = self.config.get_plugin_config(self.name)["gifts"] def on_world_start(self, data, connection): """ Client on world hook. After a client connects, when their world first loads, check if they are new to the server (never been seen before). If they're new, send them a nice message and give them some starter items. :param data: The packet saying the client connected. :param connection: The connection from which the packet came. :return: Boolean: True. Anything else stops the client from being able to connect. """ player = self.plugins['player_manager'].get_player_by_name( connection.player.name) if hasattr(player, 'seen_before'): return True else: asyncio.ensure_future(self._new_player_greeter(connection)) asyncio.ensure_future(self._new_player_gifter(connection)) player.seen_before = True return True # Helper functions - Used by commands @asyncio.coroutine def _new_player_greeter(self, connection): """ Helper routine for greeting new players. :param connection: The connection we're showing the message to. :return: Null. """ yield from asyncio.sleep(1.3) yield from send_message(connection, "{}".format(self.greeting), mode=ChatReceiveMode.CHANNEL) return @asyncio.coroutine def _new_player_gifter(self, connection): """ Helper routine for giving items to new players. :param connection: The connection we're showing the message to. :return: Null. """ yield from asyncio.sleep(2) for item, count in self.gifts.items(): count = int(count) if count > 10000 and item != "money": count = 10000 item_base = GiveItem.build(dict(name=item, count=count, variant_type=7, description="")) item_packet = pparser.build_packet(packets.packets['give_item'], item_base) yield from asyncio.sleep(.1) yield from connection.raw_write(item_packet) send_message(connection, "You have been given {} {}".format(str(count), item), mode=ChatReceiveMode.COMMAND_RESULT) return
class NewPlayerGreeter(SimpleCommandPlugin): name = "new_player_greeters" depends = ["player_manager"] default_config = { "greeting": "Why hello there. You look like you're " "new here. Here, take this. It should " "help you on your way.", "gifts": DotDict({}) } def __init__(self): super().__init__() self.greeting = None self.gifts = DotDict({}) def activate(self): super().activate() self.greeting = self.config.get_plugin_config(self.name)["greeting"] self.gifts = self.config.get_plugin_config(self.name)["gifts"] def on_world_start(self, data, connection): """ Client on world hook. After a client connects, when their world first loads, check if they are new to the server (never been seen before). If they're new, send them a nice message and give them some starter items. :param data: The packet saying the client connected. :param connection: The connection from which the packet came. :return: Boolean: True. Anything else stops the client from being able to connect. """ player = self.plugins['player_manager'].get_player_by_name( connection.player.name) if hasattr(player, 'seen_before'): return True else: asyncio.ensure_future(self._new_player_greeter(connection)) asyncio.ensure_future(self._new_player_gifter(connection)) player.seen_before = True return True # Helper functions - Used by commands @asyncio.coroutine def _new_player_greeter(self, connection): """ Helper routine for greeting new players. :param connection: The connection we're showing the message to. :return: Null. """ yield from asyncio.sleep(1.3) yield from send_message(connection, "{}".format(self.greeting), mode=ChatReceiveMode.RADIO_MESSAGE) return @asyncio.coroutine def _new_player_gifter(self, connection): """ Helper routine for giving items to new players. :param connection: The connection we're showing the message to. :return: Null. """ yield from asyncio.sleep(2) for item, count in self.gifts.items(): count = int(count) if count > 10000 and item != "money": count = 10000 item_base = GiveItem.build( dict(name=item, count=count, variant_type=7, description="")) item_packet = pparser.build_packet(packets.packets['give_item'], item_base) yield from asyncio.sleep(.1) yield from connection.raw_write(item_packet) send_message(connection, "You have been given {} {}".format(str(count), item), mode=ChatReceiveMode.COMMAND_RESULT) return
def get_storage(self, caller): name = caller.name if name not in self.plugin_shelf: self.plugin_shelf[name] = DotDict({}) return self.plugin_shelf[name]