From 8ccaf4cfe06c8a076175e53ac4fb69b3f0be523d Mon Sep 17 00:00:00 2001 From: Joel Pedraza Date: Mon, 3 Feb 2014 12:06:30 -0500 Subject: [PATCH 01/81] fix bug that caused all plugins to be activated twice on startup --- server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server.py b/server.py index cf45096..cb0389e 100644 --- a/server.py +++ b/server.py @@ -537,7 +537,6 @@ def __init__(self): except FatalPluginError: logger.critical("Shutting Down.") sys.exit() - self.plugin_manager.activate_plugins() self.reaper = LoopingCall(self.reap_dead_protocols) self.reaper.start(self.config.reap_time) logger.debug("Factory created, endpoint of port %d" % self.config.bind_port) From 7d7bd6a16298766c15490e8c9c7f0d43c0265fc3 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 16:39:04 -0600 Subject: [PATCH 02/81] Added way better file path support. Should make testing easier, as well as allowing you to run it anywhere else. --- config.py | 51 +++++++++++-------- .../starbound_config_manager.py | 10 ++-- utility_functions.py | 6 ++- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/config.py b/config.py index 7a3aa59..aeb44b4 100644 --- a/config.py +++ b/config.py @@ -3,7 +3,7 @@ import logging import inspect import sys -from utility_functions import recursive_dictionary_update +from utility_functions import recursive_dictionary_update, path class Singleton(type): @@ -21,23 +21,31 @@ class ConfigurationManager(object): logger = logging.getLogger("starrypy.config.ConfigurationManager") def __init__(self): - try: - with open("config/config.json.default", "r") as default_config: - default = json.load(default_config) - except IOError: + default_config_path = path.preauthChild("config/config.json.default") + self.config_path = path.preauthChild("config/config.json") + if default_config_path.exists(): + try: + with default_config_path.open() as default_config: + default = json.load(default_config) + except ValueError: + self.logger.critical("The configuration defaults file (config.json.default) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down." ) + sys.exit() + else: self.logger.critical("The configuration defaults file (config.json.default) doesn't exist! Shutting down.") sys.exit() - except ValueError: - self.logger.critical("The configuration defaults file (config.json.default) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down." ) - sys.exit() - try: - with open("config/config.json", "r") as c: - config = json.load(c) - self.config = recursive_dictionary_update(default, config) - except IOError: + + if self.config_path.exists(): + try: + with self.config_path.open() as c: + config = json.load(c) + self.config = recursive_dictionary_update(default, config) + except ValueError: + self.logger.critical("The configuration file (config.json) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down.") + sys.exit() + else: self.logger.warning("The configuration file (config.json) doesn't exist! Creating one from defaults.") try: - with open("config/config.json", "w") as f: + with self.config_path.open("w") as f: json.dump(default, f, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii = False) except IOError: self.logger.critical("Couldn't write a default configuration file. Please check that StarryPy has write access in the config/ directory.") @@ -45,24 +53,21 @@ def __init__(self): sys.exit() self.logger.warning("StarryPy will now exit. Please examine config.json and adjust the variables appropriately.") sys.exit() - except ValueError: - self.logger.critical("The configuration file (config.json) contains invalid JSON. Please run it against a JSON linter, such as http://jsonlint.com. Shutting down.") - sys.exit() self.logger.debug("Created configuration manager.") self.save() def save(self): try: - with io.open("config/config.json", "w", encoding="utf-8") as config: + with io.open(self.config_path.path, "w", encoding="utf-8") as config: self.logger.debug("Writing configuration file.") - config.write(json.dumps(self.config, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii = False)) + config.write(json.dumps(self.config, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii=False)) except Exception as e: self.logger.critical("Tried to save the configuration file, failed.\n%s", str(e)) raise def __getattr__(self, item): - if item == "config": + if item in ["config", "config_path"]: return super(ConfigurationManager, self).__getattribute__(item) @@ -84,9 +89,13 @@ def __getattr__(self, item): def __setattr__(self, key, value): if key == "config": super(ConfigurationManager, self).__setattr__(key, value) + self.save() + elif key == "config_path": + super(ConfigurationManager, self).__setattr__(key, value) elif key == "plugin_config": caller = inspect.stack()[1][0].f_locals["self"].__class__.name self.config["plugin_config"][caller] = value + self.save else: self.config[key] = value - self.save() \ No newline at end of file + self.save() \ No newline at end of file diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index 0c2bb34..b1e7066 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -1,5 +1,5 @@ import json -import os +from twisted.python.filepath import FilePath from base_plugin import SimpleCommandPlugin from plugin_manager import FatalPluginError from plugins.core import permissions, UserLevels @@ -13,14 +13,14 @@ class StarboundConfigManager(SimpleCommandPlugin): def activate(self): super(StarboundConfigManager, self).activate() try: - configuration_file = os.path.join(self.config.starbound_path, "starbound.config") - if not os.path.exists(configuration_file): + configuration_file = FilePath(self.config.starbound_path).child('starbound.config') + if not configuration_file.exists(): raise FatalPluginError( "Could not open starbound configuration file. Tried path: %s" % configuration_file) except AttributeError: raise FatalPluginError("The starbound path (starbound_path) is not set in the configuration.") try: - with open(configuration_file, "r") as f: + with configuration_file.open() as f: starbound_config = json.load(f) except Exception as e: raise FatalPluginError( @@ -36,4 +36,4 @@ def activate(self): def spawn(self, data): """Moves your ship to spawn. Syntax: /move_ship_to_spawn""" self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message("Moving your ship to spawn.") + self.protocol.send_chat_message("Moving your ship to spawn.") \ No newline at end of file diff --git a/utility_functions.py b/utility_functions.py index 4b03807..a28da38 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -1,11 +1,14 @@ import collections import logging +import os from construct import Container +from twisted.python.filepath import FilePath import packets +path = FilePath(os.path.dirname(os.path.abspath(__file__))) logger = logging.getLogger("starrypy.utility_functions") @@ -85,4 +88,5 @@ def extract_name(l): else: name.append(s) raise ValueError("Final terminator character of <%s> not found" % - terminator) \ No newline at end of file + terminator) + From 0d81707255b55cb289cf11336e465f8e4096af5d Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 17:38:41 -0600 Subject: [PATCH 03/81] Fixed paths everywhere; you can now run it without having to cd to the StarryPy directory itself. --- plugin_manager.py | 18 +++++++++--------- plugins/core/player_manager/manager.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugin_manager.py b/plugin_manager.py index 86c4893..4643d3d 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -4,14 +4,13 @@ """ import inspect import logging -import os import sys from twisted.internet import reactor from twisted.internet.task import deferLater from base_plugin import BasePlugin from config import ConfigurationManager - +from utility_functions import path class DuplicatePluginError(Exception): """ @@ -55,8 +54,9 @@ def __init__(self, factory, base_class=BasePlugin): self.base_class = base_class self.factory = factory self.load_order = [] - self.plugin_dir = os.path.realpath(self.config.plugin_path) - sys.path.append(self.plugin_dir) + #self.plugin_dir = os.path.realpath(self.config.plugin_path) + self.plugin_dir = path.child(self.config.plugin_path) + sys.path.append(self.plugin_dir.path) self.load_plugins(self.plugin_dir) self.logger.info("Loaded plugins:\n%s" % "\n".join( @@ -71,11 +71,11 @@ def load_plugins(self, plugin_dir): :return: None """ seen_plugins = [] - for f in os.listdir(plugin_dir): - if f.endswith(".py"): - name = f[:-3] - elif os.path.isdir(os.path.join(plugin_dir, f)): - name = f + for f in plugin_dir.globChildren("*"): + if f.splitext()[1] == ".py": + name = f.basename()[:-3] + elif f.isdir(): + name = f.basename() else: continue try: diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 990759f..f3bcc3d 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -11,7 +11,7 @@ from sqlalchemy.ext.declarative import declarative_base as sqla_declarative_base from twisted.words.ewords import AlreadyLoggedIn from sqlalchemy.types import TypeDecorator, VARCHAR - +from utility_functions import path class JSONEncodedDict(TypeDecorator): impl = VARCHAR @@ -159,7 +159,7 @@ class Ban(Base): class PlayerManager(object): def __init__(self, config): self.config = config - self.engine = create_engine('sqlite:///%s' % self.config.player_db) + self.engine = create_engine('sqlite:///%s' % path.preauthChild(self.config.player_db).path) self.session = Session(self.engine) Base.metadata.create_all(self.engine) for player in self.session.query(Player).all(): From 4982efbda2d34ff0580e797f2afb195a08d4eb1c Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 20:45:21 -0600 Subject: [PATCH 04/81] Fixed error message on some log-ins because of the bouncer. --- plugins/bouncer_plugin/bouncer_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/bouncer_plugin/bouncer_plugin.py b/plugins/bouncer_plugin/bouncer_plugin.py index 43119b1..8bc3b47 100644 --- a/plugins/bouncer_plugin/bouncer_plugin.py +++ b/plugins/bouncer_plugin/bouncer_plugin.py @@ -28,6 +28,6 @@ def activate(self): (lambda x: False if self.protocol.player.access_level < UserLevels.REGISTERED else True)) def after_connect_response(self, data): - if self.protocol.player.access_level < UserLevels.REGISTERED: + if self.protocol.player is not None and self.protocol.player.access_level < UserLevels.REGISTERED: self.protocol.send_chat_message( "^#FF0000;This server is protected. You can't build or perform any destructive actions. Speak to an administrator about becoming a registered user.^#F7EB43;") \ No newline at end of file From 2a48082c8dceb13a93ce1533655f4f4f282ff853 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 20:53:55 -0600 Subject: [PATCH 05/81] Fixed connection logging and added a few other checks to reduce chances of unnecessary exceptions. Shortened after_ callback time from 1 second to .01 second, may move it to immediate. --- plugin_manager.py | 2 +- plugins/announcer_plugin/announcer_plugin.py | 2 +- .../admin_command_plugin.py | 2 +- plugins/core/player_manager/plugin.py | 29 ++++++------ .../new_player_greeter_plugin.py | 15 +++---- server.py | 44 +++++++------------ 6 files changed, 39 insertions(+), 55 deletions(-) diff --git a/plugin_manager.py b/plugin_manager.py index 4643d3d..d33b017 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -208,7 +208,7 @@ def wrapped_function(self, data): res = self.plugin_manager.do(self, on, data) if res: res = func(self, data) - d = deferLater(reactor, 1, self.plugin_manager.do, self, after, data) + d = deferLater(reactor, .01, self.plugin_manager.do, self, after, data) d.addErrback(print_this_defered_failure) return res diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index b1ce7b3..136e0c8 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -23,7 +23,7 @@ def after_connect_response(self, data): return except: self.logger.exception("Unknown error in after_connect_response.", exc_info=True) - raise + return def on_client_disconnect(self, data): if self.protocol.player is not None: diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index fcf8af8..17399e9 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -152,7 +152,7 @@ def kick(self, data): info = self.player_manager.whois(name) if info and info.logged_in: tp = self.factory.protocols[info.protocol] - tp.die() + tp.loseConnection() self.factory.broadcast("%s kicked %s (reason: %s)" % (self.protocol.player.name, info.name, diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 1512e21..fbc789d 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -84,25 +84,26 @@ def after_connect_response(self, data): self.protocol.transport.getPeer().host)) def after_world_start(self, data): - world_start = packets.world_start().parse(data.data) - coords = world_start.planet['config']['coordinate'] - if coords is not None: - parent_system = coords['parentSystem'] - location = parent_system['location'] - l = location - self.protocol.player.on_ship = False - planet = Planet(parent_system['sector'], l[0], l[1], l[2], - coords['planetaryOrbitNumber'], coords['satelliteOrbitNumber']) - self.protocol.player.planet = str(planet) - self.logger.debug("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) - else: - self.logger.info("Player %s is now on a ship.", self.protocol.player.name) - self.protocol.player.on_ship = True + world_start = packets.world_start().parse(data.data) + coords = world_start.planet['config']['coordinate'] + if coords is not None: + parent_system = coords['parentSystem'] + location = parent_system['location'] + l = location + self.protocol.player.on_ship = False + planet = Planet(parent_system['sector'], l[0], l[1], l[2], + coords['planetaryOrbitNumber'], coords['satelliteOrbitNumber']) + self.protocol.player.planet = str(planet) + self.logger.debug("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) + else: + self.logger.info("Player %s is now on a ship.", self.protocol.player.name) + self.protocol.player.on_ship = True def on_client_disconnect(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: self.logger.info("Player disconnected: %s", self.protocol.player.name) self.protocol.player.logged_in = False + return True @permissions(UserLevels.ADMIN) def delete_player(self, data): diff --git a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py index 6579cb2..2c33183 100644 --- a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py +++ b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py @@ -13,16 +13,13 @@ def activate(self): super(NewPlayerGreeter, self).activate() def after_connect_response(self, data): - try: + if self.protocol.player is not None and self.protocol.player.logged_in: my_storage = self.protocol.player.storage - except AttributeError: - self.logger.debug("Tried to give item to non-existent protocol.") - return - if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": - my_storage['given_starter_items'] = "True" - self.give_items() - self.send_greetings() - self.logger.info("Gave starter items to %s.", self.protocol.player.name) + if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": + my_storage['given_starter_items'] = "True" + self.give_items() + self.send_greetings() + self.logger.info("Gave starter items to %s.", self.protocol.player.name) def give_items(self): for item in self.config.plugin_config["items"]: diff --git a/server.py b/server.py index cb0389e..1b5a518 100644 --- a/server.py +++ b/server.py @@ -96,6 +96,9 @@ def __init__(self): packets.Packets.HEARTBEAT: self.heartbeat, } self.client_protocol = None + self.packet_stream = PacketStream(self) + self.packet_stream.direction = packets.Direction.CLIENT + self.plugin_manager = self.factory.plugin_manager def connectionMade(self): """ @@ -106,9 +109,6 @@ def connectionMade(self): actual starbound server using StarboundClientFactory() :rtype : None """ - self.plugin_manager = self.factory.plugin_manager - self.packet_stream = PacketStream(self) - self.packet_stream.direction = packets.Direction.CLIENT logger.info("Connection established from IP: %s", self.transport.getPeer().host) reactor.connectTCP(self.config.upstream_hostname, self.config.upstream_port, StarboundClientFactory(self), timeout=self.config.server_connect_timeout) @@ -429,33 +429,16 @@ def connectionLost(self, reason=connectionDone): :param reason: The reason for the disconnection. :return: None """ + logger.info("Losing connection from IP: %s", self.transport.getPeer().host) + if self.player is not None: + self.player.logged_in = False + self.client_disconnect("") + if self.client_protocol is not None: + self.client_protocol.disconnect() try: - if self.client_protocol is not None: - x = build_packet(packets.Packets.CLIENT_DISCONNECT, - packets.client_disconnect().build(Container(data=0))) - if self.player is not None and self.player.logged_in: - self.client_disconnect(x) - self.player.protocol = None - self.player = None - self.client_protocol.transport.write(x) - self.client_protocol.transport.abortConnection() + self.factory.protocols.pop(self.id) except: - logger.error("Couldn't disconnect protocol.") - finally: - try: - self.factory.protocols.pop(self.id) - except: - self.logger.trace("Protocol was not in factory list. This should not happen.") - finally: - logger.info("Lost connection from IP: %s", self.transport.getPeer().host) - self.transport.abortConnection() - - def die(self): - self.connectionLost() - - def connectionFailed(self, *args, **kwargs): - self.connectionLost() - + self.logger.trace("Protocol was not in factory list. This should not happen.") class ClientProtocol(Protocol): """ @@ -476,7 +459,6 @@ def connectionMade(self): :return: None """ self.server_protocol.client_protocol = self - self.parsing = False def string_received(self, packet): @@ -517,6 +499,10 @@ def dataReceived(self, data): else: self.packet_stream += data + def disconnect(self): + x = build_packet(packets.Packets.CLIENT_DISCONNECT, packets.client_disconnect().build(Container(data=0))) + self.transport.write(x) + self.transport.abortConnection() class StarryPyServerFactory(ServerFactory): """ From d37fc19853816e935cfb92788a0322e5d466373a Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 21:23:37 -0600 Subject: [PATCH 06/81] Fixed kick command --- .../admin_commands_plugin/admin_command_plugin.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 17399e9..7af8049 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -149,20 +149,24 @@ def kick(self, data): name, reason = extract_name(data) if reason is None: reason = "no reason given" + else: + reason = " ".join(reason) info = self.player_manager.whois(name) if info and info.logged_in: tp = self.factory.protocols[info.protocol] - tp.loseConnection() + tp.transport.loseConnection() self.factory.broadcast("%s kicked %s (reason: %s)" % (self.protocol.player.name, info.name, - " ".join(reason))) - self.logger.info("%s kicked %s (reason: %s", self.protocol.player.name, info.name, - " ".join(reason)) + reason)) + self.logger.info("%s kicked %s (reason: %s)", self.protocol.player.name, info.name, + reason) else: self.protocol.send_chat_message("Couldn't find a user by the name %s." % name) return False + + @permissions(UserLevels.ADMIN) def ban(self, data): """Bans an IP (retrieved by /whois). Syntax: /ban [ip address]""" From 0a809cf351847d774599ba12a1a5d67f3879fa46 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Mon, 3 Feb 2014 23:07:00 -0600 Subject: [PATCH 07/81] Removed all exc_info's for better pypy compatibility, and no longer stop on import error. --- packet_stream.py | 4 +-- packets/data_types.py | 9 +++---- plugin_manager.py | 4 +-- plugins/announcer_plugin/announcer_plugin.py | 2 +- .../admin_command_plugin.py | 2 +- plugins/core/command_plugin/command_plugin.py | 2 +- plugins/core/player_manager/plugin.py | 25 +++++++++++-------- plugins/motd_plugin/motd_plugin.py | 2 +- server.py | 4 +-- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packet_stream.py b/packet_stream.py index 9ee3db4..a460786 100644 --- a/packet_stream.py +++ b/packet_stream.py @@ -56,7 +56,7 @@ def start_packet(self): self.packet_size = self.payload_size + self.header_length return True except: - self.logger.exception("Unknown error in start_packet.", exc_info=True) + self.logger.exception("Unknown error in start_packet.") return False def check_packet(self): @@ -89,7 +89,7 @@ def check_packet(self): if self.start_packet(): self.check_packet() except: - self.logger.exception("Unknown error in check_packet", exc_info=True) + self.logger.exception("Unknown error in check_packet") def reset(self): self.id = None diff --git a/packets/data_types.py b/packets/data_types.py index 755f562..676301d 100644 --- a/packets/data_types.py +++ b/packets/data_types.py @@ -26,7 +26,7 @@ def _build(self, obj, stream, context): value -= 1 VLQ("")._build(value, stream, context) except: - self.logger.exception("Error building SignedVLQ.", exc_info=True) + self.logger.exception("Error building SignedVLQ.") raise @@ -73,11 +73,8 @@ def _decode(self, obj, context): class Joiner(Adapter): def _encode(self, obj, context): return obj - def _decode(self, obj, context): return "".join(obj) - - star_string_struct = lambda name="star_string": Struct(name, VLQ("length"), String("string", lambda ctx: ctx.length) @@ -121,6 +118,6 @@ class StarByteArray(Construct): def _parse(self, stream, context): l = VLQ("").parse_stream(stream) return _read_stream(stream, l) - def _build(self, obj, stream, context): - _write_stream(stream, len(obj), VLQ("").build(len(obj)) + obj) + _write_stream(stream, len(obj), VLQ("").build(len(obj))+obj) + diff --git a/plugin_manager.py b/plugin_manager.py index d33b017..eb278d8 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -93,7 +93,6 @@ def load_plugins(self, plugin_dir): except ImportError: self.logger.critical("Import error for %s", name) - sys.exit() try: dependencies = {x.name: set(x.depends) for x in seen_plugins} classes = {x.name: x for x in seen_plugins} @@ -171,8 +170,7 @@ def do(self, protocol, command, data): res = True return_values.append(res) except: - self.logger.exception("Error in plugin %s with function %s.", str(plugin), command, - exc_info=True) + self.logger.exception("Error in plugin %s with function %s.", str(plugin), command) return all(return_values) def get_by_name(self, name): diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index 136e0c8..f681ee8 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -22,7 +22,7 @@ def after_connect_response(self, data): self.logger.debug("Attribute error in after_connect_response.") return except: - self.logger.exception("Unknown error in after_connect_response.", exc_info=True) + self.logger.exception("Unknown error in after_connect_response.") return def on_client_disconnect(self, data): diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 7af8049..b5db436 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -172,7 +172,7 @@ def ban(self, data): """Bans an IP (retrieved by /whois). Syntax: /ban [ip address]""" try: ip = data[0] - socket.inet_aton(ip) + print socket.inet_aton(ip) self.logger.debug("Banning IP address %s" % ip) self.player_manager.ban(ip) self.protocol.send_chat_message("Banned IP: %s" % ip) diff --git a/plugins/core/command_plugin/command_plugin.py b/plugins/core/command_plugin/command_plugin.py index d5ac54b..4cdc725 100644 --- a/plugins/core/command_plugin/command_plugin.py +++ b/plugins/core/command_plugin/command_plugin.py @@ -24,7 +24,7 @@ def on_chat_sent(self, data): return True return False except: - self.logger.exception("Error in on_chat_sent.", exc_info=True) + self.logger.exception("Error in on_chat_sent.") raise def register(self, f, names): diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index fbc789d..41d3e51 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -72,16 +72,21 @@ def reject_with_reason(self, reason): self.protocol.transport.write(rejection) self.protocol.transport.loseConnection() - def after_connect_response(self, data): - connection_parameters = connect_response().parse(data.data) - if not connection_parameters.success: - self.protocol.transport.loseConnection() - else: - self.protocol.player.client_id = connection_parameters.client_id - self.protocol.player.logged_in = True - self.logger.info("Player %s (UUID: %s, IP: %s) logged in" % ( - self.protocol.player.name, self.protocol.player.uuid, - self.protocol.transport.getPeer().host)) + def on_connect_response(self, data): + try: + connection_parameters = connect_response().parse(data.data) + if not connection_parameters.success: + self.protocol.transport.loseConnection() + else: + self.protocol.player.client_id = connection_parameters.client_id + self.protocol.player.logged_in = True + self.logger.info("Player %s (UUID: %s, IP: %s) logged in" % ( + self.protocol.player.name, self.protocol.player.uuid, + self.protocol.transport.getPeer().host)) + except: + self.logger.exception("Exception in on_connect_response, player info may not have been logged.") + finally: + return True def after_world_start(self, data): world_start = packets.world_start().parse(data.data) diff --git a/plugins/motd_plugin/motd_plugin.py b/plugins/motd_plugin/motd_plugin.py index 94e6382..172b1b9 100644 --- a/plugins/motd_plugin/motd_plugin.py +++ b/plugins/motd_plugin/motd_plugin.py @@ -44,7 +44,7 @@ def set_motd(self, motd): self.logger.info("MOTD changed to: %s", self._motd) self.send_motd() except: - self.logger.exception("Couldn't change message of the day.", exc_info=True) + self.logger.exception("Couldn't change message of the day.") raise diff --git a/server.py b/server.py index 1b5a518..17ba507 100644 --- a/server.py +++ b/server.py @@ -478,7 +478,7 @@ def string_received(self, packet): packet): self.server_protocol.write(packet.original_data) except construct.core.FieldError: - logger.exception("Construct field error in string_received.", exc_info=True) + logger.exception("Construct field error in string_received.") self.server_protocol.write( packet.original_data) @@ -550,7 +550,7 @@ def broadcast(self, text, channel=1, world='', name=''): try: p.send_chat_message(text) except: - logger.exception("Exception in broadcast.", exc_info=True) + logger.exception("Exception in broadcast.") def buildProtocol(self, address): """ From 79c5abf6da6357443649aafa09ac8bb62e7ecb5c Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 12:40:17 +0100 Subject: [PATCH 08/81] lets try this landscape.io out --- .landscape.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .landscape.yaml diff --git a/.landscape.yaml b/.landscape.yaml new file mode 100644 index 0000000..6a8d7d1 --- /dev/null +++ b/.landscape.yaml @@ -0,0 +1,4 @@ +doc-warnings: yes +test-warnings: no +strictness: high +autodetect: yes \ No newline at end of file From 65ed4165e0092e48a3547c2beb62c874dac3b3ae Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 12:59:58 +0100 Subject: [PATCH 09/81] changes hardcoded ## to use chat_prefix --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index b5db436..b0b128e 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -293,9 +293,9 @@ class MuteManager(BasePlugin): def on_chat_sent(self, data): data = chat_sent().parse(data.data) if self.protocol.player.muted and data.message[0] != self.config.command_prefix and data.message[ - :2] != "##": + :2] != self.config.chat_prefix*2: self.protocol.send_chat_message( - "You are currently muted and cannot speak. You are limited to commands and admin chat (prefix your lines with ## for admin chat.") + "You are currently muted and cannot speak. You are limited to commands and admin chat (prefix your lines with %s for admin chat." % (self.config.chat_prefix*2)) return False return True From 4fbbde95f396d29d894136039549b38b7c67233a Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 21:33:34 +0100 Subject: [PATCH 10/81] reworks the UDP proxy to be a plugin --- plugins/UDPForwarder/UDPForwarder.py | 32 ++++++++++++++++++++++++++++ plugins/UDPForwarder/__init__.py | 1 + server.py | 13 +---------- 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 plugins/UDPForwarder/UDPForwarder.py create mode 100644 plugins/UDPForwarder/__init__.py diff --git a/plugins/UDPForwarder/UDPForwarder.py b/plugins/UDPForwarder/UDPForwarder.py new file mode 100644 index 0000000..f766aec --- /dev/null +++ b/plugins/UDPForwarder/UDPForwarder.py @@ -0,0 +1,32 @@ +from twisted.internet import reactor +from twisted.internet.error import CannotListenError +from twisted.internet.protocol import DatagramProtocol + +from base_plugin import BasePlugin + + +class UDPForwader(BasePlugin): + """Forwards UDP datagrams to the gameserver, mostly used for Valve's Steam style statistis queries""" + name = "udp_forwarder" + auto_activate = True + + def activate(self): + + super(UDPForwader, self).activate() + try: + reactor.listenUDP(self.config.bind_port, UDPProxy(self.config.upstream_hostname, self.config.upstream_port)) + self.logger.info("Listening for UDP on port %d" % self.config.bind_port) + except CannotListenError: + self.logger.error( + "Could not listen on UDP port %d. Will continue running, but please note that steam statistics will be unavailable.", + self.factory.config.bind_port + ) + + +class UDPProxy(DatagramProtocol): + def __init__(self,upstream_hostname, upstream_port): + self.upstream_hostname = upstream_hostname + self.upstream_port = upstream_port + + def datagramReceived(self, datagram, addr): + self.transport.write(datagram, (self.upstream_hostname, self.upstream_port)) diff --git a/plugins/UDPForwarder/__init__.py b/plugins/UDPForwarder/__init__.py new file mode 100644 index 0000000..47aadc5 --- /dev/null +++ b/plugins/UDPForwarder/__init__.py @@ -0,0 +1 @@ +from UDPForwarder import UDPForwader diff --git a/server.py b/server.py index 17ba507..11c8f6b 100644 --- a/server.py +++ b/server.py @@ -9,7 +9,7 @@ import construct from twisted.internet import reactor from twisted.internet.error import CannotListenError -from twisted.internet.protocol import ClientFactory, ServerFactory, Protocol, connectionDone, DatagramProtocol +from twisted.internet.protocol import ClientFactory, ServerFactory, Protocol, connectionDone from construct import Container import construct.core from twisted.internet.task import LoopingCall @@ -602,11 +602,6 @@ def buildProtocol(self, address): protocol.server_protocol = self.server_protocol return protocol - -class UDPProxy(DatagramProtocol): - def datagramReceived(self, datagram, addr): - self.transport.write(datagram, (self.config.upstream_hostname, self.config.upstream_port)) - if __name__ == '__main__': logger = logging.getLogger('starrypy') logger.setLevel(9) @@ -658,10 +653,4 @@ def datagramReceived(self, datagram, addr): logger.critical("Cannot listen on TCP port %d. Exiting.", factory.config.bind_port) sys.exit() logger.info("Listening on port %s" % factory.config.bind_port) - try: - reactor.listenUDP(config.bind_port, UDPProxy()) - except CannotListenError: - logger.error( - "Could not listen on UDP port %d. Will continue running, but please note that steam statistics will be unavailable.", - factory.config.bind_port) reactor.run() From 969cc15786e70e8436c5be4182521fef6f39c196 Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 21:58:27 +0100 Subject: [PATCH 11/81] makes use of datetime.utcnow() --- packet_stream.py | 4 ++-- plugins/core/player_manager/manager.py | 2 +- server.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packet_stream.py b/packet_stream.py index a460786..00151bc 100644 --- a/packet_stream.py +++ b/packet_stream.py @@ -29,7 +29,7 @@ def __init__(self, protocol): self.packet_size = None self.protocol = protocol self.direction = None - self.last_received_timestamp = datetime.datetime.now() + self.last_received_timestamp = datetime.datetime.utcnow() def __add__(self, other): self._stream += other @@ -39,7 +39,7 @@ def __add__(self, other): except: pass finally: - self.last_received_timestamp = datetime.datetime.now() + self.last_received_timestamp = datetime.datetime.utcnow() return self def start_packet(self): diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index f3bcc3d..1f288e0 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -186,7 +186,7 @@ def fetch_or_create(self, uuid, name, ip, protocol=None): else: logger.info("Adding new player with name: %s" % name) player = Player(uuid=uuid, name=name, - last_seen=datetime.datetime.now(), + last_seen=datetime.datetime.utcnow(), access_level=int(UserLevels.GUEST), logged_in=False, protocol=protocol, diff --git a/server.py b/server.py index 11c8f6b..4a067fe 100644 --- a/server.py +++ b/server.py @@ -565,7 +565,7 @@ def buildProtocol(self, address): def reap_dead_protocols(self): logger.debug("Reaping dead connections.") count = 0 - start_time = datetime.datetime.now() + start_time = datetime.datetime.utcnow() for protocol in self.protocols.itervalues(): if ( protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: From 3788f5944f90117eb0061703ff4b8619f660256b Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 22:45:23 +0100 Subject: [PATCH 12/81] makes more things configurable - [x] back packet types - [x] protect everything --- config/config.json.default | 12 ++++++++++++ plugins/planet_protect/planet_protect_plugin.py | 15 ++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index eec15b2..c761ac5 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -57,6 +57,18 @@ }, "planet_protect": { "auto_activate": true, + "protect_everything": false, + "bad_packets": [ + "CONNECT_WIRE", + "DISCONNECT_ALL_WIRES", + "OPEN_CONTAINER", + "CLOSE_CONTAINER", + "SWAP_IN_CONTAINER", + "DAMAGE_TILE", + "DAMAGE_TILE_GROUP", + "REQUEST_DROP", + "MODIFY_TILE_LIST" + ], "blacklist": [ "bomb", "bombblockexplosion", diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index ca6b775..7981779 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -15,24 +15,17 @@ class PlanetProtectPlugin(SimpleCommandPlugin): def activate(self): super(PlanetProtectPlugin, self).activate() - bad_packets = [ - "CONNECT_WIRE", - "DISCONNECT_ALL_WIRES", - "OPEN_CONTAINER", - "CLOSE_CONTAINER", - "SWAP_IN_CONTAINER", - "DAMAGE_TILE", - "DAMAGE_TILE_GROUP", - "REQUEST_DROP", - "MODIFY_TILE_LIST"] + bad_packets = self.config.plugin_config.get("bad_packets", []) for n in ["on_" + n.lower() for n in bad_packets]: setattr(self, n, (lambda x: self.planet_check())) self.protected_planets = self.config.plugin_config.get("protected_planets", []) self.blacklist = self.config.plugin_config.get("blacklist", []) self.player_manager = self.plugins.get("player_manager", []) + self.protect_everything = self.config.plugin_config.get("protect_everything", []) def planet_check(self): - if not self.protocol.player.on_ship and self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.REGISTERED: + if self.protect_everything or ( + not self.protocol.player.on_ship and self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.REGISTERED): return False else: return True From 5b270a32349c3a15bca3c7cd6d86e6e51fcc3722 Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 4 Feb 2014 22:49:53 +0100 Subject: [PATCH 13/81] removes Bouncer plugin its functionality is now in planet_protect --- config/config.json.default | 3 --- plugins/bouncer_plugin/__init__.py | 1 - plugins/bouncer_plugin/bouncer_plugin.py | 33 ------------------------ 3 files changed, 37 deletions(-) delete mode 100644 plugins/bouncer_plugin/__init__.py delete mode 100644 plugins/bouncer_plugin/bouncer_plugin.py diff --git a/config/config.json.default b/config/config.json.default index c761ac5..35c11c9 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -25,9 +25,6 @@ "announcer_plugin": { "auto_activate": true }, - "bouncer": { - "auto_activate": false - }, "chat_logger": { "auto_activate": true }, diff --git a/plugins/bouncer_plugin/__init__.py b/plugins/bouncer_plugin/__init__.py deleted file mode 100644 index 516cfab..0000000 --- a/plugins/bouncer_plugin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from bouncer_plugin import BouncerPlugin \ No newline at end of file diff --git a/plugins/bouncer_plugin/bouncer_plugin.py b/plugins/bouncer_plugin/bouncer_plugin.py deleted file mode 100644 index 8bc3b47..0000000 --- a/plugins/bouncer_plugin/bouncer_plugin.py +++ /dev/null @@ -1,33 +0,0 @@ -from base_plugin import BasePlugin -from plugins.core.player_manager import UserLevels - - -class BouncerPlugin(BasePlugin): - """ - Prohibits players with a UserLevel < REGISTRED from destructive actions. - """ - name = "bouncer" - auto_activate = False - - def activate(self): - super(BouncerPlugin, self).activate() - bad_packets = [ - "CONNECT_WIRE", - "DISCONNECT_ALL_WIRES", - "OPEN_CONTAINER", - "CLOSE_CONTAINER", - "SWAP_IN_CONTAINER", - "DAMAGE_TILE", - "DAMAGE_TILE_GROUP", - "REQUEST_DROP", - "ENTITY_INTERACT", - "MODIFY_TILE_LIST" - ] - for n in ["on_" + n.lower() for n in bad_packets]: - setattr(self, n, - (lambda x: False if self.protocol.player.access_level < UserLevels.REGISTERED else True)) - - def after_connect_response(self, data): - if self.protocol.player is not None and self.protocol.player.access_level < UserLevels.REGISTERED: - self.protocol.send_chat_message( - "^#FF0000;This server is protected. You can't build or perform any destructive actions. Speak to an administrator about becoming a registered user.^#F7EB43;") \ No newline at end of file From d29be381a2480477da93c9c4cf8f18d332e73a4f Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Wed, 5 Feb 2014 15:47:37 +0100 Subject: [PATCH 14/81] adds broadcast_planet method --- .../core/admin_commands_plugin/admin_command_plugin.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index b0b128e..17a69fe 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -13,7 +13,7 @@ class UserCommandPlugin(SimpleCommandPlugin): """ name = "user_management_commands" depends = ['command_dispatcher', 'player_manager'] - commands = ["who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", + commands = ["me", "who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", "passthrough", "shutdown"] auto_activate = True @@ -21,6 +21,13 @@ def activate(self): super(UserCommandPlugin, self).activate() self.player_manager = self.plugins['player_manager'].player_manager + @permissions(UserLevels.GUEST) + def me(self, data): + """Preforms a player emote. Syntax: /me """ + emote = " ".join(data) + self.factory.broadcast_planet("^#CCCCCC;%s %s" % (self.protocol.player.name, emote), planet=self.protocol.player.planet) + return False + @permissions(UserLevels.GUEST) def who(self, data): """Returns all current users on the server. Syntax: /who""" From 53cf0d162fe6329864c0d866dddc3b1762eeaa53 Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Wed, 5 Feb 2014 15:48:59 +0100 Subject: [PATCH 15/81] Revert "adds broadcast_planet method" This reverts commit d29be381a2480477da93c9c4cf8f18d332e73a4f. --- .../core/admin_commands_plugin/admin_command_plugin.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 17a69fe..b0b128e 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -13,7 +13,7 @@ class UserCommandPlugin(SimpleCommandPlugin): """ name = "user_management_commands" depends = ['command_dispatcher', 'player_manager'] - commands = ["me", "who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", + commands = ["who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", "passthrough", "shutdown"] auto_activate = True @@ -21,13 +21,6 @@ def activate(self): super(UserCommandPlugin, self).activate() self.player_manager = self.plugins['player_manager'].player_manager - @permissions(UserLevels.GUEST) - def me(self, data): - """Preforms a player emote. Syntax: /me """ - emote = " ".join(data) - self.factory.broadcast_planet("^#CCCCCC;%s %s" % (self.protocol.player.name, emote), planet=self.protocol.player.planet) - return False - @permissions(UserLevels.GUEST) def who(self, data): """Returns all current users on the server. Syntax: /who""" From a2ae07f5666e5c42b20bb7e41e6b5aea45493699 Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Wed, 5 Feb 2014 15:49:07 +0100 Subject: [PATCH 16/81] adds broadcast_planet --- server.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server.py b/server.py index 4a067fe..f918f58 100644 --- a/server.py +++ b/server.py @@ -552,6 +552,24 @@ def broadcast(self, text, channel=1, world='', name=''): except: logger.exception("Exception in broadcast.") + def broadcast_planet(self, text, planet, name=''): + """ + Convenience method to send a broadcasted message to all clients on the + current planet (and ships orbiting it). + + :param text: Message text + :param planet: The planet to send the message to + :param name: The name to prepend before the message, format is , not prepanded when empty + :return: None + """ + for p in self.protocols.itervalues(): + if p.player.planet == planet: + try: + p.send_chat_message(text) + except: + logger.exception("Exception in broadcast.") + + def buildProtocol(self, address): """ Builds the protocol to a given address. From 0ac825d2c92246843cae205e9c5d4cd7eef3370d Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Mon, 10 Feb 2014 12:09:23 +0100 Subject: [PATCH 17/81] adds config param to set address to listen on --- config/config.json.default | 1 + plugins/UDPForwarder/UDPForwarder.py | 2 +- server.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 35c11c9..0e4788b 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -1,5 +1,6 @@ { "bind_port": 21025, + "bind_address": "", "chat_prefix": "@", "colors": { "admin": "^#C443F7;", diff --git a/plugins/UDPForwarder/UDPForwarder.py b/plugins/UDPForwarder/UDPForwarder.py index f766aec..c02579d 100644 --- a/plugins/UDPForwarder/UDPForwarder.py +++ b/plugins/UDPForwarder/UDPForwarder.py @@ -14,7 +14,7 @@ def activate(self): super(UDPForwader, self).activate() try: - reactor.listenUDP(self.config.bind_port, UDPProxy(self.config.upstream_hostname, self.config.upstream_port)) + reactor.listenUDP(self.config.bind_port, UDPProxy(self.config.upstream_hostname, self.config.upstream_port), interface=self.config.bind_address) self.logger.info("Listening for UDP on port %d" % self.config.bind_port) except CannotListenError: self.logger.error( diff --git a/server.py b/server.py index f918f58..3a09ea3 100644 --- a/server.py +++ b/server.py @@ -666,7 +666,7 @@ def buildProtocol(self, address): factory = StarryPyServerFactory() logger.debug("Attempting to listen on TCP port %d", factory.config.bind_port) try: - reactor.listenTCP(factory.config.bind_port, factory) + reactor.listenTCP(factory.config.bind_port, factory, interface=factory.config.bind_address) except CannotListenError: logger.critical("Cannot listen on TCP port %d. Exiting.", factory.config.bind_port) sys.exit() From 3f12b6737e18116cf9b51d2f1d472edb7254eb7c Mon Sep 17 00:00:00 2001 From: slitherrr Date: Mon, 10 Feb 2014 09:01:48 -0800 Subject: [PATCH 18/81] Some SQLAlchemy session cleanup Rebuilt #93 on development, then split out the plugin part. This is the session handling cleanup. The point is to abstract any session management away from the endpoints are using the data, and to initiate a session per transaction rather than leaving one session open for the lifetime of the server (the latter was causing concurrency issues when different factories were accessing the session). --- base_plugin.py | 7 +- plugin_manager.py | 4 +- .../admin_command_plugin.py | 22 +- plugins/core/colored_names/colored_names.py | 1 - plugins/core/player_manager/manager.py | 222 +++++++++++++----- plugins/core/player_manager/plugin.py | 14 +- .../planet_protect/planet_protect_plugin.py | 2 +- server.py | 38 ++- 8 files changed, 209 insertions(+), 101 deletions(-) diff --git a/base_plugin.py b/base_plugin.py index c6f9401..2ca7153 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -3,11 +3,11 @@ class BasePlugin(object): 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) @@ -357,8 +357,7 @@ def activate(self): for alias in alias_list: self.plugins['command_dispatcher'].register(alias, command) - def deactivate(self): super(SimpleCommandPlugin, self).deactivate() for command in self.commands: - self.plugins['command_dispatcher'].unregister(command) \ No newline at end of file + self.plugins['command_dispatcher'].unregister(command) diff --git a/plugin_manager.py b/plugin_manager.py index eb278d8..d55dc74 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -119,7 +119,6 @@ def load_plugins(self, plugin_dir): self.logger.critical(str(e)) self.activate_plugins() - def reload_plugins(self): self.logger.warning("Reloading plugins.") for x in self.plugins: @@ -132,7 +131,6 @@ def reload_plugins(self): self.logger.exception("Couldn't reload plugins!") raise - def activate_plugins(self): for plugin in [self.plugins[x] for x in self.load_order]: if self.config.config['plugin_config'][plugin.name]['auto_activate']: @@ -217,4 +215,4 @@ def print_this_defered_failure(f): class FatalPluginError(Exception): - pass \ No newline at end of file + pass diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index b0b128e..a8dfe03 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -107,41 +107,21 @@ def promote(self, data): def make_guest(self, player): self.logger.trace("Setting %s to GUEST", player.name) player.access_level = UserLevels.GUEST - try: - self.player_manager.session.commit() - except: - self.player_manager.session.rollback() - raise @permissions(UserLevels.MODERATOR) def make_registered(self, player): self.logger.trace("Setting %s to REGISTERED", player.name) player.access_level = UserLevels.REGISTERED - try: - self.player_manager.session.commit() - except: - self.player_manager.session.rollback() - raise @permissions(UserLevels.ADMIN) def make_mod(self, player): player.access_level = UserLevels.MODERATOR self.logger.trace("Setting %s to MODERATOR", player.name) - try: - self.player_manager.session.commit() - except: - self.player_manager.session.rollback() - raise @permissions(UserLevels.OWNER) def make_admin(self, player): self.logger.trace("Setting %s to ADMIN", player.name) player.access_level = UserLevels.ADMIN - try: - self.player_manager.session.commit() - except: - self.player_manager.session.rollback() - raise @permissions(UserLevels.MODERATOR) def kick(self, data): @@ -198,7 +178,7 @@ def unban(self, data): ip = data[0] for ban in self.player_manager.bans: if ban.ip == ip: - self.player_manager.session.delete(ban) + self.player_manager.remove_ban(ban) self.protocol.send_chat_message("Unbanned IP: %s" % ip) break else: diff --git a/plugins/core/colored_names/colored_names.py b/plugins/core/colored_names/colored_names.py index 1ed9721..d9df066 100644 --- a/plugins/core/colored_names/colored_names.py +++ b/plugins/core/colored_names/colored_names.py @@ -27,4 +27,3 @@ def on_chat_received(self, data): self.logger.warning("Received AttributeError in colored_name. %s", str(e)) self.protocol.transport.write(data.original_data) return False - diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 1f288e0..a86c680 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager import datetime from functools import wraps import inspect @@ -6,13 +7,29 @@ from enum import Enum from sqlalchemy.ext.mutable import Mutable -from sqlalchemy.orm import Session, relationship, backref +from sqlalchemy.orm import sessionmaker, relationship, backref from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey, Boolean, func from sqlalchemy.ext.declarative import declarative_base as sqla_declarative_base from twisted.words.ewords import AlreadyLoggedIn from sqlalchemy.types import TypeDecorator, VARCHAR from utility_functions import path + +@contextmanager +def _autoclosing_session(sm): + session = sm() + + try: + yield session + + except: + session.rollback() + raise + + finally: + session.close() + + class JSONEncodedDict(TypeDecorator): impl = VARCHAR @@ -91,6 +108,32 @@ def __delitem__(self, key): MutableDict.associate_with(JSONEncodedDict) +class RecordWithAttachedSession(object): + def __init__(self, record, sessionmaker): + self.__dict__['record'] = record + self.__dict__['sessionmaker'] = sessionmaker + + def __getattr__(self, name): + with _autoclosing_session(self.sessionmaker) as session: + if sessionmaker.object_session(self.record) != session: + session.add(self.record) + session.refresh(self.record) + val = getattr(self.record, name) + + return val + + def __setattr__(self, name, val): + with _autoclosing_session(self.sessionmaker) as session: + if sessionmaker.object_session(self.record) != session: + session.add(self.record) + session.refresh(self.record) + setattr(self.record, name, val) + session.merge(self.record) + session.commit() + + return val + + class Player(Base): __tablename__ = 'players' @@ -160,75 +203,144 @@ class PlayerManager(object): def __init__(self, config): self.config = config self.engine = create_engine('sqlite:///%s' % path.preauthChild(self.config.player_db).path) - self.session = Session(self.engine) Base.metadata.create_all(self.engine) - for player in self.session.query(Player).all(): - player.logged_in = False - player.protocol = None + self.sessionmaker = sessionmaker(bind=self.engine, autoflush=True) + with _autoclosing_session(self.sessionmaker) as session: + for player in session.query(Player).all(): + player.logged_in = False + player.protocol = None + session.commit() + + def _cache_and_return_from_session(self, session, record, collection=False): + to_return = record + + if not isinstance(record, Base): + return to_return + + if collection: + to_return = [] + for r in record: + to_return.append(RecordWithAttachedSession(r, self.sessionmaker)) + else: + to_return = RecordWithAttachedSession(record, self.sessionmaker) + + return to_return def fetch_or_create(self, uuid, name, ip, protocol=None): - if self.session.query(Player).filter_by(uuid=uuid, logged_in=True).first(): - raise AlreadyLoggedIn - if self.check_bans(ip): - raise Banned - while self.whois(name): - logger.info("Got a duplicate player, affixing _ to name") - name += "_" - player = self.session.query(Player).filter_by(uuid=uuid).first() - if player: - if player.name != name: - logger.info("Detected username change.") - player.name = name - if ip not in player.ips: - player.ips.append(IPAddress(ip=ip)) - player.ip = ip - player.protocol = protocol - else: - logger.info("Adding new player with name: %s" % name) - player = Player(uuid=uuid, name=name, - last_seen=datetime.datetime.utcnow(), - access_level=int(UserLevels.GUEST), - logged_in=False, - protocol=protocol, - client_id=-1, - ip=ip, - planet="", - on_ship=True) - player.ips = [IPAddress(ip=ip)] - self.session.add(player) - if uuid == self.config.owner_uuid: - player.access_level = int(UserLevels.OWNER) - try: - self.session.commit() - except: - self.session.rollback() - raise - return player + with _autoclosing_session(self.sessionmaker) as session: + if session.query(Player).filter_by(uuid=uuid, logged_in=True).first(): + raise AlreadyLoggedIn + if self.check_bans(ip): + raise Banned + while self.whois(name): + logger.info("Got a duplicate player, affixing _ to name") + name += "_" + player = session.query(Player).filter_by(uuid=uuid).first() + if player: + if player.name != name: + logger.info("Detected username change.") + player.name = name + if ip not in player.ips: + player.ips.append(IPAddress(ip=ip)) + player.ip = ip + player.protocol = protocol + else: + logger.info("Adding new player with name: %s" % name) + player = Player(uuid=uuid, name=name, + last_seen=datetime.datetime.now(), + access_level=int(UserLevels.GUEST), + logged_in=False, + protocol=protocol, + client_id=-1, + ip=ip, + planet="", + on_ship=True) + player.ips = [IPAddress(ip=ip)] + session.add(player) + if uuid == self.config.owner_uuid: + player.access_level = int(UserLevels.OWNER) + + session.commit() + + return self._cache_and_return_from_session(session, player) + + def delete(self, player_cache): + with _autoclosing_session(self.sessionmaker) as session: + session.delete(player_cache.record) + session.commit() def who(self): - return self.session.query(Player).filter_by(logged_in=True).all() + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter_by(logged_in=True).all(), + collection=True, + ) + + def all(self): + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).all(), + collection=True, + ) + + def all_like(self, regex): + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter(Player.name.like(regex)).all(), + collection=True, + ) def whois(self, name): - return self.session.query(Player).filter(Player.logged_in == True, - func.lower(Player.name) == func.lower(name)).first() + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter( + Player.logged_in is True, + func.lower(Player.name) == func.lower(name), + ).first(), + ) def check_bans(self, ip): - return self.session.query(Ban).filter_by(ip=ip).first() is not None + with _autoclosing_session(self.sessionmaker) as session: + return session.query(Ban).filter_by(ip=ip).first() is not None def ban(self, ip): - self.session.add(Ban(ip=ip)) - try: - self.session.commit() - except: - self.session.rollback() - raise + with _autoclosing_session(self.sessionmaker) as session: + session.add(Ban(ip=ip)) + session.commit() + + @property + def bans(self): + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Ban).all(), + ) + + def delete_ban(self, ban_cache): + with _autoclosing_session(self.sessionmaker) as session: + session.delete(ban_cache.record) + session.commit() def get_by_name(self, name): - return self.session.query(Player).filter(func.lower(Player.name) == func.lower(name)).first() + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter(func.lower(Player.name) == func.lower(name)).first(), + ) def get_logged_in_by_name(self, name): - return self.session.query(Player).filter(Player.logged_in == True, - func.lower(Player.name) == func.lower(name)).first() + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter( + Player.logged_in, + func.lower(Player.name) == func.lower(name), + ).first(), + ) def permissions(level=UserLevels.OWNER): diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 41d3e51..a2b248b 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -26,7 +26,7 @@ def deactivate(self): del self.player_manager def check_logged_in(self): - for player in self.player_manager.session.query(Player).filter_by(logged_in=True).all(): + for player in self.player_manager.who(): if player.protocol not in self.factory.protocols.keys(): player.logged_in = False @@ -45,8 +45,11 @@ def on_client_connect(self, data): name=client_data.name, uuid=str(client_data.uuid), ip=self.protocol.transport.getPeer().host, - protocol=self.protocol.id) + protocol=self.protocol.id, + ) + return True + except AlreadyLoggedIn: self.reject_with_reason( "You're already logged in! If this is not the case, please wait 10 seconds and try again.") @@ -123,17 +126,16 @@ def delete_player(self, data): self.protocol.send_chat_message( "Couldn't find a player named %s. Please check the spelling and try again." % name) return False - self.player_manager.session.delete(player) + self.player_manager.delete(player) self.protocol.send_chat_message("Deleted player with name %s." % name) @permissions(UserLevels.ADMIN) def list_players(self, data): if len(data) == 0: - self.format_player_response(self.player_manager.session.query(Player).all()) + self.format_player_response(self.player_manager.all()) else: rx = re.sub(r"[\*]", "%", " ".join(data)) - self.format_player_response( - self.player_manager.session.query(Player).filter(Player.name.like(rx)).all()) + self.format_player_response(self.player_manager.all_like(rx)) def format_player_response(self, players): if len(players) <= 25: diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 7981779..394c3eb 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -76,4 +76,4 @@ def on_entity_create(self, data): self.logger.trace( "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", self.protocol.player.name, p_type) - return False \ No newline at end of file + return False diff --git a/server.py b/server.py index 3a09ea3..dddc8aa 100644 --- a/server.py +++ b/server.py @@ -27,6 +27,21 @@ logging.Logger.trace = lambda s, m, *a, **k: s._log(TRACE_LVL, m, a, **k) +def port_check(upstream_hostname, upstream_port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex((upstream_hostname, upstream_port)) + + if result != 0: + sock.close() + return False + else: + sock.shutdown(SHUT_RDWR) + sock.close() + + return True + + class StarryPyServerProtocol(Protocol): """ The main protocol class for handling connections from Starbound clients. @@ -115,7 +130,7 @@ def connectionMade(self): def string_received(self, packet): """ - This method is called whenever a completed packet is received from the + This method is called whenever a completed packet is received from the client going to the Starbound server. This is the first and only time where these packets can be modified, stopped, or allowed. @@ -415,6 +430,7 @@ def send_chat_message(self, text, channel=0, world='', name=''): logger.trace("Built chat packet. Data: %s", chat_packet.encode("hex")) self.transport.write(chat_packet) logger.debug("Sent chat message with text: %s", text) + def write(self, data): """ Convenience method to send data to the client. @@ -440,6 +456,7 @@ def connectionLost(self, reason=connectionDone): except: self.logger.trace("Protocol was not in factory list. This should not happen.") + class ClientProtocol(Protocol): """ The protocol class which handles the connection to the Starbound server. @@ -460,7 +477,6 @@ def connectionMade(self): """ self.server_protocol.client_protocol = self - def string_received(self, packet): """ This method is called whenever a completed packet is received from the @@ -482,7 +498,6 @@ def string_received(self, packet): self.server_protocol.write( packet.original_data) - def dataReceived(self, data): """ Called whenever a packet is received. Generally this should not be @@ -504,6 +519,7 @@ def disconnect(self): self.transport.write(x) self.transport.abortConnection() + class StarryPyServerFactory(ServerFactory): """ Factory which creates `StarryPyServerProtocol` instances. @@ -569,7 +585,6 @@ def broadcast_planet(self, text, planet, name=''): except: logger.exception("Exception in broadcast.") - def buildProtocol(self, address): """ Builds the protocol to a given address. @@ -623,12 +638,14 @@ def buildProtocol(self, address): if __name__ == '__main__': logger = logging.getLogger('starrypy') logger.setLevel(9) + if TRACE: trace_logger = logging.FileHandler("trace.log") trace_logger.setLevel("TRACE") trace_logger.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) logger.addHandler(trace_logger) logger.trace("Initialized trace logger.") + fh_d = logging.FileHandler("debug.log") fh_d.setLevel(logging.DEBUG) fh_w = logging.FileHandler("server.log") @@ -647,28 +664,29 @@ def buildProtocol(self, address): fh_d.setFormatter(debugfile_formatter) fh_w.setFormatter(logfile_formatter) sh.setFormatter(console_formatter) + if config.port_check: logger.debug("Port check enabled. Performing port check to %s:%d", config.upstream_hostname, config.upstream_port) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(1) - result = sock.connect_ex((config.upstream_hostname, config.upstream_port)) - if result != 0: + + if port_check(config.upstream_hostname, config.upstream_port): logger.critical("The starbound server is not connectable at the address %s:%d." % ( config.upstream_hostname, config.upstream_port)) logger.critical( "Please ensure that you are running starbound_server on the correct port and that is reflected in the StarryPy configuration.") sys.exit() - sock.shutdown(SHUT_RDWR) - sock.close() + logger.debug("Port check succeeded. Continuing.") + logger.info("Started StarryPy server version %s" % VERSION) factory = StarryPyServerFactory() logger.debug("Attempting to listen on TCP port %d", factory.config.bind_port) + try: reactor.listenTCP(factory.config.bind_port, factory, interface=factory.config.bind_address) except CannotListenError: logger.critical("Cannot listen on TCP port %d. Exiting.", factory.config.bind_port) sys.exit() + logger.info("Listening on port %s" % factory.config.bind_port) reactor.run() From 3e559cbb7e57817640d36718a50f9343babf35f1 Mon Sep 17 00:00:00 2001 From: slitherrr Date: Mon, 10 Feb 2014 17:33:40 -0800 Subject: [PATCH 19/81] Fix for plugin_storage --- plugins/core/player_manager/manager.py | 4 ++-- .../new_player_greeter_plugin/new_player_greeter_plugin.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index a86c680..235f031 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -164,7 +164,7 @@ def colored_name(self, colors): @property def storage(self): - caller = inspect.stack()[1][0].f_locals["self"].__class__.name + caller = inspect.stack()[2][0].f_locals["self"].__class__.name if self.plugin_storage is None: self.plugin_storage = {} try: @@ -175,7 +175,7 @@ def storage(self): @storage.setter def storage(self, store): - caller = inspect.stack()[1][0].f_locals["self"].__class__.name + caller = inspect.stack()[2][0].f_locals["self"].__class__.name self.plugin_storage[caller] = store def as_dict(self): diff --git a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py index 2c33183..a97e6e3 100644 --- a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py +++ b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py @@ -16,9 +16,10 @@ def after_connect_response(self, data): if self.protocol.player is not None and self.protocol.player.logged_in: my_storage = self.protocol.player.storage if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": - my_storage['given_starter_items'] = "True" self.give_items() self.send_greetings() + my_storage['given_starter_items'] = "True" + self.protocol.player.storage = my_storage self.logger.info("Gave starter items to %s.", self.protocol.player.name) def give_items(self): From 7485437b509f910fdd08336d770c8096e3d22c60 Mon Sep 17 00:00:00 2001 From: Marv Cool Date: Tue, 11 Feb 2014 11:21:12 +0100 Subject: [PATCH 20/81] adds "boneswoosh" to blocked projectiles this closes issue #95 --- config/config.json.default | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.json.default b/config/config.json.default index 0e4788b..2a7f3cd 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -70,6 +70,7 @@ "blacklist": [ "bomb", "bombblockexplosion", + "boneswoosh", "bouldersmashexplosion", "bouncycluster", "bouncyclustergrenade", From 7ca524ca54f51cac87e1dccc793ff74adb4e1aec Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Tue, 18 Feb 2014 18:25:59 -0600 Subject: [PATCH 21/81] Update for Enraged Koala. --- packets/packet_types.py | 84 ++++++++++--------- plugins/core/player_manager/plugin.py | 16 ++-- .../planet_protect/planet_protect_plugin.py | 4 +- plugins/warpy_plugin/warpy_plugin.py | 4 +- server.py | 2 + 5 files changed, 58 insertions(+), 52 deletions(-) diff --git a/packets/packet_types.py b/packets/packet_types.py index e574d74..6723602 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -15,47 +15,49 @@ class Packets(IntEnum): HANDSHAKE_CHALLENGE = 3 CHAT_RECEIVED = 4 UNIVERSE_TIME_UPDATE = 5 - CLIENT_CONNECT = 6 - CLIENT_DISCONNECT = 7 - HANDSHAKE_RESPONSE = 8 - WARP_COMMAND = 9 - CHAT_SENT = 10 - CLIENT_CONTEXT_UPDATE = 11 - WORLD_START = 12 - WORLD_STOP = 13 - TILE_ARRAY_UPDATE = 14 - TILE_UPDATE = 15 - TILE_LIQUID_UPDATE = 16 - TILE_DAMAGE_UPDATE = 17 - TILE_MODIFICATION_FAILURE = 18 - GIVE_ITEM = 19 - SWAP_IN_CONTAINER_RESULT = 20 - ENVIRONMENT_UPDATE = 21 - ENTITY_INTERACT_RESULT = 22 - MODIFY_TILE_LIST = 23 - DAMAGE_TILE = 24 - DAMAGE_TILE_GROUP = 25 - REQUEST_DROP = 26 - SPAWN_ENTITY = 27 - ENTITY_INTERACT = 28 - CONNECT_WIRE = 29 - DISCONNECT_ALL_WIRES = 30 - OPEN_CONTAINER = 31 - CLOSE_CONTAINER = 32 - SWAP_IN_CONTAINER = 33 - ITEM_APPLY_IN_CONTAINER = 34 - START_CRAFTING_IN_CONTAINER = 35 - STOP_CRAFTING_IN_CONTAINER = 36 - BURN_CONTAINER = 37 - CLEAR_CONTAINER = 38 - WORLD_UPDATE = 39 - ENTITY_CREATE = 40 - ENTITY_UPDATE = 41 - ENTITY_DESTROY = 42 - DAMAGE_NOTIFICATION = 43 - STATUS_EFFECT_REQUEST = 44 - UPDATE_WORLD_PROPERTIES = 45 - HEARTBEAT = 46 + CELESTIALRESPONSE = 6 + CLIENT_CONNECT = 7 + CLIENT_DISCONNECT = 8 + HANDSHAKE_RESPONSE = 9 + WARP_COMMAND = 10 + CHAT_SENT = 11 + CELESTIALREQUEST = 12 + CLIENT_CONTEXT_UPDATE = 13 + WORLD_START = 14 + WORLD_STOP = 15 + TILE_ARRAY_UPDATE = 16 + TILE_UPDATE = 17 + TILE_LIQUID_UPDATE = 18 + TILE_DAMAGE_UPDATE = 19 + TILE_MODIFICATION_FAILURE = 20 + GIVE_ITEM = 21 + SWAP_IN_CONTAINER_RESULT = 22 + ENVIRONMENT_UPDATE = 23 + ENTITY_INTERACT_RESULT = 24 + MODIFY_TILE_LIST = 25 + DAMAGE_TILE = 26 + DAMAGE_TILE_GROUP = 27 + REQUEST_DROP = 28 + SPAWN_ENTITY = 29 + ENTITY_INTERACT = 30 + CONNECT_WIRE = 31 + DISCONNECT_ALL_WIRES = 32 + OPEN_CONTAINER = 33 + CLOSE_CONTAINER = 34 + SWAP_IN_CONTAINER = 35 + ITEM_APPLY_IN_CONTAINER = 36 + START_CRAFTING_IN_CONTAINER = 37 + STOP_CRAFTING_IN_CONTAINER = 38 + BURN_CONTAINER = 39 + CLEAR_CONTAINER = 40 + WORLD_UPDATE = 41 + ENTITY_CREATE = 42 + ENTITY_UPDATE = 43 + ENTITY_DESTROY = 44 + DAMAGE_NOTIFICATION = 45 + STATUS_EFFECT_REQUEST = 46 + UPDATE_WORLD_PROPERTIES = 47 + HEARTBEAT = 48 class EntityType(IntEnum): diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index a2b248b..be39ca8 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -49,7 +49,6 @@ def on_client_connect(self, data): ) return True - except AlreadyLoggedIn: self.reject_with_reason( "You're already logged in! If this is not the case, please wait 10 seconds and try again.") @@ -93,19 +92,20 @@ def on_connect_response(self, data): def after_world_start(self, data): world_start = packets.world_start().parse(data.data) - coords = world_start.planet['config']['coordinate'] - if coords is not None: - parent_system = coords['parentSystem'] + if 'fuel.max' in world_start['world_properties']: + self.logger.info("Player %s is now on a ship.", self.protocol.player.name) + self.protocol.player.on_ship = True + else: + coords = world_start.planet['celestialParameters']['coordinate'] + parent_system = coords location = parent_system['location'] l = location self.protocol.player.on_ship = False planet = Planet(parent_system['sector'], l[0], l[1], l[2], - coords['planetaryOrbitNumber'], coords['satelliteOrbitNumber']) + coords['planet'], coords['satellite']) self.protocol.player.planet = str(planet) self.logger.debug("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) - else: - self.logger.info("Player %s is now on a ship.", self.protocol.player.name) - self.protocol.player.on_ship = True + def on_client_disconnect(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 394c3eb..5c17ac5 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -69,11 +69,13 @@ def save(self): def on_entity_create(self, data): if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level <= UserLevels.MODERATOR: entities = entity_create.parse(data.data) + print entities for entity in entities.entity: if entity.entity_type == EntityType.PROJECTILE: p_type = star_string("").parse(entity.entity) + print p_type if p_type in self.blacklist: - self.logger.trace( + self.logger.debug( "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", self.protocol.player.name, p_type) return False diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 1149d1f..3441974 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -1,6 +1,6 @@ from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import warp_command_write, Packets +from packets import warp_command_write, Packets, warp_command from utility_functions import build_packet, move_ship_to_coords, extract_name @@ -73,7 +73,7 @@ def warp_player_to_player(self, from_string, to_string): else: warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t='WARP_UP')) - from_protocol.client_protocol.transport.write(warp_packet) + print warp_packet.encode("hex") else: self.protocol.send_chat_message("No player by the name %s found." % to_string) self.protocol.send_chat_message(self.warp.__doc__) diff --git a/server.py b/server.py index dddc8aa..f3182e4 100644 --- a/server.py +++ b/server.py @@ -78,6 +78,8 @@ def __init__(self): packets.Packets.WORLD_STOP: self.world_stop, packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, packets.Packets.TILE_UPDATE: self.tile_update, + packets.Packets.CELESTIALRESPONSE: lambda x: True, + packets.Packets.CELESTIALREQUEST: lambda x: True, packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, From 1437fcf79ff4802160a859c1e34e3c0c7d4db3c7 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Tue, 18 Feb 2014 18:38:17 -0600 Subject: [PATCH 22/81] Fixes for bizarre enum34 behavior when used in pypy. --- .../admin_command_plugin.py | 2 +- plugins/core/player_manager/manager.py | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index a8dfe03..02a83aa 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -86,7 +86,7 @@ def promote(self, data): self.logger.trace("Sending promotion message to promoter.") self.protocol.send_chat_message("%s: %s -> %s" % ( - player.colored_name(self.config.colors), str(UserLevels(old_rank)).split(".")[1], + player.colored_name(self.config.colors), UserLevels(old_rank), rank.upper())) self.logger.trace("Sending promotion message to promoted player.") try: diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 235f031..697b888 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -73,17 +73,25 @@ def as_dict(self): class Banned(Exception): pass +class _UserLevels(object): + ranks = dict( + GUEST = 0, + REGISTERED = 1, + MODERATOR = 10, + ADMIN = 100, + OWNER = 1000) + ranks_reverse = dict(zip(ranks.values(), ranks.keys())) + + def __call__(self, lvl, *args, **kwargs): + return self.ranks_reverse[lvl] + + def __getattr__(self, item): + if item in ['GUEST', 'REGISTERED', 'MODERATOR', 'ADMIN', 'OWNER']: + return super(_UserLevels, self).__getattribute__('ranks')[item] + else: + return super(_UserLevels, self).__getattribute__(item) -class IntEnum(int, Enum): - pass - - -class UserLevels(IntEnum): - GUEST = 0 - REGISTERED = 1 - MODERATOR = 10 - ADMIN = 100 - OWNER = 1000 +UserLevels = _UserLevels() class MutableDict(Mutable, dict): @@ -154,7 +162,7 @@ class Player(Base): def colored_name(self, colors): logger.trace("Building colored name.") - color = colors[str(UserLevels(self.access_level)).split(".")[1].lower()] + color = colors[UserLevels(self.access_level).lower()] logger.trace("Color is %s", color) name = self.name logger.trace("Name is %s", name) From 75f117f08077e8aef4d04f061a103d3010039f87 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Tue, 18 Feb 2014 21:25:12 -0600 Subject: [PATCH 23/81] Removed some debug code I stupidly left in that would spam the logs on a protected planet. --- plugins/planet_protect/planet_protect_plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 5c17ac5..0de7ef7 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -69,11 +69,9 @@ def save(self): def on_entity_create(self, data): if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level <= UserLevels.MODERATOR: entities = entity_create.parse(data.data) - print entities for entity in entities.entity: if entity.entity_type == EntityType.PROJECTILE: p_type = star_string("").parse(entity.entity) - print p_type if p_type in self.blacklist: self.logger.debug( "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", From 44af4b7beba19ecaecf0782e4f2119a182a83c10 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Tue, 18 Feb 2014 23:30:57 -0600 Subject: [PATCH 24/81] Made all chat messaged strings translatable. --- plugins/admin_messenger/admin_messenger.py | 4 +- .../admin_command_plugin.py | 84 ++-- plugins/core/player_manager/manager.py | 2 +- plugins/core/player_manager/plugin.py | 13 +- .../starbound_config_manager.py | 4 +- plugins/motd_plugin/motd_plugin.py | 6 +- .../planet_protect/planet_protect_plugin.py | 16 +- .../plugin_manager_plugin.py | 34 +- plugins/warpy_plugin/warpy_plugin.py | 18 +- res/messages.po | 417 ++++++++++++++++++ server.py | 15 +- 11 files changed, 522 insertions(+), 91 deletions(-) create mode 100644 res/messages.po diff --git a/plugins/admin_messenger/admin_messenger.py b/plugins/admin_messenger/admin_messenger.py index 7444666..189263e 100644 --- a/plugins/admin_messenger/admin_messenger.py +++ b/plugins/admin_messenger/admin_messenger.py @@ -27,7 +27,7 @@ def message_admins(self, message): for protocol in self.factory.protocols.itervalues(): if protocol.player.access_level >= UserLevels.MODERATOR: protocol.send_chat_message( - "Received an admin message from %s: %s." % (self.protocol.player.name, + _("Received an admin message from %s: %s.") % (self.protocol.player.name, message.message[2:])) self.logger.info("Received an admin message from %s. Message: %s", self.protocol.player.name, message.message[2:]) @@ -35,7 +35,7 @@ def message_admins(self, message): @permissions(UserLevels.ADMIN) def broadcast_message(self, message): for protocol in self.factory.protocols.itervalues(): - protocol.send_chat_message("%sSERVER BROADCAST: %s%s" % ( + protocol.send_chat_message(_("%sSERVER BROADCAST: %s%s") % ( self.config.colors["admin"], message.message[3:], self.config.colors["default"])) self.logger.info("Broadcast from %s. Message: %s", self.protocol.player.name, message.message[3:]) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 02a83aa..bb47302 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -23,35 +23,35 @@ def activate(self): @permissions(UserLevels.GUEST) def who(self, data): - """Returns all current users on the server. Syntax: /who""" + __doc__ = _("""Returns all current users on the server. Syntax: /who""") who = [w.colored_name(self.config.colors) for w in self.player_manager.who()] - self.protocol.send_chat_message("%d players online: %s" % (len(who), ", ".join(who))) + self.protocol.send_chat_message("_(%d players online: %s)" % (len(who), ", ".join(who))) return False @permissions(UserLevels.GUEST) def planet(self, data): - """Displays who is on your current planet.""" + __doc__ = _("""Displays who is on your current planet.""") who = [w.colored_name(self.config.colors) for w in self.player_manager.who() if w.planet == self.protocol.player.planet and not w.on_ship] - self.protocol.send_chat_message("%d players on your current planet: %s" % (len(who), ", ".join(who))) + self.protocol.send_chat_message(_("%d players on your current planet: %s") % (len(who), ", ".join(who))) @permissions(UserLevels.ADMIN) def whois(self, data): - """Returns client data about the specified user. Syntax: /whois [user name]""" + __doc__ = _("""Returns client data about the specified user. Syntax: /whois [user name]""") name = " ".join(data) info = self.player_manager.whois(name) if info: self.protocol.send_chat_message( - "Name: %s\nUserlevel: %s\nUUID: %s\nIP address: %s\nCurrent planet: %s""" % ( + _("Name: %s\nUserlevel: %s\nUUID: %s\nIP address: %s\nCurrent planet: %s""") % ( info.colored_name(self.config.colors), UserLevels(info.access_level), info.uuid, info.ip, info.planet)) else: - self.protocol.send_chat_message("Player not found!") + self.protocol.send_chat_message(_("Player not found!")) return False @permissions(UserLevels.MODERATOR) def promote(self, data): - """Promotes/demoates a user to a specific rank. Syntax: /promote [username] [rank] (where rank is either: registered, moderator, admin, or guest))""" + __doc__ = _("""Promotes/demotes a user to a specific rank. Syntax: /promote [username] [rank] (where rank is either: registered, moderator, admin, or guest))""") self.logger.trace("Promote command received with the following data: %s" % ":".join(data)) if len(data) > 0: name = " ".join(data[:-1]) @@ -69,7 +69,7 @@ def promote(self, data): self.logger.trace( "The old rank was greater or equal to the current rank. Sending a message and returning.") self.protocol.send_chat_message( - "You cannot change that user's access level as they are at least at an equal level as you.") + _("You cannot change that user's access level as they are at least at an equal level as you.")) return if rank == "admin": self.make_admin(player) @@ -81,23 +81,23 @@ def promote(self, data): self.make_guest(player) else: self.logger.trace("Non-existent rank. Returning with a help message.") - self.protocol.send_chat_message("No such rank!\n" + self.promote.__doc__) + self.protocol.send_chat_message(_("No such rank!\n") + self.promote.__doc__) return self.logger.trace("Sending promotion message to promoter.") - self.protocol.send_chat_message("%s: %s -> %s" % ( + self.protocol.send_chat_message(_("%s: %s -> %s") % ( player.colored_name(self.config.colors), UserLevels(old_rank), rank.upper())) self.logger.trace("Sending promotion message to promoted player.") try: self.factory.protocols[player.protocol].send_chat_message( - "%s has promoted you to %s" % ( + _("%s has promoted you to %s") % ( self.protocol.player.colored_name(self.config.colors), rank.upper())) except KeyError: self.logger.trace("Promoted player is not logged in.") else: self.logger.trace("Player wasn't found. Sending chat message to player.") - self.protocol.send_chat_message("Player not found!\n" + self.promote.__doc__) + self.protocol.send_chat_message(_("Player not found!\n") + self.promote.__doc__) return else: self.logger.trace("Received blank promotion command. Sending help message.") @@ -125,7 +125,7 @@ def make_admin(self, player): @permissions(UserLevels.MODERATOR) def kick(self, data): - """Kicks a user from the server. Usage: /kick [username] [reason]""" + __doc__ = _("""Kicks a user from the server. Usage: /kick [username] [reason]""") name, reason = extract_name(data) if reason is None: reason = "no reason given" @@ -142,20 +142,20 @@ def kick(self, data): self.logger.info("%s kicked %s (reason: %s)", self.protocol.player.name, info.name, reason) else: - self.protocol.send_chat_message("Couldn't find a user by the name %s." % name) + self.protocol.send_chat_message(_("Couldn't find a user by the name %s.") % name) return False @permissions(UserLevels.ADMIN) def ban(self, data): - """Bans an IP (retrieved by /whois). Syntax: /ban [ip address]""" + __doc__ = _("""Bans an IP (retrieved by /whois). Syntax: /ban [ip address]""") try: ip = data[0] print socket.inet_aton(ip) self.logger.debug("Banning IP address %s" % ip) self.player_manager.ban(ip) - self.protocol.send_chat_message("Banned IP: %s" % ip) + self.protocol.send_chat_message(_("Banned IP: %s") % ip) self.logger.warning("%s banned IP: %s", self.protocol.player.name, ip) return False except socket.error: @@ -168,38 +168,38 @@ def ban_by_name(self, data): @permissions(UserLevels.ADMIN) def bans(self, data): - """Lists the currently banned IPs. Syntax: /bans""" + __doc__ = _("""Lists the currently banned IPs. Syntax: /bans""") self.protocol.send_chat_message("\n".join( - "IP: %s " % self.player_manager.bans)) + _("IP: %s ") % self.player_manager.bans)) @permissions(UserLevels.ADMIN) def unban(self, data): - """Unbans an IP. Syntax: /unban [ip address]""" + __doc__ = _("""Unbans an IP. Syntax: /unban [ip address]""") ip = data[0] for ban in self.player_manager.bans: if ban.ip == ip: self.player_manager.remove_ban(ban) - self.protocol.send_chat_message("Unbanned IP: %s" % ip) + self.protocol.send_chat_message(_("Unbanned IP: %s") % ip) break else: - self.protocol.send_chat_message("Couldn't find IP: %s" % ip) + self.protocol.send_chat_message(_("Couldn't find IP: %s") % ip) return False @permissions(UserLevels.ADMIN) def give_item(self, data): - """Gives an item to a player. Syntax: /give [target player] [item name] [optional: item count]""" + __doc__ = _("""Gives an item to a player. Syntax: /give [target player] [item name] [optional: item count]""") if len(data) >= 2: try: name, item = extract_name(data) except ValueError as e: - self.protocol.send_chat_message("Please check your syntax. %s" % str(e)) + self.protocol.send_chat_message(_("Please check your syntax. %s") % str(e)) return except AttributeError: self.protocol.send_chat_message( - "Please check that the username you are referencing exists. If it has spaces, please surround it by quotes.") + _("Please check that the username you are referencing exists. If it has spaces, please surround it by quotes.")) return except: - self.protocol.send_chat_message("An unknown error occured. %s" % str(e)) + self.protocol.send_chat_message(_("An unknown error occured. %s") % str(e)) target_player = self.player_manager.get_logged_in_by_name(name) target_protocol = self.factory.protocols[target_player.protocol] if target_player is not None: @@ -211,22 +211,22 @@ def give_item(self, data): item_count = 1 give_item_to_player(target_protocol, item_name, item_count) target_protocol.send_chat_message( - "%s has given you: %s (count: %s)" % ( + _("%s has given you: %s (count: %s)") % ( self.protocol.player.name, item_name, item_count)) - self.protocol.send_chat_message("Sent the item(s).") + self.protocol.send_chat_message(_("Sent the item(s).")) self.logger.info("%s gave %s %s (count: %s)", self.protocol.player.name, name, item_name, item_count) else: - self.protocol.send_chat_message("You have to give an item name.") + self.protocol.send_chat_message(_("You have to give an item name.")) else: - self.protocol.send_chat_message("Couldn't find name: %s" % name) + self.protocol.send_chat_message(_("Couldn't find name: %s" % name)) return False else: self.protocol.send_chat_message(self.give_item.__doc__) @permissions(UserLevels.MODERATOR) def mute(self, data): - """Mute a player. Syntax: /mute [player name]""" + __doc__ = _("""Mute a player. Syntax: /mute [player name]""") name = " ".join(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: @@ -234,36 +234,36 @@ def mute(self, data): return target_protocol = self.factory.protocols[player.protocol] player.muted = True - target_protocol.send_chat_message("You have been muted.") - self.protocol.send_chat_message("%s has been muted." % name) + target_protocol.send_chat_message(_("You have been muted.")) + self.protocol.send_chat_message(_("%s has been muted.") % name) @permissions(UserLevels.MODERATOR) def unmute(self, data): - """Unmute a currently muted player. Syntax: /unmute [player name]""" + __doc__ = _("""Unmute a currently muted player. Syntax: /unmute [player name]""") name = " ".join(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: - self.protocol.send_chat_message("Couldn't find a user by the name %s" % name) + self.protocol.send_chat_message(_("Couldn't find a user by the name %s") % name) return target_protocol = self.factory.protocols[player.protocol] player.muted = False - target_protocol.send_chat_message("You have been unmuted.") - self.protocol.send_chat_message("%s has been unmuted." % name) + target_protocol.send_chat_message(_("You have been unmuted.")) + self.protocol.send_chat_message(_("%s has been unmuted.") % name) @permissions(UserLevels.ADMIN) def passthrough(self, data): - """Sets the server to passthrough mode. *This is irreversible without restart.* Syntax: /passthrough""" + __doc__ = _("""Sets the server to passthrough mode. *This is irreversible without restart.* Syntax: /passthrough""") self.config.passthrough = True @permissions(UserLevels.ADMIN) def shutdown(self, data): - """Shutdown the server in n seconds. Syntax: /shutdown [number of seconds] (>0)""" + __doc__ = _("""Shutdown the server in n seconds. Syntax: /shutdown [number of seconds] (>0)""") try: x = float(data[0]) except ValueError: - self.protocol.send_chat_message("%s is not a number. Please enter a value in seconds." % data[0]) + self.protocol.send_chat_message(_("%s is not a number. Please enter a value in seconds.") % data[0]) return - self.factory.broadcast("SERVER ANNOUNCEMENT: Server is shutting down in %s seconds!" % data[0]) + self.factory.broadcast(_("SERVER ANNOUNCEMENT: Server is shutting down in %s seconds!") % data[0]) reactor.callLater(x, reactor.stop) @@ -275,7 +275,7 @@ def on_chat_sent(self, data): if self.protocol.player.muted and data.message[0] != self.config.command_prefix and data.message[ :2] != self.config.chat_prefix*2: self.protocol.send_chat_message( - "You are currently muted and cannot speak. You are limited to commands and admin chat (prefix your lines with %s for admin chat." % (self.config.chat_prefix*2)) + _("You are currently muted and cannot speak. You are limited to commands and admin chat (prefix your lines with %s for admin chat.") % (self.config.chat_prefix*2)) return False return True diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 697b888..762964c 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -362,7 +362,7 @@ def wrapped_function(self, *args, **kwargs): if self.protocol.player.access_level >= level: return f(self, *args, **kwargs) else: - self.protocol.send_chat_message("You are not an admin.") + self.protocol.send_chat_message(_("You are not an admin.")) return False return wrapped_function diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index be39ca8..6b02d1a 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -92,6 +92,7 @@ def on_connect_response(self, data): def after_world_start(self, data): world_start = packets.world_start().parse(data.data) + print world_start if 'fuel.max' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True @@ -118,16 +119,16 @@ def delete_player(self, data): name = " ".join(data) if self.player_manager.get_logged_in_by_name(name) is not None: self.protocol.send_chat_message( - "That player is currently logged in. Refusing to delete logged in character.") + _("That player is currently logged in. Refusing to delete logged in character.")) return False else: player = self.player_manager.get_by_name(name) if player is None: self.protocol.send_chat_message( - "Couldn't find a player named %s. Please check the spelling and try again." % name) + _("Couldn't find a player named %s. Please check the spelling and try again.") % name) return False self.player_manager.delete(player) - self.protocol.send_chat_message("Deleted player with name %s." % name) + self.protocol.send_chat_message(_("Deleted player with name %s.") % name) @permissions(UserLevels.ADMIN) def list_players(self, data): @@ -140,10 +141,10 @@ def list_players(self, data): def format_player_response(self, players): if len(players) <= 25: self.protocol.send_chat_message( - "Results: %s" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players])) + _("Results: %s") % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players])) else: self.protocol.send_chat_message( - "Results: %s" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players[:25]])) + _("Results: %s)" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players[:25]]))) self.protocol.send_chat_message( - "And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately." % ( + _("And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately.") % ( len(players) - 25)) diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index b1e7066..bfb9027 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -34,6 +34,6 @@ def activate(self): @permissions(UserLevels.GUEST) def spawn(self, data): - """Moves your ship to spawn. Syntax: /move_ship_to_spawn""" + __doc__ = _("""Moves your ship to spawn. Syntax: /spawn""") self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message("Moving your ship to spawn.") \ No newline at end of file + self.protocol.send_chat_message(_("Moving your ship to spawn.")) \ No newline at end of file diff --git a/plugins/motd_plugin/motd_plugin.py b/plugins/motd_plugin/motd_plugin.py index 172b1b9..aa7c372 100644 --- a/plugins/motd_plugin/motd_plugin.py +++ b/plugins/motd_plugin/motd_plugin.py @@ -25,11 +25,11 @@ def after_connect_response(self, data): self.send_motd() def send_motd(self): - self.protocol.send_chat_message("Message of the Day:\n%s" % self._motd) + self.protocol.send_chat_message(_("Message of the Day:\n%s") % self._motd) @permissions(UserLevels.GUEST) def motd(self, data): - """Displays the message of the day. Usage: /motd""" + __doc__ = _("""Displays the message of the day. Usage: /motd""") if len(data) == 0: self.send_motd() else: @@ -37,7 +37,7 @@ def motd(self, data): @permissions(UserLevels.MODERATOR) def set_motd(self, motd): - """Sets the message of the day to a new value. Usage: /set_motd [New message of the day]""" + __doc__ = _("""Sets the message of the day to a new value. Usage: /set_motd [New message of the day]""") try: self._motd = " ".join(motd).encode("utf-8") self.config.plugin_config['motd'] = self._motd diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 0de7ef7..583bce4 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -32,34 +32,34 @@ def planet_check(self): @permissions(UserLevels.ADMIN) def protect(self, data): - """Protects the current planet. Only registered users can build on protected planets. Syntax: /protect""" + __doc__ = _("""Protects the current planet. Only registered users can build on protected planets. Syntax: /protect""") planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship if on_ship: - self.protocol.send_chat_message("Can't protect ships (at the moment)") + self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) return if planet not in self.protected_planets: self.protected_planets.append(planet) - self.protocol.send_chat_message("Planet successfully protected.") + self.protocol.send_chat_message(_("Planet successfully protected.")) self.logger.info("Protected planet %s", planet) else: - self.protocol.send_chat_message("Planet is already protected!") + self.protocol.send_chat_message(_("Planet is already protected!")) self.save() @permissions(UserLevels.ADMIN) def unprotect(self, data): - """Removes the protection from the current planet. Syntax: /unprotect""" + __doc__ = _("""Removes the protection from the current planet. Syntax: /unprotect""") planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship if on_ship: - self.protocol.send_chat_message("Can't protect ships (at the moment)") + self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) return if planet in self.protected_planets: self.protected_planets.remove(planet) - self.protocol.send_chat_message("Planet successfully unprotected.") + self.protocol.send_chat_message(_("Planet successfully unprotected.")) self.logger.info("Unprotected planet %s", planet) else: - self.protocol.send_chat_message("Planet is not protected!") + self.protocol.send_chat_message(_("Planet is not protected!")) self.save() def save(self): diff --git a/plugins/plugin_manager_plugin/plugin_manager_plugin.py b/plugins/plugin_manager_plugin/plugin_manager_plugin.py index 6a8a26a..8d2ce1e 100644 --- a/plugins/plugin_manager_plugin/plugin_manager_plugin.py +++ b/plugins/plugin_manager_plugin/plugin_manager_plugin.py @@ -15,62 +15,62 @@ def plugin_manager(self): @permissions(UserLevels.ADMIN) def list_plugins(self, data): - """Lists all currently loaded plugins. Syntax: /list_plugins""" - self.protocol.send_chat_message("Currently loaded plugins: %s" % " ".join( + __doc__ = """Lists all currently loaded plugins. Syntax: /list_plugins""" + self.protocol.send_chat_message(_("Currently loaded plugins: %s") % " ".join( [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if plugin.active])) inactive = [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if not plugin.active] if len(inactive) > 0: - self.protocol.send_chat_message("Inactive plugins: %s" % " ".join( + self.protocol.send_chat_message(_("Inactive plugins: %s") % " ".join( [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if not plugin.active])) @permissions(UserLevels.ADMIN) def disable_plugin(self, data): - """Disables a currently activated plugin. Syntax: /disable_plugin [plugin name]""" + __doc__ = """Disables a currently activated plugin. Syntax: /disable_plugin [plugin name]""" self.logger.debug("disable_plugin called: %s" " ".join(data)) if len(data) == 0: - self.protocol.send_chat_message("You have to specify a plugin.") + self.protocol.send_chat_message(_("You have to specify a plugin.")) return try: plugin = self.plugin_manager.get_by_name(data[0]) except PluginNotFound: - self.protocol.send_chat_message("Couldn't find a plugin with the name %s" % data[0]) + self.protocol.send_chat_message(_("Couldn't find a plugin with the name %s") % data[0]) return if plugin is self: - self.protocol.send_chat_message("Sorry, this plugin can't be deactivated.") + self.protocol.send_chat_message(_("Sorry, this plugin can't be deactivated.")) return if not plugin.active: - self.protocol.send_chat_message("That plugin is already deactivated.") + self.protocol.send_chat_message(_("That plugin is already deactivated.")) return plugin.deactivate() - self.protocol.send_chat_message("Successfully deactivated plugin.") + self.protocol.send_chat_message(_("Successfully deactivated plugin.")) @permissions(UserLevels.ADMIN) def enable_plugin(self, data): - """Enables a currently deactivated plugin. Syntax: /enable_plugin [plugin name]""" + __doc__ = """Enables a currently deactivated plugin. Syntax: /enable_plugin [plugin name]""" self.logger.debug("enable_plugin called: %s", " ".join(data)) if len(data) == 0: - self.protocol.send_chat_message("You have to specify a plugin.") + self.protocol.send_chat_message(_("You have to specify a plugin.")) return try: plugin = self.plugin_manager.get_by_name(data[0]) except PluginNotFound: - self.protocol.send_chat_message("Couldn't find a plugin with the name %s" % data[0]) + self.protocol.send_chat_message(_("Couldn't find a plugin with the name %s") % data[0]) return if plugin.active: - self.protocol.send_chat_message("That plugin is already active.") + self.protocol.send_chat_message(_("That plugin is already active.")) return plugin.activate() - self.protocol.send_chat_message("Successfully activated plugin.") + self.protocol.send_chat_message(_("Successfully activated plugin.")) @permissions(UserLevels.GUEST) def help(self, data): - """Prints help messages for plugin commands. Syntax: /help [command]""" + __doc__ = """Prints help messages for plugin commands. Syntax: /help [command]""" if len(data) > 0: command = data[0].lower() func = self.plugins['command_dispatcher'].commands.get(command, None) if func is None: - self.protocol.send_chat_message("Couldn't find a command with the name %s" % command) + self.protocol.send_chat_message(_("Couldn't find a command with the name %s") % command) self.protocol.send_chat_message("%s%s: %s" % (self.config.command_prefix, command, func.__doc__)) else: available = [] @@ -79,5 +79,5 @@ def help(self, data): available.append(name) available.sort(key=str.lower) self.protocol.send_chat_message( - "Available commands: %s\nAlso try /help command" % ", ".join(available)) + _("Available commands: %s\nAlso try /help command") % ", ".join(available)) return True \ No newline at end of file diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 3441974..272800b 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -19,7 +19,7 @@ def activate(self): @permissions(UserLevels.ADMIN) def warp(self, name): - """Warps you to a player's ship, or a player to another player's ship. Syntax: /warp [player name] OR /warp [player 1] [player 2]""" + __doc__ = _("""Warps you to a player's ship, or a player to another player's ship. Syntax: /warp [player name] OR /warp [player 1] [player 2]""") if len(name) == 0: self.protocol.send_chat_message(self.warp.__doc__) return @@ -40,7 +40,7 @@ def warp(self, name): @permissions(UserLevels.ADMIN) def move_ship(self, location): - """Move your ship to another player or specific coordinates. Syntax: /move_ship [player_name] OR /move_ship [from player] [to player]""" + __doc__ = _("""Move your ship to another player or specific coordinates. Syntax: /move_ship [player_name] OR /move_ship [from player] [to player]""") try: first_name, rest = extract_name(location) if not rest: @@ -51,7 +51,7 @@ def move_ship(self, location): self.protocol.send_chat_message(str(e)) self.protocol.send_chat_message(self.move_ship.__doc__) except AttributeError: - self.protocol.send_chat_message("Couldn't find one or both of the users you specified.") + self.protocol.send_chat_message(_("Couldn't find one or both of the users you specified.")) def warp_self_to_player(self, name): self.logger.debug("Warp command called by %s to %s", self.protocol.player.name, name) @@ -75,18 +75,18 @@ def warp_player_to_player(self, from_string, to_string): warp_command_write(t='WARP_UP')) print warp_packet.encode("hex") else: - self.protocol.send_chat_message("No player by the name %s found." % to_string) + self.protocol.send_chat_message(_("No player by the name %s found.") % to_string) self.protocol.send_chat_message(self.warp.__doc__) return else: - self.protocol.send_chat_message("No player by the name %s found." % from_string) + self.protocol.send_chat_message(_("No player by the name %s found.") % from_string) self.protocol.send_chat_message(self.warp.__doc__) def move_player_ship(self, protocol, location): if len(location) < 5: - self.logger.warning("Couldn't derive a warp location in move_player_ship. Coordinates given: %s", + self.logger.warning(_("Couldn't derive a warp location in move_player_ship. Coordinates given: %s"), ":".join(location)) - self.protocol.send_chat_message("Sorry, an error occurred.") + self.protocol.send_chat_message(_("Sorry, an error occurred.")) return if len(location) == 5: satellite = 0 @@ -105,7 +105,7 @@ def move_own_ship_to_player(self, player_name): raise ValueError if t.planet == u"": self.protocol.send_chat_message( - "Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped down to a planet since logging in?" % t.name) + _("Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped down to a planet since logging in?") % t.name) return self.move_player_ship(self.protocol, t.planet.split(":")) @@ -117,7 +117,7 @@ def move_player_ship_to_other(self, from_player, to_player): raise ValueError if t.planet == u"": self.protocol.send_chat_message( - "Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped to a planet since logging in?" % to_player) + _("Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped to a planet since logging in?") % to_player) return self.move_player_ship(self.factory.protocols[f.protocol], t.planet.split(":")) diff --git a/res/messages.po b/res/messages.po new file mode 100644 index 0000000..4a9792f --- /dev/null +++ b/res/messages.po @@ -0,0 +1,417 @@ +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-02-18 23:27-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: plugins/planet_protect/planet_protect_plugin.py:35 +msgid "" +"Protects the current planet. Only registered users can build on protected " +"planets. Syntax: /protect" +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:39 +#: plugins/planet_protect/planet_protect_plugin.py:55 +msgid "Can't protect ships (at the moment)" +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:43 +msgid "Planet successfully protected." +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:46 +msgid "Planet is already protected!" +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:51 +msgid "Removes the protection from the current planet. Syntax: /unprotect" +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:59 +msgid "Planet successfully unprotected." +msgstr "" + +#: plugins/planet_protect/planet_protect_plugin.py:62 +msgid "Planet is not protected!" +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:22 +msgid "" +"Warps you to a player's ship, or a player to another player's ship. Syntax: /" +"warp [player name] OR /warp [player 1] [player 2]" +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:43 +msgid "" +"Move your ship to another player or specific coordinates. Syntax: /move_ship " +"[player_name] OR /move_ship [from player] [to player]" +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:54 +msgid "Couldn't find one or both of the users you specified." +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:78 +#: plugins/warpy_plugin/warpy_plugin.py:82 +#, python-format +msgid "No player by the name %s found." +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:87 +#, python-format +msgid "" +"Couldn't derive a warp location in move_player_ship. Coordinates given: %s" +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:89 +msgid "Sorry, an error occurred." +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:108 +#, python-format +msgid "" +"Sorry, we don't have a tracked planet location for %s. Perhaps they haven't " +"warped down to a planet since logging in?" +msgstr "" + +#: plugins/warpy_plugin/warpy_plugin.py:120 +#, python-format +msgid "" +"Sorry, we don't have a tracked planet location for %s. Perhaps they haven't " +"warped to a planet since logging in?" +msgstr "" + +#: plugins/admin_messenger/admin_messenger.py:30 +#, python-format +msgid "Received an admin message from %s: %s." +msgstr "" + +#: plugins/admin_messenger/admin_messenger.py:38 +#, python-format +msgid "%sSERVER BROADCAST: %s%s" +msgstr "" + +#: plugins/motd_plugin/motd_plugin.py:28 +#, python-format +msgid "" +"Message of the Day:\n" +"%s" +msgstr "" + +#: plugins/motd_plugin/motd_plugin.py:32 +msgid "Displays the message of the day. Usage: /motd" +msgstr "" + +#: plugins/motd_plugin/motd_plugin.py:40 +msgid "" +"Sets the message of the day to a new value. Usage: /set_motd [New message of " +"the day]" +msgstr "" + +#: plugins/core/starbound_config_manager/starbound_config_manager.py:37 +msgid "Moves your ship to spawn. Syntax: /spawn" +msgstr "" + +#: plugins/core/starbound_config_manager/starbound_config_manager.py:39 +msgid "Moving your ship to spawn." +msgstr "" + +#: plugins/core/player_manager/plugin.py:122 +msgid "" +"That player is currently logged in. Refusing to delete logged in character." +msgstr "" + +#: plugins/core/player_manager/plugin.py:128 +#, python-format +msgid "" +"Couldn't find a player named %s. Please check the spelling and try again." +msgstr "" + +#: plugins/core/player_manager/plugin.py:131 +#, python-format +msgid "Deleted player with name %s." +msgstr "" + +#: plugins/core/player_manager/plugin.py:144 +#, python-format +msgid "Results: %s" +msgstr "" + +#: plugins/core/player_manager/plugin.py:147 +#, python-format +msgid "Results: %s)" +msgstr "" + +#: plugins/core/player_manager/plugin.py:149 +#, python-format +msgid "" +"And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it " +"will be replaced appropriately." +msgstr "" + +#: plugins/core/player_manager/manager.py:365 +msgid "You are not an admin." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:26 +msgid "Returns all current users on the server. Syntax: /who" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:33 +msgid "Displays who is on your current planet." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:36 +#, python-format +msgid "%d players on your current planet: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:40 +msgid "" +"Returns client data about the specified user. Syntax: /whois [user name]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:45 +#, python-format +msgid "" +"Name: %s\n" +"Userlevel: %s\n" +"UUID: %s\n" +"IP address: %s\n" +"Current planet: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:49 +msgid "Player not found!" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:54 +msgid "" +"Promotes/demotes a user to a specific rank. Syntax: /promote [username] " +"[rank] (where rank is either: registered, moderator, admin, or guest))" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:72 +msgid "" +"You cannot change that user's access level as they are at least at an equal " +"level as you." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:84 +msgid "No such rank!\n" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:88 +#, python-format +msgid "%s: %s -> %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:94 +#, python-format +msgid "%s has promoted you to %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:100 +msgid "Player not found!\n" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:128 +msgid "Kicks a user from the server. Usage: /kick [username] [reason]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:145 +#, python-format +msgid "Couldn't find a user by the name %s." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:152 +msgid "Bans an IP (retrieved by /whois). Syntax: /ban [ip address]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:158 +#, python-format +msgid "Banned IP: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:171 +msgid "Lists the currently banned IPs. Syntax: /bans" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:173 +#, python-format +msgid "IP: %s " +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:177 +msgid "Unbans an IP. Syntax: /unban [ip address]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:182 +#, python-format +msgid "Unbanned IP: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:185 +#, python-format +msgid "Couldn't find IP: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:190 +msgid "" +"Gives an item to a player. Syntax: /give [target player] [item name] " +"[optional: item count]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:195 +#, python-format +msgid "Please check your syntax. %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:199 +msgid "" +"Please check that the username you are referencing exists. If it has spaces, " +"please surround it by quotes." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:202 +#, python-format +msgid "An unknown error occured. %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:214 +#, python-format +msgid "%s has given you: %s (count: %s)" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:216 +msgid "Sent the item(s)." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:220 +msgid "You have to give an item name." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:222 +#, python-format +msgid "Couldn't find name: %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:229 +msgid "Mute a player. Syntax: /mute [player name]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:237 +msgid "You have been muted." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:238 +#, python-format +msgid "%s has been muted." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:242 +msgid "Unmute a currently muted player. Syntax: /unmute [player name]" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:246 +#, python-format +msgid "Couldn't find a user by the name %s" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:250 +msgid "You have been unmuted." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:251 +#, python-format +msgid "%s has been unmuted." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:255 +msgid "" +"Sets the server to passthrough mode. *This is irreversible without restart.* " +"Syntax: /passthrough" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:260 +msgid "" +"Shutdown the server in n seconds. Syntax: /shutdown [number of seconds] (>0)" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:264 +#, python-format +msgid "%s is not a number. Please enter a value in seconds." +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:266 +#, python-format +msgid "SERVER ANNOUNCEMENT: Server is shutting down in %s seconds!" +msgstr "" + +#: plugins/core/admin_commands_plugin/admin_command_plugin.py:278 +#, python-format +msgid "" +"You are currently muted and cannot speak. You are limited to commands and " +"admin chat (prefix your lines with %s for admin chat." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:19 +#, python-format +msgid "Currently loaded plugins: %s" +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:23 +#, python-format +msgid "Inactive plugins: %s" +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:31 +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:53 +msgid "You have to specify a plugin." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:36 +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:58 +#, python-format +msgid "Couldn't find a plugin with the name %s" +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:39 +msgid "Sorry, this plugin can't be deactivated." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:42 +msgid "That plugin is already deactivated." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:46 +msgid "Successfully deactivated plugin." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:61 +msgid "That plugin is already active." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:64 +msgid "Successfully activated plugin." +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:73 +#, python-format +msgid "Couldn't find a command with the name %s" +msgstr "" + +#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:82 +#, python-format +msgid "" +"Available commands: %s\n" +"Also try /help command" +msgstr "" diff --git a/server.py b/server.py index f3182e4..fcdc04b 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,7 @@ # -*- coding: UTF-8 -*- from _socket import SHUT_RDWR +import gettext +import locale import logging from uuid import uuid4 import sys @@ -636,11 +638,22 @@ def buildProtocol(self, address): protocol = ClientFactory.buildProtocol(self, address) protocol.server_protocol = self.server_protocol return protocol +def init_localization(): + locale.setlocale(locale.LC_ALL, '') + loc = locale.getlocale() + filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] + try: + print "Opening message file %s for locale %s." % (filename, loc[0]) + trans = gettext.GNUTranslations(open(filename, "rb" )) + except IOError: + print "Locale not found. Using default messages." + trans = gettext.NullTranslations() + trans.install() if __name__ == '__main__': + init_localization() logger = logging.getLogger('starrypy') logger.setLevel(9) - if TRACE: trace_logger = logging.FileHandler("trace.log") trace_logger.setLevel("TRACE") From dc1cba746e4f78d782391ca1701a90993bc3a081 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Tue, 18 Feb 2014 23:39:32 -0600 Subject: [PATCH 25/81] Fixed logging level of planet warp. --- config/config.json.default | 2 +- plugins/core/player_manager/plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 2a7f3cd..f6f0406 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -148,7 +148,7 @@ "auto_activate": true, "name_removal_regexes": [ "\\^#[\\w]+;", - "[^ \\w]+" + "[\\w]+" ] }, "plugin_manager": { diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 6b02d1a..07b632f 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -105,7 +105,7 @@ def after_world_start(self, data): planet = Planet(parent_system['sector'], l[0], l[1], l[2], coords['planet'], coords['satellite']) self.protocol.player.planet = str(planet) - self.logger.debug("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) + self.logger.info("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) def on_client_disconnect(self, player): From f3b9e3686858c7c13d4d2c2dbd5fcb1369bfbcb2 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 00:02:49 -0600 Subject: [PATCH 26/81] Added force command to /protect. --- plugins/planet_protect/planet_protect_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 583bce4..a5939f5 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -35,7 +35,7 @@ def protect(self, data): __doc__ = _("""Protects the current planet. Only registered users can build on protected planets. Syntax: /protect""") planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship - if on_ship: + if on_ship and not ("force" in " ".join(data).lower()): self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) return if planet not in self.protected_planets: @@ -51,7 +51,7 @@ def unprotect(self, data): __doc__ = _("""Removes the protection from the current planet. Syntax: /unprotect""") planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship - if on_ship: + if on_ship and not ("force" in " ".join(data).lower()): self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) return if planet in self.protected_planets: From 4ad1bfaa45315a14b73b6a6c823a48ce468c2c81 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 15:00:31 -0600 Subject: [PATCH 27/81] Fixed issue with names not returning any results. --- plugins/core/player_manager/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 762964c..bfa0bda 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -306,7 +306,7 @@ def whois(self, name): return self._cache_and_return_from_session( session, session.query(Player).filter( - Player.logged_in is True, + Player.logged_in, func.lower(Player.name) == func.lower(name), ).first(), ) From 351c82ed1a944f068a9d1a9432aa239b545925b5 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 15:30:26 -0600 Subject: [PATCH 28/81] Removed extraneous debug prints. --- plugins/core/player_manager/plugin.py | 1 - plugins/planet_protect/planet_protect_plugin.py | 2 ++ plugins/warpy_plugin/warpy_plugin.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 07b632f..cab6804 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -92,7 +92,6 @@ def on_connect_response(self, data): def after_world_start(self, data): world_start = packets.world_start().parse(data.data) - print world_start if 'fuel.max' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index a5939f5..e057ef1 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -22,6 +22,7 @@ def activate(self): self.blacklist = self.config.plugin_config.get("blacklist", []) self.player_manager = self.plugins.get("player_manager", []) self.protect_everything = self.config.plugin_config.get("protect_everything", []) + self.block_all = False def planet_check(self): if self.protect_everything or ( @@ -71,6 +72,7 @@ def on_entity_create(self, data): entities = entity_create.parse(data.data) for entity in entities.entity: if entity.entity_type == EntityType.PROJECTILE: + if self.block_all: return False p_type = star_string("").parse(entity.entity) if p_type in self.blacklist: self.logger.debug( diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 272800b..af7524f 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -70,10 +70,11 @@ def warp_player_to_player(self, from_string, to_string): warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t="WARP_OTHER_SHIP", player=to_player.name.encode('utf-8'))) + else: warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t='WARP_UP')) - print warp_packet.encode("hex") + else: self.protocol.send_chat_message(_("No player by the name %s found.") % to_string) self.protocol.send_chat_message(self.warp.__doc__) From a3aa937a1c0527b732c81d9860695e46661c271b Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 16:02:07 -0600 Subject: [PATCH 29/81] Put bans command back in to list bans. --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index bb47302..c750791 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -14,7 +14,7 @@ class UserCommandPlugin(SimpleCommandPlugin): name = "user_management_commands" depends = ['command_dispatcher', 'player_manager'] commands = ["who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", - "passthrough", "shutdown"] + "passthrough", "shutdown", "bans"] auto_activate = True def activate(self): From bd6d5b464f3fd1d50bb7ac637a0109d2ddb232b4 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 16:06:03 -0600 Subject: [PATCH 30/81] Fixed /warp command. --- plugins/warpy_plugin/warpy_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index af7524f..049ac04 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -74,7 +74,7 @@ def warp_player_to_player(self, from_string, to_string): else: warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t='WARP_UP')) - + from_protocol.client_protocol.transport.write(warp_packet) else: self.protocol.send_chat_message(_("No player by the name %s found.") % to_string) self.protocol.send_chat_message(self.warp.__doc__) From 1e2470371c6b725f6eb4628ec0165abb1a4b8939 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 16:11:48 -0600 Subject: [PATCH 31/81] Fixed formatting of /bans. --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index c750791..8a8916c 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -170,7 +170,7 @@ def ban_by_name(self, data): def bans(self, data): __doc__ = _("""Lists the currently banned IPs. Syntax: /bans""") self.protocol.send_chat_message("\n".join( - _("IP: %s ") % self.player_manager.bans)) + [_("IP: %s ") % x.ip for x in self.player_manager.bans])) @permissions(UserLevels.ADMIN) def unban(self, data): From f13b1ae229b9ce6343662d2ebbf3bfaeed8c461d Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 20:05:53 -0600 Subject: [PATCH 32/81] Disabled default portcheck since it's broken. --- config/config.json.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json.default b/config/config.json.default index f6f0406..0f3042a 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -165,7 +165,7 @@ } }, "plugin_path": "plugins", - "port_check": true, + "port_check": false, "reap_time": 10, "server_connect_timeout": 5, "starbound_path": "/opt/starbound/", From 24e11f178f462c04c27829e18640574134309dca Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Thu, 20 Feb 2014 20:16:05 -0600 Subject: [PATCH 33/81] Fixed portcheck. --- config/config.json.default | 2 +- server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 0f3042a..f6f0406 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -165,7 +165,7 @@ } }, "plugin_path": "plugins", - "port_check": false, + "port_check": true, "reap_time": 10, "server_connect_timeout": 5, "starbound_path": "/opt/starbound/", diff --git a/server.py b/server.py index fcdc04b..28292e9 100644 --- a/server.py +++ b/server.py @@ -684,7 +684,7 @@ def init_localization(): logger.debug("Port check enabled. Performing port check to %s:%d", config.upstream_hostname, config.upstream_port) - if port_check(config.upstream_hostname, config.upstream_port): + if not port_check(config.upstream_hostname, config.upstream_port): logger.critical("The starbound server is not connectable at the address %s:%d." % ( config.upstream_hostname, config.upstream_port)) logger.critical( From d75ac465a359251fe7b670cca503f6a7da0aac72 Mon Sep 17 00:00:00 2001 From: CarrotsAreMediocre Date: Fri, 21 Feb 2014 01:33:34 -0600 Subject: [PATCH 34/81] Fixed permissions on make_guest. --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 8a8916c..7171089 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -103,7 +103,7 @@ def promote(self, data): self.logger.trace("Received blank promotion command. Sending help message.") self.protocol.send_chat_message(self.promote.__doc__) - @permissions(UserLevels.OWNER) + @permissions(UserLevels.MODERATOR) def make_guest(self, player): self.logger.trace("Setting %s to GUEST", player.name) player.access_level = UserLevels.GUEST From 7b3fcc904b88fa87c88700afcb92a106ba5c1745 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 00:03:19 -0500 Subject: [PATCH 35/81] brining the development branch up to speed --- config/config.json.default | 2 +- packet_stream.py | 15 +- packets/packet_types.py | 15 +- plugins/admin_messenger/admin_messenger.py | 27 +- plugins/announcer_plugin/announcer_plugin.py | 4 +- .../admin_command_plugin.py | 249 ++++++++++++------ plugins/core/colored_names/colored_names.py | 9 +- plugins/core/player_manager/manager.py | 70 +++-- plugins/core/player_manager/plugin.py | 165 +++++++++--- .../starbound_config_manager.py | 8 +- plugins/motd_plugin/motd_plugin.py | 18 +- .../planet_protect/planet_protect_plugin.py | 159 ++++++++--- .../plugin_manager_plugin.py | 56 ++-- plugins/warpy_plugin/warpy_plugin.py | 63 +++-- server.py | 78 +++--- 15 files changed, 661 insertions(+), 277 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index f6f0406..abfcfc6 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -1,6 +1,6 @@ { - "bind_port": 21025, "bind_address": "", + "bind_port": 21025, "chat_prefix": "@", "colors": { "admin": "^#C443F7;", diff --git a/packet_stream.py b/packet_stream.py index 00151bc..979704b 100644 --- a/packet_stream.py +++ b/packet_stream.py @@ -29,7 +29,7 @@ def __init__(self, protocol): self.packet_size = None self.protocol = protocol self.direction = None - self.last_received_timestamp = datetime.datetime.utcnow() + self.last_received_timestamp = datetime.datetime.now() def __add__(self, other): self._stream += other @@ -39,7 +39,7 @@ def __add__(self, other): except: pass finally: - self.last_received_timestamp = datetime.datetime.utcnow() + self.last_received_timestamp = datetime.datetime.now() return self def start_packet(self): @@ -55,8 +55,8 @@ def start_packet(self): self.header_length = 1 + len(packets.SignedVLQ("").build(packet_header.payload_size)) self.packet_size = self.payload_size + self.header_length return True - except: - self.logger.exception("Unknown error in start_packet.") + except RuntimeError: + self.logger.error("Unknown error in start_packet.") return False def check_packet(self): @@ -88,11 +88,12 @@ def check_packet(self): self.reset() if self.start_packet(): self.check_packet() - except: - self.logger.exception("Unknown error in check_packet") + except RuntimeError: + self.logger.error("Unknown error in check_packet") + #return False def reset(self): self.id = None self.payload_size = None self.packet_size = None - self.compressed = False \ No newline at end of file + self.compressed = False diff --git a/packets/packet_types.py b/packets/packet_types.py index 6723602..92cb7f6 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -127,7 +127,7 @@ def _decode(self, obj, context): chat_sent = lambda name="chat_sent": Struct(name, star_string("message"), - Padding(1)) + Byte("chat_channel")) client_connect = lambda name="client_connect": Struct(name, VLQ("asset_digest_length"), @@ -222,6 +222,13 @@ def _decode(self, obj, context): Byte("entity_type"), VLQ("entity_size"), String("entity", lambda ctx: ctx.entity_size), - SignedVLQ("entity_id") - ))) -projectile = DictVariant("projectile") \ No newline at end of file + SignedVLQ("entity_id")))) + +client_context_update = lambda name="client_context": Struct(name, + VLQ("length"), + Byte("arguments"), + Array(lambda ctx: ctx.arguments, + Struct("key", + Variant("value")))) + +projectile = DictVariant("projectile") diff --git a/plugins/admin_messenger/admin_messenger.py b/plugins/admin_messenger/admin_messenger.py index 189263e..42b7339 100644 --- a/plugins/admin_messenger/admin_messenger.py +++ b/plugins/admin_messenger/admin_messenger.py @@ -1,10 +1,11 @@ from base_plugin import BasePlugin from plugins.core.player_manager import permissions, UserLevels import packets +from datetime import datetime class AdminMessenger(BasePlugin): - """Adds support to message moderators/admins/owner with a ## prefixed message.""" + """Adds support to message moderators/admins/owner with a @@ prefixed message.""" name = "admin_messenger" depends = ['player_manager'] auto_activate = True @@ -24,18 +25,28 @@ def on_chat_sent(self, data): return True def message_admins(self, message): + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^red;<" + now.strftime("%H:%M") + "> ^yellow;" + else: + timestamp = "" for protocol in self.factory.protocols.itervalues(): if protocol.player.access_level >= UserLevels.MODERATOR: - protocol.send_chat_message( - _("Received an admin message from %s: %s.") % (self.protocol.player.name, - message.message[2:])) + protocol.send_chat_message(timestamp + + "%sADMIN: ^yellow;<%s^yellow;> %s%s" % (self.config.colors["moderator"], self.protocol.player.colored_name(self.config.colors), + self.config.colors["moderator"],message.message[2:].decode("utf-8"))) self.logger.info("Received an admin message from %s. Message: %s", self.protocol.player.name, - message.message[2:]) + message.message[2:].decode("utf-8")) @permissions(UserLevels.ADMIN) def broadcast_message(self, message): + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^red;<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" for protocol in self.factory.protocols.itervalues(): - protocol.send_chat_message(_("%sSERVER BROADCAST: %s%s") % ( - self.config.colors["admin"], message.message[3:], self.config.colors["default"])) + protocol.send_chat_message(timestamp + "%sBROADCAST: ^red;%s%s" % ( + self.config.colors["admin"], message.message[3:].decode("utf-8").upper(), self.config.colors["default"])) self.logger.info("Broadcast from %s. Message: %s", self.protocol.player.name, - message.message[3:]) + message.message[3:].decode("utf-8").upper()) diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index f681ee8..2dd2927 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -17,7 +17,7 @@ def after_connect_response(self, data): c = connect_response().parse(data.data) if c.success: self.factory.broadcast( - self.protocol.player.colored_name(self.config.colors) + " joined.", 0, "", "Announcer") + self.protocol.player.colored_name(self.config.colors) + " logged in.", 0, "", "Announcer") except AttributeError: self.logger.debug("Attribute error in after_connect_response.") return @@ -27,6 +27,6 @@ def after_connect_response(self, data): def on_client_disconnect(self, data): if self.protocol.player is not None: - self.factory.broadcast(self.protocol.player.colored_name(self.config.colors) + " left.", 0, + self.factory.broadcast(self.protocol.player.colored_name(self.config.colors) + " logged out.", 0, "", "Announcer") diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 7171089..d2a9afe 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -13,8 +13,8 @@ class UserCommandPlugin(SimpleCommandPlugin): """ name = "user_management_commands" depends = ['command_dispatcher', 'player_manager'] - commands = ["who", "whois", "promote", "kick", "ban", "give_item", "planet", "mute", "unmute", - "passthrough", "shutdown", "bans"] + commands = ["who", "whoami", "whois", "promote", "kick", "ban", "ban_list", "unban", "item", + "planet", "mute", "unmute", "passthrough", "shutdown", "timestamps"] auto_activate = True def activate(self): @@ -23,35 +23,53 @@ def activate(self): @permissions(UserLevels.GUEST) def who(self, data): - __doc__ = _("""Returns all current users on the server. Syntax: /who""") + """Displays all current players on the server.\nSyntax: /who""" who = [w.colored_name(self.config.colors) for w in self.player_manager.who()] - self.protocol.send_chat_message("_(%d players online: %s)" % (len(who), ", ".join(who))) + self.protocol.send_chat_message("^cyan;%d^green; players online: %s" % (len(who), ", ".join(who))) return False @permissions(UserLevels.GUEST) def planet(self, data): - __doc__ = _("""Displays who is on your current planet.""") + """Displays who is on your current planet.\nSyntax: /planet""" who = [w.colored_name(self.config.colors) for w in self.player_manager.who() if w.planet == self.protocol.player.planet and not w.on_ship] - self.protocol.send_chat_message(_("%d players on your current planet: %s") % (len(who), ", ".join(who))) + self.protocol.send_chat_message("^cyan;%d^green; players on planet: %s" % (len(who), ", ".join(who))) - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.GUEST) + def whoami(self, data): + """Displays client data about yourself.\nSyntax: /whoami""" + info = self.protocol.player + self.protocol.send_chat_message( + "Name: %s ^green;: ^gray;%s\nUserlevel: ^yellow;%s^green; (^gray;%s^green;)\nUUID: ^yellow;%s^green;\nIP address: ^cyan;%s^green;\nCurrent planet: ^yellow;%s^green;" % ( + info.colored_name(self.config.colors), info.org_name, UserLevels(info.access_level), + info.last_seen.strftime("%c"), info.uuid, info.ip, info.planet)) + return False + + @permissions(UserLevels.REGISTERED) def whois(self, data): - __doc__ = _("""Returns client data about the specified user. Syntax: /whois [user name]""") - name = " ".join(data) + """Displays client data about the specified user.\nSyntax: /whois (player)""" + if len(data) == 0: + self.protocol.send_chat_message(self.whois.__doc__) + return + name, garbage = extract_name(data) info = self.player_manager.whois(name) - if info: + if info and self.protocol.player.access_level >= UserLevels.ADMIN: + self.protocol.send_chat_message( + "Name: %s ^green;: ^gray;%s\nUserlevel: ^yellow;%s^green; (^gray;%s^green;)\nUUID: ^yellow;%s^green;\nIP address: ^cyan;%s^green;\nCurrent planet: ^yellow;%s^green;" % ( + info.colored_name(self.config.colors), info.org_name, UserLevels(info.access_level), + info.last_seen.strftime("%c"), info.uuid, info.ip, info.planet)) + elif info: self.protocol.send_chat_message( - _("Name: %s\nUserlevel: %s\nUUID: %s\nIP address: %s\nCurrent planet: %s""") % ( - info.colored_name(self.config.colors), UserLevels(info.access_level), info.uuid, info.ip, - info.planet)) + "Name: %s ^green;: ^gray;%s\nUserlevel: ^yellow;%s^green;\nLast seen: ^gray;%s" % ( + info.colored_name(self.config.colors), info.org_name, UserLevels(info.access_level), + info.last_seen.strftime("%c"))) else: - self.protocol.send_chat_message(_("Player not found!")) + self.protocol.send_chat_message("Player not found!") return False @permissions(UserLevels.MODERATOR) def promote(self, data): - __doc__ = _("""Promotes/demotes a user to a specific rank. Syntax: /promote [username] [rank] (where rank is either: registered, moderator, admin, or guest))""") + """Promotes/demotes a player to a specific rank.\nSyntax: /promote (player) (rank) (where rank is either: guest, registered, moderator, admin, or owner)""" self.logger.trace("Promote command received with the following data: %s" % ":".join(data)) if len(data) > 0: name = " ".join(data[:-1]) @@ -65,13 +83,24 @@ def promote(self, data): for line in pprint.pformat(player).split("\n"): self.logger.trace("\t" + line) old_rank = player.access_level - if old_rank >= self.protocol.player.access_level: + players = self.player_manager.all() + if old_rank == 1000: + owner_count = 0 + for aclvl in players: + if aclvl.access_level == old_rank: + owner_count += 1 + if owner_count <= 1: + self.protocol.send_chat_message("You are the only (or last) owner. Promote denied!") + return + if old_rank >= self.protocol.player.access_level and not self.protocol.player.access_level != UserLevels.ADMIN: self.logger.trace( "The old rank was greater or equal to the current rank. Sending a message and returning.") self.protocol.send_chat_message( - _("You cannot change that user's access level as they are at least at an equal level as you.")) + "You cannot change that user's access level as they are at least at an equal level as you.") return - if rank == "admin": + if rank == "owner": + self.make_owner(player) + elif rank == "admin": self.make_admin(player) elif rank == "moderator": self.make_mod(player) @@ -81,23 +110,23 @@ def promote(self, data): self.make_guest(player) else: self.logger.trace("Non-existent rank. Returning with a help message.") - self.protocol.send_chat_message(_("No such rank!\n") + self.promote.__doc__) + self.protocol.send_chat_message("No such rank!\n" + self.promote.__doc__) return self.logger.trace("Sending promotion message to promoter.") - self.protocol.send_chat_message(_("%s: %s -> %s") % ( + self.protocol.send_chat_message("%s: %s -> %s" % ( player.colored_name(self.config.colors), UserLevels(old_rank), rank.upper())) self.logger.trace("Sending promotion message to promoted player.") try: self.factory.protocols[player.protocol].send_chat_message( - _("%s has promoted you to %s") % ( + "%s has promoted you to %s" % ( self.protocol.player.colored_name(self.config.colors), rank.upper())) except KeyError: - self.logger.trace("Promoted player is not logged in.") + self.logger.info("Promoted player is not logged in.") else: self.logger.trace("Player wasn't found. Sending chat message to player.") - self.protocol.send_chat_message(_("Player not found!\n") + self.promote.__doc__) + self.protocol.send_chat_message("Player not found!\n" + self.promote.__doc__) return else: self.logger.trace("Received blank promotion command. Sending help message.") @@ -123,83 +152,128 @@ def make_admin(self, player): self.logger.trace("Setting %s to ADMIN", player.name) player.access_level = UserLevels.ADMIN + @permissions(UserLevels.OWNER) + def make_owner(self, player): + player.access_level = UserLevels.OWNER + @permissions(UserLevels.MODERATOR) def kick(self, data): - __doc__ = _("""Kicks a user from the server. Usage: /kick [username] [reason]""") + """Kicks a user from the server.\nSyntax: /kick (player) [reason]""" + if len(data) == 0: + self.protocol.send_chat_message(self.kick.__doc__) + return name, reason = extract_name(data) - if reason is None: - reason = "no reason given" + if not reason: + reason = ["no reason given"] else: reason = " ".join(reason) info = self.player_manager.whois(name) if info and info.logged_in: - tp = self.factory.protocols[info.protocol] - tp.transport.loseConnection() - self.factory.broadcast("%s kicked %s (reason: %s)" % - (self.protocol.player.name, - info.name, - reason)) + self.factory.broadcast("%s^green; kicked %s ^green;(reason: ^yellow;%s^green;)" % + (self.protocol.player.colored_name(self.config.colors), + info.colored_name(self.config.colors), + " ".join(reason))) self.logger.info("%s kicked %s (reason: %s)", self.protocol.player.name, info.name, - reason) + " ".join(reason)) + tp = self.factory.protocols[info.protocol] + tp.die() else: - self.protocol.send_chat_message(_("Couldn't find a user by the name %s.") % name) + self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;." % name) return False @permissions(UserLevels.ADMIN) def ban(self, data): - __doc__ = _("""Bans an IP (retrieved by /whois). Syntax: /ban [ip address]""") + """Bans an IP or a Player (by name).\nSyntax: /ban (IP | player)\nTip: Use /whois (player) to get IP""" + if len(data) == 0: + self.protocol.send_chat_message(self.ban.__doc__) + return try: ip = data[0] + socket.inet_aton(ip) print socket.inet_aton(ip) self.logger.debug("Banning IP address %s" % ip) self.player_manager.ban(ip) - self.protocol.send_chat_message(_("Banned IP: %s") % ip) + self.protocol.send_chat_message("Banned IP: ^red;%s^green;" % ip) self.logger.warning("%s banned IP: %s", self.protocol.player.name, ip) return False except socket.error: self.ban_by_name(data) return False - def ban_by_name(self, data): - raise NotImplementedError - - @permissions(UserLevels.ADMIN) - def bans(self, data): - __doc__ = _("""Lists the currently banned IPs. Syntax: /bans""") - self.protocol.send_chat_message("\n".join( - [_("IP: %s ") % x.ip for x in self.player_manager.bans])) + def ban_list(self, data): + """Displays the currently banned IPs.\nSyntax: /ban_list""" + res = self.player_manager.list_bans() + if res: + self.protocol.send_chat_message("Banned list (IPs and Names):") + for banned in res: + try: + socket.inet_aton(banned.ip) + self.protocol.send_chat_message( + "IP: ^red;%s ^green;Reason: ^yellow;%s^green;" % (banned.ip, banned.reason)) + except: + self.protocol.send_chat_message( + "Player: ^red;%s ^green;Reason: ^yellow;%s^green;" % ( + self.player_manager.get_by_org_name(banned.ip).name, banned.reason)) + else: + self.protocol.send_chat_message("No bans found.") @permissions(UserLevels.ADMIN) def unban(self, data): - __doc__ = _("""Unbans an IP. Syntax: /unban [ip address]""") - ip = data[0] - for ban in self.player_manager.bans: - if ban.ip == ip: - self.player_manager.remove_ban(ban) - self.protocol.send_chat_message(_("Unbanned IP: %s") % ip) - break + """Unbans an IP or a Player (by name).\nSyntax: /unban (IP | player)""" + if len(data) == 0: + self.protocol.send_chat_message(self.unban.__doc__) + return + try: + ip = data[0] + socket.inet_aton(ip) + self.player_manager.unban(ip) + self.protocol.send_chat_message("Unbanned IP: ^yellow;%s^green;" % ip) + self.logger.warning("%s unbanned IP: %s", self.protocol.player.name, ip) + return False + except socket.error: + self.unban_by_name(data) + return False + + def ban_by_name(self, data): + name, reason = extract_name(data) + info = self.player_manager.get_by_name(name) + if info: + self.player_manager.ban(info.org_name) + self.protocol.send_chat_message("Banned: %s" % info.colored_name(self.config.colors)) + self.logger.warning("%s banned player: %s", self.protocol.player.org_name, info.org_name) else: - self.protocol.send_chat_message(_("Couldn't find IP: %s") % ip) + self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;." % name) + return False + + def unban_by_name(self, data): + name, reason = extract_name(data) + info = self.player_manager.get_by_name(name) + if info: + self.player_manager.unban(info.org_name) + self.protocol.send_chat_message("Unbanned: %s" % info.colored_name(self.config.colors)) + self.logger.warning("%s unbanned: %s", self.protocol.player.org_name, info.org_name) + else: + self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;." % name) return False @permissions(UserLevels.ADMIN) - def give_item(self, data): - __doc__ = _("""Gives an item to a player. Syntax: /give [target player] [item name] [optional: item count]""") + def item(self, data): + """Gives an item to a player.\nSyntax: /item (player) (item) [count]""" if len(data) >= 2: try: name, item = extract_name(data) except ValueError as e: - self.protocol.send_chat_message(_("Please check your syntax. %s") % str(e)) + self.protocol.send_chat_message("Please check your syntax. %s" % str(e)) return except AttributeError: self.protocol.send_chat_message( - _("Please check that the username you are referencing exists. If it has spaces, please surround it by quotes.")) + "Please check that the username you are referencing exists. If it has spaces, please surround it by quotes.") return except: - self.protocol.send_chat_message(_("An unknown error occured. %s") % str(e)) + self.protocol.send_chat_message("An unknown error occured. %s" % str(e)) target_player = self.player_manager.get_logged_in_by_name(name) target_protocol = self.factory.protocols[target_player.protocol] if target_player is not None: @@ -211,61 +285,77 @@ def give_item(self, data): item_count = 1 give_item_to_player(target_protocol, item_name, item_count) target_protocol.send_chat_message( - _("%s has given you: %s (count: %s)") % ( - self.protocol.player.name, item_name, item_count)) - self.protocol.send_chat_message(_("Sent the item(s).")) + "%s^green; has given you: ^yellow;%s^green; (count: ^cyan;%s^green;)" % ( + self.protocol.player.colored_name(self.config.colors), item_name, item_count)) + self.protocol.send_chat_message("Sent ^yellow;%s^green; (count: ^cyan;%s^green;) to %s" % ( + item_name, item_count, target_player.colored_name(self.config.colors))) self.logger.info("%s gave %s %s (count: %s)", self.protocol.player.name, name, item_name, item_count) else: - self.protocol.send_chat_message(_("You have to give an item name.")) + self.protocol.send_chat_message("You have to give an item name.") else: - self.protocol.send_chat_message(_("Couldn't find name: %s" % name)) + self.protocol.send_chat_message("Couldn't find name: ^yellow;%s^green;" % name) return False else: - self.protocol.send_chat_message(self.give_item.__doc__) + self.protocol.send_chat_message(self.item.__doc__) @permissions(UserLevels.MODERATOR) def mute(self, data): - __doc__ = _("""Mute a player. Syntax: /mute [player name]""") - name = " ".join(data) + """Mute a player.\nSyntax: /mute (player)""" + name, garbage = extract_name(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: - self.protocol.send_chat_message("Couldn't find a user by the name %s" % name) + self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;" % name) return target_protocol = self.factory.protocols[player.protocol] player.muted = True - target_protocol.send_chat_message(_("You have been muted.")) - self.protocol.send_chat_message(_("%s has been muted.") % name) + target_protocol.send_chat_message("You have been ^red;muted^green;.") + self.protocol.send_chat_message( + "%s^green; has been ^red;muted^green;." % target_protocol.player.colored_name(self.config.colors)) @permissions(UserLevels.MODERATOR) def unmute(self, data): - __doc__ = _("""Unmute a currently muted player. Syntax: /unmute [player name]""") - name = " ".join(data) + """Unmute a currently muted player.\nSyntax: /unmute (player)""" + name, garbage = extract_name(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: - self.protocol.send_chat_message(_("Couldn't find a user by the name %s") % name) + self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;" % name) return target_protocol = self.factory.protocols[player.protocol] player.muted = False - target_protocol.send_chat_message(_("You have been unmuted.")) - self.protocol.send_chat_message(_("%s has been unmuted.") % name) + target_protocol.send_chat_message("You have been ^yellow;unmuted^green;.") + self.protocol.send_chat_message( + "%s^green; has been ^yellow;unmuted^green;." % target_protocol.player.colored_name(self.config.colors)) @permissions(UserLevels.ADMIN) def passthrough(self, data): - __doc__ = _("""Sets the server to passthrough mode. *This is irreversible without restart.* Syntax: /passthrough""") + """Sets the server to passthrough mode.\nSyntax: /passthrough\n^red;This is irreversible without stopping the wrapper, changing config.json and restarting!""" self.config.passthrough = True @permissions(UserLevels.ADMIN) def shutdown(self, data): - __doc__ = _("""Shutdown the server in n seconds. Syntax: /shutdown [number of seconds] (>0)""") + """Shutdown the server in n seconds.\nSyntax: /shutdown (seconds) (>0)""" try: x = float(data[0]) except ValueError: - self.protocol.send_chat_message(_("%s is not a number. Please enter a value in seconds.") % data[0]) + self.protocol.send_chat_message( + "^yellow;%s^green; is not a number. Please enter a value in seconds." % data[0]) return - self.factory.broadcast(_("SERVER ANNOUNCEMENT: Server is shutting down in %s seconds!") % data[0]) + self.factory.broadcast( + "SERVER ANNOUNCEMENT: ^red;Server is shutting down in ^yellow;%s^red; seconds!^green;" % data[0]) reactor.callLater(x, reactor.stop) + @permissions(UserLevels.OWNER) + def timestamps(self, data): + """Toggles chat time stamps.\nSyntax: /timestamps""" + if self.config.chattimestamps: + self.config.chattimestamps = False + self.factory.broadcast("Chat timestamps are now ^red;HIDDEN") + + else: + self.config.chattimestamps = True + self.factory.broadcast("Chat timestamps are now ^yellow;SHOWN") + class MuteManager(BasePlugin): name = "mute_manager" @@ -273,9 +363,10 @@ class MuteManager(BasePlugin): def on_chat_sent(self, data): data = chat_sent().parse(data.data) if self.protocol.player.muted and data.message[0] != self.config.command_prefix and data.message[ - :2] != self.config.chat_prefix*2: + :2] != self.config.chat_prefix * 2: self.protocol.send_chat_message( - _("You are currently muted and cannot speak. You are limited to commands and admin chat (prefix your lines with %s for admin chat.") % (self.config.chat_prefix*2)) + "You are currently ^red;muted^green; and cannot speak. You are limited to commands and admin chat (prefix your lines with ^yellow;%s^green; for admin chat." % ( + self.config.chat_prefix * 2)) return False return True diff --git a/plugins/core/colored_names/colored_names.py b/plugins/core/colored_names/colored_names.py index d9df066..294a393 100644 --- a/plugins/core/colored_names/colored_names.py +++ b/plugins/core/colored_names/colored_names.py @@ -1,6 +1,7 @@ from base_plugin import BasePlugin from packets.packet_types import chat_received, Packets from utility_functions import build_packet +from datetime import datetime class ColoredNames(BasePlugin): @@ -16,12 +17,16 @@ def activate(self): self.player_manager = self.plugins['player_manager'].player_manager def on_chat_received(self, data): + now = datetime.now() try: p = chat_received().parse(data.data) if p.name == "server": return - sender = self.player_manager.get_logged_in_by_name(p.name) - p.name = sender.colored_name(self.config.colors) + sender = self.player_manager.get_by_org_name(str(p.name)) + if self.config.chattimestamps: + p.name = now.strftime("%H:%M") + "> <" + sender.colored_name(self.config.colors) + else: + p.name = sender.colored_name(self.config.colors) self.protocol.transport.write(build_packet(Packets.CHAT_RECEIVED, chat_received().build(p))) except AttributeError as e: self.logger.warning("Received AttributeError in colored_name. %s", str(e)) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index bfa0bda..7ed0b53 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -4,6 +4,7 @@ import inspect import logging import json +import sqlite3 from enum import Enum from sqlalchemy.ext.mutable import Mutable @@ -147,6 +148,7 @@ class Player(Base): uuid = Column(String, primary_key=True) name = Column(String) + org_name = Column(String) last_seen = Column(DateTime) access_level = Column(Integer) logged_in = Column(Boolean) @@ -210,11 +212,12 @@ class Ban(Base): class PlayerManager(object): def __init__(self, config): self.config = config + logger.info("Loading player database.") self.engine = create_engine('sqlite:///%s' % path.preauthChild(self.config.player_db).path) Base.metadata.create_all(self.engine) self.sessionmaker = sessionmaker(bind=self.engine, autoflush=True) with _autoclosing_session(self.sessionmaker) as session: - for player in session.query(Player).all(): + for player in session.query(Player).filter_by(logged_in=True).all(): player.logged_in = False player.protocol = None session.commit() @@ -234,27 +237,33 @@ def _cache_and_return_from_session(self, session, record, collection=False): return to_return - def fetch_or_create(self, uuid, name, ip, protocol=None): + def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): with _autoclosing_session(self.sessionmaker) as session: if session.query(Player).filter_by(uuid=uuid, logged_in=True).first(): raise AlreadyLoggedIn if self.check_bans(ip): raise Banned - while self.whois(name): - logger.info("Got a duplicate player, affixing _ to name") + if self.check_bans(org_name): + raise Banned + while (self.get_by_name(name) and not self.get_by_org_name(org_name)) or ( + self.get_by_name(name) and self.get_by_org_name(org_name) and self.get_by_name( + name).uuid != self.get_by_org_name(org_name).uuid): + logger.info("Got a duplicate nickname, affixing _ to name") name += "_" player = session.query(Player).filter_by(uuid=uuid).first() if player: if player.name != name: logger.info("Detected username change.") player.name = name + self.protocol.player.name = name if ip not in player.ips: player.ips.append(IPAddress(ip=ip)) player.ip = ip player.protocol = protocol + player.last_seen = datetime.datetime.now() else: logger.info("Adding new player with name: %s" % name) - player = Player(uuid=uuid, name=name, + player = Player(uuid=uuid, name=name, org_name=org_name, last_seen=datetime.datetime.now(), access_level=int(UserLevels.GUEST), logged_in=False, @@ -283,7 +292,7 @@ def who(self): session, session.query(Player).filter_by(logged_in=True).all(), collection=True, - ) + ) def all(self): with _autoclosing_session(self.sessionmaker) as session: @@ -291,7 +300,7 @@ def all(self): session, session.query(Player).all(), collection=True, - ) + ) def all_like(self, regex): with _autoclosing_session(self.sessionmaker) as session: @@ -299,22 +308,29 @@ def all_like(self, regex): session, session.query(Player).filter(Player.name.like(regex)).all(), collection=True, - ) + ) def whois(self, name): with _autoclosing_session(self.sessionmaker) as session: - return self._cache_and_return_from_session( - session, - session.query(Player).filter( - Player.logged_in, - func.lower(Player.name) == func.lower(name), - ).first(), - ) + return session.query(Player).filter(func.lower(Player.name) == func.lower(name)).first() + + def list_bans(self): + with _autoclosing_session(self.sessionmaker) as session: + return session.query(Ban).all() def check_bans(self, ip): with _autoclosing_session(self.sessionmaker) as session: return session.query(Ban).filter_by(ip=ip).first() is not None + def unban(self, ip): + with _autoclosing_session(self.sessionmaker) as session: + res = session.query(Ban).filter_by(ip=ip).first() + if res == None: + #self.protocol.send_chat_message(self.user_management_commands.unban.__doc__) + return + session.delete(res) + session.commit() + def ban(self, ip): with _autoclosing_session(self.sessionmaker) as session: session.add(Ban(ip=ip)) @@ -326,7 +342,7 @@ def bans(self): return self._cache_and_return_from_session( session, session.query(Ban).all(), - ) + ) def delete_ban(self, ban_cache): with _autoclosing_session(self.sessionmaker) as session: @@ -338,7 +354,21 @@ def get_by_name(self, name): return self._cache_and_return_from_session( session, session.query(Player).filter(func.lower(Player.name) == func.lower(name)).first(), - ) + ) + + def get_by_org_name(self, org_name): + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter(func.lower(Player.org_name) == func.lower(org_name)).first(), + ) + + def get_by_uuid(self, uuid): + with _autoclosing_session(self.sessionmaker) as session: + return self._cache_and_return_from_session( + session, + session.query(Player).filter(func.lower(Player.uuid) == func.lower(uuid)).first(), + ) def get_logged_in_by_name(self, name): with _autoclosing_session(self.sessionmaker) as session: @@ -347,8 +377,8 @@ def get_logged_in_by_name(self, name): session.query(Player).filter( Player.logged_in, func.lower(Player.name) == func.lower(name), - ).first(), - ) + ).first(), + ) def permissions(level=UserLevels.OWNER): @@ -362,7 +392,7 @@ def wrapped_function(self, *args, **kwargs): if self.protocol.player.access_level >= level: return f(self, *args, **kwargs) else: - self.protocol.send_chat_message(_("You are not an admin.")) + self.protocol.send_chat_message("You are not authorized to do this.") return False return wrapped_function diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index cab6804..7122c9b 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -1,3 +1,4 @@ +import base64 import re from construct import Container @@ -6,14 +7,14 @@ from base_plugin import SimpleCommandPlugin from manager import PlayerManager, Banned, Player, permissions, UserLevels -from packets import client_connect, connect_response +from packets import client_connect, connect_response, warp_command import packets -from utility_functions import build_packet, Planet +from utility_functions import extract_name, build_packet, Planet class PlayerManagerPlugin(SimpleCommandPlugin): name = "player_manager" - commands = ["list_players", "delete_player"] + commands = ["player_list", "player_delete", "nick", "nick_set"] def activate(self): super(PlayerManagerPlugin, self).activate() @@ -33,20 +34,40 @@ def check_logged_in(self): def on_client_connect(self, data): client_data = client_connect().parse(data.data) try: - original_name = client_data.name - for regex in self.regexes: - client_data.name = re.sub(regex, "", client_data.name) + changed_name = client_data.name + for regex in self.regexes: # Replace problematic chars in client name + changed_name = re.sub(regex, "", changed_name) + if len(client_data.name.strip()) == 0: # If the username is nothing but spaces. raise NameError("Your name must not be empty!") - if client_data.name != original_name: + + if client_data.name != changed_name: # Logging changed username self.logger.info("Player tried to log in with name %s, replaced with %s.", - original_name, client_data.name) + client_data.name, changed_name) + + changed_player = self.player_manager.get_by_uuid(client_data.uuid) + if changed_player is not None and changed_player.name != changed_name: + self.logger.info("Got player with changed nickname. Fetching nickname!") + changed_name = changed_player.name + + duplicate_player = self.player_manager.get_by_org_name(client_data.name) + if duplicate_player is not None and duplicate_player.uuid != client_data.uuid: + raise NameError( + "The name of this character is already taken on the server!\nPlease, create a new character with a different name or use Starcheat and change the name.") + self.logger.info("Got a duplicate original player name, asking player to change character name!") + #rnd_append = str(randrange(10, 99)) + #original_name += rnd_append + #client_data.name += rnd_append + + original_name = client_data.name + client_data.name = changed_name self.protocol.player = self.player_manager.fetch_or_create( name=client_data.name, + org_name=original_name, uuid=str(client_data.uuid), ip=self.protocol.transport.getPeer().host, protocol=self.protocol.id, - ) + ) return True except AlreadyLoggedIn: @@ -61,6 +82,9 @@ def on_client_connect(self, data): self.reject_with_reason(str(e)) def reject_with_reason(self, reason): + # here there be magic... ask Carrots or Teihoo about this... + magic_sector = "AQAAAAwAAAAy+gofAAX14QD/Z2mAAJiWgAUFYWxwaGEMQWxwaGEgU2VjdG9yAAAAELIfhbMFQWxwaGEHAgt0aHJlYXRMZXZlbAYCBAIEAg51bmxvY2tlZEJpb21lcwYHBQRhcmlkBQZkZXNlcnQFBmZvcmVzdAUEc25vdwUEbW9vbgUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBwcCaWQFBWFscGhhBG5hbWUFDEFscGhhIFNlY3RvcgpzZWN0b3JTZWVkBISWofyWZgxzZWN0b3JTeW1ib2wFFy9jZWxlc3RpYWwvc2VjdG9yLzEucG5nCGh1ZVNoaWZ0BDsGcHJlZml4BQVBbHBoYQ93b3JsZFBhcmFtZXRlcnMHAgt0aHJlYXRMZXZlbAYCBAIEAg51bmxvY2tlZEJpb21lcwYHBQRhcmlkBQZkZXNlcnQFBmZvcmVzdAUEc25vdwUEbW9vbgUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBGJldGELQmV0YSBTZWN0b3IAAADUWh1fvwRCZXRhBwILdGhyZWF0TGV2ZWwGAgQEBAQOdW5sb2NrZWRCaW9tZXMGCQUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAcHAmlkBQRiZXRhBG5hbWUFC0JldGEgU2VjdG9yCnNlY3RvclNlZWQEtYuh6v5+DHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IvMi5wbmcIaHVlU2hpZnQEAAZwcmVmaXgFBEJldGEPd29ybGRQYXJhbWV0ZXJzBwILdGhyZWF0TGV2ZWwGAgQEBAQOdW5sb2NrZWRCaW9tZXMGCQUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAVnYW1tYQxHYW1tYSBTZWN0b3IAAADMTMw79wVHYW1tYQcCC3RocmVhdExldmVsBgIEBgQGDnVubG9ja2VkQmlvbWVzBgoFBGFyaWQFBmRlc2VydAUIc2F2YW5uYWgFBmZvcmVzdAUEc25vdwUEbW9vbgUGanVuZ2xlBQpncmFzc2xhbmRzBQZiYXJyZW4FDWFzdGVyb2lkZmllbGQHBwJpZAUFZ2FtbWEEbmFtZQUMR2FtbWEgU2VjdG9yCnNlY3RvclNlZWQEs4nM4e9uDHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IvMy5wbmcIaHVlU2hpZnQEPAZwcmVmaXgFBUdhbW1hD3dvcmxkUGFyYW1ldGVycwcCC3RocmVhdExldmVsBgIEBgQGDnVubG9ja2VkQmlvbWVzBgoFBGFyaWQFBmRlc2VydAUIc2F2YW5uYWgFBmZvcmVzdAUEc25vdwUEbW9vbgUGanVuZ2xlBQpncmFzc2xhbmRzBQZiYXJyZW4FDWFzdGVyb2lkZmllbGQFZGVsdGEMRGVsdGEgU2VjdG9yAAAA1Ooj2GcFRGVsdGEHAgt0aHJlYXRMZXZlbAYCBAgECA51bmxvY2tlZEJpb21lcwYOBQRhcmlkBQZkZXNlcnQFCHNhdmFubmFoBQZmb3Jlc3QFBHNub3cFBG1vb24FBmp1bmdsZQUKZ3Jhc3NsYW5kcwUFbWFnbWEFCXRlbnRhY2xlcwUGdHVuZHJhBQh2b2xjYW5pYwUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBwcCaWQFBWRlbHRhBG5hbWUFDERlbHRhIFNlY3RvcgpzZWN0b3JTZWVkBLWdop7hTgxzZWN0b3JTeW1ib2wFFy9jZWxlc3RpYWwvc2VjdG9yLzQucG5nCGh1ZVNoaWZ0BHgGcHJlZml4BQVEZWx0YQ93b3JsZFBhcmFtZXRlcnMHAgt0aHJlYXRMZXZlbAYCBAgECA51bmxvY2tlZEJpb21lcwYOBQRhcmlkBQZkZXNlcnQFCHNhdmFubmFoBQZmb3Jlc3QFBHNub3cFBG1vb24FBmp1bmdsZQUKZ3Jhc3NsYW5kcwUFbWFnbWEFCXRlbnRhY2xlcwUGdHVuZHJhBQh2b2xjYW5pYwUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkB3NlY3RvcngIWCBTZWN0b3IAAABjhzJHNwFYBwILdGhyZWF0TGV2ZWwGAgQKBBQOdW5sb2NrZWRCaW9tZXMGDgUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFCmdyYXNzbGFuZHMFBW1hZ21hBQl0ZW50YWNsZXMFBnR1bmRyYQUIdm9sY2FuaWMFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAcIAmlkBQdzZWN0b3J4BG5hbWUFCFggU2VjdG9yCnNlY3RvclNlZWQEmPDzkpxuDHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IveC5wbmcIaHVlU2hpZnQEgTQIcHZwRm9yY2UDAQZwcmVmaXgFAVgPd29ybGRQYXJhbWV0ZXJzBwILdGhyZWF0TGV2ZWwGAgQKBBQOdW5sb2NrZWRCaW9tZXMGDgUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFCmdyYXNzbGFuZHMFBW1hZ21hBQl0ZW50YWNsZXMFBnR1bmRyYQUIdm9sY2FuaWMFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZA==" + unlocked_sector_magic = base64.decodestring(magic_sector.encode("ascii")) rejection = build_packet( packets.Packets.CONNECT_RESPONSE, packets.connect_response().build( @@ -69,7 +93,7 @@ def reject_with_reason(self, reason): client_id=0, reject_reason=reason ) - ) + ) + unlocked_sector_magic ) self.protocol.transport.write(rejection) self.protocol.transport.loseConnection() @@ -91,21 +115,20 @@ def on_connect_response(self, data): return True def after_world_start(self, data): - world_start = packets.world_start().parse(data.data) - if 'fuel.max' in world_start['world_properties']: - self.logger.info("Player %s is now on a ship.", self.protocol.player.name) - self.protocol.player.on_ship = True - else: - coords = world_start.planet['celestialParameters']['coordinate'] - parent_system = coords - location = parent_system['location'] - l = location - self.protocol.player.on_ship = False - planet = Planet(parent_system['sector'], l[0], l[1], l[2], - coords['planet'], coords['satellite']) - self.protocol.player.planet = str(planet) - self.logger.info("Player %s is now at planet: %s", self.protocol.player.name, str(planet)) - + world_start = packets.world_start().parse(data.data) + if 'fuel.max' in world_start['world_properties']: + self.logger.info("Player %s is now on a ship.", self.protocol.player.name) + self.protocol.player.on_ship = True + self.protocol.player.planet = "On ship" + else: + coords = world_start.planet['celestialParameters']['coordinate'] + parent_system = coords + location = parent_system['location'] + l = location + self.protocol.player.on_ship = False + planet = Planet(parent_system['sector'], l[0], l[1], l[2], + coords['planet'], coords['satellite']) + self.protocol.player.planet = str(planet) def on_client_disconnect(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: @@ -113,24 +136,92 @@ def on_client_disconnect(self, player): self.protocol.player.logged_in = False return True + @permissions(UserLevels.REGISTERED) + def nick(self, data): + """Changes your nickname.\nSyntax: /nick (new name)""" + if len(data) == 0: + self.protocol.send_chat_message(self.nick.__doc__) + return + name = " ".join(data) + org_name = self.protocol.player.org_name + for regex in self.regexes: # Replace problematic chars in client name + name = re.sub(regex, "", name) + if self.player_manager.get_by_name(name) or (self.player_manager.get_by_org_name(name) and org_name != name): + self.protocol.send_chat_message("There's already a player by that name.") + else: + old_name = self.protocol.player.colored_name(self.config.colors) + self.protocol.player.name = name + self.factory.broadcast("%s^green;'s name has been changed to %s" % ( + old_name, self.protocol.player.colored_name(self.config.colors))) + @permissions(UserLevels.ADMIN) - def delete_player(self, data): + def nick_set(self, data): + """Changes player nickname.\nSyntax: /nick_set (name) (new name)""" + if len(data) <= 1: + self.protocol.send_chat_message(self.nick_set.__doc__) + return + try: + first_name, rest = extract_name(data) + for regex in self.regexes: # Replace problematic chars in client name + first_name = re.sub(regex, "", first_name) + except ValueError: + self.protocol.send_chat_message("Name not recognized. If it has spaces, please surround it by quotes!") + return + if rest is None or len(rest) == 0: + self.protocol.send_chat_message(self.nick_set.__doc__) + else: + try: + second_name = extract_name(rest)[0] + for regex in self.regexes: # Replace problematic chars in client name + second_name = re.sub(regex, "", second_name) + except ValueError: + self.protocol.send_chat_message( + "New name not recognized. If it has spaces, please surround it by quotes!") + return + player = self.player_manager.get_by_name(str(first_name)) + player2 = self.player_manager.get_by_name(str(second_name)) + org_player = self.player_manager.get_by_org_name(str(first_name)) + org_player2 = self.player_manager.get_by_org_name(str(second_name)) + if player: + first_uuid = player.uuid + elif org_player: + first_uuid = org_player.uuid + if player2: + second_uuid = player2.uuid + elif org_player2: + second_uuid = org_player2.uuid + if player or org_player: + if (player2 or org_player2) and first_uuid != second_uuid: + self.protocol.send_chat_message("There's already a player by that name.") + else: + old_name = player.colored_name(self.config.colors) + player.name = second_name + self.factory.broadcast("%s^green;'s name has been changed to %s" % ( + old_name, player.colored_name(self.config.colors))) + + @permissions(UserLevels.ADMIN) + def player_delete(self, data): + """Delete a player from database.\nSyntax: /player_del (player)""" + if len(data) == 0: + self.protocol.send_chat_message(self.player_del.__doc__) + return name = " ".join(data) if self.player_manager.get_logged_in_by_name(name) is not None: self.protocol.send_chat_message( - _("That player is currently logged in. Refusing to delete logged in character.")) + "That player is currently logged in. Refusing to delete logged in character.") return False else: player = self.player_manager.get_by_name(name) if player is None: self.protocol.send_chat_message( - _("Couldn't find a player named %s. Please check the spelling and try again.") % name) + "Couldn't find a player named %s. Please check the spelling and try again." % name) return False self.player_manager.delete(player) - self.protocol.send_chat_message(_("Deleted player with name %s.") % name) + self.protocol.send_chat_message("Deleted player with name %s." % name) @permissions(UserLevels.ADMIN) - def list_players(self, data): + def player_list(self, data): + """List registered players on the server.\nSyntax: /player_list [search term]""" if len(data) == 0: self.format_player_response(self.player_manager.all()) else: @@ -140,10 +231,18 @@ def list_players(self, data): def format_player_response(self, players): if len(players) <= 25: self.protocol.send_chat_message( - _("Results: %s") % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players])) + #_("Results: %s") % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players])) + "Results:\n%s" % "\n".join( + ["^cyan;%s: ^yellow;%s ^green;: ^gray;%s" % ( + player.uuid, player.colored_name(self.config.colors), player.org_name) for player in + players])) else: self.protocol.send_chat_message( - _("Results: %s)" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players[:25]]))) + #_("Results: %s)" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players[:25]]))) + "Results:\n%s" % "\n".join( + ["^cyan;%s: ^yellow;%s ^green;: ^gray;%s" % ( + player.uuid, player.colored_name(self.config.colors), player.org_name) for player in + players[:25]])) self.protocol.send_chat_message( - _("And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately.") % ( + "And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately." % ( len(players) - 25)) diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index bfb9027..9cfb4eb 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -34,6 +34,10 @@ def activate(self): @permissions(UserLevels.GUEST) def spawn(self, data): - __doc__ = _("""Moves your ship to spawn. Syntax: /spawn""") + """Warps your ship to spawn.\nSyntax: /spawn""" + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message(_("Moving your ship to spawn.")) \ No newline at end of file + self.protocol.send_chat_message("Moving your ship to spawn.") diff --git a/plugins/motd_plugin/motd_plugin.py b/plugins/motd_plugin/motd_plugin.py index aa7c372..eab6d91 100644 --- a/plugins/motd_plugin/motd_plugin.py +++ b/plugins/motd_plugin/motd_plugin.py @@ -9,7 +9,7 @@ class MOTDPlugin(SimpleCommandPlugin): successful connection. """ name = "motd_plugin" - commands = ["motd", "set_motd"] + commands = ["motd", "motd_set"] auto_activate = True def activate(self): @@ -25,19 +25,19 @@ def after_connect_response(self, data): self.send_motd() def send_motd(self): - self.protocol.send_chat_message(_("Message of the Day:\n%s") % self._motd) + #self.protocol.send_chat_message("^yellow;%s" % self._motd) + if not self.config.server_name: + self.config.server_name = "MY" + self.protocol.send_chat_message("%s" % (self._motd)) @permissions(UserLevels.GUEST) def motd(self, data): - __doc__ = _("""Displays the message of the day. Usage: /motd""") - if len(data) == 0: - self.send_motd() - else: - self.set_motd(data) + """Displays the message of the day.\nSyntax: /motd""" + self.send_motd() @permissions(UserLevels.MODERATOR) - def set_motd(self, motd): - __doc__ = _("""Sets the message of the day to a new value. Usage: /set_motd [New message of the day]""") + def motd_set(self, motd): + """Sets the message of the day to a new value.\nSyntax: /motd_set (new message of the day)""" try: self._motd = " ".join(motd).encode("utf-8") self.config.plugin_config['motd'] = self._motd diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index e057ef1..1d4548f 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -1,6 +1,8 @@ +import re from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import UserLevels, permissions from packets import entity_create, EntityType, star_string +from utility_functions import extract_name class PlanetProtectPlugin(SimpleCommandPlugin): @@ -10,7 +12,7 @@ class PlanetProtectPlugin(SimpleCommandPlugin): """ name = "planet_protect" description = "Protects planets." - commands = ["protect", "unprotect"] + commands = ["protect", "unprotect", "protect_list", "protect_all"] depends = ["player_manager", "command_dispatcher"] def activate(self): @@ -19,63 +21,160 @@ def activate(self): for n in ["on_" + n.lower() for n in bad_packets]: setattr(self, n, (lambda x: self.planet_check())) self.protected_planets = self.config.plugin_config.get("protected_planets", []) + self.player_planets = self.config.plugin_config.get("player_planets", {}) self.blacklist = self.config.plugin_config.get("blacklist", []) - self.player_manager = self.plugins.get("player_manager", []) + self.player_manager = self.plugins["player_manager"].player_manager self.protect_everything = self.config.plugin_config.get("protect_everything", []) + self.regexes = self.plugins['player_manager'].regexes self.block_all = False def planet_check(self): - if self.protect_everything or ( - not self.protocol.player.on_ship and self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.REGISTERED): + if self.protocol.player.on_ship: + return True + elif self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.ADMIN: + name = self.protocol.player.org_name + if name in self.player_planets[self.protocol.player.planet]: + return True + else: + return False + elif self.protect_everything and self.protocol.player.access_level < UserLevels.REGISTERED: return False else: return True - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def protect(self, data): - __doc__ = _("""Protects the current planet. Only registered users can build on protected planets. Syntax: /protect""") + """Protects the current planet. Only administrators and allowed players can build on protected planets.\nSyntax: /protect [player]""" planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship - if on_ship and not ("force" in " ".join(data).lower()): - self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) + if len(data) == 0: + addplayer = self.protocol.player.org_name + first_name_color = self.protocol.player.colored_name(self.config.colors) + else: + self.logger.info("stream: %s" % data) + addplayer = data[0] + try: + addplayer, rest = extract_name(data) + self.logger.info("name: %s" % str(addplayer)) + addplayer = self.player_manager.get_by_name(addplayer).org_name + first_name_color = self.player_manager.get_by_org_name(addplayer).colored_name(self.config.colors) + except: + self.protocol.send_chat_message("There's no player named: ^yellow;%s" % str(addplayer)) + return + + first_name = str(addplayer) + + try: + if first_name in self.player_planets[self.protocol.player.planet]: + self.protocol.send_chat_message( + "Player ^yellow;%s^green; is already in planet protect list." % first_name_color) + return + except: + pass + + planet = self.protocol.player.planet # reset planet back to current planet + if on_ship: + self.protocol.send_chat_message("Can't protect ships (at the moment)") return if planet not in self.protected_planets: self.protected_planets.append(planet) - self.protocol.send_chat_message(_("Planet successfully protected.")) + self.protocol.send_chat_message("Planet successfully protected.") self.logger.info("Protected planet %s", planet) + if len(first_name) > 0: + if planet not in self.player_planets: + self.player_planets[planet] = [first_name] + else: + self.player_planets[planet] = self.player_planets[planet] + [first_name] + self.protocol.send_chat_message("Adding ^yellow;%s^green; to planet list" % first_name_color) + else: + if len(first_name) == 0: + self.protocol.send_chat_message("Planet is already protected!") + else: + if planet not in self.player_planets: + self.player_planets[planet] = [first_name] + else: + self.player_planets[planet] = self.player_planets[planet] + [first_name] + self.protocol.send_chat_message("Adding ^yellow;%s^green; to planet list" % first_name_color) + self.save() + + @permissions(UserLevels.OWNER) + def protect_all(self, data): + """Toggles planetary protection (from guests).\nSyntax: /protect_all""" + if self.protect_everything: + self.protect_everything = False + self.factory.broadcast("Planetary protection is now ^yellow;DISABLED") else: - self.protocol.send_chat_message(_("Planet is already protected!")) + self.protect_everything = True + self.factory.broadcast("Planetary protection is now ^red;ENABLED") self.save() - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) + def protect_list(self, data): + """Displays players registered to the protected planet.\nSyntax: /protect_list""" + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if on_ship: + self.protocol.send_chat_message("Can't protect ships (at the moment)") + return + if planet in self.player_planets: + self.protocol.send_chat_message("Players registered to this planet: ^yellow;" + '^green;, ^yellow;'.join( + self.player_planets[planet]).replace('[', '').replace(']', '')) # .replace("'", '') + else: + self.protocol.send_chat_message("Planet is not protected!") + + @permissions(UserLevels.MODERATOR) def unprotect(self, data): - __doc__ = _("""Removes the protection from the current planet. Syntax: /unprotect""") + """Removes the protection from the current planet, or removes a registered player.\nSyntax: /unprotect [player]""" planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship - if on_ship and not ("force" in " ".join(data).lower()): - self.protocol.send_chat_message(_("Can't protect ships (at the moment)")) + if len(data) == 0: + addplayer = self.protocol.player.org_name + first_name_color = self.protocol.player.colored_name(self.config.colors) + else: + addplayer, rest = extract_name(data) + first_name_color = addplayer + first_name = str(addplayer) + if on_ship: + self.protocol.send_chat_message("Can't protect ships (at the moment)") return - if planet in self.protected_planets: - self.protected_planets.remove(planet) - self.protocol.send_chat_message(_("Planet successfully unprotected.")) - self.logger.info("Unprotected planet %s", planet) + if len(data) == 0: + if planet in self.protected_planets: + del self.player_planets[planet] + self.protected_planets.remove(planet) + self.protocol.send_chat_message("Planet successfully unprotected.") + self.logger.info("Unprotected planet %s", planet) + else: + self.protocol.send_chat_message("Planet is not protected!") else: - self.protocol.send_chat_message(_("Planet is not protected!")) + if first_name in self.player_planets[planet]: + self.player_planets[planet].remove(first_name) + self.protocol.send_chat_message("Removed ^yellow;" + first_name_color + "^green; from planet list") + else: + self.protocol.send_chat_message( + "Cannot remove ^yellow;" + first_name_color + "^green; from planet list (not in list)") self.save() def save(self): self.config.plugin_config['protected_planets'] = self.protected_planets + self.config.plugin_config['player_planets'] = self.player_planets self.config.plugin_config['blacklist'] = self.blacklist + self.config.plugin_config['protect_everything'] = self.protect_everything + self.config.save() #we want to save permissions just in case def on_entity_create(self, data): - if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level <= UserLevels.MODERATOR: - entities = entity_create.parse(data.data) - for entity in entities.entity: - if entity.entity_type == EntityType.PROJECTILE: - if self.block_all: return False - p_type = star_string("").parse(entity.entity) - if p_type in self.blacklist: - self.logger.debug( - "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", - self.protocol.player.name, p_type) - return False + """Projectile protection check""" + if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.ADMIN: + name = self.protocol.player.org_name + if name in self.player_planets[self.protocol.player.planet]: + return True + else: + entities = entity_create.parse(data.data) + for entity in entities.entity: + if entity.entity_type == EntityType.PROJECTILE: + if self.block_all: return False + p_type = star_string("").parse(entity.entity) + if p_type in self.blacklist: + self.logger.info( + "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", + self.protocol.player.org_name, p_type) + return False diff --git a/plugins/plugin_manager_plugin/plugin_manager_plugin.py b/plugins/plugin_manager_plugin/plugin_manager_plugin.py index 8d2ce1e..2cce4e8 100644 --- a/plugins/plugin_manager_plugin/plugin_manager_plugin.py +++ b/plugins/plugin_manager_plugin/plugin_manager_plugin.py @@ -4,9 +4,9 @@ class PluginManagerPlugin(SimpleCommandPlugin): - """ Provides a simple chat interface to the PluginManager""" + """Provides a simple chat interface to the PluginManager""" name = "plugin_manager" - commands = ["list_plugins", "enable_plugin", "disable_plugin", "help"] + commands = ["plugin_list", "plugin_enable", "plugin_disable", "help"] auto_activate = True @property @@ -14,64 +14,68 @@ def plugin_manager(self): return self.protocol.plugin_manager @permissions(UserLevels.ADMIN) - def list_plugins(self, data): - __doc__ = """Lists all currently loaded plugins. Syntax: /list_plugins""" - self.protocol.send_chat_message(_("Currently loaded plugins: %s") % " ".join( + def plugin_list(self, data): + """Displays all currently loaded plugins.\nSyntax: /plugin_list""" + self.protocol.send_chat_message("Currently loaded plugins: ^yellow;%s" % "^green;, ^yellow;".join( [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if plugin.active])) inactive = [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if not plugin.active] if len(inactive) > 0: - self.protocol.send_chat_message(_("Inactive plugins: %s") % " ".join( + self.protocol.send_chat_message("Inactive plugins: ^red;%s" % "^green;, ^red;".join( [plugin.name for plugin in self.plugin_manager.plugins.itervalues() if not plugin.active])) - @permissions(UserLevels.ADMIN) - def disable_plugin(self, data): - __doc__ = """Disables a currently activated plugin. Syntax: /disable_plugin [plugin name]""" + @permissions(UserLevels.OWNER) + def plugin_disable(self, data): + """Disables a currently activated plugin.\nSyntax: /plugin_disable (plugin name)""" self.logger.debug("disable_plugin called: %s" " ".join(data)) if len(data) == 0: - self.protocol.send_chat_message(_("You have to specify a plugin.")) + self.protocol.send_chat_message("You have to specify a plugin.") return try: plugin = self.plugin_manager.get_by_name(data[0]) except PluginNotFound: - self.protocol.send_chat_message(_("Couldn't find a plugin with the name %s") % data[0]) + self.protocol.send_chat_message("Couldn't find a plugin with the name %s" % data[0]) return if plugin is self: - self.protocol.send_chat_message(_("Sorry, this plugin can't be deactivated.")) + self.protocol.send_chat_message("Sorry, this plugin can't be deactivated.") return if not plugin.active: - self.protocol.send_chat_message(_("That plugin is already deactivated.")) + self.protocol.send_chat_message("That plugin is already deactivated.") return plugin.deactivate() - self.protocol.send_chat_message(_("Successfully deactivated plugin.")) + self.protocol.send_chat_message("Successfully deactivated plugin.") - @permissions(UserLevels.ADMIN) - def enable_plugin(self, data): - __doc__ = """Enables a currently deactivated plugin. Syntax: /enable_plugin [plugin name]""" + @permissions(UserLevels.OWNER) + def plugin_enable(self, data): + """Enables a currently deactivated plugin.\nSyntax: /plugin_enable (plugin name)""" self.logger.debug("enable_plugin called: %s", " ".join(data)) if len(data) == 0: - self.protocol.send_chat_message(_("You have to specify a plugin.")) + self.protocol.send_chat_message("You have to specify a plugin.") return try: plugin = self.plugin_manager.get_by_name(data[0]) except PluginNotFound: - self.protocol.send_chat_message(_("Couldn't find a plugin with the name %s") % data[0]) + self.protocol.send_chat_message("Couldn't find a plugin with the name %s" % data[0]) return if plugin.active: - self.protocol.send_chat_message(_("That plugin is already active.")) + self.protocol.send_chat_message("That plugin is already active.") return plugin.activate() - self.protocol.send_chat_message(_("Successfully activated plugin.")) + self.protocol.send_chat_message("Successfully activated plugin.") @permissions(UserLevels.GUEST) def help(self, data): - __doc__ = """Prints help messages for plugin commands. Syntax: /help [command]""" + """Prints help messages for server commands.\nSyntax: /help [command]""" if len(data) > 0: command = data[0].lower() func = self.plugins['command_dispatcher'].commands.get(command, None) if func is None: - self.protocol.send_chat_message(_("Couldn't find a command with the name %s") % command) - self.protocol.send_chat_message("%s%s: %s" % (self.config.command_prefix, command, func.__doc__)) + self.protocol.send_chat_message("Couldn't find a command with the name ^yellow;%s" % command) + elif func.level > self.protocol.player.access_level: + self.protocol.send_chat_message("You do not have access to this command.") + else: + #self.protocol.send_chat_message("%s%s: %s" % (self.config.command_prefix, command, func.__doc__)) + self.protocol.send_chat_message("%s" % func.__doc__) else: available = [] for name, f in self.plugins['command_dispatcher'].commands.iteritems(): @@ -79,5 +83,5 @@ def help(self, data): available.append(name) available.sort(key=str.lower) self.protocol.send_chat_message( - _("Available commands: %s\nAlso try /help command") % ", ".join(available)) - return True \ No newline at end of file + "Available commands: ^yellow;%s\n^green;Get more help on commands with ^yellow;/help [command]" % "^green;, ^yellow;".join(available)) + return True diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 049ac04..6f3b924 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -10,7 +10,7 @@ class Warpy(SimpleCommandPlugin): """ name = "warpy_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["warp", "move_ship"] + commands = ["warp", "warp_ship"] auto_activate = True def activate(self): @@ -19,7 +19,7 @@ def activate(self): @permissions(UserLevels.ADMIN) def warp(self, name): - __doc__ = _("""Warps you to a player's ship, or a player to another player's ship. Syntax: /warp [player name] OR /warp [player 1] [player 2]""") + """Warps you to a player's ship (or player to player).\nSyntax: /warp [player] (to player)""" if len(name) == 0: self.protocol.send_chat_message(self.warp.__doc__) return @@ -39,19 +39,25 @@ def warp(self, name): self.warp_player_to_player(first_name, second_name) @permissions(UserLevels.ADMIN) - def move_ship(self, location): - __doc__ = _("""Move your ship to another player or specific coordinates. Syntax: /move_ship [player_name] OR /move_ship [from player] [to player]""") + def warp_ship(self, location): + """Warps a player ship to another players ship.\nSyntax: /warp_ship [player] (to player)""" + if len(location) == 0: + self.protocol.send_chat_message(self.warp_ship.__doc__) + return try: first_name, rest = extract_name(location) - if not rest: - self.move_own_ship_to_player(first_name) - else: - self.move_player_ship_to_other(first_name, extract_name(rest)[0]) except ValueError as e: self.protocol.send_chat_message(str(e)) - self.protocol.send_chat_message(self.move_ship.__doc__) - except AttributeError: - self.protocol.send_chat_message(_("Couldn't find one or both of the users you specified.")) + return + if rest is None or len(rest) == 0: + self.move_own_ship_to_player(first_name) + else: + try: + second_name = extract_name(rest)[0] + except ValueError as e: + self.protocol.send_chat_message(str(e)) + return + self.move_player_ship_to_other(first_name, second_name) def warp_self_to_player(self, name): self.logger.debug("Warp command called by %s to %s", self.protocol.player.name, name) @@ -69,25 +75,28 @@ def warp_player_to_player(self, from_string, to_string): if from_player is not to_player: warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t="WARP_OTHER_SHIP", - player=to_player.name.encode('utf-8'))) - + player=to_player.org_name.encode('utf-8'))) else: warp_packet = build_packet(Packets.WARP_COMMAND, warp_command_write(t='WARP_UP')) from_protocol.client_protocol.transport.write(warp_packet) + if from_string != to_string: + self.protocol.send_chat_message("Warped ^yellow;%s^green; to ^yellow;%s^green;." % (from_string, to_string)) + else: + self.protocol.send_chat_message("Warped to ^yellow;%s^green;." % to_string) else: - self.protocol.send_chat_message(_("No player by the name %s found.") % to_string) + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % to_string) self.protocol.send_chat_message(self.warp.__doc__) return else: - self.protocol.send_chat_message(_("No player by the name %s found.") % from_string) + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % from_string) self.protocol.send_chat_message(self.warp.__doc__) def move_player_ship(self, protocol, location): if len(location) < 5: - self.logger.warning(_("Couldn't derive a warp location in move_player_ship. Coordinates given: %s"), + self.logger.warning("Couldn't derive a warp location in move_player_ship. Coordinates given: ^cyan;%s", ":".join(location)) - self.protocol.send_chat_message(_("Sorry, an error occurred.")) + self.protocol.send_chat_message("Sorry, an error occurred.") return if len(location) == 5: satellite = 0 @@ -103,22 +112,32 @@ def move_player_ship(self, protocol, location): def move_own_ship_to_player(self, player_name): t = self.player_manager.get_logged_in_by_name(player_name) if t is None: - raise ValueError + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % player_name) + self.protocol.send_chat_message(self.warp.__doc__) + return if t.planet == u"": self.protocol.send_chat_message( - _("Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped down to a planet since logging in?") % t.name) + "Sorry, we don't have a tracked planet location for ^yellow;%s^green;. Perhaps they haven't warped down to a planet since logging in?" % t.name) return self.move_player_ship(self.protocol, t.planet.split(":")) + self.protocol.send_chat_message("Warp drive engaged. Warping to ^yellow;%s^green;." % player_name) def move_player_ship_to_other(self, from_player, to_player): f = self.player_manager.get_logged_in_by_name(from_player) t = self.player_manager.get_logged_in_by_name(to_player) - if f is None or t is None: - raise ValueError + if f is None: + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % from_player) + self.protocol.send_chat_message(self.warp.__doc__) + return + if t is None: + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % to_player) + self.protocol.send_chat_message(self.warp.__doc__) + return if t.planet == u"": self.protocol.send_chat_message( - _("Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped to a planet since logging in?") % to_player) + "Sorry, we don't have a tracked planet location for %s. Perhaps they haven't warped to a planet since logging in?" % to_player) return self.move_player_ship(self.factory.protocols[f.protocol], t.planet.split(":")) + self.protocol.send_chat_message("Warp drive engaged. Warping ^yellow;%s^green; to ^yellow;%s^green;." % (from_player, to_player)) diff --git a/server.py b/server.py index 28292e9..c90f22e 100644 --- a/server.py +++ b/server.py @@ -22,7 +22,7 @@ from plugin_manager import PluginManager, route, FatalPluginError from utility_functions import build_packet -VERSION = "1.2.3" +VERSION = "1.4.4" TRACE = False TRACE_LVL = 9 logging.addLevelName(9, "TRACE") @@ -30,18 +30,18 @@ def port_check(upstream_hostname, upstream_port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(1) - result = sock.connect_ex((upstream_hostname, upstream_port)) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex((upstream_hostname, upstream_port)) - if result != 0: - sock.close() - return False - else: - sock.shutdown(SHUT_RDWR) - sock.close() + if result != 0: + sock.close() + return False + else: + sock.shutdown(SHUT_RDWR) + sock.close() - return True + return True class StarryPyServerProtocol(Protocol): @@ -85,7 +85,7 @@ def __init__(self): packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, - packets.Packets.GIVE_ITEM: self.give_item, + packets.Packets.GIVE_ITEM: self.item, packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, @@ -232,7 +232,7 @@ def tile_modification_failure(self, data): return True @route - def give_item(self, data): + def item(self, data): return True @route @@ -449,16 +449,28 @@ def connectionLost(self, reason=connectionDone): :param reason: The reason for the disconnection. :return: None """ - logger.info("Losing connection from IP: %s", self.transport.getPeer().host) - if self.player is not None: - self.player.logged_in = False - self.client_disconnect("") - if self.client_protocol is not None: - self.client_protocol.disconnect() try: - self.factory.protocols.pop(self.id) + if self.client_protocol is not None: + x = build_packet(packets.Packets.CLIENT_DISCONNECT, + packets.client_disconnect().build(Container(data=0))) + if self.player is not None and self.player.logged_in: + self.client_disconnect(x) + self.client_protocol.transport.write(x) + self.client_protocol.transport.abortConnection() except: - self.logger.trace("Protocol was not in factory list. This should not happen.") + logger.error("Couldn't disconnect protocol.") + finally: + try: + self.factory.protocols.pop(self.id) + except: + logger.info("Protocol was not in factory list. This should not happen.") + logger.info("protocol id: %s" % self.id) + finally: + logger.info("Lost connection from IP: %s", self.transport.getPeer().host) + self.transport.abortConnection() + + def die(self): + self.connectionLost() class ClientProtocol(Protocol): @@ -602,16 +614,14 @@ def buildProtocol(self, address): def reap_dead_protocols(self): logger.debug("Reaping dead connections.") count = 0 - start_time = datetime.datetime.utcnow() + start_time = datetime.datetime.now() for protocol in self.protocols.itervalues(): - if ( - protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: + if (protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: logger.trace("Reaping protocol %s. Reason: Server protocol timeout.", protocol.id) protocol.connectionLost() count += 1 continue - if protocol.client_protocol is not None and ( - protocol.client_protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: + if protocol.client_protocol is not None and (protocol.client_protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: protocol.connectionLost() logger.trace("Reaping protocol %s. Reason: Client protocol timeout.", protocol.id) count += 1 @@ -639,16 +649,20 @@ def buildProtocol(self, address): protocol.server_protocol = self.server_protocol return protocol def init_localization(): - locale.setlocale(locale.LC_ALL, '') - loc = locale.getlocale() - filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] try: + locale.setlocale(locale.LC_ALL, '') + except: + locale.setlocale(locale.LC_ALL, 'en_US.utf8') + """try: + loc = locale.getlocale() + filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] print "Opening message file %s for locale %s." % (filename, loc[0]) - trans = gettext.GNUTranslations(open(filename, "rb" )) - except IOError: + trans = gettext.GNUTranslations(open(filename, "rb")) + except (IOError, TypeError, IndexError): print "Locale not found. Using default messages." trans = gettext.NullTranslations() - trans.install() + trans.install()""" + if __name__ == '__main__': init_localization() From b7c1be467ca8bfa3400625a0e238b136d3a4e9d2 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 00:20:46 -0500 Subject: [PATCH 36/81] A few more fixes --- config/config.json.default | 21 ++++++++++++++++----- plugins/core/player_manager/manager.py | 1 - plugins/players/__init__.py | 1 + plugins/players/players.py | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 plugins/players/__init__.py create mode 100644 plugins/players/players.py diff --git a/config/config.json.default b/config/config.json.default index abfcfc6..a61db7d 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -2,6 +2,7 @@ "bind_address": "", "bind_port": 21025, "chat_prefix": "@", + "chattimestamps": true, "colors": { "admin": "^#C443F7;", "default": "^#F7EB43;", @@ -51,7 +52,7 @@ 5 ] ], - "message": "Welcome to the server! Here are some starter items to get you off to a good start." + "message": "Welcome to the server!" }, "planet_protect": { "auto_activate": true, @@ -59,12 +60,21 @@ "bad_packets": [ "CONNECT_WIRE", "DISCONNECT_ALL_WIRES", - "OPEN_CONTAINER", - "CLOSE_CONTAINER", "SWAP_IN_CONTAINER", "DAMAGE_TILE", "DAMAGE_TILE_GROUP", - "REQUEST_DROP", + "MODIFY_TILE_LIST" + ], + "bad_packets_ship": [ + "ENTITY_INTERACT", + "OPEN_CONTAINER", + "CLOSE_CONTAINER" + ], + "bad_packets_mild": [ + "CONNECT_WIRE", + "DISCONNECT_ALL_WIRES", + "DAMAGE_TILE", + "DAMAGE_TILE_GROUP", "MODIFY_TILE_LIST" ], "blacklist": [ @@ -148,7 +158,7 @@ "auto_activate": true, "name_removal_regexes": [ "\\^#[\\w]+;", - "[\\w]+" + "[^ \\w]+" ] }, "plugin_manager": { @@ -168,6 +178,7 @@ "port_check": true, "reap_time": 10, "server_connect_timeout": 5, + "server_name": "--ADD NAME--", "starbound_path": "/opt/starbound/", "upstream_hostname": "localhost", "upstream_port": 21024 diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 7ed0b53..11127e7 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -6,7 +6,6 @@ import json import sqlite3 -from enum import Enum from sqlalchemy.ext.mutable import Mutable from sqlalchemy.orm import sessionmaker, relationship, backref from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey, Boolean, func diff --git a/plugins/players/__init__.py b/plugins/players/__init__.py new file mode 100644 index 0000000..6c4baa9 --- /dev/null +++ b/plugins/players/__init__.py @@ -0,0 +1 @@ +from players import PlayersPlugin diff --git a/plugins/players/players.py b/plugins/players/players.py new file mode 100644 index 0000000..8e9f375 --- /dev/null +++ b/plugins/players/players.py @@ -0,0 +1,21 @@ +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels + + +class PlayersPlugin(SimpleCommandPlugin): + """ + Very simple plugin that adds /players command alias for /who command in StarryPy. + """ + name = "players_plugin" + depends = ["command_dispatcher", "user_management_commands"] + commands = ["players"] + auto_activate = True + + def activate(self): + super(PlayersPlugin, self).activate() + self.user_commands = self.plugins['user_management_commands'] + + @permissions(UserLevels.GUEST) + def players(self, data): + """Displays all current players on the server.\nSyntax: /players""" + self.user_commands.who(data) From fc1ff52ed55df1170e90284d45dda779f1203b39 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 12:54:47 -0500 Subject: [PATCH 37/81] Initial work on bringing packets up to the newest versions. A lot of guesswork going on right now... --- packets/data_types.py | 4 +- packets/packet_types.py | 118 +++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 52 deletions(-) diff --git a/packets/data_types.py b/packets/data_types.py index 676301d..37aed19 100644 --- a/packets/data_types.py +++ b/packets/data_types.py @@ -75,10 +75,10 @@ def _encode(self, obj, context): return obj def _decode(self, obj, context): return "".join(obj) + star_string_struct = lambda name="star_string": Struct(name, VLQ("length"), - String("string", lambda ctx: ctx.length) -) + String("string", lambda ctx: ctx.length)) class VariantVariant(Construct): def _parse(self, stream, context): diff --git a/packets/packet_types.py b/packets/packet_types.py index 92cb7f6..022fb9b 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -1,6 +1,6 @@ from construct import * from enum import IntEnum -from data_types import SignedVLQ, VLQ, Variant, star_string, DictVariant, StarByteArray +from data_types import SignedVLQ, VLQ, Variant, star_string, DictVariant, StarByteArray class Direction(IntEnum): @@ -10,54 +10,59 @@ class Direction(IntEnum): class Packets(IntEnum): PROTOCOL_VERSION = 0 - CONNECT_RESPONSE = 1 - SERVER_DISCONNECT = 2 + SERVER_DISCONNECT = 1 + CONNECT_RESPONSE = 2 HANDSHAKE_CHALLENGE = 3 CHAT_RECEIVED = 4 UNIVERSE_TIME_UPDATE = 5 - CELESTIALRESPONSE = 6 + CELESTIAL_RESPONSE = 6 CLIENT_CONNECT = 7 - CLIENT_DISCONNECT = 8 + CLIENT_DISCONNECT_REQUEST = 8 HANDSHAKE_RESPONSE = 9 - WARP_COMMAND = 10 - CHAT_SENT = 11 - CELESTIALREQUEST = 12 - CLIENT_CONTEXT_UPDATE = 13 - WORLD_START = 14 - WORLD_STOP = 15 - TILE_ARRAY_UPDATE = 16 - TILE_UPDATE = 17 - TILE_LIQUID_UPDATE = 18 - TILE_DAMAGE_UPDATE = 19 - TILE_MODIFICATION_FAILURE = 20 - GIVE_ITEM = 21 - SWAP_IN_CONTAINER_RESULT = 22 - ENVIRONMENT_UPDATE = 23 - ENTITY_INTERACT_RESULT = 24 - MODIFY_TILE_LIST = 25 - DAMAGE_TILE = 26 - DAMAGE_TILE_GROUP = 27 - REQUEST_DROP = 28 - SPAWN_ENTITY = 29 - ENTITY_INTERACT = 30 - CONNECT_WIRE = 31 - DISCONNECT_ALL_WIRES = 32 - OPEN_CONTAINER = 33 - CLOSE_CONTAINER = 34 - SWAP_IN_CONTAINER = 35 - ITEM_APPLY_IN_CONTAINER = 36 - START_CRAFTING_IN_CONTAINER = 37 - STOP_CRAFTING_IN_CONTAINER = 38 - BURN_CONTAINER = 39 - CLEAR_CONTAINER = 40 - WORLD_UPDATE = 41 - ENTITY_CREATE = 42 - ENTITY_UPDATE = 43 - ENTITY_DESTROY = 44 - DAMAGE_NOTIFICATION = 45 - STATUS_EFFECT_REQUEST = 46 - UPDATE_WORLD_PROPERTIES = 47 - HEARTBEAT = 48 + PLAYER_WARP = 10 + FLY_SHIP = 11 + CHAT_SENT = 12 + CELESTIAL_REQUEST = 13 + CLIENT_CONTEXT_UPDATE = 14 + WORLD_START = 15 + WORLD_STOP = 16 + CENTRAL_STRUCTURE_UPDATE = 17 + TILE_ARRAY_UPDATE = 18 + TILE_UPDATE = 19 + TILE_LIQUID_UPDATE = 20 + TILE_DAMAGE_UPDATE = 21 + TILE_MODIFICATION_FAILURE = 22 + GIVE_ITEM = 23 + SWAP_IN_CONTAINER_RESULT = 24 + ENVIRONMENT_UPDATE = 25 + ENTITY_INTERACT_RESULT = 26 + UPDATE_TILE_PROTECTION = 27 + MODIFY_TILE_LIST = 28 + DAMAGE_TILE_GROUP = 29 + COLLECT_LIQUID = 30 + REQUEST_DROP = 31 + SPAWN_ENTITY = 32 + ENTITY_INTERACT = 33 + CONNECT_WIRE = 34 + DISCONNECT_ALL_WIRES = 35 + OPEN_CONTAINER = 36 + CLOSE_CONTAINER = 37 + SWAP_IN_CONTAINER = 38 + ITEM_APPLY_IN_CONTAINER = 39 + START_CRAFTING_IN_CONTAINER = 40 + STOP_CRAFTING_IN_CONTAINER = 41 + BURN_CONTAINER = 42 + CLEAR_CONTAINER = 43 + WORLD_CLIENT_STATE_UPDATE = 44 + ENTITY_CREATE = 45 + ENTITY_UPDATE = 46 + ENTITY_DESTROY = 47 + HIT_REQUEST = 48 + DAMAGE_REQUEST = 49 + DAMAGE_NOTIFICATION = 50 + CALL_SCRIPTED_ENTITY = 51 + UPDATE_WORLD_PROPERTIES = 52 + HEARTBEAT = 53 class EntityType(IntEnum): @@ -70,6 +75,14 @@ class EntityType(IntEnum): PLANT = 5 PLANTDROP = 6 EFFECT = 7 + NPC = 8 + + +class MessageContextMode(IntEnum): + CHANNEL = 1 + BROADCAST = 2 + WHISPER = 3 + COMMAND_RESULT = 4 class PacketOutOfOrder(Exception): @@ -119,8 +132,12 @@ def _decode(self, obj, context): star_string("reject_reason")) chat_received = lambda name="chat_received": Struct(name, - Byte("chat_channel"), - star_string("world"), + Struct("message_context", + Byte("mode"), + If(lambda ctx: ctx.mode is True, + star_string("chat_channel") + ) + ), UBInt32("client_id"), star_string("name"), star_string("message")) @@ -180,12 +197,13 @@ def _decode(self, obj, context): world_start = lambda name="world_start": Struct(name, - Variant("planet"), - Variant("world_structure"), + Variant("planet"), # rename to templateData? + #Variant("world_structure"), StarByteArray("sky_structure"), StarByteArray("weather_data"), - BFloat32("spawn_x"), - BFloat32("spawn_y"), + #BFloat32("spawn_x"), + #BFloat32("spawn_y"), + Array(2, BFloat32("coordinate")), Variant("world_properties"), UBInt32("client_id"), Flag("local_interpolation")) From 735dbf2bffc17f5bc95ad84882a0f250d01385d1 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 13:55:46 -0500 Subject: [PATCH 38/81] More packet editing. tried correcting things to match SB documentation. added own comment directives in per-packet to help keep track. --- packets/packet_types.py | 61 ++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/packets/packet_types.py b/packets/packet_types.py index 022fb9b..b126928 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -98,12 +98,15 @@ def _decode(self, obj, context): return obj.encode("hex") +# may need to be corrected. new version only has hash, uses ByteArray handshake_response = lambda name="handshake_response": Struct(name, star_string("claim_response"), star_string("hash")) +# small correction. added proper context. may need to check if this is correct (need double. used bfloat64). universe_time_update = lambda name="universe_time": Struct(name, - VLQ("unknown")) + #VLQ("unknown")) + BFloat64("universe_time")) packet = lambda name="base_packet": Struct(name, Byte("id"), @@ -115,22 +118,28 @@ def _decode(self, obj, context): Byte("id"), SignedVLQ("payload_size")) +# should still work. nothing's changed here. protocol_version = lambda name="protocol_version": Struct(name, UBInt32("server_build")) connection = lambda name="connection": Struct(name, GreedyRange(Byte("compressed_data"))) +# may need to be corrected. new version only has salt, uses ByteArray handshake_challenge = lambda name="handshake_challenge": Struct(name, star_string("claim_message"), star_string("salt"), SBInt32("round_count")) +# Needs attention connect_response = lambda name="connect_response": Struct(name, Flag("success"), VLQ("client_id"), - star_string("reject_reason")) + star_string("reject_reason") + # may need to add something here CelestialBaseInformation + ) +# corrected. needs testing chat_received = lambda name="chat_received": Struct(name, Struct("message_context", Byte("mode"), @@ -142,23 +151,35 @@ def _decode(self, obj, context): star_string("name"), star_string("message")) +# corrected. shouldn't need too much testing chat_sent = lambda name="chat_sent": Struct(name, star_string("message"), - Byte("chat_channel")) + Enum(Byte("send_mode"), + BROADCAST=1, + LOCAL=2, + PARTY=3) + ) +# quite a bit of guesswork and hackery here with the ship_upgrades. client_connect = lambda name="client_connect": Struct(name, VLQ("asset_digest_length"), String("asset_digest", lambda ctx: ctx.asset_digest_length), - Variant("claim"), + #Variant("claim"), # no longer used? Flag("uuid_exists"), If(lambda ctx: ctx.uuid_exists is True, HexAdapter(Field("uuid", 16)) ), star_string("name"), star_string("species"), - VLQ("shipworld_length"), - Field("shipworld", lambda ctx: ctx.shipworld_length), + #VLQ("shipworld_length"), + #Field("shipworld", lambda ctx: ctx.shipworld_length), + StarByteArray("ship_data"), + # Replace: ShipUpgrades + Struct("ship_upgrades", + UBInt32("ship_level"), + UBInt32("max_fuel"), + VLQ("capabilities")), star_string("account")) client_disconnect = lambda name="client_disconnect": Struct(name, @@ -196,6 +217,7 @@ def _decode(self, obj, context): player=player)) +# partially correct. Needs work on dungeon ID value world_start = lambda name="world_start": Struct(name, Variant("planet"), # rename to templateData? #Variant("world_structure"), @@ -211,6 +233,7 @@ def _decode(self, obj, context): world_stop = lambda name="world_stop": Struct(name, star_string("status")) +# I THINK this is ok. Will test later. give_item = lambda name="give_item": Struct(name, star_string("name"), VLQ("count"), @@ -222,12 +245,15 @@ def _decode(self, obj, context): variant_type=7, description='')) +# replaced with something closer matched to SB documentation update_world_properties = lambda name="world_properties": Struct(name, - UBInt8("count"), - Array(lambda ctx: ctx.count, - Struct("properties", - star_string("key"), - Variant("value")))) + DictVariant("updated_properties")) +#update_world_properties = lambda name="world_properties": Struct(name, +# UBInt8("count"), +# Array(lambda ctx: ctx.count, +# Struct("properties", +# star_string("key"), +# Variant("value")))) update_world_properties_write = lambda dictionary: update_world_properties().build( Container( @@ -242,11 +268,14 @@ def _decode(self, obj, context): String("entity", lambda ctx: ctx.entity_size), SignedVLQ("entity_id")))) +# replaced with something closer matched to SB documentation client_context_update = lambda name="client_context": Struct(name, - VLQ("length"), - Byte("arguments"), - Array(lambda ctx: ctx.arguments, - Struct("key", - Variant("value")))) + StarByteArray("update_data")) +#client_context_update = lambda name="client_context": Struct(name, +# VLQ("length"), +# Byte("arguments"), +# Array(lambda ctx: ctx.arguments, +# Struct("key", +# Variant("value")))) projectile = DictVariant("projectile") From 87545c934442660f6058bcfb057d6dae487da96c Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 14:02:07 -0500 Subject: [PATCH 39/81] small changes to allow server to start for testing. will need to properly correct these latter. --- .../starbound_config_manager/starbound_config_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index 9cfb4eb..96f980d 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -13,7 +13,7 @@ class StarboundConfigManager(SimpleCommandPlugin): def activate(self): super(StarboundConfigManager, self).activate() try: - configuration_file = FilePath(self.config.starbound_path).child('starbound.config') + configuration_file = FilePath(self.config.starbound_path).child('starbound_server.config') if not configuration_file.exists(): raise FatalPluginError( "Could not open starbound configuration file. Tried path: %s" % configuration_file) @@ -30,7 +30,8 @@ def activate(self): raise FatalPluginError( "The starbound gamePort option (%d) does not match the config.json upstream_port (%d)." % ( starbound_config['gamePort'], self.config.upstream_port)) - self._spawn = starbound_config['defaultWorldCoordinate'].split(":") + #self._spawn = starbound_config['defaultWorldCoordinate'].split(":") + self._spawn = "junk" @permissions(UserLevels.GUEST) def spawn(self, data): From 7af5c397fd484bcead6129e8630dc6b9a061d23d Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 4 Jan 2015 14:13:59 -0500 Subject: [PATCH 40/81] corrected server.py to match new packet types --- server.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index c90f22e..aa26382 100644 --- a/server.py +++ b/server.py @@ -65,23 +65,25 @@ def __init__(self): self.plugin_manager = None self.call_mapping = { packets.Packets.PROTOCOL_VERSION: self.protocol_version, - packets.Packets.CONNECT_RESPONSE: self.connect_response, packets.Packets.SERVER_DISCONNECT: self.server_disconnect, + packets.Packets.CONNECT_RESPONSE: self.connect_response, packets.Packets.HANDSHAKE_CHALLENGE: self.handshake_challenge, packets.Packets.CHAT_RECEIVED: self.chat_received, packets.Packets.UNIVERSE_TIME_UPDATE: self.universe_time_update, + packets.Packets.CELESTIAL_RESPONSE: lambda x: True, packets.Packets.CLIENT_CONNECT: self.client_connect, - packets.Packets.CLIENT_DISCONNECT: self.client_disconnect, + packets.Packets.CLIENT_DISCONNECT_REQUEST: self.client_disconnect, packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, - packets.Packets.WARP_COMMAND: self.warp_command, + packets.Packets.PLAYER_WARP: self.warp_command, + packets.Packets.FLY_SHIP: lambda x: True, packets.Packets.CHAT_SENT: self.chat_sent, + packets.Packets.CELESTIAL_REQUEST: lambda x: True, packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, packets.Packets.WORLD_START: self.world_start, packets.Packets.WORLD_STOP: self.world_stop, + packets.Packets.CENTRAL_STRUCTURE_UPDATE: lambda x: True, packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, packets.Packets.TILE_UPDATE: self.tile_update, - packets.Packets.CELESTIALRESPONSE: lambda x: True, - packets.Packets.CELESTIALREQUEST: lambda x: True, packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, @@ -89,9 +91,10 @@ def __init__(self): packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, + packets.Packets.UPDATE_TILE_PROTECTION: lambda x: True, packets.Packets.MODIFY_TILE_LIST: self.modify_tile_list, - packets.Packets.DAMAGE_TILE: self.damage_tile, packets.Packets.DAMAGE_TILE_GROUP: self.damage_tile_group, + packets.Packets.COLLECT_LIQUID: lambda x: True, packets.Packets.REQUEST_DROP: self.request_drop, packets.Packets.SPAWN_ENTITY: self.spawn_entity, packets.Packets.ENTITY_INTERACT: self.entity_interact, @@ -105,12 +108,13 @@ def __init__(self): packets.Packets.STOP_CRAFTING_IN_CONTAINER: self.stop_crafting_in_container, packets.Packets.BURN_CONTAINER: self.burn_container, packets.Packets.CLEAR_CONTAINER: self.clear_container, - packets.Packets.WORLD_UPDATE: self.world_update, + packets.Packets.WORLD_CLIENT_STATE_UPDATE: self.world_update, packets.Packets.ENTITY_CREATE: self.entity_create, packets.Packets.ENTITY_UPDATE: self.entity_update, packets.Packets.ENTITY_DESTROY: self.entity_destroy, + packets.Packets.DAMAGE_REQUEST: lambda x: True, packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, - packets.Packets.STATUS_EFFECT_REQUEST: self.status_effect_request, + packets.Packets.CALL_SCRIPTED_ENTITY: lambda x: True, packets.Packets.UPDATE_WORLD_PROPERTIES: self.update_world_properties, packets.Packets.HEARTBEAT: self.heartbeat, } From 71551216c4e05040b8f2561ff343128f7f0f693b Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 5 Jan 2015 11:28:01 -0500 Subject: [PATCH 41/81] Got everything working using a vanilla connection. Need to start whack-a-mole on plugin errors, namely warp. --- base_plugin.py | 6 + packets/packet_types.py | 76 ++-- plugins/announcer_plugin/announcer_plugin.py | 4 +- plugins/core/player_manager/plugin.py | 7 +- plugins/warpy_plugin/warpy_plugin.py | 7 +- res/messages.po | 417 ------------------- server.py | 38 +- utility_functions.py | 11 +- 8 files changed, 76 insertions(+), 490 deletions(-) delete mode 100644 res/messages.po diff --git a/base_plugin.py b/base_plugin.py index 2ca7153..cfc6b29 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -56,6 +56,9 @@ def on_handshake_challenge(self, data): def on_chat_received(self, data): return True + def on_celestial_request(self, data): + return True + def on_universe_time_update(self, data): return True @@ -197,6 +200,9 @@ def after_handshake_challenge(self, data): def after_chat_received(self, data): return True + def after_celestial_request(self, data): + return True + def after_universe_time_update(self, data): return True diff --git a/packets/packet_types.py b/packets/packet_types.py index b126928..e25d964 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -79,10 +79,10 @@ class EntityType(IntEnum): class MessageContextMode(IntEnum): - CHANNEL = 1 - BROADCAST = 2 - WHISPER = 3 - COMMAND_RESULT = 4 + CHANNEL = 0 + BROADCAST = 1 + WHISPER = 2 + COMMAND_RESULT = 3 class PacketOutOfOrder(Exception): @@ -141,12 +141,8 @@ def _decode(self, obj, context): # corrected. needs testing chat_received = lambda name="chat_received": Struct(name, - Struct("message_context", - Byte("mode"), - If(lambda ctx: ctx.mode is True, - star_string("chat_channel") - ) - ), + Byte("mode"), + star_string("chat_channel"), UBInt32("client_id"), star_string("name"), star_string("message")) @@ -155,9 +151,9 @@ def _decode(self, obj, context): chat_sent = lambda name="chat_sent": Struct(name, star_string("message"), Enum(Byte("send_mode"), - BROADCAST=1, - LOCAL=2, - PARTY=3) + BROADCAST=0, + LOCAL=1, + PARTY=2) ) # quite a bit of guesswork and hackery here with the ship_upgrades. @@ -165,28 +161,28 @@ def _decode(self, obj, context): VLQ("asset_digest_length"), String("asset_digest", lambda ctx: ctx.asset_digest_length), - #Variant("claim"), # no longer used? Flag("uuid_exists"), If(lambda ctx: ctx.uuid_exists is True, HexAdapter(Field("uuid", 16)) ), star_string("name"), star_string("species"), - #VLQ("shipworld_length"), - #Field("shipworld", lambda ctx: ctx.shipworld_length), StarByteArray("ship_data"), - # Replace: ShipUpgrades - Struct("ship_upgrades", - UBInt32("ship_level"), - UBInt32("max_fuel"), - VLQ("capabilities")), + UBInt32("ship_level"), + UBInt32("max_fuel"), + VLQ("capabilities"), star_string("account")) +server_disconnect = lambda name="server_disconnect": Struct(name, + star_string("reason")) + client_disconnect = lambda name="client_disconnect": Struct(name, Byte("data")) +celestial_request = lambda name="celestial_request": Struct(name, + GreedyRange(star_string("requests"))) + world_coordinate = lambda name="world_coordinate": Struct(name, - star_string("sector"), SBInt32("x"), SBInt32("y"), SBInt32("z"), @@ -203,11 +199,10 @@ def _decode(self, obj, context): world_coordinate(), star_string("player")) -warp_command_write = lambda t, sector=u'', x=0, y=0, z=0, planet=0, satellite=0, player=u'': warp_command().build( +warp_command_write = lambda t, x=0, y=0, z=0, planet=0, satellite=0, player=u'': warp_command().build( Container( warp_type=t, world_coordinate=Container( - sector=sector, x=x, y=y, z=z, @@ -220,11 +215,9 @@ def _decode(self, obj, context): # partially correct. Needs work on dungeon ID value world_start = lambda name="world_start": Struct(name, Variant("planet"), # rename to templateData? - #Variant("world_structure"), - StarByteArray("sky_structure"), + StarByteArray("sky_data"), StarByteArray("weather_data"), - #BFloat32("spawn_x"), - #BFloat32("spawn_y"), + #dungeon id stuff here Array(2, BFloat32("coordinate")), Variant("world_properties"), UBInt32("client_id"), @@ -246,14 +239,14 @@ def _decode(self, obj, context): description='')) # replaced with something closer matched to SB documentation -update_world_properties = lambda name="world_properties": Struct(name, - DictVariant("updated_properties")) #update_world_properties = lambda name="world_properties": Struct(name, -# UBInt8("count"), -# Array(lambda ctx: ctx.count, -# Struct("properties", -# star_string("key"), -# Variant("value")))) +# DictVariant("updated_properties")) +update_world_properties = lambda name="world_properties": Struct(name, + UBInt8("count"), + Array(lambda ctx: ctx.count, + Struct("properties", + star_string("key"), + Variant("value")))) update_world_properties_write = lambda dictionary: update_world_properties().build( Container( @@ -268,14 +261,11 @@ def _decode(self, obj, context): String("entity", lambda ctx: ctx.entity_size), SignedVLQ("entity_id")))) -# replaced with something closer matched to SB documentation client_context_update = lambda name="client_context": Struct(name, - StarByteArray("update_data")) -#client_context_update = lambda name="client_context": Struct(name, -# VLQ("length"), -# Byte("arguments"), -# Array(lambda ctx: ctx.arguments, -# Struct("key", -# Variant("value")))) + VLQ("length"), + Byte("arguments"), + Array(lambda ctx: ctx.arguments, + Struct("key", + Variant("value")))) projectile = DictVariant("projectile") diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index 2dd2927..d731daa 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -17,7 +17,7 @@ def after_connect_response(self, data): c = connect_response().parse(data.data) if c.success: self.factory.broadcast( - self.protocol.player.colored_name(self.config.colors) + " logged in.", 0, "", "Announcer") + self.protocol.player.colored_name(self.config.colors) + " logged in.", 0, "Announcer") except AttributeError: self.logger.debug("Attribute error in after_connect_response.") return @@ -28,5 +28,5 @@ def after_connect_response(self, data): def on_client_disconnect(self, data): if self.protocol.player is not None: self.factory.broadcast(self.protocol.player.colored_name(self.config.colors) + " logged out.", 0, - "", "Announcer") + "Announcer") diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 7122c9b..7392866 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -116,17 +116,16 @@ def on_connect_response(self, data): def after_world_start(self, data): world_start = packets.world_start().parse(data.data) - if 'fuel.max' in world_start['world_properties']: + if 'ship.maxFuel' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True self.protocol.player.planet = "On ship" else: coords = world_start.planet['celestialParameters']['coordinate'] parent_system = coords - location = parent_system['location'] - l = location + l = parent_system['location'] self.protocol.player.on_ship = False - planet = Planet(parent_system['sector'], l[0], l[1], l[2], + planet = Planet(l[0], l[1], l[2], coords['planet'], coords['satellite']) self.protocol.player.planet = str(planet) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 6f3b924..6b7cb71 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -73,11 +73,11 @@ def warp_player_to_player(self, from_string, to_string): if to_player is not None: from_protocol = self.factory.protocols[from_player.protocol] if from_player is not to_player: - warp_packet = build_packet(Packets.WARP_COMMAND, + warp_packet = build_packet(Packets.PLAYER_WARP, warp_command_write(t="WARP_OTHER_SHIP", player=to_player.org_name.encode('utf-8'))) else: - warp_packet = build_packet(Packets.WARP_COMMAND, + warp_packet = build_packet(Packets.PLAYER_WARP, warp_command_write(t='WARP_UP')) from_protocol.client_protocol.transport.write(warp_packet) if from_string != to_string: @@ -106,8 +106,7 @@ def move_player_ship(self, protocol, location): z = int(location.pop()) y = int(location.pop()) x = int(location.pop()) - sector = location.pop() - move_ship_to_coords(protocol, sector, x, y, z, planet, satellite) + move_ship_to_coords(protocol, x, y, z, planet, satellite) def move_own_ship_to_player(self, player_name): t = self.player_manager.get_logged_in_by_name(player_name) diff --git a/res/messages.po b/res/messages.po deleted file mode 100644 index 4a9792f..0000000 --- a/res/messages.po +++ /dev/null @@ -1,417 +0,0 @@ -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-02-18 23:27-0600\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#: plugins/planet_protect/planet_protect_plugin.py:35 -msgid "" -"Protects the current planet. Only registered users can build on protected " -"planets. Syntax: /protect" -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:39 -#: plugins/planet_protect/planet_protect_plugin.py:55 -msgid "Can't protect ships (at the moment)" -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:43 -msgid "Planet successfully protected." -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:46 -msgid "Planet is already protected!" -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:51 -msgid "Removes the protection from the current planet. Syntax: /unprotect" -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:59 -msgid "Planet successfully unprotected." -msgstr "" - -#: plugins/planet_protect/planet_protect_plugin.py:62 -msgid "Planet is not protected!" -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:22 -msgid "" -"Warps you to a player's ship, or a player to another player's ship. Syntax: /" -"warp [player name] OR /warp [player 1] [player 2]" -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:43 -msgid "" -"Move your ship to another player or specific coordinates. Syntax: /move_ship " -"[player_name] OR /move_ship [from player] [to player]" -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:54 -msgid "Couldn't find one or both of the users you specified." -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:78 -#: plugins/warpy_plugin/warpy_plugin.py:82 -#, python-format -msgid "No player by the name %s found." -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:87 -#, python-format -msgid "" -"Couldn't derive a warp location in move_player_ship. Coordinates given: %s" -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:89 -msgid "Sorry, an error occurred." -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:108 -#, python-format -msgid "" -"Sorry, we don't have a tracked planet location for %s. Perhaps they haven't " -"warped down to a planet since logging in?" -msgstr "" - -#: plugins/warpy_plugin/warpy_plugin.py:120 -#, python-format -msgid "" -"Sorry, we don't have a tracked planet location for %s. Perhaps they haven't " -"warped to a planet since logging in?" -msgstr "" - -#: plugins/admin_messenger/admin_messenger.py:30 -#, python-format -msgid "Received an admin message from %s: %s." -msgstr "" - -#: plugins/admin_messenger/admin_messenger.py:38 -#, python-format -msgid "%sSERVER BROADCAST: %s%s" -msgstr "" - -#: plugins/motd_plugin/motd_plugin.py:28 -#, python-format -msgid "" -"Message of the Day:\n" -"%s" -msgstr "" - -#: plugins/motd_plugin/motd_plugin.py:32 -msgid "Displays the message of the day. Usage: /motd" -msgstr "" - -#: plugins/motd_plugin/motd_plugin.py:40 -msgid "" -"Sets the message of the day to a new value. Usage: /set_motd [New message of " -"the day]" -msgstr "" - -#: plugins/core/starbound_config_manager/starbound_config_manager.py:37 -msgid "Moves your ship to spawn. Syntax: /spawn" -msgstr "" - -#: plugins/core/starbound_config_manager/starbound_config_manager.py:39 -msgid "Moving your ship to spawn." -msgstr "" - -#: plugins/core/player_manager/plugin.py:122 -msgid "" -"That player is currently logged in. Refusing to delete logged in character." -msgstr "" - -#: plugins/core/player_manager/plugin.py:128 -#, python-format -msgid "" -"Couldn't find a player named %s. Please check the spelling and try again." -msgstr "" - -#: plugins/core/player_manager/plugin.py:131 -#, python-format -msgid "Deleted player with name %s." -msgstr "" - -#: plugins/core/player_manager/plugin.py:144 -#, python-format -msgid "Results: %s" -msgstr "" - -#: plugins/core/player_manager/plugin.py:147 -#, python-format -msgid "Results: %s)" -msgstr "" - -#: plugins/core/player_manager/plugin.py:149 -#, python-format -msgid "" -"And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it " -"will be replaced appropriately." -msgstr "" - -#: plugins/core/player_manager/manager.py:365 -msgid "You are not an admin." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:26 -msgid "Returns all current users on the server. Syntax: /who" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:33 -msgid "Displays who is on your current planet." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:36 -#, python-format -msgid "%d players on your current planet: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:40 -msgid "" -"Returns client data about the specified user. Syntax: /whois [user name]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:45 -#, python-format -msgid "" -"Name: %s\n" -"Userlevel: %s\n" -"UUID: %s\n" -"IP address: %s\n" -"Current planet: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:49 -msgid "Player not found!" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:54 -msgid "" -"Promotes/demotes a user to a specific rank. Syntax: /promote [username] " -"[rank] (where rank is either: registered, moderator, admin, or guest))" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:72 -msgid "" -"You cannot change that user's access level as they are at least at an equal " -"level as you." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:84 -msgid "No such rank!\n" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:88 -#, python-format -msgid "%s: %s -> %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:94 -#, python-format -msgid "%s has promoted you to %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:100 -msgid "Player not found!\n" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:128 -msgid "Kicks a user from the server. Usage: /kick [username] [reason]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:145 -#, python-format -msgid "Couldn't find a user by the name %s." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:152 -msgid "Bans an IP (retrieved by /whois). Syntax: /ban [ip address]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:158 -#, python-format -msgid "Banned IP: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:171 -msgid "Lists the currently banned IPs. Syntax: /bans" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:173 -#, python-format -msgid "IP: %s " -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:177 -msgid "Unbans an IP. Syntax: /unban [ip address]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:182 -#, python-format -msgid "Unbanned IP: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:185 -#, python-format -msgid "Couldn't find IP: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:190 -msgid "" -"Gives an item to a player. Syntax: /give [target player] [item name] " -"[optional: item count]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:195 -#, python-format -msgid "Please check your syntax. %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:199 -msgid "" -"Please check that the username you are referencing exists. If it has spaces, " -"please surround it by quotes." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:202 -#, python-format -msgid "An unknown error occured. %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:214 -#, python-format -msgid "%s has given you: %s (count: %s)" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:216 -msgid "Sent the item(s)." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:220 -msgid "You have to give an item name." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:222 -#, python-format -msgid "Couldn't find name: %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:229 -msgid "Mute a player. Syntax: /mute [player name]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:237 -msgid "You have been muted." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:238 -#, python-format -msgid "%s has been muted." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:242 -msgid "Unmute a currently muted player. Syntax: /unmute [player name]" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:246 -#, python-format -msgid "Couldn't find a user by the name %s" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:250 -msgid "You have been unmuted." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:251 -#, python-format -msgid "%s has been unmuted." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:255 -msgid "" -"Sets the server to passthrough mode. *This is irreversible without restart.* " -"Syntax: /passthrough" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:260 -msgid "" -"Shutdown the server in n seconds. Syntax: /shutdown [number of seconds] (>0)" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:264 -#, python-format -msgid "%s is not a number. Please enter a value in seconds." -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:266 -#, python-format -msgid "SERVER ANNOUNCEMENT: Server is shutting down in %s seconds!" -msgstr "" - -#: plugins/core/admin_commands_plugin/admin_command_plugin.py:278 -#, python-format -msgid "" -"You are currently muted and cannot speak. You are limited to commands and " -"admin chat (prefix your lines with %s for admin chat." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:19 -#, python-format -msgid "Currently loaded plugins: %s" -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:23 -#, python-format -msgid "Inactive plugins: %s" -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:31 -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:53 -msgid "You have to specify a plugin." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:36 -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:58 -#, python-format -msgid "Couldn't find a plugin with the name %s" -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:39 -msgid "Sorry, this plugin can't be deactivated." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:42 -msgid "That plugin is already deactivated." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:46 -msgid "Successfully deactivated plugin." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:61 -msgid "That plugin is already active." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:64 -msgid "Successfully activated plugin." -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:73 -#, python-format -msgid "Couldn't find a command with the name %s" -msgstr "" - -#: plugins/plugin_manager_plugin/plugin_manager_plugin.py:82 -#, python-format -msgid "" -"Available commands: %s\n" -"Also try /help command" -msgstr "" diff --git a/server.py b/server.py index aa26382..b89a9f9 100644 --- a/server.py +++ b/server.py @@ -77,7 +77,7 @@ def __init__(self): packets.Packets.PLAYER_WARP: self.warp_command, packets.Packets.FLY_SHIP: lambda x: True, packets.Packets.CHAT_SENT: self.chat_sent, - packets.Packets.CELESTIAL_REQUEST: lambda x: True, + packets.Packets.CELESTIAL_REQUEST: self.celestial_request, packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, packets.Packets.WORLD_START: self.world_start, packets.Packets.WORLD_STOP: self.world_stop, @@ -146,7 +146,9 @@ def string_received(self, packet): Processing of parsed data is handled in handle_starbound_packets() :rtype : None """ - if 48 >= packet.id: + if 53 >= packet.id: + # DEBUG - print all packet IDs going to client + #logger.info("%s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) @@ -365,6 +367,16 @@ def chat_sent(self, data): """ return True + @route + def celestial_request(self, data): + """ + Called when the client requests celestial data...? + + :param data: Parsed chat packet. + :rtype : bool + """ + return True + @route def damage_notification(self, data): return True @@ -402,11 +414,11 @@ def warp_command(self, data): def handle_starbound_packets(self, p): """ This function is the meat of it all. Every time a full packet with - a derived ID <= 48, it is passed through here. + a derived ID <= 53, it is passed through here. """ return self.call_mapping[p.id](p) - def send_chat_message(self, text, channel=0, world='', name=''): + def send_chat_message(self, text, mode=0, channel='', name=''): """ Convenience function to send chat messages to the client. Note that this does *not* send messages to the server at large; broadcast should be @@ -414,8 +426,7 @@ def send_chat_message(self, text, channel=0, world='', name=''): otherwise. :param text: Message text, may contain multiple lines. - :param channel: The chat channel/context. 0 is global, 1 is planet. - :param world: World + :param channel: The chat channel/context. :param name: The name to display before the message. Blank leaves no brackets, otherwise it will be displayed as ``. :return: None @@ -426,9 +437,9 @@ def send_chat_message(self, text, channel=0, world='', name=''): self.send_chat_message(line) return if self.player is not None: - logger.trace("Calling send_chat_message from player %s on channel %d on world '%s' with reported username of %s with message: %s", self.player.name, channel, world, name, text) - chat_data = packets.chat_received().build(Container(chat_channel=channel, - world=world, + logger.trace("Calling send_chat_message from player %s on channel %d with mode %d with reported username of %s with message: %s", self.player.name, channel, mode, name, text) + chat_data = packets.chat_received().build(Container(mode=mode, + chat_channel=channel, client_id=0, name=name, message=text.encode("utf-8"))) @@ -455,7 +466,7 @@ def connectionLost(self, reason=connectionDone): """ try: if self.client_protocol is not None: - x = build_packet(packets.Packets.CLIENT_DISCONNECT, + x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, packets.client_disconnect().build(Container(data=0))) if self.player is not None and self.player.logged_in: self.client_disconnect(x) @@ -535,7 +546,7 @@ def dataReceived(self, data): self.packet_stream += data def disconnect(self): - x = build_packet(packets.Packets.CLIENT_DISCONNECT, packets.client_disconnect().build(Container(data=0))) + x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, packets.client_disconnect().build(Container(data=0))) self.transport.write(x) self.transport.abortConnection() @@ -571,14 +582,13 @@ def stopFactory(self): self.config.save() self.plugin_manager.die() - def broadcast(self, text, channel=1, world='', name=''): + def broadcast(self, text, mode=1, name=''): """ Convenience method to send a broadcasted message to all clients on the server. :param text: Message text - :param channel: Channel to broadcast on. 0 is global, 1 is planet. - :param world: World + :param mode: Channel to broadcast on. 0 is global, 1 is planet. :param name: The name to prepend before the message, format is :return: None """ diff --git a/utility_functions.py b/utility_functions.py index a28da38..86d11a9 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -49,8 +49,7 @@ def build_packet(packet_type, data): class Planet(object): - def __init__(self, sector, x, y, z, planet, satellite): - self.sector = sector + def __init__(self, x, y, z, planet, satellite): self.x = x self.y = y self.z = z @@ -58,15 +57,15 @@ def __init__(self, sector, x, y, z, planet, satellite): self.satellite = satellite def __str__(self): - return "%s:%d:%d:%d:%d:%d" % (self.sector, self.x, self.y, self.z, self.planet, self.satellite) + return "%d:%d:%d:%d:%d" % (self.x, self.y, self.z, self.planet, self.satellite) -def move_ship_to_coords(protocol, sector, x, y, z, planet, satellite): +def move_ship_to_coords(protocol, x, y, z, planet, satellite): logger.info("Moving %s's ship to coordinates: %s", protocol.player.name, - ":".join((sector, str(x), str(y), str(z), str(planet), str(satellite)))) + ":".join((str(x), str(y), str(z), str(planet), str(satellite)))) x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) warp_packet = build_packet(packets.Packets.WARP_COMMAND, - packets.warp_command_write(t="MOVE_SHIP", sector=sector, x=x, y=y, z=z, + packets.warp_command_write(t="MOVE_SHIP", x=x, y=y, z=z, planet=planet, satellite=satellite, player="".encode('utf-8'))) protocol.client_protocol.transport.write(warp_packet) From 08157b32e7c4bc65f1e37be74840f0f897c08d36 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Thu, 8 Jan 2015 02:39:32 -0500 Subject: [PATCH 42/81] Fixed warping. Added command for warping to outposts directly. --- .gitignore | 5 ++- packets/data_types.py | 29 ++++++++++++++++- packets/packet_types.py | 33 +++++++------------ plugins/core/player_manager/plugin.py | 5 +++ plugins/warpy_plugin/warpy_plugin.py | 46 ++++++++++++++++++++++++--- server.py | 4 ++- utility_functions.py | 2 +- 7 files changed, 94 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index d4e4c2f..37d2614 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ nosetests.xml *.db *.db-journal *.log -.idea/ \ No newline at end of file +.idea/ + +# Vim mess cleanup +*.swp diff --git a/packets/data_types.py b/packets/data_types.py index 37aed19..6570456 100644 --- a/packets/data_types.py +++ b/packets/data_types.py @@ -1,6 +1,6 @@ import logging from construct import Construct, Struct, Byte, BFloat64, Flag, \ - String, Container + String, Container, Field from construct.core import _read_stream, _write_stream, Adapter @@ -95,6 +95,33 @@ def _parse(self, stream, context): c[key] = value return c +class WarpVariant(Construct): +# Not all variants have been properly treated! + def _parse(self, stream, context): + x = Byte("").parse_stream(stream) + if x == 0: + return None + elif x == 1: + return star_string().parse_stream(stream) + elif x == 2: + return None + elif x == 3: + flag = Flag("").parse_stream(stream) + return Field("", 16).parse_stream(stream).encode("hex") + def _build(self, obj, stream, context): + if len(obj) == 32: + _write_stream(stream, 1, chr(3)) + _write_stream(stream, 1, chr(1)) + _write_stream(stream, len(obj.decode("hex")), obj.decode("hex")) + return + elif obj is "outpost": + _write_stream(stream, 1, chr(1)) + star_string()._build(obj, stream, context) + return + elif obj is None: + _write_stream(stream, 1, chr(4)) + _write_stream(stream, 1, chr(0)) + return class Variant(Construct): def _parse(self, stream, context): diff --git a/packets/packet_types.py b/packets/packet_types.py index e25d964..67145cd 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -1,6 +1,7 @@ from construct import * from enum import IntEnum from data_types import SignedVLQ, VLQ, Variant, star_string, DictVariant, StarByteArray +from data_types import WarpVariant class Direction(IntEnum): @@ -118,7 +119,6 @@ def _decode(self, obj, context): Byte("id"), SignedVLQ("payload_size")) -# should still work. nothing's changed here. protocol_version = lambda name="protocol_version": Struct(name, UBInt32("server_build")) @@ -190,26 +190,18 @@ def _decode(self, obj, context): SBInt32("satellite")) warp_command = lambda name="warp_command": Struct(name, - Enum(UBInt32("warp_type"), - MOVE_SHIP=1, - WARP_UP=2, - WARP_OTHER_SHIP=3, - WARP_DOWN=4, - WARP_HOME=5), - world_coordinate(), - star_string("player")) - -warp_command_write = lambda t, x=0, y=0, z=0, planet=0, satellite=0, player=u'': warp_command().build( + Enum(UBInt8("warp_type"), + WARP_TO=0, + WARP_RETURN=1, + WARP_TO_HOME_WORLD=2, + WARP_TO_ORBITED_WORLD=3, + WARP_TO_OWN_SHIP=4), + WarpVariant("world_id")) + +warp_command_write = lambda t, world_id: warp_command().build( Container( warp_type=t, - world_coordinate=Container( - x=x, - y=y, - z=z, - planet=planet, - satellite=satellite - ), - player=player)) + world_id=world_id)) # partially correct. Needs work on dungeon ID value @@ -238,9 +230,6 @@ def _decode(self, obj, context): variant_type=7, description='')) -# replaced with something closer matched to SB documentation -#update_world_properties = lambda name="world_properties": Struct(name, -# DictVariant("updated_properties")) update_world_properties = lambda name="world_properties": Struct(name, UBInt8("count"), Array(lambda ctx: ctx.count, diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 7392866..369ea69 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -115,11 +115,16 @@ def on_connect_response(self, data): return True def after_world_start(self, data): + # may need to add more world types in here world_start = packets.world_start().parse(data.data) + # self.logger.debug("World start: %s", world_start) # debug world packets if 'ship.maxFuel' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True self.protocol.player.planet = "On ship" + elif world_start.planet['celestialParameters'] is None: + self.protocol.player.on_ship = False + self.protocol.player.planet = "On Outpost" else: coords = world_start.planet['celestialParameters']['coordinate'] parent_system = coords diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 6b7cb71..d2db7f5 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -1,3 +1,4 @@ +# -*- coding: UTF-8 -*- from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels from packets import warp_command_write, Packets, warp_command @@ -10,13 +11,19 @@ class Warpy(SimpleCommandPlugin): """ name = "warpy_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["warp", "warp_ship"] + commands = ["warp", "warp_ship", "outpost"] auto_activate = True def activate(self): super(Warpy, self).activate() self.player_manager = self.plugins['player_manager'].player_manager + ## Warp debugging + #def on_warp_command(self, data): + # self.logger.debug("Warp packet: %s", data.data.encode('hex')) + # warp_data = warp_command().parse(data.data) + # self.logger.debug("Warp packet: %s", warp_data) + @permissions(UserLevels.ADMIN) def warp(self, name): """Warps you to a player's ship (or player to player).\nSyntax: /warp [player] (to player)""" @@ -38,6 +45,7 @@ def warp(self, name): return self.warp_player_to_player(first_name, second_name) +# THIS IS CURRENTLY BROKEN! @permissions(UserLevels.ADMIN) def warp_ship(self, location): """Warps a player ship to another players ship.\nSyntax: /warp_ship [player] (to player)""" @@ -58,6 +66,20 @@ def warp_ship(self, location): self.protocol.send_chat_message(str(e)) return self.move_player_ship_to_other(first_name, second_name) +#!!!!!!!!!!!!!!!!!!!!!!! + + @permissions(UserLevels.ADMIN) + def outpost(self, name): + """Warps you (or another player) to the outpost.\nSyntax: /outpost [player]""" + if len(name) == 0: + self.warp_player_to_outpost(self.protocol.player.name) + else: + try: + player_name, rest = extract_name(name) + except ValueError as e: + self.protocol.send_chat_message(str(e)) + return + self.warp_player_to_outpost(player_name) def warp_self_to_player(self, name): self.logger.debug("Warp command called by %s to %s", self.protocol.player.name, name) @@ -73,12 +95,14 @@ def warp_player_to_player(self, from_string, to_string): if to_player is not None: from_protocol = self.factory.protocols[from_player.protocol] if from_player is not to_player: + self.logger.debug("target: %s", to_player.uuid) warp_packet = build_packet(Packets.PLAYER_WARP, - warp_command_write(t="WARP_OTHER_SHIP", - player=to_player.org_name.encode('utf-8'))) + warp_command_write(t="WARP_TO", + world_id=to_player.uuid)) else: warp_packet = build_packet(Packets.PLAYER_WARP, - warp_command_write(t='WARP_UP')) + warp_command_write(t="WARP_TO_OWN_SHIP", + world_id=None)) from_protocol.client_protocol.transport.write(warp_packet) if from_string != to_string: self.protocol.send_chat_message("Warped ^yellow;%s^green; to ^yellow;%s^green;." % (from_string, to_string)) @@ -140,3 +164,17 @@ def move_player_ship_to_other(self, from_player, to_player): self.move_player_ship(self.factory.protocols[f.protocol], t.planet.split(":")) self.protocol.send_chat_message("Warp drive engaged. Warping ^yellow;%s^green; to ^yellow;%s^green;." % (from_player, to_player)) + def warp_player_to_outpost(self, player_string): + self.logger.debug("Warp player-to-outpost command called by %s: sending %s to the outpost", self.protocol.player.name, player_string) + player_to_send = self.player_manager.get_logged_in_by_name(player_string) + if player_to_send is not None: + player_protocol = self.factory.protocols[player_to_send.protocol] + warp_packet = build_packet(Packets.PLAYER_WARP, + warp_command_write(t="WARP_TO", + world_id="outpost")) + player_protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warped ^yellow;%s^green; to the outpost." % player_string) + else: + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % player_string) + self.protocol.send_chat_message(self.warp.__doc__) + diff --git a/server.py b/server.py index b89a9f9..9de9910 100644 --- a/server.py +++ b/server.py @@ -148,7 +148,7 @@ def string_received(self, packet): """ if 53 >= packet.id: # DEBUG - print all packet IDs going to client - #logger.info("%s", packet.id) + # logger.info("From Client: %s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) @@ -521,6 +521,8 @@ def string_received(self, packet): :return: None """ try: + # DEBUG - print all packet IDs coming from client + # logger.info("From Server: %s", packet.id) if self.server_protocol.handle_starbound_packets( packet): self.server_protocol.write(packet.original_data) diff --git a/utility_functions.py b/utility_functions.py index 86d11a9..562ec65 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -65,7 +65,7 @@ def move_ship_to_coords(protocol, x, y, z, planet, satellite): ":".join((str(x), str(y), str(z), str(planet), str(satellite)))) x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) warp_packet = build_packet(packets.Packets.WARP_COMMAND, - packets.warp_command_write(t="MOVE_SHIP", x=x, y=y, z=z, + packets.warp_command_write(t="WARP_TO", x=x, y=y, z=z, planet=planet, satellite=satellite, player="".encode('utf-8'))) protocol.client_protocol.transport.write(warp_packet) From e5e1b2cf18868730b33022bea932abbfd997f3e1 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Thu, 8 Jan 2015 02:43:14 -0500 Subject: [PATCH 43/81] small correction --- utility_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utility_functions.py b/utility_functions.py index 562ec65..a8eb265 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -38,7 +38,7 @@ def recursive_dictionary_update(d, u): def build_packet(packet_type, data): """ Convenience method to build packets for sending. - :param packet_type: An integer 1 <= packet_type <= 48 + :param packet_type: An integer 1 <= packet_type <= 53 :param data: Data to send. :return: The build packet. :rtype : str From 47538b916fbee1873598d14385a3d37a048500ec Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 31 Jan 2015 01:45:28 -0500 Subject: [PATCH 44/81] Removed unnecessary plugin (UDP), unnecessary/old folder (test), and fixed client crash on first connect to server (resulting from give item) --- plugins/UDPForwarder/UDPForwarder.py | 32 --- plugins/UDPForwarder/__init__.py | 1 - .../starbound_config_manager.py | 8 +- .../new_player_greeter_plugin.py | 3 +- tests/__init__.py | 0 tests/large_packets.json | 4 - tests/packet_tests.py | 227 ------------------ 7 files changed, 6 insertions(+), 269 deletions(-) delete mode 100644 plugins/UDPForwarder/UDPForwarder.py delete mode 100644 plugins/UDPForwarder/__init__.py delete mode 100644 tests/__init__.py delete mode 100644 tests/large_packets.json delete mode 100644 tests/packet_tests.py diff --git a/plugins/UDPForwarder/UDPForwarder.py b/plugins/UDPForwarder/UDPForwarder.py deleted file mode 100644 index c02579d..0000000 --- a/plugins/UDPForwarder/UDPForwarder.py +++ /dev/null @@ -1,32 +0,0 @@ -from twisted.internet import reactor -from twisted.internet.error import CannotListenError -from twisted.internet.protocol import DatagramProtocol - -from base_plugin import BasePlugin - - -class UDPForwader(BasePlugin): - """Forwards UDP datagrams to the gameserver, mostly used for Valve's Steam style statistis queries""" - name = "udp_forwarder" - auto_activate = True - - def activate(self): - - super(UDPForwader, self).activate() - try: - reactor.listenUDP(self.config.bind_port, UDPProxy(self.config.upstream_hostname, self.config.upstream_port), interface=self.config.bind_address) - self.logger.info("Listening for UDP on port %d" % self.config.bind_port) - except CannotListenError: - self.logger.error( - "Could not listen on UDP port %d. Will continue running, but please note that steam statistics will be unavailable.", - self.factory.config.bind_port - ) - - -class UDPProxy(DatagramProtocol): - def __init__(self,upstream_hostname, upstream_port): - self.upstream_hostname = upstream_hostname - self.upstream_port = upstream_port - - def datagramReceived(self, datagram, addr): - self.transport.write(datagram, (self.upstream_hostname, self.upstream_port)) diff --git a/plugins/UDPForwarder/__init__.py b/plugins/UDPForwarder/__init__.py deleted file mode 100644 index 47aadc5..0000000 --- a/plugins/UDPForwarder/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from UDPForwarder import UDPForwader diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index 96f980d..b82a2a2 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -13,7 +13,7 @@ class StarboundConfigManager(SimpleCommandPlugin): def activate(self): super(StarboundConfigManager, self).activate() try: - configuration_file = FilePath(self.config.starbound_path).child('starbound_server.config') + configuration_file = FilePath(self.config.starbound_path).child('starbound.config') if not configuration_file.exists(): raise FatalPluginError( "Could not open starbound configuration file. Tried path: %s" % configuration_file) @@ -26,10 +26,10 @@ def activate(self): raise FatalPluginError( "Could not parse the starbound configuration file as JSON. Error given from JSON decoder: %s" % str( e)) - if self.config.upstream_port != starbound_config['gamePort']: + if self.config.upstream_port != starbound_config['gameServerPort']: raise FatalPluginError( - "The starbound gamePort option (%d) does not match the config.json upstream_port (%d)." % ( - starbound_config['gamePort'], self.config.upstream_port)) + "The starbound gameServerPort option (%d) does not match the config.json upstream_port (%d)." % ( + starbound_config['gameServerPort'], self.config.upstream_port)) #self._spawn = starbound_config['defaultWorldCoordinate'].split(":") self._spawn = "junk" diff --git a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py index a97e6e3..16f0121 100644 --- a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py +++ b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py @@ -1,3 +1,4 @@ +from twisted.internet import reactor, defer from base_plugin import BasePlugin from utility_functions import give_item_to_player @@ -12,7 +13,7 @@ class NewPlayerGreeter(BasePlugin): def activate(self): super(NewPlayerGreeter, self).activate() - def after_connect_response(self, data): + def after_world_start(self, data): if self.protocol.player is not None and self.protocol.player.logged_in: my_storage = self.protocol.player.storage if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/large_packets.json b/tests/large_packets.json deleted file mode 100644 index 597ff0b..0000000 --- a/tests/large_packets.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "client_connect": "202caf2f18a03c02d5baa3321f01f1970a66769e1c27fe1b2993fb334be580a3b90101d3d1bc89462baa6f872b1d179c749a220e416e6b684d6f72706f726b69616e06676c697463688ce800534242463031000004000000020001000000000000009000000000000000000042544442303100000000000000000187000e0957524c444256312e31000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000172000e0957524c444256312e31000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004646ffffffffffffffff0000003e000000000000004f0000000000000050000000000000005200000000000000540000000000000055000000000000005600000000000000570000000000000058000000000000005a0000000000000063000000000000006400000000000000650000000000000066000000000000006700000000000000680000000000000069000000000000006a000000000000006c000000000000006f00000000000000700000000000000072000000000000007700000000000000780000000000000079000000000000007a000000000000007b000000000000007c000000000000007e000000000000008100000000000000830000000000000183000000000000018800000000000000a700000000000001530000000000000126000000000000008e0000000000000133000000000000009e0000000000000141000000000000001b00000000000001420000000000000150000000000000007f00000000000000b400000000000000ec0000000000000104000000000000015c0000000000000161000000000000014a000000000000012f000000000000017b000000000000018d00000000000000db0000000000000075000000000000014c00000000000000b100000000000000e800000000000000890000000000000131000000000000018a000000000000015b000000000000016b01374c4c00000005010010001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010010001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000054c4c00000007010009001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010009001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000a000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000c04c4c00000004010004001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c04070000021b00000204811b0000021c00000204070000021d00000204070000021e00000204070000021f00000204070000022000000204070000022100000204070000022200000204811b0000022300000204070000022400000204070000022500000204070000022600000204070000022700000204070000022800000204070000022900000204811b0000022a00000204070000022b00000204070000022c00000204070000022d00000204070000022e00000204070000022f00000204070000023000000204811b0000023100000204070000023200000204070000023300000204070000023400000204070000023500000204070000023600000204070000023700000204070000023800000204070000023900000204070000023a00000204070000023b00000204070000023c00000204070000023d00000204070000023e00000204077f0000020a0000020d070000020b0000020d070000020c0000020d070000020d0000020d070000020e0000020d070000020f0000020d07000002100000020d07000002110000020d07000002120000020d07000002130000020d07000002140000020d07000002150000020d07000002160000020d07000002170000020d07000002180000020d07000002190000020d070000021a0000020d070000021b0000020d070000021c0000020d0700000000000000de4c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010017000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000801001c001160789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001c001260789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001c001360789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001c001460789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001d000060789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618000000000000010d4c4c0000000401000d001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000002f020005001309789c63000000010001020005001409789c63000000010001020005001509789c63000000010001020005001609789c63000000010001020005001709789c63000000010001020006000009789c63000000010001020006000109789c63000000010001020006000209789c63000000010001020006000309789c63000000010001020006000409789c63000000010001020006000509789c63000000010001020006000609789c63000000010001020006000709789c63000000010001020006000809789c63000000010001020006000909789c63000000010001020006000a09789c63000000010001020006000b09789c63000000010001020006000c09789c63000000010001020006000d09789c63000000010001020006000e09789c63000000010001020006000f09789c63000000010001020006001009789c63000000010001020006001109789c63000000010001020006001209789c63000000010001020006001309789c63000000010001020006001409789c63000000010001020006001509789c63000000010001020006001609789c63000000010001020006001709789c63000000010001020006001809789c63000000010001020007000009789c63000000010001020007000109789c63000000010001020007000209789c63000000010001020007000000000000001d4c4c09789c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010006000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0001020011001609789c63000000010001020011001709789c63000000010001020011001809789c63000000010001020012000009789c63000000010001020012000109789c63000000010001020012000209789c63000000010001020012000309789c63000000010001020012000409789c63000000010001020012000509789c63000000010001020012000609789c63000000010001020012000709789c63000000010001020012000809789c63000000010001020012000909789c63000000010001020012000a09789c63000000010001020012000b09789c63000000010001020012000c09789c63000000010001020012000d09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000201000a000f811f789cedd4410a83301005d040361ec63b66e1b58558c552eca222d838ca0b8461de2e339f0c7dae35a5d75dcb7c968231c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6d772eaca96731dffcc711e8f1fcff21d997f0f369523dc72c571f8dd97cf601b7180f884e733f31d87e5fbb97c97ed60f93e3fdfdf1f5eb8a5e17d9e0040d344a101000a0010816b789ceddb410a83301005d0809b1ea677cca2d716522c9582a868ab76340f14f10926137f36213eee4dbae5544a77bcce949ad276972ab8bfcf3d277c318e13b603f853bc7c638c31c618638c31c618638c31c618638c31c618638c8fe189ed4b7635e14bb07ce32bb37ce320dc3f7d8b7ce3e03c91d885bcee8780795ef89252f27839a3fc55939bf0bae2ebe0df06365cbe77e1b3e4fbc839bf659501821c27df7f982471fabd2fe721b7731ca7df78c8f22ddf41b9f20c1e3eb03804c7c9c95939ceb7ac9ae30402638c31c618638c31c6cb390f7976597c1dc7a9320ac759c9c11863bc869f3e466469000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000d00108169789cedd5db0a82401405d0015ffa98fe711efaedc02e68505034ead471660921adc0ced96ef1741cc631a5eb673add8efb09638c71704e87fcccc378c638e5128e33771c2e4b50b0bfad669c8bace3f9fbe32f13c66f5fff7106c418638c31c618638c31c618638cbbe65cc471e6c678fbc6963d0d1e121ca255f3af932ce338ebe076aa59d6d89afd5e5bfbb26be7575e1c6ccde523f3a704bfe638ebe0766ef15ee7fe43267106dc2b6f52b6aeabb949b071e6de2bd7ac661fb5ef634bc1b6b3656309f671d3ba66c1568a4a825835312e61d5c418638c31c618638c31c61863dc1e5f00b36d4a5e01000d00117d789cedce310a80300c05d040170fe31d3b786d216a2d8882e0e020f802e193970c99c69219b1768fad5a608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c5fe018ea994bce6d57afd7dfe29bbf31c6f837dce783f7c018638c31c618638c31c618638c31c618638c31c6f8112fcba18ae10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c1b37b3be7133eb1b638c31c618638c31c61863fc0d5f01fd6df61c01000b00128115789cedd23b0a80301045d1808d8b718f166e5b881f6c2c2cd414fa380361e05419b8d3d0d55acafa8eb5cdbe30c618638c31c618638c31c618638c31c61863fc844b3f9eb9abf3a73e88f10bd637fe175f142b641cc1fac6c9ac6f9cccfac6ff62c5e264d6374e667de364d6374e667de364d6374ee67b7d37e1ef1c8fe359df18638c31c618638c31c618638c71135e000440ac1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000a000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010014000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000884c4c0000000301000a00118109789cedd23b0a80301045d180db718f166e3b103f58052cec1ee3190803a7cac0ddd7658cd6cef7ac6bee8531c618638c31c618638c31c618638c31c618638c31c618e31abccddcc33e88ffca2f697ee39c73701d96260e6569e25096260e6569e25096260e6569e25096260e6569e25096262ec1d2c418638c31c618638c31c618635c8f0f5ec0ea7c01000a00128103789cedd73b0a80400c40c1c0361ec63b5a786d217eb0b1b2126298c012986ee13559e7911971bc7b9d732d8c31c618638c31c618638c31c618638c31c618638c31c618638c31c6c538a6e5c923b78fb9cee7717bd637c62fac6fdc99f58d3bb3be7167d6372ecaee4bdc99f58d31c618638c31c618638c31c61863fc2fde01f066b1f801000a00138111789cedd63b0a80301005c0808d87f18e165e5b881fb4b0122ce4196761599832fb16320d5dada5ac7d8cadf68131c6389c4b3f5eb9abf38739e7617104e74453be31be6197865b66f9c6f80d760db865966f8c4fcef95735c6392bfe35e704a231ce5931c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61863fc9c17a4347d3c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010017000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000002a4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010002000960789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000dd4c4c0000000401000e000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e00108210789cedd55d6a8350148551212f1d43c6d039de874e5b306d4d0249488b70ccdd9a2588b884e3df877e7d1ea66918bed7f3e667f9dd608c310ee7e1a3ddf2611a31c618afcb6dde9f8ffecbc799dbed90cbd02b0f182fe0b622e7dc25de0f4b1387b2347128fbcfe3505e334dc5e2de6996ccce792638823b7c07158b43d3542cde569a8ac5bb0822fe02f17bf0e5e8599e73bbe7f12f7e1882b7cead82979cb273b14f66f77e0d788fbc66b1cb38e799e0cdf24b8bd537eecd00000000000000ce4c4c31c618638c3fb8015324dafc010010000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000e001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cfc010008001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010008001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010013000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000be4c4c9c6300000001000102000a000b09789c6300000001000102000a000c09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000901000e000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000015f4c4c000309789c63000000010001020007000409789c63000000010001020007000509789c63000000010001020007000609789c63000000010001020007000709789c63000000010001020007000809789c63000000010001020007000909789c63000000010001020007000a09789c63000000010001020007000b09789c63000000010001020007000c09789c63000000010001020007000d09789c63000000010001020007000e09789c63000000010001020007000f09789c63000000010001020007001009789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ce2d2ebdf1308813325f00d77c9fa98f6bdb5d7fe1cef751c3fe1a5e7ed02d7790ece6169e2a22c4d8c31c618638c31c618638c31c618638c31c618638c31c618638c31c6189f78e9f9ca678331aef3781ccffac6c92c4d9cccfac6c9ac6f9ccb6dee429e8e90bf709d7be37fe5b162f58defc6631387f904df8b158b9359df3899f58d9359df3899f58d93b978df4f901de60e01000c00128139789cedda410a83301040d1809b1ec63bbae8b50bb196bae8428818613a798204de2a307f7679ce53ada5bcffefb17d9f03e3162e8fe597a7fa3ae638f7c6b885f58dff8b158b33b3be7166d637ceccfa1e95cf4d7e0c8e331daceffe1c673a6639ea2cb1be5b38ce74f0d590e35c10e30bac6f8c31c618638c31c618638c31c618638c31c6f87e8ef37cc98b29dc9fe3c4a66f8cbbb26dc09959df18ef6c1b7066d637ceccfac69959df18ef6c1b7066d677085e019f82488000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010005000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000224c4c18638c31c6f8830fc493e2f9010001000760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000000010000000000af2e000004410000040faf24000001ac4403800044014000845a000004410000040f844600000000000000000000000000000000000000000000000000000000000000000042a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000494c4c31c618638c3fb8015324dafc010005000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000a001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000000040100100011811f789cedd4410a83301040d1816c728b5ec03b76d16b0ba94aa14410845619f40542e09145203f790da5b588697e96792c0bc618638c31c618638c31c618638cefc1519f3d9736e2641cf5d1719e7cd273babbc47bfa5eed0ebcc9595e1adee46fdff9f2398efff243a4bb4bbca7ef6407c4f807d637be32eb1b638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3f3f80d0f509efb010010001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010003001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010003001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010004000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000004b49490000000017000000000000014b01001100130000000000000181010012000200000000000000d9010012000b0000000000000182010012000e00000000000001650100120011000000000000011001001200150000000000000160010013000000000000000001300100130006000000000000001a010013000c00000000000000ab010013000f00000000000000470100130012000000000000011b010013001500000000000000d20100140000000000000000005e01001400060000000000000011010014000c000000000000011a010014000f000000000000008b010014001100000000000000cf01001400150000000000000174010015000100000000000000af01001500070000000000000176010015000d000000000000009001001500130000000000000122010015001700000000000000ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00010001020008001609789c63000000010001020008001709789c63000000010001020008001809789c63000000010001020009000009789c63000000010001020009000109789c63000000010001020009000209789c63000000010001020009000309789c63000000010001020009000409789c63000000010001020009000509789c63000000010001020009000609789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000f001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000f001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010010000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c030011696e76696e6369626c65506c61796572730301086675656c2e6d6178048f500a6675656c2e6c6576656c04892a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010017000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001f020009000709789c63000000010001020009000809789c63000000010001020009000909789c63000000010001020009000a09789c63000000010001020009000b09789c63000000010001020009000c09789c63000000010001020009000d09789c63000000010001020009000e09789c63000000010001020009000f09789c63000000010001020009001024789c636ad66116622dc92f4ace6067606060d46160606262644413b585880200aade065a020009001109789c6300000001000102000900121f789c636cd66116622dc92f4ace6067606060d4666060f2616404003374036e02000900131f789c636cd66116622dc92f4ace6067606060346760604a606400003403038d020009001409789c63000000010001020009001509789c63000000010001020009001609789c63000000010001020009001709789c63000000010001020009001809789c6300000001000102000a000009789c6300000001000102000a000109789c6300000001000102000a000209789c6300000001000102000a000309789c6300000001000102000a000409789c6300000001000102000a000509789c6300000001000102000a000609789c6300000001000102000a000709789c6300000001000102000a000809789c6300000001000102000a000909789c6300000001000102000a000a097800000000000000ed4c4c09789c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c020a07000002190000020a070000021a0000020a070000021b0000020a811b0000021c0000020a070000021d0000020a070000021e0000020a070000021f0000020a07000002200000020a07000002210000020a07000002220000020a811b000002230000020a07000002240000020a07000002250000020a07000002260000020a07000002270000020a07000002280000020a07000002290000020a811b0000022a0000020a070000022b0000020a070000022c0000020a070000022d0000020a070000022e0000020a070000022f0000020a070000020b00000209070000020c00000209070000020d00000209070000020e00000209070000020f00000209070000021000000209070000021100000209811b0000021200000209070000021300000209070000021400000209811b0000021500000209070000021600000209070000021700000209070000021800000209070000021900000209070000021a00000209070000021b00000209811b0000021c00000209070000021d00000209070000021e00000209070000021f00000209070000022000000209070000022100000209070000022200000209811b0000022300000209070000022400000209070000022500000209070000022600000209070000022700000209070000022800000209070000022900000209811b0000022a0000000000000001734c4c00000004010008000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010018000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010018000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000d01001e000560789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000001294c4c00000029020012000e09789c63000000010001020012000f09789c6300000001000102001200101d789c636cd66116622dc92f4ace60676060607204624e1646003353034502001200111d789c636cd66116622dc92f4ace6067606060f205620346460034160375020012001209789c63000000010001020012001309789c63000000010001020012001409789c63000000010001020012001509789c63000000010001020012001609789c63000000010001020012001709789c63000000010001020012001809789c63000000010001020013000009789c63000000010001020013000109789c63000000010001020013000209789c63000000010001020013000309789c63000000010001020013000409789c63000000010001020013000509789c63000000010001020013000609789c63000000010001020013000709789c63000000010001020013000809789c63000000010001020013000909789c63000000010001020013000a09789c63000000010001020013000b09789c63000000010001020013000c09789c63000000010001020013000d09789c63000000010001020013000e09789c63000000010001020013000f09789c6300000001000102001300102a789c636ed66116622dc92f4ace6067606060ca076239460634d1342066606444134d87880200820f0a3f0200130011000000000000002c4c4c31c618638c3fb8015324dafc01000b000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000003302000a000d09789c6300000001000102000a000e09789c6300000001000102000a000f1d789c636cd66116622dc92f4ace60676060600c07e27f8c8c0036ba044b02000a001009789c6300000001000102000a001109789c6300000001000102000a00121f789c636cd66116622dc92f4ace6067606060746060600a626404003419038902000a00131f789c636cd66116622dc92f4ace60676060600c676060ca62640000350103b702000a001409789c6300000001000102000a001509789c6300000001000102000a001609789c6300000001000102000a001709789c6300000001000102000a001809789c6300000001000102000b000009789c6300000001000102000b000109789c6300000001000102000b000209789c6300000001000102000b000309789c6300000001000102000b000409789c6300000001000102000b000509789c6300000001000102000b000609789c6300000001000102000b000709789c6300000001000102000b000809789c6300000001000102000b000909789c6300000001000102000b000a09789c6300000001000102000b000b09789c6300000001000102000b000c09789c6300000001000102000b000d09789c6300000001000102000b000e09789c6300000001000102000b000f22789c636ad66116622dc92f4ace60676060602c07e23f8c0000000000000192494900000000170000000000000112010004001000000000000000a101000400130000000000000003010004001700000000000000aa0100050007000000000000001f010005000d00000000000001180100050011000000000000003f010005001500000000000001360100050019000000000000009d01000600050000000000000096010006000b00000000000000c7010006001200000000000001320100060015000000000000006b010006001900000000000000f10100070007000000000000004d010007000d0000000000000044010007001200000000000000c2010007001500000000000001690100070019000000000000009b0100080006000000000000012c010008000c000000000000002e0100080010000000000000016d0100080012000000000000017a010009000000000000000000e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c0534ed53b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ce2d2ebdf1308813325f00d77c9fa98f6bdb5d7fe1cef751c3fe1a5e7ed02d7790ece6169e2a22c4d8c31c618638c31c618638c31c618638c31c618638c31c618638c31c6189f78e9f9ca678331aef3781ccffac6c92c4d9cccfac6c9ac6f9ccb6dee429e8e90bf709d7be37fe5b162f58defc6631387f904df8b158b9359df3899f58d9359df3899f58d93b978df4f901de60e01000c00128139789cedda410a83301040d1809b1ec63bbae8b50bb196bae8428818613a798204de2a307f7679ce53ada5bcffefb17d9f03e3162e8fe597a7fa3ae638f7c6b885f58dff8b158b33b3be7166d637ceccfa1e95cf4d7e0c8e331daceffe1c673a6639ea2cb1be5b38ce74f0d590e35c10e30bac6f8c31c618638c31c618638c31c618638c31c6f87e8ef37cc98b29dc9fe3c4a66f8cbbb26dc09959df18ef6c1b7066d637ceccfac69959df18ef6c1b7066d677085e019f82488000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010000001160789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001260789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001360789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001460789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000801001d000460789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000760789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61800000000000001164c4c0000003302000a000d09789c6300000001000102000a000e09789c6300000001000102000a000f1d789c636cd66116622dc92f4ace60676060600c07e27f8c8c0036ba044b02000a001009789c6300000001000102000a001109789c6300000001000102000a00121f789c636cd66116622dc92f4ace6067606060746060600a626404003419038902000a00131f789c636cd66116622dc92f4ace60676060600c676060ca62640000350103b702000a001409789c6300000001000102000a001509789c6300000001000102000a001609789c6300000001000102000a001709789c6300000001000102000a001809789c6300000001000102000b000009789c6300000001000102000b000109789c6300000001000102000b000209789c6300000001000102000b000309789c6300000001000102000b000409789c6300000001000102000b000509789c6300000001000102000b000609789c6300000001000102000b000709789c6300000001000102000b000809789c6300000001000102000b000909789c6300000001000102000b000a09789c6300000001000102000b000b09789c6300000001000102000b000c09789c6300000001000102000b000d09789c6300000001000102000b000e09789c6300000001000102000b000f22789c636ad66116622dc92f4ace60676060602c07e23f8c00000000000001234c4c31c618638c3fb8015324dafc01000d000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000d00108169789cedd5db0a82401405d0015ffa98fe711efaedc02e68505034ead471660921adc0ced96ef1741cc631a5eb673add8efb09638c71704e87fcccc378c638e5128e33771c2e4b50b0bfad669c8bace3f9fbe32f13c66f5fff7106c418638c31c618638c31c618638cbbe65cc471e6c678fbc6963d0d1e121ca255f3af932ce338ebe076aa59d6d89afd5e5bfbb26be7575e1c6ccde523f3a704bfe638ebe0766ef15ee7fe43267106dc2b6f52b6aeabb949b071e6de2bd7ac661fb5ef634bc1b6b3656309f671d3ba66c1568a4a825835312e61d5c418638c31c618638c31c61863dc1e5f00b36d4a5e01000d00117d789cedce310a80300c05d040170fe31d3b786d216a2d8882e0e020f802e193970c99c69219b1768fad5a608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c5fe018ea994bce6d57afd7dfe29bbf31c6f837dce783f7c018638c31c618638c31c618638c31c618638c31c6f8112fcba18ae10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000701001d001060789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001d001160789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000060789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000160789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000260789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000001284c4c0206070000021400000206811b0000021500000206070000021600000206070000021700000206070000021800000206070000021900000206070000021a00000206070000021b00000206811b0000021c00000206070000021d00000206070000021e00000206070000021f00000206070000022000000206070000022100000206070000022200000206811b0000022300000206070000022400000206070000022500000206070000022600000206070000022700000206070000022800000206070000022900000206811b0000022a00000206070000022b00000206070000022c00000206070000022d00000206070000022e00000206070000022f00000206070000023000000206811b0000023100000206070000023200000206070000023300000206070000023400000206070000023500000206070000023600000206070000023700000206070000023800000206070000023900000206070000023a00000206070000023b00000206070000023c00000206070000023d00000206070000020b00000205070000020c00000205070000020d00000205070000020e00000205070000020f00000205070000021000000205070000021100000205811b0000021200000205070000021300000205070000021400000205811b000002150000020507000002160000020507000002170000000000000000014e4c4c00000004010005001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000028020007001109789c63000000010001020007001209789c63000000010001020007001309789c63000000010001020007001409789c63000000010001020007001509789c63000000010001020007001609789c63000000010001020007001709789c63000000010001020007001809789c63000000010001020008000009789c63000000010001020008000109789c63000000010001020008000209789c63000000010001020008000309789c63000000010001020008000409789c63000000010001020008000509789c63000000010001020008000609789c63000000010001020008000709789c63000000010001020008000809789c63000000010001020008000909789c63000000010001020008000a09789c63000000010001020008000b09789c63000000010001020008000c09789c63000000010001020008000d09789c63000000010001020008000e09789c63000000010001020008000f09789c63000000010001020008001027789c636ad66116622dc92f4ace6067606060646460609261644013e5018ab231320200a727061b02000800111f789c636cd66116622dc92f4ace6067606060e4606060b26064040032430337020008001209789c63000000010001020008001309789c63000000010001020008001409789c63000000010001020008001509789c63000000000000000000274c4c00000002010009000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000301000b000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000701000d000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000ea4c4c00000005010007000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000700108164789ceddc4b0a83300045d180932ea67b74d06d17d20f2dc50e042525e9f308221c10315c242a7a394fb596725f5f9bc7f2dc60dc9bcb695ef254af9b799cd3c178c1fac6bdb94983eb6996f973c8dd3cc050e13fe45d217f1a5ce5f7defac6bdb8df1559dff8f7ac6f9cccfac6c9ac6f9cccfac6c9ac6f9cccfac6c9ac6f9cccdb5ee4d4fac5a525eb1beb7bb411c423f3d6bed71a343fc1a3b1be7132eb1b27b3e7273899f58d9359df3899f58d93d9fd254e667de364363f000000000000009a4c4c0000000401000b000f8104789cedd24b0a80300c05c040371ec63b76e1b585fa41110fd00f6502e191d905deb6a65222ce7de29a3b30c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3793896fce754f6ca3ccef3787aeed06f8c9bf17be7aff6b5799ce7f1f4dca1df18638c31c618e3767c001390ad1901000b00108135789cedd8bb0a83300086d1804b1fa6ef98a1af5d482fb108999ac192fe1c41a2c70c2a1f1972bb6ead95f23a6b1f9e47bb977e8d31c618638c31c618638c31c618638c31c618638cff943f4f77f905aff3f1f8a420d679137d7ffd6397af0ae7b0d830c6f814ae235b1e7110eb1b27b3be71040b1927b3be7132eb1b27f34cdfe532ccdedeb331c6182fcefbfdc17dc07859ae230b1907b1be7132eb1b27b3be7132eb1b27f34cdfebece4608c319ee10780d0b3dd01000b0011813f789cedd24d0ac2301485d182db718f0edcb6507f70240a7650bdb939851038a3d7bcef7c3cacebb2dccef3ba7f8f0be3583ebdf2256c408cf58df15bd637aee00f216fe39cdfc13dbc679a42c6a169ee3960ce0be2df6e3e274d7dcfca73a4a9ef01599a11ef9d33600e4b73409e630dd29c95e377294d1cdab734f1bf795b55d2c463b1347133eb1b37b3be7133eb1b37b3be7133eb1b37b3be7133eb1b37b3be7133eb000000000000000f4c4c0000000501001a001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a001360789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001a001460789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001a001560789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001a001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000001854c4c00000003010013000f810f789cedd23b0a80301404c0401a0fe31d2dbcb6103f68612582c546261016a6dcb7f3585b2b65fb67ecef088c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ee6324c77ae6de988afb838a7581cc1718b7dc73fdf775cdfdd71ce2db17d7fcf39b73488df5f07dbf78d738ac511bc023cd97a4e0100130010811e789cedd2310ac2501045d10f695c8c7b4ce1b685af09361616c1c2bce71918064e35c5bd5d9739c778eeeb6cb31f8c31c618638c31c618638c31c618638c31c618638c31fe1f1e97f59d97793fd583187fc1fac659fca15821e30ad637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637fe351f6b50b1388bf55dcf0f3bb69d7601001300118109789cedd23b0a80400c40c1856d3c8c77b4f0da42fc606321586ee2044260aa146f9d7b446bc7dee79ceb64e5362d4feeb1bdf3387f63fc85f58d73b1627165d637cec58ac59559df38172b1657667de35cac585c99f58d4bb0627165d637c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6ffe11ddb70bac2000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010016000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000004a4c4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000447a000000000000000000000000000000000000000000000000000000000000000000b97c2fea48417a810000a97f00000f0000022c0000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002210000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002150000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002390000020411617065786361707461696e7363686169720007010b756e627265616b61626c65030000000231000002040c6170657873686970646f6f720007000000020e000002040a74656c65706f727465720007010b756e627265616b61626c6503000000022d000002060b7465636873746174696f6e0007010b756e627265616b61626c65030000000218000002060e61706578736869706c6f636b65720007030d7472656173757265506f6f6c7306010515676c69746368537461727465725472656173757265056c6576656c04020b756e627265616b61626c65030000000235000000000000000001754c4c31c618638c3fb8015324dafc010016000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010004000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010013000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010007000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000018f4c4c0000003902001a000c09789c6300000001000102001a000d09789c6300000001000102001a000e09789c6300000001000102001a000f09789c6300000001000102001a001009789c6300000001000102001a001109789c6300000001000102001a001209789c6300000001000102001a001309789c6300000001000102001a001409789c6300000001000102001b000009789c6300000001000102001b000109789c6300000001000102001b000209789c6300000001000102001b000309789c6300000001000102001b000409789c6300000001000102001b000509789c6300000001000102001b000609789c6300000001000102001b000709789c6300000001000102001b000809789c6300000001000102001b000909789c6300000001000102001b000a09789c6300000001000102001b000b09789c6300000001000102001b000c09789c6300000001000102001b000d09789c6300000001000102001b000e09789c6300000001000102001b000f09789c6300000001000102001b001009789c6300000001000102001b001109789c6300000001000102001b001209789c6300000001000102001b001309789c6300000001000102001c000009789c6300000001000102001c000109789c6300000001000102001c000209789c6300000001000102001c000309789c6300000001000102001c00000000000001454c4c00000009010011001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000b24c4c0000000401000b0013810b789cedd23b0a80400c45d181695c8c7bb470db42fc60a39dddc3398110385de0ae73af6aedd8fb9c731d8c31c618638c31c6e3709b9627f7da3e73ce3b183f58df38b42a69e250fe59df39efc473d58b5b18e75495c339f9c4734ec8fac618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c87e21d23a6988601000b001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010002000360789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000460789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000760789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61800000000000000534c4c00000023020014000509789c63000000010001020014000609789c63000000010001020014000709789c63000000010001020014000809789c63000000010001020014000909789c63000000010001020014000a09789c63000000010001020014000b09789c63000000010001020014000c09789c63000000010001020014000d09789c63000000010001020014000e09789c63000000010001020014000f09789c6300000001000102001400101d789c636cd66116622dc92f4ace60676060606a076206464600351c037f020014001109789c63000000010001020014001209789c63000000010001020014001309789c63000000010001020014001409789c63000000010001020014001509789c63000000010001020014001609789c63000000010001020014001709789c63000000010001020014001809789c63000000010001020015000009789c63000000010001020015000109789c63000000010001020015000209789c63000000010001020015000309789c63000000010001020015000409789c63000000010001020015000509789c63000000010001020015000609789c63000000010001020015000709789c63000000010001020015000809789c63000000010001020015000909789c63000000010001020015000a09789c63000000010001020015000b09789c630000000100000000000000554c4c638c31c618e30f6eca34eafb010002000860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0001020015000c09789c63000000010001020015000d09789c63000000010001020015000e09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010016000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010012000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000f73789cedd2310a80301004c083343e267fb4c8b7854b2236e91502cec2b130ed6dab253362dc533377618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f85b8ee35cb9e4f51bdee70dd8bedfe70e9caabac201001200108159789cedd84d0a83301446d18050ba86aea17b74d06d17d21f1cb4410a81809ff508229c81a8ef46c4db75aab594e7be1c5edbfb90c3e53c7ff354efb887736689f53d9e7366299fb5e9cced75ffe494078b23b85cca692db63e9e9b730fe495ebeee3ace78d37e125aeb693bdf2e75d5a0d3828cdccd5103934acef4d1649e4880fcd399dec95736689f5adef63714e27f19c3334ac6f21e3a8aa848cc7734e6cfe68e3f19c13b2772c1ecf8ac5ffccc3bf0a848c43599a18638c31c618638c31c618638c31c618638c31c618638c31c618631cc80f310beafd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001602001000111d789c636cd66116622dc92f4ace60676060606204621d46060031f50324020010001209789c63000000010001020010001309789c63000000010001020010001409789c63000000010001020010001509789c63000000010001020010001609789c63000000010001020010001709789c63000000010001020010001809789c63000000010001020011000039789c636cd661d117e44d2acdcb4e2d2ac82f2e492d3262626770e1b22ab06f9f50c3000487e6868639372c9d7548eb6e0288cf000090840f60020011000109789c63000000010001020011000209789c63000000010001020011000309789c63000000010001020011000409789c63000000010001020011000509789c63000000010001020011000609789c63000000010001020011000709789c63000000010001020011000809789c63000000010001020011000909789c63000000010001020011000a09789c63000000010001020011000b09789c63000000010001020011000c09789c63000000010001020011000d09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000701000a000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000006d4c4c000309789c63000000010001020017000409789c63000000010001020017000509789c63000000010001020017000609789c63000000010001020017000709789c63000000010001020017000809789c63000000010001020017000909789c63000000010001020017000a09789c63000000010001020017000b09789c63000000010001020017000c09789c63000000010001020017000d09789c63000000010001020017000e09789c63000000010001020017000f09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010007000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010002000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010014000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001434c4c0000000601001b000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001464c4c0000000801000b001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000b001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000c000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000011d4c4c638c31c618e30f6eca34eafb01001b001160789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010002001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010002001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010018000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010017001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010017001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010018000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c1d42d6377e19eb1bef9973fa963daee79c90658feb39a758d9e37ace491347704e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9acbf90462c5a64600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000005010012001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010012001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6000000000000007f4c4c00000028020007001109789c63000000010001020007001209789c63000000010001020007001309789c63000000010001020007001409789c63000000010001020007001509789c63000000010001020007001609789c63000000010001020007001709789c63000000010001020007001809789c63000000010001020008000009789c63000000010001020008000109789c63000000010001020008000209789c63000000010001020008000309789c63000000010001020008000409789c63000000010001020008000509789c63000000010001020008000609789c63000000010001020008000709789c63000000010001020008000809789c63000000010001020008000909789c63000000010001020008000a09789c63000000010001020008000b09789c63000000010001020008000c09789c63000000010001020008000d09789c63000000010001020008000e09789c63000000010001020008000f09789c63000000010001020008001027789c636ad66116622dc92f4ace6067606060e4616060626364441365048aca30320000a64b061b02000800111f789c636cd66116622dc92f4ace6067606060e4606060b26064040032430337020008001209789c63000000010001020008001309789c63000000010001020008001409789c63000000010001020008001509789c63000000000000000000e64c4c00000029020012000e09789c63000000010001020012000f09789c6300000001000102001200101d789c636cd66116622dc92f4ace60676060607204624e1646003353034502001200111d789c636cd66116622dc92f4ace6067606060f205620346460034160375020012001209789c63000000010001020012001309789c63000000010001020012001409789c63000000010001020012001509789c63000000010001020012001609789c63000000010001020012001709789c63000000010001020012001809789c63000000010001020013000009789c63000000010001020013000109789c63000000010001020013000209789c63000000010001020013000309789c63000000010001020013000409789c63000000010001020013000509789c63000000010001020013000609789c63000000010001020013000709789c63000000010001020013000809789c63000000010001020013000909789c63000000010001020013000a09789c63000000010001020013000b09789c63000000010001020013000c09789c63000000010001020013000d09789c63000000010001020013000e09789c63000000010001020013000f09789c63000000010001020013001028789c636ed66116622dc92f4ace60676060604a076206464634d134aca2f9402cc7c800007bb30a3f0200130011097800000000000000d14c4c00000004010016000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010006001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006001760789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010006001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000023020014000509789c63000000010001020014000609789c63000000010001020014000709789c63000000010001020014000809789c63000000010001020014000909789c63000000010001020014000a09789c63000000010001020014000b09789c63000000010001020014000c09789c63000000010001020014000d09789c63000000010001020014000e09789c63000000010001020014000f09789c6300000001000102001400101d789c636cd66116622dc92f4ace60676060606a076206464600351c037f020014001109789c63000000010001020014001209789c63000000010001020014001309789c63000000010001020014001409789c63000000010001020014001509789c63000000010001020014001609789c63000000010001020014001709789c63000000010001020014001809789c63000000010001020015000009789c63000000010001020015000109789c63000000010001020015000209789c63000000010001020015000309789c63000000010001020015000409789c63000000010001020015000509789c63000000010001020015000609789c63000000010001020015000709789c63000000010001020015000809789c63000000010001020015000909789c63000000010001020015000a09789c63000000010001020015000b09789c630000000100000000000001404c4c31c618638c3fb8015324dafc01000a000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000801000c001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000c001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000d000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000ef4c4c0000003302000a000d09789c6300000001000102000a000e09789c6300000001000102000a000f1d789c636cd66116622dc92f4ace60676060600c07e27f8c8c0036ba044b02000a001009789c6300000001000102000a001109789c6300000001000102000a00121f789c636cd66116622dc92f4ace6067606060746060600a626404003419038902000a00131f789c636cd66116622dc92f4ace60676060600c676060ca62640000350103b702000a001409789c6300000001000102000a001509789c6300000001000102000a001609789c6300000001000102000a001709789c6300000001000102000a001809789c6300000001000102000b000009789c6300000001000102000b000109789c6300000001000102000b000209789c6300000001000102000b000309789c6300000001000102000b000409789c6300000001000102000b000509789c6300000001000102000b000609789c6300000001000102000b000709789c6300000001000102000b000809789c6300000001000102000b000909789c6300000001000102000b000a09789c6300000001000102000b000b09789c6300000001000102000b000c09789c6300000001000102000b000d09789c6300000001000102000b000e09789c6300000001000102000b000f22789c636ad66116622dc92f4ace60676060602c07e23f8c000000000000009e4c4c0000000301000b000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0207070000020e00000207070000020f00000207070000021000000207070000021100000207811b0000021200000207070000021300000207070000021400000207811b0000021500000207070000021600000207070000021700000207070000021800000207070000021900000207070000021a00000207070000021b00000207811b0000021c00000207070000021d00000207070000021e00000207070000021f00000207070000022000000207070000022100000207070000022200000207811b0000022300000207070000022400000207070000022500000207070000022600000207070000022700000207070000022800000207070000022900000207811b0000022a00000207070000022b00000207070000022c00000207070000022d00000207070000022e00000207070000022f00000207070000023000000207811b0000023100000207070000023200000207070000023300000207070000023400000207070000023500000207070000023600000207070000023700000207070000023800000207070000023900000207070000023a00000207070000023b00000207070000020b00000206070000020c00000206070000020d00000206070000020e00000206070000020f00000206070000021000000206070000021100000206811b000002120000020607000002130000000000000000003e4c4c0000000401000e00118170789cedda410ac2301445d140272ec63d3a70db42d522054752fdd4d7cf2984c01985e40e12e8f53ccdf3188ff19a9edf3261bcf2ad17e76c2c8ee09c34f58deb39274d7de37ace4953dfb89e73d2d437aee79c34f58deb39274d7de37ace4953dfb89e73d2d437aee79c34f58d7fe471babcf3b4747254ced9581cc13969eadbc9ef124433ceaa2a87731ad4b7beeb8bcd59f7ce9c93a6bef55dcf396936ecdbe3e3ef9c93a6bef55dcf39696ee79c878d06f197ac6fdc998f7c3fc1f813eb1b77667de3ceac6fdc99f58d3bb3be7167d637eeccfac69d59dfb833eb1bb7e086ff5761bcb2be7167dedef71d3e457b2f01000e00127e789cedd2310ac020104541c1ebe4965e5b301ad2a44f21df11d685e9165ebbea18a5cc79d77acfc27843ee7ff03ee760fc617de30359df3899f58d9359df3899f58d9359df3899f58d9359df3899f58d9359df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3f9ddf90fdab401000e001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e001460789cedc8c10900200c04c100000000000001204c4c0000000901000f001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000284c4c00000003010001001460789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010015001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010015001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000201000a000f811f789cedd4410a83301005d040361ec63b66e1b58558c552eca222d838ca0b8461de2e339f0c7dae35a5d75dcb7c968231c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6d772eaca96731dffcc711e8f1fcff21d997f0f369523dc72c571f8dd97cf601b7180f884e733f31d87e5fbb97c97ed60f93e3fdfdf1f5eb8a5e17d9e0040d344a101000a0010816b789ceddb410a83301005d0809b1ea677cca2d716522c9582a868ab76340f14f10926137f36213eee4dbae5544a77bcce949ad276972ab8bfcf3d277c318e13b603f853bc7c638c31c618638c31c618638c31c618638c31c618638c8fe189ed4b7635e14bb07ce32bb37ce320dc3f7d8b7ce3e03c91d885bcee8780795ef89252f27839a3fc55939bf0bae2ebe0df06365cbe77e1b3e4fbc839bf659501821c27df7f982471fabd2fe721b7731ca7df78c8f22ddf41b9f20c1e3eb03804c7c9c95939ceb7ac9ae30402638c31c618638c31c6cb390f7976597c1dc7a9320ac759c9c11863bc869f3e466469000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013000f810f789cedd23b0a80301404c0401a0fe31d2dbcb6103f68612582c546261016a6dcb7f3585b2b65fb67ecef088c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ee6324c77ae6de988afb838a7581cc1718b7dc73fdf775cdfdd71ce2db17d7fcf39b73488df5f07dbf78d738ac511bc023cd97a4e0100130010811e789cedd2310ac2501045d10f695c8c7b4ce1b685af09361616c1c2bce71918064e35c5bd5d9739c778eeeb6cb31f8c31c618638c31c618638c31c618638c31c618638c31fe1f1e97f59d97793fd583187fc1fac659fca15821e30ad637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637fe351f6b50b1388bf55dcf0f3bb69d7601001300118109789cedd23b0a80400c40c1856d3c8c77b4f0da42fc606321586ee2044260aa146f9d7b446bc7dee79ceb64e5362d4feeb1bdf3387f63fc85f58d73b1627165d637cec58ac59559df38172b1657667de35cac585c99f58d4bb0627165d637c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6ffe11ddb70bac2000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010014000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000019020011000e09789c63000000010001020011000f09789c6300000001000102001100108272789c8d92bf4b033114c7efae3fe295aaad388ae82814b505455c14ece0220eae22e472b94b68ee722439aa9ba548ff00177114fa1f38ab140417ff1047271d7d77d2a120878107f9269f97f723af3a6c95371b752f8d7b54091e32c31c6475eb9f6f5d7bc5cad6f3edc7c1d1c5fbf9e45e7ee507c35669a962a4220c817236c0e6cad9e9fab2929e349c108503c3e3d0604fd01cda022b3b3640c3bd45c3a96a6f1b4a1861549b1c58cb00db9a5976a3a971ec6b23631a614315c7621022cbe54ac69e90a4574256d548b893b0ab05026b96d70025d47daeccd46bc491dd988ae3949e311e18e730e0fc1ae2cca083d322d40d5221fa52faed2ab290a1512268e76617824b1583537605ca8d70186105191647aef63981be43ba8b91d4fa2ac120fd2c9df9ff3f52c99ad419e13f9993f1f805985294fac5af64c48016113029ad859aa11ada9cf01e453027eed3f7ec9c3c3c4eee5e77a673b2dac409bd24383198c79a30cc55fedbfbbfbffd033b26ba6102001100111d789c636cd66116622dc92f4ace60676060603205622d464600335c0357020011001209789c63000000010001020011001309789c63000000010001020011001409789c63000000010001020011001509789c63000000000000000000ac4c4c0000000401000a000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000002e020015000f09789c63000000010001020015001009789c63000000010001020015001109789c63000000010001020015001209789c63000000010001020015001309789c63000000010001020015001409789c63000000010001020015001509789c63000000010001020015001609789c63000000010001020016000009789c63000000010001020016000109789c63000000010001020016000209789c63000000010001020016000309789c63000000010001020016000409789c63000000010001020016000509789c63000000010001020016000609789c63000000010001020016000709789c63000000010001020016000809789c63000000010001020016000909789c63000000010001020016000a09789c63000000010001020016000b09789c63000000010001020016000c09789c63000000010001020016000d09789c63000000010001020016000e09789c63000000010001020016000f09789c63000000010001020016001009789c63000000010001020016001109789c63000000010001020016001209789c63000000010001020016001309789c63000000010001020016001409789c63000000010001020016001509789c63000000010001020017000009789c63000000010001020017000109789c63000000010001020017000209789c6300000001000102001700000000000001494c4c00000003010013001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010009000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000900108179789ceddbdd0a82301806e041275d4cf7e841b71d2c2c4508358d7e5ef30165f0e0c1e7f60edc70e7d3a11c9b526b7bddee520ef5d23618e38e6bbd71697a2e61dcd53d705a0fe28f45b38ce704638c31c618638c31c618638c31c618638c31c618638c31c6781b3cff23ec9f71df074f9ece191d2cdff29d97937d5412c743beefcd04e71c36c839df9093aa9c4ab6ca396193efc5954c9e4f7bdfdc59d7b1f1336da8fbdeec8a73f3bdf6fce50bf95e17e4852cb111fcce8500000000000001504c4c00000005010000001560789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000060789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000160789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000a04c4c9c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010015000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000b0013810b789cedd23b0a80400c45d181695c8c7bb470db42fc60a39dddc3398110385de0ae73af6aedd8fb9c731d8c31c618638c31c6e3709b9627f7da3e73ce3b183f58df38b42a69e250fe59df39efc473d58b5b18e75495c339f9c4734ec8fac618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c87e21d23a6988601000b001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010001000e60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001000f60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001001060789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010011000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000f000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f0010815a789cedd25b0a82501486d113bd34865e9a4073f4a16907762183a2422be2cfbd04d9b884e3e53bbbedb2ef5b3b9e97713ace03638c7138b75577cbcb7e3f5b1ec6473cfa919b491cf6ab66c3eb369d5b3785c7aedd3d5ee49b7cbf63dfe127ef8d2379b8be6e888631c618638cf11ff2e2f79cf3f118638c31c618638c31c6b3e3e12ec618638c31c618638c31c618638c31c6f835f7f8a79c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef919f2014cb97fb101000f0011817b789cedd2310a02411005d101afe31d0dbcb630ab606866b316560d34032fad7fbf5ef65eeb79efeff5f663ad159fc73b3e9539e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ff977cfb9c218ee33886f38a7fcb3b3e9539e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604ef9561500000000000000fa4c4c638c31c618e30f6eca34eafb010001000d60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010014000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010010000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0100100010812b789cedd4510a02211440d11703d11a5a43d012fd985db4d6c06a68181cfa2d9e7604118efe8817e7cb546bc473be97d758168c31c6c9394ea5e5a9de31ee95e316c7864b7c3c8d71c3e7a59bf4bcef7b3d56b66f1d638c31c618e3bfe275f7879ce7f27878d6371e99f58d47667de391396fdf71ddf1e1eb9cee75f0b87d63dc65df18638c31c618638c31c618638c31c6b85fae18638c31c618638c31c618638c31c6382f3f005368bbe10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010001001160789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001001260789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001001360789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000002010014000f8106789cedd6410a80201005d001371da63bb6e8dac15462b869978b9027c81f1e6e842fb8af2533e2da2dee5503638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3578e65ab438b889247e77e7a20ffe7f2787ad66f8c87b2d7806766fdc6f8613f253c33eb37fec0275835b7d40100140010811a789cedd93b0a80400c40c1051b0fe31d2dbcb6b07eb05150d4cac459580253a5785d86aea9b594f96f6379ebf80797b6df7353c773fecede18df617de358fcac587de358ac6f1c8dfb238f57fc9dbd31d637cec78ac59959df38162b1667667de358ac589c99f58d13f0e92d43df3801bbec618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ec3130184cc4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601000f000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000cb4c4c0000000401001c000d60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001c000e60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001c000f60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001c001060789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000029020012000e09789c63000000010001020012000f09789c6300000001000102001200101d789c636cd66116622dc92f4ace60676060607204624e1646003353034502001200111d789c636cd66116622dc92f4ace6067606060f205620346460034160375020012001209789c63000000010001020012001309789c63000000010001020012001409789c63000000010001020012001509789c63000000010001020012001609789c63000000010001020012001709789c63000000010001020012001809789c63000000010001020013000009789c63000000010001020013000109789c63000000010001020013000209789c63000000010001020013000309789c63000000010001020013000409789c63000000010001020013000509789c63000000010001020013000609789c63000000010001020013000709789c63000000010001020013000809789c63000000010001020013000909789c63000000010001020013000a09789c63000000010001020013000b09789c63000000010001020013000c09789c63000000010001020013000d09789c63000000010001020013000e09789c63000000010001020013000f09789c63000000010001020013001028789c636ed66116622dc92f4ace60676060604a076206464634d134aca2f9402cc7c800007bb30a3f0200130011097800000000000001264c4c00000006010010000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000f3464600000000000000000000002f000000000000002600000000000000c300000000000000b200000000000000cf000000000000008b000000000000002c0000000000000032000000000000011b000000000000004700000000000000ff000000000000013c0000000000000178000000000000013d000000000000016700000000000000fa000000000000008600000000000000b5000000000000011c00000000000000b7000000000000000800000000000000f2000000000000018c000000000000012100000000000000e7000000000000014b000000000000003c000000000000016600000000000001590000000000000103000000000000011a0000000000000037000000000000018e00000000000001940000000000000192000000000000003400000000000000d5000000000000013a00000000000001890000000000000085000000000000011000000000000000c600000000000000c500000000000000f3000000000000008f00000000000000ab00000000000001650000000000000182638c31c618638c31c618638c31c618638c3fb8015324dafc010015001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000fc4c4c31c618638c3fb8015324dafc010008000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c09789c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601001a000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000017d4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010010000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000174c4c00000006010006000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000000b4c4c00000006010010000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000984c4c31c618638c3fb8015324dafc010010000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cc1c9dcf2fb79df17e3d158df18efe3963f9918e074305eb0bef101d8440427b3be31c6f8887c03208fb5f00100070011814b789cedd74b0ac2401444d140262ec63d66e0b685f6831385d0061b7d564e401ace28e82d624ec7b9b569ba7e1ec7edba1f18638c31c618638c31c618e3249e0ecb33cfed5cea0631fe80f58d9359df3899f58d9359df3899f58dbf56551daef355e13fe43a21eb1b8f67b1e164d6374e667de364d6374ee63a2f92ab935a5eefdbd2f0bb5c27e4ad8f8c95ecb771959f01c7f34ffe1219094eee7b081b094eee7b081b4938efbcef216c241877d84830eeb09160dc6123c1b8c3468271878d04e30ecb1eef8e2fc56ea71a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000007010007001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010008000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000009c4c4c31c618638c3fb8015324dafc010008000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010005001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010006000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000a24c4c00000003010013001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000002010000000c60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000000d60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010004001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010006000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000801000d001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000d001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000e000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001074c4c0000000301001100108158789cedd6510a82401440d101215a436b688ff3d12e5a6b3095264208256a3ea723887a141cf52a5ece4d29293de6d7e239b50b8c31c6c1391d73bb724d876ebb29b78187a331de259fbab0dffbce69f4e845b83b25de98d77cc46182e8b7f3f059c718638c318ec0fddeef38ceb831d637fe77d637ae99f58d6b667de39a59dfb866d6f7dc7bb2578e73bf3d4b8c3ff0b4bebd0d786b5ef3fbad6fbc35fb3fc1788c4b9891603ca3d8691ce77270f5bc48b1fac63517ab6f5c73b1fac63fe33821638c31c618638c31c618638c31c618e3fdf11d15eba22901001100118117789cedd44b0a8020140550c149bb7003edb141db0eaca468d6c71a241c419e9ec11d5d1dfb987308cbdec6bacac018638c31c618638c31c618638c9be6d00de5d0a5ed1ef374c947086e815393acdf58bfeff151e4f07936c635fcec159f67eb37fe1beb37c63b9fd75ebf71db5cf5ad638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638cdff00c7b916878010011001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010004000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601000e001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000f000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001064c4c00000002010014000f8106789cedd6410a80201005d001371da63bb6e8dac15462b869978b9027c81f1e6e842fb8af2533e2da2dee5503638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3578e65ab438b889247e77e7a20ffe7f2787ad66f8c87b2d7806766fdc6f8613f253c33eb37fec0275835b7d40100140010811a789cedd93b0a80400c40c1051b0fe31d2dbcb6b07eb05150d4cac459580253a5785d86aea9b594f96f6379ebf80797b6df7353c773fecede18df617de358fcac587de358ac6f1c8dfb238f57fc9dbd31d637cec78ac59959df38162b1667667de358ac589c99f58d13f0e92d43df3801bbec618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ec3130184cc4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000002e020003001309789c63000000010001020003001409789c63000000010001020003001509789c63000000010001020003001609789c63000000010001020004000009789c63000000010001020004000109789c63000000010001020004000209789c63000000010001020004000309789c63000000010001020004000409789c63000000010001020004000509789c63000000010001020004000609789c63000000010001020004000709789c63000000010001020004000809789c63000000010001020004000909789c63000000010001020004000a09789c63000000010001020004000b09789c63000000010001020004000c09789c63000000010001020004000d09789c63000000010001020004000e09789c63000000010001020004000f09789c63000000010001020004001009789c63000000010001020004001109789c63000000010001020004001209789c63000000010001020004001309789c63000000010001020004001409789c63000000010001020004001509789c63000000010001020004001609789c63000000010001020005000009789c63000000010001020005000109789c63000000010001020005000209789c63000000010001020005000309789c63000000010001020005000409789c63000000010001020005000509789c6300000001000102000500000000000001394c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000009010004001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010004001860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010005000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000012b4c4c00000003010013000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000900138101789cedd23b0a80400c05c0c0361ec63b5a786d217eb0b1105658615926101e4c95c05be7921971ec1de75c81717b8e697972c9ed9dfbb91be31ad66f3c327feb7713eee7793c3ceb37c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c7fe01d902fc67a010009001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000509789c63000000010001020019000609789c63000000010001020019000709789c63000000010001020019000809789c63000000010001020019000909789c63000000010001020019000a09789c63000000010001020019000b09789c63000000010001020019000c09789c63000000010001020019000d09789c63000000010001020019000e09789c63000000010001020019000f09789c63000000010001020019001009789c63000000010001020019001109789c63000000010001020019001209789c63000000010001020019001309789c63000000010001020019001409789c63000000010001020019001509789c6300000001000102001a000009789c6300000001000102001a000109789c6300000001000102001a000209789c6300000001000102001a000309789c6300000001000102001a000409789c6300000001000102001a000509789c6300000001000102001a000609789c6300000001000102001a000709789c6300000001000102001a000809789c6300000001000102001a000909789c6300000001000102001a000a09789c6300000001000102001a000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601000a001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000a001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901000b000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000334c4c00000006010015000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000824c4c00000032020001000f09789c63000000010001020001001009789c63000000010001020001001109789c63000000010001020001001209789c63000000010001020001001309789c63000000010001020001001409789c63000000010001020001001509789c63000000010001020001001609789c63000000010001020002000009789c63000000010001020002000109789c63000000010001020002000209789c63000000010001020002000309789c63000000010001020002000409789c63000000010001020002000509789c63000000010001020002000609789c63000000010001020002000709789c63000000010001020002000809789c63000000010001020002000909789c63000000010001020002000a09789c63000000010001020002000b09789c63000000010001020002000c09789c63000000010001020002000d09789c63000000010001020002000e09789c63000000010001020002000f09789c63000000010001020002001009789c63000000010001020002001109789c63000000010001020002001209789c63000000010001020002001309789c63000000010001020002001409789c63000000010001020002001509789c63000000010001020002001609789c63000000010001020003000009789c63000000010001020003000109789c6300000001000102000300000000000000ee4c4c0000000401000d001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000023020014000509789c63000000010001020014000609789c63000000010001020014000709789c63000000010001020014000809789c63000000010001020014000909789c63000000010001020014000a09789c63000000010001020014000b09789c63000000010001020014000c09789c63000000010001020014000d09789c63000000010001020014000e09789c63000000010001020014000f09789c6300000001000102001400101d789c636cd66116622dc92f4ace60676060606a076206464600351c037f020014001109789c63000000010001020014001209789c63000000010001020014001309789c63000000010001020014001409789c63000000010001020014001509789c63000000010001020014001609789c63000000010001020014001709789c63000000010001020014001809789c63000000010001020015000009789c63000000010001020015000109789c63000000010001020015000209789c63000000010001020015000309789c63000000010001020015000409789c63000000010001020015000509789c63000000010001020015000609789c63000000010001020015000709789c63000000010001020015000809789c63000000010001020015000909789c63000000010001020015000a09789c63000000010001020015000b09789c630000000100000000000000c34c4c00000009010002001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010003000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c0000000000000101494900000000120000000000000041010009000500000000000000e5010009000d000000000000007f010009001300000000000000ac0100090017000000000000000201000a0004000000000000005901000a000b000000000000016801000a000f000000000000016c01000a0011000000000000016401000a0014000000000000002301000a001800000000000000ae01000b0004000000000000013701000b000c000000000000009401000b000f000000000000019401000b0013000000000000002c01000b0017000000000000006001000c0005000000000000013e01000c000b00000000000000b701000c000f000000000000018900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c0000000401000f000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000003002000c000e09789c6300000001000102000c000f1d789c636cd66116622dc92f4ace6067606060ec06e25f8c8c00381a047b02000c001009789c6300000001000102000c001109789c6300000001000102000c00121f789c636cd66116622dc92f4ace6067606060ec616060f262640000361403cc02000c00131f789c636cd66116622dc92f4ace6067606060ec6360604a61640400367103e902000c001409789c6300000001000102000c001509789c6300000001000102000c001609789c6300000001000102000c001709789c6300000001000102000c001809789c6300000001000102000d000009789c6300000001000102000d000109789c6300000001000102000d000209789c6300000001000102000d000309789c6300000001000102000d000409789c6300000001000102000d000509789c6300000001000102000d000609789c6300000001000102000d000709789c6300000001000102000d000809789c6300000001000102000d000909789c6300000001000102000d000a09789c6300000001000102000d000b09789c6300000001000102000d000c09789c6300000001000102000d000d09789c6300000001000102000d000e09789c6300000001000102000d000f09789c6300000001000102000d00101f789c636cd66116622dc92f4ace60676060605cc3c0c0c4000000000000015d4c4c0000003002000c000e09789c6300000001000102000c000f1d789c636cd66116622dc92f4ace6067606060ec06e25f8c8c00381a047b02000c001009789c6300000001000102000c001109789c6300000001000102000c00121f789c636cd66116622dc92f4ace6067606060ec616060f262640000361403cc02000c00131f789c636cd66116622dc92f4ace6067606060ec6360604a61640400367103e902000c001409789c6300000001000102000c001509789c6300000001000102000c001609789c6300000001000102000c001709789c6300000001000102000c001809789c6300000001000102000d000009789c6300000001000102000d000109789c6300000001000102000d000209789c6300000001000102000d000309789c6300000001000102000d000409789c6300000001000102000d000509789c6300000001000102000d000609789c6300000001000102000d000709789c6300000001000102000d000809789c6300000001000102000d000909789c6300000001000102000d000a09789c6300000001000102000d000b09789c6300000001000102000d000c09789c6300000001000102000d000d09789c6300000001000102000d000e09789c6300000001000102000d000f09789c6300000001000102000d00101f789c636cd66116622dc92f4ace60676060605cc3c0c0c4000000000000011c4c4c00000003010012000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000f73789cedd2310a80301004c083343e267fb4c8b7854b2236e91502cec2b130ed6dab253362dc533377618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f85b8ee35cb9e4f51bdee70dd8bedfe70e9caabac201001200108159789cedd84d0a83301446d18050ba86aea17b74d06d17d21f1cb4410a81809ff508229c81a8ef46c4db75aab594e7be1c5edbfb90c3e53c7ff354efb887736689f53d9e7366299fb5e9cced75ffe494078b23b85cca692db63e9e9b730fe495ebeee3ace78d37e125aeb693bdf2e75d5a0d3828cdccd5103934acef4d1649e4880fcd399dec95736689f5adef63714e27f19c3334ac6f21e3a8aa848cc7734e6cfe68e3f19c13b2772c1ecf8ac5ffccc3bf0a848c43599a18638c31c618638c31c618638c31c618638c31c618638c31c618631cc80f310beafd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000b000960789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01000b000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010015001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010015001860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010015001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010016000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000f44c4c00000006010011000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001054c4c31c618638c3fb8015324dafc01000c000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010003000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010013000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010009000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000a000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000a000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000f000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000301000700128109789cedd2310a80301045c180d7f18e165e5b484cb0122cec3ec92c8485a9b2f0ce7dabb594fb3dabcf5818638c31c618638c31c618638c31c618638c31c618638c31c618a7f3f1e62bec837855fe48f31fe79c83e76169e25096260e6569e25096260e6569e25096260e6569e25096260e6569e229589a18638c31c618638c31c618afca0dcc1dea7c010007001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0001020015000c09789c63000000010001020015000d09789c63000000010001020015000e09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000002e020015000f09789c63000000010001020015001009789c63000000010001020015001109789c63000000010001020015001209789c63000000010001020015001309789c63000000010001020015001409789c63000000010001020015001509789c63000000010001020015001609789c63000000010001020016000009789c63000000010001020016000109789c63000000010001020016000209789c63000000010001020016000309789c63000000010001020016000409789c63000000010001020016000509789c63000000010001020016000609789c63000000010001020016000709789c63000000010001020016000809789c63000000010001020016000909789c63000000010001020016000a09789c63000000010001020016000b09789c63000000010001020016000c09789c63000000010001020016000d09789c63000000010001020016000e09789c63000000010001020016000f09789c63000000010001020016001009789c63000000010001020016001109789c63000000010001020016001209789c63000000010001020016001309789c63000000010001020016001409789c63000000010001020016001509789c63000000010001020017000009789c63000000010001020017000109789c63000000010001020017000209789c63000000010001020017000000000000017c4c4c0000000102001000108660789cd555cd6ed3401076feea346d43698b2a81387002210eb482130815a14a484808892312626c4fec25fb6376d76dc285fe407be335780d5e8137a0cfc00b309bd821719dc0154b2b79f79b999d9f6f66bba7f71a67afba90e2c0242ce52aeca3f63dcfab6fd25aa979335f6daf7da006c877eef7cf99effd371b5f63642c137f51097886855867889cabc389d634d69620d5a186d46d96638d280b68faa659a46c70da4627d320592694c68aed8ce822bd09b6b14869c5280e7aee6d1dc785310fdae5eb220642c92897f463c58bffb2d14bfb94836572b25fa43b8395f59856b2ea7f4a67deffdf7c2c85b79af2ccaa3c33fea53c79b5f98b9aead656a2388b60186ae85104b18580e3a8b3966835eb3512daee58341c4ccafa63a8e3a096d3dff21318a62c5769d25aab79552acb13954b507b027d0ebb6142fc1518c10103b93bc2b71d5e6ef1f56eace100b9008b9a01ff404416100bd0c4892f995f5b2f90e719be4e58cfd6f77a8c1d916ed3808cde50e622a66d21f57491c68ce466a5a43e3f77925da122d4d2d1a1a7b438b943b5359072caecc90bbfd93616c54b10d8a2ae03632cb5deaa3bfb6368fdd7cd3d32b4d2a3ba408c23e1515b07d49857f2d389f8519e8faed52c005edcfbdcf75a870982ad97c27c52e9fc077fe47ccb2566f76c7f512a8a688e00000000000000c64c4c1f4f47b3643813389c0de5fbed6f5fdf954359eae94ca4c34b817c7ffbe8c7bb72aa4fee56bab27f71f1d3d94de023e8c8d2244888f85b2118cb31615184d2d22c8894d214ff0ab189fa41a005ee3a8278761254f1ec7a25cfd6834cd2900990ac3fa3e20e1ad46331f1d7a42091b7c6ad4475a7118a7a3c076272a79b0cb9b29c83a463b9738bf849f381deaecc24ce2bc5261849afe56d485da1995d23f5fc5aa5fa141736493d75f201460f48bd6315e54519c4883cd84832e9ae8db5ca6464950edd15abc573e912b151220265624195374315d01c303458b0103abdfacf2d72fc699ce61b5569be5999669f122c6080ceef71e89cc5894d3a538f5af3cfc34562553e8edeb350a5296a9a7fa77ddfbb7aa894e303f022dd94ed25f2a8cf5c736c3872e001f0512b5a0e22a5d32967fc5001275bc70fa75fca464e2ba7491571dee4254833f99e9148d837bb74dc1eb9972867aa1380a07aba6af87ee524accf1f928df9d0b5f9506d3eb435867e030386c417000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000007010006000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000015149490000000013000000000000003d01001e00050000000000000031020001000f00000000000000b0020003001300000000000000a802000500130000000000000009020007001100000000000000400200090007000000000000002b02000a000d000000000000003a02000c000e00000000000000b602000e000c000000000000015202000f000b00000000000000d002000f001600000000000000dc0200100010000000000000016402001000110000000000000158020011000e0000000000000148020012000e000000000000016f02001400050000000000000196020015000f000000000000007d0200170010000000000000010202001a000c000000000000004e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049490000000016000000000000002101000000080000000000000109010000000900000000000000da010000000c000000000000009f010000000e00000000000000f5010000001100000000000000380100000015000000000000008001000100020000000000000115010001000800000000000000d8010001000e00000000000000840100010011000000000000008a01000100140000000000000074010001001700000000000000f90100020003000000000000005101000200090000000000000015010002000f000000000000005c01000200120000000000000062010002001600000000000000b301000300070000000000000124010003000d00000000000000bd010003001000000000000000eb0100030016000000000000002501000400040000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c638c31c618e30f6eca34eafb01001c000c60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000f000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601001b000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b001060789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61800000000000000614c4c31c618638c3fb8015324dafc010003001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c1d42d6377e19eb1bef9973fa963daee79c90658feb39a758d9e37ace491347704e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9acbf90462c5a64600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010014001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000b02000f000b09789c6300000001000102000f000c09789c6300000001000102000f000d09789c6300000001000102000f000e09789c6300000001000102000f000f09789c6300000001000102000f00108107789c636bd66196e0cec84fce4ead2c494cca496567606060fcc2c0c0c4c2c8009413e34c2c48ad28c84ccccb07cbfc02c930310265a478934af3b2538b0af28b4b528b8cc0b21f81b29c607d12dc498929e9f9f90539a5c51960b99770336505406616a526e717a514e42456a6168115fc871b2dc45a925f940cd1f61328cac5c2000013d4264702000f001109789c6300000001000102000f001209789c6300000001000102000f001309789c6300000001000102000f001409789c6300000001000102000f001509789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010011001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010011001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010012000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000005010013001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010013001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000764c4c00000006010019000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000e44c4c0000000401000c000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000c000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ce2d2ebdf1308813325f00d77c9fa98f6bdb5d7fe1cef751c3fe1a5e7ed02d7790ece6169e2a22c4d8c31c618638c31c618638c31c618638c31c618638c31c618638c31c6189f78e9f9ca678331aef3781ccffac6c92c4d9cccfac6c9ac6f9ccb6dee429e8e90bf709d7be37fe5b162f58defc6631387f904df8b158b9359df3899f58d9359df3899f58d93b978df4f901de60e01000c00128139789cedda410a83301040d1809b1ec63bbae8b50bb196bae8428818613a798204de2a307f7679ce53ada5bcffefb17d9f03e3162e8fe597a7fa3ae638f7c6b885f58dff8b158b33b3be7166d637ceccfa1e95cf4d7e0c8e331daceffe1c673a6639ea2cb1be5b38ce74f0d590e35c10e30bac6f8c31c618638c31c618638c31c618638c31c6f87e8ef37cc98b29dc9fe3c4a66f8cbbb26dc09959df18ef6c1b7066d637ceccfac69959df18ef6c1b7066d677085e019f82488000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401001b001260789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001b001360789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001b001460789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001b001560789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010001000860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000960789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001000a60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001000b60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010001000c60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61800000000000000874c4c00000009010012000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000f84c4c00000003010000000960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000000a60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000000b60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000f000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001302000f001609789c6300000001000102000f001709789c6300000001000102000f001809789c63000000010001020010000009789c63000000010001020010000109789c63000000010001020010000209789c63000000010001020010000309789c63000000010001020010000409789c63000000010001020010000509789c63000000010001020010000609789c63000000010001020010000709789c63000000010001020010000809789c63000000010001020010000909789c63000000010001020010000a09789c63000000010001020010000b09789c63000000010001020010000c09789c63000000010001020010000d09789c63000000010001020010000e09789c63000000010001020010000f09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010002000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000021d0000020d070000021e0000020d070000021f0000020d07000002200000020d07000002210000020d07000002220000020d07000002230000020d07000002240000020d07000002250000020d07000002260000020d07000002270000020d07000002280000020d07000002290000020d070000022a0000020d070000022b0000020d070000022c0000020d070000022d0000020d070000022e0000020d070000022f0000020d07000002300000020d07000002310000020d070000020a0000020c07000002300000020c07000002310000020c070000020a0000020b07000002300000020b07000002310000020b070000020a0000020a07000002300000020a07000002310000020a070000020a000002090700000230000002090700000231000002090700000232000002090a00000233000002090a00000234000002090a00000235000002090a00000236000002090a00000237000002090a00000238000002090a00000239000002090a0000020a00000208070000023a000002080a0000023b000002080a0000020a00000207070000023c000002070a0000023d000002070a0000020a00000206070000023e000002060a0000023f000002060a0000020a00000205070000023f000002050a0000020a00000204070000023f000002040a0000020a00000203070000020b00000200000000000001204c4ccf336783ed3b9e67ce06db773ccf9c0db6ef789e391b6cdff13c7336d8bee379e66cb07dc7f3ccd960fb8ee799b3c1f61dcf336783ed3b9e67ce06db773ccf9c0db6ef789e391b6cdff13c7336d8bee32ff900557af55801000f0012811d789cedd2d10983501044d105db4997b62dbc2858c2480673168685f37df7cfb6d6ccb9fb5db78e99793f2ffca7dcd3a0be719e7b1ad437ce734f83fac679ee6950df38cf3d0dea1be7b9a7417de33cf734a86f9ce79e06f58df3dcd3a0be719e7b1ad437ce734f83d2c47916327e333f99a6bef1af59df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61887f80bb4dbd96d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010016000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000701001c000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000012a4c4c31c618638c3fb8015324dafc01001b000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010009000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010019000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000008010009000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000bf4c4cc2795b07d498178bd3a9f861c147cf6a16002fef7dec7bed5182609b9530bfa975feb99f3bdf76c4ec9d3e5a464519cdd1bdd968560d6702c7f3a1bcb9fedbeb67d55056fb3a13e9f85c206f7ebcfbd7b32ad5c7376a5d79f4f6ed3fce6e022f40479626414285bf1b82b11c131645282dcd8248294df1af5135513f08b4c05d47509d1d077575f6456d9d6d0699a4211320597f40c93d58a11e8ba97e4d0a12797bd24a94771aa1a827732026777ac9982bcb39483a96b7ae517dd27ca0b72b3389f34ab12946d21b451b525768663748bdb856a901c5852d524f9d7c80d16d52ef5a45bc2883189107db4926ddb5b156998cacd2a1bb62bd7c2e1d11db95422026966479275401cd014383054ba193adcf6e91a39f27345fa9a3f96a2dcd3e112ce0009ddf93d0398b139b74671eb5d6c7878bc4ea7cccdfb350a5296a9a7f2703dfdb1a29e5ea81fe66147413dbabe4d180b9e6d876c58143e0792b5a0e22a5d31967fc5001275b4777665fca95a2ac9c2665c47953a420cde44f8c44c281d9a3e34eee5ea29ca96e0082f2e9b2e1fbb593b0b97848ae2c862e2d861a8ba1dd09f40189a3d1ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000019020011000e09789c63000000010001020011000f09789c6300000001000102001100108273789c8d52cd4ac34010dea43f6b4ab53fe84110d1a350fce941c48b057bf0221e7c00d96c36c9d24d36ec6ea8de2c45fa00be82c5b750a117f14d3c7a103c787012e9a120c181819d996ffef69beab853eeacd40cd382e8840f18c6a8ef3c7df5ad0d94c9f3fdfbc9e9fadaf7acf579953bc69dd2f8a861385307fb86d190864c1b0c017b0bb46ca105b19a2d4d624f1b19b38818a63811a30023872b19bb42d24109a3aa911093f0aaf930452878101a1ba3bac79599674d38b69a73e32c659721f78dddf339bf853e0bd0d14511d4f1532186527a07558cb061512258f7ee109a4b1543521602cb8948101105131677ae0e391d3005e33622a9f54d42c0f4b27196ff5fa4927d527742fec49c4fa72f802945a9575c25438c581102c8db5e55d2958653aa886f781c18e20a9613b89711685b7011bbcdba9bc6b0564e4508cbf5eb1faf8b37d17b98b5df1ee737b1d92209bba6243184c79a8684abbce6f1ef5100a25d3152d130f7ee802e95d10fec8cb8a502001100111d789c636cd66116622dc92f4ace60676060603205622d464600335c0357020011001209789c63000000010001020011001309789c63000000010001020011001409789c63000000010001020011001509789c630000000000000001214c4c0000001302000f001609789c6300000001000102000f001709789c6300000001000102000f001809789c63000000010001020010000009789c63000000010001020010000109789c63000000010001020010000209789c63000000010001020010000309789c63000000010001020010000409789c63000000010001020010000509789c63000000010001020010000609789c63000000010001020010000709789c63000000010001020010000809789c63000000010001020010000909789c63000000010001020010000a09789c63000000010001020010000b09789c63000000010001020010000c09789c63000000010001020010000d09789c63000000010001020010000e09789c63000000010001020010000f09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f9010015000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000d00108169789cedd5db0a82401405d0015ffa98fe711efaedc02e68505034ead471660921adc0ced96ef1741cc631a5eb673add8efb09638c71704e87fcccc378c638e5128e33771c2e4b50b0bfad669c8bace3f9fbe32f13c66f5fff7106c418638c31c618638c31c618638cbbe65cc471e6c678fbc6963d0d1e121ca255f3af932ce338ebe076aa59d6d89afd5e5bfbb26be7575e1c6ccde523f3a704bfe638ebe0766ef15ee7fe43267106dc2b6f52b6aeabb949b071e6de2bd7ac661fb5ef634bc1b6b3656309f671d3ba66c1568a4a825835312e61d5c418638c31c618638c31c61863dc1e5f00b36d4a5e01000d00117d789cedce310a80300c05d040170fe31d3b786d216a2d8882e0e020f802e193970c99c69219b1768fad5a608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c5fe018ea994bce6d57afd7dfe29bbf31c6f837dce783f7c018638c31c618638c31c618638c31c618638c31c6f8112fcba18ae10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010003001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000cd4c4c0000001802000e000c09789c6300000001000102000e000d09789c6300000001000102000e000e09789c6300000001000102000e000f09789c6300000001000102000e001024789c636ad66116622dc92f4ace6067606060bcc8c0c0c4c2c888267a00220a00c15c078602000e00111f789c636cd66116622dc92f4ace60676060603cc3c0c064c0c80000378603f202000e001209789c6300000001000102000e001309789c6300000001000102000e001409789c6300000001000102000e001509789c6300000001000102000e001609789c6300000001000102000e001709789c6300000001000102000e001809789c6300000001000102000f000039789c636cd661d113e0292ec9cf4b4d2b2dca4b4c4e65626770fe70dbc73ee2b78cf952f76d87e6868a3bb76aef38c4b62a90010400bd87112f02000f000109789c6300000001000102000f000209789c6300000001000102000f000309789c6300000001000102000f000409789c6300000001000102000f000509789c6300000001000102000f000609789c6300000001000102000f000709789c6300000001000102000f000809789c6300000001000102000f000909789c6300000001000102000f000a09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c9c6300000001000102000a000b09789c6300000001000102000a000c09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000209789c63000000010001020003000309789c63000000010001020003000409789c63000000010001020003000509789c63000000010001020003000609789c63000000010001020003000709789c63000000010001020003000809789c63000000010001020003000909789c63000000010001020003000a09789c63000000010001020003000b09789c63000000010001020003000c09789c63000000010001020003000d09789c63000000010001020003000e09789c63000000010001020003000f09789c63000000010001020003001009789c63000000010001020003001109789c63000000010001020003001209789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000d000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010011000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001174c4c00000008010006001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010007000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000005b4c4c0000001302000f001609789c6300000001000102000f001709789c6300000001000102000f001809789c63000000010001020010000009789c63000000010001020010000109789c63000000010001020010000209789c63000000010001020010000309789c63000000010001020010000409789c63000000010001020010000509789c63000000010001020010000609789c63000000010001020010000709789c63000000010001020010000809789c63000000010001020010000909789c63000000010001020010000a09789c63000000010001020010000b09789c63000000010001020010000c09789c63000000010001020010000d09789c63000000010001020010000e09789c63000000010001020010000f09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010010000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010016000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010000000e60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000000f60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010000001060789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff494900000000120000000000000041010009000500000000000000e5010009000d0000000000000157010009001300000000000000ac0100090017000000000000000201000a0004000000000000005901000a000b000000000000001001000a000f000000000000000d01000a0011000000000000001201000a0014000000000000002301000a001800000000000000ae01000b0004000000000000013701000b000c000000000000004201000b000f000000000000004501000b001300000000000000fe01000b0017000000000000006001000c0005000000000000013e01000c000b00000000000000d401000c000f000000000000016300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c0000000601001b000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001b000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000e24c4c31c618638c3fb8015324dafc010012000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010001001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010002000060789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000160789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010002000260789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ccf336783ed3b9e67ce06db773ccf9c0db6ef789e391b6cdff13c7336d8bee379e66cb07dc7f3ccd960fb8ee799b3c1f61dcf336783ed3b9e67ce06db773ccf9c0db6ef789e391b6cdff13c7336d8bee32ff900557af55801000f0012811d789cedd2d10983501044d105db4997b62dbc2858c2480673168685f37df7cfb6d6ccb9fb5db78e99793f2ffca7dcd3a0be719e7b1ad437ce734f83fac679ee6950df38cf3d0dea1be7b9a7417de33cf734a86f9ce79e06f58df3dcd3a0be719e7b1ad437ce734f83d2c47916327e333f99a6bef1af59df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61887f80bb4dbd96d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010012000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010015001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010015001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000b0013810b789cedd23b0a80400c45d181695c8c7bb470db42fc60a39dddc3398110385de0ae73af6aedd8fb9c731d8c31c618638c31c6e3709b9627f7da3e73ce3b183f58df38b42a69e250fe59df39efc473d58b5b18e75495c339f9c4734ec8fac618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c87e21d23a6988601000b001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001802000e000c09789c6300000001000102000e000d09789c6300000001000102000e000e09789c6300000001000102000e000f09789c6300000001000102000e001024789c636ad66116622dc92f4ace6067606060bcc8c0c0c4c2c888267a00220a00c15c078602000e00111f789c636cd66116622dc92f4ace60676060603cc3c0c064c0c80000378603f202000e001209789c6300000001000102000e001309789c6300000001000102000e001409789c6300000001000102000e001509789c6300000001000102000e001609789c6300000001000102000e001709789c6300000001000102000e001809789c6300000001000102000f000039789c636cd661d113e0292ec9cf4b4d2b2dca4b4c4e65626770fe70dbc73ee2b78cf952f76d87e6868a3bb76aef38c4b62a90010400bd87112f02000f000109789c6300000001000102000f000209789c6300000001000102000f000309789c6300000001000102000f000409789c6300000001000102000f000509789c6300000001000102000f000609789c6300000001000102000f000709789c6300000001000102000f000809789c6300000001000102000f000909789c6300000001000102000f000a09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000009010019001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001a000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001774c4c31c618638c3fb8015324dafc010003000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000003e020017001009789c63000000010001020017001109789c63000000010001020017001209789c63000000010001020017001309789c63000000010001020017001409789c63000000010001020017001509789c63000000010001020018000009789c63000000010001020018000109789c63000000010001020018000209789c63000000010001020018000309789c63000000010001020018000409789c63000000010001020018000509789c63000000010001020018000609789c63000000010001020018000709789c63000000010001020018000809789c63000000010001020018000909789c63000000010001020018000a09789c63000000010001020018000b09789c63000000010001020018000c09789c63000000010001020018000d09789c63000000010001020018000e09789c63000000010001020018000f09789c63000000010001020018001009789c63000000010001020018001109789c63000000010001020018001209789c63000000010001020018001309789c63000000010001020018001409789c63000000010001020018001509789c63000000010001020019000009789c63000000010001020019000109789c63000000010001020019000209789c63000000010001020019000309789c63000000010001020019000409789c6300000001000102001900000000000000ad4c4c000000040100100011811f789cedd4410a83301040d1816c728b5ec03b76d16b0ba94aa14410845619f40542e09145203f790da5b588697e96792c0bc618638c31c618638c31c618638cefc1519f3d9736e2641cf5d1719e7cd273babbc47bfa5eed0ebcc9595e1adee46fdff9f2398efff243a4bb4bbca7ef6407c4f807d637be32eb1b638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3f3f80d0f509efb010010001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c1d42d6377e19eb1bef9973fa963daee79c90658feb39a758d9e37ace491347704e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9acbf90462c5a64600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010011000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000f000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000e000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401001d000c60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001d000d60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001d000e60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001d000f60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000001010000000860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010016000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001474c4c0000000401000f000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f0010815a789cedd25b0a82501486d113bd34865e9a4073f4a16907762183a2422be2cfbd04d9b884e3e53bbbedb2ef5b3b9e97713ace03638c7138b75577cbcb7e3f5b1ec6473cfa919b491cf6ab66c3eb369d5b3785c7aedd3d5ee49b7cbf63dfe127ef8d2379b8be6e888631c618638cf11ff2e2f79cf3f118638c31c618638c31c6b3e3e12ec618638c31c618638c31c618638c31c6f835f7f8a79c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef919f2014cb97fb101000f0011817b789cedd2310a02411005d101afe31d0dbcb630ab606866b316560d34032fad7fbf5ef65eeb79efeff5f663ad159fc73b3e9539e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ff977cfb9c218ee33886f38a7fcb3b3e9539e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604ef9561500000000000000df4c4c0000000601001c000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000a60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001c000b60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61800000000000000ca4c4c638c31c618e30f6eca34eafb01001d000160789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000260789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000360789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000601000d000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000003b4c4c020c070000022c0000020c070000022d0000020c070000022e0000020c070000022f0000020c070000020b0000020b070000020c0000020b070000020d0000020b070000020e0000020b070000020f0000020b07000002100000020b07000002110000020b811b000002120000020b07000002130000020b07000002140000020b811b000002150000020b07000002160000020b07000002170000020b07000002180000020b07000002190000020b070000021a0000020b070000021b0000020b811b0000021c0000020b070000021d0000020b070000021e0000020b070000021f0000020b07000002200000020b07000002210000020b07000002220000020b811b000002230000020b07000002240000020b07000002250000020b07000002260000020b07000002270000020b07000002280000020b07000002290000020b811b0000022a0000020b070000022b0000020b070000022c0000020b070000022d0000020b070000022e0000020b070000022f0000020b070000020b0000020a070000020c0000020a070000020d0000020a070000020e0000020a070000020f0000020a07000002100000020a07000002110000020a811b000002120000020a07000002130000020a07000002140000020a811b000002150000020a07000002160000020a07000002170000020a07000002180000000000000000002d4c4c000000040100120011810e789cedd23d0a80300c06d042170fe31d1dbcb6507f7071108a421be51542e02d4dd36f1e7329296d75b6fd1c0d638c31c618638c31c618638c31c618638c31c64f380dd3957359420d88f10bbec9f7cf38cebe71db0c76b852be71b3b0c579e557378865b037c7f94b2cdf18638c31c618638c31c618638c31c618638c31c618638c31c618638cab790549ac7d3c010012001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010018000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000002f4c4c00000006010004000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000a54c4c31c618638c3fb8015324dafc010004000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010003000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010001000260789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000360789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000460789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000560789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010001000660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000204c4c638c31c618e30f6eca34eafb01001d000960789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000a60789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb01001d000b60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010011000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010005000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000e0011817e789ceddacb0a8250184561c1490fd33b36e8b503bb100e2288533b5b6d962007be91fe2c8e0a1ef7f3b24cd3e5bc2fd7e3b6c8f2caa72ee60c564630274dfb96f3cc49d3bee53c73d2b46f39cf9c34ed5bce33274dfb96f3cc49d3bee53c73d2b46f39cf9c34ed5bce33274dfb963fe6c3f34e36e769f7c0f31b4c1aac8c604adf21e60c16ce91fd6474f7210451c6acaa386cdf1dccaa8a532ce7ba37664e9af66ddf79e6a459d8f7771fff94bb4433274dfbb6ef3c73d21ce71f7c34d9a09c65fb969bf99fdf4f64f915dbb7dcccf62d37b37dcbcd6cdf7233dbb7dcccf62d37b37dcbcd6cdf7233dbb75cc185ff57c9f2caf62d37f378df671919abea01000e00127e789cedd2310ac020104541c1ebe4965e5b301ad2a44f21df11d685e9165ebbea18a5cc79d77acfc27843ee7ff03ee760fc617de30359df3899f58d9359df3899f58d9359df3899f58d9359df3899f58d9359df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3f9ddf90fdab401000e001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e00000000000001554c4c00000003010014000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000c000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff49490000000013000000000000003d01001e00050000000000000031020001000f00000000000000b0020003001300000000000000a802000500130000000000000009020007001100000000000000400200090007000000000000002b02000a000d000000000000003402000c000e00000000000000b702000e000c00000000000000ff02000f000b000000000000016702000f001600000000000000f2020010001000000000000000c502001000110000000000000159020011000e00000000000000e7020012000e0000000000000032020014000500000000000000b2020015000f00000000000000c40200170010000000000000010202001a000c000000000000004e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c31c618638c3fb8015324dafc010019001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c03070000020c00000203070000020d00000203070000020e00000203070000020f00000203070000021000000203070000021100000203070000021200000203070000021300000203070000021400000203070000021500000203070000021600000203070000021700000203070000021800000203070000021900000203070000021a00000203070000021b00000203070000021c00000203070000021d00000203070000021e00000203070000021f00000203070000022000000203070000022100000203070000022200000203070000022300000203070000022400000203070000022500000203070000022600000203070000022700000203070000022800000203070000022900000203070000022a00000203070000022b00000203070000022c00000203070000022d00000203070000022e00000203070000022f00000203070000023000000203070000023100000203070000023200000203070000023300000203070000023400000203070000023500000203070000023600000203070000023700000203070000023800000203070000023900000203070000023a00000203070000023b00000203070000023c00000203070000023d00000203070000023e00000203070000023f0000020307010b706c61796572537061776e010000020e000002040408656e67696e654f6e00000000000000294c4c0000010001020011001609789c63000000010001020011001709789c63000000010001020011001809789c63000000010001020012000009789c63000000010001020012000109789c63000000010001020012000209789c63000000010001020012000309789c63000000010001020012000409789c63000000010001020012000509789c63000000010001020012000609789c63000000010001020012000709789c63000000010001020012000809789c63000000010001020012000909789c63000000010001020012000a09789c63000000010001020012000b09789c63000000010001020012000c09789c63000000010001020012000d09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010015001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010015001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c8c68a259105100cfd208c402000b00101f789c636cd66116622dc92f4ace60676060602c656060926064000034dd038302000b001109789c6300000001000102000b00121f789c636cd66116622dc92f4ace6067606060ac6560600a6662040035c903c802000b001309789c6300000001000102000b001409789c6300000001000102000b001509789c6300000001000102000b001609789c6300000001000102000b001709789c6300000001000102000b001809789c6300000001000102000c000009789c6300000001000102000c000109789c6300000001000102000c000209789c6300000001000102000c000309789c6300000001000102000c000409789c6300000001000102000c000509789c6300000001000102000c000609789c6300000001000102000c000709789c6300000001000102000c000809789c6300000001000102000c000909789c6300000001000102000c000a09789c6300000001000102000c000b09789c6300000001000102000c000c09789c6300000001000102000c000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010003000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010003000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001144c4c00000007010016001760789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010017000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000064c4c9c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010018000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f901001e000360789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000460789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f901001e000a60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f901001e000b60789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9020001000909789c63000000010001020001000a09789c63000000010001020001000b09789c63000000010001020001000c09789c63000000010001020001000d09789c63000000010001020001000e09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01001c000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001c000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010005000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010008000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000914c4c00000003010014000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010017001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000b02000f000b09789c6300000001000102000f000c09789c6300000001000102000f000d09789c6300000001000102000f000e09789c6300000001000102000f000f09789c6300000001000102000f00108107789c636bd66196e0cec84fce4ead2c494cca496567606060fcc2c0c0c4c2c8009413e34c2c48ad28c84ccccb07cbfc02c930310265a478934af3b2538b0af28b4b528b8cc0b21f81b29c607d12dc498929e9f9f90539a5c51960b99770336505406616a526e717a514e42456a6168115fc871b2dc45a925f940cd1f61328cac5c2000013d4264702000f00111f789c636cd66116622dc92f4ace60676060607cc5c0c0a4c3c80000384c040c02000f001209789c6300000001000102000f001309789c6300000001000102000f001409789c6300000001000102000f001509789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010013000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000004c4c4c11001609789c63000000010001020011001709789c63000000010001020011001809789c63000000010001020012000009789c63000000010001020012000109789c63000000010001020012000209789c63000000010001020012000309789c63000000010001020012000409789c63000000010001020012000509789c63000000010001020012000609789c63000000010001020012000709789c63000000010001020012000809789c63000000010001020012000909789c63000000010001020012000a09789c63000000010001020012000b09789c63000000010001020012000c09789c63000000010001020012000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000301000600128169789cedd83b0ac2501445d1808d83718e164e5b881fac02828144c25b5b90c06ac271dfcadbe534cfd3f4fc7e1eafcffbb12f5f977cdffd95f13f793a2f129fa8c4ddf7e0dc7d77df63339d981e6f309d981e6f309d981e6f309d981e6f309d981e6f309d981e6f309df8cbf8757c9c39f192fbffbb8b1d9be9c4f47883e9c4f47883e9c4f47883e9c4f47883e9c4f47883e9c4f47883e9c45fc66fc2c75949335d871e6f309d981e6f309d981e6f309d981e1fabdcd9c72373f71d83dcd9c72373f71dff721083ddc960730c5e779a74e24d7eaaf8bf7c9cf389e3388ee3388ee378737e008d10fa6f010006001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010006001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff49490000000013000000000000003d01001e00050000000000000031020001000f00000000000000b0020003001300000000000000a802000500130000000000000073020007001100000000000000170200090007000000000000014202000a000d000000000000009902000c000e000000000000014c02000e000c00000000000000ec02000f000b000000000000012f02000f001600000000000000e80200100010000000000000016302001000110000000000000167020011000e000000000000018a020012000e000000000000008e020014000500000000000000b2020015000f00000000000000c40200170010000000000000010202001a000c000000000000004e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c31c618638c3fb8015324dafc010015000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010019000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000011f4c4c00000004010005001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010005001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010005001760789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010005001860789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000801000b000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000b000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000b94c4c00000006010004000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010004000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001134c4c000609789c63000000010001020005000709789c63000000010001020005000809789c63000000010001020005000909789c63000000010001020005000a09789c63000000010001020005000b09789c63000000010001020005000c09789c63000000010001020005000d09789c63000000010001020005000e09789c63000000010001020005000f09789c63000000010001020005001009789c63000000010001020005001109789c63000000010001020005001209789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ccde6f80b1d3b51c90f969d7877fc857ce76cabec93e7472720271b671f22912cdf18638c31c618633ccbcd235f3ece392f3fca393b3918638cd7f0152acce5a201000900118100789cedd2310a80301045c180d7f18e165e5b484cb0901482dd47676159986a8bb7af4bada59c7b9d3ee3608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c678e26de6e38973fec69fe777692a164b13ff9da58943599a3894a58943599a3894a5896fdc0085bae62001000900128165789ceddbcb0a8250188551c1490fd33b3ae8b503bb1a21082aa7dafcad208435b2fcf6e0049d8efd3876ddf5fdbcdc5ef7cb4f7878f0f0e2f33b77eb38e7e3e03abcad41c5e22f71779855d56b10d7e1c5be536e10e3f6bc90bd35e012ac6f8c27b6065c99f58df1c4d6802bb3be319e78db1a3ec939df09aec3fac6f81b6c3bb832eb1be37d6c3bb832eb1be37d9c7342f7a3006ecf39b1e91b638c31c618e3390f73f687c5bfe09c43135ecd39f9c473ce43c3fa6e5f6cce7d63bc86f58d2bb3be7165d637aecc39c7204bc3ed39a7587d633c71ced2ec12b7e79c62e3fbbe000536114900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c1d42d6377e19eb1bef9973fa963daee79c90658feb39a758d9e37ace491347704e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9afac6f59c93a6be713de7a4a96f5ccf3969ea1bd7734e9acbf90462c5a64600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff49490000000016000000000000019001000c0017000000000000006e01000d0005000000000000010e01000d000b000000000000016601000d0012000000000000000801000d001600000000000000a301000e0004000000000000001c01000e000d000000000000017801000e0011000000000000018901000e0015000000000000001801000e001900000000000000a601000f0005000000000000008c01000f000b00000000000000b501000f000f000000000000008601000f00130000000000000073010010000200000000000000970100100008000000000000008f010010000e000000000000018c0100100011000000000000010301001000150000000000000001010011000000000000000000f0010011000600000000000000bb010011000c0000000000000085000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c0000000601000c000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000bc4c4ce2d2ebdf1308813325f00d77c9fa98f6bdb5d7fe1cef751c3fe1a5e7ed02d7790ece6169e2a22c4d8c31c618638c31c618638c31c618638c31c618638c31c618638c31c6189f78e9f9ca678331aef3781ccffac6c92c4d9cccfac6c9ac6f9ccb6dee429e8e90bf709d7be37fe5b162f58defc6631387f904df8b158b9359df3899f58d9359df3899f58d93b978df4f901de60e01000c00128139789cedda410a83301040d1809b1ec63bbae8b50bb196bae8428818613a798204de2a307f7679ce53ada5bcffefb17d9f03e3162e8fe597a7fa3ae638f7c6b885f58dff8b158b33b3be7166d637ceccfa1e95cf4d7e0c8e331daceffe1c673a6639ea2cb1be5b38ce74f0d590e35c10e30bac6f8c31c618638c31c618638c31c618638c31c6f87e8ef37cc98b29dc9fe3c4a66f8cbbb26dc09959df18ef6c1b7066d637ceccfac69959df18ef6c1b7066d677085e019f82488000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010018001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013000f810f789cedd23b0a80301404c0401a0fe31d2dbcb6103f68612582c546261016a6dcb7f3585b2b65fb67ecef088c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ee6324c77ae6de988afb838a7581cc1718b7dc73fdf775cdfdd71ce2db17d7fcf39b73488df5f07dbf78d738ac511bc023cd97a4e0100130010811e789cedd2310ac2501045d10f695c8c7b4ce1b685af09361616c1c2bce71918064e35c5bd5d9739c778eeeb6cb31f8c31c618638c31c618638c31c618638c31c618638c31fe1f1e97f59d97793fd583187fc1fac659fca15821e30ad637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637fe351f6b50b1388bf55dcf0f3bb69d7601001300118109789cedd23b0a80400c40c1856d3c8c77b4f0da42fc606321586ee2044260aa146f9d7b446bc7dee79ceb64e5362d4feeb1bdf3387f63fc85f58d73b1627165d637cec58ac59559df38172b1657667de35cac585c99f58d4bb0627165d637c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6ffe11ddb70bac2000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001f020009000709789c63000000010001020009000809789c63000000010001020009000909789c63000000010001020009000a09789c63000000010001020009000b09789c63000000010001020009000c09789c63000000010001020009000d09789c63000000010001020009000e09789c63000000010001020009000f09789c63000000010001020009001024789c636ad66116622dc92f4ace6067606060b46560606262644413d581880200ac54065a020009001109789c6300000001000102000900121f789c636cd66116622dc92f4ace6067606060d4666060f2616404003374036e02000900131f789c636cd66116622dc92f4ace6067606060346760604a606400003403038d020009001409789c63000000010001020009001509789c63000000010001020009001609789c63000000010001020009001709789c63000000010001020009001809789c6300000001000102000a000009789c6300000001000102000a000109789c6300000001000102000a000209789c6300000001000102000a000309789c6300000001000102000a000409789c6300000001000102000a000509789c6300000001000102000a000609789c6300000001000102000a000709789c6300000001000102000a000809789c6300000001000102000a000909789c6300000001000102000a000a0978000000000000001b4c4c31c618638c3fb8015324dafc010014000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000007010018000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010018000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000304c4c000409789c6300000001000102001c000509789c6300000001000102001c000609789c6300000001000102001c000709789c6300000001000102001c000809789c6300000001000102001c000909789c6300000001000102001c000a09789c6300000001000102001c000b09789c6300000001000102001c000c09789c6300000001000102001c000d09789c6300000001000102001c000e09789c6300000001000102001c000f09789c6300000001000102001c001009789c6300000001000102001d000009789c6300000001000102001d000109789c6300000001000102001d000209789c6300000001000102001d000309789c6300000001000102001d000409789c6300000001000102001d000509789c6300000001000102001d000609789c6300000001000102001d000709789c6300000001000102001d000809789c6300000001000102001d000909789c6300000001000102001d000a09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01001b000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010016000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000019020011000e09789c63000000010001020011000f09789c6300000001000102001100108270789c8d52cd4ac34010dea46dd6946a5bf128a2174528fe5411f1a2480f5e44c427d86c36d9a54936ec6ea8de2c45fa003e435f43851ef4ea6378126f1e9d447a284870606067e69bbffdc61975aa9da5ba613a223a157d8631eab94fdf3d6b15e5f2fcf8717afea906d383ebadc231ea5446c74d2398dadf338c72ca99361802f63a68d5427362b5da9a24be36326131314c09120d438c5ca164e24592f62b183946424cc2ab1ec0143c12213736460d5f2833cb1a0b6cb566c645c66eb8088c7d1608710f7de6a0c3ab32a81b64513490d2df7730c286c569c4ba0f47d05caa0492f210586e4cc2982898b0bcb33310b4cf148cdb8ca5d6772901d3cfc759fc7f915afe49dd31f913733999bc00a612677e79951c31646508206f6345494f1a41a922811149688817b182c0dd9c40db828bd86935bc2c81b50a2a382cd76b7cbdcddfc4ebfbf47053ce6e62ad4d52764b496a884834e544a8a2e6c9ef510062b966a4a2bcf06e832e54d10f0ce1b96702001100111d789c636cd66116622dc92f4ace60676060603205622d464600335c0357020011001209789c63000000010001020011001309789c63000000010001020011001409789c63000000010001020011001509789c630000000100000000000001544c4c000309789c63000000010001020017000409789c63000000010001020017000509789c63000000010001020017000609789c63000000010001020017000709789c63000000010001020017000809789c63000000010001020017000909789c63000000010001020017000a09789c63000000010001020017000b09789c63000000010001020017000c09789c63000000010001020017000d09789c63000000010001020017000e09789c63000000010001020017000f09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010011000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000301001100108158789cedd6510a82401440d101215a436b688ff3d12e5a6b3095264208256a3ea723887a141cf52a5ece4d29293de6d7e239b50b8c31c6c1391d73bb724d876ebb29b78187a331de259fbab0dffbce69f4e845b83b25de98d77cc46182e8b7f3f059c718638c318ec0fddeef38ceb831d637fe77d637ae99f58d6b667de39a59dfb866d6f7dc7bb2578e73bf3d4b8c3ff0b4bebd0d786b5ef3fbad6fbc35fb3fc1788c4b9891603ca3d8691ce77270f5bc48b1fac63517ab6f5c73b1fac63fe33821638c31c618638c31c618638c31c618e3fdf11d15eba22901001100118117789cedd44b0a8020140550c149bb7003edb141db0eaca468d6c71a241c419e9ec11d5d1dfb987308cbdec6bacac018638c31c618638c31c618638c9be6d00de5d0a5ed1ef374c947086e815393acdf58bfeff151e4f07936c635fcec159f67eb37fe1beb37c63b9fd75ebf71db5cf5ad638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638cdff00c7b916878010011001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000ffffffffffffffff4c4c0000003002000c000e09789c6300000001000102000c000f1d789c636cd66116622dc92f4ace6067606060ec06e25f8c8c00381a047b02000c001009789c6300000001000102000c001109789c6300000001000102000c00121f789c636cd66116622dc92f4ace6067606060ec616060f262640000361403cc02000c00131f789c636cd66116622dc92f4ace6067606060ec6360604a61640400367103e902000c001409789c6300000001000102000c001509789c6300000001000102000c001609789c6300000001000102000c001709789c6300000001000102000c001809789c6300000001000102000d000009789c6300000001000102000d000109789c6300000001000102000d000209789c6300000001000102000d000309789c6300000001000102000d000409789c6300000001000102000d000509789c6300000001000102000d000609789c6300000001000102000d000709789c6300000001000102000d000809789c6300000001000102000d000909789c6300000001000102000d000a09789c6300000001000102000d000b09789c6300000001000102000d000c09789c6300000001000102000d000d09789c6300000001000102000d000e09789c6300000001000102000d000f09789c6300000001000102000d00101f789c636cd66116622dc92f4ace60676060605cc3c0c0c400000000000000754c4c00000003010010000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0100100010812b789cedd4510a02211440d11703d11a5a43d012fd985db4d6c06a68181cfa2d9e7604118efe8817e7cb546bc473be97d758168c31c6c9394ea5e5a9de31ee95e316c7864b7c3c8d71c3e7a59bf4bcef7b3d56b66f1d638c31c618e3bfe275f7879ce7f27878d6371e99f58d47667de391396fdf71ddf1e1eb9cee75f0b87d63dc65df18638c31c618638c31c618638c31c6b85fae18638c31c618638c31c618638c31c6382f3f005368bbe10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0205070000021800000205070000021900000205070000021a00000205070000021b00000205811b0000021c00000205070000021d00000205070000021e00000205070000021f00000205070000022000000205070000022100000205070000022200000205811b0000022300000205070000022400000205070000022500000205070000022600000205070000022700000205070000022800000205070000022900000205811b0000022a00000205070000022b00000205070000022c00000205070000022d00000205070000022e00000205070000022f00000205070000023000000205811b0000023100000205070000023200000205070000023300000205070000023400000205070000023500000205070000023600000205070000023700000205070000023800000205070000023900000205070000023a00000205070000023b00000205070000023c00000205070000023d00000205070000023e00000205070000020b00000204070000020c00000204070000020d00000204070000020e00000204070000020f00000204070000021000000204070000021100000204811b0000021200000204070000021300000204070000021400000204811b0000021500000204070000021600000204070000021700000204070000021800000204070000021900000204070000021a00000200000000000000044c4c00000002010014000f8106789cedd6410a80201005d001371da63bb6e8dac15462b869978b9027c81f1e6e842fb8af2533e2da2dee5503638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3578e65ab438b889247e77e7a20ffe7f2787ad66f8c87b2d7806766fdc6f8613f253c33eb37fec0275835b7d40100140010811a789cedd93b0a80400c40c1051b0fe31d2dbcb6b07eb05150d4cac459580253a5785d86aea9b594f96f6379ebf80797b6df7353c773fecede18df617de358fcac587de358ac6f1c8dfb238f57fc9dbd31d637cec78ac59959df38162b1667667de358ac589c99f58d13f0e92d43df3801bbec618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ec3130184cc4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4ccde6f80b1d3b51c90f969d7877fc857ce76cabec93e7472720271b671f22912cdf18638c31c618633ccbcd235f3ece392f3fca393b3918638cd7f0152acce5a201000900118100789cedd2310a80301045c180d7f18e165e5b484cb0901482dd47676159986a8bb7af4bada59c7b9d3ee3608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c678e26de6e38973fec69fe777692a164b13ff9da58943599a3894a58943599a3894a5896fdc0085bae62001000900128165789ceddbcb0a8250188551c1490fd33b3ae8b503bb1a21082aa7dafcad208435b2fcf6e0049d8efd3876ddf5fdbcdc5ef7cb4f7878f0f0e2f33b77eb38e7e3e03abcad41c5e22f71779855d56b10d7e1c5be536e10e3f6bc90bd35e012ac6f8c27b6065c99f58df1c4d6802bb3be319e78db1a3ec939df09aec3fac6f81b6c3bb832eb1be37d6c3bb832eb1be37d9c7342f7a3006ecf39b1e91b638c31c618e3390f73f687c5bfe09c43135ecd39f9c473ce43c3fa6e5f6cce7d63bc86f58d2bb3be7165d637aecc39c7204bc3ed39a7587d633c71ced2ec12b7e79c62e3fbbe000536114900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc010006001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0100060011813f789cedd74b0a83401005c00137394ceee822d70e1895ac041749103afd4a90815af5e70de8e33e2dcb18ebfb3eb6673f30c618638c31c618638c31c618638c31c618638c31c618638c31c618638cf1b73c1ff989eb709d9c946713accc2ec94513ac5360345fb99d66b7e1b3baffb5cb666c0d46d5992dcd4c3ab35d9a49678edee549f3d13369c6c1bb1cb743f3937cb7e3e05d9ee6bb4a8158be7f60f90ee0e84fd08c2ea359bedb7719cdf2ddbecb6896eff65d06739d5fac17355fef4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000001802000e000c09789c6300000001000102000e000d09789c6300000001000102000e000e09789c6300000001000102000e000f09789c6300000001000102000e001024789c636ad66116622dc92f4ace6067606060bcc8c0c0c4c2c888267a00220a00c15c078602000e00111f789c636cd66116622dc92f4ace60676060603cc3c0c064c0c80000378603f202000e001209789c6300000001000102000e001309789c6300000001000102000e001409789c6300000001000102000e001509789c6300000001000102000e001609789c6300000001000102000e001709789c6300000001000102000e001809789c6300000001000102000f000039789c636cd661d113e0292ec9cf4b4d2b2dca4b4c4e65626770fe70dbc73ee2b78cf952f76d87e6868a3bb76aef38c4b62a90010400bd87112f02000f000109789c6300000001000102000f000209789c6300000001000102000f000309789c6300000001000102000f000409789c6300000001000102000f000509789c6300000001000102000f000609789c6300000001000102000f000709789c6300000001000102000f000809789c6300000001000102000f000909789c6300000001000102000f000a09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0001020011001609789c63000000010001020011001709789c63000000010001020011001809789c63000000010001020012000009789c63000000010001020012000109789c63000000010001020012000209789c63000000010001020012000309789c63000000010001020012000409789c63000000010001020012000509789c63000000010001020012000609789c63000000010001020012000709789c63000000010001020012000809789c63000000010001020012000909789c63000000010001020012000a09789c63000000010001020012000b09789c63000000010001020012000c09789c63000000010001020012000d09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000003010013000f810f789cedd23b0a80301404c0401a0fe31d2dbcb6103f68612582c546261016a6dcb7f3585b2b65fb67ecef088c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c317ee6324c77ae6de988afb838a7581cc1718b7dc73fdf775cdfdd71ce2db17d7fcf39b73488df5f07dbf78d738ac511bc023cd97a4e0100130010811e789cedd2310ac2501045d10f695c8c7b4ce1b685af09361616c1c2bce71918064e35c5bd5d9739c778eeeb6cb31f8c31c618638c31c618638c31c618638c31c618638c31fe1f1e97f59d97793fd583187fc1fac659fca15821e30ad637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637ce62c5e266d637fe351f6b50b1388bf55dcf0f3bb69d7601001300118109789cedd23b0a80400c40c1856d3c8c77b4f0da42fc606321586ee2044260aa146f9d7b446bc7dee79ceb64e5362d4feeb1bdf3387f63fc85f58d73b1627165d637cec58ac59559df38172b1657667de35cac585c99f58d4bb0627165d637c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6ffe11ddb70bac2000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000006010009000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010009000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000900108179789ceddbdd0a82301806e041275d4cf7e841b71d2c2c4508358d7e5ef30165f0e0c1e7f60edc70e7d3a11c9b526b7bddee520ef5d23618e38e6bbd71697a2e61dcd53d705a0fe28f45b38ce704638c31c618638c31c618638c31c618638c31c618638c31c6781b3cff23ec9f71df074f9ece191d2cdff29d97937d5412c743beefcd04e71c36c839df9093aa9c4ab6ca396193efc5954c9e4f7bdfdc59d7b1f1336da8fbdeec8a73f3bdf6fce50bf95e17e4852cb111fcce85000000000000013b4c4c00000016020010001109789c63000000010001020010001209789c63000000010001020010001309789c63000000010001020010001409789c63000000010001020010001509789c63000000010001020010001609789c63000000010001020010001709789c63000000010001020010001809789c63000000010001020011000039789c636cd661d117e44d2acdcb4e2d2ac82f2e492d3262626770e1b22ab06f9f50c3000487e6868639372c9d7548eb6e0288cf000090840f60020011000109789c63000000010001020011000209789c63000000010001020011000309789c63000000010001020011000409789c63000000010001020011000509789c63000000010001020011000609789c63000000010001020011000709789c63000000010001020011000809789c63000000010001020011000909789c63000000010001020011000a09789c63000000010001020011000b09789c63000000010001020011000c09789c63000000010001020011000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000016020010001109789c63000000010001020010001209789c63000000010001020010001309789c63000000010001020010001409789c63000000010001020010001509789c63000000010001020010001609789c63000000010001020010001709789c63000000010001020010001809789c63000000010001020011000039789c636cd661d117e44d2acdcb4e2d2ac82f2e492d3262626770e1b22ab06f9f50c3000487e6868639372c9d7548eb6e0288cf000090840f60020011000109789c63000000010001020011000209789c63000000010001020011000309789c63000000010001020011000409789c63000000010001020011000509789c63000000010001020011000609789c63000000010001020011000709789c63000000010001020011000809789c63000000010001020011000909789c63000000010001020011000a09789c63000000010001020011000b09789c63000000010001020011000c09789c63000000010001020011000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010017001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010017001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010018000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000301001100108158789cedd6510a82401440d101215a436b688ff3d12e5a6b3095264208256a3ea723887a141cf52a5ece4d29293de6d7e239b50b8c31c6c1391d73bb724d876ebb29b78187a331de259fbab0dffbce69f4e845b83b25de98d77cc46182e8b7f3f059c718638c318ec0fddeef38ceb831d637fe77d637ae99f58d6b667de39a59dfb866d6f7dc7bb2578e73bf3d4b8c3ff0b4bebd0d786b5ef3fbad6fbc35fb3fc1788c4b9891603ca3d8691ce77270f5bc48b1fac63517ab6f5c73b1fac63fe33821638c31c618638c31c618638c31c618e3fdf11d15eba22901001100118117789cedd44b0a8020140550c149bb7003edb141db0eaca468d6c71a241c419e9ec11d5d1dfb987308cbdec6bacac018638c31c618638c31c618638c9be6d00de5d0a5ed1ef374c947086e815393acdf58bfeff151e4f07936c635fcec159f67eb37fe1beb37c63b9fd75ebf71db5cf5ad638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638cdff00c7b916878010011001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000ffffffffffffffff4c4c0000000401000e000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e00108210789cedd55d6a8350148551212f1d43c6d039de874e5b306d4d0249488b70ccdd9a2588b884e3df877e7d1ea66918bed7f3e667f9dd608c310ee7e1a3ddf2611a31c618afcb6dde9f8ffecbc799dbed90cbd02b0f182fe0b622e7dc25de0f4b1387b2347128fbcfe3505e334dc5e2de6996ccce792638823b7c07158b43d3542cde569a8ac5bb0822fe02f17bf0e5e8599e73bbe7f12f7e1882b7cead82979cb273b14f66f77e0d788fbc66b1cb38e799e0cdf24b8bd537eecd00000000000001044c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010017000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010017000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01000e000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000005010012001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010012001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000a949490000000016000000000000019101000c0017000000000000006e01000d0005000000000000010e01000d000b000000000000018b01000d001200000000000000b101000d001600000000000000a301000e0004000000000000001c01000e000d000000000000015c01000e0011000000000000015801000e0015000000000000001801000e001900000000000000a601000f0005000000000000008c01000f000b00000000000000db01000f000f000000000000018d01000f001300000000000000d101001000020000000000000097010010000800000000000000ce010010000e00000000000000890100100011000000000000007d01001000150000000000000001010011000000000000000000f0010011000600000000000000bb010011000c000000000000014a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00000006010015000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000fd4c4c0000000401000c000f812d789cedd7410a83301445d180932ec63d3ae8b60b5aa5d03af34fd2473c42089c51c06b4c9ef3b4aeadbdc767da9f63c218638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8875f25ce59374ee7f658ce3c1d5575e0b654f80f0becc939410cc6fa8ee09c20c6e46f55a75301eec4be067d8fccb59f575c3ef11cf08af165b6dbeb7b64aedd60e262d337b6dbeb1b57fabee7d97e03a3c0dfa901000c00108217789ceddbd10ac22000466161373d4cefe845af1d58e1245b1473eafcd513c4ea8b9a73c70862b7ebe29c31afbbf59be7cddd8d7f0ccfc8e6623f798939bc29f0fafccd7e23c702130b4b307dc370e01fd9ebb0ce54c11d72dab73d7dc3300cc3300cc3296c6546029f7b8ae1735927885e597e62a7ee64ea833fc0e1d555c29cc42c396e3883275a24bbfbfe97fdf787881d255c9ee517c981628bb0c4d98125b8e62269d577af6ba7c12e399cdd21cfd1379ccb451649835dd61c200cc3300cc3300cc3300cc3300cc37db1ce95449d5fc10a4bb24ec8f40d0fc1f40d67b1ddb2d61ff1f43d2fa7a559af587e71c01b5649b308d3f7483c549a459862459834ab307d93e6104cb1a439320fd73769c2113f003d6052ee01000c0011812c789ceddbb10a83301486d1808b0fd37774f0b505db4a17031d000000000000001e4c4c0000000102001000108713789cd5564b8f1347101e7bed1de3dd35fb4248211c3811210e61052750b2089090224591382245d4cc94673aeec7d0dd63af39907d84dd1b97fc8828bf22e427e49e4338e61c7186eaf18cb167c7866b2cb534dd5f5575d5578ff6c5939bad1beb1d2380f3408d9abef7d00bdf3f6c7ce9b9df1fbffefbed83d3df5ffef96ef8777e707273e5f4871ea47860129672150e50fb74dedca1b5d6f0e67e8dfdce501d20bff5f5e08cf9deff66e36b8c8c65e2132a01cfb014eb8e9173359a6acd621d09528d34a46e7321d688b284666f9a47aa06676d74330d92654269acd9ce892ed39b62dbcb94d68ce2a017ded675b530a9834ef5ba888150322a24fd58f1f2bb6af4dc3ee560999cee97e9ce61553da695acfb9ed159f4fd291f2be1ada73cb3aa60c63fc793d758bca8a9aeed268ab308c6a1863e45105b0838e69db54aabd56c90d0e5ae45c3c1a46c3081ba0e6a3bfd5d3f8171ca0a9516ad8d8657a77261aa720eea4ca15fc25e9850fd0a8c60c840eee5f86587575b7cb3176b182217605133e0cfa99005c40234d5c4abcc6f6c96c8e30c9f24ac6f9bfb7dc60e49b76540464f89b988695b4add5fa63127b9532ba9cfce9c644fa808b574e5d0575a1c7f45b935907262f6f83bbfd53116c5f720b04d5d07c6586abd7577f6d1d0e67f57f7c9d05a9ff20231e600000000000000e64c4c00000003010012000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000f73789cedd2310a80301004c083343e267fb4c8b7854b2236e91502cec2b130ed6dab253362dc533377618c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f85b8ee35cb9e4f51bdee70dd8bedfe70e9caabac201001200108159789cedd84d0a83301446d18050ba86aea17b74d06d17d21f1cb4410a81809ff508229c81a8ef46c4db75aab594e7be1c5edbfb90c3e53c7ff354efb887736689f53d9e7366299fb5e9cced75ffe494078b23b85cca692db63e9e9b730fe495ebeee3ace78d37e125aeb693bdf2e75d5a0d3828cdccd5103934acef4d1649e4880fcd399dec95736689f5adef63714e27f19c3334ac6f21e3a8aa848cc7734e6cfe68e3f19c13b2772c1ecf8ac5ffccc3bf0a848c43599a18638c31c618638c31c618638c31c618638c31c618638c31c618631cc80f310beafd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000701000d000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c000000000000003c4c4c0000000b02000f000b09789c6300000001000102000f000c09789c6300000001000102000f000d09789c6300000001000102000f000e09789c6300000001000102000f000f09789c6300000001000102000f00108108789c636bd66196e0cec84fce4ead2c494cca496567606060fcc2c0c0c4c2c8009493e24d2acdcb4e2d2ac82f2e492d3202cb7e04ca72826525b8931253d2f3f30b724a8b33c0722fe13a6505120b522b8a5293f38b520a72122b538bc00afe83143031021508b196e4172543b4fd048a72b180b4897182b4156426e6e583657e41d4030041db264702000f001109789c6300000001000102000f001209789c6300000001000102000f001309789c6300000001000102000f001409789c6300000001000102000f001509789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c09789c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010007001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010007001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401001a000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a001060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c8c68a2e5105100ceb408c402000b00101f789c636cd66116622dc92f4ace60676060602c656060926064000034dd038302000b001109789c6300000001000102000b00121f789c636cd66116622dc92f4ace6067606060ac6560600a6662040035c903c802000b001309789c6300000001000102000b001409789c6300000001000102000b001509789c6300000001000102000b001609789c6300000001000102000b001709789c6300000001000102000b001809789c6300000001000102000c000009789c6300000001000102000c000109789c6300000001000102000c000209789c6300000001000102000c000309789c6300000001000102000c000409789c6300000001000102000c000509789c6300000001000102000c000609789c6300000001000102000c000709789c6300000001000102000c000809789c6300000001000102000c000909789c6300000001000102000c000a09789c6300000001000102000c000b09789c6300000001000102000c000c09789c6300000001000102000c000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000004010011000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000201000800108217789ceddcd10a82301487f181373d4cefe845af1d189630a81869eabe9d7d900c7f17d1cef9db0c5cb7eb902e639aa6f9f53c521aa6fb3cace2e53cf36b9011bc4b8bbb664e2f65f3fd2d9a69cc3559cfcb7b67a6cd127f5de25bdc3597f3cd8dbdf9962333ee4a03d4448ec39bd69d43985313599665599665599665599665599665599665599665599665599665f97cf6993339329b6f3932fb4cbc1c89736297c17c83bf4f3631602daeb78be3cc0a7a935325df3f722bb3ec33df9c7d5e8def2fae109fcf9a8cef1fb0c8940a96b9d57cffdb4b4a1b482b49a87ce357d73203261fee7f2dccf72ef98e3279f38de8651f4ccac991a9e230a7823db0f936dfb599f4eba84de6dc29e1d97c37c89cf8e0f9011c25e69801000800118152789cedda4b0a83301486d180932ea67b74d06d0be9833aa8d05221b497df2348e04c54f2dd41c0cb79eabdb5dbfd5ceed763d9c7ed34bff2d497f73ce49118ff8cf58d9359df3899f58d9359df3899f58d8bf2be3487709d8fc7f1ac6f9ccc1ac4c9ac6f9cccfac6c9ac6f9cccfac6c9ac6f8c31c618638c31c623d8f91227b3be7132eb1b27b3be7132ffe1ffd863709d2d3e34d709228ceb6c31c6df709dd93169783cd72956df18af6c1a7032eb1be3954d034ee6b8bee72d2f9fb8ce7be3021c772ede370d86249ccbf77d00000000000000364c4c00000007010018001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9010019000060789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001794c4c00000029020012000e09789c63000000010001020012000f09789c6300000001000102001200101d789c636cd66116622dc92f4ace60676060607204624e1646003353034502001200111d789c636cd66116622dc92f4ace6067606060f205620346460034160375020012001209789c63000000010001020012001309789c63000000010001020012001409789c63000000010001020012001509789c63000000010001020012001609789c63000000010001020012001709789c63000000010001020012001809789c63000000010001020013000009789c63000000010001020013000109789c63000000010001020013000209789c63000000010001020013000309789c63000000010001020013000409789c63000000010001020013000509789c63000000010001020013000609789c63000000010001020013000709789c63000000010001020013000809789c63000000010001020013000909789c63000000010001020013000a09789c63000000010001020013000b09789c63000000010001020013000c09789c63000000010001020013000d09789c63000000010001020013000e09789c63000000010001020013000f09789c6300000001000102001300102a789c636ed66116622dc92f4ace6067606060ca076239460634d1342066606444134d87880200820f0a3f020013001100000000000000924949000000001700000000000000a401001100130000000000000181010012000200000000000000d9010012000b00000000000000fb010012000e00000000000000b80100120011000000000000019301001200150000000000000160010013000000000000000001300100130006000000000000001a010013000c000000000000005d010013000f000000000000015601001300120000000000000171010013001500000000000000d20100140000000000000000005e01001400060000000000000011010014000c000000000000012d010014000f000000000000014f0100140011000000000000019501001400150000000000000174010015000100000000000000af01001500070000000000000176010015000d000000000000016201001500130000000000000122010015001700000000000000ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00000003010013001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010013001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4949010000000600000000000000c9010004000a00000000000000350100090003000000000000019401000c0013000000000000013d010011001000000000000000260100160003000000000000019101001d0010000000000000011e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c000209070000022b00000209070000022c00000209070000022d00000209070000022e00000209070000022f00000209070000020b00000208070000020c00000208070000020d00000208070000020e00000208070000020f00000208070000021000000208070000021100000208811b0000021200000208070000021300000208070000021400000208811b0000021500000208070000021600000208070000021700000208070000021800000208070000021900000208070000021a00000208070000021b00000208811b0000021c00000208070000021d00000208070000021e00000208070000021f00000208070000022000000208070000022100000208070000022200000208811b0000022300000208070000022400000208070000022500000208070000022600000208070000022700000208070000022800000208070000022900000208811b0000022a00000208070000022b00000208070000022c00000208070000022d00000208070000022e00000208070000022f00000208070000023000000208811b0000023100000208070000023200000208070000023300000208070000023400000208070000023500000208070000023600000208070000023700000208070000023800000208070000023900000208070000020b00000207070000020c00000207070000020d000000000000000000714c4c00000006010014001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001660789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010014001760789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010014001860789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010014001960789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c600000000000000e94c4c02070d617065786675656c68617463680007010b756e627265616b61626c650300000002090000020a0c626f6f73746572666c616d650007010b756e627265616b61626c6503000000022d0000020c0e68796c6f746c6c616e7465726e31000700000002260000020c0e68796c6f746c6c616e7465726e310007000000021f0000020c0e68796c6f746c6c616e7465726e31000700000002180000020c0e68796c6f746c6c616e7465726e310007000000020e0000020c0e68796c6f746c6c616e7465726e31000700830f0000020b0000020c070000020c0000020c070000020d0000020c070000020e0000020c070000020f0000020c07000002100000020c07000002110000020c811b000002120000020c07000002130000020c07000002140000020c811b000002150000020c07000002160000020c07000002170000020c07000002180000020c07000002190000020c070000021a0000020c070000021b0000020c811b0000021c0000020c070000021d0000020c070000021e0000020c070000021f0000020c07000002200000020c07000002210000020c07000002220000020c811b000002230000020c07000002240000020c07000002250000020c07000002260000020c07000002270000020c07000002280000020c07000002290000020c811b0000022a0000020c070000022b0000000000000000010f4c4c00000006010015000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000860789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000960789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000a60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010015000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000001344c4c31c618638c3fb8015324dafc01001a000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01001a000760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000e000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e00108210789cedd55d6a8350148551212f1d43c6d039de874e5b306d4d0249488b70ccdd9a2588b884e3df877e7d1ea66918bed7f3e667f9dd608c310ee7e1a3ddf2611a31c618afcb6dde9f8ffecbc799dbed90cbd02b0f182fe0b622e7dc25de0f4b1387b2347128fbcfe3505e334dc5e2de6996ccce792638823b7c07158b43d3542cde569a8ac5bb0822fe02f17bf0e5e8599e73bbe7f12f7e1882b7cead82979cb273b14f66f77e0d788fbc66b1cb38e799e0cdf24b8bd537eecd000000000000013c4c4c31c618638c3fb8015324dafc010019000460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019000560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000000080100080012813a789cedd3410ac2401004c0855c7c8c7ff4e0b785a8c15320a0c9c68c6d05c2429d66a77bafe7611c5b7bfcafe3f94dc7be7c99f36d051f3037fe26b7d32cf9614a7e81ebccdd893d9270fef37e77e12e8f041760b5ffa0df7506c46fb27eeb7736cbd24e9259967692ccb2b4936496a59d24b32ced2499656927c91c96e5c275ba709d5be2ad85a833890ee2a35935f16fb16a628c31c618638c31c618638c31c618638c31c618638c31c618638c31c61863bc17df018847ec5a010008001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010008001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324da00000000000000194c4c9853dec19cf20ee6946f55f13c7336d8bee379e66cb07dc7f3ccd960fb8ee799b3c1f61dcf336783ed3b9e67ce06db773ccf9c0db6ef789e391b6cdff13c7336d8bee379e66cb07dc7f3ccd960fb8ee799b3c1f61dcf336783ed3bfe926f6a1d789801000f0012811d789cedd2d10983501044d105db4997b62dbc2858c2480673168685f37df7cfb6d6ccb9fb5db78e99793f2ffca7dcd3a0be719e7b1ad437ce734f83fac679ee6950df38cf3d0dea1be7b9a7417de33cf734a86f9ce79e06f58df3dcd3a0be719e7b1ad437ce734f83d2c47916327e333f99a6bef1af59df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c61887f80bb4dbd96d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000309789c63000000010001020017000409789c63000000010001020017000509789c63000000010001020017000609789c63000000010001020017000709789c63000000010001020017000809789c63000000010001020017000909789c63000000010001020017000a09789c63000000010001020017000b09789c63000000010001020017000c09789c63000000010001020017000d09789c63000000010001020017000e09789c63000000010001020017000f09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c31c618638c3fb8015324dafc01001a000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000d001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000d001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0001020015000c09789c63000000010001020015000d09789c63000000010001020015000e09789c630000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4cc2c80000362203a602000d001124789c636ad66116622dc92f4ace6067606060dcc0c0c064c2c88026ba0b220a00c29d07bd02000d001209789c6300000001000102000d001309789c6300000001000102000d001409789c6300000001000102000d001509789c6300000001000102000d001609789c6300000001000102000d001709789c6300000001000102000d001809789c6300000001000102000e000067789c636ed661d113e0292ec9cf4b4d2b2dca4b4c4e656667707eff5fd2dee4480c03101c9a1bcae9dc6ae270882d8d17c467c0d4c104d4f16e528ddd67a5b21d8ee5c6401ddc047480ed50fc626fdaceb08321a709a843cab9d5a2e5109bf36bb00e008c3a2e6102000e000109789c6300000001000102000e000209789c6300000001000102000e000309789c6300000001000102000e000409789c6300000001000102000e000509789c6300000001000102000e000609789c6300000001000102000e000709789c6300000001000102000e000809789c6300000001000102000e000909789c6300000001000102000e000a09789c6300000001000102000e000b09789c6300000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000009010011001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010011001760789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c00000000000000d14c4c00000003010012000b60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012000d60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff49490000000017000000000000015b0100110013000000000000017f010012000200000000000000d9010012000b00000000000000f6010012000e00000000000001540100120011000000000000012d01001200150000000000000160010013000000000000000001300100130006000000000000001a010013000c0000000000000153010013000f00000000000001410100130012000000000000009e010013001500000000000000d20100140000000000000000005e01001400060000000000000011010014000c000000000000013d010014000f00000000000000a70100140011000000000000008501001400150000000000000174010015000100000000000000af01001500070000000000000149010015000d00000000000000900100150013000000000000017e010015001700000000000000ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00000004010019001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010019001460789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010019001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c18638c31c6f8830fc493e2f900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c09789c63000000010001020013001209789c63000000010001020013001309789c63000000010001020013001409789c63000000010001020013001509789c63000000010001020013001609789c63000000010001020013001709789c63000000010001020013001809789c63000000010001020014000009789c63000000010001020014000109789c63000000010001020014000209789c63000000010001020014000309789c63000000010001020014000409789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4949010000000600000000000000c9010004000a0000000000000035010009000300000000000000f601000c0013000000000000018b010011001000000000000001700100160003000000000000019101001d001000000000000000c80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000494901000000060000000000000016010004000a000000000000002d010009000300000000000000b401000c0013000000000000016101001100100000000000000183010016000300000000000000d001001d0010000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c0000000401000e0011817e789ceddacb0a8250184561c1490fd33b36e8b503bb100e2288533b5b6d962007be91fe2c8e0a1ef7f3b24cd3e5bc2fd7e3b6c8f2caa72ee60c564630274dfb96f3cc49d3bee53c73d2b46f39cf9c34ed5bce33274dfb96f3cc49d3bee53c73d2b46f39cf9c34ed5bce33274dfb963fe6c3f34e36e769f7c0f31b4c1aac8c604adf21e60c16ce91fd6474f7210451c6acaa386cdf1dccaa8a532ce7ba37664e9af66ddf79e6a459d8f7771fff94bb4433274dfbb6ef3c73d21ce71f7c34d9a09c65fb969bf99fdf4f64f915dbb7dcccf62d37b37dcbcd6cdf7233dbb7dcccf62d37b37dcbcd6cdf7233dbb75cc185ff57c9f2caf62d37f378df671919abea01000e00127e789cedd2310ac020104541c1ebe4965e5b301ad2a44f21df11d685e9165ebbea18a5cc79d77acfc27843ee7ff03ee760fc617de30359df3899f58d9359df3899f58d9359df3899f58d9359df3899f58d9359df18638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e3f9ddf90fdab401000e001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000e000000000000013a4c4c00000019020011000e09789c63000000010001020011000f09789c630000000100010200110010826c789c8d92cd4ac340108037699b98926a2b1ea5d4a350d4f620a207057bf0523cf8049bcd26bb34c986dd0dd59b25481fa0cfd0d7a8422f7d158fde3c3a89f45090e0c2c0cecc37bf8c95f76bdd0e4ee933c1a9c63c5184612e6d8490790d523750deaf9fb55d2f4b2654463c649a99361ab95f9b91718c8af7bef8bcbdef4e37ebd6222f0d90f3b0a18524accc730ab2572fac27475278427342240e344f428dbd8896d07951cc3400caaf0e34a77270a129618451a54ba0f7dbcdce33da1d85135f6991d0186b2a398e66a18d1c2e45e245824c6a36b2b4009f805f3388b062e50c3082eb73a9b751736e1bedadf290d127c6036dde059cbf429d1d74f658853a4116455321fc8165235bd3388de8f0ed128a0b994050e102cd89711863091d5657b6a69cc0dea1dd835828f5926250fda29dd6ff93348a250de7f84f66bc5c7e00538b33bf3a4b41cc68150197d2df6f6aaa60cd299f501beec4597defdec9b8b7765737a5e1078b25b7a602001100111d789c636cd66116622dc92f4ace60676060603205622d464600335c0357020011001209789c63000000010001020011001309789c63000000010001020011001409789c63000000010001020011001509789c630000000100010200000000000000013149490000000016000000000000019001000c0017000000000000006e01000d0005000000000000010e01000d000b000000000000004301000d0012000000000000017e01000d001600000000000000a301000e0004000000000000001c01000e000d000000000000001601000e0011000000000000011901000e0015000000000000001801000e001900000000000000a601000f0005000000000000008c01000f000b00000000000000c101000f000f000000000000010b01000f001300000000000000730100100002000000000000009701001000080000000000000095010010000e000000000000014d0100100011000000000000002401001000150000000000000001010011000000000000000000f0010011000600000000000000bb010011000c000000000000016c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00000003010010000e60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010010000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc0100100010812b789cedd4510a02211440d11703d11a5a43d012fd985db4d6c06a68181cfa2d9e7604118efe8817e7cb546bc473be97d758168c31c6c9394ea5e5a9de31ee95e316c7864b7c3c8d71c3e7a59bf4bcef7b3d56b66f1d638c31c618e3bfe275f7879ce7f27878d6371e99f58d47667de391396fdf71ddf1e1eb9cee75f0b87d63dc65df18638c31c618638c31c618638c31c6b85fae18638c31c618638c31c618638c31c6382f3f005368bbe10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000f000f60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000f0010815a789cedd25b0a82501486d113bd34865e9a4073f4a16907762183a2422be2cfbd04d9b884e3e53bbbedb2ef5b3b9e97713ace03638c7138b75577cbcb7e3f5b1ec6473cfa919b491cf6ab66c3eb369d5b3785c7aedd3d5ee49b7cbf63dfe127ef8d2379b8be6e888631c618638cf11ff2e2f79cf3f118638c31c618638c31c6b3e3e12ec618638c31c618638c31c618638c31c6f835f7f8a79c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef91a9c53be06e794afc139e56b704ef919f2014cb97fb101000f00118206789cedd54daa02311406d18093b718f7e8c06d3f883f08e2d84b5358d51042ceb43ee8ebf9b4f75af7f3ba1edffe5f6bc5c7f18e0f654e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604e790773ca3b9853dec19cf20ee6947730a7bc8339e51dcc29ef604e790773caff20afbfcb279f9e19e2388e6338bfde6f26fdb91dbce3439953dec19cf20ee6947730a7bc8339e51dcc29ef604e790773ca3b000000000000017b4c4c0000000401000c000f812d789cedd7410a83301445d180932ec63d3ae8b60b5aa5d03af34fd2473c42089c51c06b4c9ef3b4aeadbdc767da9f63c218638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8875f25ce59374ee7f658ce3c1d5575e0b654f80f0becc939410cc6fa8ee09c20c6e46f55a75301eec4be067d8fccb59f575c3ef11cf08af165b6dbeb7b64aedd60e262d337b6dbeb1b57fabee7d97e03a3c0dfa901000c00108217789ceddbd10ac22000466161373d4cefe845af1d58e1245b1473eafcd513c4ea8b9a73c70862b7ebe29c31afbbf59be7cddd8d7f0ccfc8e6623f798939bc29f0fafccd7e23c702130b4b307dc370e01fd9ebb0ce54c11d72dab73d7dc3300cc3300cc3296c6546029f7b8ae1735927885e597e62a7ee64ea833fc0e1d555c29cc42c396e3883275a24bbfbfe97fdf787881d255c9ee517c981628bb0c4d98125b8e62269d577af6ba7c12e399cdd21cfd1379ccb451649835dd61c200cc3300cc3300cc3300cc3300cc37db1ce95449d5fc10a4bb24ec8f40d0fc1f40d67b1ddb2d61ff1f43d2fa7a559af587e71c01b5649b308d3f7483c549a459862459834ab307d93e6104cb1a439320fd73769c2113f003d6052ee01000c0011812c789ceddbb10a83301486d1808b0fd37774f0b505db4a17031d00000000000000374c4c31c618638c3fb8015324dafc010007000c60789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c0000000401000c0013810b789cedd23b0a80301005c0401a0fe31d2dbcb6b07eb0b110221a4864169685a916de9bc71c91d2b6e7d9e738f82da761ba728ee59edbf91be312d66fdc173f6bac7ee3be58bf3be49aa1fd8cdb090debf7f7dc4e6818638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e32abc028c09a35201000c001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c001560789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc01000c001660789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4949000000001e000000000000010a01001600090000000000000048010016000f00000000000000e0010016001300000000000001970100160017000000000000012501001700060000000000000013010017000c000000000000015e0100170010000000000000012e0100170014000000000000015a0100180001000000000000014401001800080000000000000111010018000e0000000000000127010018001200000000000001400100180016000000000000016e010019000600000000000000d3010019000c0000000000000135010019001200000000000001840100190016000000000000010001001a0008000000000000009301001a000e000000000000016a01001a0012000000000000004601001b0000000000000000005f01001b000600000000000000f701001b000c00000000000000cc01001b001200000000000000d701001c000000000000000000e101001c0007000000000000010c01001c000d000000000000008d01001c0011000000000000000701001d0004000000000000003901001d000c000000000000010800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c8c68a259105100cfd208c402000b00101f789c636cd66116622dc92f4ace60676060602c656060926064000034dd038302000b001109789c6300000001000102000b00121f789c636cd66116622dc92f4ace6067606060ac6560600a6662040035c903c802000b001309789c6300000001000102000b001409789c6300000001000102000b001509789c6300000001000102000b001609789c6300000001000102000b001709789c6300000001000102000b001809789c6300000001000102000c000009789c6300000001000102000c000109789c6300000001000102000c000209789c6300000001000102000c000309789c6300000001000102000c000409789c6300000001000102000c000509789c6300000001000102000c000609789c6300000001000102000c000709789c6300000001000102000c000809789c6300000001000102000c000909789c6300000001000102000c000a09789c6300000001000102000c000b09789c6300000001000102000c000c09789c6300000001000102000c000d09789c63000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c000000040100120011810e789cedd23d0a80300c06d042170fe31d1dbcb6507f7071108a421be51542e02d4dd36f1e7329296d75b6fd1c0d638c31c618638c31c618638c31c618638c31c64f380dd3957359420d88f10bbec9f7cf38cebe71db0c76b852be71b3b0c579e557378865b037c7f94b2cdf18638c31c618638c31c618638c31c618638c31c618638c31c618638cab790549ac7d3c010012001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010012001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff494900000000120000000000000041010009000500000000000000e5010009000d0000000000000157010009001300000000000000ac0100090017000000000000000201000a0004000000000000005901000a000b000000000000001001000a000f000000000000000d01000a0011000000000000001201000a0014000000000000002301000a001800000000000000ae01000b0004000000000000013701000b000c000000000000004201000b000f000000000000004501000b001300000000000000fe01000b0017000000000000006001000c0005000000000000013e01000c000b00000000000000d501000c000f000000000000018e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4c00000004010014001160789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001260789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010014001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff4c4c00000023020014000509789c63000000010001020014000609789c63000000010001020014000709789c63000000010001020014000809789c63000000010001020014000909789c63000000010001020014000a09789c63000000010001020014000b09789c63000000010001020014000c09789c63000000010001020014000d09789c63000000010001020014000e09789c63000000010001020014000f09789c6300000001000102001400101d789c636cd66116622dc92f4ace60676060606a076206464600351c037f020014001109789c63000000010001020014001209789c63000000010001020014001309789c63000000010001020014001409789c63000000010001020014001509789c63000000010001020014001609789c63000000010001020014001709789c63000000010001020014001809789c63000000010001020015000009789c63000000010001020015000109789c63000000010001020015000209789c63000000010001020015000309789c63000000010001020015000409789c63000000010001020015000509789c63000000010001020015000609789c63000000010001020015000709789c63000000010001020015000809789c63000000010001020015000909789c63000000010001020015000a09789c63000000010001020015000b09789c6300000001000000000000017f4c4c00000004010016001360789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001460789cedc8c10900200c04c140dab14b0b4f8c5881ef39381666afac8a387f99dd608c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c3fb8015324dafc010016001560789cedc8c10900200c04c12029c72e2d3c096205bee7e05898b35755c4fc65963718638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618e30f6eca34eafb010016001660789cedc8b10900201004c10b2cc72eeddbf7116cc07836599835533ba97aebc61dc618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c618638c31c6f8830fc493e2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff00", - "world_start": "845a000004410000040f844600000000000000000000000000000000000000000000000000000000000000000042a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000447a000000000000000000000000000000000000000000000000000000000000000000b97c2fea48417a810000a97f00000f0000022c0000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002210000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002150000020111736d616c6c626f6f73746572666c616d650007010b756e627265616b61626c650300000002390000020411617065786361707461696e7363686169720007010b756e627265616b61626c65030000000231000002040c6170657873686970646f6f720007000000020e000002040a74656c65706f727465720007010b756e627265616b61626c6503000000022d000002060b7465636873746174696f6e0007010b756e627265616b61626c65030000000218000002060e61706578736869706c6f636b65720007030b756e627265616b61626c650300056c6576656c04020d7472656173757265506f6f6c7306010515676c6974636853746172746572547265617375726500000235000002070d617065786675656c68617463680007010b756e627265616b61626c650300000002090000020a0c626f6f73746572666c616d650007010b756e627265616b61626c6503000000022d0000020c0e68796c6f746c6c616e7465726e31000700000002260000020c0e68796c6f746c6c616e7465726e310007000000021f0000020c0e68796c6f746c6c616e7465726e31000700000002180000020c0e68796c6f746c6c616e7465726e310007000000020e0000020c0e68796c6f746c6c616e7465726e31000700830f0000020b0000020c070000020c0000020c070000020d0000020c070000020e0000020c070000020f0000020c07000002100000020c07000002110000020c811b000002120000020c07000002130000020c07000002140000020c811b000002150000020c07000002160000020c07000002170000020c07000002180000020c07000002190000020c070000021a0000020c070000021b0000020c811b0000021c0000020c070000021d0000020c070000021e0000020c070000021f0000020c07000002200000020c07000002210000020c07000002220000020c811b000002230000020c07000002240000020c07000002250000020c07000002260000020c07000002270000020c07000002280000020c07000002290000020c811b0000022a0000020c070000022b0000020c070000022c0000020c070000022d0000020c070000022e0000020c070000022f0000020c070000020b0000020b070000020c0000020b070000020d0000020b070000020e0000020b070000020f0000020b07000002100000020b07000002110000020b811b000002120000020b07000002130000020b07000002140000020b811b000002150000020b07000002160000020b07000002170000020b07000002180000020b07000002190000020b070000021a0000020b070000021b0000020b811b0000021c0000020b070000021d0000020b070000021e0000020b070000021f0000020b07000002200000020b07000002210000020b07000002220000020b811b000002230000020b07000002240000020b07000002250000020b07000002260000020b07000002270000020b07000002280000020b07000002290000020b811b0000022a0000020b070000022b0000020b070000022c0000020b070000022d0000020b070000022e0000020b070000022f0000020b070000020b0000020a070000020c0000020a070000020d0000020a070000020e0000020a070000020f0000020a07000002100000020a07000002110000020a811b000002120000020a07000002130000020a07000002140000020a811b000002150000020a07000002160000020a07000002170000020a07000002180000020a07000002190000020a070000021a0000020a070000021b0000020a811b0000021c0000020a070000021d0000020a070000021e0000020a070000021f0000020a07000002200000020a07000002210000020a07000002220000020a811b000002230000020a07000002240000020a07000002250000020a07000002260000020a07000002270000020a07000002280000020a07000002290000020a811b0000022a0000020a070000022b0000020a070000022c0000020a070000022d0000020a070000022e0000020a070000022f0000020a070000020b00000209070000020c00000209070000020d00000209070000020e00000209070000020f00000209070000021000000209070000021100000209811b0000021200000209070000021300000209070000021400000209811b0000021500000209070000021600000209070000021700000209070000021800000209070000021900000209070000021a00000209070000021b00000209811b0000021c00000209070000021d00000209070000021e00000209070000021f00000209070000022000000209070000022100000209070000022200000209811b0000022300000209070000022400000209070000022500000209070000022600000209070000022700000209070000022800000209070000022900000209811b0000022a00000209070000022b00000209070000022c00000209070000022d00000209070000022e00000209070000022f00000209070000020b00000208070000020c00000208070000020d00000208070000020e00000208070000020f00000208070000021000000208070000021100000208811b0000021200000208070000021300000208070000021400000208811b0000021500000208070000021600000208070000021700000208070000021800000208070000021900000208070000021a00000208070000021b00000208811b0000021c00000208070000021d00000208070000021e00000208070000021f00000208070000022000000208070000022100000208070000022200000208811b0000022300000208070000022400000208070000022500000208070000022600000208070000022700000208070000022800000208070000022900000208811b0000022a00000208070000022b00000208070000022c00000208070000022d00000208070000022e00000208070000022f00000208070000023000000208811b0000023100000208070000023200000208070000023300000208070000023400000208070000023500000208070000023600000208070000023700000208070000023800000208070000023900000208070000020b00000207070000020c00000207070000020d00000207070000020e00000207070000020f00000207070000021000000207070000021100000207811b0000021200000207070000021300000207070000021400000207811b0000021500000207070000021600000207070000021700000207070000021800000207070000021900000207070000021a00000207070000021b00000207811b0000021c00000207070000021d00000207070000021e00000207070000021f00000207070000022000000207070000022100000207070000022200000207811b0000022300000207070000022400000207070000022500000207070000022600000207070000022700000207070000022800000207070000022900000207811b0000022a00000207070000022b00000207070000022c00000207070000022d00000207070000022e00000207070000022f00000207070000023000000207811b0000023100000207070000023200000207070000023300000207070000023400000207070000023500000207070000023600000207070000023700000207070000023800000207070000023900000207070000023a00000207070000023b00000207070000020b00000206070000020c00000206070000020d00000206070000020e00000206070000020f00000206070000021000000206070000021100000206811b0000021200000206070000021300000206070000021400000206811b0000021500000206070000021600000206070000021700000206070000021800000206070000021900000206070000021a00000206070000021b00000206811b0000021c00000206070000021d00000206070000021e00000206070000021f00000206070000022000000206070000022100000206070000022200000206811b0000022300000206070000022400000206070000022500000206070000022600000206070000022700000206070000022800000206070000022900000206811b0000022a00000206070000022b00000206070000022c00000206070000022d00000206070000022e00000206070000022f00000206070000023000000206811b0000023100000206070000023200000206070000023300000206070000023400000206070000023500000206070000023600000206070000023700000206070000023800000206070000023900000206070000023a00000206070000023b00000206070000023c00000206070000023d00000206070000020b00000205070000020c00000205070000020d00000205070000020e00000205070000020f00000205070000021000000205070000021100000205811b0000021200000205070000021300000205070000021400000205811b0000021500000205070000021600000205070000021700000205070000021800000205070000021900000205070000021a00000205070000021b00000205811b0000021c00000205070000021d00000205070000021e00000205070000021f00000205070000022000000205070000022100000205070000022200000205811b0000022300000205070000022400000205070000022500000205070000022600000205070000022700000205070000022800000205070000022900000205811b0000022a00000205070000022b00000205070000022c00000205070000022d00000205070000022e00000205070000022f00000205070000023000000205811b0000023100000205070000023200000205070000023300000205070000023400000205070000023500000205070000023600000205070000023700000205070000023800000205070000023900000205070000023a00000205070000023b00000205070000023c00000205070000023d00000205070000023e00000205070000020b00000204070000020c00000204070000020d00000204070000020e00000204070000020f00000204070000021000000204070000021100000204811b0000021200000204070000021300000204070000021400000204811b0000021500000204070000021600000204070000021700000204070000021800000204070000021900000204070000021a00000204070000021b00000204811b0000021c00000204070000021d00000204070000021e00000204070000021f00000204070000022000000204070000022100000204070000022200000204811b0000022300000204070000022400000204070000022500000204070000022600000204070000022700000204070000022800000204070000022900000204811b0000022a00000204070000022b00000204070000022c00000204070000022d00000204070000022e00000204070000022f00000204070000023000000204811b0000023100000204070000023200000204070000023300000204070000023400000204070000023500000204070000023600000204070000023700000204070000023800000204070000023900000204070000023a00000204070000023b00000204070000023c00000204070000023d00000204070000023e00000204077f0000020a0000020d070000020b0000020d070000020c0000020d070000020d0000020d070000020e0000020d070000020f0000020d07000002100000020d07000002110000020d07000002120000020d07000002130000020d07000002140000020d07000002150000020d07000002160000020d07000002170000020d07000002180000020d07000002190000020d070000021a0000020d070000021b0000020d070000021c0000020d070000021d0000020d070000021e0000020d070000021f0000020d07000002200000020d07000002210000020d07000002220000020d07000002230000020d07000002240000020d07000002250000020d07000002260000020d07000002270000020d07000002280000020d07000002290000020d070000022a0000020d070000022b0000020d070000022c0000020d070000022d0000020d070000022e0000020d070000022f0000020d07000002300000020d07000002310000020d070000020a0000020c07000002300000020c07000002310000020c070000020a0000020b07000002300000020b07000002310000020b070000020a0000020a07000002300000020a07000002310000020a070000020a000002090700000230000002090700000231000002090700000232000002090a00000233000002090a00000234000002090a00000235000002090a00000236000002090a00000237000002090a00000238000002090a00000239000002090a0000020a00000208070000023a000002080a0000023b000002080a0000020a00000207070000023c000002070a0000023d000002070a0000020a00000206070000023e000002060a0000023f000002060a0000020a00000205070000023f000002050a0000020a00000204070000023f000002040a0000020a00000203070000020b00000203070000020c00000203070000020d00000203070000020e00000203070000020f00000203070000021000000203070000021100000203070000021200000203070000021300000203070000021400000203070000021500000203070000021600000203070000021700000203070000021800000203070000021900000203070000021a00000203070000021b00000203070000021c00000203070000021d00000203070000021e00000203070000021f00000203070000022000000203070000022100000203070000022200000203070000022300000203070000022400000203070000022500000203070000022600000203070000022700000203070000022800000203070000022900000203070000022a00000203070000022b00000203070000022c00000203070000022d00000203070000022e00000203070000022f00000203070000023000000203070000023100000203070000023200000203070000023300000203070000023400000203070000023500000203070000023600000203070000023700000203070000023800000203070000023900000203070000023a00000203070000023b00000203070000023c00000203070000023d00000203070000023e00000203070000023f0000020307010b706c61796572537061776e010000020e00000204916a817f02095716a727bbd6d53f8000003f8000003f8000003f8000003f0c8c8d3f0c8c8d3f34b4b53f8000003ef0f0f23ef0f0f23f34b4b53f8000003f3ebebf3f3ebebf3f52d2d43f8000003f2aaaab3f2aaaab3f52d2d43f8000003f20a0a13ef0f0f23f34b4b53f8000003f0c8c8d3ec8c8ca3f34b4b53f8000003e20a0a1000000003e70f0f23f8000003d20a0a1000000003da0a0a13f8000003f0c8c8d3f0c8c8d3f34b4b53f8000003f3ebebf3f3ebebf3f52d2d43f8000003f20a0a13ef0f0f23f34b4b53f8000003dd0d0d23dd0d0d23df8f8fa3f80000044edae5244c80000457a000005616c706861fce5da4aff4b6886fe62174d00000005000000000000000000004284211c00000000008e60333fbb59db182f736b792f6f72626974616c732f636c6f7564342e706e673e55a5f84444ae0b4043e4db182f736b792f6f72626974616c732f636c6f7564322e706e673e7eb1344446d6f34048af6c182f736b792f6f72626974616c732f636c6f7564332e706e673e6c13ab4447bd3340b9e067182f736b792f6f72626974616c732f636c6f7564342e706e673e868453443ed6dd3f7a293f182f736b792f6f72626974616c732f636c6f7564322e706e673e8cf2ca443c467c3e5b44fb182f736b792f6f72626974616c732f636c6f7564332e706e673e83061844391bce40b04bea182f736b792f6f72626974616c732f636c6f7564342e706e673e8adf0b44435f4d3f015d05182f736b792f6f72626974616c732f636c6f7564322e706e673e5be03a44425bbf3ffe1ae8182f736b792f6f72626974616c732f636c6f7564332e706e673e852e1a444988bc3fa92d36182f736b792f6f72626974616c732f636c6f7564322e706e673e5d44144439a3d73e18e0f0182f736b792f6f72626974616c732f636c6f7564342e706e673e80deab443a1e174078545b182f736b792f6f72626974616c732f636c6f7564342e706e673e96f4184449ab6b3fe0ec39182f736b792f6f72626974616c732f636c6f7564322e706e673e6ef49c4444bcab4042c04c182f736b792f6f72626974616c732f636c6f7564322e706e673e93bf844448915e40919336182f736b792f6f72626974616c732f636c6f7564332e706e673e5da3b1443f19b93e9e4d7b182f736b792f6f72626974616c732f636c6f7564322e706e673e8eb92c444335e240b7a918182f736b792f6f72626974616c732f636c6f7564332e706e673e7506ce443b90bb40281704182f736b792f6f72626974616c732f636c6f7564332e706e673e92f4a44444f1ec409cbcf2182f736b792f6f72626974616c732f636c6f7564342e706e673e7a848b443bb8fc401590be182f736b792f6f72626974616c732f636c6f7564322e706e673e90594e4445c7b040ab580a182f736b792f6f72626974616c732f636c6f7564342e706e673e713c834443563f40b779db182f736b792f6f72626974616c732f636c6f7564322e706e673e5c9c44443e46fd40852efe182f736b792f6f72626974616c732f636c6f7564322e706e673e58edc14446c2be4081ffe1182f736b792f6f72626974616c732f636c6f7564322e706e673e53fcb144469cb740ab3018182f736b792f6f72626974616c732f636c6f7564342e706e673e786c3e444569df4008a010182f736b792f6f72626974616c732f636c6f7564322e706e673e7b493c4439bf7f3f5a382d182f736b792f6f72626974616c732f636c6f7564322e706e673e77f674443b4e5a40145246182f736b792f6f72626974616c732f636c6f7564342e706e673e79a1b4443e2b043f9653d2182f736b792f6f72626974616c732f636c6f7564342e706e673e939e7d4449706740be364e182f736b792f6f72626974616c732f636c6f7564342e706e673e7afd2844426d6f40c10354182f736b792f6f72626974616c732f636c6f7564322e706e673e747291443edf973ffdaeac182f736b792f6f72626974616c732f636c6f7564332e706e673e8e3a9d443a8b4f40458dee182f736b792f6f72626974616c732f636c6f7564332e706e673e6d95394447f3d93f945ee6182f736b792f6f72626974616c732f636c6f7564342e706e673e9010b644459a6f40ad9264182f736b792f6f72626974616c732f636c6f7564322e706e673e4cfaab44481c6f3f682c7b182f736b792f6f72626974616c732f636c6f7564332e706e673e66c1844448dcf240b63501182f736b792f6f72626974616c732f636c6f7564322e706e673e5a52c2443cf7963eb4071f182f736b792f6f72626974616c732f636c6f7564342e706e673e5ff118443c78bf407056b0182f736b792f6f72626974616c732f636c6f7564342e706e673e8fc6414441809a40181499182f736b792f6f72626974616c732f636c6f7564342e706e673e67b9954446f83940a0721b182f736b792f6f72626974616c732f636c6f7564342e706e673e95a05e443bd7df40822f46182f736b792f6f72626974616c732f636c6f7564322e706e673e890898444120af40a1e924182f736b792f6f72626974616c732f636c6f7564332e706e673e8811be4439aca040b7cb06182f736b792f6f72626974616c732f636c6f7564322e706e673e807eb844464b73403c62a0182f736b792f6f72626974616c732f636c6f7564332e706e673e518045443cf4be406d0753182f736b792f6f72626974616c732f636c6f7564332e706e673e748ff2443a66093f746368182f736b792f6f72626974616c732f636c6f7564322e706e673e7fd3ee444728843f96fdab182f736b792f6f72626974616c732f636c6f7564342e706e673e9408cd4449ca5940078739182f736b792f6f72626974616c732f636c6f7564322e706e673e961706443f8d2a3fa5edcb182f736b792f6f72626974616c732f636c6f7564342e706e673e90fa5c443db5b040836567182f736b792f6f72626974616c732f636c6f7564322e706e673e94a50944395b254104d68143313f8110d83fb4d6483fca8bf65e142850214880c9be0b61a8be8c0a667e99f113cca9e6983ff4ef903fcca6b007c95949f3e8e2593f9654de3fb9e852004229b4a446433ac941200000030000000000000000000000003e22a38f00000000000000000000000000000000000000003ab94ae90f0100000000000000000000000000004403800044014000040a6675656c2e6c6576656c04861a086675656c2e6d6178048f5011696e76696e6369626c65506c6179657273030108656e67696e654f6e03000000000100" -} \ No newline at end of file diff --git a/tests/packet_tests.py b/tests/packet_tests.py deleted file mode 100644 index 6ad4350..0000000 --- a/tests/packet_tests.py +++ /dev/null @@ -1,227 +0,0 @@ -import json -from nose.tools import * - -from packets import * - -''' -Tests for packet type 0x01: Protocol Version, Server -> Client -''' - - -class ProtocolVersionTest(unittest.TestCase): - def testParseBuild(self): - packet = "00000274".decode("hex") - parsed = protocol_version().parse(packet) - assert_equal(parsed.server_build, 628) - assert_equal(protocol_version().build(parsed), packet) - - -''' -Tests for packet type 0x02: Connect Response, Server -> Client -''' - - -class ConnectRsponseTest(unittest.TestCase): - def testParseBuild(self): - packet = "010100".decode("hex") - parsed = connect_response().parse(packet) - - assert_equal(connect_response().build(parsed), packet) - - -''' -Tests for packet type 0x03: Server Disconnect, Server -> Client -''' - - -class ServerDisconnectTest(unittest.TestCase): - def testParseBuild(self): - raise "not implemented, no test data available" - - -''' -Tests for packet type 0x04: Handshake Challenge, Server -> Client -''' - - -class HandshakeChallangeTest(unittest.TestCase): - def testParseBuild(self): - packet = "00203575416369525a6b6d774b556b656b336b72552b73324c54765048453676325000001388".decode("hex") - parsed = handshake_challenge().parse(packet) - assert_is_instance(parsed.claim_message, str) - assert_is_instance(parsed.salt, str) - assert_is_instance(parsed.round_count, int) - assert_equal(handshake_challenge().build(parsed), packet) - - -''' -Tests for packet type 0x05: Chat Received, Server -> Client -''' - - -class ChatReceivedTest(unittest.TestCase): - def testChatReceived(self): - packet = "TODO: NO TEST DATA".decode("hex") - parsed = chat_received().parse(packet) - - assert_equal(chat_received().build(parsed), packet) - - -''' -Tests for packet type 0x06: Universe Time Update, Server -> Client -''' - - -class UniverseTimeUpdateTest(unittest.TestCase): - def testParseBuild(self): - packet = "85e2c976".decode("hex") - parsed = universe_time_update().parse(packet) - - assert_equal(universe_time_update().build(parsed), packet) - - -''' -Tests for packet type 0x07: Client Connect, Server -> Client, compressed -''' - - -class ClientConnectTest(unittest.TestCase): - def testParseBuild(self): - with open("tests/large_packets.json", "r+") as large_packets: - packet = json.load(large_packets)['client_connect'].decode("hex") - parsed = client_connect().parse(packet) - - assert_equal(client_connect().build(parsed), packet) - - -''' -Tests for packet type 0x08: Client Disconnect, Server -> Client, compressed -''' - - -class ClientDisconnectTest(unittest.TestCase): - def testParseBuild(self): - packet = "00".decode("hex") - parsed = client_disconnect().parse(packet) - - assert_equal(client_disconnect().build(parsed), packet) - - -''' -Tests for packet types 0x09: Handshake Response, Client -> Server -''' - - -class HandshakeResponseTest(unittest.TestCase): - def testParseBuild(self): - packet = "002c345639357a77384158783633425433316a4c755955346e786f6e7970374b4179526a4a794f42516c6330553d".decode( - "hex") - parsed = handshake_response().parse(packet) - assert_is_instance(parsed.claim_response, str) - assert_is_instance(parsed.hash, str) - assert_equal(handshake_response().build(parsed), packet) - - -''' -Tests for packet types 0x0A: Warp Command, Client -> Server -''' - - -class WarpCommandTest(unittest.TestCase): - def testMoveShip(self): - packet = "0000000105616c706861fce5da4aff4b6886fe62174d000000050000000000".decode("hex") - parsed = warp_command().parse(packet) - assert_equal(parsed.world_coordinate.sector, 'alpha') - assert_equal(parsed.world_coordinate.planet, 5) - assert_equal(parsed.warp_type, 'MOVE_SHIP') - built_packet = warp_command().build(parsed) - assert_equal(packet, built_packet) - - def testParseUp(self): - packet = "0000000200000000000000000000000000000000000000000000".decode("hex") - parsed = warp_command().parse(packet) - - assert_equal(parsed.world_coordinate.sector, '') - assert_equal(parsed.world_coordinate.planet, 0) - assert_equal(parsed.player, '') - assert_equal(parsed.warp_type, 'WARP_UP') - - built_packet = warp_command().build(parsed) - assert_equal(packet, built_packet) - - - def testWarpOtherShip(self): - packet = "00000003000000000000000000000000000000000000000000056d61666669".decode("hex") - parsed = warp_command().parse(packet) - assert_is_not(parsed.player, '') - assert_equal(parsed.warp_type, 'WARP_OTHER_SHIP') - - built_packet = warp_command().build(parsed) - assert_equal(packet, built_packet) - - def testWarpDown(self): - packet = "0000000400000000000000000000000000000000000000000000".decode("hex") - parsed = warp_command().parse(packet) - assert_equal(parsed.warp_type, 'WARP_DOWN') - - built_packet = warp_command().build(parsed) - assert_equal(packet, built_packet) - - def testWarpDownHomePlanet(self): - packet = "0000000500000000000000000000000000000000000000000000".decode("hex") - parsed = warp_command().parse(packet) - assert_equal(parsed.warp_type, 'WARP_HOME') - - built_packet = warp_command().build(parsed) - assert_equal(packet, built_packet) - - -''' -Tests for packet types 0x0B: Chat Sent, Client -> Server -''' - - -class ChatSentTest(unittest.TestCase): - def testParseBuild(self): - packet = "0b68656c6c6f20776f726c6400".decode("hex") - parsed = chat_sent().parse(packet) - assert_equal(parsed.message, "hello world") - assert_equal(chat_sent().build(parsed), packet) - - -''' -Tests for packet types 0x0C: Client Context Update , Client -> Server -''' - - -class ClientContextUpdateTest(unittest.TestCase): - def testParseBuild(self): - raise "not yet understood" - - -''' -Tests for packet types 0x0D: World Start, Server -> Client -''' - - -class WorldStartTest(unittest.TestCase): - def testParseBuild(self): - with open("tests/large_packets.json", "r+") as large_packets: - packet = json.load(large_packets)['world_start'].decode("hex") - parsed = world_start().parse(packet) - - assert_equal(world_start().build(parsed), packet) - - -''' -Tests for packet types 0x0E: World Stop, Server -> Client -''' - - -class WorldStopTest(unittest.TestCase): - def testParseBuild(self): - packet = "0752656d6f766564".decode("hex") - parsed = world_stop().parse(packet) - assert_equal(parsed.status, "Removed") - assert_equal(world_stop().build(parsed), packet) - From ca070f7402cfc29b6d8cf2b6479e9897c057e445 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 1 Feb 2015 03:46:11 -0500 Subject: [PATCH 45/81] code refactoring, fixing client DC and crash bugs. Brought in IRC bot for testing --- base_plugin.py | 18 ++- packets/packet_types.py | 18 +-- plugins/announcer_plugin/announcer_plugin.py | 2 +- plugins/core/player_manager/plugin.py | 7 +- plugins/irc_plugin/__init__.py | 1 + plugins/irc_plugin/irc_manager.py | 76 +++++++++++ plugins/irc_plugin/irc_plugin.py | 67 ++++++++++ plugins/warpy_plugin/warpy_plugin.py | 12 +- server.py | 127 ++++++++++--------- utility_functions.py | 4 +- 10 files changed, 245 insertions(+), 87 deletions(-) create mode 100644 plugins/irc_plugin/__init__.py create mode 100644 plugins/irc_plugin/irc_manager.py create mode 100644 plugins/irc_plugin/irc_plugin.py diff --git a/base_plugin.py b/base_plugin.py index cfc6b29..f15d210 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -149,7 +149,7 @@ def on_burn_container(self, data): def on_clear_container(self, data): return True - def on_world_update(self, data): + def on_world_client_state_update(self, data): return True def on_entity_create(self, data): @@ -182,10 +182,13 @@ def on_damage_notification(self, data): def on_client_connect(self, data): return True - def on_client_disconnect(self, player): + def on_client_disconnect_request(self, player): return True - def on_warp_command(self, data): + def on_player_warp(self, data): + return True + + def on_central_structure_update(self, data): return True def after_protocol_version(self, data): @@ -293,7 +296,7 @@ def after_burn_container(self, data): def after_clear_container(self, data): return True - def after_world_update(self, data): + def after_world_client_state_update(self, data): return True def after_entity_create(self, data): @@ -326,10 +329,13 @@ def after_damage_notification(self, data): def after_client_connect(self, data): return True - def after_client_disconnect(self, data): + def after_client_disconnect_request(self, data): + return True + + def after_player_warp(self, data): return True - def after_warp_command(self, data): + def after_central_structure_update(self, data): return True def __repr__(self): diff --git a/packets/packet_types.py b/packets/packet_types.py index 67145cd..4ae9bd7 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -131,13 +131,12 @@ def _decode(self, obj, context): star_string("salt"), SBInt32("round_count")) -# Needs attention +# Needs to be corrected to include 'celestial information' as well as proper reject +# sucess handling. connect_response = lambda name="connect_response": Struct(name, Flag("success"), VLQ("client_id"), - star_string("reject_reason") - # may need to add something here CelestialBaseInformation - ) + star_string("reject_reason")) # corrected. needs testing chat_received = lambda name="chat_received": Struct(name, @@ -176,8 +175,8 @@ def _decode(self, obj, context): server_disconnect = lambda name="server_disconnect": Struct(name, star_string("reason")) -client_disconnect = lambda name="client_disconnect": Struct(name, - Byte("data")) +client_disconnect_request = lambda name="client_disconnect_request": Struct(name, + Byte("data")) celestial_request = lambda name="celestial_request": Struct(name, GreedyRange(star_string("requests"))) @@ -189,7 +188,7 @@ def _decode(self, obj, context): SBInt32("planet"), SBInt32("satellite")) -warp_command = lambda name="warp_command": Struct(name, +player_warp = lambda name="player_warp": Struct(name, Enum(UBInt8("warp_type"), WARP_TO=0, WARP_RETURN=1, @@ -198,7 +197,7 @@ def _decode(self, obj, context): WARP_TO_OWN_SHIP=4), WarpVariant("world_id")) -warp_command_write = lambda t, world_id: warp_command().build( +player_warp_write = lambda t, world_id: player_warp().build( Container( warp_type=t, world_id=world_id)) @@ -257,4 +256,7 @@ def _decode(self, obj, context): Struct("key", Variant("value")))) +central_structure_update = lambda name="central_structure_update": Struct(name, + Variant("structureData")) + projectile = DictVariant("projectile") diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index d731daa..9a35421 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -25,7 +25,7 @@ def after_connect_response(self, data): self.logger.exception("Unknown error in after_connect_response.") return - def on_client_disconnect(self, data): + def on_client_disconnect_request(self, data): if self.protocol.player is not None: self.factory.broadcast(self.protocol.player.colored_name(self.config.colors) + " logged out.", 0, "Announcer") diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 369ea69..62e4ca1 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -7,7 +7,7 @@ from base_plugin import SimpleCommandPlugin from manager import PlayerManager, Banned, Player, permissions, UserLevels -from packets import client_connect, connect_response, warp_command +from packets import client_connect, connect_response import packets from utility_functions import extract_name, build_packet, Planet @@ -53,7 +53,7 @@ def on_client_connect(self, data): duplicate_player = self.player_manager.get_by_org_name(client_data.name) if duplicate_player is not None and duplicate_player.uuid != client_data.uuid: raise NameError( - "The name of this character is already taken on the server!\nPlease, create a new character with a different name or use Starcheat and change the name.") + "The name of this character is already taken on the server!\nPlease, create a new character with a different name or talk to an administrator.") self.logger.info("Got a duplicate original player name, asking player to change character name!") #rnd_append = str(randrange(10, 99)) #original_name += rnd_append @@ -118,6 +118,7 @@ def after_world_start(self, data): # may need to add more world types in here world_start = packets.world_start().parse(data.data) # self.logger.debug("World start: %s", world_start) # debug world packets + # self.logger.debug("World start raw: %s", data.data.encode('hex')) # debug world packets if 'ship.maxFuel' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True @@ -134,7 +135,7 @@ def after_world_start(self, data): coords['planet'], coords['satellite']) self.protocol.player.planet = str(planet) - def on_client_disconnect(self, player): + def on_client_disconnect_request(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: self.logger.info("Player disconnected: %s", self.protocol.player.name) self.protocol.player.logged_in = False diff --git a/plugins/irc_plugin/__init__.py b/plugins/irc_plugin/__init__.py new file mode 100644 index 0000000..d2369c3 --- /dev/null +++ b/plugins/irc_plugin/__init__.py @@ -0,0 +1 @@ +from irc_plugin import IrcPlugin diff --git a/plugins/irc_plugin/irc_manager.py b/plugins/irc_plugin/irc_manager.py new file mode 100644 index 0000000..1f1b024 --- /dev/null +++ b/plugins/irc_plugin/irc_manager.py @@ -0,0 +1,76 @@ +# twisted imports +from twisted.words.protocols import irc +from twisted.internet import reactor, protocol +from uuid import uuid4 + + +class StarryPyIrcBot(irc.IRCClient): + + def __init__(self, logger, nickname, nickserv_password, factory, broadcast_target, colors, echo_from_channel): + self.logger = logger + self.nickname = nickname + self.nickserv_password = nickserv_password + self.id = str(uuid4().hex) + self.factory = factory + self.broadcast_target = broadcast_target + self.colors = colors + self.echo_from_channel = echo_from_channel + + def signedOn(self): + if self.nickserv_password: + self.msg("NickServ", "identify %s" % self.nickserv_password) + if self.factory.target.startswith("#"): + self.join(self.factory.target) + else: + self.send_greeting(self.factory.target) + + self.logger.info("Connected to IRC") + + def joined(self, target): + self.send_greeting(target) + + def send_greeting(self, target): + self.msg(target, "%s is live!" % self.nickname) + + def privmsg(self, user, target, msg): + user = user.split('!', 1)[0] + self.logger.info("IRC Message <%s>: %s" % (user, msg)) + if self.echo_from_channel: + self.broadcast_target.broadcast("%s%s: <%s>: %s%s" % ( + self.colors['irc'], + target, + user, + msg, + self.colors['default'], + )) + + def action(self, user, target, msg): + user = user.split('!', 1)[0] + self.logger.info("IRC Action: %s %s" % (user, msg)) + + +class StarryPyIrcBotFactory(protocol.ClientFactory): + def __init__(self, target, logger, nickname, nickserv_password, broadcast_target, colors, echo_from_channel): + self.nickname = nickname + try: + self.nickserv_password = nickserv_password + except AttributeError: + self.nickserv_password = None + + self.target = target + self.broadcast_target = broadcast_target + self.colors = colors + self.logger = logger + self.irc_clients = {} + self.echo_from_channel = echo_from_channel + + def buildProtocol(self, addr): + irc_client = StarryPyIrcBot(self.logger, self.nickname, self.nickserv_password, self, self.broadcast_target, self.colors, self.echo_from_channel) + self.irc_clients[irc_client.id] = irc_client + return irc_client + + def clientConnectionLost(self, connector, reason): + connector.connect() + + def clientConnectionFailed(self, connector, reason): + self.logger.error("connection failed: %s" % reason) diff --git a/plugins/irc_plugin/irc_plugin.py b/plugins/irc_plugin/irc_plugin.py new file mode 100644 index 0000000..2ee2655 --- /dev/null +++ b/plugins/irc_plugin/irc_plugin.py @@ -0,0 +1,67 @@ +from twisted.internet import reactor + +from base_plugin import BasePlugin +from packets import chat_sent, client_connect +from irc_manager import StarryPyIrcBotFactory + +#TODO: multiple channels, multiple servers + + +class IrcPlugin(BasePlugin): + name = "irc" + + def __init__(self, *args, **kwargs): + super(IrcPlugin, self).__init__(*args, **kwargs) + self.web_factory = None + + def activate(self): + super(IrcPlugin, self).activate() + + self.server = self.config.plugin_config['server'] + + try: + self.port = int(self.config.plugin_config['port']) + except (AttributeError, ValueError): + self.port = 6667 + + self.nickname = self.config.plugin_config['bot_nickname'].encode("utf-8") + self.channel = self.config.plugin_config['channel'].encode("utf-8") + if 'nickserv_password' in self.config.plugin_config: + self.nickserv_password = self.config.plugin_config['nickserv_password'].encode("utf-8") + else: + self.nickserv_password = None + + self.echo_from_channel = self.config.plugin_config['echo_from_channel'] + + self.colors_with_irc_color = self.config.colors + self.colors_with_irc_color['irc'] = self.config.plugin_config['color'] + + if not getattr(self, 'irc_factory', None): + self.irc_factory = StarryPyIrcBotFactory(self.channel, self.logger, self.nickname, self.nickserv_password, self.factory, self.colors_with_irc_color, self.echo_from_channel) + self.irc_port = reactor.connectTCP(self.server, self.port, self.irc_factory) + + def deactivate(self): + if getattr(self, 'irc_manager', None): + self.irc_port.disconnect() + del self.irc_factory + + def on_chat_sent(self, data): + parsed = chat_sent().parse(data.data) + if not parsed.message.startswith('/'): + for p in self.irc_factory.irc_clients.itervalues(): + p.msg(self.channel, "<%s> %s" % (self.protocol.player.name.encode("utf-8"), parsed.message.encode("utf-8"))) + return True + + def on_client_connect(self, data): + parsed = client_connect().parse(data.data) + self.logger.info(parsed.name) + + for p in self.irc_factory.irc_clients.itervalues(): + p.msg(self.channel, "%s connected" % parsed.name.encode("utf-8")) + + return True + + def on_client_disconnect_request(self, data): + if self.protocol.player is not None: + for p in self.irc_factory.irc_clients.itervalues(): + p.msg(self.channel, "%s disconnected" % self.protocol.player.name.encode("utf-8")) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index d2db7f5..42012c7 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import warp_command_write, Packets, warp_command +from packets import player_warp_write, Packets, player_warp from utility_functions import build_packet, move_ship_to_coords, extract_name @@ -19,9 +19,9 @@ def activate(self): self.player_manager = self.plugins['player_manager'].player_manager ## Warp debugging - #def on_warp_command(self, data): + #def on_player_warp(self, data): # self.logger.debug("Warp packet: %s", data.data.encode('hex')) - # warp_data = warp_command().parse(data.data) + # warp_data = player_warp().parse(data.data) # self.logger.debug("Warp packet: %s", warp_data) @permissions(UserLevels.ADMIN) @@ -97,11 +97,11 @@ def warp_player_to_player(self, from_string, to_string): if from_player is not to_player: self.logger.debug("target: %s", to_player.uuid) warp_packet = build_packet(Packets.PLAYER_WARP, - warp_command_write(t="WARP_TO", + player_warp_write(t="WARP_TO", world_id=to_player.uuid)) else: warp_packet = build_packet(Packets.PLAYER_WARP, - warp_command_write(t="WARP_TO_OWN_SHIP", + player_warp_write(t="WARP_TO_OWN_SHIP", world_id=None)) from_protocol.client_protocol.transport.write(warp_packet) if from_string != to_string: @@ -170,7 +170,7 @@ def warp_player_to_outpost(self, player_string): if player_to_send is not None: player_protocol = self.factory.protocols[player_to_send.protocol] warp_packet = build_packet(Packets.PLAYER_WARP, - warp_command_write(t="WARP_TO", + player_warp_write(t="WARP_TO", world_id="outpost")) player_protocol.client_protocol.transport.write(warp_packet) self.protocol.send_chat_message("Warped ^yellow;%s^green; to the outpost." % player_string) diff --git a/server.py b/server.py index 9de9910..3d65519 100644 --- a/server.py +++ b/server.py @@ -64,59 +64,60 @@ def __init__(self): self.after_write_callback = None self.plugin_manager = None self.call_mapping = { - packets.Packets.PROTOCOL_VERSION: self.protocol_version, - packets.Packets.SERVER_DISCONNECT: self.server_disconnect, - packets.Packets.CONNECT_RESPONSE: self.connect_response, - packets.Packets.HANDSHAKE_CHALLENGE: self.handshake_challenge, - packets.Packets.CHAT_RECEIVED: self.chat_received, - packets.Packets.UNIVERSE_TIME_UPDATE: self.universe_time_update, - packets.Packets.CELESTIAL_RESPONSE: lambda x: True, - packets.Packets.CLIENT_CONNECT: self.client_connect, - packets.Packets.CLIENT_DISCONNECT_REQUEST: self.client_disconnect, - packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, - packets.Packets.PLAYER_WARP: self.warp_command, - packets.Packets.FLY_SHIP: lambda x: True, - packets.Packets.CHAT_SENT: self.chat_sent, - packets.Packets.CELESTIAL_REQUEST: self.celestial_request, - packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, - packets.Packets.WORLD_START: self.world_start, - packets.Packets.WORLD_STOP: self.world_stop, - packets.Packets.CENTRAL_STRUCTURE_UPDATE: lambda x: True, - packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, - packets.Packets.TILE_UPDATE: self.tile_update, - packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, - packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, - packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, - packets.Packets.GIVE_ITEM: self.item, - packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, - packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, - packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, - packets.Packets.UPDATE_TILE_PROTECTION: lambda x: True, - packets.Packets.MODIFY_TILE_LIST: self.modify_tile_list, - packets.Packets.DAMAGE_TILE_GROUP: self.damage_tile_group, - packets.Packets.COLLECT_LIQUID: lambda x: True, - packets.Packets.REQUEST_DROP: self.request_drop, - packets.Packets.SPAWN_ENTITY: self.spawn_entity, - packets.Packets.ENTITY_INTERACT: self.entity_interact, - packets.Packets.CONNECT_WIRE: self.connect_wire, - packets.Packets.DISCONNECT_ALL_WIRES: self.disconnect_all_wires, - packets.Packets.OPEN_CONTAINER: self.open_container, - packets.Packets.CLOSE_CONTAINER: self.close_container, - packets.Packets.SWAP_IN_CONTAINER: self.swap_in_container, - packets.Packets.ITEM_APPLY_IN_CONTAINER: self.item_apply_in_container, - packets.Packets.START_CRAFTING_IN_CONTAINER: self.start_crafting_in_container, - packets.Packets.STOP_CRAFTING_IN_CONTAINER: self.stop_crafting_in_container, - packets.Packets.BURN_CONTAINER: self.burn_container, - packets.Packets.CLEAR_CONTAINER: self.clear_container, - packets.Packets.WORLD_CLIENT_STATE_UPDATE: self.world_update, - packets.Packets.ENTITY_CREATE: self.entity_create, - packets.Packets.ENTITY_UPDATE: self.entity_update, - packets.Packets.ENTITY_DESTROY: self.entity_destroy, - packets.Packets.DAMAGE_REQUEST: lambda x: True, - packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, - packets.Packets.CALL_SCRIPTED_ENTITY: lambda x: True, - packets.Packets.UPDATE_WORLD_PROPERTIES: self.update_world_properties, - packets.Packets.HEARTBEAT: self.heartbeat, + packets.Packets.PROTOCOL_VERSION: self.protocol_version, # 0 + packets.Packets.SERVER_DISCONNECT: self.server_disconnect, # 1 + packets.Packets.CONNECT_RESPONSE: self.connect_response, # 2 + packets.Packets.HANDSHAKE_CHALLENGE: self.handshake_challenge, #3 + packets.Packets.CHAT_RECEIVED: self.chat_received, # 4 + packets.Packets.UNIVERSE_TIME_UPDATE: self.universe_time_update, # 5 + packets.Packets.CELESTIAL_RESPONSE: lambda x: True, # 6 + packets.Packets.CLIENT_CONNECT: self.client_connect, # 7 + packets.Packets.CLIENT_DISCONNECT_REQUEST: self.client_disconnect_request, # 8 + packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, # 9 + packets.Packets.PLAYER_WARP: self.player_warp, # 10 + packets.Packets.FLY_SHIP: lambda x: True, # 11 + packets.Packets.CHAT_SENT: self.chat_sent, # 12 + packets.Packets.CELESTIAL_REQUEST: self.celestial_request, # 13 + packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, # 14 + packets.Packets.WORLD_START: self.world_start, # 15 + packets.Packets.WORLD_STOP: self.world_stop, # 16 + packets.Packets.CENTRAL_STRUCTURE_UPDATE: self.central_structure_update, # 17 + packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, # 18 + packets.Packets.TILE_UPDATE: self.tile_update, # 19 + packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, # 20 + packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, # 21 + packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, #22 + packets.Packets.GIVE_ITEM: self.give_item, # 23 + packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, # 24 + packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, # 25 + packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, # 26 + packets.Packets.UPDATE_TILE_PROTECTION: lambda x: True, # 27 + packets.Packets.MODIFY_TILE_LIST: self.modify_tile_list, # 28 + packets.Packets.DAMAGE_TILE_GROUP: self.damage_tile_group, # 29 + packets.Packets.COLLECT_LIQUID: lambda x: True, # 30 + packets.Packets.REQUEST_DROP: self.request_drop, # 31 + packets.Packets.SPAWN_ENTITY: self.spawn_entity, # 32 + packets.Packets.ENTITY_INTERACT: self.entity_interact, # 33 + packets.Packets.CONNECT_WIRE: self.connect_wire, # 34 + packets.Packets.DISCONNECT_ALL_WIRES: self.disconnect_all_wires, # 35 + packets.Packets.OPEN_CONTAINER: self.open_container, # 36 + packets.Packets.CLOSE_CONTAINER: self.close_container, # 37 + packets.Packets.SWAP_IN_CONTAINER: self.swap_in_container, # 38 + packets.Packets.ITEM_APPLY_IN_CONTAINER: self.item_apply_in_container, # 39 + packets.Packets.START_CRAFTING_IN_CONTAINER: self.start_crafting_in_container, # 40 + packets.Packets.STOP_CRAFTING_IN_CONTAINER: self.stop_crafting_in_container, # 41 + packets.Packets.BURN_CONTAINER: self.burn_container, # 42 + packets.Packets.CLEAR_CONTAINER: self.clear_container, # 43 + packets.Packets.WORLD_CLIENT_STATE_UPDATE: self.world_client_state_update, # 44 + packets.Packets.ENTITY_CREATE: self.entity_create, # 45 + packets.Packets.ENTITY_UPDATE: self.entity_update, # 46 + packets.Packets.ENTITY_DESTROY: self.entity_destroy, # 47 + packets.Packets.HIT_REQUEST: lambda x: True, # 48 + packets.Packets.DAMAGE_REQUEST: lambda x: True, # 49 + packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, # 50 + packets.Packets.CALL_SCRIPTED_ENTITY: lambda x: True, # 51 + packets.Packets.UPDATE_WORLD_PROPERTIES: self.update_world_properties, # 52 + packets.Packets.HEARTBEAT: self.heartbeat, # 53 } self.client_protocol = None self.packet_stream = PacketStream(self) @@ -217,6 +218,10 @@ def world_start(self, data): def world_stop(self, data): return True + @route + def central_structure_update(self, data): + return True + @route def tile_array_update(self, data): return True @@ -238,7 +243,7 @@ def tile_modification_failure(self, data): return True @route - def item(self, data): + def give_item(self, data): return True @route @@ -318,7 +323,7 @@ def clear_container(self, data): return True @route - def world_update(self, data): + def world_client_state_update(self, data): return True @route @@ -392,7 +397,7 @@ def client_connect(self, data): return True @route - def client_disconnect(self, player): + def client_disconnect_request(self, player): """ Called when the client signals that it is about to disconnect from the Starbound server. @@ -402,11 +407,11 @@ def client_disconnect(self, player): return True @route - def warp_command(self, data): + def player_warp(self, data): """ Called when the players issues a warp. - :param data: The warp_command data. + :param data: The player_warp data. :rtype : bool """ return True @@ -467,9 +472,9 @@ def connectionLost(self, reason=connectionDone): try: if self.client_protocol is not None: x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, - packets.client_disconnect().build(Container(data=0))) + packets.client_disconnect_request().build(Container(data=0))) if self.player is not None and self.player.logged_in: - self.client_disconnect(x) + self.client_disconnect_request(x) self.client_protocol.transport.write(x) self.client_protocol.transport.abortConnection() except: @@ -548,7 +553,7 @@ def dataReceived(self, data): self.packet_stream += data def disconnect(self): - x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, packets.client_disconnect().build(Container(data=0))) + x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, packets.client_disconnect_request().build(Container(data=0))) self.transport.write(x) self.transport.abortConnection() diff --git a/utility_functions.py b/utility_functions.py index a8eb265..c57a524 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -64,8 +64,8 @@ def move_ship_to_coords(protocol, x, y, z, planet, satellite): logger.info("Moving %s's ship to coordinates: %s", protocol.player.name, ":".join((str(x), str(y), str(z), str(planet), str(satellite)))) x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) - warp_packet = build_packet(packets.Packets.WARP_COMMAND, - packets.warp_command_write(t="WARP_TO", x=x, y=y, z=z, + warp_packet = build_packet(packets.Packets.PLAYER_WARP, + packets.player_warp_write(t="WARP_TO", x=x, y=y, z=z, planet=planet, satellite=satellite, player="".encode('utf-8'))) protocol.client_protocol.transport.write(warp_packet) From 1c8e1959e1ef0022805d7c83ee9ab8e374cc5ae7 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sun, 1 Feb 2015 23:24:08 -0500 Subject: [PATCH 46/81] changed some permissions on warpy --- plugins/warpy_plugin/warpy_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 42012c7..7926b2f 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -24,7 +24,7 @@ def activate(self): # warp_data = player_warp().parse(data.data) # self.logger.debug("Warp packet: %s", warp_data) - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def warp(self, name): """Warps you to a player's ship (or player to player).\nSyntax: /warp [player] (to player)""" if len(name) == 0: @@ -68,7 +68,7 @@ def warp_ship(self, location): self.move_player_ship_to_other(first_name, second_name) #!!!!!!!!!!!!!!!!!!!!!!! - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def outpost(self, name): """Warps you (or another player) to the outpost.\nSyntax: /outpost [player]""" if len(name) == 0: From ef9d969a37bf4913b56436bcea5d5213ae22d2ad Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 2 Feb 2015 03:31:53 -0500 Subject: [PATCH 47/81] split bookmarks into two plugins (bookmarks and poi). Moved spawn command out of command dispatecher, and into warpy. Ship warping still DOES NOT WORK. Packet parsing issues remain to be sorted out. --- .../a87d5e4bfa6fabc4deea7ddd50fb6e99.json | 1 + config/pois.json | 1 + plugins/bookmarks_plugin/__init__.py | 1 + plugins/bookmarks_plugin/bookmarks_plugin.py | 156 ++++++++++++++++++ .../starbound_config_manager.py | 16 +- plugins/poi_plugin/__init__.py | 1 + plugins/poi_plugin/poi_plugin.py | 117 +++++++++++++ plugins/warpy_plugin/warpy_plugin.py | 11 +- 8 files changed, 288 insertions(+), 16 deletions(-) create mode 100644 config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json create mode 100644 config/pois.json create mode 100644 plugins/bookmarks_plugin/__init__.py create mode 100644 plugins/bookmarks_plugin/bookmarks_plugin.py create mode 100644 plugins/poi_plugin/__init__.py create mode 100644 plugins/poi_plugin/poi_plugin.py diff --git a/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json b/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json new file mode 100644 index 0000000..afec3fd --- /dev/null +++ b/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json @@ -0,0 +1 @@ +[["-30562840:658406569:-263745435:4:0", "forest"], ["-30562840:658406569:-263745435:3:6", "spawn"]] \ No newline at end of file diff --git a/config/pois.json b/config/pois.json new file mode 100644 index 0000000..39d1633 --- /dev/null +++ b/config/pois.json @@ -0,0 +1 @@ +[["-30562840:658406569:-263745435:3:6", "spawn"], ["-30562840:658406569:-263745435:4:0", "forest"]] \ No newline at end of file diff --git a/plugins/bookmarks_plugin/__init__.py b/plugins/bookmarks_plugin/__init__.py new file mode 100644 index 0000000..1c97db5 --- /dev/null +++ b/plugins/bookmarks_plugin/__init__.py @@ -0,0 +1 @@ +from bookmarks_plugin import Bookmarks \ No newline at end of file diff --git a/plugins/bookmarks_plugin/bookmarks_plugin.py b/plugins/bookmarks_plugin/bookmarks_plugin.py new file mode 100644 index 0000000..83ad309 --- /dev/null +++ b/plugins/bookmarks_plugin/bookmarks_plugin.py @@ -0,0 +1,156 @@ +import os +import errno +import json +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from packets import player_warp_write, Packets +from utility_functions import build_packet + + +class Bookmarks(SimpleCommandPlugin): + """ + Plugin that allows defining planets as personal bookmarks you can /goto to. + """ + name = "bookmarks_plugin" + depends = ['command_dispatcher', 'player_manager'] + commands = ["bookmark", "remove", "goto"] + auto_activate = True + + def activate(self): + super(Bookmarks, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + self.verify_path("./config/bookmarks") + + @permissions(UserLevels.GUEST) + def bookmark(self, name): + """Bookmarks a planet for fast warp routes.\nSyntax: /bookmark (name)""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + + name = " ".join(name).strip().strip("\t") + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + + if on_ship: + self.protocol.send_chat_message("You need to be on a planet!") + return + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.bookmark.__doc__) + self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) + return + + for warp in self.bookmarks: + if warp[0] == planet: + self.protocol.send_chat_message("The planet you're on is already bookmarked: ^yellow;" + warp[1]) + return + if warp[1] == name: + self.protocol.send_chat_message("Bookmark with that name already exists!") + return + self.bookmarks.append([planet, name]) + self.protocol.send_chat_message("Bookmark ^yellow;%s^green; added." % name) + self.savebms() + + @permissions(UserLevels.GUEST) + def remove(self, name): + """Removes current planet from bookmarks.\nSyntax: /remove (name)""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.remove.__doc__) + self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) + return + + for warp in self.bookmarks: + if warp[1] == name: + self.bookmarks.remove(warp) + self.protocol.send_chat_message("Bookmark ^yellow;%s^green; removed." % name) + self.savebms() + return + self.protocol.send_chat_message("There is no bookmark named: ^yellow;%s" % name) + + @permissions(UserLevels.GUEST) + def goto(self, name): + """Warps your ship to a previously bookmarked planet.\nSyntax: /goto [name] *omit name for a list of bookmarks""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.goto.__doc__) + self.protocol.send_chat_message("Bookmarks: ^yellow;" + warpnames) + return + + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return + + for warp in self.bookmarks: + if warp[1] == name: + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map((x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.PLAYER_WARP, + player_warp_write(t="WARP_TO", + x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) + return + self.protocol.send_chat_message("There is no bookmark named: ^yellow;%s" % name) + + def verify_path(self, path): + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise + + def savebms(self): + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename, "w") as f: + json.dump(self.bookmarks, f) + except: + self.logger.exception("Couldn't save bookmarks.") + raise + + def beam_to_planet(self, where): + warp_packet = build_packet(Packets.PLAYER_WARP, player_warp_write(t="WARP_DOWN")) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Beamed down to ^yellow;%s^green; and your ship will arrive soon." % where) + self.factory.broadcast_planet( + "%s^green; beamed down to the planet" % self.protocol.player.colored_name(self.config.colors), + planet=self.protocol.player.planet) diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index b82a2a2..3c4e6eb 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -2,13 +2,11 @@ from twisted.python.filepath import FilePath from base_plugin import SimpleCommandPlugin from plugin_manager import FatalPluginError -from plugins.core import permissions, UserLevels class StarboundConfigManager(SimpleCommandPlugin): name = "starbound_config_manager" - depends = ['command_dispatcher', 'warpy_plugin'] - commands = ["spawn"] + depends = ['command_dispatcher'] def activate(self): super(StarboundConfigManager, self).activate() @@ -30,15 +28,3 @@ def activate(self): raise FatalPluginError( "The starbound gameServerPort option (%d) does not match the config.json upstream_port (%d)." % ( starbound_config['gameServerPort'], self.config.upstream_port)) - #self._spawn = starbound_config['defaultWorldCoordinate'].split(":") - self._spawn = "junk" - - @permissions(UserLevels.GUEST) - def spawn(self, data): - """Warps your ship to spawn.\nSyntax: /spawn""" - on_ship = self.protocol.player.on_ship - if not on_ship: - self.protocol.send_chat_message("You need to be on a ship!") - return - self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message("Moving your ship to spawn.") diff --git a/plugins/poi_plugin/__init__.py b/plugins/poi_plugin/__init__.py new file mode 100644 index 0000000..766075f --- /dev/null +++ b/plugins/poi_plugin/__init__.py @@ -0,0 +1 @@ +from poi_plugin import PointsofInterest diff --git a/plugins/poi_plugin/poi_plugin.py b/plugins/poi_plugin/poi_plugin.py new file mode 100644 index 0000000..7c4a817 --- /dev/null +++ b/plugins/poi_plugin/poi_plugin.py @@ -0,0 +1,117 @@ +import json +#from twisted.internet import reactor +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from packets import player_warp_write, Packets +from utility_functions import build_packet + + +class PointsofInterest(SimpleCommandPlugin): + """ + Plugin that allows admins to define Planets of Interest (PoI) any player can /poi to. + """ + name = "poi_plugin" + depends = ['command_dispatcher', 'player_manager'] + commands = ["poi_set", "poi_del", "poi"] + auto_activate = True + + def activate(self): + super(PointsofInterest, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + try: + with open("./config/pois.json") as f: + self.pois = json.load(f) + except: + self.pois = [] + + # Commands that allows admins to define Planets of Interest (PoI) any player can /poi to. + @permissions(UserLevels.ADMIN) + def poi_set(self, name): + """Sets current planet as Planet of Interest (PoI).\nSyntax: /poi_set (name)""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + self.protocol.send_chat_message(self.poi_set.__doc__) + return + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if on_ship: + self.protocol.send_chat_message("You need to be on a planet!") + return + for warp in self.pois: + if warp[0] == planet: + self.protocol.send_chat_message("The planet you're on is already set as a PoI: ^yellow;" + warp[1]) + return + if warp[1] == name: + self.protocol.send_chat_message("Planet of Interest named ^yellow;%s^green; already exists." % name) + return + self.pois.append([planet, name]) + self.protocol.send_chat_message("Planet of Interest ^yellow;%s^green; added." % name) + self.savepois() + + @permissions(UserLevels.ADMIN) + def poi_del(self, name): + """Removes current planet as Planet of Interest (PoI).\nSyntax: /poi_del (name)""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + self.protocol.send_chat_message(self.poi_del.__doc__) + return + for warp in self.pois: + if warp[1] == name: + self.pois.remove(warp) + self.protocol.send_chat_message("Planet of Interest ^yellow;%s^green; removed." % name) + self.savepois() + return + self.protocol.send_chat_message("There is no PoI named: ^yellow;%s^green;." % name) + + @permissions(UserLevels.GUEST) + def poi(self, name): + """Warps your ship to a Planet of Interest (PoI).\nSyntax: /poi [name] *omit name for a list of PoI's""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.pois: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;, ^yellow;".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.poi.__doc__) + self.protocol.send_chat_message("List of PoI's: ^yellow;" + warpnames) + return + + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return + + for warp in self.pois: + if warp[1] == name: + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.PLAYER_WARP, + player_warp_write(t="WARP_TO", + x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) + #reactor.callLater(1, self.beam_to_planet, name) # enable this for 1s delayed warping of a player + return + self.protocol.send_chat_message("There is no PoI named ^yellow;%s^green;." % name) + + def savepois(self): + try: + with open("./config/pois.json", "w") as f: + json.dump(self.pois, f) + except: + self.logger.exception("Couldn't save PoI's.") + raise + + def beam_to_planet(self, where): + warp_packet = build_packet(Packets.PLAYER_WARP, player_warp_write(t="WARP_DOWN")) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Beamed down to ^yellow;%s^green; and your ship will arrive soon." % where) + self.factory.broadcast_planet( + "%s^green; beamed down to the planet" % self.protocol.player.colored_name(self.config.colors), + planet=self.protocol.player.planet) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 7926b2f..1b18a15 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -11,7 +11,7 @@ class Warpy(SimpleCommandPlugin): """ name = "warpy_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["warp", "warp_ship", "outpost"] + commands = ["warp", "warp_ship", "outpost", "spawn"] auto_activate = True def activate(self): @@ -178,3 +178,12 @@ def warp_player_to_outpost(self, player_string): self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % player_string) self.protocol.send_chat_message(self.warp.__doc__) + @permissions(UserLevels.GUEST) + def spawn(self, data): + """Warps your ship to spawn.\nSyntax: /spawn""" + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return + self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) + self.protocol.send_chat_message("Moving your ship to spawn.") From 3a24e87725e11272ccc948ff0ac96ccffc8bc8aa Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 2 Feb 2015 11:59:18 -0500 Subject: [PATCH 48/81] Fixed the default.json file to work out of the box correctly (no more bugging out over) --- config/config.json.default | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index a61db7d..f4194c5 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -7,6 +7,7 @@ "admin": "^#C443F7;", "default": "^#F7EB43;", "guest": "^#F7EB43;", + "irc": "^#e39313;", "moderator": "^#4385F7;", "owner": "^#F7434C;", "registered": "^#A0F743;" @@ -27,12 +28,28 @@ "announcer_plugin": { "auto_activate": true }, + "bookmarks_plugin": { + "auto_activate": true + }, "chat_logger": { "auto_activate": true }, + "colored_names_plugin": { + "auto_activate": true + }, "command_dispatcher": { "auto_activate": true }, + "irc": { + "auto_activate": true, + "bot_nickname": "StarryPyBot", + "channel": "##test", + "color": "^#e39313;", + "echo_from_channel": true, + "nickserv_password": "password", + "port": 6667, + "server": "irc.freenode.net" + }, "motd_plugin": { "auto_activate": true, "motd": "Welcome to the server! Play nice." @@ -46,10 +63,6 @@ [ "coalore", 200 - ], - [ - "alienburger", - 5 ] ], "message": "Welcome to the server!" @@ -65,11 +78,6 @@ "DAMAGE_TILE_GROUP", "MODIFY_TILE_LIST" ], - "bad_packets_ship": [ - "ENTITY_INTERACT", - "OPEN_CONTAINER", - "CLOSE_CONTAINER" - ], "bad_packets_mild": [ "CONNECT_WIRE", "DISCONNECT_ALL_WIRES", @@ -77,6 +85,11 @@ "DAMAGE_TILE_GROUP", "MODIFY_TILE_LIST" ], + "bad_packets_ship": [ + "ENTITY_INTERACT", + "OPEN_CONTAINER", + "CLOSE_CONTAINER" + ], "blacklist": [ "bomb", "bombblockexplosion", @@ -161,9 +174,15 @@ "[^ \\w]+" ] }, + "players_plugin": { + "auto_activate": true + }, "plugin_manager": { "auto_activate": true }, + "poi_plugin": { + "auto_activate": true + }, "starbound_config_manager": { "auto_activate": true }, From 083cc04510230c3aebaadc1102f466f1f15d8236 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 2 Feb 2015 12:21:46 -0500 Subject: [PATCH 49/81] Turned IRC auto_enable off in config.json.defualts --- config/config.json.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json.default b/config/config.json.default index f4194c5..518289a 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -41,7 +41,7 @@ "auto_activate": true }, "irc": { - "auto_activate": true, + "auto_activate": false, "bot_nickname": "StarryPyBot", "channel": "##test", "color": "^#e39313;", From 88dbf6dd7cd74a0ed895a0ee7ea7c31067a64867 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 2 Feb 2015 20:01:37 -0500 Subject: [PATCH 50/81] Made some changes to logging. I removed the extra logging-level that was put in (TRACE), and instead pushed everything onto DEBUG. In addition, I modified the default options the live in the config.json file. My reasoning is that, logging was somewhat confusing, and the way it was setup carried more options than were needed. Instead, the server-owner need-only choose their logging-level from the config file, and run the server. In turn, the server now pushes all logging information into one central log-file, to reduce redundancy. I will likely add another log level back in for packet-dumping, since that is sometimes more than what debug needs as well. --- __init__.py | 6 - base_plugin.py | 6 + config/config.json.default | 5 +- packet_stream.py | 13 +-- packets/packet_types.py | 17 +-- .../admin_command_plugin.py | 32 +++--- plugins/core/player_manager/manager.py | 10 +- plugins/poi_plugin/poi_plugin.py | 5 +- server.py | 105 +++++++++--------- 9 files changed, 102 insertions(+), 97 deletions(-) diff --git a/__init__.py b/__init__.py index 6b163a6..ade1461 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1 @@ import logging -TRACE_LVL = 9 -logging.addLevelName(TRACE_LVL, "TRACE") - -def trace(self, message, *args, **kws): - self._log(TRACE_LVL, message, args, **kws) -logging.Logger.trace = trace \ No newline at end of file diff --git a/base_plugin.py b/base_plugin.py index f15d210..4cbd2bf 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -188,6 +188,9 @@ def on_client_disconnect_request(self, player): def on_player_warp(self, data): return True + def on_fly_ship(self, data): + return True + def on_central_structure_update(self, data): return True @@ -335,6 +338,9 @@ def after_client_disconnect_request(self, data): def after_player_warp(self, data): return True + def after_fly_ship(self, data): + return True + def after_central_structure_update(self, data): return True diff --git a/config/config.json.default b/config/config.json.default index 518289a..934e18b 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -14,10 +14,7 @@ }, "command_prefix": "/", "core_plugin_path": "core_plugins", - "debug_file": "debug.log", - "logging_format_console": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", - "logging_format_debugfile": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", - "logging_format_logfile": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", + "log_level": "DEBUG", "owner_uuid": "!!--REPLACE THIS--!!", "passthrough": false, "player_db": "config/player.db", diff --git a/packet_stream.py b/packet_stream.py index 979704b..9ea81e6 100644 --- a/packet_stream.py +++ b/packet_stream.py @@ -71,14 +71,13 @@ def check_packet(self): z = zlib.decompressobj() p_parsed.data = z.decompress(p_parsed.data) except zlib.error: - self.logger.error("Decompression error in check_packet.") - self.logger.trace("Parsed packet:") - self.logger.trace(pprint.pformat(p_parsed)) - self.logger.trace("Packet data:") - self.logger.trace(pprint.pformat(p_parsed.original_data.encode("hex"))) - self.logger.trace("Following packet data:") - self.logger.trace(pprint.pformat(self._stream.encode("hex"))) + self.logger.debug("Parsed packet:") + self.logger.debug(pprint.pformat(p_parsed)) + self.logger.debug("Packet data:") + self.logger.debug(pprint.pformat(p_parsed.original_data.encode("hex"))) + self.logger.debug("Following packet data:") + self.logger.debug(pprint.pformat(self._stream.encode("hex"))) raise packet = Packet(packet_id=p_parsed.id, payload_size=p_parsed.payload_size, data=p_parsed.data, original_data=p, direction=self.direction) diff --git a/packets/packet_types.py b/packets/packet_types.py index 4ae9bd7..0ea1ed6 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -181,12 +181,12 @@ def _decode(self, obj, context): celestial_request = lambda name="celestial_request": Struct(name, GreedyRange(star_string("requests"))) -world_coordinate = lambda name="world_coordinate": Struct(name, - SBInt32("x"), - SBInt32("y"), - SBInt32("z"), - SBInt32("planet"), - SBInt32("satellite")) +celestial_coordinate = lambda name="celestial_coordinate": Struct(name, + SBInt32("x"), + SBInt32("y"), + SBInt32("z"), + SBInt32("planet"), + SBInt32("satellite")) player_warp = lambda name="player_warp": Struct(name, Enum(UBInt8("warp_type"), @@ -202,6 +202,8 @@ def _decode(self, obj, context): warp_type=t, world_id=world_id)) +fly_ship = lambda name="fly_ship": Struct(name, + celestial_coordinate()) # partially correct. Needs work on dungeon ID value world_start = lambda name="world_start": Struct(name, @@ -209,7 +211,8 @@ def _decode(self, obj, context): StarByteArray("sky_data"), StarByteArray("weather_data"), #dungeon id stuff here - Array(2, BFloat32("coordinate")), + BFloat32("x"), + BFloat32("y"), Variant("world_properties"), UBInt32("client_id"), Flag("local_interpolation")) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index d2a9afe..12174f1 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -70,18 +70,18 @@ def whois(self, data): @permissions(UserLevels.MODERATOR) def promote(self, data): """Promotes/demotes a player to a specific rank.\nSyntax: /promote (player) (rank) (where rank is either: guest, registered, moderator, admin, or owner)""" - self.logger.trace("Promote command received with the following data: %s" % ":".join(data)) + self.logger.debug("Promote command received with the following data: %s" % ":".join(data)) if len(data) > 0: name = " ".join(data[:-1]) - self.logger.trace("Extracted the name %s in promote command." % name) + self.logger.debug("Extracted the name %s in promote command." % name) rank = data[-1].lower() - self.logger.trace("Extracted the rank %s in the promote command." % rank) + self.logger.debug("Extracted the rank %s in the promote command." % rank) player = self.player_manager.get_by_name(name) - self.logger.trace("Player object in promote command, found by name, is %s." % str(player)) + self.logger.debug("Player object in promote command, found by name, is %s." % str(player)) if player is not None: - self.logger.trace("Player object was not None. Dump of player object follows.") + self.logger.debug("Player object was not None. Dump of player object follows.") for line in pprint.pformat(player).split("\n"): - self.logger.trace("\t" + line) + self.logger.debug("\t" + line) old_rank = player.access_level players = self.player_manager.all() if old_rank == 1000: @@ -93,7 +93,7 @@ def promote(self, data): self.protocol.send_chat_message("You are the only (or last) owner. Promote denied!") return if old_rank >= self.protocol.player.access_level and not self.protocol.player.access_level != UserLevels.ADMIN: - self.logger.trace( + self.logger.debug( "The old rank was greater or equal to the current rank. Sending a message and returning.") self.protocol.send_chat_message( "You cannot change that user's access level as they are at least at an equal level as you.") @@ -109,15 +109,15 @@ def promote(self, data): elif rank == "guest": self.make_guest(player) else: - self.logger.trace("Non-existent rank. Returning with a help message.") + self.logger.debug("Non-existent rank. Returning with a help message.") self.protocol.send_chat_message("No such rank!\n" + self.promote.__doc__) return - self.logger.trace("Sending promotion message to promoter.") + self.logger.debug("Sending promotion message to promoter.") self.protocol.send_chat_message("%s: %s -> %s" % ( player.colored_name(self.config.colors), UserLevels(old_rank), rank.upper())) - self.logger.trace("Sending promotion message to promoted player.") + self.logger.debug("Sending promotion message to promoted player.") try: self.factory.protocols[player.protocol].send_chat_message( "%s has promoted you to %s" % ( @@ -125,31 +125,31 @@ def promote(self, data): except KeyError: self.logger.info("Promoted player is not logged in.") else: - self.logger.trace("Player wasn't found. Sending chat message to player.") + self.logger.debug("Player wasn't found. Sending chat message to player.") self.protocol.send_chat_message("Player not found!\n" + self.promote.__doc__) return else: - self.logger.trace("Received blank promotion command. Sending help message.") + self.logger.debug("Received blank promotion command. Sending help message.") self.protocol.send_chat_message(self.promote.__doc__) @permissions(UserLevels.MODERATOR) def make_guest(self, player): - self.logger.trace("Setting %s to GUEST", player.name) + self.logger.debug("Setting %s to GUEST", player.name) player.access_level = UserLevels.GUEST @permissions(UserLevels.MODERATOR) def make_registered(self, player): - self.logger.trace("Setting %s to REGISTERED", player.name) + self.logger.debug("Setting %s to REGISTERED", player.name) player.access_level = UserLevels.REGISTERED @permissions(UserLevels.ADMIN) def make_mod(self, player): player.access_level = UserLevels.MODERATOR - self.logger.trace("Setting %s to MODERATOR", player.name) + self.logger.debug("Setting %s to MODERATOR", player.name) @permissions(UserLevels.OWNER) def make_admin(self, player): - self.logger.trace("Setting %s to ADMIN", player.name) + self.logger.debug("Setting %s to ADMIN", player.name) player.access_level = UserLevels.ADMIN @permissions(UserLevels.OWNER) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 11127e7..a510ec1 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -162,13 +162,13 @@ class Player(Base): ips = relationship("IPAddress", order_by="IPAddress.id", backref="players") def colored_name(self, colors): - logger.trace("Building colored name.") + logger.debug("Building colored name.") color = colors[UserLevels(self.access_level).lower()] - logger.trace("Color is %s", color) + logger.debug("Color is %s", color) name = self.name - logger.trace("Name is %s", name) - logger.trace("Returning the following data for colored name. %s:%s:%s", color, name, - colors['default']) + logger.debug("Name is %s", name) + logger.debug("Returning the following data for colored name. %s:%s:%s", + color, name, colors['default']) return color + name + colors["default"] @property diff --git a/plugins/poi_plugin/poi_plugin.py b/plugins/poi_plugin/poi_plugin.py index 7c4a817..7ed195f 100644 --- a/plugins/poi_plugin/poi_plugin.py +++ b/plugins/poi_plugin/poi_plugin.py @@ -2,7 +2,7 @@ #from twisted.internet import reactor from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import player_warp_write, Packets +from packets import player_warp_write, Packets, fly_ship from utility_functions import build_packet @@ -15,6 +15,9 @@ class PointsofInterest(SimpleCommandPlugin): commands = ["poi_set", "poi_del", "poi"] auto_activate = True + def after_fly_ship(self, data): + self.logger.debug("Coords: %s", fly_ship().parse(data.data)) + def activate(self): super(PointsofInterest, self).activate() self.player_manager = self.plugins['player_manager'].player_manager diff --git a/server.py b/server.py index 3d65519..0cd0f46 100644 --- a/server.py +++ b/server.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- from _socket import SHUT_RDWR -import gettext +#import gettext import locale import logging from uuid import uuid4 @@ -8,13 +8,12 @@ import socket import datetime -import construct from twisted.internet import reactor from twisted.internet.error import CannotListenError from twisted.internet.protocol import ClientFactory, ServerFactory, Protocol, connectionDone +from twisted.internet.task import LoopingCall from construct import Container import construct.core -from twisted.internet.task import LoopingCall from config import ConfigurationManager from packet_stream import PacketStream @@ -23,10 +22,6 @@ from utility_functions import build_packet VERSION = "1.4.4" -TRACE = False -TRACE_LVL = 9 -logging.addLevelName(9, "TRACE") -logging.Logger.trace = lambda s, m, *a, **k: s._log(TRACE_LVL, m, a, **k) def port_check(upstream_hostname, upstream_port): @@ -53,11 +48,11 @@ def __init__(self): """ """ self.id = str(uuid4().hex) - logger.trace("Creating protocol with ID %s.", self.id) + logger.debug("Creating protocol with ID %s.", self.id) self.factory.protocols[self.id] = self self.player = None self.state = None - logger.trace("Trying to initialize configuration manager.") + logger.debug("Trying to initialize configuration manager.") self.config = ConfigurationManager() self.parsing = False self.buffering_packet = None @@ -75,7 +70,7 @@ def __init__(self): packets.Packets.CLIENT_DISCONNECT_REQUEST: self.client_disconnect_request, # 8 packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, # 9 packets.Packets.PLAYER_WARP: self.player_warp, # 10 - packets.Packets.FLY_SHIP: lambda x: True, # 11 + packets.Packets.FLY_SHIP: self.fly_ship, # 11 packets.Packets.CHAT_SENT: self.chat_sent, # 12 packets.Packets.CELESTIAL_REQUEST: self.celestial_request, # 13 packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, # 14 @@ -416,6 +411,16 @@ def player_warp(self, data): """ return True + @route + def fly_ship(self, data): + """ + Called when the players moves their ship. + + :param data: The fly_ship data. + :rtype : bool + """ + return True + def handle_starbound_packets(self, p): """ This function is the meat of it all. Every time a full packet with @@ -442,16 +447,18 @@ def send_chat_message(self, text, mode=0, channel='', name=''): self.send_chat_message(line) return if self.player is not None: - logger.trace("Calling send_chat_message from player %s on channel %d with mode %d with reported username of %s with message: %s", self.player.name, channel, mode, name, text) + logger.debug(('Calling send_chat_message from player %s on channel' + ' %s with mode %s with reported username of %s with' + ' message: %s'), self.player.name, channel, mode, name, text) chat_data = packets.chat_received().build(Container(mode=mode, chat_channel=channel, client_id=0, name=name, message=text.encode("utf-8"))) - logger.trace("Built chat payload. Data: %s", chat_data.encode("hex")) + logger.debug("Built chat payload. Data: %s", chat_data.encode("hex")) chat_packet = build_packet(packets.Packets.CHAT_RECEIVED, chat_data) - logger.trace("Built chat packet. Data: %s", chat_packet.encode("hex")) + logger.debug("Built chat packet. Data: %s", chat_packet.encode("hex")) self.transport.write(chat_packet) logger.debug("Sent chat message with text: %s", text) @@ -638,13 +645,13 @@ def reap_dead_protocols(self): start_time = datetime.datetime.now() for protocol in self.protocols.itervalues(): if (protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: - logger.trace("Reaping protocol %s. Reason: Server protocol timeout.", protocol.id) + logger.debug("Reaping protocol %s. Reason: Server protocol timeout.", protocol.id) protocol.connectionLost() count += 1 continue if protocol.client_protocol is not None and (protocol.client_protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: protocol.connectionLost() - logger.trace("Reaping protocol %s. Reason: Client protocol timeout.", protocol.id) + logger.debug("Reaping protocol %s. Reason: Client protocol timeout.", protocol.id) count += 1 if count == 1: logger.info("1 connection reaped.") @@ -661,11 +668,11 @@ class StarboundClientFactory(ClientFactory): protocol = ClientProtocol def __init__(self, server_protocol): - logger.trace("Client protocol instantiated.") + logger.debug("Client protocol instantiated.") self.server_protocol = server_protocol def buildProtocol(self, address): - logger.trace("Building protocol in StarboundClientFactory to address %s", address) + logger.debug("Building protocol in StarboundClientFactory to address %s", address) protocol = ClientFactory.buildProtocol(self, address) protocol.server_protocol = self.server_protocol return protocol @@ -674,46 +681,42 @@ def init_localization(): locale.setlocale(locale.LC_ALL, '') except: locale.setlocale(locale.LC_ALL, 'en_US.utf8') - """try: - loc = locale.getlocale() - filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] - print "Opening message file %s for locale %s." % (filename, loc[0]) - trans = gettext.GNUTranslations(open(filename, "rb")) - except (IOError, TypeError, IndexError): - print "Locale not found. Using default messages." - trans = gettext.NullTranslations() - trans.install()""" + #try: + # loc = locale.getlocale() + # filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] + # print "Opening message file %s for locale %s." % (filename, loc[0]) + # trans = gettext.GNUTranslations(open(filename, "rb")) + #except (IOError, TypeError, IndexError): + # print "Locale not found. Using default messages." + # trans = gettext.NullTranslations() + #trans.install() if __name__ == '__main__': init_localization() + + print('Attempting initialization of configuration manager singleton.') + config = ConfigurationManager() + logger = logging.getLogger('starrypy') logger.setLevel(9) - if TRACE: - trace_logger = logging.FileHandler("trace.log") - trace_logger.setLevel("TRACE") - trace_logger.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) - logger.addHandler(trace_logger) - logger.trace("Initialized trace logger.") - - fh_d = logging.FileHandler("debug.log") - fh_d.setLevel(logging.DEBUG) - fh_w = logging.FileHandler("server.log") - fh_w.setLevel(logging.INFO) - sh = logging.StreamHandler(sys.stdout) - sh.setLevel(logging.INFO) - logger.addHandler(sh) - logger.addHandler(fh_d) - logger.addHandler(fh_w) - logger.trace("Attempting initialization of configuration manager singleton.") - config = ConfigurationManager() - logger.trace("Attemping to set logging formatters from configuration.") - console_formatter = logging.Formatter(config.logging_format_console) - logfile_formatter = logging.Formatter(config.logging_format_logfile) - debugfile_formatter = logging.Formatter(config.logging_format_debugfile) - fh_d.setFormatter(debugfile_formatter) - fh_w.setFormatter(logfile_formatter) - sh.setFormatter(console_formatter) + log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s # %(message)s') + if config.log_level == 'DEBUG': + log_level = logging.DEBUG + else: + log_level = logging.INFO + + print('Setup console logging...') + console_handle = logging.StreamHandler(sys.stdout) + console_handle.setLevel(log_level) + logger.addHandler(console_handle) + console_handle.setFormatter(log_format) + + print('Setup file-based logging...') + logfile_handle = logging.FileHandler("server.log") + logfile_handle.setLevel(log_level) + logger.addHandler(logfile_handle) + logfile_handle.setFormatter(log_format) if config.port_check: logger.debug("Port check enabled. Performing port check to %s:%d", config.upstream_hostname, From f882609f4c2179334b28d0e43772f50a4b690911 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Mon, 2 Feb 2015 21:57:46 -0500 Subject: [PATCH 51/81] Fixed ship-based warping for bookmarks and PoI. Remade /spawn as a convenince command based on the PoI system. Ship-to-ship warping does not work currently. My guess is that, reading celestial coordinates from a player's protocol is currently not working as expected. This is less imporant for production, so I'll fix it whenever I get to it. --- config/pois.json | 2 +- packets/packet_types.py | 9 ++++ plugins/bookmarks_plugin/bookmarks_plugin.py | 54 +++++++++----------- plugins/poi_plugin/poi_plugin.py | 45 +++++++++------- plugins/warpy_plugin/warpy_plugin.py | 16 +----- utility_functions.py | 7 ++- 6 files changed, 66 insertions(+), 67 deletions(-) diff --git a/config/pois.json b/config/pois.json index 39d1633..afec3fd 100644 --- a/config/pois.json +++ b/config/pois.json @@ -1 +1 @@ -[["-30562840:658406569:-263745435:3:6", "spawn"], ["-30562840:658406569:-263745435:4:0", "forest"]] \ No newline at end of file +[["-30562840:658406569:-263745435:4:0", "forest"], ["-30562840:658406569:-263745435:3:6", "spawn"]] \ No newline at end of file diff --git a/packets/packet_types.py b/packets/packet_types.py index 0ea1ed6..377e790 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -205,6 +205,15 @@ def _decode(self, obj, context): fly_ship = lambda name="fly_ship": Struct(name, celestial_coordinate()) +fly_ship_write = lambda x=0, y=0, z=0, planet=0, satellite=0: fly_ship().build( + Container( + celestial_coordinate=Container( + x=x, + y=y, + z=z, + planet=planet, + satellite=satellite))) + # partially correct. Needs work on dungeon ID value world_start = lambda name="world_start": Struct(name, Variant("planet"), # rename to templateData? diff --git a/plugins/bookmarks_plugin/bookmarks_plugin.py b/plugins/bookmarks_plugin/bookmarks_plugin.py index 83ad309..c2a7989 100644 --- a/plugins/bookmarks_plugin/bookmarks_plugin.py +++ b/plugins/bookmarks_plugin/bookmarks_plugin.py @@ -3,7 +3,7 @@ import json from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import player_warp_write, Packets +from packets import Packets, fly_ship, fly_ship_write from utility_functions import build_packet @@ -13,7 +13,7 @@ class Bookmarks(SimpleCommandPlugin): """ name = "bookmarks_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["bookmark", "remove", "goto"] + commands = ["bookmark_add", "bookmark_del", "goto"] auto_activate = True def activate(self): @@ -22,8 +22,8 @@ def activate(self): self.verify_path("./config/bookmarks") @permissions(UserLevels.GUEST) - def bookmark(self, name): - """Bookmarks a planet for fast warp routes.\nSyntax: /bookmark (name)""" + def bookmark_add(self, name): + """Bookmarks a planet for fast warp routes.\nSyntax: /bookmark_add (name)""" filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" try: with open(filename) as f: @@ -45,7 +45,7 @@ def bookmark(self, name): warps.append(warp[1]) warpnames = "^green;,^yellow; ".join(warps) if warpnames == "": warpnames = "^gray;(none)^green;" - self.protocol.send_chat_message(self.bookmark.__doc__) + self.protocol.send_chat_message(self.bookmark_add.__doc__) self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) return @@ -61,8 +61,8 @@ def bookmark(self, name): self.savebms() @permissions(UserLevels.GUEST) - def remove(self, name): - """Removes current planet from bookmarks.\nSyntax: /remove (name)""" + def bookmark_del(self, name): + """Removes current planet from bookmarks.\nSyntax: /bookmark_del (name)""" filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" try: with open(filename) as f: @@ -77,7 +77,7 @@ def remove(self, name): warps.append(warp[1]) warpnames = "^green;,^yellow; ".join(warps) if warpnames == "": warpnames = "^gray;(none)^green;" - self.protocol.send_chat_message(self.remove.__doc__) + self.protocol.send_chat_message(self.bookmark_del.__doc__) self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) return @@ -118,26 +118,18 @@ def goto(self, name): for warp in self.bookmarks: if warp[1] == name: x, y, z, planet, satellite = warp[0].split(":") - x, y, z, planet, satellite = map((x, y, z, planet, satellite)) - warp_packet = build_packet(Packets.PLAYER_WARP, - player_warp_write(t="WARP_TO", - x=x, - y=y, - z=z, - planet=planet, - satellite=satellite)) + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) self.protocol.client_protocol.transport.write(warp_packet) self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) return self.protocol.send_chat_message("There is no bookmark named: ^yellow;%s" % name) - def verify_path(self, path): - try: - os.makedirs(path) - except OSError as exception: - if exception.errno != errno.EEXIST: - raise - def savebms(self): filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" try: @@ -147,10 +139,12 @@ def savebms(self): self.logger.exception("Couldn't save bookmarks.") raise - def beam_to_planet(self, where): - warp_packet = build_packet(Packets.PLAYER_WARP, player_warp_write(t="WARP_DOWN")) - self.protocol.client_protocol.transport.write(warp_packet) - self.protocol.send_chat_message("Beamed down to ^yellow;%s^green; and your ship will arrive soon." % where) - self.factory.broadcast_planet( - "%s^green; beamed down to the planet" % self.protocol.player.colored_name(self.config.colors), - planet=self.protocol.player.planet) + def verify_path(self, path): + """ + Helper function to make sure path exists, and create if it doesn't. + """ + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise diff --git a/plugins/poi_plugin/poi_plugin.py b/plugins/poi_plugin/poi_plugin.py index 7ed195f..24d0322 100644 --- a/plugins/poi_plugin/poi_plugin.py +++ b/plugins/poi_plugin/poi_plugin.py @@ -2,7 +2,7 @@ #from twisted.internet import reactor from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import player_warp_write, Packets, fly_ship +from packets import Packets, fly_ship, fly_ship_write from utility_functions import build_packet @@ -12,7 +12,7 @@ class PointsofInterest(SimpleCommandPlugin): """ name = "poi_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["poi_set", "poi_del", "poi"] + commands = ["poi_set", "poi_del", "poi", "spawn"] auto_activate = True def after_fly_ship(self, data): @@ -90,19 +90,36 @@ def poi(self, name): if warp[1] == name: x, y, z, planet, satellite = warp[0].split(":") x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) - warp_packet = build_packet(Packets.PLAYER_WARP, - player_warp_write(t="WARP_TO", - x=x, - y=y, - z=z, - planet=planet, - satellite=satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) self.protocol.client_protocol.transport.write(warp_packet) self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) - #reactor.callLater(1, self.beam_to_planet, name) # enable this for 1s delayed warping of a player return self.protocol.send_chat_message("There is no PoI named ^yellow;%s^green;." % name) + @permissions(UserLevels.GUEST) + def spawn(self, data): + """Warps your ship to spawn.\nSyntax: /spawn""" + for warp in self.pois: + if warp[1] == 'spawn': + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % 'Spawn') + return + else: + self.protocol.send_chat_message("The spawn planet must be set first!") + def savepois(self): try: with open("./config/pois.json", "w") as f: @@ -110,11 +127,3 @@ def savepois(self): except: self.logger.exception("Couldn't save PoI's.") raise - - def beam_to_planet(self, where): - warp_packet = build_packet(Packets.PLAYER_WARP, player_warp_write(t="WARP_DOWN")) - self.protocol.client_protocol.transport.write(warp_packet) - self.protocol.send_chat_message("Beamed down to ^yellow;%s^green; and your ship will arrive soon." % where) - self.factory.broadcast_planet( - "%s^green; beamed down to the planet" % self.protocol.player.colored_name(self.config.colors), - planet=self.protocol.player.planet) diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 1b18a15..8268638 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import player_warp_write, Packets, player_warp +from packets import Packets, player_warp, player_warp_write, fly_ship, fly_ship_write from utility_functions import build_packet, move_ship_to_coords, extract_name @@ -11,7 +11,7 @@ class Warpy(SimpleCommandPlugin): """ name = "warpy_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["warp", "warp_ship", "outpost", "spawn"] + commands = ["warp", "warp_ship", "outpost"] auto_activate = True def activate(self): @@ -45,7 +45,6 @@ def warp(self, name): return self.warp_player_to_player(first_name, second_name) -# THIS IS CURRENTLY BROKEN! @permissions(UserLevels.ADMIN) def warp_ship(self, location): """Warps a player ship to another players ship.\nSyntax: /warp_ship [player] (to player)""" @@ -66,7 +65,6 @@ def warp_ship(self, location): self.protocol.send_chat_message(str(e)) return self.move_player_ship_to_other(first_name, second_name) -#!!!!!!!!!!!!!!!!!!!!!!! @permissions(UserLevels.MODERATOR) def outpost(self, name): @@ -177,13 +175,3 @@ def warp_player_to_outpost(self, player_string): else: self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % player_string) self.protocol.send_chat_message(self.warp.__doc__) - - @permissions(UserLevels.GUEST) - def spawn(self, data): - """Warps your ship to spawn.\nSyntax: /spawn""" - on_ship = self.protocol.player.on_ship - if not on_ship: - self.protocol.send_chat_message("You need to be on a ship!") - return - self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message("Moving your ship to spawn.") diff --git a/utility_functions.py b/utility_functions.py index c57a524..ed846a7 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -64,10 +64,9 @@ def move_ship_to_coords(protocol, x, y, z, planet, satellite): logger.info("Moving %s's ship to coordinates: %s", protocol.player.name, ":".join((str(x), str(y), str(z), str(planet), str(satellite)))) x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) - warp_packet = build_packet(packets.Packets.PLAYER_WARP, - packets.player_warp_write(t="WARP_TO", x=x, y=y, z=z, - planet=planet, - satellite=satellite, player="".encode('utf-8'))) + warp_packet = build_packet(packets.Packets.FLY_SHIP, + packets.fly_ship_write(x=x, y=y, z=z, planet=planet, + satellite=satellite)) protocol.client_protocol.transport.write(warp_packet) From 7537ef1a545786f003c8e7234eed2147f9017d73 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 00:29:47 -0500 Subject: [PATCH 52/81] Remove visibility of other .json files from git commits. No one needs to see my test PoI's and bookmarks --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 37d2614..8697f98 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ nosetests.xml .pydevproject # Project specific -*config.json +*.json *.db *.db-journal *.log From c35caecb9f137c473dc03d15233f70a8ff7183eb Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 00:49:06 -0500 Subject: [PATCH 53/81] Re-added modchatter plugin, and tested to be sure it worked --- config/config.json.default | 3 ++ plugins/mod_chatter/__init__.py | 1 + plugins/mod_chatter/mod_chatter.py | 47 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 plugins/mod_chatter/__init__.py create mode 100644 plugins/mod_chatter/mod_chatter.py diff --git a/config/config.json.default b/config/config.json.default index 934e18b..e77e7e8 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -47,6 +47,9 @@ "port": 6667, "server": "irc.freenode.net" }, + "mod_chatter": { + "auto_activate": true + }, "motd_plugin": { "auto_activate": true, "motd": "Welcome to the server! Play nice." diff --git a/plugins/mod_chatter/__init__.py b/plugins/mod_chatter/__init__.py new file mode 100644 index 0000000..4719056 --- /dev/null +++ b/plugins/mod_chatter/__init__.py @@ -0,0 +1 @@ +from mod_chatter import ModChatter diff --git a/plugins/mod_chatter/mod_chatter.py b/plugins/mod_chatter/mod_chatter.py new file mode 100644 index 0000000..62f2111 --- /dev/null +++ b/plugins/mod_chatter/mod_chatter.py @@ -0,0 +1,47 @@ +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +import packets +from datetime import datetime + + +class ModChatter(SimpleCommandPlugin): + """Adds support for moderators/admins/owner group chatter.""" + name = "mod_chatter" + depends = ['command_dispatcher', 'player_manager'] + commands = ["modchat", "mc"] + auto_activate = True + + def activate(self): + super(ModChatter, self).activate() + self.prefix = self.config.chat_prefix + self.player_manager = self.plugins['player_manager'].player_manager + + @permissions(UserLevels.MODERATOR) + def modchat(self, data): + """Allows mod-only chatting.\nSyntax: /modchat (msg)""" + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^red;<" + now.strftime("%H:%M") + "> ^yellow;" + else: + timestamp = "" + if len(data) == 0: + self.protocol.send_chat_message(self.modchat.__doc__) + return + try: + message = " ".join(data) + for protocol in self.factory.protocols.itervalues(): + if protocol.player.access_level >= UserLevels.MODERATOR: + protocol.send_chat_message(timestamp + + "%sModChat: ^yellow;<%s^yellow;> %s%s" % (self.config.colors["admin"], self.protocol.player.colored_name(self.config.colors), + self.config.colors["admin"],message.decode("utf-8"))) + self.logger.info("Received an admin message from %s. Message: %s", self.protocol.player.name, + message.decode("utf-8")) + except ValueError as e: + self.protocol.send_chat_message(self.modchat.__doc__) + except TypeError as e: + self.protocol.send_chat_message(self.modchat.__doc__) + + @permissions(UserLevels.MODERATOR) + def mc(self, data): + """Allows mod-only chatting.\nSyntax: /modchat (msg)""" + self.modchat(data) From 834db725aba832057c7e75acbe7b2d9fe235c167 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 04:51:33 -0500 Subject: [PATCH 54/81] Worked on chat system. Fixed packet structure, resolved issue #34 regarding 'local' chat skipping IRC. Added uptime plugin --- packets/packet_types.py | 30 ++++++++++++++++------------- plugins/chat_logger/chat_logger.py | 5 ++++- plugins/irc_plugin/irc_plugin.py | 4 ++-- plugins/mod_chatter/mod_chatter.py | 1 - plugins/uptime/__init__.py | 1 + plugins/uptime/uptime.py | 31 ++++++++++++++++++++++++++++++ server.py | 4 ++-- 7 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 plugins/uptime/__init__.py create mode 100644 plugins/uptime/uptime.py diff --git a/packets/packet_types.py b/packets/packet_types.py index 377e790..b72a291 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -79,13 +79,6 @@ class EntityType(IntEnum): NPC = 8 -class MessageContextMode(IntEnum): - CHANNEL = 0 - BROADCAST = 1 - WHISPER = 2 - COMMAND_RESULT = 3 - - class PacketOutOfOrder(Exception): pass @@ -140,8 +133,12 @@ def _decode(self, obj, context): # corrected. needs testing chat_received = lambda name="chat_received": Struct(name, - Byte("mode"), - star_string("chat_channel"), + Enum(Byte("mode"), + CHANNEL=0, + BROADCAST=1, + WHISPER=2, + COMMAND_RESULT=3), + star_string("channel"), UBInt32("client_id"), star_string("name"), star_string("message")) @@ -155,6 +152,11 @@ def _decode(self, obj, context): PARTY=2) ) +chat_sent_write = lambda message, send_mode: chat_sent().build( + Container( + message=message, + send_mode=send_mode)) + # quite a bit of guesswork and hackery here with the ship_upgrades. client_connect = lambda name="client_connect": Struct(name, VLQ("asset_digest_length"), @@ -236,10 +238,12 @@ def _decode(self, obj, context): Byte("variant_type"), star_string("description")) -give_item_write = lambda name, count: give_item().build(Container(name=name, - count=count, - variant_type=7, - description='')) +give_item_write = lambda name, count: give_item().build( + Container( + name=name, + count=count, + variant_type=7, + description='')) update_world_properties = lambda name="world_properties": Struct(name, UBInt8("count"), diff --git a/plugins/chat_logger/chat_logger.py b/plugins/chat_logger/chat_logger.py index 756abcb..d46f57a 100644 --- a/plugins/chat_logger/chat_logger.py +++ b/plugins/chat_logger/chat_logger.py @@ -3,9 +3,12 @@ class ChatLogger(BasePlugin): + """ + Plugin which parses player chatter into the log file. + """ name = "chat_logger" def on_chat_sent(self, data): parsed = chat_sent().parse(data.data) parsed.message = parsed.message.decode("utf-8") - self.logger.info("Chat message sent: <%s> %s", self.protocol.player.name, parsed.message) \ No newline at end of file + self.logger.info("Chat message sent: <%s> %s", self.protocol.player.name, parsed.message) diff --git a/plugins/irc_plugin/irc_plugin.py b/plugins/irc_plugin/irc_plugin.py index 2ee2655..99cf1e2 100644 --- a/plugins/irc_plugin/irc_plugin.py +++ b/plugins/irc_plugin/irc_plugin.py @@ -47,6 +47,8 @@ def deactivate(self): def on_chat_sent(self, data): parsed = chat_sent().parse(data.data) + if parsed.send_mode == 'LOCAL': + return True if not parsed.message.startswith('/'): for p in self.irc_factory.irc_clients.itervalues(): p.msg(self.channel, "<%s> %s" % (self.protocol.player.name.encode("utf-8"), parsed.message.encode("utf-8"))) @@ -55,10 +57,8 @@ def on_chat_sent(self, data): def on_client_connect(self, data): parsed = client_connect().parse(data.data) self.logger.info(parsed.name) - for p in self.irc_factory.irc_clients.itervalues(): p.msg(self.channel, "%s connected" % parsed.name.encode("utf-8")) - return True def on_client_disconnect_request(self, data): diff --git a/plugins/mod_chatter/mod_chatter.py b/plugins/mod_chatter/mod_chatter.py index 62f2111..1240534 100644 --- a/plugins/mod_chatter/mod_chatter.py +++ b/plugins/mod_chatter/mod_chatter.py @@ -13,7 +13,6 @@ class ModChatter(SimpleCommandPlugin): def activate(self): super(ModChatter, self).activate() - self.prefix = self.config.chat_prefix self.player_manager = self.plugins['player_manager'].player_manager @permissions(UserLevels.MODERATOR) diff --git a/plugins/uptime/__init__.py b/plugins/uptime/__init__.py new file mode 100644 index 0000000..cae4653 --- /dev/null +++ b/plugins/uptime/__init__.py @@ -0,0 +1 @@ +from uptime import UptimePlugin \ No newline at end of file diff --git a/plugins/uptime/uptime.py b/plugins/uptime/uptime.py new file mode 100644 index 0000000..151c8f7 --- /dev/null +++ b/plugins/uptime/uptime.py @@ -0,0 +1,31 @@ +# -*- coding: UTF-8 -*- +from datetime import datetime + +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from server import VERSION + + +class UptimePlugin(SimpleCommandPlugin): + """ + Very simple plugin that responds to /uptime with the time StarryPy is running. + """ + name = "uptime_plugin" + depends = ["command_dispatcher", "player_manager"] + commands = ["uptime"] + auto_activate = True + + def activate(self): + super(UptimePlugin, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + self.started_at = datetime.now() + + @permissions(UserLevels.GUEST) + def uptime(self, data_): + """Displays server uptime and version.\nSyntax: /uptime""" + now = datetime.now() + delta = now - self.started_at + self.protocol.send_chat_message("<" + now.strftime("%H:%M") + "> Uptime: ^cyan;%d^green; day(s), ^cyan;%d^green; hour(s), ^cyan;%d^green; min(s), ^cyan;%d^green; user(s)\nRunning Starbound server wrapper ^magenta;StarryPy ^cyan;v%s" % ( + delta.days, (delta.seconds / 3600) % 3600, (delta.seconds / 60) % 60, + len(self.player_manager.who()), VERSION) + ) diff --git a/server.py b/server.py index 0cd0f46..8b6c132 100644 --- a/server.py +++ b/server.py @@ -428,7 +428,7 @@ def handle_starbound_packets(self, p): """ return self.call_mapping[p.id](p) - def send_chat_message(self, text, mode=0, channel='', name=''): + def send_chat_message(self, text, mode='BROADCAST', channel='', name=''): """ Convenience function to send chat messages to the client. Note that this does *not* send messages to the server at large; broadcast should be @@ -451,7 +451,7 @@ def send_chat_message(self, text, mode=0, channel='', name=''): ' %s with mode %s with reported username of %s with' ' message: %s'), self.player.name, channel, mode, name, text) chat_data = packets.chat_received().build(Container(mode=mode, - chat_channel=channel, + channel=channel, client_id=0, name=name, message=text.encode("utf-8"))) From a2929275a3f4f8681b0e53c4a0a24be4613a5eb6 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 04:59:04 -0500 Subject: [PATCH 55/81] Fixed issue #22 regarding weird spacing. --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 12174f1..6de1364 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -172,17 +172,15 @@ def kick(self, data): self.factory.broadcast("%s^green; kicked %s ^green;(reason: ^yellow;%s^green;)" % (self.protocol.player.colored_name(self.config.colors), info.colored_name(self.config.colors), - " ".join(reason))) + "".join(reason))) self.logger.info("%s kicked %s (reason: %s)", self.protocol.player.name, info.name, - " ".join(reason)) + "".join(reason)) tp = self.factory.protocols[info.protocol] tp.die() else: self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;." % name) return False - - @permissions(UserLevels.ADMIN) def ban(self, data): """Bans an IP or a Player (by name).\nSyntax: /ban (IP | player)\nTip: Use /whois (player) to get IP""" From dee6f71a7c68eb81faacd7cdf8bd70d01e8693bb Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 11:50:05 -0500 Subject: [PATCH 56/81] Re-built a custom logging level (VDEBUG) for the more verbose messages. --- __init__.py | 7 +++++++ plugins/poi_plugin/poi_plugin.py | 5 ----- server.py | 29 +++++++++++++++++++---------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/__init__.py b/__init__.py index ade1461..34a33c5 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,8 @@ import logging + +VDEBUG_LVL = 9 +logging.addLevelName(VDEBUG_LVL, "VDEBUG") +def vdebug(self, message, *args, **kws): + if self.isEnabledFor(VDEBUG_LVL): + self._log(VDEBUG_LVL, message, args, **kws) +logging.Logger.vdebug = vdebug diff --git a/plugins/poi_plugin/poi_plugin.py b/plugins/poi_plugin/poi_plugin.py index 24d0322..37b81a2 100644 --- a/plugins/poi_plugin/poi_plugin.py +++ b/plugins/poi_plugin/poi_plugin.py @@ -1,5 +1,4 @@ import json -#from twisted.internet import reactor from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels from packets import Packets, fly_ship, fly_ship_write @@ -15,9 +14,6 @@ class PointsofInterest(SimpleCommandPlugin): commands = ["poi_set", "poi_del", "poi", "spawn"] auto_activate = True - def after_fly_ship(self, data): - self.logger.debug("Coords: %s", fly_ship().parse(data.data)) - def activate(self): super(PointsofInterest, self).activate() self.player_manager = self.plugins['player_manager'].player_manager @@ -27,7 +23,6 @@ def activate(self): except: self.pois = [] - # Commands that allows admins to define Planets of Interest (PoI) any player can /poi to. @permissions(UserLevels.ADMIN) def poi_set(self, name): """Sets current planet as Planet of Interest (PoI).\nSyntax: /poi_set (name)""" diff --git a/server.py b/server.py index 8b6c132..146a12c 100644 --- a/server.py +++ b/server.py @@ -24,6 +24,13 @@ VERSION = "1.4.4" +VDEBUG_LVL = 9 +logging.addLevelName(VDEBUG_LVL, "VDEBUG") +def vdebug(self, message, *args, **kws): + if self.isEnabledFor(VDEBUG_LVL): + self._log(VDEBUG_LVL, message, args, **kws) +logging.Logger.vdebug = vdebug + def port_check(upstream_hostname, upstream_port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) @@ -48,7 +55,7 @@ def __init__(self): """ """ self.id = str(uuid4().hex) - logger.debug("Creating protocol with ID %s.", self.id) + logger.vdebug("Creating protocol with ID %s.", self.id) self.factory.protocols[self.id] = self self.player = None self.state = None @@ -447,7 +454,7 @@ def send_chat_message(self, text, mode='BROADCAST', channel='', name=''): self.send_chat_message(line) return if self.player is not None: - logger.debug(('Calling send_chat_message from player %s on channel' + logger.vdebug(('Calling send_chat_message from player %s on channel' ' %s with mode %s with reported username of %s with' ' message: %s'), self.player.name, channel, mode, name, text) chat_data = packets.chat_received().build(Container(mode=mode, @@ -455,12 +462,12 @@ def send_chat_message(self, text, mode='BROADCAST', channel='', name=''): client_id=0, name=name, message=text.encode("utf-8"))) - logger.debug("Built chat payload. Data: %s", chat_data.encode("hex")) + logger.vdebug("Built chat payload. Data: %s", chat_data.encode("hex")) chat_packet = build_packet(packets.Packets.CHAT_RECEIVED, chat_data) - logger.debug("Built chat packet. Data: %s", chat_packet.encode("hex")) + logger.vdebug("Built chat packet. Data: %s", chat_packet.encode("hex")) self.transport.write(chat_packet) - logger.debug("Sent chat message with text: %s", text) + logger.vdebug("Sent chat message with text: %s", text) def write(self, data): """ @@ -635,12 +642,12 @@ def buildProtocol(self, address): :rtype : Protocol """ - logger.debug("Building protocol to address %s", address) + logger.vdebug("Building protocol to address %s", address) p = ServerFactory.buildProtocol(self, address) return p def reap_dead_protocols(self): - logger.debug("Reaping dead connections.") + logger.vdebug("Reaping dead connections.") count = 0 start_time = datetime.datetime.now() for protocol in self.protocols.itervalues(): @@ -658,7 +665,7 @@ def reap_dead_protocols(self): elif count > 1: logger.info("%d connections reaped.") else: - logger.debug("No connections reaped.") + logger.vdebug("No connections reaped.") class StarboundClientFactory(ClientFactory): @@ -668,11 +675,11 @@ class StarboundClientFactory(ClientFactory): protocol = ClientProtocol def __init__(self, server_protocol): - logger.debug("Client protocol instantiated.") + logger.vdebug("Client protocol instantiated.") self.server_protocol = server_protocol def buildProtocol(self, address): - logger.debug("Building protocol in StarboundClientFactory to address %s", address) + logger.vdebug("Building protocol in StarboundClientFactory to address %s", address) protocol = ClientFactory.buildProtocol(self, address) protocol.server_protocol = self.server_protocol return protocol @@ -703,6 +710,8 @@ def init_localization(): log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s # %(message)s') if config.log_level == 'DEBUG': log_level = logging.DEBUG + elif config.log_level == 'VDEBUG': + log_level = "VDEBUG" else: log_level = logging.INFO From 517675cb1d887e992a30723d5df76c32286b386e Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 18:54:17 -0500 Subject: [PATCH 57/81] added auto-reconnecting functionality to the IRC Bot, in case of connection loss --- plugins/irc_plugin/irc_manager.py | 46 +++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/plugins/irc_plugin/irc_manager.py b/plugins/irc_plugin/irc_manager.py index 1f1b024..4128434 100644 --- a/plugins/irc_plugin/irc_manager.py +++ b/plugins/irc_plugin/irc_manager.py @@ -1,11 +1,9 @@ -# twisted imports from twisted.words.protocols import irc from twisted.internet import reactor, protocol from uuid import uuid4 class StarryPyIrcBot(irc.IRCClient): - def __init__(self, logger, nickname, nickserv_password, factory, broadcast_target, colors, echo_from_channel): self.logger = logger self.nickname = nickname @@ -16,23 +14,35 @@ def __init__(self, logger, nickname, nickserv_password, factory, broadcast_targe self.colors = colors self.echo_from_channel = echo_from_channel + def connectionMade(self): + irc.IRCClient.connectionMade(self) + self.logger.info("IRC connection made") + + def connectionLost(self, reason): + irc.IRCClient.connectionLost(self, reason) + self.logger.info("IRC connection lost: %s", format(reason)) + def signedOn(self): + """Called when bot has successfully signed on to server.""" if self.nickserv_password: self.msg("NickServ", "identify %s" % self.nickserv_password) if self.factory.target.startswith("#"): self.join(self.factory.target) else: self.send_greeting(self.factory.target) - - self.logger.info("Connected to IRC") + self.logger.info("Signed into IRC") def joined(self, target): + """This will get called when the bot joins the channel.""" + self.logger.debug("Sucesssfully joined the IRC channel %s.", self.factory.target) self.send_greeting(target) def send_greeting(self, target): + """IRC channel greeting function""" self.msg(target, "%s is live!" % self.nickname) def privmsg(self, user, target, msg): + """This will get called when the bot receives a message.""" user = user.split('!', 1)[0] self.logger.info("IRC Message <%s>: %s" % (user, msg)) if self.echo_from_channel: @@ -45,18 +55,31 @@ def privmsg(self, user, target, msg): )) def action(self, user, target, msg): + """This will get called when a user performs an action in the channel""" user = user.split('!', 1)[0] self.logger.info("IRC Action: %s %s" % (user, msg)) + def irc_NICK(self, prefix, params): + """Called when an IRC user changes their nickname.""" + old_nick = prefix.split('!')[0] + new_nick = params[0] + self.logger.info("%s is now known as %s" % (old_nick, new_nick)) + + +class StarryPyIrcBotFactory(protocol.ReconnectingClientFactory): + """Factory for IRC bot.""" + + # Parameters used in the auto-reconnect system. Currently hard-coded. Will + # eventually move them to the config file. + maxRetries = 100 + initalDelay = 1.0 -class StarryPyIrcBotFactory(protocol.ClientFactory): def __init__(self, target, logger, nickname, nickserv_password, broadcast_target, colors, echo_from_channel): self.nickname = nickname try: self.nickserv_password = nickserv_password except AttributeError: self.nickserv_password = None - self.target = target self.broadcast_target = broadcast_target self.colors = colors @@ -64,13 +87,20 @@ def __init__(self, target, logger, nickname, nickserv_password, broadcast_target self.irc_clients = {} self.echo_from_channel = echo_from_channel + def startedConnecting(self, connector): + self.logger.debug("Factory attempting to connect...") + def buildProtocol(self, addr): irc_client = StarryPyIrcBot(self.logger, self.nickname, self.nickserv_password, self, self.broadcast_target, self.colors, self.echo_from_channel) + irc_client.factory = self self.irc_clients[irc_client.id] = irc_client + self.resetDelay() return irc_client def clientConnectionLost(self, connector, reason): - connector.connect() + self.logger.error("IRC connection lost, reconnecting") + protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailed(self, connector, reason): - self.logger.error("connection failed: %s" % reason) + self.logger.error("IRC connection failed: %s" % format(reason)) + protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) From 782447c13860d53da3d8d1674f8f1eeb4b5e2e52 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 3 Feb 2015 19:41:23 -0500 Subject: [PATCH 58/81] Fixed some over-verbosity issues (I forgot to turn off). --- plugins/core/player_manager/manager.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index a510ec1..d51d7e2 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -18,14 +18,11 @@ @contextmanager def _autoclosing_session(sm): session = sm() - try: yield session - except: session.rollback() raise - finally: session.close() @@ -162,12 +159,12 @@ class Player(Base): ips = relationship("IPAddress", order_by="IPAddress.id", backref="players") def colored_name(self, colors): - logger.debug("Building colored name.") + logger.vdebug("Building colored name.") color = colors[UserLevels(self.access_level).lower()] - logger.debug("Color is %s", color) + logger.vdebug("Color is %s", color) name = self.name - logger.debug("Name is %s", name) - logger.debug("Returning the following data for colored name. %s:%s:%s", + logger.vdebug("Name is %s", name) + logger.vdebug("Returning the following data for colored name. %s:%s:%s", color, name, colors['default']) return color + name + colors["default"] From 907062af075dd341790705896b57bd88c2d16ded Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 00:12:05 -0500 Subject: [PATCH 59/81] Added brutus_whisper plugin (works out of the box, not modification needed). Updated config.json.defaults. --- config/config.json.default | 11 +- plugins/brutus_whisper/__init__.py | 1 + plugins/brutus_whisper/brutus_whisper.py | 123 +++++++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 plugins/brutus_whisper/__init__.py create mode 100644 plugins/brutus_whisper/brutus_whisper.py diff --git a/config/config.json.default b/config/config.json.default index e77e7e8..fa86e62 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -28,6 +28,9 @@ "bookmarks_plugin": { "auto_activate": true }, + "brutus_whisper": { + "auto_activate": true + }, "chat_logger": { "auto_activate": true }, @@ -165,7 +168,10 @@ "spinningrocket", "stationaryrocket" ], - "protected_planets": [] + "player_planets": { + }, + "protected_planets": [ + ] }, "player_manager": { "auto_activate": true, @@ -186,6 +192,9 @@ "starbound_config_manager": { "auto_activate": true }, + "uptime_plugin": { + "auto_activate": true + }, "user_management_commands": { "auto_activate": true }, diff --git a/plugins/brutus_whisper/__init__.py b/plugins/brutus_whisper/__init__.py new file mode 100644 index 0000000..c03c152 --- /dev/null +++ b/plugins/brutus_whisper/__init__.py @@ -0,0 +1 @@ +from brutus_whisper import BRWhisperPlugin \ No newline at end of file diff --git a/plugins/brutus_whisper/brutus_whisper.py b/plugins/brutus_whisper/brutus_whisper.py new file mode 100644 index 0000000..70a597e --- /dev/null +++ b/plugins/brutus_whisper/brutus_whisper.py @@ -0,0 +1,123 @@ +#=========================================================== +# BRWhisperPlugin +# Author: FZFalzar/Duck of Brutus.SG Starbound (http://steamcommunity.com/groups/BrutusSG) +# Version: v0.1 +# Description: A better whisper plugin with reply and SocialSpy +#=========================================================== + +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from utility_functions import extract_name +from datetime import datetime + +class BRWhisperPlugin(SimpleCommandPlugin): + name = "brutus_whisper" + depends = ['command_dispatcher', 'player_manager'] + commands = ["whisper", "w", "r", "ss"] + auto_activate = True + + def activate(self): + super(BRWhisperPlugin, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + self.reply_history = dict() + self.sspy_enabled_dict = dict() + + @permissions(UserLevels.GUEST) + def whisper(self, data): + """Sends a message to target player.\nSyntax: /whisper (player) (msg)""" + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^orange;<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" + if len(data) == 0: + self.protocol.send_chat_message(self.whisper.__doc__) + return + try: + targetName, message = extract_name(data) + if not message: + self.protocol.send_chat_message("Invalid message!") + self.protocol.send_chat_message(self.whisper.__doc__) + return + self.logger.info("Message to %s from %s: %s" % (targetName, self.protocol.player.name, " ".join(message))) + self.sendWhisper(targetName, " ".join(message)) + except ValueError as e: + self.protocol.send_chat_message(self.whisper.__doc__) + except TypeError as e: + self.protocol.send_chat_message(self.whisper.__doc__) + + def reply(self, data): + """Replies to last player who whispered you.\nSyntax: /r (msg)""" + if len(data) == 0: + self.protocol.send_chat_message(self.reply.__doc__) + return + + #retrieve your own history, using your name as key + try: + target = self.reply_history[self.protocol.player.name] + self.sendWhisper(target, " ".join(data)) + except KeyError as e: + self.protocol.send_chat_message("You have no one to reply to!") + + @permissions(UserLevels.GUEST) + def w(self, data): + """Sends a message to target player.\nSyntax: /whisper (player) (msg)""" + self.whisper(data) + + @permissions(UserLevels.GUEST) + def r(self, data): + """Replies to last player who whispered you.\nSyntax: /r (msg)""" + self.reply(data) + + def sendWhisper(self, target, message): + now = datetime.now() + if self.config.chattimestamps: + timestamp = "<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" + targetPlayer = self.player_manager.get_logged_in_by_name(target) + if targetPlayer is None: + self.protocol.send_chat_message(("Couldn't send a message to %s") % target) + return + else: + #show yourself the message + strMsgTo = "^violet;" + timestamp + "<%s^violet;> %s" % (self.protocol.player.colored_name(self.config.colors), message) + strTo = "%s" % targetPlayer.colored_name(self.config.colors) + self.protocol.send_chat_message(strMsgTo) + + #show target the message + protocol = self.factory.protocols[targetPlayer.protocol] + strMsgFrom = "^violet;" + timestamp + "<%s^violet;> %s" % (self.protocol.player.colored_name(self.config.colors), message) + strFrom = "%s" % self.protocol.player.colored_name(self.config.colors) + protocol.send_chat_message(strMsgFrom) + + #store your last sent history, so the other player can reply + #store your name using your target's name as key, so he can use his name to find you + self.reply_history[target] = self.protocol.player.name + + #send message to people with socialspy on + for key, value in self.sspy_enabled_dict.iteritems(): + sspy_player = self.player_manager.get_logged_in_by_name(key) + if sspy_player is not None: + if sspy_player.access_level >= UserLevels.OWNER and value == True: + protocol = self.factory.protocols[sspy_player.protocol] + protocol.send_chat_message("^red;" + timestamp + "%sSS: ^cyan;<%s ^green;-> %s^cyan;> ^green;%s" % (self.config.colors["admin"], strFrom, strTo, message)) + + @permissions(UserLevels.OWNER) + def ss(self, data): + """Toggles viewing of other players whispers.\nSyntax: /ss""" + try: + if not self.sspy_enabled_dict[self.protocol.player.name]: + self.sspy_enabled_dict[self.protocol.player.name] = True + self.protocol.send_chat_message("SocialSpy has been ^green;enabled^yellow;!") + else: + self.sspy_enabled_dict[self.protocol.player.name] = False + self.protocol.send_chat_message("SocialSpy has been ^red;disabled^yellow;!") + except: + if len(data) != 0 and " ".join(data).lower() in ["on", "true"]: + self.sspy_enabled_dict[self.protocol.player.name] = True + self.protocol.send_chat_message("SocialSpy has been ^green;enabled^yellow;!") + else: + self.sspy_enabled_dict[self.protocol.player.name] = False + self.protocol.send_chat_message(self.ss.__doc__) + self.protocol.send_chat_message("SocialSpy is ^red;disabled^yellow;!") From d2738bc257358eb10c24a3c1f13432bab98302b9 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 00:21:51 -0500 Subject: [PATCH 60/81] Added emotes plugin. Worked out of box, no modifcation needed. --- config/config.json.default | 3 ++ plugins/emotes/__init__.py | 1 + plugins/emotes/emotes.py | 98 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 plugins/emotes/__init__.py create mode 100644 plugins/emotes/emotes.py diff --git a/config/config.json.default b/config/config.json.default index fa86e62..ce79140 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -40,6 +40,9 @@ "command_dispatcher": { "auto_activate": true }, + "emotes_plugin": { + "auto_activate": true + }, "irc": { "auto_activate": false, "bot_nickname": "StarryPyBot", diff --git a/plugins/emotes/__init__.py b/plugins/emotes/__init__.py new file mode 100644 index 0000000..9376eec --- /dev/null +++ b/plugins/emotes/__init__.py @@ -0,0 +1 @@ +from emotes import EmotesPlugin \ No newline at end of file diff --git a/plugins/emotes/emotes.py b/plugins/emotes/emotes.py new file mode 100644 index 0000000..1088756 --- /dev/null +++ b/plugins/emotes/emotes.py @@ -0,0 +1,98 @@ +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from datetime import datetime +from random import randrange, choice + + +class EmotesPlugin(SimpleCommandPlugin): + """ + Very simple plugin that adds /me command to StarryPy. + """ + name = "emotes_plugin" + depends = ["command_dispatcher", "player_manager"] + commands = ["me"] + auto_activate = True + + def activate(self): + super(EmotesPlugin, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + + @permissions(UserLevels.GUEST) + def me(self, data): + """Creates a player emote message.\nSyntax: /me (emote) +Predefined emotes: ^yellow;beckon^green;, ^yellow;bow^green;, ^yellow;cheer^green;, ^yellow;cower^green;, ^yellow;cry^green;, ^yellow;dance^green;, ^yellow;hug^green;, ^yellow;hugs^green;, ^yellow;kiss^green;, ^yellow;kneel^green;, ^yellow;laugh^green;, ^yellow;lol^green;, ^yellow;no^green;, ^yellow;point^green;, ^yellow;ponder^green;, ^yellow;rofl^green;, ^yellow;salute^green;, ^yellow;shrug^green;, ^yellow;sit^green;, ^yellow;sleep^green;, ^yellow;surprised^green;, ^yellow;threaten^green;, ^yellow;wave^green;, ^yellow;yes^green; +Utility emotes: ^yellow;flip^green;, ^yellow;roll^green;""" + now = datetime.now() + if len(data) == 0: + self.protocol.send_chat_message(self.me.__doc__) + return + if self.protocol.player.muted: + self.protocol.send_chat_message( + "You are currently muted and cannot emote. You are limited to commands and admin chat (prefix your lines with %s for admin chat." % (self.config.chat_prefix*2)) + return False + emote = " ".join(data) + spec_prefix = "" #we'll use this for random rolls, to prevent faking + if emote == "beckon": + emote = "beckons you to come over" + elif emote == "bow": + emote = "bows before you" + elif emote == "cheer": + emote = "cheers at you! Yay!" + elif emote == "cower": + emote = "cowers at the sight of your weapons!" + elif emote == "cry": + emote = "bursts out in tears... sob sob" + elif emote == "dance": + emote = "is busting out some moves, some sweet dance moves" + elif emote == "flip": + flipdata = ["HEADS!", "TAILS!"] + spec_prefix = "^cyan;!" #add cyan color ! infront of name or player can /me rolled ^cyan;100 + emote = "flips a coin and its... ^cyan;%s" % choice(flipdata) + elif emote == "hug": + emote = "needs a hug!" + elif emote == "hugs": + emote = "needs a hug! Many MANY hugs!" + elif emote == "kiss": + emote = "blows you a kiss <3" + elif emote == "kneel": + emote = "kneels down before you" + elif emote == "laugh": + emote = "suddenly laughs and just as suddenly stops" + elif emote == "lol": + emote = "laughs out loud -LOL-" + elif emote == "no": + emote = "disagrees" + elif emote == "point": + emote = "points somewhere in the distance" + elif emote == "ponder": + emote = "ponders if this is worth it" + elif emote == "rofl": + emote = "rolls on the floor laughing" + elif emote == "roll": + rollx=str(randrange(1,101)) + spec_prefix = "^cyan;!" #add cyan color ! infront of name or player can /me rolled ^cyan;100 + emote = "rolled ^cyan;%s" % rollx + elif emote == "salute": + emote = "salutes you" + elif emote == "shrug": + emote = "shrugs at you" + elif emote == "sit": + emote = "sits down. Oh, boy..." + elif emote == "sleep": + emote = "falls asleep. Zzz" + elif emote == "surprised": + emote = "is surprised beyond belief" + elif emote == "threaten": + emote = "is threatening you with a butter knife!" + elif emote == "wave": + emote = "waves... Helloooo there!" + elif emote == "yes": + emote = "agrees" + + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^orange;<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" + self.factory.broadcast_planet(timestamp + spec_prefix + "^orange;%s %s" % (self.protocol.player.name, emote), planet=self.protocol.player.planet) + return False From 92f69dc542cefc59ce2159a940d051e978895c29 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 01:46:36 -0500 Subject: [PATCH 61/81] Added AFK plugin. Everything works. --- config/config.json.default | 3 ++ packets/packet_types.py | 7 ++- plugins/afk_plugin/__init__.py | 1 + plugins/afk_plugin/afk_plugin.py | 88 ++++++++++++++++++++++++++++++++ server.py | 3 +- 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 plugins/afk_plugin/__init__.py create mode 100644 plugins/afk_plugin/afk_plugin.py diff --git a/config/config.json.default b/config/config.json.default index ce79140..53d59ac 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -22,6 +22,9 @@ "admin_messenger": { "auto_activate": true }, + "afk_plugin": { + "auto_activate": true + }, "announcer_plugin": { "auto_activate": true }, diff --git a/packets/packet_types.py b/packets/packet_types.py index b72a291..67f7097 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -129,7 +129,8 @@ def _decode(self, obj, context): connect_response = lambda name="connect_response": Struct(name, Flag("success"), VLQ("client_id"), - star_string("reject_reason")) + star_string("reject_reason"), + GreedyRange(star_string("celestial_info"))) # corrected. needs testing chat_received = lambda name="chat_received": Struct(name, @@ -265,6 +266,10 @@ def _decode(self, obj, context): String("entity", lambda ctx: ctx.entity_size), SignedVLQ("entity_id")))) +entity_update = lambda name="entity_update": Struct(name, + UBInt32("entity_id"), + StarByteArray("delta")) + client_context_update = lambda name="client_context": Struct(name, VLQ("length"), Byte("arguments"), diff --git a/plugins/afk_plugin/__init__.py b/plugins/afk_plugin/__init__.py new file mode 100644 index 0000000..07d17b0 --- /dev/null +++ b/plugins/afk_plugin/__init__.py @@ -0,0 +1 @@ +from afk_plugin import AFKCommand \ No newline at end of file diff --git a/plugins/afk_plugin/afk_plugin.py b/plugins/afk_plugin/afk_plugin.py new file mode 100644 index 0000000..eaa3d6a --- /dev/null +++ b/plugins/afk_plugin/afk_plugin.py @@ -0,0 +1,88 @@ +#=========================================================== +# afk_plugin +# Author: FZFalzar of Brutus.SG Starbound +# Version: v0.1 +# Description: Simple AFK command with configurable messages +#=========================================================== +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from datetime import datetime + + +class AFKCommand(SimpleCommandPlugin): + name = "afk_plugin" + depends = ["command_dispatcher", "player_manager"] + commands = ["afk"] + auto_activate = True + afk_list = dict() + + def activate(self): + super(AFKCommand, self).activate() + self.player_manager = self.plugins["player_manager"].player_manager + self.load_config() + + def load_config(self): + try: + self.afk_message = self.config.plugin_config["afk_msg"] + self.afkreturn_message = self.config.plugin_config["afkreturn_msg"] + except Exception as e: + self.logger.info("Error occured! %s" % e) + if self.protocol is not None: + self.protocol.send_chat_message("Reload failed! Please check config.json!") + self.protocol.send_chat_message("Initiating with default values...") + self.afk_message = "^gray;is now AFK." + self.afkreturn_message = "^gray;has returned." + + @permissions(UserLevels.GUEST) + def afk(self, data): + """Marks a user as AFK (Away From Keyboard)\nSyntax: /afk""" + if self.protocol.player.name in self.afk_list: + if self.afk_list[self.protocol.player.name] == True: + self.unset_afk_status(self.protocol.player.name) + else: + self.set_afk_status(self.protocol.player.name) + else: + self.set_afk_status(self.protocol.player.name) + + def set_afk_status(self, name): + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^gray;<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" + if name in self.afk_list: + if self.afk_list[name] == False: + self.factory.broadcast(timestamp + "%s ^gray;%s" % (self.player_manager.get_by_name(name).colored_name(self.config.colors), self.afk_message)) + self.afk_list[name] = True + else: + self.afk_list[name] = True + self.set_afk_status(name) + + def unset_afk_status(self, name): + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^gray;<" + now.strftime("%H:%M") + "> " + else: + timestamp = "" + if name in self.afk_list: + if self.afk_list[name] == True: + self.factory.broadcast(timestamp + "%s ^gray;%s" % (self.player_manager.get_by_name(name).colored_name(self.config.colors), self.afkreturn_message)) + self.afk_list[name] = False + else: + self.afk_list[name] = False + self.unset_afk_status(name) + + #if player disconnects, remove him from list! + def on_client_disconnect(self, player): + self.unset_afk_status(self.protocol.player.name) + self.afk_list.pop(self.protocol.player.name, None) + + #if player does any of these, unmark him from afk! + def on_chat_sent(self, data): + self.unset_afk_status(self.protocol.player.name) + + def on_entity_create(self, data): + self.unset_afk_status(self.protocol.player.name) + + def on_entity_interact(self, data): + self.unset_afk_status(self.protocol.player.name) diff --git a/server.py b/server.py index 146a12c..11a23ee 100644 --- a/server.py +++ b/server.py @@ -151,7 +151,8 @@ def string_received(self, packet): """ if 53 >= packet.id: # DEBUG - print all packet IDs going to client - # logger.info("From Client: %s", packet.id) + #if packet.id not in [14, 44, 46, 53]: + # logger.info("From Client: %s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) From 3f18a7af2c37c68649950753d11ae6b5259136d7 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 01:55:19 -0500 Subject: [PATCH 62/81] Added login_who back in. Works! :) --- config/config.json.default | 3 +++ plugins/loginwho_plugin/__init__.py | 1 + plugins/loginwho_plugin/loginwho_plugin.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 plugins/loginwho_plugin/__init__.py create mode 100644 plugins/loginwho_plugin/loginwho_plugin.py diff --git a/config/config.json.default b/config/config.json.default index 53d59ac..5ca4c0b 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -56,6 +56,9 @@ "port": 6667, "server": "irc.freenode.net" }, + "loginwho_plugin": { + "auto_activate": true + }, "mod_chatter": { "auto_activate": true }, diff --git a/plugins/loginwho_plugin/__init__.py b/plugins/loginwho_plugin/__init__.py new file mode 100644 index 0000000..1065951 --- /dev/null +++ b/plugins/loginwho_plugin/__init__.py @@ -0,0 +1 @@ +from loginwho_plugin import LoginWhoPlugin \ No newline at end of file diff --git a/plugins/loginwho_plugin/loginwho_plugin.py b/plugins/loginwho_plugin/loginwho_plugin.py new file mode 100644 index 0000000..f5a178f --- /dev/null +++ b/plugins/loginwho_plugin/loginwho_plugin.py @@ -0,0 +1,18 @@ +# -*- coding: UTF-8 -*- +from base_plugin import BasePlugin +from plugins.core.player_manager import permissions + +class LoginWhoPlugin(BasePlugin): + """ + Displays a /who upon login + """ + name = "loginwho_plugin" + depends = ["command_dispatcher", "user_management_commands"] + auto_activate = True + + def activate(self): + super(LoginWhoPlugin, self).activate() + self.user_commands = self.plugins['user_management_commands'] + + def after_connect_response(self, data): + self.user_commands.who(data) From 20cb4e6c75384597b49d328836d8ffa88d749e12 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 02:05:44 -0500 Subject: [PATCH 63/81] Readded planet_visitor_announcer. Brought it up to speed with the new warp packets. Eveything else worked. --- config/config.json.default | 3 +++ plugins/planet_visitor_announcer/__init__.py | 1 + .../planet_visitor_announcer.py | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 plugins/planet_visitor_announcer/__init__.py create mode 100644 plugins/planet_visitor_announcer/planet_visitor_announcer.py diff --git a/config/config.json.default b/config/config.json.default index 5ca4c0b..34a5d45 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -182,6 +182,9 @@ "protected_planets": [ ] }, + "planet_visitor_announcer_plugin": { + "auto_activate": true + }, "player_manager": { "auto_activate": true, "name_removal_regexes": [ diff --git a/plugins/planet_visitor_announcer/__init__.py b/plugins/planet_visitor_announcer/__init__.py new file mode 100644 index 0000000..8cdca43 --- /dev/null +++ b/plugins/planet_visitor_announcer/__init__.py @@ -0,0 +1 @@ +from planet_visitor_announcer import PlanetVisitorAnnouncer \ No newline at end of file diff --git a/plugins/planet_visitor_announcer/planet_visitor_announcer.py b/plugins/planet_visitor_announcer/planet_visitor_announcer.py new file mode 100644 index 0000000..b32f616 --- /dev/null +++ b/plugins/planet_visitor_announcer/planet_visitor_announcer.py @@ -0,0 +1,23 @@ +from base_plugin import BasePlugin +from packets import player_warp +from twisted.internet import reactor + +class PlanetVisitorAnnouncer(BasePlugin): + """ + Broadcasts a message whenever a player beams down to a planet. + """ + name = "planet_visitor_announcer_plugin" + auto_activate = True + + def activate(self): + super(PlanetVisitorAnnouncer, self).activate() + + def after_player_warp(self, data): + w = player_warp().parse(data.data) + if w.warp_type == "WARP_TO_ORBITED_WORLD" or w.warp_type == "WARP_TO_HOME_WORLD": + reactor.callLater(1, self.announce_on_planet, self.protocol.player) + + def announce_on_planet(self, who_beamed): + self.factory.broadcast_planet("%s^green; beamed down to the planet" % who_beamed.colored_name(self.config.colors), planet=self.protocol.player.planet) + + From dddbde4cadf383b9044e57b091ff40bfc492fbf2 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 02:22:26 -0500 Subject: [PATCH 64/81] Added fuelgiver back in. Small text tweaks. --- config/config.json.default | 7 ++++- plugins/afk_plugin/afk_plugin.py | 2 +- plugins/fuelgiver/__init__.py | 1 + plugins/fuelgiver/fuelgiver_plugin.py | 37 +++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 plugins/fuelgiver/__init__.py create mode 100644 plugins/fuelgiver/fuelgiver_plugin.py diff --git a/config/config.json.default b/config/config.json.default index 34a5d45..4dabc7d 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -23,7 +23,9 @@ "auto_activate": true }, "afk_plugin": { - "auto_activate": true + "auto_activate": true, + "afk_message": "^gray;is now AFK.", + "afkreturn_message": "^gray;has returned." }, "announcer_plugin": { "auto_activate": true @@ -46,6 +48,9 @@ "emotes_plugin": { "auto_activate": true }, + "fuelgiver_plugin": { + "auto_activate": true + }, "irc": { "auto_activate": false, "bot_nickname": "StarryPyBot", diff --git a/plugins/afk_plugin/afk_plugin.py b/plugins/afk_plugin/afk_plugin.py index eaa3d6a..dfbe61d 100644 --- a/plugins/afk_plugin/afk_plugin.py +++ b/plugins/afk_plugin/afk_plugin.py @@ -26,7 +26,7 @@ def load_config(self): self.afk_message = self.config.plugin_config["afk_msg"] self.afkreturn_message = self.config.plugin_config["afkreturn_msg"] except Exception as e: - self.logger.info("Error occured! %s" % e) + self.logger.error("Error occured! %s", e) if self.protocol is not None: self.protocol.send_chat_message("Reload failed! Please check config.json!") self.protocol.send_chat_message("Initiating with default values...") diff --git a/plugins/fuelgiver/__init__.py b/plugins/fuelgiver/__init__.py new file mode 100644 index 0000000..f122379 --- /dev/null +++ b/plugins/fuelgiver/__init__.py @@ -0,0 +1 @@ +from fuelgiver_plugin import FuelGiver \ No newline at end of file diff --git a/plugins/fuelgiver/fuelgiver_plugin.py b/plugins/fuelgiver/fuelgiver_plugin.py new file mode 100644 index 0000000..2c3ae29 --- /dev/null +++ b/plugins/fuelgiver/fuelgiver_plugin.py @@ -0,0 +1,37 @@ +#Kamilion's Fuel Giver plugin (https://gist.github.com/kamilion/9150547) +from base_plugin import SimpleCommandPlugin +from utility_functions import give_item_to_player +from plugins.core.player_manager import permissions, UserLevels +from time import time + + +class FuelGiver(SimpleCommandPlugin): + """ + Courteously give players fuel once a day (for those who ask for it). + """ + name = "fuelgiver_plugin" + depends = ["command_dispatcher", "player_manager"] + commands = ["fuel"] + auto_activate = True + + def activate(self): + super(FuelGiver, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + + @permissions(UserLevels.GUEST) + def fuel(self, data): + """Gives you enough fuel to fill your ship's tank (once a day).\nSyntax: /fuel""" + try: + my_storage = self.protocol.player.storage + except AttributeError: + self.logger.warning("Tried to give item to non-existent protocol.") + return + if not 'last_given_fuel' in my_storage or float(my_storage['last_given_fuel']) <= float(time()) - 86400: + my_storage['last_given_fuel'] = str(time()) + give_item_to_player(self.protocol, "fillerup", 1) + self.protocol.player.storage = my_storage + self.protocol.send_chat_message("You were given a daily fuel supply! Now go explore ;)") + self.logger.info("Gave fuel to %s.", self.protocol.player.name) + else: + self.protocol.send_chat_message("^red;No... -.- Go mining!") + From 9976a481a07c8a301831e1db2efd3bcf44d32e2a Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 02:30:35 -0500 Subject: [PATCH 65/81] Fixed shutdown command to not throw an error on no input. --- plugins/core/admin_commands_plugin/admin_command_plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 6de1364..0be65b0 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -333,6 +333,9 @@ def passthrough(self, data): @permissions(UserLevels.ADMIN) def shutdown(self, data): """Shutdown the server in n seconds.\nSyntax: /shutdown (seconds) (>0)""" + if len(data) == 0: + self.protocol.send_chat_message(self.shutdown.__doc__) + return try: x = float(data[0]) except ValueError: From 93cb4d83fb4f02f78d6c1dc36269959caa9d3bb2 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 02:51:11 -0500 Subject: [PATCH 66/81] Added stateritems plugin back in. Works. --- config/config.json.default | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 4dabc7d..744ad7d 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -23,9 +23,9 @@ "auto_activate": true }, "afk_plugin": { - "auto_activate": true, "afk_message": "^gray;is now AFK.", - "afkreturn_message": "^gray;has returned." + "afkreturn_message": "^gray;has returned.", + "auto_activate": true }, "announcer_plugin": { "auto_activate": true @@ -49,7 +49,7 @@ "auto_activate": true }, "fuelgiver_plugin": { - "auto_activate": true + "auto_activate": false }, "irc": { "auto_activate": false, @@ -75,7 +75,7 @@ "auto_activate": true }, "new_player_greeter_plugin": { - "auto_activate": true, + "auto_activate": false, "items": [ [ "coalore", @@ -209,6 +209,16 @@ "starbound_config_manager": { "auto_activate": true }, + "starteritems_plugin": { + "auto_activate": false, + "message": "Enjoy these gifts from us!", + "items": [ + [ + "coalore", + 200 + ] + ] + }, "uptime_plugin": { "auto_activate": true }, From 7eb08f2bbcabd3daf8060e3d0790c37624b72937 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 14:06:19 -0500 Subject: [PATCH 67/81] Added claims and web_gui. Teihoo did the testing. :) --- .../a87d5e4bfa6fabc4deea7ddd50fb6e99.json | 1 - config/config.json.default | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) delete mode 100644 config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json diff --git a/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json b/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json deleted file mode 100644 index afec3fd..0000000 --- a/config/bookmarks/a87d5e4bfa6fabc4deea7ddd50fb6e99.json +++ /dev/null @@ -1 +0,0 @@ -[["-30562840:658406569:-263745435:4:0", "forest"], ["-30562840:658406569:-263745435:3:6", "spawn"]] \ No newline at end of file diff --git a/config/config.json.default b/config/config.json.default index 744ad7d..90b2d88 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -1,7 +1,7 @@ { "bind_address": "", "bind_port": 21025, - "chat_prefix": "@", + "chat_prefix": "#", "chattimestamps": true, "colors": { "admin": "^#C443F7;", @@ -23,8 +23,8 @@ "auto_activate": true }, "afk_plugin": { - "afk_message": "^gray;is now AFK.", - "afkreturn_message": "^gray;has returned.", + "afk_msg": "^gray;is now AFK.", + "afkreturn_msg": "^gray;has returned.", "auto_activate": true }, "announcer_plugin": { @@ -39,6 +39,11 @@ "chat_logger": { "auto_activate": true }, + "claims": { + "auto_activate": true, + "max_claims": 5, + "unclaimable_planets": [] + }, "colored_names_plugin": { "auto_activate": true }, @@ -86,7 +91,6 @@ }, "planet_protect": { "auto_activate": true, - "protect_everything": false, "bad_packets": [ "CONNECT_WIRE", "DISCONNECT_ALL_WIRES", @@ -184,6 +188,7 @@ ], "player_planets": { }, + "protect_everything": false, "protected_planets": [ ] }, @@ -227,6 +232,16 @@ }, "warpy_plugin": { "auto_activate": true + }, + "web_gui": { + "auto_activate": true, + "cookie_token": "", + "log_path": "webgui.log", + "log_path_access": "webgui_access.log", + "ownerpassword": "--ADD PASSWORD--", + "port": 8083, + "remember_cookie_token": true, + "restart_script": "" } }, "plugin_path": "plugins", From f2f1c6588a46acb3ca57496574cfb54a45bf7595 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Wed, 4 Feb 2015 14:17:22 -0500 Subject: [PATCH 68/81] Fixed my flub... --- config/bookmarks/bookmark_store | 1 + plugins/claims/__init__.py | 1 + plugins/claims/claims_plugin.py | 239 + plugins/web_gui/__init__.py | 1 + plugins/web_gui/static/adminstop.html | 37 + plugins/web_gui/static/ajax/dashboard.html | 92 + plugins/web_gui/static/ajax/playeredit.html | 93 + plugins/web_gui/static/ajax/playerlist.html | 37 + .../web_gui/static/ajax/playerlistonline.html | 36 + .../web_gui/static/ajax/playerquickmenu.html | 10 + .../web_gui/static/ajax/playersonline.html | 3 + .../web_gui/static/css/bootstrap-sortable.css | 84 + plugins/web_gui/static/css/footer.css | 6 + plugins/web_gui/static/css/style.min.css | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20290 bytes .../fonts/glyphicons-halflings-regular.svg | 229 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41236 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23292 bytes plugins/web_gui/static/images/bg.jpg | Bin 0 -> 471 bytes plugins/web_gui/static/images/bullet.gif | Bin 0 -> 281 bytes plugins/web_gui/static/images/clock.gif | Bin 0 -> 272 bytes plugins/web_gui/static/images/comment.gif | Bin 0 -> 364 bytes plugins/web_gui/static/images/favicon.ico | Bin 0 -> 1150 bytes plugins/web_gui/static/images/gradientbg.jpg | Bin 0 -> 337 bytes plugins/web_gui/static/images/page.gif | Bin 0 -> 393 bytes plugins/web_gui/static/img/chevron-left.png | Bin 0 -> 1368 bytes plugins/web_gui/static/img/chevron-right.png | Bin 0 -> 1442 bytes .../web_gui/static/img/devoops_getdata.gif | Bin 0 -> 2892 bytes .../static/img/devoops_pattern_b10.png | Bin 0 -> 10010 bytes plugins/web_gui/static/img/logo-200.png | Bin 0 -> 2872 bytes plugins/web_gui/static/img/logo.png | Bin 0 -> 1168 bytes plugins/web_gui/static/img/sort-asc.png | Bin 0 -> 1022 bytes plugins/web_gui/static/img/sort-desc.png | Bin 0 -> 1017 bytes plugins/web_gui/static/img/sort.png | Bin 0 -> 1060 bytes plugins/web_gui/static/img/times.png | Bin 0 -> 1428 bytes .../web_gui/static/img/ui-accordion-down.png | Bin 0 -> 1125 bytes .../web_gui/static/img/ui-accordion-right.png | Bin 0 -> 1107 bytes plugins/web_gui/static/img/ui-left.png | Bin 0 -> 1085 bytes plugins/web_gui/static/img/ui-right.png | Bin 0 -> 1104 bytes plugins/web_gui/static/index.html | 126 + .../web_gui/static/js/bootstrap-sortable.js | 129 + plugins/web_gui/static/js/devoops.min.js | 1 + plugins/web_gui/static/js/noty/jquery.noty.js | 488 + .../web_gui/static/js/noty/layouts/bottom.js | 34 + .../static/js/noty/layouts/bottomCenter.js | 41 + .../static/js/noty/layouts/bottomLeft.js | 43 + .../static/js/noty/layouts/bottomRight.js | 43 + .../web_gui/static/js/noty/layouts/center.js | 56 + .../static/js/noty/layouts/centerLeft.js | 61 + .../static/js/noty/layouts/centerRight.js | 61 + .../web_gui/static/js/noty/layouts/inline.js | 31 + plugins/web_gui/static/js/noty/layouts/top.js | 34 + .../static/js/noty/layouts/topCenter.js | 41 + .../web_gui/static/js/noty/layouts/topLeft.js | 43 + .../static/js/noty/layouts/topRight.js | 43 + .../js/noty/packaged/jquery.noty.packaged.js | 1180 ++ .../noty/packaged/jquery.noty.packaged.min.js | 1 + plugins/web_gui/static/js/noty/promise.js | 432 + .../web_gui/static/js/noty/themes/default.js | 156 + plugins/web_gui/static/js/webgui.chat.js | 62 + plugins/web_gui/static/js/webgui.general.js | 109 + plugins/web_gui/static/login.html | 49 + .../plugins/bootstrap/bootstrap-theme.css | 347 + .../plugins/bootstrap/bootstrap-theme.css.map | 1 + .../plugins/bootstrap/bootstrap-theme.min.css | 7 + .../static/plugins/bootstrap/bootstrap.css | 5831 ++++++ .../plugins/bootstrap/bootstrap.css.map | 1 + .../static/plugins/bootstrap/bootstrap.js | 1951 ++ .../plugins/bootstrap/bootstrap.min.css | 7 + .../static/plugins/bootstrap/bootstrap.min.js | 6 + .../bootstrapvalidator/bootstrapValidator.css | 11 + .../bootstrapvalidator/bootstrapValidator.js | 849 + .../bootstrapValidator.min.css | 12 + .../bootstrapValidator.min.js | 11 + .../bootstrapValidator.scss | 15 + plugins/web_gui/static/plugins/d3/LICENSE | 26 + plugins/web_gui/static/plugins/d3/d3.v3.js | 9274 ++++++++++ .../web_gui/static/plugins/d3/d3.v3.min.js | 5 + .../static/plugins/datatables/TableTools.js | 2475 +++ .../plugins/datatables/TableTools_orig.js | 2475 +++ .../plugins/datatables/ZeroClipboard.js | 367 + .../plugins/datatables/copy_csv_xls_pdf.swf | Bin 0 -> 58827 bytes .../datatables/dataTables.bootstrap.js | 148 + .../plugins/datatables/jquery.dataTables.js | 12099 +++++++++++++ .../web_gui/static/plugins/fancybox/blank.gif | Bin 0 -> 43 bytes .../plugins/fancybox/fancybox_loading.gif | Bin 0 -> 6567 bytes .../plugins/fancybox/fancybox_loading@2x.gif | Bin 0 -> 13984 bytes .../plugins/fancybox/fancybox_overlay.png | Bin 0 -> 1003 bytes .../plugins/fancybox/fancybox_sprite.png | Bin 0 -> 1362 bytes .../plugins/fancybox/fancybox_sprite@2x.png | Bin 0 -> 6553 bytes .../fancybox/helpers/fancybox_buttons.png | Bin 0 -> 1080 bytes .../helpers/jquery.fancybox-buttons.css | 97 + .../helpers/jquery.fancybox-buttons.js | 122 + .../fancybox/helpers/jquery.fancybox-media.js | 199 + .../helpers/jquery.fancybox-thumbs.css | 55 + .../helpers/jquery.fancybox-thumbs.js | 162 + .../plugins/fancybox/jquery.fancybox.css | 274 + .../plugins/fancybox/jquery.fancybox.js | 2020 +++ .../plugins/fancybox/jquery.fancybox.pack.js | 46 + .../static/plugins/fineuploader/LICENSE | 676 + .../static/plugins/fineuploader/edit.gif | Bin 0 -> 145 bytes .../fineuploader/fineuploader-4.3.1.css | 205 + .../fineuploader/fineuploader-4.3.1.min.css | 19 + .../fineuploader/iframe.xss.response-4.3.1.js | 7 + .../fineuploader/jquery.fineuploader-4.3.1.js | 9228 ++++++++++ .../jquery.fineuploader-4.3.1.min.js | 20 + .../static/plugins/fineuploader/loading.gif | Bin 0 -> 1688 bytes .../plugins/fineuploader/processing.gif | Bin 0 -> 3209 bytes .../web_gui/static/plugins/flot/LICENSE.txt | 22 + .../static/plugins/flot/jquery.flot.js | 3137 ++++ .../static/plugins/flot/jquery.flot.resize.js | 60 + .../static/plugins/flot/jquery.flot.time.js | 431 + .../plugins/fullcalendar/fullcalendar.css | 601 + .../plugins/fullcalendar/fullcalendar.js | 6934 +++++++ .../plugins/fullcalendar/fullcalendar.min.js | 7 + .../fullcalendar/fullcalendar.print.css | 32 + .../static/plugins/fullcalendar/gcal.js | 100 + .../static/plugins/fullcalendar/lang/all.js | 3 + .../static/plugins/fullcalendar/lang/ar-ma.js | 1 + .../static/plugins/fullcalendar/lang/ar.js | 1 + .../static/plugins/fullcalendar/lang/bg.js | 1 + .../static/plugins/fullcalendar/lang/ca.js | 1 + .../static/plugins/fullcalendar/lang/cs.js | 1 + .../static/plugins/fullcalendar/lang/da.js | 1 + .../static/plugins/fullcalendar/lang/de.js | 1 + .../static/plugins/fullcalendar/lang/el.js | 1 + .../static/plugins/fullcalendar/lang/en-au.js | 1 + .../static/plugins/fullcalendar/lang/en-ca.js | 1 + .../static/plugins/fullcalendar/lang/en-gb.js | 1 + .../static/plugins/fullcalendar/lang/es.js | 1 + .../static/plugins/fullcalendar/lang/fa.js | 1 + .../static/plugins/fullcalendar/lang/fi.js | 1 + .../static/plugins/fullcalendar/lang/fr-ca.js | 1 + .../static/plugins/fullcalendar/lang/fr.js | 1 + .../static/plugins/fullcalendar/lang/hi.js | 1 + .../static/plugins/fullcalendar/lang/hr.js | 1 + .../static/plugins/fullcalendar/lang/hu.js | 1 + .../static/plugins/fullcalendar/lang/it.js | 1 + .../static/plugins/fullcalendar/lang/ja.js | 1 + .../static/plugins/fullcalendar/lang/ko.js | 1 + .../static/plugins/fullcalendar/lang/lt.js | 1 + .../static/plugins/fullcalendar/lang/lv.js | 1 + .../static/plugins/fullcalendar/lang/nl.js | 1 + .../static/plugins/fullcalendar/lang/pl.js | 1 + .../static/plugins/fullcalendar/lang/pt-br.js | 1 + .../static/plugins/fullcalendar/lang/pt.js | 1 + .../static/plugins/fullcalendar/lang/ro.js | 1 + .../static/plugins/fullcalendar/lang/ru.js | 1 + .../static/plugins/fullcalendar/lang/sk.js | 1 + .../static/plugins/fullcalendar/lang/sl.js | 1 + .../static/plugins/fullcalendar/lang/sv.js | 1 + .../static/plugins/fullcalendar/lang/th.js | 1 + .../static/plugins/fullcalendar/lang/tr.js | 1 + .../static/plugins/fullcalendar/lang/uk.js | 1 + .../static/plugins/fullcalendar/lang/zh-cn.js | 1 + .../static/plugins/fullcalendar/lang/zh-tw.js | 1 + .../static/plugins/jQuery-Knob/LICENSE | 20 + .../static/plugins/jQuery-Knob/jquery.knob.js | 766 + .../jquery-ui-timepicker-addon/LICENSE-MIT | 22 + .../i18n/jquery-ui-timepicker-af.js | 21 + .../i18n/jquery-ui-timepicker-am.js | 21 + .../i18n/jquery-ui-timepicker-bg.js | 21 + .../i18n/jquery-ui-timepicker-ca.js | 21 + .../i18n/jquery-ui-timepicker-cs.js | 21 + .../i18n/jquery-ui-timepicker-da.js | 21 + .../i18n/jquery-ui-timepicker-de.js | 21 + .../i18n/jquery-ui-timepicker-el.js | 21 + .../i18n/jquery-ui-timepicker-es.js | 21 + .../i18n/jquery-ui-timepicker-et.js | 21 + .../i18n/jquery-ui-timepicker-eu.js | 21 + .../i18n/jquery-ui-timepicker-fi.js | 21 + .../i18n/jquery-ui-timepicker-fr.js | 21 + .../i18n/jquery-ui-timepicker-gl.js | 21 + .../i18n/jquery-ui-timepicker-he.js | 21 + .../i18n/jquery-ui-timepicker-hr.js | 21 + .../i18n/jquery-ui-timepicker-hu.js | 21 + .../i18n/jquery-ui-timepicker-id.js | 21 + .../i18n/jquery-ui-timepicker-it.js | 21 + .../i18n/jquery-ui-timepicker-ja.js | 21 + .../i18n/jquery-ui-timepicker-ko.js | 21 + .../i18n/jquery-ui-timepicker-lt.js | 21 + .../i18n/jquery-ui-timepicker-nl.js | 21 + .../i18n/jquery-ui-timepicker-no.js | 21 + .../i18n/jquery-ui-timepicker-pl.js | 21 + .../i18n/jquery-ui-timepicker-pt-BR.js | 21 + .../i18n/jquery-ui-timepicker-pt.js | 21 + .../i18n/jquery-ui-timepicker-ro.js | 21 + .../i18n/jquery-ui-timepicker-ru.js | 21 + .../i18n/jquery-ui-timepicker-sk.js | 21 + .../i18n/jquery-ui-timepicker-sr-RS.js | 21 + .../i18n/jquery-ui-timepicker-sr-YU.js | 21 + .../i18n/jquery-ui-timepicker-sv.js | 21 + .../i18n/jquery-ui-timepicker-th.js | 18 + .../i18n/jquery-ui-timepicker-tr.js | 21 + .../i18n/jquery-ui-timepicker-uk.js | 21 + .../i18n/jquery-ui-timepicker-vi.js | 21 + .../i18n/jquery-ui-timepicker-zh-CN.js | 21 + .../i18n/jquery-ui-timepicker-zh-TW.js | 21 + .../jquery-ui-timepicker-addon/index.html | 971 + .../jquery-ui-sliderAccess.js | 91 + .../jquery-ui-timepicker-addon.css | 11 + .../jquery-ui-timepicker-addon.js | 2145 +++ .../jquery-ui-timepicker-addon.min.css | 5 + .../jquery-ui-timepicker-addon.min.js | 5 + .../jquery-ui/i18n/jquery-ui-i18n.min.js | 6 + .../i18n/jquery.ui.datepicker-af.min.js | 4 + .../i18n/jquery.ui.datepicker-ar-DZ.min.js | 4 + .../i18n/jquery.ui.datepicker-ar.min.js | 4 + .../i18n/jquery.ui.datepicker-az.min.js | 4 + .../i18n/jquery.ui.datepicker-be.min.js | 4 + .../i18n/jquery.ui.datepicker-bg.min.js | 4 + .../i18n/jquery.ui.datepicker-bs.min.js | 4 + .../i18n/jquery.ui.datepicker-ca.min.js | 4 + .../i18n/jquery.ui.datepicker-cs.min.js | 4 + .../i18n/jquery.ui.datepicker-cy-GB.min.js | 4 + .../i18n/jquery.ui.datepicker-da.min.js | 4 + .../i18n/jquery.ui.datepicker-de.min.js | 4 + .../i18n/jquery.ui.datepicker-el.min.js | 4 + .../i18n/jquery.ui.datepicker-en-AU.min.js | 4 + .../i18n/jquery.ui.datepicker-en-GB.min.js | 4 + .../i18n/jquery.ui.datepicker-en-NZ.min.js | 4 + .../i18n/jquery.ui.datepicker-eo.min.js | 4 + .../i18n/jquery.ui.datepicker-es.min.js | 4 + .../i18n/jquery.ui.datepicker-et.min.js | 4 + .../i18n/jquery.ui.datepicker-eu.min.js | 4 + .../i18n/jquery.ui.datepicker-fa.min.js | 4 + .../i18n/jquery.ui.datepicker-fi.min.js | 4 + .../i18n/jquery.ui.datepicker-fo.min.js | 4 + .../i18n/jquery.ui.datepicker-fr-CA.min.js | 4 + .../i18n/jquery.ui.datepicker-fr-CH.min.js | 4 + .../i18n/jquery.ui.datepicker-fr.min.js | 4 + .../i18n/jquery.ui.datepicker-gl.min.js | 4 + .../i18n/jquery.ui.datepicker-he.min.js | 4 + .../i18n/jquery.ui.datepicker-hi.min.js | 4 + .../i18n/jquery.ui.datepicker-hr.min.js | 4 + .../i18n/jquery.ui.datepicker-hu.min.js | 4 + .../i18n/jquery.ui.datepicker-hy.min.js | 4 + .../i18n/jquery.ui.datepicker-id.min.js | 4 + .../i18n/jquery.ui.datepicker-is.min.js | 4 + .../i18n/jquery.ui.datepicker-it.min.js | 4 + .../i18n/jquery.ui.datepicker-ja.min.js | 4 + .../i18n/jquery.ui.datepicker-ka.min.js | 4 + .../i18n/jquery.ui.datepicker-kk.min.js | 4 + .../i18n/jquery.ui.datepicker-km.min.js | 4 + .../i18n/jquery.ui.datepicker-ko.min.js | 4 + .../i18n/jquery.ui.datepicker-ky.min.js | 4 + .../i18n/jquery.ui.datepicker-lb.min.js | 4 + .../i18n/jquery.ui.datepicker-lt.min.js | 4 + .../i18n/jquery.ui.datepicker-lv.min.js | 4 + .../i18n/jquery.ui.datepicker-mk.min.js | 4 + .../i18n/jquery.ui.datepicker-ml.min.js | 4 + .../i18n/jquery.ui.datepicker-ms.min.js | 4 + .../i18n/jquery.ui.datepicker-nb.min.js | 4 + .../i18n/jquery.ui.datepicker-nl-BE.min.js | 4 + .../i18n/jquery.ui.datepicker-nl.min.js | 4 + .../i18n/jquery.ui.datepicker-nn.min.js | 4 + .../i18n/jquery.ui.datepicker-no.min.js | 4 + .../i18n/jquery.ui.datepicker-pl.min.js | 4 + .../i18n/jquery.ui.datepicker-pt-BR.min.js | 4 + .../i18n/jquery.ui.datepicker-pt.min.js | 4 + .../i18n/jquery.ui.datepicker-rm.min.js | 4 + .../i18n/jquery.ui.datepicker-ro.min.js | 4 + .../i18n/jquery.ui.datepicker-ru.min.js | 4 + .../i18n/jquery.ui.datepicker-sk.min.js | 4 + .../i18n/jquery.ui.datepicker-sl.min.js | 4 + .../i18n/jquery.ui.datepicker-sq.min.js | 4 + .../i18n/jquery.ui.datepicker-sr-SR.min.js | 4 + .../i18n/jquery.ui.datepicker-sr.min.js | 4 + .../i18n/jquery.ui.datepicker-sv.min.js | 4 + .../i18n/jquery.ui.datepicker-ta.min.js | 4 + .../i18n/jquery.ui.datepicker-th.min.js | 4 + .../i18n/jquery.ui.datepicker-tj.min.js | 4 + .../i18n/jquery.ui.datepicker-tr.min.js | 4 + .../i18n/jquery.ui.datepicker-uk.min.js | 4 + .../i18n/jquery.ui.datepicker-vi.min.js | 4 + .../i18n/jquery.ui.datepicker-zh-CN.min.js | 4 + .../i18n/jquery.ui.datepicker-zh-HK.min.js | 4 + .../i18n/jquery.ui.datepicker-zh-TW.min.js | 4 + .../jquery-ui/images/animated-overlay.gif | Bin 0 -> 1738 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../static/plugins/jquery-ui/jquery-ui.css | 1189 ++ .../static/plugins/jquery-ui/jquery-ui.js | 15040 ++++++++++++++++ .../plugins/jquery-ui/jquery-ui.min.css | 4 + .../static/plugins/jquery-ui/jquery-ui.min.js | 12 + .../static/plugins/jquery/jquery-2.1.0.min.js | 4 + .../plugins/justified-gallery/README.md | 14 + .../jquery.justifiedgallery.css | 73 + .../jquery.justifiedgallery.js | 258 + .../jquery.justifiedgallery.min.css | 17 + .../jquery.justifiedgallery.min.js | 24 + .../plugins/justified-gallery/loading.gif | Bin 0 -> 9427 bytes plugins/web_gui/static/plugins/moment/LICENSE | 22 + .../web_gui/static/plugins/moment/langs.js | 5841 ++++++ .../static/plugins/moment/langs.min.js | 3 + .../plugins/moment/moment-with-langs.js | 7768 ++++++++ .../plugins/moment/moment-with-langs.min.js | 9 + .../static/plugins/moment/moment.min.js | 6 + .../web_gui/static/plugins/morris/morris.js | 1767 ++ .../static/plugins/morris/morris.min.js | 1 + .../static/plugins/raphael/raphael-min.js | 11 + .../web_gui/static/plugins/raphael/raphael.js | 8111 +++++++++ .../web_gui/static/plugins/select2/LICENSE | 18 + .../web_gui/static/plugins/select2/README.md | 83 + .../web_gui/static/plugins/select2/release.sh | 73 + .../plugins/select2/select2-bootstrap.css | 87 + .../plugins/select2/select2-spinner.gif | Bin 0 -> 1849 bytes .../static/plugins/select2/select2.css | 615 + .../web_gui/static/plugins/select2/select2.js | 3255 ++++ .../static/plugins/select2/select2.min.js | 22 + .../static/plugins/select2/select2.png | Bin 0 -> 613 bytes .../plugins/select2/select2_locale_ar.js | 17 + .../plugins/select2/select2_locale_bg.js | 17 + .../plugins/select2/select2_locale_ca.js | 17 + .../plugins/select2/select2_locale_cs.js | 49 + .../plugins/select2/select2_locale_da.js | 17 + .../plugins/select2/select2_locale_de.js | 15 + .../plugins/select2/select2_locale_el.js | 17 + .../select2/select2_locale_en.js.template | 17 + .../plugins/select2/select2_locale_es.js | 15 + .../plugins/select2/select2_locale_et.js | 17 + .../plugins/select2/select2_locale_eu.js | 43 + .../plugins/select2/select2_locale_fa.js | 17 + .../plugins/select2/select2_locale_fi.js | 28 + .../plugins/select2/select2_locale_fr.js | 15 + .../plugins/select2/select2_locale_gl.js | 43 + .../plugins/select2/select2_locale_he.js | 17 + .../plugins/select2/select2_locale_hr.js | 42 + .../plugins/select2/select2_locale_hu.js | 15 + .../plugins/select2/select2_locale_id.js | 17 + .../plugins/select2/select2_locale_is.js | 16 + .../plugins/select2/select2_locale_it.js | 15 + .../plugins/select2/select2_locale_ja.js | 15 + .../plugins/select2/select2_locale_ko.js | 17 + .../plugins/select2/select2_locale_lt.js | 29 + .../plugins/select2/select2_locale_lv.js | 16 + .../plugins/select2/select2_locale_mk.js | 17 + .../plugins/select2/select2_locale_ms.js | 17 + .../plugins/select2/select2_locale_nl.js | 15 + .../plugins/select2/select2_locale_no.js | 18 + .../plugins/select2/select2_locale_pl.js | 37 + .../plugins/select2/select2_locale_pt-BR.js | 15 + .../plugins/select2/select2_locale_pt-PT.js | 15 + .../plugins/select2/select2_locale_ro.js | 15 + .../plugins/select2/select2_locale_ru.js | 15 + .../plugins/select2/select2_locale_sk.js | 48 + .../plugins/select2/select2_locale_sv.js | 17 + .../plugins/select2/select2_locale_th.js | 17 + .../plugins/select2/select2_locale_tr.js | 17 + .../plugins/select2/select2_locale_ua.js | 17 + .../plugins/select2/select2_locale_vi.js | 18 + .../plugins/select2/select2_locale_zh-CN.js | 14 + .../plugins/select2/select2_locale_zh-TW.js | 14 + .../static/plugins/select2/select2x2.png | Bin 0 -> 845 bytes .../plugins/sparkline/jquery.sparkline.js | 3054 ++++ .../plugins/sparkline/jquery.sparkline.min.js | 5 + .../plugins/tinymce/jquery.tinymce.min.js | 1 + .../static/plugins/tinymce/langs/readme.md | 3 + .../static/plugins/tinymce/license.txt | 504 + .../tinymce/plugins/advlist/plugin.min.js | 1 + .../tinymce/plugins/anchor/plugin.min.js | 1 + .../tinymce/plugins/autolink/plugin.min.js | 1 + .../tinymce/plugins/autoresize/plugin.min.js | 1 + .../tinymce/plugins/autosave/plugin.min.js | 1 + .../tinymce/plugins/bbcode/plugin.min.js | 1 + .../tinymce/plugins/charmap/plugin.min.js | 1 + .../tinymce/plugins/code/plugin.min.js | 1 + .../tinymce/plugins/contextmenu/plugin.min.js | 1 + .../plugins/directionality/plugin.min.js | 1 + .../plugins/emoticons/img/smiley-cool.gif | Bin 0 -> 354 bytes .../plugins/emoticons/img/smiley-cry.gif | Bin 0 -> 329 bytes .../emoticons/img/smiley-embarassed.gif | Bin 0 -> 331 bytes .../emoticons/img/smiley-foot-in-mouth.gif | Bin 0 -> 342 bytes .../plugins/emoticons/img/smiley-frown.gif | Bin 0 -> 340 bytes .../plugins/emoticons/img/smiley-innocent.gif | Bin 0 -> 336 bytes .../plugins/emoticons/img/smiley-kiss.gif | Bin 0 -> 338 bytes .../plugins/emoticons/img/smiley-laughing.gif | Bin 0 -> 343 bytes .../emoticons/img/smiley-money-mouth.gif | Bin 0 -> 321 bytes .../plugins/emoticons/img/smiley-sealed.gif | Bin 0 -> 323 bytes .../plugins/emoticons/img/smiley-smile.gif | Bin 0 -> 344 bytes .../emoticons/img/smiley-surprised.gif | Bin 0 -> 338 bytes .../emoticons/img/smiley-tongue-out.gif | Bin 0 -> 328 bytes .../emoticons/img/smiley-undecided.gif | Bin 0 -> 337 bytes .../plugins/emoticons/img/smiley-wink.gif | Bin 0 -> 350 bytes .../plugins/emoticons/img/smiley-yell.gif | Bin 0 -> 336 bytes .../tinymce/plugins/emoticons/plugin.min.js | 1 + .../tinymce/plugins/example/plugin.min.js | 1 + .../plugins/example_dependency/plugin.min.js | 1 + .../tinymce/plugins/fullpage/plugin.min.js | 1 + .../tinymce/plugins/fullscreen/plugin.min.js | 1 + .../plugins/tinymce/plugins/hr/plugin.min.js | 1 + .../tinymce/plugins/image/plugin.min.js | 1 + .../tinymce/plugins/importcss/plugin.min.js | 1 + .../plugins/insertdatetime/plugin.min.js | 1 + .../tinymce/plugins/layer/plugin.min.js | 1 + .../plugins/legacyoutput/plugin.min.js | 1 + .../tinymce/plugins/link/plugin.min.js | 1 + .../tinymce/plugins/lists/plugin.min.js | 1 + .../tinymce/plugins/media/moxieplayer.swf | Bin 0 -> 20017 bytes .../tinymce/plugins/media/plugin.min.js | 1 + .../tinymce/plugins/nonbreaking/plugin.min.js | 1 + .../tinymce/plugins/noneditable/plugin.min.js | 1 + .../tinymce/plugins/pagebreak/plugin.min.js | 1 + .../tinymce/plugins/paste/plugin.min.js | 1 + .../tinymce/plugins/preview/plugin.min.js | 1 + .../tinymce/plugins/print/plugin.min.js | 1 + .../tinymce/plugins/save/plugin.min.js | 1 + .../plugins/searchreplace/plugin.min.js | 1 + .../plugins/spellchecker/plugin.min.js | 1 + .../tinymce/plugins/tabfocus/plugin.min.js | 1 + .../tinymce/plugins/table/plugin.min.js | 1 + .../tinymce/plugins/template/plugin.min.js | 1 + .../tinymce/plugins/textcolor/plugin.min.js | 1 + .../plugins/visualblocks/css/visualblocks.css | 128 + .../plugins/visualblocks/plugin.min.js | 1 + .../tinymce/plugins/visualchars/plugin.min.js | 1 + .../tinymce/plugins/wordcount/plugin.min.js | 1 + .../skins/lightgray/content.inline.min.css | 1 + .../tinymce/skins/lightgray/content.min.css | 1 + .../tinymce/skins/lightgray/fonts/readme.md | 1 + .../skins/lightgray/fonts/tinymce-small.eot | Bin 0 -> 8348 bytes .../skins/lightgray/fonts/tinymce-small.svg | 175 + .../skins/lightgray/fonts/tinymce-small.ttf | Bin 0 -> 8164 bytes .../skins/lightgray/fonts/tinymce-small.woff | Bin 0 -> 8340 bytes .../tinymce/skins/lightgray/fonts/tinymce.eot | Bin 0 -> 8276 bytes .../tinymce/skins/lightgray/fonts/tinymce.svg | 153 + .../tinymce/skins/lightgray/fonts/tinymce.ttf | Bin 0 -> 8112 bytes .../skins/lightgray/fonts/tinymce.woff | Bin 0 -> 8408 bytes .../tinymce/skins/lightgray/img/anchor.gif | Bin 0 -> 53 bytes .../tinymce/skins/lightgray/img/loader.gif | Bin 0 -> 2608 bytes .../tinymce/skins/lightgray/img/object.gif | Bin 0 -> 152 bytes .../tinymce/skins/lightgray/img/trans.gif | Bin 0 -> 43 bytes .../tinymce/skins/lightgray/skin.ie7.min.css | 1 + .../tinymce/skins/lightgray/skin.min.css | 1 + .../tinymce/themes/modern/theme.min.js | 1 + .../static/plugins/tinymce/tinymce.min.js | 9 + .../web_gui/static/plugins/xcharts/LICENSE | 7 + .../web_gui/static/plugins/xcharts/README.md | 17 + .../static/plugins/xcharts/xcharts.css | 283 + .../web_gui/static/plugins/xcharts/xcharts.js | 1158 ++ .../static/plugins/xcharts/xcharts.min.css | 1 + .../static/plugins/xcharts/xcharts.min.js | 5 + plugins/web_gui/static/restart.html | 37 + plugins/web_gui/web_gui.py | 325 + plugins/web_gui/web_gui_plugin.py | 88 + 456 files changed, 120892 insertions(+) create mode 100644 config/bookmarks/bookmark_store create mode 100644 plugins/claims/__init__.py create mode 100644 plugins/claims/claims_plugin.py create mode 100644 plugins/web_gui/__init__.py create mode 100644 plugins/web_gui/static/adminstop.html create mode 100644 plugins/web_gui/static/ajax/dashboard.html create mode 100644 plugins/web_gui/static/ajax/playeredit.html create mode 100644 plugins/web_gui/static/ajax/playerlist.html create mode 100644 plugins/web_gui/static/ajax/playerlistonline.html create mode 100644 plugins/web_gui/static/ajax/playerquickmenu.html create mode 100644 plugins/web_gui/static/ajax/playersonline.html create mode 100644 plugins/web_gui/static/css/bootstrap-sortable.css create mode 100644 plugins/web_gui/static/css/footer.css create mode 100644 plugins/web_gui/static/css/style.min.css create mode 100644 plugins/web_gui/static/fonts/glyphicons-halflings-regular.eot create mode 100644 plugins/web_gui/static/fonts/glyphicons-halflings-regular.svg create mode 100644 plugins/web_gui/static/fonts/glyphicons-halflings-regular.ttf create mode 100644 plugins/web_gui/static/fonts/glyphicons-halflings-regular.woff create mode 100644 plugins/web_gui/static/images/bg.jpg create mode 100644 plugins/web_gui/static/images/bullet.gif create mode 100644 plugins/web_gui/static/images/clock.gif create mode 100644 plugins/web_gui/static/images/comment.gif create mode 100644 plugins/web_gui/static/images/favicon.ico create mode 100644 plugins/web_gui/static/images/gradientbg.jpg create mode 100644 plugins/web_gui/static/images/page.gif create mode 100644 plugins/web_gui/static/img/chevron-left.png create mode 100644 plugins/web_gui/static/img/chevron-right.png create mode 100644 plugins/web_gui/static/img/devoops_getdata.gif create mode 100644 plugins/web_gui/static/img/devoops_pattern_b10.png create mode 100644 plugins/web_gui/static/img/logo-200.png create mode 100644 plugins/web_gui/static/img/logo.png create mode 100644 plugins/web_gui/static/img/sort-asc.png create mode 100644 plugins/web_gui/static/img/sort-desc.png create mode 100644 plugins/web_gui/static/img/sort.png create mode 100644 plugins/web_gui/static/img/times.png create mode 100644 plugins/web_gui/static/img/ui-accordion-down.png create mode 100644 plugins/web_gui/static/img/ui-accordion-right.png create mode 100644 plugins/web_gui/static/img/ui-left.png create mode 100644 plugins/web_gui/static/img/ui-right.png create mode 100644 plugins/web_gui/static/index.html create mode 100644 plugins/web_gui/static/js/bootstrap-sortable.js create mode 100644 plugins/web_gui/static/js/devoops.min.js create mode 100644 plugins/web_gui/static/js/noty/jquery.noty.js create mode 100644 plugins/web_gui/static/js/noty/layouts/bottom.js create mode 100644 plugins/web_gui/static/js/noty/layouts/bottomCenter.js create mode 100644 plugins/web_gui/static/js/noty/layouts/bottomLeft.js create mode 100644 plugins/web_gui/static/js/noty/layouts/bottomRight.js create mode 100644 plugins/web_gui/static/js/noty/layouts/center.js create mode 100644 plugins/web_gui/static/js/noty/layouts/centerLeft.js create mode 100644 plugins/web_gui/static/js/noty/layouts/centerRight.js create mode 100644 plugins/web_gui/static/js/noty/layouts/inline.js create mode 100644 plugins/web_gui/static/js/noty/layouts/top.js create mode 100644 plugins/web_gui/static/js/noty/layouts/topCenter.js create mode 100644 plugins/web_gui/static/js/noty/layouts/topLeft.js create mode 100644 plugins/web_gui/static/js/noty/layouts/topRight.js create mode 100644 plugins/web_gui/static/js/noty/packaged/jquery.noty.packaged.js create mode 100644 plugins/web_gui/static/js/noty/packaged/jquery.noty.packaged.min.js create mode 100644 plugins/web_gui/static/js/noty/promise.js create mode 100644 plugins/web_gui/static/js/noty/themes/default.js create mode 100644 plugins/web_gui/static/js/webgui.chat.js create mode 100644 plugins/web_gui/static/js/webgui.general.js create mode 100644 plugins/web_gui/static/login.html create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap-theme.css create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap-theme.css.map create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap-theme.min.css create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap.css create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap.css.map create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap.js create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap.min.css create mode 100644 plugins/web_gui/static/plugins/bootstrap/bootstrap.min.js create mode 100644 plugins/web_gui/static/plugins/bootstrapvalidator/bootstrapValidator.css create mode 100644 plugins/web_gui/static/plugins/bootstrapvalidator/bootstrapValidator.js create mode 100644 plugins/web_gui/static/plugins/bootstrapvalidator/bootstrapValidator.min.css create mode 100644 plugins/web_gui/static/plugins/bootstrapvalidator/bootstrapValidator.min.js create mode 100644 plugins/web_gui/static/plugins/bootstrapvalidator/bootstrapValidator.scss create mode 100644 plugins/web_gui/static/plugins/d3/LICENSE create mode 100644 plugins/web_gui/static/plugins/d3/d3.v3.js create mode 100644 plugins/web_gui/static/plugins/d3/d3.v3.min.js create mode 100644 plugins/web_gui/static/plugins/datatables/TableTools.js create mode 100644 plugins/web_gui/static/plugins/datatables/TableTools_orig.js create mode 100644 plugins/web_gui/static/plugins/datatables/ZeroClipboard.js create mode 100644 plugins/web_gui/static/plugins/datatables/copy_csv_xls_pdf.swf create mode 100644 plugins/web_gui/static/plugins/datatables/dataTables.bootstrap.js create mode 100644 plugins/web_gui/static/plugins/datatables/jquery.dataTables.js create mode 100644 plugins/web_gui/static/plugins/fancybox/blank.gif create mode 100644 plugins/web_gui/static/plugins/fancybox/fancybox_loading.gif create mode 100644 plugins/web_gui/static/plugins/fancybox/fancybox_loading@2x.gif create mode 100644 plugins/web_gui/static/plugins/fancybox/fancybox_overlay.png create mode 100644 plugins/web_gui/static/plugins/fancybox/fancybox_sprite.png create mode 100644 plugins/web_gui/static/plugins/fancybox/fancybox_sprite@2x.png create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/fancybox_buttons.png create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/jquery.fancybox-buttons.css create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/jquery.fancybox-buttons.js create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/jquery.fancybox-media.js create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/jquery.fancybox-thumbs.css create mode 100644 plugins/web_gui/static/plugins/fancybox/helpers/jquery.fancybox-thumbs.js create mode 100644 plugins/web_gui/static/plugins/fancybox/jquery.fancybox.css create mode 100644 plugins/web_gui/static/plugins/fancybox/jquery.fancybox.js create mode 100644 plugins/web_gui/static/plugins/fancybox/jquery.fancybox.pack.js create mode 100644 plugins/web_gui/static/plugins/fineuploader/LICENSE create mode 100644 plugins/web_gui/static/plugins/fineuploader/edit.gif create mode 100644 plugins/web_gui/static/plugins/fineuploader/fineuploader-4.3.1.css create mode 100644 plugins/web_gui/static/plugins/fineuploader/fineuploader-4.3.1.min.css create mode 100644 plugins/web_gui/static/plugins/fineuploader/iframe.xss.response-4.3.1.js create mode 100644 plugins/web_gui/static/plugins/fineuploader/jquery.fineuploader-4.3.1.js create mode 100644 plugins/web_gui/static/plugins/fineuploader/jquery.fineuploader-4.3.1.min.js create mode 100644 plugins/web_gui/static/plugins/fineuploader/loading.gif create mode 100644 plugins/web_gui/static/plugins/fineuploader/processing.gif create mode 100644 plugins/web_gui/static/plugins/flot/LICENSE.txt create mode 100644 plugins/web_gui/static/plugins/flot/jquery.flot.js create mode 100644 plugins/web_gui/static/plugins/flot/jquery.flot.resize.js create mode 100644 plugins/web_gui/static/plugins/flot/jquery.flot.time.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/fullcalendar.css create mode 100644 plugins/web_gui/static/plugins/fullcalendar/fullcalendar.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/fullcalendar.min.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/fullcalendar.print.css create mode 100644 plugins/web_gui/static/plugins/fullcalendar/gcal.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/all.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ar-ma.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ar.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/bg.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ca.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/cs.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/da.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/de.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/el.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/en-au.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/en-ca.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/en-gb.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/es.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/fa.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/fi.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/fr-ca.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/fr.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/hi.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/hr.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/hu.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/it.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ja.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ko.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/lt.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/lv.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/nl.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/pl.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/pt-br.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/pt.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ro.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/ru.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/sk.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/sl.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/sv.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/th.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/tr.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/uk.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/zh-cn.js create mode 100644 plugins/web_gui/static/plugins/fullcalendar/lang/zh-tw.js create mode 100644 plugins/web_gui/static/plugins/jQuery-Knob/LICENSE create mode 100644 plugins/web_gui/static/plugins/jQuery-Knob/jquery.knob.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/LICENSE-MIT create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-af.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-am.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-bg.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-ca.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-cs.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-da.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-de.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-el.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-es.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-et.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-eu.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-fi.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-fr.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-gl.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-he.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-hr.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-hu.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-id.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-it.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-ja.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-ko.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-lt.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-nl.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-no.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-pl.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-pt-BR.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-pt.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-ro.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-ru.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-sk.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-sr-RS.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-sr-YU.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-sv.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-th.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-tr.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-uk.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-vi.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-zh-CN.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/i18n/jquery-ui-timepicker-zh-TW.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/index.html create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/jquery-ui-sliderAccess.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.css create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.min.css create mode 100644 plugins/web_gui/static/plugins/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery-ui-i18n.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-af.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ar-DZ.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ar.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-az.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-be.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-bg.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-bs.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ca.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-cs.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-cy-GB.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-da.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-de.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-el.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-en-AU.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-en-GB.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-en-NZ.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-eo.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-es.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-et.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-eu.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fa.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fi.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fo.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fr-CA.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fr-CH.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-fr.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-gl.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-he.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-hi.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-hr.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-hu.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-hy.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-id.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-is.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-it.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ja.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ka.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-kk.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-km.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ko.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ky.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-lb.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-lt.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-lv.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-mk.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ml.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ms.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-nb.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-nl-BE.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-nl.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-nn.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-no.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-pl.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-pt-BR.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-pt.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-rm.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ro.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ru.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sk.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sl.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sq.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sr-SR.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sr.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-sv.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-ta.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-th.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-tj.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-tr.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-uk.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-vi.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-zh-CN.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-zh-HK.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/i18n/jquery.ui.datepicker-zh-TW.min.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/animated-overlay.gif create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-icons_222222_256x240.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-icons_2e83ff_256x240.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-icons_454545_256x240.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-icons_888888_256x240.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/images/ui-icons_cd0a0a_256x240.png create mode 100644 plugins/web_gui/static/plugins/jquery-ui/jquery-ui.css create mode 100644 plugins/web_gui/static/plugins/jquery-ui/jquery-ui.js create mode 100644 plugins/web_gui/static/plugins/jquery-ui/jquery-ui.min.css create mode 100644 plugins/web_gui/static/plugins/jquery-ui/jquery-ui.min.js create mode 100644 plugins/web_gui/static/plugins/jquery/jquery-2.1.0.min.js create mode 100644 plugins/web_gui/static/plugins/justified-gallery/README.md create mode 100644 plugins/web_gui/static/plugins/justified-gallery/jquery.justifiedgallery.css create mode 100644 plugins/web_gui/static/plugins/justified-gallery/jquery.justifiedgallery.js create mode 100644 plugins/web_gui/static/plugins/justified-gallery/jquery.justifiedgallery.min.css create mode 100644 plugins/web_gui/static/plugins/justified-gallery/jquery.justifiedgallery.min.js create mode 100644 plugins/web_gui/static/plugins/justified-gallery/loading.gif create mode 100644 plugins/web_gui/static/plugins/moment/LICENSE create mode 100644 plugins/web_gui/static/plugins/moment/langs.js create mode 100644 plugins/web_gui/static/plugins/moment/langs.min.js create mode 100644 plugins/web_gui/static/plugins/moment/moment-with-langs.js create mode 100644 plugins/web_gui/static/plugins/moment/moment-with-langs.min.js create mode 100644 plugins/web_gui/static/plugins/moment/moment.min.js create mode 100644 plugins/web_gui/static/plugins/morris/morris.js create mode 100644 plugins/web_gui/static/plugins/morris/morris.min.js create mode 100644 plugins/web_gui/static/plugins/raphael/raphael-min.js create mode 100644 plugins/web_gui/static/plugins/raphael/raphael.js create mode 100644 plugins/web_gui/static/plugins/select2/LICENSE create mode 100644 plugins/web_gui/static/plugins/select2/README.md create mode 100644 plugins/web_gui/static/plugins/select2/release.sh create mode 100644 plugins/web_gui/static/plugins/select2/select2-bootstrap.css create mode 100644 plugins/web_gui/static/plugins/select2/select2-spinner.gif create mode 100644 plugins/web_gui/static/plugins/select2/select2.css create mode 100644 plugins/web_gui/static/plugins/select2/select2.js create mode 100644 plugins/web_gui/static/plugins/select2/select2.min.js create mode 100644 plugins/web_gui/static/plugins/select2/select2.png create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ar.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_bg.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ca.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_cs.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_da.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_de.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_el.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_en.js.template create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_es.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_et.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_eu.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_fa.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_fi.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_fr.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_gl.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_he.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_hr.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_hu.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_id.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_is.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_it.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ja.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ko.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_lt.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_lv.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_mk.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ms.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_nl.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_no.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_pl.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_pt-BR.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_pt-PT.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ro.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ru.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_sk.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_sv.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_th.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_tr.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_ua.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_vi.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_zh-CN.js create mode 100644 plugins/web_gui/static/plugins/select2/select2_locale_zh-TW.js create mode 100644 plugins/web_gui/static/plugins/select2/select2x2.png create mode 100644 plugins/web_gui/static/plugins/sparkline/jquery.sparkline.js create mode 100644 plugins/web_gui/static/plugins/sparkline/jquery.sparkline.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/jquery.tinymce.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/langs/readme.md create mode 100644 plugins/web_gui/static/plugins/tinymce/license.txt create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/advlist/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/anchor/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/autolink/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/autoresize/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/autosave/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/bbcode/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/charmap/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/code/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/contextmenu/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/directionality/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-cool.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-cry.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-embarassed.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-frown.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-innocent.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-kiss.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-laughing.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-money-mouth.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-sealed.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-smile.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-surprised.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-tongue-out.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-undecided.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-wink.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/img/smiley-yell.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/emoticons/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/example/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/example_dependency/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/fullpage/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/fullscreen/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/hr/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/image/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/importcss/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/insertdatetime/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/layer/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/legacyoutput/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/link/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/lists/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/media/moxieplayer.swf create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/media/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/nonbreaking/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/noneditable/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/pagebreak/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/paste/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/preview/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/print/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/save/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/searchreplace/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/spellchecker/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/tabfocus/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/table/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/template/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/textcolor/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/visualblocks/css/visualblocks.css create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/visualblocks/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/visualchars/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/plugins/wordcount/plugin.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/content.inline.min.css create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/content.min.css create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/readme.md create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce-small.eot create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce-small.svg create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce-small.ttf create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce-small.woff create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce.eot create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce.svg create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce.ttf create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/fonts/tinymce.woff create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/img/anchor.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/img/loader.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/img/object.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/img/trans.gif create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/skin.ie7.min.css create mode 100644 plugins/web_gui/static/plugins/tinymce/skins/lightgray/skin.min.css create mode 100644 plugins/web_gui/static/plugins/tinymce/themes/modern/theme.min.js create mode 100644 plugins/web_gui/static/plugins/tinymce/tinymce.min.js create mode 100644 plugins/web_gui/static/plugins/xcharts/LICENSE create mode 100644 plugins/web_gui/static/plugins/xcharts/README.md create mode 100644 plugins/web_gui/static/plugins/xcharts/xcharts.css create mode 100644 plugins/web_gui/static/plugins/xcharts/xcharts.js create mode 100644 plugins/web_gui/static/plugins/xcharts/xcharts.min.css create mode 100644 plugins/web_gui/static/plugins/xcharts/xcharts.min.js create mode 100644 plugins/web_gui/static/restart.html create mode 100644 plugins/web_gui/web_gui.py create mode 100644 plugins/web_gui/web_gui_plugin.py diff --git a/config/bookmarks/bookmark_store b/config/bookmarks/bookmark_store new file mode 100644 index 0000000..3b6be2b --- /dev/null +++ b/config/bookmarks/bookmark_store @@ -0,0 +1 @@ +Bookmarks and PoI's are stored in this folder. \ No newline at end of file diff --git a/plugins/claims/__init__.py b/plugins/claims/__init__.py new file mode 100644 index 0000000..4567aff --- /dev/null +++ b/plugins/claims/__init__.py @@ -0,0 +1 @@ +from claims_plugin import ClaimsPlugin diff --git a/plugins/claims/claims_plugin.py b/plugins/claims/claims_plugin.py new file mode 100644 index 0000000..37645ed --- /dev/null +++ b/plugins/claims/claims_plugin.py @@ -0,0 +1,239 @@ +import re +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import UserLevels, permissions +from utility_functions import extract_name + + +class ClaimsPlugin(SimpleCommandPlugin): + """ +Allows planets to be either protector or unprotected. On protected planets, +only admins can build. Planets are unprotected by default. +""" + name = "claims" + description = "Claims planets." + commands = ["claim", "unclaim", "claim_list", "unclaimable"] + depends = ["player_manager", "command_dispatcher", "planet_protect"] + + def __init__(self): + super(ClaimsPlugin, self).__init__() + + def activate(self): + super(ClaimsPlugin, self).activate() + + #self.protected_planets = self.config.plugin_config.get("protected_planets", []) + #self.player_planets = self.config.plugin_config.get("player_planets", {}) + try: + self.max_claims = self.config.plugin_config['max_claims'] + except KeyError: + self.max_claims = 5 + self.unclaimable_planets = self.config.plugin_config.get("unclaimable_planets", []) + self.protected_planets = self.config.config['plugin_config']['planet_protect']['protected_planets'] + self.player_planets = self.config.config['plugin_config']['planet_protect']['player_planets'] + self.player_manager = self.plugins["player_manager"].player_manager + self.regexes = self.plugins['player_manager'].regexes + + @permissions(UserLevels.ADMIN) + def unclaimable(self, data): + """Set the current planet as unclaimable.\nSyntax: /unclaimable""" + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if on_ship: + self.protocol.send_chat_message("Can't claim ships (at the moment)") + return + if planet not in self.unclaimable_planets: + self.unclaimable_planets.append(planet) + self.protocol.send_chat_message("Planet successfully set as unclaimable.") + self.logger.info("Planet %s set as unclaimable", planet) + else: + self.unclaimable_planets.remove(planet) + self.protocol.send_chat_message("Planet successfully removed from unclaimable list.") + self.logger.info("Planet %s removed as unclaimable", planet) + self.save() + + @permissions(UserLevels.GUEST) + def claim(self, data): + """Claims the current planet. Only administrators and allowed players can build on claimed planets.\nSyntax: /claim [player]""" + if self.protocol.player.planet in self.unclaimable_planets: + self.protocol.send_chat_message("This planet ^red;cannot^green; be claimed!") + return + try: + my_storage = self.protocol.player.storage + except AttributeError: + return + # set storage to 0 if player never used any of the claim commands + if not 'claims' in my_storage: + my_storage['claims'] = 0 + self.protocol.player.storage = my_storage + # check if max claim limit has been reached + if int(my_storage['claims']) >= self.max_claims: + my_storage['claims'] = self.max_claims + self.protocol.player.storage = my_storage + self.protocol.send_chat_message( + "You already have max (^red;%s^green;) claimed planets!" % str(self.max_claims)) + # assuming player is eligible to claim + elif 0 <= int(my_storage['claims']) <= self.max_claims: + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if len(data) == 0: + addplayer = self.protocol.player.org_name + first_name_color = self.protocol.player.colored_name(self.config.colors) + else: + addplayer = data[0] + try: + addplayer, rest = extract_name(data) + addplayer = self.player_manager.get_by_name(addplayer).org_name + first_name_color = self.player_manager.get_by_org_name(addplayer).colored_name(self.config.colors) + except: + self.protocol.send_chat_message("There's no player named: ^yellow;%s" % str(addplayer)) + return + + first_name = str(addplayer) + orgplayer = self.protocol.player.org_name + + try: + count = 1 + for name in self.player_planets[planet]: + if name != str(orgplayer) and count == 1: + self.protocol.send_chat_message("You can only claim free planets!") + return + count += 1 + except: + if first_name != orgplayer: + self.protocol.send_chat_message("Use only /claim if you wish to claim a planet!") + return + + try: + for planet in self.player_planets: + if first_name in self.player_planets[self.protocol.player.planet]: + self.protocol.send_chat_message( + "Player ^yellow;%s^green; is already in planet protect list." % first_name_color) + return + except: + planet = self.protocol.player.planet # reset planet back to current planet + + planet = self.protocol.player.planet # reset planet back to current planet + if on_ship and not ("force" in " ".join(data).lower()): + self.protocol.send_chat_message("Can't claim ships (at the moment)") + return + if planet not in self.protected_planets: + self.protected_planets.append(planet) + self.protocol.send_chat_message("Planet successfully claimed.") + self.logger.info("Protected planet %s", planet) + my_storage['claims'] = int(my_storage['claims']) + 1 + self.protocol.player.storage = my_storage + if len(first_name) > 0: + if planet not in self.player_planets: + self.player_planets[planet] = [first_name] + else: + self.player_planets[planet] = self.player_planets[planet] + [first_name] + self.protocol.send_chat_message("Adding ^yellow;%s^green; to planet list" % first_name_color) + else: + if len(first_name) == 0: + self.protocol.send_chat_message("Planet is already claimed!") + else: + if planet not in self.player_planets: + self.player_planets[planet] = [first_name] + else: + self.player_planets[planet] = self.player_planets[planet] + [first_name] + self.protocol.send_chat_message("Adding ^yellow;%s^green; to planet list" % first_name_color) + + self.save() + + @permissions(UserLevels.GUEST) + def claim_list(self, data): + """Displays players registered to the protected planet.\nSyntax: /claim_list""" + try: + my_storage = self.protocol.player.storage + except AttributeError: + return + + if not 'claims' in my_storage: + my_storage['claims'] = 0 + self.protocol.player.storage = my_storage + + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if on_ship: + self.protocol.send_chat_message("Can't claim ships (at the moment)") + return + if planet in self.player_planets: + self.protocol.send_chat_message("Claimed ^cyan;%s^green; of max ^red;%s^green; claimed planets." % ( + str(my_storage['claims']), str(self.max_claims))) + self.protocol.send_chat_message("Players registered to this planet: ^yellow;" + '^green;, ^yellow;'.join( + self.player_planets[planet]).replace('[', '').replace(']', '')) # .replace("'", '') + elif planet in self.unclaimable_planets: + self.protocol.send_chat_message("Claimed ^cyan;%s^green; of max ^red;%s^green; claimed planets." % ( + str(my_storage['claims']), str(self.max_claims))) + self.protocol.send_chat_message("This planet ^red;cannot^green; be claimed!") + else: + self.protocol.send_chat_message("Claimed ^cyan;%s^green; of max ^red;%s^green; claimed planets." % ( + str(my_storage['claims']), str(self.max_claims))) + self.protocol.send_chat_message("Planet has not been claimed!") + + @permissions(UserLevels.GUEST) + def unclaim(self, data): + """Removes claimed planet, or removes a registered player.\nSyntax: /unclaim [player]""" + try: + my_storage = self.protocol.player.storage + except AttributeError: + return + if not 'claims' in my_storage or int(my_storage['claims']) <= 0: + my_storage['claims'] = 0 + self.protocol.player.storage = my_storage + self.protocol.send_chat_message("You have no claimed planets!") + else: + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + #print(self.player_planets[planet].first()) + #player_name = self.protocol.player.name + if len(data) == 0: + addplayer = self.protocol.player.org_name + first_name_color = self.protocol.player.colored_name(self.config.colors) + else: + addplayer, rest = extract_name(data) + first_name_color = addplayer + + first_name = str(addplayer) + orgplayer = self.protocol.player.org_name + + if on_ship: + self.protocol.send_chat_message("Can't claim ships (at the moment)") + return + try: + count = 1 + for name in self.player_planets[planet]: + #print(first_name, str(name)) + if name != str(orgplayer) and count == 1: + self.protocol.send_chat_message("You can only unclaim planets you've claimed!") + return + if len(data) != 0 and first_name == str(orgplayer): + self.protocol.send_chat_message("Use only /unclaim if you wish to remove protection!") + return + count += 1 + except: + pass + if len(data) == 0: + if planet in self.protected_planets: + del self.player_planets[planet] + self.protected_planets.remove(planet) + self.protocol.send_chat_message("Planet successfully unclaimed.") + self.logger.info("Unprotected planet %s", planet) + my_storage['claims'] = int(my_storage['claims']) - 1 + self.protocol.player.storage = my_storage + else: + self.protocol.send_chat_message("Planet has not been claimed!") + else: + if first_name in self.player_planets[planet]: + self.player_planets[planet].remove(first_name) + self.protocol.send_chat_message("Removed ^yellow;" + first_name_color + "^green; from planet list") + else: + self.protocol.send_chat_message( + "Cannot remove ^yellow;" + first_name_color + "^green; from planet list (not in list)") + self.save() + + def save(self): + self.config.config['plugin_config']['planet_protect']['protected_planets'] = self.protected_planets + self.config.config['plugin_config']['planet_protect']['player_planets'] = self.player_planets + self.config.plugin_config['max_claims'] = self.max_claims + self.config.plugin_config['unclaimable_planets'] = self.unclaimable_planets + self.config.save() # save config file diff --git a/plugins/web_gui/__init__.py b/plugins/web_gui/__init__.py new file mode 100644 index 0000000..c662274 --- /dev/null +++ b/plugins/web_gui/__init__.py @@ -0,0 +1 @@ +from .web_gui_plugin import WebGuiPlugin \ No newline at end of file diff --git a/plugins/web_gui/static/adminstop.html b/plugins/web_gui/static/adminstop.html new file mode 100644 index 0000000..c3f6fc7 --- /dev/null +++ b/plugins/web_gui/static/adminstop.html @@ -0,0 +1,37 @@ + + + + + StarryPy + + + + + + + + + + + + +
+
+
+
+
+ {% if handler.error_message == "" %} +

Server is being stopped!

+ {% else %} +

{{ handler.error_message }}

+ {% end %} +
+
+
+
+
+ + \ No newline at end of file diff --git a/plugins/web_gui/static/ajax/dashboard.html b/plugins/web_gui/static/ajax/dashboard.html new file mode 100644 index 0000000..0c3210a --- /dev/null +++ b/plugins/web_gui/static/ajax/dashboard.html @@ -0,0 +1,92 @@ + +
+ +
+
+
+
+
+
+ + Chat +
+ +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+ + Online Players +
+ +
+
+
+
+
+ +
+
+
+ + Administration +
+ +
+
+
+
+
+
+
+
+
diff --git a/plugins/web_gui/static/ajax/playeredit.html b/plugins/web_gui/static/ajax/playeredit.html new file mode 100644 index 0000000..b0a8115 --- /dev/null +++ b/plugins/web_gui/static/ajax/playeredit.html @@ -0,0 +1,93 @@ +
+ +
+
+
+ {% set iconcolour = "#54ae86" if handler.edit_player.logged_in else "#D15E5E" %} +

Edit {{ handler.edit_player.name }}

+ {% if handler.edit_player.logged_in %} + + {% end %} + {% if handler.player_manager.check_bans(handler.edit_player.ip) %} + + {% else %} + + {% end %} + +
+
+ +
+ {% if handler.error_message != "" %} + {{ handler.error_message }} + {% end %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name
Original Name{{ handler.edit_player.org_name }}
UUID{{ handler.edit_player.uuid }}
Access Level + +
IP{{ handler.edit_player.ip }}
Last Seen{{ str(handler.edit_player.last_seen).rpartition('.')[0] }}
planet{{ handler.edit_player.planet }}
On Ship{{ handler.edit_player.on_ship }}
+ {% raw xsrf_form_html() %} + +
+ +
+
+
+
+
diff --git a/plugins/web_gui/static/ajax/playerlist.html b/plugins/web_gui/static/ajax/playerlist.html new file mode 100644 index 0000000..83c249d --- /dev/null +++ b/plugins/web_gui/static/ajax/playerlist.html @@ -0,0 +1,37 @@ + + +
+ +
+
+
+ + + + + + + + + + + {% for player in handler.playerlist %} + + {% set iconcolour = "#54ae86" if player.logged_in else "#D15E5E" %} + + + + + + {% end %} + +
NamePlanetAccess LevelLast Seen
{{ player.name }} {% if handler.player_manager.check_bans(player.ip) %} {% end %}{{ player.planet }}{{ player.access_level }}{{ str(player.last_seen).rpartition('.')[0] }}
+
+
+ diff --git a/plugins/web_gui/static/ajax/playerlistonline.html b/plugins/web_gui/static/ajax/playerlistonline.html new file mode 100644 index 0000000..c735003 --- /dev/null +++ b/plugins/web_gui/static/ajax/playerlistonline.html @@ -0,0 +1,36 @@ + + + +
+
+ + + + + + + + + + + {% for player in handler.playerlistonline %} + + + + + + + {% end %} + +
NamePlanetAccess LevelLast Seen
{{ player.name }}{{ player.planet }}{{ player.access_level }}{{ str(player.last_seen).rpartition('.')[0] }}
+
+
+ diff --git a/plugins/web_gui/static/ajax/playerquickmenu.html b/plugins/web_gui/static/ajax/playerquickmenu.html new file mode 100644 index 0000000..0deec43 --- /dev/null +++ b/plugins/web_gui/static/ajax/playerquickmenu.html @@ -0,0 +1,10 @@ +{% if handler.edit_player.logged_in %} + +{% end %} + +{% if handler.player_manager.check_bans(handler.edit_player.ip) %} + +{% else %} + +{% end %} + \ No newline at end of file diff --git a/plugins/web_gui/static/ajax/playersonline.html b/plugins/web_gui/static/ajax/playersonline.html new file mode 100644 index 0000000..9549b9b --- /dev/null +++ b/plugins/web_gui/static/ajax/playersonline.html @@ -0,0 +1,3 @@ + {% for player in handler.playerlistonline %} + {{ player.name }}
+ {% end %} \ No newline at end of file diff --git a/plugins/web_gui/static/css/bootstrap-sortable.css b/plugins/web_gui/static/css/bootstrap-sortable.css new file mode 100644 index 0000000..8924f61 --- /dev/null +++ b/plugins/web_gui/static/css/bootstrap-sortable.css @@ -0,0 +1,84 @@ +table.sortable span.sign { + display: block; + position: absolute; + top: 50%; + right: 5px; + font-size: 12px; + margin-top: -10px; + color: #bfbfc1; +} + +table.sortable span.arrow, span.reversed { + border-style: solid; + border-width: 5px; + font-size: 0; + border-color: #ccc transparent transparent transparent; + line-height: 0; + height: 0; + width: 0; + margin-top: -2px; +} + + table.sortable span.arrow.up { + border-color: transparent transparent #ccc transparent; + margin-top: -7px; + } + +table.sortable span.reversed { + border-color: transparent transparent #ccc transparent; + margin-top: -7px; +} + + table.sortable span.reversed.up { + border-color: #ccc transparent transparent transparent; + margin-top: -2px; + } + + + +table.sortable span.az:before { + content: "a .. z"; +} + +table.sortable span.az.up:before { + content: "z .. a"; +} + +table.sortable span.AZ:before { + content: "A .. Z"; +} + +table.sortable span.AZ.up:before { + content: "Z .. A"; +} + +table.sortable span._19:before { + content: "1 .. 9"; +} + +table.sortable span._19.up:before { + content: "9 .. 1"; +} + +table.sortable span.month:before { + content: "jan .. dec"; +} + +table.sortable span.month.up:before { + content: "dec .. jan"; +} + +table.sortable thead th:not([data-defaultsort=disabled]) { + cursor: pointer; + position: relative; + top: 0; + left: 0; +} + +table.sortable thead th:hover:not([data-defaultsort=disabled]) { + background: #efefef; +} + +table.sortable thead th div.mozilla { + position: relative; +} diff --git a/plugins/web_gui/static/css/footer.css b/plugins/web_gui/static/css/footer.css new file mode 100644 index 0000000..7ce6adb --- /dev/null +++ b/plugins/web_gui/static/css/footer.css @@ -0,0 +1,6 @@ +#footer { + position: fixed; + bottom: 0; + padding-left: 10 px; + padding-bottom: 5px; +} \ No newline at end of file diff --git a/plugins/web_gui/static/css/style.min.css b/plugins/web_gui/static/css/style.min.css new file mode 100644 index 0000000..eddc069 --- /dev/null +++ b/plugins/web_gui/static/css/style.min.css @@ -0,0 +1 @@ +body{color:#525252;background:#6aa6d6 url(../img/devoops_pattern_b10.png) 0 0 repeat}.body-expanded,.modal-open{overflow-y:hidden;margin-right:15px}.body-expanded .expanded-panel,.fancybox-margin .expanded-panel,.modal-open .expanded-panel{margin-right:15px}.body-screensaver{overflow:hidden}h1,.h1,h2,.h2,h3,.h3{margin:0}#logo{position:relative;background:#525252 url(../img/devoops_pattern_b10.png) 0 0 repeat}#logo a{color:#fff;font-family:'Righteous',cursive;display:block;font-size:20px;line-height:50px;background:url(../img/logo.png) right 42px no-repeat;-webkit-transition:.5s;-moz-transition:.5s;-o-transition:.5s;transition:.5s}#logo a:hover{background-position:right 25px;text-decoration:none}.navbar{margin:0;border:0;position:fixed;top:0;left:0;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;box-shadow:0 1px 2px #272727;z-index:2000}.body-expanded .navbar{z-index:9}a.show-sidebar{float:left;color:#6d6d6d;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}a.show-sidebar:hover{color:#000}#sidebar-left{position:relative;z-index:inherit;padding-bottom:3000px !important;margin-bottom:-3000px !important;background:#6aa6d6 url(../img/devoops_pattern_b10.png) 0 0 repeat;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}#content{position:relative;z-index:10;background:#ebebeb;box-shadow:0 0 6px #131313;padding-bottom:3000px !important;margin-bottom:-2980px !important;overflow:hidden;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.full-content{overflow:hidden;padding:0;margin:0}.nav.main-menu,.nav.msg-menu{margin:0 -15px}.nav.main-menu>li>a,.nav.msg-menu>li>a{text-align:center;color:#f0f0f0;min-height:40px;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background:rgba(0,0,0,0.1)}.nav.main-menu>li>a:hover,.nav.main-menu>li>a:focus,.nav.main-menu>li.active>a,.nav.main-menu .open>a,.nav.main-menu .open>a:hover,.nav.main-menu .open>a:focus,.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover,.dropdown-menu>li.active>a,.nav.msg-menu>li>a:hover,.nav.msg-menu>li>a:focus,.nav.msg-menu>li.active>a,.nav.msg-menu .open>a,.nav.msg-menu .open>a:hover,.nav.msg-menu .open>a:focus{background:rgba(0,0,0,0.1);color:#f0f0f0}.nav.main-menu a.active,.nav.msg-menu a.active{background:rgba(0,0,0,0.2)}.nav.main-menu a.active:hover,.nav.msg-menu a.active:hover{background:rgba(0,0,0,0.2)}.nav.main-menu a.active-parent,.nav.msg-menu a.active-parent{background:rgba(0,0,0,0.3)}.nav.main-menu a.active-parent:hover,.nav.msg-menu a.active-parent:hover{background:rgba(0,0,0,0.3)}.nav.main-menu>li>a>i,.nav.msg-menu>li>a>i{font-size:18px;width:auto;display:block;text-align:center;vertical-align:middle}.main-menu .dropdown-menu{position:absolute;z-index:2001;left:100%;top:0;float:none;margin:0;border:0;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;padding:0;background:#6aa6d6 url(../img/devoops_pattern_b10.png) 0 0 repeat;box-shadow:none;visibility:hidden}.main-menu .active-parent:hover+.dropdown-menu{visibility:visible}.main-menu .active-parent+.dropdown-menu:hover{visibility:visible}.main-menu .dropdown-menu>li>a{padding:9px 15px 9px 40px;color:#f0f0f0}.main-menu .dropdown-menu>li:first-child>a{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0}.main-menu .dropdown-menu>li:last-child>a{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0}#top-panel{line-height:50px;height:50px;background:#ebebeb}#main{margin-top:50px;min-height:800px;overflow:hidden}#search{position:relative;margin-left:20px}#search>input{width:80%;background:#dfdfdf;border:1px solid #c7c7c7;text-shadow:0 1px 1px #EEE;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;color:#686868;line-height:1em;height:30px;padding:0 35px 0 10px;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}#search>input+i{opacity:0;position:absolute;top:18px;right:10px;color:#fff;-webkit-transition:.4s;-moz-transition:.4s;-o-transition:.4s;transition:.4s}#search>input:focus{width:100%;outline:0}#search>input:focus+i{opacity:1}.panel-menu{margin:0}.top-panel-right{padding-left:0}.panel-menu>li>a{padding:0 5px 0 10px;line-height:50px}.panel-menu>li>a:hover{background:0}.panel-menu a.account{height:50px;padding:5px 0 5px 10px;line-height:18px}.panel-menu i{margin-top:8px;padding:5px;font-size:20px;color:#7bc5d3;line-height:1em;vertical-align:top;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.panel-menu>li>a:hover>i{background:#f5f5f5}.panel-menu i.pull-right{color:#000;border:0;box-shadow:none;font-size:16px;background:none !important}.panel-menu .badge{margin-top:3px;padding:3px 6px;vertical-align:top;background:#cea9a9}.avatar{width:40px;float:left;margin-right:5px}.avatar>img{width:40px;height:40px;border:1px solid #f8f8f8}.user-mini>span{display:block;font-size:12px;color:#363636;margin-bottom:-4px}.user-mini>span.welcome{font-weight:bold;margin-top:2px}.panel-menu .dropdown-menu{position:absolute !important;background:rgba(0,0,0,0.7) !important;padding:0;border:0;right:0;left:auto;min-width:100%}.panel-menu .dropdown-menu>li>a{padding:5px 10px !important;color:#f0f0f0}.panel-menu .dropdown-menu>li>a>i{border:0;padding:0;margin:0;font-size:14px;width:20px;display:inline-block;text-align:center;vertical-align:middle}.well{padding:15px}.box{display:block;z-index:1999;position:relative;border:1px solid #f8f8f8;box-shadow:0 0 4px #d8d8d8;background:transparent;margin-bottom:20px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.full-content .box{border:0;margin-bottom:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.box-header{-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;color:#363636;font-size:16px;position:relative;overflow:hidden;background:#f5f5f5;border-bottom:1px solid #e4e4e4;height:28px}.box-name,.modal-header-name{padding-left:15px;line-height:28px}.box-name:hover{cursor:move}.box-name>i{margin-right:5px}.box-icons{position:absolute;top:0;right:0;z-index:9}.no-move{display:none}.expanded .no-move{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;display:block}.box-content{position:relative;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;padding:15px;background:#fcfcfc}.box-content.dropbox,.box-content.sortablebox{overflow:hidden}.full-content .box-content{height:100%;position:absolute;width:100%;left:0;top:0}.box-icons a{cursor:pointer;text-decoration:none !important;border-left:1px solid #fafafa;height:26px;line-height:26px;width:28px;display:block;float:left;text-align:center;color:#b8b8b8 !important;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.box-icons a.beauty-table-to-json{width:auto;padding:0 10px;font-size:14px}.box-icons a:hover{box-shadow:inset 0 0 1px 0 #cecece}.expanded a.close-link{display:none}#sidebar-left.col-xs-2{opacity:0;width:0;padding:0}.sidebar-show #sidebar-left.col-xs-2{opacity:1;width:16.666666666666664%;padding:0 15px}.sidebar-show #content.col-xs-12{opacity:1;width:83.33333333333334%}.expanded{overflow-y:scroll;border:0;z-index:3000 !important;position:fixed;width:100%;height:100%;top:0;left:0;padding:0;background:rgba(0,0,0,0.2);-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.expanded-padding{background:rgba(0,0,0,0.7);padding:50px;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.no-padding{padding:0 !important}.padding-15{padding:15px !important}.no-padding .table-bordered{border:0;margin:0}.no-padding .table-bordered thead tr th:first-child,.no-padding .table-bordered tbody tr th:first-child,.no-padding .table-bordered tfoot tr th:first-child,.no-padding .table-bordered thead tr td:first-child,.no-padding .table-bordered tbody tr td:first-child,.no-padding .table-bordered tfoot tr td:first-child{border-left:0 !important}.no-padding .table-bordered thead tr th:last-child,.no-padding .table-bordered tbody tr th:last-child,.no-padding .table-bordered tfoot tr th:last-child,.no-padding .table-bordered thead tr td:last-child,.no-padding .table-bordered tbody tr td:last-child,.no-padding .table-bordered tfoot tr td:last-child{border-right:0 !important}.table-heading thead tr{background-color:#f0f0f0;background-image:-webkit-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-moz-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-ms-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-o-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:linear-gradient(to bottom,#f0f0f0,#dfdfdf)}table.no-border-bottom tr:last-child td{border-bottom:0}.dataTables_wrapper{overflow:hidden}.dataTables_wrapper table.table{clear:both;max-width:inherit;margin-bottom:0}.table-datatable *,.table-datatable :after,.table-datatable :before{margin:0;padding:0;-webkit-box-sizing:content-box;box-sizing:content-box;-moz-box-sizing:content-box}.table-datatable label{position:relative;display:block;font-weight:400}.table-datatable tbody td{vertical-align:middle !important}.table-datatable img{margin-right:10px;border:1px solid #f8f8f8;width:40px}.table-datatable .sorting{background:url(../img/sort.png) right center no-repeat;padding-right:16px;cursor:pointer}.table-datatable .sorting_asc{background:url(../img/sort-asc.png) right center no-repeat;padding-right:16px;cursor:pointer}.table-datatable .sorting_desc{background:url(../img/sort-desc.png) right center no-repeat;padding-right:16px;cursor:pointer}div.DTTT_collection_background{z-index:2002}div.DTTT .btn{color:#333 !important;font-size:12px}ul.DTTT_dropdown.dropdown-menu{z-index:2003;background:rgba(0,0,0,0.7) !important;padding:0;border:0;margin:0;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;min-width:157px}ul.DTTT_dropdown.dropdown-menu li{position:relative}ul.DTTT_dropdown.dropdown-menu>li>a{position:relative;display:block;padding:5px 10px !important;color:#f0f0f0 !important}ul.DTTT_dropdown.dropdown-menu>li:first-child>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}ul.DTTT_dropdown.dropdown-menu>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}ul.DTTT_dropdown.dropdown-menu>li:hover>a{background:rgba(0,0,0,0.3);color:#f0f0f0}.dataTables_wrapper input[type="text"]{display:block;width:90%;height:26px;padding:2px 12px;font-size:14px;line-height:1.428571429;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-moz-appearance:none;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.dataTables_wrapper input[type="text"]:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}#breadcrumb{padding:0;line-height:40px;background:#525252;background:#5a8db6 url(../img/devoops_pattern_b10.png) 0 0 repeat;margin-bottom:20px}.breadcrumb{padding:0 15px;background:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0}.breadcrumb>li>a{color:#d8d8d8}.breadcrumb>li>a:hover,.breadcrumb>li:last-child>a{color:#f8f8f8}.bs-callout{padding:15px;border-left:3px solid #525252;background:#dfdfdf}.bs-callout h4{margin-top:0;margin-bottom:5px;color:#525252}.no-padding .bs-callout{border:0}.page-header{margin:0 0 10px;border-bottom:1px solid #c7c7c7}.box-content .page-header,legend,.full-calendar .page-header{margin:0 0 10px;border-bottom:1px dashed #b6b6b6}.invoice-header{margin:0 0 10px;border-bottom:1px dashed #b6b6b6;display:inline-block}.box-content .form-group,.devoops-modal-inner .form-group{margin-top:15px;margin-bottom:15px}.show-grid [class^="col-"]{padding-top:10px;padding-bottom:10px;background-color:#525252;background-color:rgba(129,199,199,0.2);border:1px solid #ebebeb}.show-grid [class^="col-"]:hover{padding-top:10px;padding-bottom:10px;background-color:rgba(107,134,182,0.2);border:1px solid #ebebeb}.show-grid,.show-grid-forms{margin-bottom:15px}.show-grid-forms [class^="col-"]{padding-top:10px;padding-bottom:10px}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th,td.beauty-hover{background-color:rgba(219,219,219,0.3) !important}.table-hover>tbody>tr:hover>td.beauty-hover:hover{background-color:rgba(219,219,219,0.9) !important}.DTTT.btn-group{position:absolute;top:-28px;right:83px;border-right:1px solid #dbdbdb}.DTTT.btn-group a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;line-height:1em;font-size:14px;font-weight:bold;outline:0;box-shadow:none !important;padding:6px 12px;margin:0;background:#f7f7f7;border:0}#screensaver{position:fixed;top:0;left:0;width:100%;height:100%;z-index:3000;background:#000;display:none}#screensaver.show{display:block}#canvas{position:relative}#screensaver i{position:absolute;top:50px;right:50px;background:rgba(255,255,255,0.5);line-height:100px;width:100px;height:100px;text-align:center;font-size:60px;color:rgba(0,0,0,0.8);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well pre{padding:0;margin-top:0;margin-bottom:0;background-color:transparent;border:0;white-space:nowrap}.well pre code{white-space:normal}.btn{border-width:1px;border-style:solid;border-width:1px;text-decoration:none;border-color:rgba(0,0,0,0.3);cursor:pointer;outline:0;font-family:"Lucida Grande","Lucida Sans","Lucida Sans Unicode","Segoe UI",Verdana,sans-serif;display:inline-block;vertical-align:top;position:relative;font-size:12px;font-weight:bold;text-align:center;background-color:#a2a2a2;background:#a2a2a2 -moz-linear-gradient(top,rgba(255,255,255,0.6),rgba(255,255,255,0));background:#a2a2a2 -webkit-gradient(linear,0 0,0 100%,from(rgba(255,255,255,0.6)),to(rgba(255,255,255,0)));line-height:24px;margin:0 0 10px 0;padding:0 10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-moz-user-select:none;-webkit-user-select:none;outline:none !important}.btn-label-left,.btn-label-right{padding:0 10px}.btn-label-left span{position:relative;left:-10px;display:inline-block;padding:0 8px;background:rgba(0,0,0,0.1)}.btn-label-right span{position:relative;right:-10px;display:inline-block;padding:0 8px;background:rgba(0,0,0,0.1)}.btn i{vertical-align:middle}.btn-app{width:80px;height:80px;padding:0;font-size:16px}.btn-app i{font-size:36px;line-height:78px;display:block}.btn-app-sm{width:50px;height:50px;padding:0;font-size:12px}.btn-app-sm i{font-size:18px;line-height:48px;display:block}.btn-circle{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;border:2px solid rgba(0,0,0,0.25)}.btn.active{background-image:none;outline:0;-webkit-box-shadow:none;box-shadow:none}.btn-default,.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active,.progress-bar{background-color:#d8d8d8;border-color:rgba(0,0,0,0.3);color:#929292}.btn-primary,.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active,.table>thead>tr>td.primary,.table>tbody>tr>td.primary,.table>tfoot>tr>td.primary,.table>thead>tr>th.primary,.table>tbody>tr>th.primary,.table>tfoot>tr>th.primary,.table>thead>tr.primary>td,.table>tbody>tr.primary>td,.table>tfoot>tr.primary>td,.table>thead>tr.primary>th,.table>tbody>tr.primary>th,.table>tfoot>tr.primary>th{background-color:#6aa6d6;border-color:rgba(0,0,0,0.3);color:#f8f8f8}.btn-success,.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active,.progress-bar-success,.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#63cc9e;border-color:rgba(0,0,0,0.3);color:#f8f8f8}.btn-info,.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active,.progress-bar-info,.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#7bc5d3;border-color:rgba(0,0,0,0.3);color:#f8f8f8}.btn-warning,.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active,.progress-bar-warning,.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#dfd271;border-color:rgba(0,0,0,0.3);color:#f8f8f8}.btn-danger,.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active,.progress-bar-danger,.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#d15e5e;border-color:rgba(0,0,0,0.3);color:#f8f8f8}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#525252;background-color:#b8b8b8;border-color:rgba(0,0,0,0.3)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary,.table-hover>tbody>tr>td.primary:hover,.table-hover>tbody>tr>th.primary:hover,.table-hover>tbody>tr.primary:hover>td,.table-hover>tbody>tr.primary:hover>th{color:#fff;background-color:#5a8db6;border-color:rgba(0,0,0,0.3)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{color:#fff;background-color:#54ae86;border-color:rgba(0,0,0,0.3)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{color:#fff;background-color:#69a8b4;border-color:rgba(0,0,0,0.3)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{color:#fff;background-color:#beb360;border-color:rgba(0,0,0,0.3)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{color:#fff;background-color:#b25050;border-color:rgba(0,0,0,0.3)}.progress{overflow:visible}.progress-ui{height:10px}.progress-bar{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.progress-bar.ui-widget-content{background:0;border:0;height:100%;position:relative}.progress-bar .ui-state-default{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;height:10px;width:10px;top:0;margin-left:-5px;cursor:pointer;border:0 solid #d3d3d3;outline:none !important;background-color:#f0f0f0;background-image:-webkit-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-moz-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-ms-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-o-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:linear-gradient(to bottom,#f0f0f0,#dfdfdf)}.progress-bar .ui-widget-header{background:#d8d8d8}.progress-bar-primary .ui-widget-header{background:#6aa6d6;color:#f8f8f8}.progress-bar-success .ui-widget-header{background:#63cc9e;color:#f8f8f8}.progress-bar-info .ui-widget-header{background:#7bc5d3;color:#f8f8f8}.progress-bar-warning .ui-widget-header{background:#dfd271;color:#f8f8f8}.progress-bar-danger .ui-widget-header{background:#d15e5e;color:#f8f8f8}.progress-bar .ui-state-default{background:#b8b8b8}.progress-bar-primary .ui-state-default{background:#5a8db6}.progress-bar-success .ui-state-default{background:#54ae86}.progress-bar-info .ui-state-default{background:#69a8b4}.progress-bar-warning .ui-state-default{background:#beb360}.progress-bar-danger .ui-state-default{background:#b25050}.slider-range-min-amount,.slider-range-max-amount,.slider-range-amount{border:0;background:0;outline:none !important}.progress-bar.ui-slider-vertical{width:20px}.progress-bar.ui-slider-vertical .ui-state-default{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;height:20px;width:20px;top:auto;margin-left:0;left:0}#equalizer .progress{height:160px;display:inline-block;margin:15px}.beauty-table{width:100%;border-collapse:separate;border-spacing:0}.beauty-table input{border:1px solid transparent;background:0;font-size:16px;text-align:center;padding:2px 15px !important;width:100%;outline:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.beauty-table input:focus{border:1px solid #dfdfdf;background:#fefefe;font-size:16px;text-align:center;padding:2px 15px !important;width:100%;outline:0}.c{color:#999;display:block}.nt{color:#2f6f9f}.na{color:#4f9fcf}.s{color:#d44950}.radio,.checkbox,.radio-inline,.checkbox-inline{position:relative}.radio label,.checkbox label,.radio-inline label,.checkbox-inline label{font-weight:normal;cursor:pointer;padding-left:8px;-webkit-transition:1s;-moz-transition:1s;-o-transition:1s;transition:1s}.radio+.radio,.checkbox+.checkbox{margin-top:10px}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;clip:rect(0,0,0,0)}.checkbox i,.checkbox-inline i,.radio i,.radio-inline i{cursor:pointer;position:absolute;left:0;top:0;font-size:24px;-webkit-transition:1s;-moz-transition:1s;-o-transition:1s;transition:1s}.checkbox i.small,.checkbox-inline i.small,.radio i.small,.radio-inline i.small{font-size:18px;top:2px}.checkbox input[type=checkbox]:checked+i:before,.checkbox-inline input[type=checkbox]:checked+i:before{content:"\f046"}.radio input[type=radio]:checked+i:before,.radio-inline input[type=radio]:checked+i:before{content:"\f192"}.toggle-switch{position:relative;width:60px}.toggle-switch input{display:none}.toggle-switch label{display:block;overflow:hidden;cursor:pointer;-webkit-border-radius:20px;-moz-border-radius:20px;border-radius:20px}.toggle-switch-inner{width:200%;margin-left:-100%;-webkit-transition:margin .3s ease-in 0;-moz-transition:margin .3s ease-in 0;-o-transition:margin .3s ease-in 0;transition:margin .3s ease-in 0}.toggle-switch-inner:before,.toggle-switch-inner:after{float:left;width:50%;height:20px;padding:0;line-height:20px;font-size:12px;text-shadow:1px 1px 1px #fff;color:#929292;background-color:#f5f5f5;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:20px;-moz-border-radius:20px;border-radius:20px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.toggle-switch-inner:before{content:"ON";padding-left:15px;-webkit-border-radius:20px 0 0 20px;-moz-border-radius:20px 0 0 20px;border-radius:20px 0 0 20px}.toggle-switch-inner:after{content:"OFF";padding-right:15px;text-align:right;-webkit-border-radius:0 20px 20px 0;-moz-border-radius:0 20px 20px 0;border-radius:0 20px 20px 0}.toggle-switch-switch{width:20px;margin:0;border:2px solid #d8d8d8;-webkit-border-radius:20px;-moz-border-radius:20px;border-radius:20px;position:absolute;top:0;bottom:0;right:40px;color:#f8f8f8;line-height:1em;text-shadow:0 0 1px #adadad;text-align:center;-webkit-transition:all .3s ease-in 0;-moz-transition:all .3s ease-in 0;-o-transition:all .3s ease-in 0;transition:all .3s ease-in 0;background-color:#f0f0f0;background-image:-webkit-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-moz-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-ms-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:-o-linear-gradient(top,#f0f0f0,#dfdfdf);background-image:linear-gradient(to bottom,#f0f0f0,#dfdfdf)}.toggle-switch input:checked+.toggle-switch-inner{margin-left:0}.toggle-switch input:checked+.toggle-switch-inner+.toggle-switch-switch{right:0}.toggle-switch-danger input:checked+.toggle-switch-inner+.toggle-switch-switch{border:2px solid #d15e5e;background:#d15e5e}.toggle-switch-warning input:checked+.toggle-switch-inner+.toggle-switch-switch{border:2px solid #dfd271;background:#dfd271}.toggle-switch-info input:checked+.toggle-switch-inner+.toggle-switch-switch{border:2px solid #7bc5d3;background:#7bc5d3}.toggle-switch-success input:checked+.toggle-switch-inner+.toggle-switch-switch{border:2px solid #63cc9e;background:#63cc9e}.toggle-switch-primary input:checked+.toggle-switch-inner+.toggle-switch-switch{border:2px solid #6aa6d6;background:#6aa6d6}.select2-container{width:100%}.select2-container .select2-choice{height:30px}.knob-slider{position:relative;text-align:center;display:inline-block;width:100%;margin-bottom:5px}.knob-slider>div{display:inline-block !important}.knob-slider input{outline:none !important}.ipod{background:#dedede;text-align:center;padding:50px 0}.knob-clock{text-align:center}.knob-clock>div{font-size:50px;text-align:center;color:#a2a2a2}.knob{border:0;background:0}.box-pricing:hover{box-shadow:0 0 5px #525252;-webkit-transition:.5s;-moz-transition:.5s;-o-transition:.5s;transition:.5s}.box-pricing .row-fluid>div{padding:18px 15px 8px;line-height:1.428571429;vertical-align:top}.box-pricing .row-fluid.centered>div{background-color:#f5f5f5;padding:8px;text-align:center}.box-pricing .row-fluid.centered>div:nth-child(odd){background-color:#f9f9f9}.box-pricing .box-header{height:80px;padding:10px 0}.box-pricing .box-name{padding:0 10px;text-align:center}.box-pricing .box-name:hover{cursor:inherit}#messages #breadcrumb{margin-bottom:0;position:fixed;width:100%;z-index:2}#messages-menu{position:fixed;top:90px;background:#a5a5a5;margin:0;height:100%;z-index:2}#messages-list{margin-top:40px;padding:0}.one-list-message{background:#f1f1f1;border-bottom:1px solid #CCC;padding:15px 15px 15px 25px;margin:0}.one-list-message .checkbox{margin:0;overflow:hidden;white-space:nowrap}.one-list-message .message-title{overflow:hidden;white-space:nowrap;width:80%}.one-list-message .message-date{overflow:hidden;white-space:nowrap;font-size:11px;line-height:20px;text-align:center;position:absolute;right:10px;font-weight:bold;background:#d8d8d8;padding:0;width:50px;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;color:#000}.form-control{height:26px;padding:2px 12px}.input-lg{height:39px}.input-sm{height:18px}.bg-default{background:#d8d8d8 !important}.bg-primary{background:#6aa6d6 !important;color:#f8f8f8 !important}.bg-success{background:#63cc9e !important;color:#f8f8f8 !important}.bg-info{background:#7bc5d3 !important;color:#f8f8f8 !important}.bg-warning{background:#dfd271 !important;color:#f8f8f8 !important}.bg-danger{background:#d15e5e !important;color:#f8f8f8 !important}.txt-default{color:#d8d8d8 !important}.txt-primary{color:#6aa6d6 !important}.txt-success,.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#63cc9e !important}.txt-info{color:#7bc5d3 !important}.txt-warning,.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#dfd271 !important}.txt-danger,.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#d15e5e !important}.has-success .form-control{border-color:#63cc9e}.has-warning .form-control{border-color:#dfd271}.has-error .form-control{border-color:#d15e5e}.has-success .form-control:focus{border-color:#63cc9e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #63cc9e;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #63cc9e}.has-warning .form-control:focus{border-color:#dfd271;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #dfd271;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #dfd271}.has-error .form-control:focus{border-color:#d15e5e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #d15e5e;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #d15e5e}.select2-container-multi .select2-choices{min-height:26px;display:block;height:26px;padding:0 0 0 8px;overflow:hidden;position:relative;border:1px solid #aaa;white-space:nowrap;line-height:26px;color:#444;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background-clip:padding-box;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#eee),color-stop(0.5,#fff));background-image:-webkit-linear-gradient(center bottom,#eee 0,#fff 50%);background-image:-moz-linear-gradient(center bottom,#eee 0,#fff 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff',endColorstr = '#eeeeee',GradientType = 0);background-image:linear-gradient(top,#fff 0,#eee 50%)}.select2-container-multi .select2-choices .select2-search-field input{padding:0;margin:0}.has-feedback .form-control-feedback{width:26px;height:26px;line-height:26px}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{min-height:inherit;padding-top:0}.form-horizontal .control-label{padding-top:4px}.input-group-addon{padding:0 6px}.form-group .form-control,.form-group .input-group{margin-bottom:5px}.input-group .form-control{margin:0}#ui-datepicker-div{background:rgba(0,0,0,0.7) !important;border:0}#ui-datepicker-div .ui-widget-header{background:rgba(0,0,0,0.2);border:0;border-bottom:1px solid #686868;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;color:#f8f8f8;padding:1px 0}#ui-datepicker-div.ui-widget-content{color:#f8f8f8 !important}#ui-datepicker-div .ui-state-default,#ui-datepicker-div .ui-widget-content .ui-state-default,#ui-datepicker-div .ui-widget-header .ui-state-default{background:0;border:0;color:#f8f8f8;text-align:center}#ui-datepicker-div .ui-state-hover,#ui-datepicker-div.ui-widget-content .ui-state-hover,#ui-datepicker-div .ui-widget-header .ui-state-hover,#ui-datepicker-div .ui-state-focus,#ui-datepicker-div.ui-widget-content .ui-state-focus,#ui-datepicker-div .ui-widget-header .ui-state-focus,#ui-datepicker-div .ui-state-highlight,#ui-datepicker-div.ui-widget-content .ui-state-highlight,#ui-datepicker-div .ui-widget-header .ui-state-highlight{background:rgba(0,0,0,0.3) !important;border:0;top:2px}#ui-datepicker-div .ui-datepicker-group{border-left:1px solid #686868}#ui-datepicker-div .ui-datepicker-group:first-child{border-left:0}#ui-datepicker-div .ui-datepicker-buttonpane{margin:0}#ui-datepicker-div .ui-datepicker-group table{margin:0 auto !important}.ui-datepicker .ui-datepicker-prev{left:2px !important;cursor:pointer}.ui-datepicker .ui-datepicker-next{right:2px !important;cursor:pointer}.ui-icon-circle-triangle-w{background:url(../img/ui-left.png) 0 0 no-repeat !important}.ui-icon-circle-triangle-e{background:url(../img/ui-right.png) 0 0 no-repeat !important}.ui-icon-circle-arrow-s{background:url(../img/ui-accordion-down.png) 0 0 no-repeat !important}.ui-icon-circle-arrow-e{background:url(../img/ui-accordion-right.png) 0 0 no-repeat !important}#ui-datepicker-div .ui-slider-horizontal{background:rgba(0,0,0,0.5);height:4px;border:0}#ui-datepicker-div .ui-slider-horizontal .ui-slider-handle{background:#d8d8d8 !important;border:1px solid #f8f8f8;height:8px;width:8px;top:-2px;margin-left:-4px;outline:0;cursor:pointer}.ui-spinner-input{margin:0}.ui-spinner .form-control{margin-bottom:0}#tabs.ui-widget-content,#tabs .ui-widget-header{border:0;background:0;padding:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}#tabs .ui-widget-header{border-bottom:1px solid #d8d8d8}#tabs .ui-state-default,#tabs.ui-widget-content .ui-state-default,#tabs .ui-widget-header .ui-state-default{border:0;margin:0 0 -1px 0;background:none !important}#tabs .ui-state-active,#tabs.ui-widget-content .ui-state-active,#tabs .ui-widget-header .ui-state-active{background:none !important}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin:0;padding:0}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{padding:5px 15px;outline:none !important}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{background:#fcfcfc;border:1px solid #d8d8d8;border-bottom:0}.ui-tabs .ui-tabs-nav{padding:0}.ui-tabs .ui-tabs-panel{padding:1em 0}.ui-widget{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.jqstooltip{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;border:0 !important;text-align:center !important;margin:0 !important;width:50px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:0}.ui-accordion .ui-accordion-header{padding:6px 12px;margin:0;top:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.ui-accordion .ui-accordion-icons{padding-left:28px}.ui-accordion-header.ui-state-default{background:#f5f5f5 !important;border:1px solid #fcfcfc;border-left:0;border-right:0}.ui-accordion-header.ui-state-hover,.ui-accordion-header.ui-state-focus{background:#ebebeb !important}.ui-accordion-header.ui-state-active{background:#d8d8d8 !important}.ui-accordion .ui-accordion-content{padding:10px 12px;background:0;border:1px solid #d8d8d8;border-top:0;border-bottom:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}#simple_gallery{text-align:center}#simple_gallery a.fancybox{display:inline-block;padding:5px}#simple_gallery a.fancybox img{width:100%;padding:2px;border:1px solid #979797;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}#simple_gallery a.fancybox img:hover{box-shadow:0 0 10px #c7c7c7}.justifiedGallery{overflow:hidden;width:100%}.jg-row{position:relative;white-space:nowrap}.justifiedGallery .jg-image{position:absolute;display:inline-block;vertical-align:top;margin-left:0}.justifiedGallery .jg-image a{text-decoration:none}.justifiedGallery .jg-image img{border:0}.justifiedGallery .jg-image-label{white-space:normal;font:normal 12px arial;background:#000;color:#fff;position:absolute;left:0;right:0;padding:5px 5px 10px 8px;text-align:left;opacity:0}.ex-tooltip{position:absolute;display:none;z-index:2000}.morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style,.ex-tooltip{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:6px 20px;color:#525252;background:rgba(255,255,255,0.8);font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:.25em 0}.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:.1em 0}#dashboard-header{margin-bottom:20px}#dashboard_links{padding:0}#dashboard_links .nav{background:#3575a0 url(../img/devoops_pattern_b10.png) 0 0 repeat;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;overflow:hidden}#dashboard_links .nav-stacked>li{border-bottom:1px solid rgba(0,0,0,0.25);border-top:1px solid rgba(255,255,255,0.12);font-size:12px;font-weight:700;line-height:15px;padding:0;margin:0}#dashboard_links .nav-pills>li>a{color:#f8f8f8;display:block;padding:20px 10px 20px 15px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;outline:0}#dashboard_links .nav-pills>li.active{border-top-color:rgba(0,0,0,0.11);position:relative;margin:0}#dashboard_links .nav-pills>li.active>a,#dashboard_links .nav-pills>li.active>a:hover,#dashboard_links .nav-pills>li.active>a:focus,#dashboard_links .nav>li>a:hover,#dashboard_links .nav>li>a:focus{background:rgba(0,0,0,0.1)}#dashboard_links .nav-pills>li.active>a:before{font-family:FontAwesome;content:"\f0da";position:absolute;left:-2px;font-size:30px;color:#f8f8f8}#dashboard_tabs{background:#f8f8f8;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}#dashboard-overview{padding-bottom:15px}.sparkline-dashboard{float:left;margin-right:10px;text-align:center}.sparkline-dashboard-info{float:left;display:block;text-align:center}.sparkline-dashboard-info span{display:block;font-weight:bold;color:#b25050}#ow-marketplace{margin-top:20px}.ow-server{padding-top:8px;padding-bottom:25px}.ow-server:hover{background:#e7e7e7}.ow-server .page-header{padding-bottom:3px}.ow-server h4 i{position:absolute;left:15px}.ow-server small{position:absolute;right:15px;top:51px}.ow-server-bottom{margin-top:25px}.ow-server-bottom .knob-slider{font-size:11px}#ow-server-footer{overflow:hidden;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.ow-settings{position:absolute;top:7px;left:40px;display:none}.ow-settings a{color:#525252}.ow-server:hover .ow-settings{display:block}#ow-server-footer a{display:block;padding:10px 0;border-left:1px solid #f8f8f8;text-decoration:none}#ow-server-footer a:first-child{border-left:0}#ow-server-footer span{display:block}.m-table>thead>tr>th,.m-table>tbody>tr>th,.m-table>tfoot>tr>th,.m-table>thead>tr>td,.m-table>tbody>tr>td,.m-table>tfoot>tr>td{vertical-align:middle;padding:2px 5px}.m-ticker span{display:block;font-size:.8em;line-height:1em}.m-price{text-align:right}.m-change .fa-angle-up{color:#54ae86;font-weight:bold}.m-change .fa-angle-down{color:#b25050;font-weight:bold}#ow-summary{font-size:12px}#ow-summary b{float:right;padding:1px 4px;margin:1px;border:1px solid #d8d8d8;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}#ow-donut{margin:0 0 20px}#ow-donut>div{padding:0}#ow-activity .row{margin:0 0 0 -15px;font-size:13px}#ow-setting{border:1px solid #c7c7c7;padding:0;position:absolute;width:158px;height:28px;top:1px;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;opacity:0;right:-200px;-webkit-transition:.1s;-moz-transition:.1s;-o-transition:.1s;transition:.1s}#ow-marketplace:hover #ow-setting{opacity:1;right:15px}#ow-setting a{text-align:center;float:left;margin-left:10px;color:#d8d8d8;font-size:16px;display:block;line-height:28px;width:20px;height:26px;-webkit-transition:.1s;-moz-transition:.1s;-o-transition:.1s;transition:.1s}#ow-setting a:hover{font-size:16px;color:#222;line-height:24px}#ow-licenced{margin:20px 0}#ow-licenced .row{margin:0}#ow-stat .row{margin:0}#dashboard-clients .one-list-message{background:0;padding:10px 15px}#dashboard-clients .one-list-message:last-child{border-bottom:0}#dashboard-clients .one-list-message .message-date{position:relative;width:auto;right:auto;left:15px;padding:0 15px}.btn+.dropdown-menu{margin-top:-10px;background:rgba(0,0,0,0.7) !important;padding:0;border:0;right:0;left:auto;min-width:100%}.btn+.dropdown-menu>li>a{padding:5px 10px !important;color:#f0f0f0}.v-txt{-moz-transform:rotate(-90deg);-webkit-transform:rotate(-90deg);-o-transform:rotate(-90deg);position:absolute;top:60px;left:-20px;color:#d8d8d8;font-size:18px;box-shadow:0 0 10px #d8d8d8;padding:0 5px}.full-calendar{padding:25px 0;background:#fcfcfc}.external-event{padding:2px 6px;margin:4px 0;background:#f5f5f5}.external-event:hover{cursor:move;background:#6aa6d6;color:#f8f8f8}#add-new-event{background:#ebebeb;margin-bottom:30px;padding:10px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.modal-backdrop{z-index:2000}.modal{z-index:2001}.fc-event{border:1px solid #6aa6d6;background-color:#6aa6d6}.qq-upload-drop-area{position:absolute;background:#fcfcfc;width:100%;height:100%}.qq-upload-button{float:right;margin:20px 15px 0 0}.qq-upload-list{position:relative;z-index:3;margin:60px 15px 0;padding:0;list-style:none}.qq-upload-list li{position:relative;display:inline-block;padding:15px;margin:15px;border:1px solid #e6e6e6;text-align:center;font-size:12px;background:rgba(245,245,245,0.9)}.qq-upload-settings{opacity:0;visibility:hidden;bottom:0;position:absolute;width:100%;left:0;padding:7px 0;background:#FFF;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-transition:.2s;-moz-transition:.2s;-o-transition:.2s;transition:.2s}.qq-upload-list li:hover .qq-upload-settings{opacity:1;visibility:visible}.qq-upload-list li img{border:1px solid #b4b4b4;margin-bottom:5px}.qq-upload-filename{display:block;overflow:hidden}.qq-upload-file,.qq-upload-size,.qq-upload-status-text{display:block}.qq-dropped-zone{position:absolute;top:5%;left:50%;margin-left:-71px;text-align:center;font-weight:bold}.qq-dropped-zone i{font-size:5em;display:block;color:#f5f5f5;text-shadow:0 -1px 1px #d8d8d8}#page-500 h1,.page-404 h1{font-size:5em}.page-404 .form-inline{margin:40px auto;width:60%;padding:15px;background:#fafafa;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.page-404 .input-group-btn:last-child>.btn,.page-404 .input-group-btn:last-child>.btn-group{margin-left:-1px;margin-bottom:0;height:39px}#page-500 h3,.page-404 h3{margin:5px 0 20px}.preloader{position:absolute;width:100%;height:100%;left:0;background:#ebebeb;z-index:2000}.devoops-getdata{position:absolute;top:25px;left:15px;color:#ebebeb}#page-500,#page-login{position:absolute;height:100%;width:100%}#page-500{background:#ebebeb}#page-500 img{display:block;margin:30px auto}#page-login .logo{position:absolute}#page-login h3{font-size:20px;font-family:'Righteous',cursive}#page-login .text-right{margin-top:15px}#page-login .box{margin-top:15%}.one-result{margin-top:20px}.one-result p{margin:0}.large{font-size:1.25em}.nav-search>li.active>a{background:#f0f0f0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#525252;border-bottom:1px solid #cecece;font-weight:bold}.page-feed .avatar{width:60px;float:left;margin:10px 15px;text-align:center;overflow:hidden}.page-feed .avatar img{width:60px;height:60px;border:1px solid #f8f8f8}.page-feed-content{position:relative;padding:3px 15px 5px;background:#fcfcfc;margin-left:90px;min-height:80px}.page-feed-content small.time{font-style:italic}.page-feed .page-feed-content:before{font-family:FontAwesome;content:"\f0d9";position:absolute;left:-10px;top:15px;font-size:30px;color:#fcfcfc}.likebox{overflow:hidden}.likebox .navbar-nav{margin:0}.likebox .navbar-nav li{margin-right:15px;float:left}.likebox .fa-thumbs-up{color:#6aa6d6}.likebox .fa-thumbs-down{color:#d15e5e}#modalbox{display:none;position:fixed;overflow:auto;overflow-x:hidden;top:0;right:0;bottom:0;left:0;z-index:5000;background:rgba(0,0,0,0.8)}#modalbox .devoops-modal{position:absolute;top:90px;margin-left:-300px;left:50%;border:1px solid #f8f8f8;box-shadow:0 0 20px #6aa6d6;background:transparent;margin-bottom:20px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;width:600px;z-index:6000}#modalbox .devoops-modal-header{color:#363636;font-size:16px;position:relative;overflow:hidden;background:#f5f5f5;border-bottom:1px solid #e4e4e4;height:28px}#modalbox .devoops-modal-inner{position:relative;overflow:hidden;padding:15px;background:#fcfcfc}#modalbox .devoops-modal-bottom{position:relative;overflow:hidden;padding:15px;background:#d8d8d8}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:4px 10px;margin-left:-1px;line-height:1.428571429;color:#969696;text-decoration:none;background-color:#f5f5f5;border:1px solid #d8d8d8}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#8a8a8a;background-color:#eee}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#979797;cursor:not-allowed;background-color:#fcfcfc;border-color:#d8d8d8}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#6aa6d6;border-color:#6aa6d6}.fancybox-nav{position:fixed;width:50%}.fancybox-close{position:fixed;top:20px;right:36px;background:url(../img/times.png) 0 0 no-repeat}.fancybox-prev span{left:21px;background:url(../img/chevron-left.png) 0 0 no-repeat}.fancybox-next span{right:36px;background:url(../img/chevron-right.png) 0 0 no-repeat}#social a{margin:10px 3px;color:#666;display:block;float:left}#event_delete{margin-left:20px}@media(min-width:768px){#sidebar-left.col-sm-2{opacity:1;width:16.666666666666664%;padding:0 15px}.sidebar-show #sidebar-left.col-sm-2{opacity:0;width:0;padding:0}.sidebar-show #content.col-sm-10{opacity:1;width:100%}.page-404 .form-inline{width:60%}}@media(min-width:992px){.nav.main-menu>li>a,.nav.msg-menu>li>a{text-align:left}.nav.main-menu>li>a>i,.nav.msg-menu>li>a>i{font-size:14px;width:20px;display:inline-block}.main-menu .dropdown-menu{position:relative;z-index:inherit;left:0;margin:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;background:rgba(0,0,0,0.2);visibility:visible}.main-menu .dropdown-menu>li>a{-webkit-border-radius:0 !important;-moz-border-radius:0 !important;border-radius:0 !important}.page-404 .form-inline{width:40%}}@media(max-width:767px){#main{margin-top:100px}#messages-menu{top:140px}.page-404 .form-inline{width:100%}#dashboard_links .nav{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}#dashboard_links .nav-stacked>li{float:left}#dashboard_links .nav-pills>li>a{padding:15px}#dashboard_links .nav-pills>li.active>a:before{bottom:0;left:50%;margin-left:-9px}}@media(max-width:620px){.user-mini{display:none}}@media(max-width:400px){.panel-menu a.account{padding:5px 0 5px 0}.avatar{margin:0}.panel-menu i.pull-right{margin-left:0}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background:0}#dashboard_links .nav-stacked>li{float:none}#dashboard_links .nav-pills>li.active>a:before{display:none}}#chat {width: 100%;height: 25em;overflow: auto;margin-bottom: 15px} diff --git a/plugins/web_gui/static/fonts/glyphicons-halflings-regular.eot b/plugins/web_gui/static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..423bd5d3a20b804f596e04e5cd02fb4f16cfcbc1 GIT binary patch literal 20290 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/web_gui/static/fonts/glyphicons-halflings-regular.ttf b/plugins/web_gui/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a498ef4e7c8b556fc36f580c5ff524025bb11c84 GIT binary patch literal 41236 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 VT!1}Ptu549TOm(@MS}nTO#rGvU~d2b literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/images/bullet.gif b/plugins/web_gui/static/images/bullet.gif new file mode 100644 index 0000000000000000000000000000000000000000..c301719d7018b2c988b964bdbd3c8d96108e2e79 GIT binary patch literal 281 zcmZ?wbhEHbR9%a z?XESe#DDzyyzzATpT9qwHYhDR6+Hih|I90nK zLTlqXh10heFFBYn`-o@B5}u#GzkdJu;q2WNO>0#4TGf)F4{$v3g zqXQyAeqvxVbC^=#p(E9QVo8yNmWXB_Pg=5+mXJD&hl0pNEkVxo4-Te$6V4ym#K7Ur S#-?Z>#bhPkpC-w|U=0A_*%WBc~)&z?Q|{{8#gw{Op$J-c(~P9Gni zef##^zI}W8^yz2LoY}Eshp(^i{rmT)O`G=P#}A-h20}pbCkrD3g9w8TNFm5h2G(8$ z)jkK#9t}>OHv!8Q9S|!y$hGovYCM5*{6@|7v)oXNgSSs0g8QIxc_>)Dvc({ct*?HMH`TV;W u`8haSTRC}Hriw64pUKI|%|G8&c@Y=Oy7?srT?||T+w?cNsTn9TSOWm6w_FSW literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/images/comment.gif b/plugins/web_gui/static/images/comment.gif new file mode 100644 index 0000000000000000000000000000000000000000..951082f93a4bb0b7401e9cabdaaea9606cadb3f2 GIT binary patch literal 364 zcmZ?wbhEHb6krfwxT?&!0cPef##kd-qeZ`j)~xC4>$`UC+N4R7+S=N_eEG6t$Bw5@pPoN|{?VgH4<0-?apJ_|$B*yd zzkl%H!QS59zkmN$RaM=(b?e8EA9Lo+$Z8A}F?QAS8T!J3FOpN6dCh_oaF;8P^ zk!qjB&%-}Ic{>2~v3&%M* zj#H=+M93LVhU3T==s#=v{H!aROeP3|0G{VTkZvs&3mDGktXZs9gwt6xwY1hS!Wp;X z^?K3M(*s$SAxRPffq-rq&hjvRcb|gZD=8zFjVsL0IBjihh(sbtrBVoo!?4+Gh{xkP zE;P3wJ~{zOQDHvM$8xg9G)ALQB$G)5gF&5JC=?Kj#gNTrX?-4j18Iys-9~hHv~Ev~ zZ#J8e$zAo3#Y~0@2gteg3#i89uI=H%w(=K}(Mey}1a#RK7?>VxzCA7BvVU^v0R$jm6nz$D1XEXer(2tyD9BM<;x zi2$rDoM<9IIRT&$Gm1K4#=yiRK@=g78UJrF@BkGu2{H>Z*fYE>*}NdEeL{4D*6D@| L2rIxW{{J@tj9Vv} literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/images/page.gif b/plugins/web_gui/static/images/page.gif new file mode 100644 index 0000000000000000000000000000000000000000..0c49ba877f0cdd497b308ee97f14aea8a7ea2448 GIT binary patch literal 393 zcmZ?wbhEHb6krfwxT?nR|NsA6w{AUr`0&S%A6KtleevSO!Gi}se*F0L>({+|_Z~QK z;NHD^ZEbB86%|jOJlVQ+>*L3dPn*L3dfBN+4%aH)={EQ|~cY79Cc zBSC&*U|Z(UP~agmtFOUd!QnsyPk)9dlLMO_yNj5hm5h_1o2Y<=$hq{6!-gF#tu2lR z7p#ze(Ol!)F>$>jQ)8nDb2Wp4G^-?6IhPnGCu6G z_|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFe_z-M3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWv2L<~p`n7AnVzAE zshOFfj)IYap^?4;5Si&3npl~dSs9rtK!Fm_wxX0Ys~{IQs9ivwtx`rwNr9EVetCJh zUb(Seeo?xx^|#0uTKVr7^KE~&-IMVSR9nfZANAQKal@=Hr> zm4GgVcpgi&u1T;Y}Gc(1?$=J}u$;8Rn(#aec>aNC)29{=)hHe(dmc~Y= zE*2Iry)OC5rManjB{01y2)!ma^@5T@ZUN9{m(-%nveXo}qWoM1u$Qeeal6G3r+HAl zDY)HYgj26R&@uX;h((HMm=G}afSB-P3*^8Perg^twHE=Cb=kgG>lqjr&v?2xhE&{o zGQ-#Fu!G3)&kMIsTN1hRBZpINHN)NpYxA}999{^rxPD;CV`=8dTXmNs@5o&7fY)9z zFIGuU|GS`tW9|P&ou(USs%LuYSbVsB#%FT;{n<139GT;{{FsgD=^2}+O`X8)cR;#= zLHdgPtW8svdSps&akRbmo%Nf~=M+cjE9pJ@P1D}g zac|;0!lCM_c5hnF?J!}bHHwPGtXV7{Pp?rvGSN~;towT;*TxjDrIY+m)v?Qp zS2}xHq_d9X_0rmNI}i2>c(ZLt*&!%obbZ0BsXXdVJ5K~CPnpdV`a(lrGVA9?&l$zb zoZGa%IL^4tFm+E0-vI}s*$J+%S)nQ^>O%7$fQ6ZB8mTU_+wojY0HPNHfB=ps?0F5mrxxZC*E%3Au3Y#+j zTgBxic>)_VW>50C`|PjQ+$sCG|Lpv`|Hq94m#>#CBSfb&Ecdv+aa!*B3)2ot{a~@p iWq-W?f73VJ0}Kb{-&m}6D&GMr>pWfkT-G@yGywnuqwe(p literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/chevron-right.png b/plugins/web_gui/static/img/chevron-right.png new file mode 100644 index 0000000000000000000000000000000000000000..61de8865bf010a083e22408c727adbd9626c1195 GIT binary patch literal 1442 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFe_z-M3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWv2L<~p`n7AnVzAE zshOFfj)IYap^?4;5Si&3npl~dSs9rtK!Fm_wxX0Ys~{IQs9ivwtx`rwNr9EVetCJh zUb(Seeo?xx^|#0uTKVr7^KE~&-IMVSR9nfZANAQKal@=Hr> zm4GgVcpgi&u1T;Y}Gc(1?$<@HZ+{n$?(#aec>aNC4rmn`WrcOqd&PHzL zMoy+My)OC5rManjB{01y2)!ma^@5T@ZUN9{m(-%nveXo}qWoM1u$Qeeal6G3r+HAl zDY)HYgj26R&@uX;h((HMm=G}afSB-P3*^8Perg^twHE=C_2X4f@);PI#5`RbLn>~) znQ_{SDNy3Ltf`bs!5)_i@63y<#5r!2-@VH165_^xVV&!Gfs&isIGxNJqXKr{Q(WYm zcdWOospH|=|DA>|&p-9fn;BE`;P{I4XCFRW-#=6HoMUg?c|+f`FK29W6OCfxf53dF zsd9A-%U;dkU(*iSPGH+*caTrx6`vkQP3x|^c`WVE6(u)Kx%~2pb?St>>_U^n6giC) z)?7Sq-!7miXi?(P#KW=rYq9vD{d<^o*K$a>c2x(N&-N|5!8ZLsuE8}Azs^M`!dG0( z_+sCu`t)PP53Z}PzaCs4{h&)@D|gEJ>!)qo*-kczWC#~&@haUZUJ<=;IkU9#GQ zH0PgJFBV=EwmK?S`oS!P&;(0W&q-%oR|Ktm5xbi&Z$(g}gTu4A&WCN9ln)ha9h?{Z zw8%1NYe4*~2^yt|;kI8`&T%|nJH=~hq;<=q2Q3A0h9X?Gk-A4xj1In#$7`fm?!ySuq&eBthPMJh%IrmP;H4;&jFPs&n)2KdmA&2aP2^+lICOH?Eoszib zJl8KhG}0~mE?fh}=O2-9li}do_0=g5JDyQy?KbBQhdgg#u3BO1K&%J}Q z4t9NGcd|8YN=Rxwxh+BLptSb(|0&kb>e+eUMmI1rB!9Ag94~OD0aR*wy85}Sb4q9e E05!%6pa1{> literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/devoops_getdata.gif b/plugins/web_gui/static/img/devoops_getdata.gif new file mode 100644 index 0000000000000000000000000000000000000000..891304ad26e0ec88696ef75a995abd2d9b6c582d GIT binary patch literal 2892 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 h(J5^}y^tW9gzF|Zu6%!1{fY5^_cK-kzHu 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 Sp#SgRYMN@gs#VIium20kP#JFk literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/logo-200.png b/plugins/web_gui/static/img/logo-200.png new file mode 100644 index 0000000000000000000000000000000000000000..551b91c8b7c5ed9ed44574a6a3c5f2e35aa69f9b GIT binary patch literal 2872 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 OH!?dht-iWF|L_m~ zH$YKTqByCQ$#{l*L-Bs{|GgtzB*R&pui_b0$4$eB)P#j<5ELC_4wj)|EiZnAV-yvm z?Mf9_^(omz4r4?ZCUiW)rl_&;&@;?=h(Qg`*{(wWeEW_Dwx!Us8J*KT6*lb26(7#5 zOjpd6c~i3J@ylQ=l!<@?u>nG7!42e4pl_ZE{uDwN*kqaV4qlT8SLX%7nj^KD*SvP2xi4=?t z4Ugp+E;>>NsO$fSI!+fI;4(bS_n*Q+W!Zyl83t&{H%a5_!%-+tR()t-OYsSZ~|=8^+l+~Hb>+-y&7WCh0~ku~Vsx1d$Fz) zGoD>P_H=7|JO1M3-6zM_p090g-+uU|Qu?vQ4SBKMYmc}KdqX!)iVxh8_|YS~?D_sX zR4woaKS1v575wdSZRE{+FE*MvUH_$8u~mM3QFwMDq4pgaq^{rpL@D1lxacw#^V8a^ I+|7G`0IGFBUjP6A literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/sort.png b/plugins/web_gui/static/img/sort.png new file mode 100644 index 0000000000000000000000000000000000000000..337ff81f9083e87e82f6d3f62fe64edacf629b96 GIT binary patch literal 1060 zcmaJ=TSyd97#;|_7>0=k_B4(tO6|;?y}2{EiM!)v%Q0COTzbiI=IBm3bHmf?0CW|&TCpKjuc0nT#|)(h#-XAO_P>C& z6t#!Z6G@zuTSOHZtm0zWjA0QrMb*}2EJf{wnC^l-x*29RmY*=Ru7#P7fXvHQ6sGj% zAse<2wI?d5@&%JToAhOvWoa2)RD`%hsfF=#<94jnXLtE6$=Cm#t!33Ss1=kVQw^gLGAgZfYp)RBz&CS&W$uR!kMfq%cDiSY6jd$rlI#zYrAs zyZ}IhFX9b_gs3;<^8r5qk@_rGLh674O`PRwyWE_ZPqJ zELekcFW;?1E`BsFADo}{fvz~a2>NIJmBUw0O^IbcZ{5Cm=h;S4!vq-PDnd^y2U@CD z&y~j3^M2&DM5d<~r^c+i3&|Dk-r1?sbC0XowMo9||k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFe_z-M3hAM`dB6B=jtVb)aX^@765fKFxc2v6eK2Rr0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWv2L<~p`n7AnVzAE zshOFfj)IYap^?4;5Si&3npl~dSs9rtK!Fm_wxX0Ys~{IQs9ivwtx`rwNr9EVetCJh zUb(Seeo?xx^|#0uTKVr7^KE~&-IMVSR9nfZANAQKal@=Hr> zm4GgVcpgi&u1T;Y}Gc(1?+1%35!qvdo(#aec>aNC4POh#NCKit7F2-&K zZm#Aqy)OC5rManjB{01y2)!ma^@5T@ZUN9{m(-%nveXo}qWoM1u$Qeeal6G3r+HAl zDY)HYgj26R&@uX;h((HMm=G}afSB-P3*^8Perg^twHE=Cb$nvVF9rrC4o?@ykcwMx zrf>8*>>zP`_O&1l#VKVg?lw(M5dUggVi*xq%j{b4ol$GYO8yIcLE`L=;q3Pn{4On& zk_qz8;a+O;wf-Sf<_XpE%n3@CKMHg8uK)S*?%nU8OuPB%VJlOOwXX#&40*A><=X+} z9ju{^_op9JtzbGHV|-#G|DkQU)0ICk+G*-9cqN&x*vx+3=8)v;N~A<#X9DcMN1B)+$o!Q{&{{&i`wLq#=F*b zh<^9}cw$1DC|B#*1&yZHc-EBnJ7+MLthmw<+_q)jhe^Echf7PoJ3Osm=)7XSyoZx7 zHL^}vXp>HNiK!H$=|U6d6sfL7FAfPx@vyx+zGR8a%%GJ^+V36_dG-DGz4fQPERCKP zMfT5KS<88vLF$E@>NWwmdNE^J#9~3;X@c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDkcW@2jT>SAncX>Mw6Xy|I}Xl`cgW@+kb=xAnU zW^CXF)9aF-T$-DjR|3v4~Pj*wm=R%;iu*SQ+p9GSs&<Gadh9E-^t^<0ENqH;M>Qr{tE!ekIgUx|glO^b)2Uk3kA-mpL z3AP6YERsG9tKyr?Wf?@jFt1FFF!6V}&eQKE6Bx#FBz=K}*mNtmno7nOvi1sJSy!aV h`oLWN<6$z**UXRGo_2AxP?SCrm+E?LY7c zR#B9P*3ucAk#-6S(gU)K2{^h**c4S&U$GUKpI zYaFm(>p*i_8R%A`DqT|zsvLm`=n%`m(R&PAaANeFu0ZDQHba9s2=0#2^GRi-77#}k z1d%|&rKZ0&9 z${O}UnMlDHM>ZKY5Oj}}14`2Wp}M|^ws8vn$@ibacDmn$ObXhl*HTF1I!fJ8rVzKF zjFFW_s3%v&mM(;m-GxjLZ{w{{8g>*1? zWI@ZH3!7JOfve`vh0DujNHUDu*uNT`+am69r;D~Fi$&u@gSg!ywjMrr`zyH;50heD z+IiXjV|KR2vvqLA@PnzTt4k(d4LX9?Uae52H;V^Hrak3*%f7AgzN6mUTm7*B+&VEi z?U^aBjo(hPVHq)$ literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/ui-left.png b/plugins/web_gui/static/img/ui-left.png new file mode 100644 index 0000000000000000000000000000000000000000..dd7e49f84f09395118a1d96fd1197b90a03b128d GIT binary patch literal 1085 zcmaJ=U1-x#7)=?IIT&s*)QR9VMO3g!)3vb;jqBQUwrb|=3acn%X>zxQEx9qdwcVJa zPR9B$@Ig=%L}k7T6QA_K3H1+@DLzen7JO3JYh~VS>-51IlH4E8Ip24`@7&5j|M9lg zJ*^Z)wWU*1hK%j**RqZL_pPoeWaz@l93DiYxTM&S>eWylg0!KG!3842kc>aP0x+c;?A=xim30TlmQ#Kr& z>d&fE<7z~sdyau^=-x?E0Hc>YzZt|DwUX0 zH-qdkmWxCpu0|l>BM6@}X=0`9GaYYTL4uBI>lW6L30y@bkBV5NNv0b|FsvxQy~gbfvp>?}eP z^(qb&5JpY`Ss*dUgF{2QsiBf{cpWdxLfUk&VyZALi8N7QbX^l7JTJxL34i2hfakbm zcRZZr!$FP@^9imm%!le+38_T`nz+u@{&2a5T-OSQMIuYk)-OV>&qfAVKeC`V%q5;| zsJF@08s-x3Ysh6uGOXL!zZzZNBJOdgTec;OE#pIzxZNhUe)-D#lU#|#wA7m|f63R@ z)_R%_T&bjrE3xU8xtWg#c2}%kPy3tL%G!h3=xq8)`^WPw)laF~tyt||j=evA*;D-< zt2LkPV0LwSE_CkvaJsTxdKgl>mv+djC#UZ$cKvLgU-neLE-cJkibXpr%R8!{ujb}& zUVnPKGhrpYZ*Ocb%)Y1c#mrzn7!;S54i-+-ViUu;*6Od+Gy}d#XFRs2BiuPR)U+r* f+v|O_(gl{N^RLg{9cK5BxEDH|?3W(LN3Q(_SX^K- literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/img/ui-right.png b/plugins/web_gui/static/img/ui-right.png new file mode 100644 index 0000000000000000000000000000000000000000..dba43d67926337a853f43b93e74864aba012daa8 GIT binary patch literal 1104 zcmaJ=TWHfz7>?FW-9@UP&I_1jh#T0XN!QGVb!*qOT4v_x3id(4G&x(tmYkTJ*=!&b zQQQWCh&MLyO?^?TZ&L?~^hHomlsPXCGJNty)E5P1p3HUnU=2ymh41_R|NQ@V#(R7A z*4J#UAqb*Axlc^t(d&L|s_}ov*9935TTncM`d~lG%N8J_D$D{hsmlW(1!Q&P=wr}M z5FScPXHZ7kFDOu_Wfwy`x{28Y(cbBpvN8w|nFRxy5vD%fe?XC%8m0~gCBJ0GKu+5? zYJtAdp0qMLsDxCivx97R1T3HfB$JLlWY~ferWSPtJa@Mlid=-C!7%kZsf^T1#-Ihr zARX{2ewHP9o@QHvJkRbXIX}xWe*E%2wp9r70@p?^Jrs^+sr^DqOf1F1t1y*A$P^eR zpU>0z01d4Hh7E;6t_H{XFv4e#7)W+}hTT+F5P_{&nu#=MkglSfg~KRJ;Y@!YK{uCW z4ST6fxL}MUn+!|)-6NHOlJtM5t}ml)lmdV9{im><9x(xv0yZ4B6x_J}CO4ER#4I2q zXr&<>Dp#>L2NAS$&?IAhJlT9uGgO$jTNd$>BqR+R$%X=wVwl1Tw5F+o$cMyeA{Yp@ zaXib$10j)(#e#`ww5`?Oy=yM=9i^xTyT_)3(LVl?f% z$^KYa==AI?tUXg)QF>c&1h2hbA(|#@XFYdzpFh5K%r1P})%6bLgT$KK zD_Lf8t}Rb!{;hb9gL8VV_D!^Nukc;sDOX((r9*ruzmw~!Bs)y2bw?&9-V_l_syJ>spdBPV_V DZ>4B< literal 0 HcmV?d00001 diff --git a/plugins/web_gui/static/index.html b/plugins/web_gui/static/index.html new file mode 100644 index 0000000..fe12fa9 --- /dev/null +++ b/plugins/web_gui/static/index.html @@ -0,0 +1,126 @@ + + + + + StarryPy + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ preloader +
+
+
+ +
+
+ + + + + + + + diff --git a/plugins/web_gui/static/js/bootstrap-sortable.js b/plugins/web_gui/static/js/bootstrap-sortable.js new file mode 100644 index 0000000..d23d884 --- /dev/null +++ b/plugins/web_gui/static/js/bootstrap-sortable.js @@ -0,0 +1,129 @@ +/* TINY SORT modified according to this https://github.com/Sjeiti/TinySort/pull/51*/ +(function (e, t) { function h(e) { return e && e.toLowerCase ? e.toLowerCase() : e } function p(e, t) { for (var r = 0, i = e.length; r < i; r++) if (e[r] == t) return !n; return n } var n = !1, r = null, i = parseFloat, s = Math.min, o = /(-?\d+\.?\d*)$/g, u = /(\d+\.?\d*)$/g, a = [], f = [], l = function (e) { return typeof e == "string" }, c = Array.prototype.indexOf || function (e) { var t = this.length, n = Number(arguments[1]) || 0; n = n < 0 ? Math.ceil(n) : Math.floor(n); if (n < 0) n += t; for (; n < t; n++) { if (n in this && this[n] === e) return n } return -1 }; e.tinysort = { id: "TinySort", version: "1.5.2", copyright: "Copyright (c) 2008-2013 Ron Valstar", uri: "http://tinysort.sjeiti.com/", licensed: { MIT: "http://www.opensource.org/licenses/mit-license.php", GPL: "http://www.gnu.org/licenses/gpl.html" }, plugin: function () { var e = function (e, t) { a.push(e); f.push(t) }; e.indexOf = c; return e }(), defaults: { order: "asc", attr: r, data: r, useVal: n, place: "start", returns: n, cases: n, forceStrings: n, ignoreDashes: n, sortFunction: r } }; e.fn.extend({ tinysort: function () { var d, v, m = this, g = [], y = [], b = [], w = [], E = 0, S, x = [], T = [], N = function (t) { e.each(a, function (e, n) { n.call(n, t) }) }, C = function (t, r) { var s = 0; if (E !== 0) E = 0; while (s === 0 && E < S) { var a = w[E], c = a.oSettings, p = c.ignoreDashes ? u : o; N(c); if (c.sortFunction) { s = c.sortFunction(t, r) } else if (c.order == "rand") { s = Math.random() < .5 ? 1 : -1 } else { var d = n, v = !c.cases ? h(t.s[E]) : t.s[E], m = !c.cases ? h(r.s[E]) : r.s[E]; v = v.replace(/^\s*/i, "").replace(/\s*$/i, ""); m = m.replace(/^\s*/i, "").replace(/\s*$/i, ""); if (!A.forceStrings) { var g = l(v) ? v && v.match(p) : n, y = l(m) ? m && m.match(p) : n; if (g && y) { var b = v.substr(0, v.length - g[0].length), x = m.substr(0, m.length - y[0].length); if (b == x) { d = !n; v = i(g[0]); m = i(y[0]) } } } s = a.iAsc * (v < m ? -1 : v > m ? 1 : 0) } e.each(f, function (e, t) { s = t.call(t, d, v, m, s) }); if (s === 0) E++ } return s }; for (d = 0, v = arguments.length; d < v; d++) { var k = arguments[d]; if (l(k)) { if (x.push(k) - 1 > T.length) T.length = x.length - 1 } else { if (T.push(k) > x.length) x.length = T.length } } if (x.length > T.length) T.length = x.length; S = x.length; if (S === 0) { S = x.length = 1; T.push({}) } for (d = 0, v = S; d < v; d++) { var L = x[d], A = e.extend({}, e.tinysort.defaults, T[d]), O = !(!L || L == ""), M = O && L[0] == ":"; w.push({ sFind: L, oSettings: A, bFind: O, bAttr: !(A.attr === r || A.attr == ""), bData: A.data !== r, bFilter: M, $Filter: M ? m.filter(L) : m, fnSort: A.sortFunction, iAsc: A.order == "asc" ? 1 : -1 }) } m.each(function (n, r) { var i = e(r), s = i.parent().get(0), o, u = []; for (j = 0; j < S; j++) { var a = w[j], f = a.bFind ? a.bFilter ? a.$Filter.filter(r) : i.find(a.sFind) : i; u.push(a.bData ? f.data(a.oSettings.data) : a.bAttr ? f.attr(a.oSettings.attr) : a.oSettings.useVal ? f.val() : f.text()); if (o === t) o = f } var l = c.call(b, s); if (l < 0) { l = b.push(s) - 1; y[l] = { s: [], n: [] } } if (o.length > 0) y[l].s.push({ s: u, e: i, n: n }); else y[l].n.push({ e: i, n: n }) }); e.each(y, function (e, t) { t.s.sort(C) }); e.each(y, function (t, r) { var i = r.s.length, o = [], u = i, a = [0, 0]; switch (A.place) { case "first": e.each(r.s, function (e, t) { u = s(u, t.n) }); break; case "org": e.each(r.s, function (e, t) { o.push(t.n) }); break; case "end": u = r.n.length; break; default: u = 0 } for (d = 0; d < i; d++) { var f = p(o, d) ? !n : d >= u && d < u + r.s.length, l = (f ? r.s : r.n)[a[f ? 0 : 1]].e; l.parent().append(l); if (f || !A.returns) g.push(l.get(0)); a[f ? 0 : 1]++ } }); m.length = 0; Array.prototype.push.apply(m, g); return m } }); e.fn.TinySort = e.fn.Tinysort = e.fn.tsort = e.fn.tinysort })(jQuery); + +(function ($) { + + var $document = $(document), + bsSort = [], + lastSort, + signClass; + + $.bootstrapSortable = function (applyLast, sign) { + + // check if moment.js is available + var momentJsAvailable = (typeof moment !== 'undefined'); + + //Set class based on sign parameter + signClass = !sign ? "arrow" : sign; + + // set attributes needed for sorting + $('table.sortable').each(function () { + var $this = $(this); + applyLast = (applyLast === true); + $this.find('span.sign').remove(); + $this.find('thead tr').each(function (rowIndex) { + var columnsSkipped = 0; + $(this).find('th').each(function (columnIndex) { + var $this = $(this); + $this.attr('data-sortcolumn', columnIndex + columnsSkipped); + $this.attr('data-sortkey', columnIndex + '-' + rowIndex); + if ($this.attr("colspan") !== undefined) { + columnsSkipped += parseInt($this.attr("colspan")) - 1; + } + }); + }); + $this.find('td').each(function () { + var $this = $(this); + if ($this.attr('data-dateformat') != undefined && momentJsAvailable) { + $this.attr('data-value', moment($this.text(), $this.attr('data-dateformat')).format('YYYY/MM/DD/HH/mm/ss')); + } + else { + $this.attr('data-value') === undefined && $this.attr('data-value', $this.text()); + } + }); + $this.find('thead th[data-defaultsort!="disabled"]').each(function (index) { + var $this = $(this); + var $sortTable = $this.closest('table.sortable'); + $this.data('sortTable', $sortTable); + var sortKey = $this.attr('data-sortkey'); + var thisLastSort = applyLast ? lastSort : -1; + bsSort[sortKey] = applyLast ? bsSort[sortKey] : $this.attr('data-defaultsort'); + if (bsSort[sortKey] != null && (applyLast == (sortKey == thisLastSort))) { + bsSort[sortKey] = bsSort[sortKey] == 'asc' ? 'desc' : 'asc'; + doSort($this, $sortTable) + } + }); + $this.trigger('sorted'); + }); + }; + + // add click event to table header + $document.on('click', 'table.sortable thead th[data-defaultsort!="disabled"]', function (e) { + var $this = $(this), $table = $this.data('sortTable') || $this.closest('table.sortable'); + doSort($this, $table); + $table.trigger('sorted'); + }); + + //Sorting mechanism separated + function doSort($this, $table) { + var sortColumn = $this.attr('data-sortcolumn'); + + var colspan = $this.attr('colspan'); + if (colspan) { + var selector; + for (var i = parseFloat(sortColumn) ; i < parseFloat(sortColumn) + parseFloat(colspan) ; i++) { + selector = selector + ', [data-sortcolumn="' + i + '"]'; + } + var subHeader = $(selector).not('[colspan]'); + var mainSort = subHeader.filter('[data-mainsort]').eq(0); + + sortColumn = mainSort.length ? mainSort : subHeader.eq(0); + doSort(sortColumn, $table); + return; + } + + //sortColumn = newColumn ? newColumn : sortColumn; + + var localSignClass = $this.attr('data-defaultsign') || signClass; + // update arrow icon + if ($.browser.mozilla) { + var moz_arrow = $table.find('div.mozilla'); + if (moz_arrow != null) { + moz_arrow.parent().html(moz_arrow.text()); + } + $this.wrapInner('
'); + $this.children().eq(0).append(''); + } + else { + $table.find('span.sign').remove(); + $this.append(''); + } + + // sort direction + + var sortKey = $this.attr('data-sortkey'); + + lastSort = sortKey; + bsSort[sortKey] = bsSort[sortKey] == 'asc' ? 'desc' : 'asc'; + if (bsSort[sortKey] == 'desc') { $this.find('span.sign').addClass('up'); } + + // sort rows + var rows = $table.find('tbody tr'); + rows.tsort('td:eq(' + sortColumn + ')', { order: bsSort[sortKey], attr: 'data-value' }); + } + + // jQuery 1.9 removed this object + if (!$.browser) { + $.browser = { chrome: false, mozilla: false, opera: false, msie: false, safari: false }; + var ua = navigator.userAgent; + $.each($.browser, function (c) { + $.browser[c] = ((new RegExp(c, 'i').test(ua))) ? true : false; + if ($.browser.mozilla && c == 'mozilla') { $.browser.mozilla = ((new RegExp('firefox', 'i').test(ua))) ? true : false; } + if ($.browser.chrome && c == 'safari') { $.browser.safari = false; } + }); + } + + // Initialise on DOM ready + $($.bootstrapSortable); + +}(jQuery)); diff --git a/plugins/web_gui/static/js/devoops.min.js b/plugins/web_gui/static/js/devoops.min.js new file mode 100644 index 0000000..be8b8dc --- /dev/null +++ b/plugins/web_gui/static/js/devoops.min.js @@ -0,0 +1 @@ +"use strict";function LoadCalendarScript(callback){function LoadFullCalendarScript(){if(!$.fn.fullCalendar)$.getScript('plugins/fullcalendar/fullcalendar.js',callback);else if(callback&&typeof callback==="function")callback();}if(!$.fn.moment)$.getScript('plugins/moment/moment.min.js',LoadFullCalendarScript);else LoadFullCalendarScript();}function LoadOpenLayersScript(callback){if(!$.fn.OpenLayers)$.getScript('http://www.openlayers.org/api/OpenLayers.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadTimePickerScript(callback){if(!$.fn.timepicker)$.getScript('plugins/jquery-ui-timepicker-addon/jquery-ui-timepicker-addon.min.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadBootstrapValidatorScript(callback){if(!$.fn.bootstrapValidator)$.getScript('plugins/bootstrapvalidator/bootstrapValidator.min.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadSelect2Script(callback){if(!$.fn.select2)$.getScript('plugins/select2/select2.min.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadDataTablesScripts(callback){function LoadDatatables(){$.getScript('plugins/datatables/jquery.dataTables.js',function(){$.getScript('plugins/datatables/ZeroClipboard.js',function(){$.getScript('plugins/datatables/TableTools.js',function(){$.getScript('plugins/datatables/dataTables.bootstrap.js',callback);});});});}if(!$.fn.dataTables)LoadDatatables();else if(callback&&typeof callback==="function")callback();}function LoadFineUploader(callback){if(!$.fn.fineuploader)$.getScript('plugins/fineuploader/jquery.fineuploader-4.3.1.min.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadXChartScript(callback){function LoadXChart(){$.getScript('plugins/xcharts/xcharts.min.js',callback);}function LoadD3Script(){if(!$.fn.d3)$.getScript('plugins/d3/d3.v3.min.js',LoadXChart);else LoadXChart();}if(!$.fn.xcharts)LoadD3Script();else if(callback&&typeof callback==="function")callback();}function LoadFlotScripts(callback){function LoadFlotScript(){$.getScript('plugins/flot/jquery.flot.js',LoadFlotResizeScript);}function LoadFlotResizeScript(){$.getScript('plugins/flot/jquery.flot.resize.js',LoadFlotTimeScript);}function LoadFlotTimeScript(){$.getScript('plugins/flot/jquery.flot.time.js',callback);}if(!$.fn.flot)LoadFlotScript();else if(callback&&typeof callback==="function")callback();}function LoadMorrisScripts(callback){function LoadMorrisScript(){if(!$.fn.Morris)$.getScript('plugins/morris/morris.min.js',callback);else if(callback&&typeof callback==="function")callback();}if(!$.fn.raphael)$.getScript('plugins/raphael/raphael-min.js',LoadMorrisScript);else LoadMorrisScript();}function LoadFancyboxScript(callback){if(!$.fn.fancybox)$.getScript('plugins/fancybox/jquery.fancybox.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadKnobScripts(callback){if(!$.fn.knob)$.getScript('plugins/jQuery-Knob/jquery.knob.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadSparkLineScript(callback){if(!$.fn.sparkline)$.getScript('plugins/sparkline/jquery.sparkline.min.js',callback);else if(callback&&typeof callback==="function")callback();}function LoadAjaxContent(url){$('.preloader').show();$.ajax({mimeType:'text/html; charset=utf-8',url:url,type:'GET',success:function(data){$('#ajax-content').html(data);$('.preloader').hide();},error:function(jqXHR,textStatus,errorThrown){alert(errorThrown);},dataType:"html",async:false});}function WinMove(){$("div.box").not('.no-drop').draggable({revert:true,zIndex:2000,cursor:"crosshair",handle:'.box-name',opacity:0.8}).droppable({tolerance:'pointer',drop:function(event,ui){var draggable=ui.draggable;var droppable=$(this);var dragPos=draggable.position();var dropPos=droppable.position();draggable.swap(droppable);setTimeout(function(){var dropmap=droppable.find('[id^=map-]');var dragmap=draggable.find('[id^=map-]');if(dragmap.length>0||dropmap.length>0){dragmap.resize();dropmap.resize();}else{draggable.resize();droppable.resize();}},50);setTimeout(function(){draggable.find('[id^=map-]').resize();droppable.find('[id^=map-]').resize();},250);}});}jQuery.fn.swap=function(b){b=jQuery(b)[0];var a=this[0];var t=a.parentNode.insertBefore(document.createTextNode(''),a);b.parentNode.insertBefore(a,b);t.parentNode.insertBefore(b,t);t.parentNode.removeChild(t);return this;};function ScreenSaver(){var canvas=document.getElementById("canvas");var ctx=canvas.getContext("2d");var W=window.innerWidth;var H=window.innerHeight;canvas.width=W;canvas.height=H;var particles=[];for(var i=0;i<25;i++)particles.push(new Particle());function Particle(){this.location={x:Math.random()*W,y:Math.random()*H};this.radius=0;this.speed=3;this.angle=Math.random()*360;var r=Math.round(Math.random()*255);var g=Math.round(Math.random()*255);var b=Math.round(Math.random()*255);var a=Math.random();this.rgba="rgba("+r+", "+g+", "+b+", "+a+")";}function draw(){ctx.globalCompositeOperation="source-over";ctx.fillStyle="rgba(0, 0, 0, 0.02)";ctx.fillRect(0,0,W,H);ctx.globalCompositeOperation="lighter";for(var i=0;iW)p.location.x=0;if(p.location.y<0)p.location.y=H;if(p.location.y>H)p.location.y=0;}}setInterval(draw,30);}function drawGoogleChart(chart_data,chart_options,element,chart_type){var data=google.visualization.arrayToDataTable(chart_data);var chart=new chart_type(document.getElementById(element));chart.draw(data,chart_options);}function DrawKnob(elem){elem.knob({change:function(value){},release:function(value){console.log("release : "+value);},cancel:function(){console.log("cancel : ",this);},draw:function(){if(this.$.data('skin')=='tron'){var a=this.angle(this.cv);var sa=this.startAngle;var sat=this.startAngle;var ea;var eat=sat+a;var r=1;this.g.lineWidth=this.lineWidth;this.o.cursor&&(sat=eat-0.3)&&(eat=eat+0.3);if(this.o.displayPrevious){ea=this.startAngle+this.angle(this.v);this.o.cursor&&(sa=ea-0.3)&&(ea=ea+0.3);this.g.beginPath();this.g.strokeStyle=this.pColor;this.g.arc(this.xy,this.xy,this.radius-this.lineWidth,sa,ea,false);this.g.stroke();}this.g.beginPath();this.g.strokeStyle=r?this.o.fgColor:this.fgColor;this.g.arc(this.xy,this.xy,this.radius-this.lineWidth,sat,eat,false);this.g.stroke();this.g.lineWidth=2;this.g.beginPath();this.g.strokeStyle=this.o.fgColor;this.g.arc(this.xy,this.xy,this.radius-this.lineWidth+1+this.lineWidth*2/3,0,2*Math.PI,false);this.g.stroke();return false;}}});var v;var up=0;var down=0;var i=0;var $idir=$("div.idir");var $ival=$("div.ival");var incr=function(){i++;$idir.show().html("+").fadeOut();$ival.html(i);};var decr=function(){i--;$idir.show().html("-").fadeOut();$ival.html(i);};$("input.infinite").knob({min:0,max:20,stopper:false,change:function(){if(v>this.cv)if(up){decr();up=0;}else{up=1;down=0;}else if(v1)map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':true}));map.addControl(new OpenLayers.Control.Permalink());map.addControl(new OpenLayers.Control.MousePosition({displayProjection:epsg4326}));return map;}function PrettyDates(){var currDate=new Date();var year=currDate.getFullYear();var month=currDate.getMonth()+1;var startmonth=1;if(month>3)startmonth=month-2;if(startmonth<=9)startmonth='0'+startmonth;if(month<=9)month='0'+month;var day=currDate.getDate();if(day<=9)day='0'+day;var startdate=year+'-'+startmonth+'-01';var enddate=year+'-'+month+'-'+day;return [startdate,enddate];}function SetMinBlockHeight(elem){elem.css('min-height',window.innerHeight-49);}function MessagesMenuWidth(){var W=window.innerWidth;var W_menu=$('#sidebar-left').outerWidth();var w_messages=(W-W_menu)*16.666666666666664/100;$('#messages-menu').width(w_messages);}function DashboardTabChecker(){$('#content').on('click','a.tab-link',function(e){e.preventDefault();$('div#dashboard_tabs').find('div[id^=dashboard]').each(function(){$(this).css('visibility','hidden').css('position','absolute');});var attr=$(this).attr('id');$('#'+'dashboard-'+attr).css('visibility','visible').css('position','relative');$(this).closest('.nav').find('li').removeClass('active');$(this).closest('li').addClass('active');});}function TinyMCEStart(elem,mode){var plugins=[];if(mode=='extreme')plugins=["advlist anchor autolink autoresize autosave bbcode charmap code contextmenu directionality ","emoticons fullpage fullscreen hr image insertdatetime layer legacyoutput","link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace","tabfocus table template textcolor visualblocks visualchars wordcount"];tinymce.init({selector:elem,theme:"modern",plugins:plugins,toolbar:"insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons",style_formats:[{title:'Header 2',block:'h2',classes:'page-header'},{title:'Header 3',block:'h3',classes:'page-header'},{title:'Header 4',block:'h4',classes:'page-header'},{title:'Header 5',block:'h5',classes:'page-header'},{title:'Header 6',block:'h6',classes:'page-header'},{title:'Bold text',inline:'b'},{title:'Red text',inline:'span',styles:{color:'#ff0000'}},{title:'Red header',block:'h1',styles:{color:'#ff0000'}},{title:'Example 1',inline:'span',classes:'example1'},{title:'Example 2',inline:'span',classes:'example2'},{title:'Table styles'},{title:'Table row 1',selector:'tr',classes:'tablerow1'}]});}function SparkLineDrawBarGraph(elem,arr,color){if(color)var stacked_color=color;else stacked_color='#6AA6D6';elem.sparkline(arr,{type:'bar',barWidth:7,highlightColor:'#000',barSpacing:2,height:40,stackedBarColor:stacked_color});}function OpenModalBox(header,inner,bottom){var modalbox=$('#modalbox');modalbox.find('.modal-header-name span').html(header);modalbox.find('.devoops-modal-inner').html(inner);modalbox.find('.devoops-modal-bottom').html(bottom);modalbox.fadeIn('fast');$('body').addClass("body-expanded");}function CloseModalBox(){var modalbox=$('#modalbox');modalbox.fadeOut('fast',function(){modalbox.find('.modal-header-name span').children().remove();modalbox.find('.devoops-modal-inner').children().remove();modalbox.find('.devoops-modal-bottom').children().remove();$('body').removeClass("body-expanded");});}(function($){$.fn.beautyTables=function(){var table=this;var string_fill=false;this.on('keydown',function(event){var target=event.target;var tr=$(target).closest("tr");var col=$(target).closest("td");if(target.tagName.toUpperCase()=='INPUT'){if(event.shiftKey===true)switch(event.keyCode){case 37:col.prev().children("input[type=text]").focus();break;case 39:col.next().children("input[type=text]").focus();break;case 40:if(string_fill==false)tr.next().find('td:eq('+col.index()+') input[type=text]').focus();break;case 38:if(string_fill==false)tr.prev().find('td:eq('+col.index()+') input[type=text]').focus();break;}if(event.ctrlKey===true)switch(event.keyCode){case 37:tr.find('td:eq(1)').find("input[type=text]").focus();break;case 39:tr.find('td:last-child').find("input[type=text]").focus();break;case 40:if(string_fill==false)table.find('tr:last-child td:eq('+col.index()+') input[type=text]').focus();break;case 38:if(string_fill==false)table.find('tr:eq(1) td:eq('+col.index()+') input[type=text]').focus();break;}if(event.keyCode==13||event.keyCode==9){event.preventDefault();col.next().find("input[type=text]").focus();}if(string_fill==false){if(event.keyCode==34){event.preventDefault();table.find('tr:last-child td:last-child').find("input[type=text]").focus();}if(event.keyCode==33){event.preventDefault();table.find('tr:eq(1) td:eq(1)').find("input[type=text]").focus();}}}});table.find("input[type=text]").each(function(){$(this).on('blur',function(event){var target=event.target;var col=$(target).parents("td");if(table.find("input[name=string-fill]").prop("checked")==true)col.nextAll().find("input[type=text]").each(function(){$(this).val($(target).val());});});});};})(jQuery);(function($){$.fn.beautyHover=function(){var table=this;table.on('mouseover','td',function(){var idx=$(this).index();var rows=$(this).closest('table').find('tr');rows.each(function(){$(this).find('td:eq('+idx+')').addClass('beauty-hover');});}).on('mouseleave','td',function(e){var idx=$(this).index();var rows=$(this).closest('table').find('tr');rows.each(function(){$(this).find('td:eq('+idx+')').removeClass('beauty-hover');});});};})(jQuery);function Table2Json(table){var result={};table.find("tr").each(function(){var oneRow=[];var varname=$(this).index();$("td",this).each(function(index){if(index!=0)oneRow.push($("input",this).val());});result[varname]=oneRow;});var result_json=JSON.stringify(result);OpenModalBox('Table to JSON values',result_json);}function FlotGraph1(){var data=[],totalPoints=300;function getRandomData(){if(data.length>0)data=data.slice(1);while(data.length0?data[data.length-1]:50,y=prev+Math.random()*10-5;if(y<0)y=0;else if(y>100)y=100;data.push(y);}var res=[];for(var i=0;iaxes.xaxis.max||pos.yaxes.yaxis.max)return;var i,j,dataset=plot.getData();for(i=0;ipos.x)break;var y,p1=series.data[j-1],p2=series.data[j];if(p1==null)y=p2[1];else if(p2==null)y=p1[1];else y=p1[1]+(p2[1]-p1[1])*(pos.x-p1[0])/(p2[0]-p1[0]);legends.eq(i).text(series.label.replace(/=.*/,"= "+y.toFixed(2)));}}$("#box-two-content").bind("plothover",function(event,pos,item){latestPosition=pos;if(!updateLegendTimeout)updateLegendTimeout=setTimeout(updateLegend,50);});}function FlotGraph3(){var d1=[];for(var i=0;i<=60;i+=1)d1.push([i,parseInt(Math.random()*30-10)]);function plotWithOptions(t){$.plot("#box-three-content",[{data:d1,color:"rgb(30, 180, 20)",threshold:{below:t,color:"rgb(200, 20, 30)"},lines:{steps:true}}]);}plotWithOptions(0);}function FlotGraph4(){var d1=[];for(var i=0;i<14;i+=0.5)d1.push([i,Math.sin(i)]);var d2=[[0,3],[4,8],[8,5],[9,13]];var d3=[];for(var i=0;i<14;i+=0.5)d3.push([i,Math.cos(i)]);var d4=[];for(var i=0;i<14;i+=0.1)d4.push([i,Math.sqrt(i*10)]);var d5=[];for(var i=0;i<14;i+=0.5)d5.push([i,Math.sqrt(i)]);var d6=[];for(var i=0;i<14;i+=0.5+Math.random())d6.push([i,Math.sqrt(2*i+Math.sin(i)+5)]);$.plot("#box-four-content",[{data:d1,lines:{show:true,fill:true}},{data:d2,bars:{show:true}},{data:d3,points:{show:true}},{data:d4,lines:{show:true}},{data:d5,lines:{show:true},points:{show:true}},{data:d6,lines:{show:true,steps:true}}]);}function MorrisChart1(){var day_data=[{"period":"2013-10-01","licensed":3407,"sorned":660},{"period":"2013-09-30","licensed":3351,"sorned":629},{"period":"2013-09-29","licensed":3269,"sorned":618},{"period":"2013-09-20","licensed":3246,"sorned":661},{"period":"2013-09-19","licensed":3257,"sorned":667},{"period":"2013-09-18","licensed":3248,"sorned":627},{"period":"2013-09-17","licensed":3171,"sorned":660},{"period":"2013-09-16","licensed":3171,"sorned":676},{"period":"2013-09-15","licensed":3201,"sorned":656},{"period":"2013-09-10","licensed":3215,"sorned":622}];Morris.Bar({element:'morris-chart-1',data:day_data,xkey:'period',ykeys:['licensed','sorned'],labels:['Licensed','SORN'],xLabelAngle:60});}function MorrisChart2(){Morris.Area({element:'morris-chart-2',data:[{x:'2011 Q1',y:3,z:3,m:1},{x:'2011 Q2',y:2,z:0,m:7},{x:'2011 Q3',y:2,z:5,m:2},{x:'2011 Q4',y:4,z:4,m:5},{x:'2012 Q1',y:6,z:1,m:11},{x:'2012 Q2',y:4,z:4,m:3},{x:'2012 Q3',y:4,z:4,m:7},{x:'2012 Q4',y:4,z:4,m:9}],xkey:'x',ykeys:['y','z','m'],labels:['Y','Z','M']}).on('click',function(i,row){console.log(i,row);});}function MorrisChart3(){var decimal_data=[];for(var x=0;x<=360;x+=10)decimal_data.push({x:x,y:Math.sin(Math.PI*x/180).toFixed(4),z:Math.cos(Math.PI*x/180).toFixed(4)});Morris.Line({element:'morris-chart-3',data:decimal_data,xkey:'x',ykeys:['y','z'],labels:['sin(x)','cos(x)'],parseTime:false,goals:[-1,0,1]});}function MorrisChart4(){Morris.Bar({element:'morris-chart-4',data:[{x:'2011 Q1',y:0},{x:'2011 Q2',y:1},{x:'2011 Q3',y:2},{x:'2011 Q4',y:3},{x:'2012 Q1',y:4},{x:'2012 Q2',y:5},{x:'2012 Q3',y:6},{x:'2012 Q4',y:7},{x:'2013 Q1',y:8},{x:'2013 Q2',y:7},{x:'2013 Q3',y:6},{x:'2013 Q4',y:5},{x:'2014 Q1',y:9}],xkey:'x',ykeys:['y'],labels:['Y'],barColors:function(row,series,type){if(type==='bar'){var red=Math.ceil(255*row.y/this.ymax);return 'rgb('+red+',0,0)';}else return '#000';}});}function MorrisChart5(){Morris.Area({element:'morris-chart-5',data:[{period:'2010 Q1',iphone:2666,ipad:null,itouch:2647},{period:'2010 Q2',iphone:2778,ipad:2294,itouch:2441},{period:'2010 Q3',iphone:4912,ipad:1969,itouch:2501},{period:'2010 Q4',iphone:3767,ipad:3597,itouch:5689},{period:'2011 Q1',iphone:6810,ipad:1914,itouch:2293},{period:'2011 Q2',iphone:5670,ipad:4293,itouch:1881},{period:'2011 Q3',iphone:4820,ipad:3795,itouch:1588},{period:'2011 Q4',iphone:15073,ipad:5967,itouch:5175},{period:'2012 Q1',iphone:10687,ipad:4460,itouch:2028},{period:'2012 Q2',iphone:8432,ipad:5713,itouch:1791}],xkey:'period',ykeys:['iphone','ipad','itouch'],labels:['iPhone','iPad','iPod Touch'],pointSize:2,hideHover:'auto'});}function DrawAllCharts(){var chart1_data=[['Smartphones','PC','Notebooks','Monitors','Routers','Switches'],['01.01.2014',1234,2342,344,232,131],['02.01.2014',1254,232,314,232,331],['03.01.2014',2234,342,298,232,665],['04.01.2014',2234,42,559,232,321],['05.01.2014',1999,82,116,232,334],['06.01.2014',1634,834,884,232,191],['07.01.2014',321,342,383,232,556],['08.01.2014',845,112,499,232,731]];var chart1_options={title:'Sales of company',hAxis:{title:'Date',titleTextStyle:{color:'red'}},backgroundColor:'#fcfcfc',vAxis:{title:'Quantity',titleTextStyle:{color:'blue'}}};var chart1_element='google-chart-1';var chart1_type=google.visualization.ColumnChart;drawGoogleChart(chart1_data,chart1_options,chart1_element,chart1_type);var chart2_data=[['Height','Width'],['Samsung',74.5],['Apple',31.24],['LG',12.10],['Huawei',11.14],['Sony',8.3],['Nokia',7.4],['Blackberry',6.8],['HTC',6.63],['Motorola',3.5],['Other',43.15]];var chart2_options={title:'Smartphone marketshare 2Q 2013',backgroundColor:'#fcfcfc'};var chart2_element='google-chart-2';var chart2_type=google.visualization.PieChart;drawGoogleChart(chart2_data,chart2_options,chart2_element,chart2_type);var chart3_data=[['Age','Weight'],[8,12],[4,5.5],[11,14],[4,5],[3,3.5],[6.5,7]];var chart3_options={title:'Age vs. Weight comparison',hAxis:{title:'Age',minValue:0,maxValue:15},vAxis:{title:'Weight',minValue:0,maxValue:15},legend:'none',backgroundColor:'#fcfcfc'};var chart3_element='google-chart-3';var chart3_type=google.visualization.ScatterChart;drawGoogleChart(chart3_data,chart3_options,chart3_element,chart3_type);var chart4_data=[['ID','Life Expectancy','Fertility Rate','Region','Population'],['CAN',80.66,1.67,'North America',33739900],['DEU',79.84,1.36,'Europe',81902307],['DNK',78.6,1.84,'Europe',5523095],['EGY',72.73,2.78,'Middle East',79716203],['GBR',80.05,2,'Europe',61801570],['IRN',72.49,1.7,'Middle East',73137148],['IRQ',68.09,4.77,'Middle East',31090763],['ISR',81.55,2.96,'Middle East',7485600],['RUS',68.6,1.54,'Europe',141850000],['USA',78.09,2.05,'North America',307007000]];var chart4_options={title:'Correlation between life expectancy, fertility rate and population of some world countries (2010)',hAxis:{title:'Life Expectancy'},vAxis:{title:'Fertility Rate'},backgroundColor:'#fcfcfc',bubble:{textStyle:{fontSize:11}}};var chart4_element='google-chart-4';var chart4_type=google.visualization.BubbleChart;drawGoogleChart(chart4_data,chart4_options,chart4_element,chart4_type);var chart5_data=[['Country','Popularity'],['Germany',200],['United States',300],['Brazil',400],['Canada',500],['France',600],['RU',700]];var chart5_options={backgroundColor:'#fcfcfc',enableRegionInteractivity:true};var chart5_element='google-chart-5';var chart5_type=google.visualization.GeoChart;drawGoogleChart(chart5_data,chart5_options,chart5_element,chart5_type);var chart6_data=[['Year','Sales','Expenses'],['2004',1000,400],['2005',1170,460],['2006',660,1120],['2007',1030,540],['2008',2080,740],['2009',1949,690],['2010',2334,820]];var chart6_options={backgroundColor:'#fcfcfc',title:'Company Performance'};var chart6_element='google-chart-6';var chart6_type=google.visualization.LineChart;drawGoogleChart(chart6_data,chart6_options,chart6_element,chart6_type);var chart7_data=[['Task','Hours per Day'],['Work',11],['Eat',2],['Commute',2],['Watch TV',2],['Sleep',7]];var chart7_options={backgroundColor:'#fcfcfc',title:'My Daily Activities',pieHole:0.4};var chart7_element='google-chart-7';var chart7_type=google.visualization.PieChart;drawGoogleChart(chart7_data,chart7_options,chart7_element,chart7_type);var chart8_data=[['Generation','Descendants'],[0,1],[1,33],[2,269],[3,2013]];var chart8_options={backgroundColor:'#fcfcfc',title:'Descendants by Generation',hAxis:{title:'Generation',minValue:0,maxValue:3},vAxis:{title:'Descendants',minValue:0,maxValue:2100},trendlines:{0:{type:'exponential',visibleInLegend:true}}};var chart8_element='google-chart-8';var chart8_type=google.visualization.ScatterChart;drawGoogleChart(chart8_data,chart8_options,chart8_element,chart8_type);}function xGraph1(){var tt=document.createElement('div'),leftOffset=-(~~$('html').css('padding-left').replace('px','')+~~$('body').css('margin-left').replace('px','')),topOffset=-32;tt.className='ex-tooltip';document.body.appendChild(tt);var data={"xScale":"time","yScale":"linear","main":[{"className":".xchart-class-1","data":[{"x":"2012-11-05","y":6},{"x":"2012-11-06","y":6},{"x":"2012-11-07","y":8},{"x":"2012-11-08","y":3},{"x":"2012-11-09","y":4},{"x":"2012-11-10","y":9},{"x":"2012-11-11","y":6},{"x":"2012-11-12","y":16},{"x":"2012-11-13","y":4},{"x":"2012-11-14","y":9},{"x":"2012-11-15","y":2}]}]};var opts={"dataFormatX":function(x){return d3.time.format('%Y-%m-%d').parse(x);},"tickFormatX":function(x){return d3.time.format('%A')(x);},"mouseover":function(d,i){var pos=$(this).offset();$(tt).text(d3.time.format('%A')(d.x)+': '+d.y).css({top:topOffset+pos.top,left:pos.left+leftOffset}).show();},"mouseout":function(x){$(tt).hide();}};var myChart=new xChart('line-dotted',data,'#xchart-1',opts);}function xGraph2(){var data={"xScale":"ordinal","yScale":"linear","main":[{"className":".xchart-class-2","data":[{"x":"Apple","y":575},{"x":"Facebook","y":163},{"x":"Microsoft","y":303},{"x":"Cisco","y":121},{"x":"Google","y":393}]}]};var myChart=new xChart('bar',data,'#xchart-2');}function xGraph3(){var data={"xScale":"time","yScale":"linear","type":"line","main":[{"className":".xchart-class-3","data":[{"x":"2012-11-05","y":1},{"x":"2012-11-06","y":6},{"x":"2012-11-07","y":13},{"x":"2012-11-08","y":-3},{"x":"2012-11-09","y":-4},{"x":"2012-11-10","y":9},{"x":"2012-11-11","y":6},{"x":"2012-11-12","y":7},{"x":"2012-11-13","y":-2},{"x":"2012-11-14","y":-7}]}]};var opts={"dataFormatX":function(x){return d3.time.format('%Y-%m-%d').parse(x);},"tickFormatX":function(x){return d3.time.format('%A')(x);}};var myChart=new xChart('line',data,'#xchart-3',opts);}function CoinDeskGraph(){var dates=PrettyDates();var startdate=dates[0];var enddate=dates[1];var jsonURL='http://api.coindesk.com/v1/bpi/historical/close.json?start='+startdate+'&end='+enddate;$.getJSON(jsonURL,function(result){$.each(result.bpi,function(key,val){xchart_data.push({'x':key,'y':val});});var graphXChartResize;$('#coindesk-xchart').resize(function(){clearTimeout(graphXChartResize);graphXChartResize=setTimeout(DrawCoinDeskXCharts,500);});DrawCoinDeskXCharts();$.each(result.bpi,function(key,val){google_data.push([key,val]);});var graphGChartResize;$('#coindesk-google-chart').resize(function(){clearTimeout(graphGChartResize);graphGChartResize=setTimeout(DrawCoinDeskGoogleCharts,500);});DrawCoinDeskGoogleCharts();$.each(result.bpi,function(key,val){var parseDate=key;parseDate=parseDate.split("-");var newDate=parseDate[1]+"/"+parseDate[2]+"/"+parseDate[0];var new_date=new Date(newDate).getTime();exchange_rate.push([new_date,val]);});DrawCoinDeskFlot();var graphSparklineResize;$('#coindesk-sparklines').resize(function(){clearTimeout(graphSparklineResize);graphSparklineResize=setTimeout(DrawCoinDeskSparkLine,500);});DrawCoinDeskSparkLine();});}function DrawCoinDeskSparkLine(){$('#coindesk-sparklines').sparkline(exchange_rate,{height:'100%',width:'100%'});}function DrawCoinDeskXCharts(){var data={"xScale":"ordinal","yScale":"linear","main":[{"className":".pizza","data":xchart_data}]};var myChart=new xChart('line-dotted',data,'#coindesk-xchart');}function DrawCoinDeskFlot(){var data1=[{data:exchange_rate,label:"Bitcoin exchange rate ($)"}];var options={canvas:true,xaxes:[{mode:"time"}],yaxes:[{min:0},{position:"right",alignTicksWithAxis:1,tickFormatter:function(value,axis){return value.toFixed(axis.tickDecimals)+"€";}}],legend:{position:"sw"}};$.plot("#coindesk-flot",data1,options);}function DrawCoinDeskGoogleCharts(){var google_options={backgroundColor:'#fcfcfc',title:'Coindesk Exchange Rate'};var google_element='coindesk-google-chart';var google_type=google.visualization.LineChart;drawGoogleChart(google_data,google_options,google_element,google_type);}function TestTable1(){$('#datatable-1').dataTable({"aaSorting":[[0,"asc"]],"sDom":"<'box-content'<'col-sm-6'f><'col-sm-6 text-right'l><'clearfix'>>rt<'box-content'<'col-sm-6'i><'col-sm-6 text-right'p><'clearfix'>>","sPaginationType":"bootstrap","oLanguage":{"sSearch":"","sLengthMenu":'_MENU_'}});}function TestTable2(){var asInitVals=[];var oTable=$('#datatable-2').dataTable({"aaSorting":[[0,"asc"]],"sDom":"<'box-content'<'col-sm-6'f><'col-sm-6 text-right'l><'clearfix'>>rt<'box-content'<'col-sm-6'i><'col-sm-6 text-right'p><'clearfix'>>","sPaginationType":"bootstrap","oLanguage":{"sSearch":"","sLengthMenu":'_MENU_'},bAutoWidth:false});var header_inputs=$("#datatable-2 thead input");header_inputs.on('keyup',function(){oTable.fnFilter(this.value,header_inputs.index(this));}).on('focus',function(){if(this.className=="search_init"){this.className="";this.value="";}}).on('blur',function(i){if(this.value==""){this.className="search_init";this.value=asInitVals[header_inputs.index(this)];}});header_inputs.each(function(i){asInitVals[i]=this.value;});}function TestTable3(){$('#datatable-3').dataTable({"aaSorting":[[0,"asc"]],"sDom":"T<'box-content'<'col-sm-6'f><'col-sm-6 text-right'l><'clearfix'>>rt<'box-content'<'col-sm-6'i><'col-sm-6 text-right'p><'clearfix'>>","sPaginationType":"bootstrap","oLanguage":{"sSearch":"","sLengthMenu":'_MENU_'},"oTableTools":{"sSwfPath":"plugins/datatables/copy_csv_xls_pdf.swf","aButtons":["copy","print",{"sExtends":"collection","sButtonText":'Save ',"aButtons":["csv","xls","pdf"]}]}});}function SmallChangeVal(val){var new_val=Math.floor(100*Math.random());var plusOrMinus=Math.random()<0.5?-1:1;var result=val[0]+new_val*plusOrMinus;if(parseInt(result)>1000)return [val[0]-new_val];if(parseInt(result)<0)return [val[0]+new_val];return [result];}function SparklineTestData(){var arr=[];for(var i=1;i<9;i++)arr.push([Math.floor(1000*Math.random())]);return arr;}function RedrawKnob(elem){elem.animate({value:Math.floor(100*Math.random())},{duration:3000,easing:'swing',progress:function(){$(this).val(parseInt(Math.ceil(elem.val()))).trigger('change');}});}function SparklineLoop(){SparkLineDrawBarGraph($('#sparkline-1'),sparkline_arr_1.map(SmallChangeVal));SparkLineDrawBarGraph($('#sparkline-2'),sparkline_arr_2.map(SmallChangeVal),'#7BC5D3');SparkLineDrawBarGraph($('#sparkline-3'),sparkline_arr_3.map(SmallChangeVal),'#B25050');}function MorrisDashboard(){Morris.Line({element:'stat-graph',data:[{"period":"2014-01","Win8":13.4,"Win7":55.3,'Vista':1.5,'NT':0.3,'XP':11,'Linux':4.9,'Mac':9.6,'Mobile':4},{"period":"2013-12","Win8":10,"Win7":55.9,'Vista':1.5,'NT':3.1,'XP':11.6,'Linux':4.8,'Mac':9.2,'Mobile':3.8},{"period":"2013-11","Win8":8.6,"Win7":56.4,'Vista':1.6,'NT':3.7,'XP':11.7,'Linux':4.8,'Mac':9.6,'Mobile':3.7},{"period":"2013-10","Win8":9.9,"Win7":56.7,'Vista':1.6,'NT':1.4,'XP':12.4,'Linux':4.9,'Mac':9.6,'Mobile':3.3},{"period":"2013-09","Win8":10.2,"Win7":56.8,'Vista':1.6,'NT':0.4,'XP':13.5,'Linux':4.8,'Mac':9.3,'Mobile':3.3},{"period":"2013-08","Win8":9.6,"Win7":55.9,'Vista':1.7,'NT':0.4,'XP':14.7,'Linux':5,'Mac':9.2,'Mobile':3.4},{"period":"2013-07","Win8":9,"Win7":56.2,'Vista':1.8,'NT':0.4,'XP':15.8,'Linux':4.9,'Mac':8.7,'Mobile':3.2},{"period":"2013-06","Win8":8.6,"Win7":56.3,'Vista':2,'NT':0.4,'XP':15.4,'Linux':4.9,'Mac':9.1,'Mobile':3.2},{"period":"2013-05","Win8":7.9,"Win7":56.4,'Vista':2.1,'NT':0.4,'XP':15.7,'Linux':4.9,'Mac':9.7,'Mobile':2.6},{"period":"2013-04","Win8":7.3,"Win7":56.4,'Vista':2.2,'NT':0.4,'XP':16.4,'Linux':4.8,'Mac':9.7,'Mobile':2.2},{"period":"2013-03","Win8":6.7,"Win7":55.9,'Vista':2.4,'NT':0.4,'XP':17.6,'Linux':4.7,'Mac':9.5,'Mobile':2.3},{"period":"2013-02","Win8":5.7,"Win7":55.3,'Vista':2.4,'NT':0.4,'XP':19.1,'Linux':4.8,'Mac':9.6,'Mobile':2.2},{"period":"2013-01","Win8":4.8,"Win7":55.3,'Vista':2.6,'NT':0.5,'XP':19.9,'Linux':4.8,'Mac':9.3,'Mobile':2.2}],xkey:'period',ykeys:['Win8','Win7','Vista','NT','XP','Linux','Mac','Mobile'],labels:['Win8','Win7','Vista','NT','XP','Linux','Mac','Mobile']});Morris.Donut({element:'morris_donut_1',data:[{value:70,label:'pay',formatted:'at least 70%'},{value:15,label:'client',formatted:'approx. 15%'},{value:10,label:'buy',formatted:'approx. 10%'},{value:5,label:'hosted',formatted:'at most 5%'}],formatter:function(x,data){return data.formatted;}});Morris.Donut({element:'morris_donut_2',data:[{value:20,label:'office',formatted:'current'},{value:35,label:'store',formatted:'approx. 35%'},{value:20,label:'shop',formatted:'approx. 20%'},{value:25,label:'cars',formatted:'at most 25%'}],formatter:function(x,data){return data.formatted;}});Morris.Donut({element:'morris_donut_3',data:[{value:17,label:'current',formatted:'current'},{value:22,label:'week',formatted:'last week'},{value:10,label:'month',formatted:'last month'},{value:25,label:'period',formatted:'period'},{value:25,label:'year',formatted:'this year'}],formatter:function(x,data){return data.formatted;}});}function DrawSparklineDashboard(){SparklineLoop();setInterval(SparklineLoop,1000);var sparkline_clients=[[309],[223],[343],[652],[455],[18],[912],[15]];$('.bar').each(function(){$(this).sparkline(sparkline_clients.map(SmallChangeVal),{type:'bar',barWidth:5,highlightColor:'#000',barSpacing:2,height:30,stackedBarColor:'#6AA6D6'});});var sparkline_table=[[1,341],[2,464],[4,564],[5,235],[6,335],[7,535],[8,642],[9,342],[10,765]];$('.td-graph').each(function(){var arr=$.map(sparkline_table,function(val,index){return [[val[0],SmallChangeVal([val[1]])]];});$(this).sparkline(arr,{defaultPixelsPerValue:10,minSpotColor:null,maxSpotColor:null,spotColor:null,fillColor:false,lineWidth:2,lineColor:'#5A8DB6'});});}function DrawKnobDashboard(){var srv_monitoring_selectors=[$("#knob-srv-1"),$("#knob-srv-2"),$("#knob-srv-3"),$("#knob-srv-4"),$("#knob-srv-5"),$("#knob-srv-6")];srv_monitoring_selectors.forEach(DrawKnob);setInterval(function(){srv_monitoring_selectors.forEach(RedrawKnob);},3000);}function FileUpload(){$('#bootstrapped-fine-uploader').fineUploader({template:'qq-template-bootstrap',classes:{success:'alert alert-success',fail:'alert alert-error'},thumbnails:{placeholders:{waitingPath:"assets/waiting-generic.png",notAvailablePath:"assets/not_available-generic.png"}},request:{endpoint:'server/handleUploads'},validation:{allowedExtensions:['jpeg','jpg','gif','png']}});}function LoadTestMap(){$.getJSON("http://www.telize.com/geoip?callback=?",function(json){var osmap=new OpenLayers.Layer.OSM("OpenStreetMap");var googlestreets=new OpenLayers.Layer.Google("Google Streets",{numZoomLevels:22,visibility:false});var googlesattelite=new OpenLayers.Layer.Google("Google Sattelite",{type:google.maps.MapTypeId.SATELLITE,numZoomLevels:22});var map1_layers=[googlestreets,osmap,googlesattelite];var map1=drawMap(json.longitude,json.latitude,"map-1",map1_layers);$("#map-1").resize(function(){setTimeout(map1.updateSize(),500);});var osmap1=new OpenLayers.Layer.OSM("OpenStreetMap");var map2_layers=[osmap1];var map2=drawMap(json.longitude,json.latitude,"map-2",map2_layers);$("#map-2").resize(function(){setTimeout(map2.updateSize(),500);});var sattelite=new OpenLayers.Layer.Google("Google Sattelite",{type:google.maps.MapTypeId.SATELLITE,numZoomLevels:22});var map3_layers=[sattelite];var map3=drawMap(json.longitude,json.latitude,"map-3",map3_layers);$("#map-3").resize(function(){setTimeout(map3.updateSize(),500);});});}function FullScreenMap(){$.getJSON("http://www.telize.com/geoip?callback=?",function(json){var osmap=new OpenLayers.Layer.OSM("OpenStreetMap");var googlestreets=new OpenLayers.Layer.Google("Google Streets",{numZoomLevels:22,visibility:false});var googlesattelite=new OpenLayers.Layer.Google("Google Sattelite",{type:google.maps.MapTypeId.SATELLITE,numZoomLevels:22});var map1_layers=[googlestreets,osmap,googlesattelite];var map_fs=drawMap(json.longitude,json.latitude,"full-map",map1_layers);});}function displayFlickrImages(data){var res;$.each(data.items,function(i,item){if(i>11)return false;res="+item.title+";$('#box-one-content').append(res);});setTimeout(function(){$("#box-one-content").justifiedGallery({'usedSuffix':'lt240','justifyLastRow':true,'rowHeight':150,'fixedHeight':false,'captions':true,'margins':1});$('#box-one-content').fadeIn('slow');},100);}function DemoFormValidator(){$('#defaultForm').bootstrapValidator({message:'This value is not valid',fields:{username:{message:'The username is not valid',validators:{notEmpty:{message:'The username is required and can\'t be empty'},stringLength:{min:6,max:30,message:'The username must be more than 6 and less than 30 characters long'},regexp:{regexp:/^[a-zA-Z0-9_\.]+$/,message:'The username can only consist of alphabetical, number, dot and underscore'}}},country:{validators:{notEmpty:{message:'The country is required and can\'t be empty'}}},acceptTerms:{validators:{notEmpty:{message:'You have to accept the terms and policies'}}},email:{validators:{notEmpty:{message:'The email address is required and can\'t be empty'},emailAddress:{message:'The input is not a valid email address'}}},website:{validators:{uri:{message:'The input is not a valid URL'}}},phoneNumber:{validators:{digits:{message:'The value can contain only digits'}}},color:{validators:{hexColor:{message:'The input is not a valid hex color'}}},zipCode:{validators:{usZipCode:{message:'The input is not a valid US zip code'}}},password:{validators:{notEmpty:{message:'The password is required and can\'t be empty'},identical:{field:'confirmPassword',message:'The password and its confirm are not the same'}}},confirmPassword:{validators:{notEmpty:{message:'The confirm password is required and can\'t be empty'},identical:{field:'password',message:'The password and its confirm are not the same'}}},ages:{validators:{lessThan:{value:100,inclusive:true,message:'The ages has to be less than 100'},greaterThan:{value:10,inclusive:false,message:'The ages has to be greater than or equals to 10'}}}}});}function FormLayoutExampleInputLength(selector){var steps=["col-sm-1","col-sm-2","col-sm-3","col-sm-4","col-sm-5","col-sm-6","col-sm-7","col-sm-8","col-sm-9","col-sm-10","col-sm-11","col-sm-12"];selector.slider({range:'min',value:1,min:0,max:11,step:1,slide:function(event,ui){if(ui.value<1)return false;var input=$("#form-styles");var f=input.parent();f.removeClass();f.addClass(steps[ui.value]);input.attr("placeholder",'.'+steps[ui.value]);}});}function RunClock(){var second=$(".second");var minute=$(".minute");var hour=$(".hour");var d=new Date();var s=d.getSeconds();var m=d.getMinutes();var h=d.getHours();if(h>11)h=h-12;$('#knob-clock-value').html(h+':'+m+':'+s);second.val(s).trigger("change");minute.val(m).trigger("change");hour.val(h).trigger("change");}function CreateAllSliders(){$(".slider-default").slider();var slider_range_min_amount=$(".slider-range-min-amount");var slider_range_min=$(".slider-range-min");var slider_range_max=$(".slider-range-max");var slider_range_max_amount=$(".slider-range-max-amount");var slider_range=$(".slider-range");var slider_range_amount=$(".slider-range-amount");slider_range_min.slider({range:"min",value:37,min:1,max:700,slide:function(event,ui){slider_range_min_amount.val("$"+ui.value);}});slider_range_min_amount.val("$"+slider_range_min.slider("value"));slider_range_max.slider({range:"max",min:1,max:100,value:2,slide:function(event,ui){slider_range_max_amount.val(ui.value);}});slider_range_max_amount.val(slider_range_max.slider("value"));slider_range.slider({range:true,min:0,max:500,values:[75,300],slide:function(event,ui){slider_range_amount.val("$"+ui.values[0]+" - $"+ui.values[1]);}});slider_range_amount.val("$"+slider_range.slider("values",0)+" - $"+slider_range.slider("values",1));$("#equalizer > div.progress > div").each(function(){var value=parseInt($(this).text(),10);$(this).empty().slider({value:value,range:"min",animate:true,orientation:"vertical"});});}function AllTimePickers(){$('#datetime_example').datetimepicker({});$('#time_example').timepicker({hourGrid:4,minuteGrid:10,timeFormat:'hh:mm tt'});$('#date3_example').datepicker({numberOfMonths:3,showButtonPanel:true});$('#date3-1_example').datepicker({numberOfMonths:3,showButtonPanel:true});$('#date_example').datepicker({});}function DrawCalendar(){$('#external-events div.external-event').each(function(){var eventObject={title:$.trim($(this).text())};$(this).data('eventObject',eventObject);$(this).draggable({zIndex:999,revert:true,revertDuration:0});});var calendar=$('#calendar').fullCalendar({header:{left:'prev,next today',center:'title',right:'month,agendaWeek,agendaDay'},selectable:true,selectHelper:true,select:function(start,end,allDay){var form=$('
'+'
'+'Event name'+'
'+''+'
'+''+'
'+''+'
'+'
'+'
');var buttons=$(''+'');OpenModalBox('Add event',form,buttons);$('#event_cancel').on('click',function(){CloseModalBox();});$('#event_submit').on('click',function(){var new_event_name=$('#newevent_name').val();if(new_event_name!='')calendar.fullCalendar('renderEvent',{title:new_event_name,description:$('#newevent_desc').val(),start:start,end:end,allDay:allDay},true);CloseModalBox();});calendar.fullCalendar('unselect');},editable:true,droppable:true,drop:function(date,allDay){var originalEventObject=$(this).data('eventObject');var copiedEventObject=$.extend({},originalEventObject);copiedEventObject.start=date;copiedEventObject.allDay=allDay;$('#calendar').fullCalendar('renderEvent',copiedEventObject,true);if($('#drop-remove').is(':checked'))$(this).remove();},eventRender:function(event,element,icon){if(event.description!="")element.attr('title',event.description);},eventClick:function(calEvent,jsEvent,view){var form=$('
'+'
'+'Event name'+'
'+''+'
'+''+'
'+''+'
'+'
'+'
');var buttons=$(''+''+'');OpenModalBox('Change event',form,buttons);$('#event_cancel').on('click',function(){CloseModalBox();});$('#event_delete').on('click',function(){calendar.fullCalendar('removeEvents',function(ev){return(ev._id==calEvent._id);});CloseModalBox();});$('#event_change').on('click',function(){calEvent.title=$('#newevent_name').val();calEvent.description=$('#newevent_desc').val();calendar.fullCalendar('updateEvent',calEvent);CloseModalBox();});}});$('#new-event-add').on('click',function(event){event.preventDefault();var event_name=$('#new-event-title').val();var event_description=$('#new-event-desc').val();if(event_name!=''){var event_template=$('
'+event_name+'
');$('#events-templates-header').after(event_template);var eventObject={title:event_name,description:event_description};event_template.data('eventObject',eventObject);event_template.draggable({zIndex:999,revert:true,revertDuration:0});}});}function DrawFullCalendar(){LoadCalendarScript(DrawCalendar);}$(document).ready(function(){$('.show-sidebar').on('click',function(){$('div#main').toggleClass('sidebar-show');setTimeout(MessagesMenuWidth,250);});var ajax_url=location.hash.replace(/^#/,'');if(ajax_url.length<1)ajax_url='ajax/dashboard.html';LoadAjaxContent(ajax_url);$('.main-menu').on('click','a',function(e){var parents=$(this).parents('li');var li=$(this).closest('li.dropdown');var another_items=$('.main-menu li').not(parents);another_items.find('a').removeClass('active');another_items.find('a').removeClass('active-parent');if($(this).hasClass('dropdown-toggle')||$(this).closest('li').find('ul').length==0){$(this).addClass('active-parent');var current=$(this).next();if(current.is(':visible')){li.find("ul.dropdown-menu").slideUp('fast');li.find("ul.dropdown-menu a").removeClass('active');}else{another_items.find("ul.dropdown-menu").slideUp('fast');current.slideDown('fast');}}else if(li.find('a.dropdown-toggle').hasClass('active-parent')){var pre=$(this).closest('ul.dropdown-menu');pre.find("li.dropdown").not($(this).closest('li')).find('ul.dropdown-menu').slideUp('fast');}if($(this).hasClass('active')==false){$(this).parents("ul.dropdown-menu").find('a').removeClass('active');$(this).addClass('active');}if($(this).hasClass('ajax-link')){e.preventDefault();if($(this).hasClass('add-full'))$('#content').addClass('full-content');else $('#content').removeClass('full-content');var url=$(this).attr('href');window.location.hash=url;LoadAjaxContent(url);}if($(this).attr('href')=='#')e.preventDefault();});var height=window.innerHeight-49;$('#main').css('min-height',height).on('click','.expand-link',function(e){var body=$('body');e.preventDefault();var box=$(this).closest('div.box');var button=$(this).find('i');button.toggleClass('fa-expand').toggleClass('fa-compress');box.toggleClass('expanded');body.toggleClass('body-expanded');var timeout=0;if(body.hasClass('body-expanded'))timeout=100;setTimeout(function(){box.toggleClass('expanded-padding');},timeout);setTimeout(function(){box.resize();box.find('[id^=map-]').resize();},timeout+50);}).on('click','.collapse-link',function(e){e.preventDefault();var box=$(this).closest('div.box');var button=$(this).find('i');var content=box.find('div.box-content');content.slideToggle('fast');button.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down');setTimeout(function(){box.resize();box.find('[id^=map-]').resize();},50);}).on('click','.close-link',function(e){e.preventDefault();var content=$(this).closest('div.box');content.remove();});$('#locked-screen').on('click',function(e){e.preventDefault();$('body').addClass('body-screensaver');$('#screensaver').addClass("show");ScreenSaver();});$('body').on('click','a.close-link',function(e){e.preventDefault();CloseModalBox();});$('#top-panel').on('click','a',function(e){if($(this).hasClass('ajax-link')){e.preventDefault();if($(this).hasClass('add-full'))$('#content').addClass('full-content');else $('#content').removeClass('full-content');var url=$(this).attr('href');window.location.hash=url;LoadAjaxContent(url);}});$('#search').on('keydown',function(e){if(e.keyCode==13){e.preventDefault();$('#content').removeClass('full-content');ajax_url='ajax/page_search.html';window.location.hash=ajax_url;LoadAjaxContent(ajax_url);}});$('#screen_unlock').on('mouseover',function(){var header='Enter current username and password';var form=$('
'+'
');var button=$('');OpenModalBox(header,form,button);});}); \ No newline at end of file diff --git a/plugins/web_gui/static/js/noty/jquery.noty.js b/plugins/web_gui/static/js/noty/jquery.noty.js new file mode 100644 index 0000000..6d1e202 --- /dev/null +++ b/plugins/web_gui/static/js/noty/jquery.noty.js @@ -0,0 +1,488 @@ +/** + * noty - jQuery Notification Plugin v2.2.2 + * Contributors: https://github.com/needim/noty/graphs/contributors + * + * Examples and Documentation - http://needim.github.com/noty/ + * + * Licensed under the MIT licenses: + * http://www.opensource.org/licenses/mit-license.php + * + **/ + +if (typeof Object.create !== 'function') { + Object.create = function (o) { + function F() { + } + + F.prototype = o; + return new F(); + }; +} + +(function ($) { + + var NotyObject = { + + init:function (options) { + + // Mix in the passed in options with the default options + this.options = $.extend({}, $.noty.defaults, options); + + this.options.layout = (this.options.custom) ? $.noty.layouts['inline'] : $.noty.layouts[this.options.layout]; + + if ($.noty.themes[this.options.theme]) + this.options.theme = $.noty.themes[this.options.theme]; + else + options.themeClassName = this.options.theme; + + delete options.layout; + delete options.theme; + + this.options = $.extend({}, this.options, this.options.layout.options); + this.options.id = 'noty_' + (new Date().getTime() * Math.floor(Math.random() * 1000000)); + + this.options = $.extend({}, this.options, options); + + // Build the noty dom initial structure + this._build(); + + // return this so we can chain/use the bridge with less code. + return this; + }, // end init + + _build:function () { + + // Generating noty bar + var $bar = $('
').attr('id', this.options.id); + $bar.append(this.options.template).find('.noty_text').html(this.options.text); + + this.$bar = (this.options.layout.parent.object !== null) ? $(this.options.layout.parent.object).css(this.options.layout.parent.css).append($bar) : $bar; + + if (this.options.themeClassName) + this.$bar.addClass(this.options.themeClassName).addClass('noty_container_type_' + this.options.type); + + // Set buttons if available + if (this.options.buttons) { + + // If we have button disable closeWith & timeout options + this.options.closeWith = []; + this.options.timeout = false; + + var $buttons = $('
').addClass('noty_buttons'); + + (this.options.layout.parent.object !== null) ? this.$bar.find('.noty_bar').append($buttons) : this.$bar.append($buttons); + + var self = this; + + $.each(this.options.buttons, function (i, button) { + var $button = $('
'),i.url&&(s=''),"undefined"==typeof s&&(s=t.renderHtml(e)),e.statusbar&&(a=e.statusbar.renderHtml()),'
'+o+'
'+s+"
"+a+"
"},fullscreen:function(e){var t=this,r=document.documentElement,i,o=t.classPrefix,a;if(e!=t._fullscreen)if(n.on(window,"resize",function(){var e;if(t._fullscreen)if(i)t._timer||(t._timer=setTimeout(function(){var e=n.getWindowSize();t.moveTo(0,0).resizeTo(e.w,e.h),t._timer=0},50));else{e=(new Date).getTime();var r=n.getWindowSize();t.moveTo(0,0).resizeTo(r.w,r.h),(new Date).getTime()-e>50&&(i=!0)}}),a=t.layoutRect(),t._fullscreen=e,e){t._initial={x:a.x,y:a.y,w:a.w,h:a.h},t._borderBox=t.parseBox("0"),t.getEl("head").style.display="none",a.deltaH-=a.headerH+2,n.addClass(r,o+"fullscreen"),n.addClass(document.body,o+"fullscreen"),t.addClass("fullscreen");var s=n.getWindowSize();t.moveTo(0,0).resizeTo(s.w,s.h)}else t._borderBox=t.parseBox(t.settings.border),t.getEl("head").style.display="",a.deltaH+=a.headerH,n.removeClass(r,o+"fullscreen"),n.removeClass(document.body,o+"fullscreen"),t.removeClass("fullscreen"),t.moveTo(t._initial.x,t._initial.y).resizeTo(t._initial.w,t._initial.h);return t.reflow()},postRender:function(){var e=this,t=[],n,o,a;setTimeout(function(){e.addClass("in")},0),e.keyboardNavigation=new r({root:e,enableLeftRight:!1,enableUpDown:!1,items:t,onCancel:function(){e.close()}}),e.find("*").each(function(e){e.canFocus&&(o=o||e.settings.autofocus,n=n||e,e.subinput?(t.push(e.getEl("inp")),e.getEl("open")&&t.push(e.getEl("open"))):t.push(e.getEl()))}),e.statusbar&&e.statusbar.find("*").each(function(e){e.canFocus&&(o=o||e.settings.autofocus,n=n||e,t.push(e.getEl()))}),e._super(),e.statusbar&&e.statusbar.postRender(),!o&&n&&n.focus(),this.dragHelper=new i(e._id+"-dragh",{start:function(){a={x:e.layoutRect().x,y:e.layoutRect().y}},drag:function(t){e.moveTo(a.x+t.deltaX,a.y+t.deltaY)}}),e.on("submit",function(t){t.isDefaultPrevented()||e.close()})},submit:function(){return this.fire("submit",{data:this.toJSON()})},remove:function(){var e=this,t=e.classPrefix;e.dragHelper.destroy(),e._super(),e.statusbar&&this.statusbar.remove(),e._fullscreen&&(n.removeClass(document.documentElement,t+"fullscreen"),n.removeClass(document.body,t+"fullscreen"))}});return o}),r(Z,[Q],function(e){var t=e.extend({init:function(e){e={border:1,padding:20,layout:"flex",pack:"center",align:"center",containerCls:"panel",autoScroll:!0,buttons:{type:"button",text:"Ok",action:"ok"},items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200}},this._super(e)},Statics:{OK:1,OK_CANCEL:2,YES_NO:3,YES_NO_CANCEL:4,msgBox:function(n){var r,i=n.callback||function(){};switch(n.buttons){case t.OK_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}},{type:"button",text:"Cancel",onClick:function(e){e.control.parents()[1].close(),i(!1)}}];break;case t.YES_NO:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}];break;case t.YES_NO_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close()}}];break;default:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}]}return new e({padding:20,x:n.x,y:n.y,minWidth:300,minHeight:100,layout:"flex",pack:"center",align:"center",buttons:r,title:n.title,items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200,text:n.text},onClose:n.onClose}).renderTo(document.body).reflow()},alert:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,t.msgBox(e)},confirm:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,e.buttons=t.OK_CANCEL,t.msgBox(e)}}});return t}),r(et,[Q,Z],function(e,t){return function(n){function r(){return o.length?o[o.length-1]:void 0}var i=this,o=[];i.windows=o,i.open=function(t,r){var i;return n.editorManager.activeEditor=n,t.title=t.title||" ",t.url=t.url||t.file,t.url&&(t.width=parseInt(t.width||320,10),t.height=parseInt(t.height||240,10)),t.body&&(t.items={defaults:t.defaults,type:t.bodyType||"form",items:t.body}),t.url||t.buttons||(t.buttons=[{text:"Ok",subtype:"primary",onclick:function(){i.find("form")[0].submit(),i.close()}},{text:"Cancel",onclick:function(){i.close()}}]),i=new e(t),o.push(i),i.on("close",function(){for(var e=o.length;e--;)o[e]===i&&o.splice(e,1);n.focus()}),t.data&&i.on("postRender",function(){this.find("*").each(function(e){var n=e.name();n in t.data&&e.value(t.data[n])})}),i.features=t||{},i.params=r||{},n.nodeChanged(),i.renderTo(document.body).reflow()},i.alert=function(e,n,r){t.alert(e,function(){n&&n.call(r||this)})},i.confirm=function(e,n,r){t.confirm(e,function(e){n.call(r||this,e)})},i.close=function(){r()&&r().close()},i.getParams=function(){return r()?r().params:null},i.setParams=function(e){r()&&(r().params=e)}}}),r(tt,[T,B,C,m,h,f],function(e,t,n,r,i,o){return function(a){function s(e,t){try{a.getDoc().execCommand(e,!1,t)}catch(n){}}function l(){var e=a.getDoc().documentMode;return e?e:6}function c(e){return e.isDefaultPrevented()}function d(){function t(e){var t=new MutationObserver(function(){});o.each(a.getBody().getElementsByTagName("*"),function(e){"SPAN"==e.tagName&&e.setAttribute("mce-data-marked",1),!e.hasAttribute("data-mce-style")&&e.hasAttribute("style")&&a.dom.setAttrib(e,"style",e.getAttribute("style"))}),t.observe(a.getDoc(),{childList:!0,attributes:!0,subtree:!0,attributeFilter:["style"]}),a.getDoc().execCommand(e?"ForwardDelete":"Delete",!1,null);var n=a.selection.getRng(),r=n.startContainer.parentNode;o.each(t.takeRecords(),function(e){if("style"==e.attributeName){var t=e.target.getAttribute("data-mce-style");t?e.target.setAttribute("style",t):e.target.removeAttribute("style")}o.each(e.addedNodes,function(e){if("SPAN"==e.nodeName&&!e.getAttribute("mce-data-marked")){var t,i;e==r&&(t=n.startOffset,i=e.firstChild),W.remove(e,!0),i&&(n.setStart(i,t),n.setEnd(i,t),a.selection.setRng(n))}})}),t.disconnect(),o.each(a.dom.select("span[mce-data-marked]"),function(e){e.removeAttribute("mce-data-marked")})}var n=a.getDoc();window.MutationObserver&&(a.on("keydown",function(n){var r=n.keyCode==F,i=e.metaKeyPressed(n);if(!c(n)&&(r||n.keyCode==z)){var o=a.selection.getRng(),s=o.startContainer,l=o.startOffset;if(!i&&o.collapsed&&3==s.nodeType&&(r?l0))return;n.preventDefault(),i&&a.selection.getSel().modify("extend",r?"forward":"backward","word"),t(r)}}),a.on("keypress",function(n){c(n)||V.isCollapsed()||!n.charCode||e.metaKeyPressed(n)||(n.preventDefault(),t(!0),a.selection.setContent(String.fromCharCode(n.charCode)))}),a.addCommand("Delete",function(){t()}),a.addCommand("ForwardDelete",function(){t(!0)}),a.on("dragstart",function(e){e.dataTransfer.setData("mce-internal",a.selection.getContent())}),a.on("drop",function(e){if(!c(e)){var r=e.dataTransfer.getData("mce-internal");r&&n.caretRangeFromPoint&&(e.preventDefault(),t(),a.selection.setRng(n.caretRangeFromPoint(e.x,e.y)),a.insertContent(r))}}),a.on("cut",function(e){!c(e)&&e.clipboardData&&(e.preventDefault(),e.clipboardData.clearData(),e.clipboardData.setData("text/html",a.selection.getContent()),e.clipboardData.setData("text/plain",a.selection.getContent({format:"text"})),t(!0))}))}function u(){function e(e){var t=W.create("body"),n=e.cloneContents();return t.appendChild(n),V.serializer.serialize(t,{format:"html"})}function n(n){if(!n.setStart){if(n.item)return!1;var r=n.duplicate();return r.moveToElementText(a.getBody()),t.compareRanges(n,r)}var i=e(n),o=W.createRng();o.selectNode(a.getBody());var s=e(o);return i===s}a.on("keydown",function(e){var t=e.keyCode,r,i;if(!c(e)&&(t==F||t==z)){if(r=a.selection.isCollapsed(),i=a.getBody(),r&&!W.isEmpty(i))return;if(!r&&!n(a.selection.getRng()))return;e.preventDefault(),a.setContent(""),i.firstChild&&W.isBlock(i.firstChild)?a.selection.setCursorLocation(i.firstChild,0):a.selection.setCursorLocation(i,0),a.nodeChanged()}})}function f(){a.on("keydown",function(t){!c(t)&&65==t.keyCode&&e.metaKeyPressed(t)&&(t.preventDefault(),a.execCommand("SelectAll"))})}function p(){a.settings.content_editable||(W.bind(a.getDoc(),"focusin",function(){V.setRng(V.getRng())}),W.bind(a.getDoc(),"mousedown",function(e){e.target==a.getDoc().documentElement&&(a.getBody().focus(),V.setRng(V.getRng()))}))}function m(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===z&&V.isCollapsed()&&0===V.getRng(!0).startOffset){var t=V.getNode(),n=t.previousSibling;if("HR"==t.nodeName)return W.remove(t),void e.preventDefault();n&&n.nodeName&&"hr"===n.nodeName.toLowerCase()&&(W.remove(n),e.preventDefault())}})}function h(){window.Range.prototype.getClientRects||a.on("mousedown",function(e){if(!c(e)&&"HTML"===e.target.nodeName){var t=a.getBody();t.blur(),setTimeout(function(){t.focus()},0)}})}function g(){a.on("click",function(e){e=e.target,/^(IMG|HR)$/.test(e.nodeName)&&V.getSel().setBaseAndExtent(e,0,e,1),"A"==e.nodeName&&W.hasClass(e,"mce-item-anchor")&&V.select(e),a.nodeChanged()})}function v(){function e(){var e=W.getAttribs(V.getStart().cloneNode(!1));return function(){var t=V.getStart();t!==a.getBody()&&(W.setAttrib(t,"style",null),I(e,function(e){t.setAttributeNode(e.cloneNode(!0))}))}}function t(){return!V.isCollapsed()&&W.getParent(V.getStart(),W.isBlock)!=W.getParent(V.getEnd(),W.isBlock)}a.on("keypress",function(n){var r;return c(n)||8!=n.keyCode&&46!=n.keyCode||!t()?void 0:(r=e(),a.getDoc().execCommand("delete",!1,null),r(),n.preventDefault(),!1)}),W.bind(a.getDoc(),"cut",function(n){var r;!c(n)&&t()&&(r=e(),setTimeout(function(){r()},0))})}function y(){var e,n;a.on("selectionchange",function(){n&&(clearTimeout(n),n=0),n=window.setTimeout(function(){var n=V.getRng();e&&t.compareRanges(n,e)||(a.nodeChanged(),e=n)},50)})}function b(){document.body.setAttribute("role","application")}function C(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===z&&V.isCollapsed()&&0===V.getRng(!0).startOffset){var t=V.getNode().previousSibling;if(t&&t.nodeName&&"table"===t.nodeName.toLowerCase())return e.preventDefault(),!1}})}function x(){l()>7||(s("RespectVisibilityInDesign",!0),a.contentStyles.push(".mceHideBrInPre pre br {display: none}"),W.addClass(a.getBody(),"mceHideBrInPre"),q.addNodeFilter("pre",function(e){for(var t=e.length,r,i,o,a;t--;)for(r=e[t].getAll("br"),i=r.length;i--;)o=r[i],a=o.prev,a&&3===a.type&&"\n"!=a.value.charAt(a.value-1)?a.value+="\n":o.parent.insert(new n("#text",3),o,!0).value="\n"}),j.addNodeFilter("pre",function(e){for(var t=e.length,n,r,i,o;t--;)for(n=e[t].getAll("br"),r=n.length;r--;)i=n[r],o=i.prev,o&&3==o.type&&(o.value=o.value.replace(/\r?\n$/,""))}))}function w(){W.bind(a.getBody(),"mouseup",function(){var e,t=V.getNode();"IMG"==t.nodeName&&((e=W.getStyle(t,"width"))&&(W.setAttrib(t,"width",e.replace(/[^0-9%]+/g,"")),W.setStyle(t,"width","")),(e=W.getStyle(t,"height"))&&(W.setAttrib(t,"height",e.replace(/[^0-9%]+/g,"")),W.setStyle(t,"height","")))})}function _(){a.on("keydown",function(t){var n,r,i,o,s;if(!c(t)&&t.keyCode==e.BACKSPACE&&(n=V.getRng(),r=n.startContainer,i=n.startOffset,o=W.getRoot(),s=r,n.collapsed&&0===i)){for(;s&&s.parentNode&&s.parentNode.firstChild==s&&s.parentNode!=o;)s=s.parentNode;"BLOCKQUOTE"===s.tagName&&(a.formatter.toggle("blockquote",null,s),n=W.createRng(),n.setStart(r,0),n.setEnd(r,0),V.setRng(n))}})}function N(){function e(){a._refreshContentEditable(),s("StyleWithCSS",!1),s("enableInlineTableEditing",!1),U.object_resizing||s("enableObjectResizing",!1)}U.readonly||a.on("BeforeExecCommand MouseDown",e)}function E(){function e(){I(W.select("a"),function(e){var t=e.parentNode,n=W.getRoot();if(t.lastChild===e){for(;t&&!W.isBlock(t);){if(t.parentNode.lastChild!==t||t===n)return;t=t.parentNode}W.add(t,"br",{"data-mce-bogus":1})}})}a.on("SetContent ExecCommand",function(t){("setcontent"==t.type||"mceInsertLink"===t.command)&&e()})}function k(){U.forced_root_block&&a.on("init",function(){s("DefaultParagraphSeparator",U.forced_root_block)})}function S(){a.on("Undo Redo SetContent",function(e){e.initial||a.execCommand("mceRepaint")})}function T(){a.on("keydown",function(e){var t;c(e)||e.keyCode!=z||(t=a.getDoc().selection.createRange(),t&&t.item&&(e.preventDefault(),a.undoManager.beforeChange(),W.remove(t.item(0)),a.undoManager.add()))})}function R(){var e;l()>=10&&(e="",I("p div h1 h2 h3 h4 h5 h6".split(" "),function(t,n){e+=(n>0?",":"")+t+":empty"}),a.contentStyles.push(e+"{padding-right: 1px !important}"))}function A(){l()<9&&(q.addNodeFilter("noscript",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.firstChild,r&&n.attr("data-mce-innertext",r.value)}),j.addNodeFilter("noscript",function(e){for(var t=e.length,i,o,a;t--;)i=e[t],o=e[t].firstChild,o?o.value=r.decode(o.value):(a=i.attributes.map["data-mce-innertext"],a&&(i.attr("data-mce-innertext",null),o=new n("#text",3),o.value=a,o.raw=!0,i.append(o)))}))}function B(){function e(e,t){var n=i.createTextRange();try{n.moveToPoint(e,t)}catch(r){n=null}return n}function t(t){var r;t.button?(r=e(t.x,t.y),r&&(r.compareEndPoints("StartToStart",a)>0?r.setEndPoint("StartToStart",a):r.setEndPoint("EndToEnd",a),r.select())):n()}function n(){var e=r.selection.createRange();a&&!e.item&&0===e.compareEndPoints("StartToEnd",e)&&a.select(),W.unbind(r,"mouseup",n),W.unbind(r,"mousemove",t),a=o=0}var r=W.doc,i=r.body,o,a,s;r.documentElement.unselectable=!0,W.bind(r,"mousedown contextmenu",function(i){if("HTML"===i.target.nodeName){if(o&&n(),s=r.documentElement,s.scrollHeight>s.clientHeight)return;o=1,a=e(i.x,i.y),a&&(W.bind(r,"mouseup",n),W.bind(r,"mousemove",t),W.win.focus(),a.select())}})}function L(){a.on("keyup focusin",function(t){65==t.keyCode&&e.metaKeyPressed(t)||V.normalize()})}function H(){a.contentStyles.push("img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}")}function M(){a.inline||a.on("keydown",function(){document.activeElement==document.body&&a.getWin().focus()})}function D(){a.inline||(a.contentStyles.push("body {min-height: 150px}"),a.on("click",function(e){"HTML"==e.target.nodeName&&(a.execCommand("SelectAll"),a.selection.collapse(!0),a.nodeChanged())}))}function P(){i.mac&&a.on("keydown",function(t){!e.metaKeyPressed(t)||37!=t.keyCode&&39!=t.keyCode||(t.preventDefault(),a.selection.getSel().modify("move",37==t.keyCode?"backward":"forward","word"))})}function O(){s("AutoUrlDetect",!1)}var I=o.each,z=e.BACKSPACE,F=e.DELETE,W=a.dom,V=a.selection,U=a.settings,q=a.parser,j=a.serializer,$=i.gecko,K=i.ie,Y=i.webkit;C(),_(),u(),L(),Y&&(d(),p(),g(),k(),i.iOS?(y(),M(),D()):f()),K&&i.ie<11&&(m(),b(),x(),w(),T(),R(),A(),B()),i.ie>=11&&D(),i.ie&&(f(),O()),$&&(m(),h(),v(),N(),E(),S(),H(),P())}}),r(nt,[f],function(e){function t(){return!1}function n(){return!0}var r="__bindings",i=e.makeMap("focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag"," ");return{fire:function(e,i,o){var a=this,s,l,c,d,u;if(e=e.toLowerCase(),i=i||{},i.type=e,i.target||(i.target=a),i.preventDefault||(i.preventDefault=function(){i.isDefaultPrevented=n},i.stopPropagation=function(){i.isPropagationStopped=n},i.stopImmediatePropagation=function(){i.isImmediatePropagationStopped=n},i.isDefaultPrevented=t,i.isPropagationStopped=t,i.isImmediatePropagationStopped=t),a[r]&&(s=a[r][e]))for(l=0,c=s.length;c>l&&(s[l]=d=s[l],!i.isImmediatePropagationStopped());l++)if(d.call(a,i)===!1)return i.preventDefault(),i;if(o!==!1&&a.parent)for(u=a.parent();u&&!i.isPropagationStopped();)u.fire(e,i,!1),u=u.parent();return i},on:function(e,t){var n=this,o,a,s,l;if(t===!1&&(t=function(){return!1}),t)for(s=e.toLowerCase().split(" "),l=s.length;l--;)e=s[l],o=n[r],o||(o=n[r]={}),a=o[e],a||(a=o[e]=[],n.bindNative&&i[e]&&n.bindNative(e)),a.push(t);return n},off:function(e,t){var n=this,o,a=n[r],s,l,c,d;if(a)if(e)for(c=e.toLowerCase().split(" "),o=c.length;o--;){if(e=c[o],s=a[e],!e){for(l in a)a[e].length=0;return n}if(s){if(t)for(d=s.length;d--;)s[d]===t&&s.splice(d,1);else s.length=0;!s.length&&n.unbindNative&&i[e]&&(n.unbindNative(e),delete a[e])}}else{if(n.unbindNative)for(e in a)n.unbindNative(e);n[r]=[]}return n},hasEventListeners:function(e){var t=this[r];return e=e.toLowerCase(),!(!t||!t[e]||0===t[e].length)}}}),r(rt,[f,h],function(e,t){var n=e.each,r=e.explode,i={f9:120,f10:121,f11:122};return function(o){var a=this,s={};o.on("keyup keypress keydown",function(e){(e.altKey||e.ctrlKey||e.metaKey)&&n(s,function(n){var r=t.mac?e.metaKey:e.ctrlKey;if(n.ctrl==r&&n.alt==e.altKey&&n.shift==e.shiftKey)return e.keyCode==n.keyCode||e.charCode&&e.charCode==n.charCode?(e.preventDefault(),"keydown"==e.type&&n.func.call(n.scope),!0):void 0})}),a.add=function(t,a,l,c){var d;return d=l,"string"==typeof l?l=function(){o.execCommand(d,!1,null)}:e.isArray(d)&&(l=function(){o.execCommand(d[0],d[1],d[2])}),n(r(t.toLowerCase()),function(e){var t={func:l,scope:c||o,desc:o.translate(a),alt:!1,ctrl:!1,shift:!1};n(r(e,"+"),function(e){switch(e){case"alt":case"ctrl":case"shift":t[e]=!0;break;default:t.charCode=e.charCodeAt(0),t.keyCode=i[e]||e.toUpperCase().charCodeAt(0)}}),s[(t.ctrl?"ctrl":"")+","+(t.alt?"alt":"")+","+(t.shift?"shift":"")+","+t.keyCode]=t}),!0}}}),r(it,[v,b,C,k,E,A,L,H,M,D,P,O,y,d,et,x,_,tt,h,f,nt,rt],function(e,n,r,i,o,a,s,l,c,d,u,f,p,m,h,g,v,y,b,C,x,w){function _(e,t){return"selectionchange"==t?e.getDoc():!e.inline&&/^mouse|click|contextmenu|drop/.test(t)?e.getDoc():e.getBody()}function N(e,t,r){var i=this,o,a;o=i.documentBaseUrl=r.documentBaseURL,a=r.baseURI,i.settings=t=T({id:e,theme:"modern",delta_width:0,delta_height:0,popup_css:"",plugins:"",document_base_url:o,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:"",visual:!0,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",forced_root_block:"p",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:"30px",inline_styles:!0,convert_fonts_to_spans:!0,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",validate:!0,entity_encoding:"named",url_converter:i.convertURL,url_converter_scope:i,ie7_compat:!0},t),n.language=t.language||"en",n.languageLoad=t.language_load,n.baseURL=r.baseURL,i.id=t.id=e,i.isNotDirty=!0,i.plugins={},i.documentBaseURI=new f(t.document_base_url||o,{base_uri:a}),i.baseURI=a,i.contentCSS=[],i.contentStyles=[],i.shortcuts=new w(i),i.execCommands={},i.queryStateCommands={},i.queryValueCommands={},i.loadedCSS={},i.suffix=r.suffix,i.editorManager=r,i.inline=t.inline,r.fire("SetupEditor",i),i.execCallback("setup",i)}var E=e.DOM,k=n.ThemeManager,S=n.PluginManager,T=C.extend,R=C.each,A=C.explode,B=C.inArray,L=C.trim,H=C.resolve,M=m.Event,D=b.gecko,P=b.ie;return N.prototype={render:function(){function e(){E.unbind(window,"ready",e),n.render()}function t(){var e=p.ScriptLoader;if(r.language&&"en"!=r.language&&!r.language_url&&(r.language_url=n.editorManager.baseURL+"/langs/"+r.language+".js"),r.language_url&&e.add(r.language_url),r.theme&&"function"!=typeof r.theme&&"-"!=r.theme.charAt(0)&&!k.urls[r.theme]){var t=r.theme_url;t=t?n.documentBaseURI.toAbsolute(t):"themes/"+r.theme+"/theme"+o+".js",k.load(r.theme,t)}C.isArray(r.plugins)&&(r.plugins=r.plugins.join(" ")),R(r.external_plugins,function(e,t){S.load(t,e),r.plugins+=" "+t}),R(r.plugins.split(/[ ,]/),function(e){if(e=L(e),e&&!S.urls[e])if("-"==e.charAt(0)){e=e.substr(1,e.length);var t=S.dependencies(e);R(t,function(e){var t={prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"};e=S.createUrl(t,e),S.load(e.resource,e)})}else S.load(e,{prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"})}),e.loadQueue(function(){n.removed||n.init()})}var n=this,r=n.settings,i=n.id,o=n.suffix;if(!M.domLoaded)return void E.bind(window,"ready",e);if(n.getElement()&&b.contentEditable){r.inline?n.inline=!0:(n.orgVisibility=n.getElement().style.visibility,n.getElement().style.visibility="hidden");var a=n.getElement().form||E.getParent(i,"form");a&&(n.formElement=a,r.hidden_input&&!/TEXTAREA|INPUT/i.test(n.getElement().nodeName)&&(E.insertAfter(E.create("input",{type:"hidden",name:i}),i),n.hasHiddenInput=!0),n.formEventDelegate=function(e){n.fire(e.type,e)},E.bind(a,"submit reset",n.formEventDelegate),n.on("reset",function(){n.setContent(n.startContent,{format:"raw"})}),!r.submit_patch||a.submit.nodeType||a.submit.length||a._mceOldSubmit||(a._mceOldSubmit=a.submit,a.submit=function(){return n.editorManager.triggerSave(),n.isNotDirty=!0,a._mceOldSubmit(a)})),n.windowManager=new h(n),"xml"==r.encoding&&n.on("GetContent",function(e){e.save&&(e.content=E.encode(e.content))}),r.add_form_submit_trigger&&n.on("submit",function(){n.initialized&&n.save()}),r.add_unload_trigger&&(n._beforeUnload=function(){!n.initialized||n.destroyed||n.isHidden()||n.save({format:"raw",no_events:!0,set_dirty:!1})},n.editorManager.on("BeforeUnload",n._beforeUnload)),t()}},init:function(){function e(n){var r=S.get(n),i,o;i=S.urls[n]||t.documentBaseUrl.replace(/\/$/,""),n=L(n),r&&-1===B(m,n)&&(R(S.dependencies(n),function(t){e(t)}),o=new r(t,i),t.plugins[n]=o,o.init&&(o.init(t,i),m.push(n)))}var t=this,n=t.settings,r=t.getElement(),i,o,a,s,l,c,d,u,f,p,m=[];if(t.rtl=this.editorManager.i18n.rtl,t.editorManager.add(t),n.aria_label=n.aria_label||E.getAttrib(r,"aria-label",t.getLang("aria.rich_text_area")),n.theme&&("function"!=typeof n.theme?(n.theme=n.theme.replace(/-/,""),l=k.get(n.theme),t.theme=new l(t,k.urls[n.theme]),t.theme.init&&t.theme.init(t,k.urls[n.theme]||t.documentBaseUrl.replace(/\/$/,""))):t.theme=n.theme),R(n.plugins.replace(/\-/g,"").split(/[ ,]/),e),n.render_ui&&t.theme&&(t.orgDisplay=r.style.display,"function"!=typeof n.theme?(i=n.width||r.style.width||r.offsetWidth,o=n.height||r.style.height||r.offsetHeight,a=n.min_height||100,f=/^[0-9\.]+(|px)$/i,f.test(""+i)&&(i=Math.max(parseInt(i,10)+(l.deltaWidth||0),100)),f.test(""+o)&&(o=Math.max(parseInt(o,10)+(l.deltaHeight||0),a)),l=t.theme.renderUI({targetNode:r,width:i,height:o,deltaWidth:n.delta_width,deltaHeight:n.delta_height}),n.content_editable||(E.setStyles(l.sizeContainer||l.editorContainer,{wi2dth:i,h2eight:o}),o=(l.iframeHeight||o)+("number"==typeof o?l.deltaHeight||0:""),a>o&&(o=a))):(l=n.theme(t,r),l.editorContainer.nodeType&&(l.editorContainer=l.editorContainer.id=l.editorContainer.id||t.id+"_parent"),l.iframeContainer.nodeType&&(l.iframeContainer=l.iframeContainer.id=l.iframeContainer.id||t.id+"_iframecontainer"),o=l.iframeHeight||r.offsetHeight),t.editorContainer=l.editorContainer),n.content_css&&R(A(n.content_css),function(e){t.contentCSS.push(t.documentBaseURI.toAbsolute(e))}),n.content_style&&t.contentStyles.push(n.content_style),n.content_editable)return r=s=l=null,t.initContentBody();for(t.iframeHTML=n.doctype+"",n.document_base_url!=t.documentBaseUrl&&(t.iframeHTML+=''),!b.caretAfter&&n.ie7_compat&&(t.iframeHTML+=''),t.iframeHTML+='',p=0;p',t.loadedCSS[h]=!0}d=n.body_id||"tinymce",-1!=d.indexOf("=")&&(d=t.getParam("body_id","","hash"),d=d[t.id]||d),u=n.body_class||"",-1!=u.indexOf("=")&&(u=t.getParam("body_class","","hash"),u=u[t.id]||""),t.iframeHTML+='
";var g='javascript:(function(){document.open();document.domain="'+document.domain+'";var ed = window.parent.tinymce.get("'+t.id+'");document.write(ed.iframeHTML);document.close();ed.initContentBody(true);})()';if(document.domain!=location.hostname&&(c=g),s=E.add(l.iframeContainer,"iframe",{id:t.id+"_ifr",src:c||'javascript:""',frameBorder:"0",allowTransparency:"true",title:t.editorManager.translate("Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"),style:{width:"100%",height:o,display:"block"}}),P)try{t.getDoc() +}catch(v){s.src=c=g}t.contentAreaContainer=l.iframeContainer,l.editorContainer&&(E.get(l.editorContainer).style.display=t.orgDisplay),E.get(t.id).style.display="none",E.setAttrib(t.id,"aria-hidden",!0),c||t.initContentBody(),r=s=l=null},initContentBody:function(t){var n=this,o=n.settings,f=E.get(n.id),p=n.getDoc(),m,h;o.inline||(n.getElement().style.visibility=n.orgVisibility),t||o.content_editable||(p.open(),p.write(n.iframeHTML),p.close()),o.content_editable&&(n.on("remove",function(){var e=this.getBody();E.removeClass(e,"mce-content-body"),E.removeClass(e,"mce-edit-focus"),E.setAttrib(e,"tabIndex",null),E.setAttrib(e,"contentEditable",null)}),E.addClass(f,"mce-content-body"),f.tabIndex=-1,n.contentDocument=p=o.content_document||document,n.contentWindow=o.content_window||window,n.bodyElement=f,o.content_document=o.content_window=null,o.root_name=f.nodeName.toLowerCase()),m=n.getBody(),m.disabled=!0,o.readonly||(n.inline&&"static"==E.getStyle(m,"position",!0)&&(m.style.position="relative"),m.contentEditable=n.getParam("content_editable_state",!0)),m.disabled=!1,n.schema=new g(o),n.dom=new e(p,{keep_values:!0,url_converter:n.convertURL,url_converter_scope:n,hex_colors:o.force_hex_style_colors,class_filter:o.class_filter,update_styles:!0,root_element:o.content_editable?n.id:null,collect:o.content_editable,schema:n.schema,onSetAttrib:function(e){n.fire("SetAttrib",e)}}),n.parser=new v(o,n.schema),n.parser.addAttributeFilter("src,href,style",function(e,t){for(var r=e.length,i,o=n.dom,a,s;r--;)i=e[r],a=i.attr(t),s="data-mce-"+t,i.attributes.map[s]||("style"===t?i.attr(s,o.serializeStyle(o.parseStyle(a),i.name)):i.attr(s,n.convertURL(a,t,i.name)))}),n.parser.addNodeFilter("script",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr("type","mce-"+(n.attr("type")||"text/javascript"))}),n.parser.addNodeFilter("#cdata",function(e){for(var t=e.length,n;t--;)n=e[t],n.type=8,n.name="#comment",n.value="[CDATA["+n.value+"]]"}),n.parser.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(e){for(var t=e.length,i,o=n.schema.getNonEmptyElements();t--;)i=e[t],i.isEmpty(o)&&(i.empty().append(new r("br",1)).shortEnded=!0)}),n.serializer=new i(o,n),n.selection=new a(n.dom,n.getWin(),n.serializer,n),n.formatter=new s(n),n.undoManager=new l(n),n.forceBlocks=new d(n),n.enterKey=new c(n),n.editorCommands=new u(n),n.fire("PreInit"),o.browser_spellcheck||o.gecko_spellcheck||(p.body.spellcheck=!1,E.setAttrib(m,"spellcheck","false")),n.fire("PostRender"),n.quirks=y(n),o.directionality&&(m.dir=o.directionality),o.nowrap&&(m.style.whiteSpace="nowrap"),o.protect&&n.on("BeforeSetContent",function(e){R(o.protect,function(t){e.content=e.content.replace(t,function(e){return""})})}),n.on("SetContent",function(){n.addVisual(n.getBody())}),o.padd_empty_editor&&n.on("PostProcess",function(e){e.content=e.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/,"")}),n.load({initial:!0,format:"html"}),n.startContent=n.getContent({format:"raw"}),n.initialized=!0,R(n._pendingNativeEvents,function(e){n.dom.bind(_(n,e),e,function(e){n.fire(e.type,e)})}),n.fire("init"),n.focus(!0),n.nodeChanged({initial:!0}),n.execCallback("init_instance_callback",n),n.contentStyles.length>0&&(h="",R(n.contentStyles,function(e){h+=e+"\r\n"}),n.dom.addStyle(h)),R(n.contentCSS,function(e){n.loadedCSS[e]||(n.dom.loadCSS(e),n.loadedCSS[e]=!0)}),o.auto_focus&&setTimeout(function(){var e=n.editorManager.get(o.auto_focus);e.selection.select(e.getBody(),1),e.selection.collapse(1),e.getBody().focus(),e.getWin().focus()},100),f=p=m=null},focus:function(e){var t,n=this,r=n.selection,i=n.settings.content_editable,o,a,s=n.getDoc(),l;e||(o=r.getRng(),o.item&&(a=o.item(0)),n._refreshContentEditable(),i||(b.opera||n.getBody().focus(),n.getWin().focus()),(D||i)&&(l=n.getBody(),l.setActive&&b.ie<11?l.setActive():l.focus(),i&&r.normalize()),a&&a.ownerDocument==s&&(o=s.body.createControlRange(),o.addElement(a),o.select())),n.editorManager.activeEditor!=n&&((t=n.editorManager.activeEditor)&&t.fire("deactivate",{relatedTarget:n}),n.fire("activate",{relatedTarget:t})),n.editorManager.activeEditor=n},execCallback:function(e){var t=this,n=t.settings[e],r;if(n)return t.callbackLookup&&(r=t.callbackLookup[e])&&(n=r.func,r=r.scope),"string"==typeof n&&(r=n.replace(/\.\w+$/,""),r=r?H(r):0,n=H(n),t.callbackLookup=t.callbackLookup||{},t.callbackLookup[e]={func:n,scope:r}),n.apply(r||t,Array.prototype.slice.call(arguments,1))},translate:function(e){var t=this.settings.language||"en",n=this.editorManager.i18n;return e?n.data[t+"."+e]||e.replace(/\{\#([^\}]+)\}/g,function(e,r){return n.data[t+"."+r]||"{#"+r+"}"}):""},getLang:function(e,n){return this.editorManager.i18n.data[(this.settings.language||"en")+"."+e]||(n!==t?n:"{#"+e+"}")},getParam:function(e,t,n){var r=e in this.settings?this.settings[e]:t,i;return"hash"===n?(i={},"string"==typeof r?R(r.split(r.indexOf("=")>0?/[;,](?![^=;,]*(?:[;,]|$))/:","),function(e){e=e.split("="),i[L(e[0])]=L(e.length>1?e[1]:e)}):i=r,i):r},nodeChanged:function(){var e=this,t=e.selection,n,r,i;e.initialized&&!e.settings.disable_nodechange&&(i=e.getBody(),n=t.getStart()||i,n=P&&n.ownerDocument!=e.getDoc()?e.getBody():n,"IMG"==n.nodeName&&t.isCollapsed()&&(n=n.parentNode),r=[],e.dom.getParent(n,function(e){return e===i?!0:void r.push(e)}),e.fire("NodeChange",{element:n,parents:r}))},addButton:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),t.text||t.icon||(t.icon=e),n.buttons=n.buttons||{},t.tooltip=t.tooltip||t.title,n.buttons[e]=t},addMenuItem:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),n.menuItems=n.menuItems||{},n.menuItems[e]=t},addCommand:function(e,t,n){this.execCommands[e]={func:t,scope:n||this}},addQueryStateHandler:function(e,t,n){this.queryStateCommands[e]={func:t,scope:n||this}},addQueryValueHandler:function(e,t,n){this.queryValueCommands[e]={func:t,scope:n||this}},addShortcut:function(e,t,n,r){this.shortcuts.add(e,t,n,r)},execCommand:function(e,t,n,r){var i=this,o=0,a;return/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(e)||r&&r.skip_focus||i.focus(),r=T({},r),r=i.fire("BeforeExecCommand",{command:e,ui:t,value:n}),r.isDefaultPrevented()?!1:(a=i.execCommands[e])&&a.func.call(a.scope,t,n)!==!0?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(R(i.plugins,function(r){return r.execCommand&&r.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),o=!0,!1):void 0}),o?o:i.theme&&i.theme.execCommand&&i.theme.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):i.editorCommands.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(i.getDoc().execCommand(e,t,n),void i.fire("ExecCommand",{command:e,ui:t,value:n})))},queryCommandState:function(e){var t=this,n,r;if(!t._isHidden()){if((n=t.queryStateCommands[e])&&(r=n.func.call(n.scope),r!==!0))return r;if(r=t.editorCommands.queryCommandState(e),-1!==r)return r;try{return t.getDoc().queryCommandState(e)}catch(i){}}},queryCommandValue:function(e){var n=this,r,i;if(!n._isHidden()){if((r=n.queryValueCommands[e])&&(i=r.func.call(r.scope),i!==!0))return i;if(i=n.editorCommands.queryCommandValue(e),i!==t)return i;try{return n.getDoc().queryCommandValue(e)}catch(o){}}},show:function(){var e=this;E.show(e.getContainer()),E.hide(e.id),e.load(),e.fire("show")},hide:function(){var e=this,t=e.getDoc();P&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),E.hide(e.getContainer()),E.setStyle(e.id,"display",e.orgDisplay),e.fire("hide")},isHidden:function(){return!E.isHidden(this.id)},setProgressState:function(e,t){this.fire("ProgressState",{state:e,time:t})},load:function(e){var n=this,r=n.getElement(),i;return r?(e=e||{},e.load=!0,i=n.setContent(r.value!==t?r.value:r.innerHTML,e),e.element=r,e.no_events||n.fire("LoadContent",e),e.element=r=null,i):void 0},save:function(e){var t=this,n=t.getElement(),r,i;if(n&&t.initialized)return e=e||{},e.save=!0,e.element=n,r=e.content=t.getContent(e),e.no_events||t.fire("SaveContent",e),r=e.content,/TEXTAREA|INPUT/i.test(n.nodeName)?n.value=r:(n.innerHTML=r,(i=E.getParent(t.id,"form"))&&R(i.elements,function(e){return e.name==t.id?(e.value=r,!1):void 0})),e.element=n=null,e.set_dirty!==!1&&(t.isNotDirty=!0),r},setContent:function(e,t){var n=this,r=n.getBody(),i;return t=t||{},t.format=t.format||"html",t.set=!0,t.content=e,t.no_events||n.fire("BeforeSetContent",t),e=t.content,0===e.length||/^\s+$/.test(e)?(i=n.settings.forced_root_block,i&&n.schema.isValidChild(r.nodeName.toLowerCase(),i.toLowerCase())?(e=P&&11>P?"":'
',e=n.dom.createHTML(i,n.settings.forced_root_block_attrs,e)):(!P||11>P)&&(e='
'),r.innerHTML=e,n.fire("SetContent",t)):("raw"!==t.format&&(e=new o({},n.schema).serialize(n.parser.parse(e,{isRootContent:!0}))),t.content=L(e),n.dom.setHTML(r,t.content),t.no_events||n.fire("SetContent",t)),t.content},getContent:function(e){var t=this,n,r=t.getBody();return e=e||{},e.format=e.format||"html",e.get=!0,e.getInner=!0,e.no_events||t.fire("BeforeGetContent",e),n="raw"==e.format?r.innerHTML:"text"==e.format?r.innerText||r.textContent:t.serializer.serialize(r,e),e.content="text"!=e.format?L(n):n,e.no_events||t.fire("GetContent",e),e.content},insertContent:function(e){this.execCommand("mceInsertContent",!1,e)},isDirty:function(){return!this.isNotDirty},getContainer:function(){var e=this;return e.container||(e.container=E.get(e.editorContainer||e.id+"_parent")),e.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return E.get(this.settings.content_element||this.id)},getWin:function(){var e=this,t;return e.contentWindow||(t=E.get(e.id+"_ifr"),t&&(e.contentWindow=t.contentWindow)),e.contentWindow},getDoc:function(){var e=this,t;return e.contentDocument||(t=e.getWin(),t&&(e.contentDocument=t.document)),e.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(e,t,n){var r=this,i=r.settings;return i.urlconverter_callback?r.execCallback("urlconverter_callback",e,n,!0,t):!i.convert_urls||n&&"LINK"==n.nodeName||0===e.indexOf("file:")||0===e.length?e:i.relative_urls?r.documentBaseURI.toRelative(e):e=r.documentBaseURI.toAbsolute(e,i.remove_script_host)},addVisual:function(e){var n=this,r=n.settings,i=n.dom,o;e=e||n.getBody(),n.hasVisual===t&&(n.hasVisual=r.visual),R(i.select("table,a",e),function(e){var t;switch(e.nodeName){case"TABLE":return o=r.visual_table_class||"mce-item-table",t=i.getAttrib(e,"border"),void(t&&"0"!=t||(n.hasVisual?i.addClass(e,o):i.removeClass(e,o)));case"A":return void(i.getAttrib(e,"href",!1)||(t=i.getAttrib(e,"name")||e.id,o="mce-item-anchor",t&&(n.hasVisual?i.addClass(e,o):i.removeClass(e,o))))}}),n.fire("VisualAid",{element:e,hasVisual:n.hasVisual})},remove:function(){var e=this;if(!e.removed){e.removed=1,e.hasHiddenInput&&E.remove(e.getElement().nextSibling);var t=e.getDoc();P&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),E.setStyle(e.id,"display",e.orgDisplay),e.settings.content_editable||(M.unbind(e.getWin()),M.unbind(e.getDoc()));var n=e.getContainer();M.unbind(e.getBody()),M.unbind(n),e.fire("remove"),e.editorManager.remove(e),E.remove(n),e.destroy()}},bindNative:function(e){var t=this;t.settings.readonly||(t.initialized?t.dom.bind(_(t,e),e,function(n){t.fire(e,n)}):t._pendingNativeEvents?t._pendingNativeEvents.push(e):t._pendingNativeEvents=[e])},unbindNative:function(e){var t=this;t.initialized&&t.dom.unbind(e)},destroy:function(e){var t=this,n;if(!t.destroyed){if(!e&&!t.removed)return void t.remove();e&&D&&(M.unbind(t.getDoc()),M.unbind(t.getWin()),M.unbind(t.getBody())),e||(t.editorManager.off("beforeunload",t._beforeUnload),t.theme&&t.theme.destroy&&t.theme.destroy(),t.selection.destroy(),t.dom.destroy()),n=t.formElement,n&&(n._mceOldSubmit&&(n.submit=n._mceOldSubmit,n._mceOldSubmit=null),E.unbind(n,"submit reset",t.formEventDelegate)),t.contentAreaContainer=t.formElement=t.container=null,t.settings.content_element=t.bodyElement=t.contentDocument=t.contentWindow=null,t.selection&&(t.selection=t.selection.win=t.selection.dom=t.selection.dom.doc=null),t.destroyed=1}},_refreshContentEditable:function(){var e=this,t,n;e._isHidden()&&(t=e.getBody(),n=t.parentNode,n.removeChild(t),n.appendChild(t),t.focus())},_isHidden:function(){var e;return D?(e=this.selection.getSel(),!e||!e.rangeCount||0===e.rangeCount):0}},T(N.prototype,x),N}),r(ot,[],function(){var e={};return{rtl:!1,add:function(t,n){for(var r in n)e[r]=n[r];this.rtl=this.rtl||"rtl"===e._dir},translate:function(t){if("undefined"==typeof t)return t;if("string"!=typeof t&&t.raw)return t.raw;if(t.push){var n=t.slice(1);t=(e[t[0]]||t[0]).replace(/\{([^\}]+)\}/g,function(e,t){return n[t]})}return e[t]||t},data:e}}),r(at,[v,h],function(e,t){function n(r){function i(){try{return document.activeElement}catch(e){return document.body}}function o(e){return e&&e.startContainer?{startContainer:e.startContainer,startOffset:e.startOffset,endContainer:e.endContainer,endOffset:e.endOffset}:e}function a(e,t){var n;return t.startContainer?(n=e.getDoc().createRange(),n.setStart(t.startContainer,t.startOffset),n.setEnd(t.endContainer,t.endOffset)):n=t,n}function s(t){return!!e.DOM.getParent(t,n.isEditorUIElement)}function l(e,t){for(var n=t.getBody();e;){if(e==n)return!0;e=e.parentNode}}function c(n){var c=n.editor,d;c.on("init",function(){"onbeforedeactivate"in document&&t.ie<11?c.dom.bind(c.getBody(),"beforedeactivate",function(){try{c.lastRng=c.selection.getRng()}catch(e){}c.selection.lastFocusBookmark=o(c.lastRng)}):(c.inline||t.ie>10)&&(c.on("nodechange keyup",function(){var e=document.activeElement;e&&e.id==c.id+"_ifr"&&(e=c.getBody()),l(e,c)&&(c.lastRng=c.selection.getRng())}),t.webkit&&(d=function(){var e=c.selection.getRng();e.collapsed||(c.lastRng=e)},e.DOM.bind(document,"selectionchange",d),c.on("remove",function(){e.DOM.unbind(document,"selectionchange",d)})))}),c.on("setcontent",function(){c.lastRng=null}),c.on("mousedown",function(){c.selection.lastFocusBookmark=null}),c.on("focusin",function(){var e=r.focusedEditor;c.selection.lastFocusBookmark&&(c.selection.setRng(a(c,c.selection.lastFocusBookmark)),c.selection.lastFocusBookmark=null),e!=c&&(e&&e.fire("blur",{focusedEditor:c}),r.activeEditor=c,r.focusedEditor=c,c.fire("focus",{blurredEditor:e}),c.focus(!1)),c.lastRng=null}),c.on("focusout",function(){window.setTimeout(function(){var e=r.focusedEditor;s(i())||e!=c||(c.fire("blur",{focusedEditor:null}),r.focusedEditor=null,c.selection&&(c.selection.lastFocusBookmark=null))},0)})}e.DOM.bind(document,"focusin",function(e){var t=r.activeEditor;t&&e.target.ownerDocument==document&&(t.selection&&(t.selection.lastFocusBookmark=o(t.lastRng)),s(e.target)||r.focusedEditor!=t||(t.fire("blur",{focusedEditor:null}),r.focusedEditor=null))}),r.on("AddEditor",c)}return n.isEditorUIElement=function(e){return-1!==e.className.indexOf("mce-")},n}),r(st,[it,v,O,h,f,nt,ot,at],function(e,n,r,i,o,a,s,l){var c=n.DOM,d=o.explode,u=o.each,f=o.extend,p=0,m,h={majorVersion:"4",minorVersion:"0.16",releaseDate:"2014-01-31",editors:[],i18n:s,activeEditor:null,setup:function(){var e=this,t,n,i="",o;if(n=document.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,""),/[\/\\]$/.test(n)||(n+="/"),o=window.tinymce||window.tinyMCEPreInit)t=o.base||o.baseURL,i=o.suffix;else for(var a=document.getElementsByTagName("script"),s=0;s0&&u(d(m),function(n){c.get(n)?(l=new e(n,t,a),s.push(l),l.render(!0)):u(document.forms,function(r){u(r.elements,function(r){r.name===n&&(n="mce_editor_"+p++,c.setAttrib(r,"id",n),l=new e(n,t,a),s.push(l),l.render(1))})})});break;case"textareas":case"specific_textareas":u(c.select("textarea"),function(r){t.editor_deselector&&i(r,t.editor_deselector)||(!t.editor_selector||i(r,t.editor_selector))&&(l=new e(n(r),t,a),s.push(l),l.render(!0))})}t.oninit&&(m=h=0,u(s,function(e){h++,e.initialized?m++:e.on("init",function(){m++,m==h&&r(t,"oninit")}),m==h&&r(t,"oninit")}))}var a=this,s=[],l;a.settings=t,c.bind(window,"ready",o)},get:function(e){return e===t?this.editors:this.editors[e]},add:function(e){var t=this,n=t.editors;return n[e.id]=e,n.push(e),t.activeEditor=e,t.fire("AddEditor",{editor:e}),m||(m=function(){t.fire("BeforeUnload")},c.bind(window,"beforeunload",m)),e},createEditor:function(t,n){return this.add(new e(t,n,this))},remove:function(e){var t=this,n,r=t.editors,i,o;{if(e){if("string"==typeof e)return e=e.selector||e,void u(c.select(e),function(e){t.remove(r[e.id])});if(i=e,!r[i.id])return null;for(delete r[i.id],n=0;n=0;n--)t.remove(r[n])}},execCommand:function(t,n,r){var i=this,o=i.get(r);switch(t){case"mceAddEditor":return i.get(r)||new e(r,i.settings,i).render(),!0;case"mceRemoveEditor":return o&&o.remove(),!0;case"mceToggleEditor":return o?(o.isHidden()?o.show():o.hide(),!0):(i.execCommand("mceAddEditor",0,r),!0)}return i.activeEditor?i.activeEditor.execCommand(t,n,r):!1},triggerSave:function(){u(this.editors,function(e){e.save()})},addI18n:function(e,t){s.add(e,t)},translate:function(e){return s.translate(e)}};return f(h,a),h.setup(),window.tinymce=window.tinyMCE=h,h}),r(lt,[st,f],function(e,t){var n=t.each,r=t.explode;e.on("AddEditor",function(e){var t=e.editor;t.on("preInit",function(){function e(e,t){n(t,function(t,n){t&&s.setStyle(e,n,t)}),s.rename(e,"span")}function i(e){s=t.dom,l.convert_fonts_to_spans&&n(s.select("font,u,strike",e.node),function(e){o[e.nodeName.toLowerCase()](s,e)})}var o,a,s,l=t.settings;l.inline_styles&&(a=r(l.font_size_legacy_values),o={font:function(t,n){e(n,{backgroundColor:n.style.backgroundColor,color:n.color,fontFamily:n.face,fontSize:a[parseInt(n.size,10)-1]})},u:function(t,n){e(n,{textDecoration:"underline"})},strike:function(t,n){e(n,{textDecoration:"line-through"})}},t.on("PreProcess SetContent",i))})})}),r(ct,[],function(){return{send:function(e){function t(){!e.async||4==n.readyState||r++>1e4?(e.success&&1e4>r&&200==n.status?e.success.call(e.success_scope,""+n.responseText,n,e):e.error&&e.error.call(e.error_scope,r>1e4?"TIMED_OUT":"GENERAL",n,e),n=null):setTimeout(t,10)}var n,r=0;if(e.scope=e.scope||this,e.success_scope=e.success_scope||e.scope,e.error_scope=e.error_scope||e.scope,e.async=e.async===!1?!1:!0,e.data=e.data||"",n=new XMLHttpRequest){if(n.overrideMimeType&&n.overrideMimeType(e.content_type),n.open(e.type||(e.data?"POST":"GET"),e.url,e.async),e.content_type&&n.setRequestHeader("Content-Type",e.content_type),n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.send(e.data),!e.async)return t();setTimeout(t,10)}}}}),r(dt,[],function(){function e(t,n){var r,i,o,a;if(n=n||'"',null===t)return"null";if(o=typeof t,"string"==o)return i="\bb t\nn\ff\rr\"\"''\\\\",n+t.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g,function(e,t){return'"'===n&&"'"===e?e:(r=i.indexOf(t),r+1?"\\"+i.charAt(r+1):(e=t.charCodeAt().toString(16),"\\u"+"0000".substring(e.length)+e))})+n;if("object"==o){if(t.hasOwnProperty&&"[object Array]"===Object.prototype.toString.call(t)){for(r=0,i="[";r0?",":"")+e(t[r],n);return i+"]"}i="{";for(a in t)t.hasOwnProperty(a)&&(i+="function"!=typeof t[a]?(i.length>1?","+n:n)+a+n+":"+e(t[a],n):"");return i+"}"}return""+t}return{serialize:e,parse:function(e){try{return window[String.fromCharCode(101)+"val"]("("+e+")")}catch(t){}}}}),r(ut,[dt,ct,f],function(e,t,n){function r(e){this.settings=i({},e),this.count=0}var i=n.extend;return r.sendRPC=function(e){return(new r).send(e)},r.prototype={send:function(n){var r=n.error,o=n.success;n=i(this.settings,n),n.success=function(t,i){t=e.parse(t),"undefined"==typeof t&&(t={error:"JSON Parse error."}),t.error?r.call(n.error_scope||n.scope,t.error,i):o.call(n.success_scope||n.scope,t.result)},n.error=function(e,t){r&&r.call(n.error_scope||n.scope,e,t)},n.data=e.serialize({id:n.id||"c"+this.count++,method:n.method,params:n.params}),n.content_type="application/json",t.send(n)}},r}),r(ft,[v],function(e){return{callbacks:{},count:0,send:function(n){var r=this,i=e.DOM,o=n.count!==t?n.count:r.count,a="tinymce_jsonp_"+o;r.callbacks[o]=function(e){i.remove(a),delete r.callbacks[o],n.callback(e)},i.add(i.doc.body,"script",{id:a,src:n.url,type:"text/javascript"}),r.count++}}}),r(pt,[],function(){function e(){s=[];for(var e in a)s.push(e);i.length=s.length}function n(){function n(e){var n,r;return r=e!==t?d+e:i.indexOf(",",d),-1===r||r>i.length?null:(n=i.substring(d,r),d=r+1,n)}var r,i,s,d=0;if(a={},c){o.load(l),i=o.getAttribute(l)||"";do{var u=n();if(null===u)break;if(r=n(parseInt(u,32)||0),null!==r){if(u=n(),null===u)break;s=n(parseInt(u,32)||0),r&&(a[r]=s)}}while(null!==r);e()}}function r(){var t,n="";if(c){for(var r in a)t=a[r],n+=(n?",":"")+r.length.toString(32)+","+r+","+t.length.toString(32)+","+t;o.setAttribute(l,n);try{o.save(l)}catch(i){}e()}}var i,o,a,s,l,c;try{if(window.localStorage)return localStorage}catch(d){}return l="tinymce",o=document.documentElement,c=!!o.addBehavior,c&&o.addBehavior("#default#userData"),i={key:function(e){return s[e]},getItem:function(e){return e in a?a[e]:null},setItem:function(e,t){a[e]=""+t,r()},removeItem:function(e){delete a[e],r()},clear:function(){a={},r()}},n(),i}),r(mt,[v,d,y,b,f,h],function(e,t,n,r,i,o){var a=window.tinymce;return a.DOM=e.DOM,a.ScriptLoader=n.ScriptLoader,a.PluginManager=r.PluginManager,a.ThemeManager=r.ThemeManager,a.dom=a.dom||{},a.dom.Event=t.Event,i.each(i,function(e,t){a[t]=e}),i.each("isOpera isWebKit isIE isGecko isMac".split(" "),function(e){a[e]=o[e.substr(2).toLowerCase()]}),{}}),r(ht,[I,f],function(e,t){return e.extend({Defaults:{firstControlClass:"first",lastControlClass:"last"},init:function(e){this.settings=t.extend({},this.Defaults,e)},preRender:function(e){e.addClass(this.settings.containerClass,"body")},applyClasses:function(e){var t=this,n=t.settings,r,i,o;r=e.items().filter(":visible"),i=n.firstControlClass,o=n.lastControlClass,r.each(function(e){e.removeClass(i).removeClass(o),n.controlClass&&e.addClass(n.controlClass)}),r.eq(0).addClass(i),r.eq(-1).addClass(o)},renderHtml:function(e){var t=this,n=t.settings,r,i="";return r=e.items(),r.eq(0).addClass(n.firstControlClass),r.eq(-1).addClass(n.lastControlClass),r.each(function(e){n.controlClass&&e.addClass(n.controlClass),i+=e.renderHtml()}),i},recalc:function(){},postRender:function(){}})}),r(gt,[ht],function(e){return e.extend({Defaults:{containerClass:"abs-layout",controlClass:"abs-layout-item"},recalc:function(e){e.items().filter(":visible").each(function(e){var t=e.settings;e.layoutRect({x:t.x,y:t.y,w:t.w,h:t.h}),e.recalc&&e.recalc()})},renderHtml:function(e){return'
'+this._super(e)}})}),r(vt,[V,Y],function(e,t){return e.extend({Mixins:[t],Defaults:{classes:"widget tooltip tooltip-n"},text:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().lastChild.innerHTML=t.encode(e)),t):t._value},renderHtml:function(){var e=this,t=e.classPrefix;return'"},repaint:function(){var e=this,t,n;t=e.getEl().style,n=e._layoutRect,t.left=n.x+"px",t.top=n.y+"px",t.zIndex=131070}})}),r(yt,[V,vt],function(e,t){var n,r=e.extend({init:function(e){var t=this;t._super(e),t.canFocus=!0,e.tooltip&&r.tooltips!==!1&&(t.on("mouseenter",function(n){var r=t.tooltip().moveTo(-65535);if(n.control==t){var i=r.text(e.tooltip).show().testMoveRel(t.getEl(),["bc-tc","bc-tl","bc-tr"]);r.toggleClass("tooltip-n","bc-tc"==i),r.toggleClass("tooltip-nw","bc-tl"==i),r.toggleClass("tooltip-ne","bc-tr"==i),r.moveRel(t.getEl(),i)}else r.hide()}),t.on("mouseleave mousedown click",function(){t.tooltip().hide()})),t.aria("label",e.tooltip)},tooltip:function(){var e=this;return n||(n=new t({type:"tooltip"}),n.renderTo(e.getContainerElm())),n},active:function(e){var t=this,n;return e!==n&&(t.aria("pressed",e),t.toggleClass("active",e)),t._super(e)},disabled:function(e){var t=this,n;return e!==n&&(t.aria("disabled",e),t.toggleClass("disabled",e)),t._super(e)},postRender:function(){var e=this,t=e.settings;e._rendered=!0,e._super(),e.parent()||!t.width&&!t.height||(e.initLayoutRect(),e.repaint()),t.autofocus&&setTimeout(function(){e.focus()},0)},remove:function(){this._super(),n&&(n.remove(),n=null)}});return r}),r(bt,[yt],function(e){return e.extend({Defaults:{classes:"widget btn",role:"button"},init:function(e){var t=this,n;t.on("click mousedown",function(e){e.preventDefault()}),t._super(e),n=e.size,e.subtype&&t.addClass(e.subtype),n&&t.addClass("btn-"+n)},icon:function(e){var t=this,n=t.classPrefix;if("undefined"==typeof e)return t.settings.icon;if(t.settings.icon=e,e=e?n+"ico "+n+"i-"+t.settings.icon:"",t._rendered){var r=t.getEl().firstChild,i=r.getElementsByTagName("i")[0];e?(i&&i==r.firstChild||(i=document.createElement("i"),r.insertBefore(i,r.firstChild)),i.className=e):i&&r.removeChild(i),t.text(t._text)}return t},repaint:function(){var e=this.getEl().firstChild.style;e.width=e.height="100%",this._super()},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon,i="";return e.settings.image&&(r="none",i=" style=\"background-image: url('"+e.settings.image+"')\""),r=e.settings.icon?n+"ico "+n+"i-"+r:"",'
"}})}),r(Ct,[q],function(e){return e.extend({Defaults:{defaultType:"button",role:"toolbar"},renderHtml:function(){var e=this,t=e._layout;return e.addClass("btn-group"),e.preRender(),t.preRender(e),'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(xt,[yt],function(e){return e.extend({Defaults:{classes:"checkbox",role:"checkbox",checked:!1},init:function(e){var t=this;t._super(e),t.on("click mousedown",function(e){e.preventDefault()}),t.on("click",function(e){e.preventDefault(),t.disabled()||t.checked(!t.checked())}),t.checked(t.settings.checked)},checked:function(e){var t=this;return"undefined"!=typeof e?(e?t.addClass("checked"):t.removeClass("checked"),t._checked=e,t.aria("checked",e),t):t._checked},value:function(e){return this.checked(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix;return'
'+e.encode(e._text)+"
"}})}),r(wt,[bt,X],function(e,t){return e.extend({showPanel:function(){var e=this,n=e.settings;if(e.active(!0),e.panel)e.panel.show();else{var r=n.panel;r.type&&(r={layout:"grid",items:r}),r.popover=!0,r.autohide=!0,e.panel=new t(r).on("hide",function(){e.active(!1)}).parent(e).renderTo(e.getContainerElm()),e.panel.fire("show"),e.panel.reflow()}e.panel.moveRel(e.getEl(),n.popoverAlign||(e.isRtl()?["bc-tr","bc-tc"]:["bc-tl","bc-tc"]))},hidePanel:function(){var e=this;e.panel&&e.panel.hide()},postRender:function(){var e=this;return e.on("click",function(t){t.control===e&&(e.panel&&e.panel.visible()?e.hidePanel():e.showPanel())}),e._super()}})}),r(_t,[wt,v],function(e,t){var n=t.DOM;return e.extend({init:function(e){this._super(e),this.addClass("colorbutton")},color:function(e){return e?(this._color=e,this.getEl("preview").style.backgroundColor=e,this):this._color},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"",i=e.settings.image?" style=\"background-image: url('"+e.settings.image+"')\"":"";return'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(r){r.control!=e||n.getParent(r.target,"."+e.classPrefix+"open")||(r.stopImmediatePropagation(),t.call(e,r))}),delete e.settings.onclick,e._super()}})}),r(Nt,[yt,U,W],function(e,t,n){return e.extend({init:function(e){var t=this;t._super(e),t.addClass("combobox"),t.subinput=!0,e=t.settings,e.menu=e.menu||e.values,e.menu&&(e.icon="caret"),t.on("click",function(n){for(var r=n.target;r;)r.id&&-1!=r.id.indexOf("-open")&&(t.fire("action"),e.menu&&(t.showMenu(),n.keyboard&&t.menu.items()[0].focus())),r=r.parentNode}),t.on("keydown",function(e){"INPUT"==e.target.nodeName&&13==e.keyCode&&t.parents().reverse().each(function(n){return e.preventDefault(),t.fire("change"),n.hasEventListeners("submit")&&n.toJSON?(n.fire("submit",{data:n.toJSON()}),!1):void 0})}),e.placeholder&&(t.addClass("placeholder"),t.on("focusin",function(){t._hasOnChange||(n.on(t.getEl("inp"),"change",function(){t.fire("change")}),t._hasOnChange=!0),t.hasClass("placeholder")&&(t.getEl("inp").value="",t.removeClass("placeholder"))}),t.on("focusout",function(){0===t.value().length&&(t.getEl("inp").value=e.placeholder,t.addClass("placeholder"))}))},showMenu:function(){var e=this,n=e.settings,r;e.menu||(r=n.menu||[],r.length?r={type:"menu",items:r}:r.type=r.type||"menu",e.menu=t.create(r).parent(e).renderTo(e.getContainerElm()),e.fire("createmenu"),e.menu.reflow(),e.menu.on("cancel",function(t){t.control===e.menu&&e.focus()}),e.menu.on("show hide",function(t){t.control.items().each(function(t){t.active(t.value()==e.value())})}).fire("show"),e.menu.on("select",function(t){e.value(t.control.value())}),e.on("focusin",function(t){"INPUT"==t.target.tagName&&e.menu.hide()}),e.aria("expanded",!0)),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),e.menu.moveRel(e.getEl(),e.isRtl()?["br-tr","tr-br"]:["bl-tl","tl-bl"])},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t.removeClass("placeholder"),t._rendered&&(t.getEl("inp").value=e),t):t._rendered?(e=t.getEl("inp").value,e!=t.settings.placeholder?e:""):t._value},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl("inp").disabled=e),t._super(e)},focus:function(){this.getEl("inp").focus()},repaint:function(){var e=this,t=e.getEl(),r=e.getEl("open"),i=e.layoutRect(),o,a;o=r?i.w-n.getSize(r).width-10:i.w-10;var s=document;return s.all&&(!s.documentMode||s.documentMode<=8)&&(a=e.layoutRect().h-2+"px"),n.css(t.firstChild,{width:o,lineHeight:a}),e._super(),e},postRender:function(){var e=this;return n.on(this.getEl("inp"),"change",function(){e.fire("change")}),e._super()},remove:function(){n.off(this.getEl("inp")),this._super()},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.classPrefix,i=n.value||n.placeholder||"",o,a,s="",l="";return"spellcheck"in n&&(l+=' spellcheck="'+n.spellcheck+'"'),n.maxLength&&(l+=' maxlength="'+n.maxLength+'"'),n.size&&(l+=' size="'+n.size+'"'),n.subtype&&(l+=' type="'+n.subtype+'"'),e.disabled()&&(l+=' disabled="disabled"'),o=n.icon,o&&"caret"!=o&&(o=r+"ico "+r+"i-"+n.icon),a=e._text,(o||a)&&(s='
",e.addClass("has-open")),'
"+s+"
" +}})}),r(Et,[yt,J],function(e,t){return e.extend({init:function(e){var t=this;e.delimiter||(e.delimiter="\xbb"),t._super(e),t.addClass("path"),t.canFocus=!0,t.on("click",function(e){var n,r=e.target;(n=r.getAttribute("data-index"))&&t.fire("select",{value:t.data()[n],index:n})})},focus:function(){var e=this;return e.keyNav=new t({root:e,enableLeftRight:!0}),e.keyNav.focusFirst(),e},data:function(e){var t=this;return"undefined"!=typeof e?(t._data=e,t.update(),t):t._data},update:function(){this.innerHtml(this._getPathHtml())},postRender:function(){var e=this;e._super(),e.data(e.settings.data)},renderHtml:function(){var e=this;return'
'+e._getPathHtml()+"
"},_getPathHtml:function(){var e=this,t=e._data||[],n,r,i="",o=e.classPrefix;for(n=0,r=t.length;r>n;n++)i+=(n>0?'":"")+'
'+t[n].name+"
";return i||(i='
 
'),i}})}),r(kt,[Et,st],function(e,t){return e.extend({postRender:function(){function e(e){if(1===e.nodeType){if("BR"==e.nodeName||e.getAttribute("data-mce-bogus"))return!0;if("bookmark"===e.getAttribute("data-mce-type"))return!0}return!1}var n=this,r=t.activeEditor;return n.on("select",function(t){var n=[],i,o=r.getBody();for(r.focus(),i=r.selection.getStart();i&&i!=o;)e(i)||n.push(i),i=i.parentNode;r.selection.select(n[n.length-1-t.index]),r.nodeChanged()}),r.on("nodeChange",function(t){for(var i=[],o=t.parents,a=o.length;a--;)if(1==o[a].nodeType&&!e(o[a])){var s=r.fire("ResolveName",{name:o[a].nodeName.toLowerCase(),target:o[a]});i.push({name:s.name})}n.data(i)}),n._super()}})}),r(St,[q],function(e){return e.extend({Defaults:{layout:"flex",align:"center",defaults:{flex:1}},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.addClass("formitem"),t.preRender(e),'
'+(e.settings.title?'
'+e.settings.title+"
":"")+'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(Tt,[q,St],function(e,t){return e.extend({Defaults:{containerCls:"form",layout:"flex",direction:"column",align:"stretch",flex:1,padding:20,labelGap:30,spacing:10,callbacks:{submit:function(){this.submit()}}},preRender:function(){var e=this,n=e.items();n.each(function(n){var r,i=n.settings.label;i&&(r=new t({layout:"flex",autoResize:"overflow",defaults:{flex:1},items:[{type:"label",text:i,flex:0,forId:n._id,disabled:n.disabled()}]}),r.type="formitem","undefined"==typeof n.settings.flex&&(n.settings.flex=1),e.replace(n,r),r.add(n))})},recalcLabels:function(){var e=this,t=0,n=[],r,i;if(e.settings.labelGapCalc!==!1)for(e.items().filter("formitem").each(function(e){var r=e.items()[0],i=r.getEl().clientWidth;t=i>t?i:t,n.push(r)}),i=e.settings.labelGap||0,r=n.length;r--;)n[r].settings.minWidth=t+i},visible:function(e){var t=this._super(e);return e===!0&&this._rendered&&this.recalcLabels(),t},submit:function(){return this.fire("submit",{data:this.toJSON()})},postRender:function(){var e=this;e._super(),e.recalcLabels(),e.fromJSON(e.settings.data)}})}),r(Rt,[Tt],function(e){return e.extend({Defaults:{containerCls:"fieldset",layout:"flex",direction:"column",align:"stretch",flex:1,padding:"25 15 5 15",labelGap:30,spacing:10,border:1},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.preRender(),t.preRender(e),'
'+(e.settings.title?''+e.settings.title+"":"")+'
'+(e.settings.html||"")+t.renderHtml(e)+"
"}})}),r(At,[Nt],function(e){return e.extend({init:function(e){var t=this,n=tinymce.activeEditor,r;e.spellcheck=!1,r=n.settings.file_browser_callback,r&&(e.icon="browse",e.onaction=function(){r(t.getEl("inp").id,t.getEl("inp").value,e.filetype,window)}),t._super(e)}})}),r(Bt,[gt],function(e){return e.extend({recalc:function(e){var t=e.layoutRect(),n=e.paddingBox();e.items().filter(":visible").each(function(e){e.layoutRect({x:n.left,y:n.top,w:t.innerW-n.right-n.left,h:t.innerH-n.top-n.bottom}),e.recalc&&e.recalc()})}})}),r(Lt,[gt],function(e){return e.extend({recalc:function(e){var t,n,r,i,o,a,s,l,c,d,u,f,p,m,h,g,v=[],y,b,C,x,w,_,N,E,k,S,T,R,A,B,L,H,M,D,P,O,I,z,F,W,V=Math.max,U=Math.min;for(r=e.items().filter(":visible"),i=e.layoutRect(),o=e._paddingBox,a=e.settings,f=e.isRtl()?a.direction||"row-reversed":a.direction,s=a.align,l=e.isRtl()?a.pack||"end":a.pack,c=a.spacing||0,("row-reversed"==f||"column-reverse"==f)&&(r=r.set(r.toArray().reverse()),f=f.split("-")[0]),"column"==f?(k="y",N="h",E="minH",S="maxH",R="innerH",T="top",A="bottom",B="deltaH",L="contentH",I="left",D="w",H="x",M="innerW",P="minW",O="maxW",z="right",F="deltaW",W="contentW"):(k="x",N="w",E="minW",S="maxW",R="innerW",T="left",A="right",B="deltaW",L="contentW",I="top",D="h",H="y",M="innerH",P="minH",O="maxH",z="bottom",F="deltaH",W="contentH"),u=i[R]-o[T]-o[T],_=d=0,t=0,n=r.length;n>t;t++)p=r[t],m=p.layoutRect(),h=p.settings,g=h.flex,u-=n-1>t?c:0,g>0&&(d+=g,m[S]&&v.push(p),m.flex=g),u-=m[E],y=o[I]+m[P]+o[z],y>_&&(_=y);if(x={},x[E]=0>u?i[E]-u+i[B]:i[R]-u+i[B],x[P]=_+i[F],x[L]=i[R]-u,x[W]=_,x.minW=U(x.minW,i.maxW),x.minH=U(x.minH,i.maxH),x.minW=V(x.minW,i.startMinWidth),x.minH=V(x.minH,i.startMinHeight),!i.autoResize||x.minW==i.minW&&x.minH==i.minH){for(C=u/d,t=0,n=v.length;n>t;t++)p=v[t],m=p.layoutRect(),b=m[S],y=m[E]+m.flex*C,y>b?(u-=m[S]-m[E],d-=m.flex,m.flex=0,m.maxFlexSize=b):m.maxFlexSize=0;for(C=u/d,w=o[T],x={},0===d&&("end"==l?w=u+o[T]:"center"==l?(w=Math.round(i[R]/2-(i[R]-u)/2)+o[T],0>w&&(w=o[T])):"justify"==l&&(w=o[T],c=Math.floor(u/(r.length-1)))),x[H]=o[I],t=0,n=r.length;n>t;t++)p=r[t],m=p.layoutRect(),y=m.maxFlexSize||m[E],"center"===s?x[H]=Math.round(i[M]/2-m[D]/2):"stretch"===s?(x[D]=V(m[P]||0,i[M]-o[I]-o[z]),x[H]=o[I]):"end"===s&&(x[H]=i[M]-m[D]-o.top),m.flex>0&&(y+=m.flex*C),x[N]=y,x[k]=w,p.layoutRect(x),p.recalc&&p.recalc(),w+=y+c}else if(x.w=x.minW,x.h=x.minH,e.layoutRect(x),this.recalc(e),null===e._lastRect){var q=e.parent();q&&(q._lastRect=null,q.recalc())}}})}),r(Ht,[ht],function(e){return e.extend({Defaults:{containerClass:"flow-layout",controlClass:"flow-layout-item",endClass:"break"},recalc:function(e){e.items().filter(":visible").each(function(e){e.recalc&&e.recalc()})}})}),r(Mt,[V,yt,X,f,st,h],function(e,t,n,r,i,o){function a(e){function t(t){function n(e){return e.replace(/%(\w+)/g,"")}var r,i,o=e.dom,a="",l,c;return c=e.settings.preview_styles,c===!1?"":(c||(c="font-family font-size font-weight font-style text-decoration text-transform color background-color border border-radius outline text-shadow"),(t=e.formatter.get(t))?(t=t[0],r=t.block||t.inline||"span",i=o.create(r),s(t.styles,function(e,t){e=n(e),e&&o.setStyle(i,t,e)}),s(t.attributes,function(e,t){e=n(e),e&&o.setAttrib(i,t,e)}),s(t.classes,function(e){e=n(e),o.hasClass(i,e)||o.addClass(i,e)}),e.fire("PreviewFormats"),o.setStyles(i,{position:"absolute",left:-65535}),e.getBody().appendChild(i),l=o.getStyle(e.getBody(),"fontSize",!0),l=/px$/.test(l)?parseInt(l,10):0,s(c.split(" "),function(t){var n=o.getStyle(i,t,!0);if(!("background-color"==t&&/transparent|rgba\s*\([^)]+,\s*0\)/.test(n)&&(n=o.getStyle(e.getBody(),t,!0),"#ffffff"==o.toHex(n).toLowerCase())||"color"==t&&"#000000"==o.toHex(n).toLowerCase())){if("font-size"==t&&/em|%$/.test(n)){if(0===l)return;n=parseFloat(n,10)/(/%$/.test(n)?100:1),n=n*l+"px"}"border"==t&&n&&(a+="padding:0 2px;"),a+=t+":"+n+";"}}),e.fire("AfterPreviewFormats"),o.remove(i),a):void 0)}function r(t,n){return function(){var r=this;e.on("nodeChange",function(i){var o=e.formatter,a=null;s(i.parents,function(e){return s(t,function(t){return n?o.matchNode(e,n,{value:t.value})&&(a=t.value):o.matchNode(e,t.value)&&(a=t.value),a?!1:void 0}),a?!1:void 0}),r.value(a)})}}function i(e){e=e.split(";");for(var t=e.length;t--;)e[t]=e[t].split("=");return e}function o(){function n(e){var t=[];if(e)return s(e,function(e){var r={text:e.title,icon:e.icon};if(e.items)r.menu=n(e.items);else{var a=e.format||"custom"+i++;e.format||(e.name=a,o.push(e)),r.format=a}t.push(r)}),t}function r(){var t;return t=n(e.settings.style_formats_merge?e.settings.style_formats?a.concat(e.settings.style_formats):a:e.settings.style_formats||a)}var i=0,o=[],a=[{title:"Headers",items:[{title:"Header 1",format:"h1"},{title:"Header 2",format:"h2"},{title:"Header 3",format:"h3"},{title:"Header 4",format:"h4"},{title:"Header 5",format:"h5"},{title:"Header 6",format:"h6"}]},{title:"Inline",items:[{title:"Bold",icon:"bold",format:"bold"},{title:"Italic",icon:"italic",format:"italic"},{title:"Underline",icon:"underline",format:"underline"},{title:"Strikethrough",icon:"strikethrough",format:"strikethrough"},{title:"Superscript",icon:"superscript",format:"superscript"},{title:"Subscript",icon:"subscript",format:"subscript"},{title:"Code",icon:"code",format:"code"}]},{title:"Blocks",items:[{title:"Paragraph",format:"p"},{title:"Blockquote",format:"blockquote"},{title:"Div",format:"div"},{title:"Pre",format:"pre"}]},{title:"Alignment",items:[{title:"Left",icon:"alignleft",format:"alignleft"},{title:"Center",icon:"aligncenter",format:"aligncenter"},{title:"Right",icon:"alignright",format:"alignright"},{title:"Justify",icon:"alignjustify",format:"alignjustify"}]}];return e.on("init",function(){s(o,function(t){e.formatter.register(t.name,t)})}),{type:"menu",items:r(),onPostRender:function(t){e.fire("renderFormatsMenu",{control:t.control})},itemDefaults:{preview:!0,textStyle:function(){return this.settings.format?t(this.settings.format):void 0},onPostRender:function(){var t=this,n=this.settings.format;n&&t.parent().on("show",function(){t.disabled(!e.formatter.canApply(n)),t.active(e.formatter.match(n))})},onclick:function(){this.settings.format&&f(this.settings.format)}}}}function a(){return e.undoManager?e.undoManager.hasUndo():!1}function l(){return e.undoManager?e.undoManager.hasRedo():!1}function c(){var t=this;t.disabled(!a()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!a())})}function d(){var t=this;t.disabled(!l()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!l())})}function u(){var t=this;e.on("VisualAid",function(e){t.active(e.hasVisual)}),t.active(e.hasVisual)}function f(t){t.control&&(t=t.control.value()),t&&e.execCommand("mceToggleFormat",!1,t)}var p;p=o(),s({bold:"Bold",italic:"Italic",underline:"Underline",strikethrough:"Strikethrough",subscript:"Subscript",superscript:"Superscript"},function(t,n){e.addButton(n,{tooltip:t,onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})},onclick:function(){f(n)}})}),s({outdent:["Decrease indent","Outdent"],indent:["Increase indent","Indent"],cut:["Cut","Cut"],copy:["Copy","Copy"],paste:["Paste","Paste"],help:["Help","mceHelp"],selectall:["Select all","SelectAll"],hr:["Insert horizontal rule","InsertHorizontalRule"],removeformat:["Clear formatting","RemoveFormat"],visualaid:["Visual aids","mceToggleVisualAid"],newdocument:["New document","mceNewDocument"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1]})}),s({blockquote:["Blockquote","mceBlockQuote"],numlist:["Numbered list","InsertOrderedList"],bullist:["Bullet list","InsertUnorderedList"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],alignleft:["Align left","JustifyLeft"],aligncenter:["Align center","JustifyCenter"],alignright:["Align right","JustifyRight"],alignjustify:["Justify","JustifyFull"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1],onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})}})}),e.addButton("undo",{tooltip:"Undo",onPostRender:c,cmd:"undo"}),e.addButton("redo",{tooltip:"Redo",onPostRender:d,cmd:"redo"}),e.addMenuItem("newdocument",{text:"New document",shortcut:"Ctrl+N",icon:"newdocument",cmd:"mceNewDocument"}),e.addMenuItem("undo",{text:"Undo",icon:"undo",shortcut:"Ctrl+Z",onPostRender:c,cmd:"undo"}),e.addMenuItem("redo",{text:"Redo",icon:"redo",shortcut:"Ctrl+Y",onPostRender:d,cmd:"redo"}),e.addMenuItem("visualaid",{text:"Visual aids",selectable:!0,onPostRender:u,cmd:"mceToggleVisualAid"}),s({cut:["Cut","Cut","Ctrl+X"],copy:["Copy","Copy","Ctrl+C"],paste:["Paste","Paste","Ctrl+V"],selectall:["Select all","SelectAll","Ctrl+A"],bold:["Bold","Bold","Ctrl+B"],italic:["Italic","Italic","Ctrl+I"],underline:["Underline","Underline"],strikethrough:["Strikethrough","Strikethrough"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],removeformat:["Clear formatting","RemoveFormat"]},function(t,n){e.addMenuItem(n,{text:t[0],icon:n,shortcut:t[2],cmd:t[1]})}),e.on("mousedown",function(){n.hideAll()}),e.addButton("styleselect",{type:"menubutton",text:"Formats",menu:p}),e.addButton("formatselect",function(){var n=[],o=i(e.settings.block_formats||"Paragraph=p;Address=address;Pre=pre;Header 1=h1;Header 2=h2;Header 3=h3;Header 4=h4;Header 5=h5;Header 6=h6");return s(o,function(e){n.push({text:e[0],value:e[1],textStyle:function(){return t(e[1])}})}),{type:"listbox",text:o[0][0],values:n,fixedWidth:!0,onselect:f,onPostRender:r(n)}}),e.addButton("fontselect",function(){var t="Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",n=[],o=i(e.settings.font_formats||t);return s(o,function(e){n.push({text:{raw:e[0]},value:e[1],textStyle:-1==e[1].indexOf("dings")?"font-family:"+e[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:n,fixedWidth:!0,onPostRender:r(n,"fontname"),onselect:function(t){t.control.settings.value&&e.execCommand("FontName",!1,t.control.settings.value)}}}),e.addButton("fontsizeselect",function(){var t=[],n="8pt 10pt 12pt 14pt 18pt 24pt 36pt",i=e.settings.fontsize_formats||n;return s(i.split(" "),function(e){t.push({text:e,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:t,fixedWidth:!0,onPostRender:r(t,"fontsize"),onclick:function(t){t.control.settings.value&&e.execCommand("FontSize",!1,t.control.settings.value)}}}),e.addMenuItem("formats",{text:"Formats",menu:p})}var s=r.each;i.on("AddEditor",function(t){t.editor.rtl&&(e.rtl=!0),a(t.editor)}),e.translate=function(e){return i.translate(e)},t.tooltips=!o.iOS}),r(Dt,[gt],function(e){return e.extend({recalc:function(e){var t=e.settings,n,r,i,o,a,s,l,c,d,u,f,p,m,h,g,v,y,b,C,x,w,_,N=[],E=[],k,S,T,R,A,B;for(t=e.settings,i=e.items().filter(":visible"),o=e.layoutRect(),r=t.columns||Math.ceil(Math.sqrt(i.length)),n=Math.ceil(i.length/r),y=t.spacingH||t.spacing||0,b=t.spacingV||t.spacing||0,C=t.alignH||t.align,x=t.alignV||t.align,g=e._paddingBox,C&&"string"==typeof C&&(C=[C]),x&&"string"==typeof x&&(x=[x]),u=0;r>u;u++)N.push(0);for(f=0;n>f;f++)E.push(0);for(f=0;n>f;f++)for(u=0;r>u&&(d=i[f*r+u],d);u++)c=d.layoutRect(),k=c.minW,S=c.minH,N[u]=k>N[u]?k:N[u],E[f]=S>E[f]?S:E[f];for(A=o.innerW-g.left-g.right,w=0,u=0;r>u;u++)w+=N[u]+(u>0?y:0),A-=(u>0?y:0)+N[u];for(B=o.innerH-g.top-g.bottom,_=0,f=0;n>f;f++)_+=E[f]+(f>0?b:0),B-=(f>0?b:0)+E[f];if(w+=g.left+g.right,_+=g.top+g.bottom,l={},l.minW=w+(o.w-o.innerW),l.minH=_+(o.h-o.innerH),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH,l.minW=Math.min(l.minW,o.maxW),l.minH=Math.min(l.minH,o.maxH),l.minW=Math.max(l.minW,o.startMinWidth),l.minH=Math.max(l.minH,o.startMinHeight),!o.autoResize||l.minW==o.minW&&l.minH==o.minH){o.autoResize&&(l=e.layoutRect(l),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH);var L;L="start"==t.packV?0:B>0?Math.floor(B/n):0;var H=0,M=t.flexWidths;if(M)for(u=0;uu;u++)N[u]+=M?M[u]*D:D;for(m=g.top,f=0;n>f;f++){for(p=g.left,s=E[f]+L,u=0;r>u&&(d=i[f*r+u],d);u++)h=d.settings,c=d.layoutRect(),a=Math.max(N[u],c.startMinWidth),T=R=0,c.x=p,c.y=m,v=h.alignH||(C?C[u]||C[0]:null),"center"==v?c.x=p+a/2-c.w/2:"right"==v?c.x=p+a-c.w:"stretch"==v&&(c.w=a),v=h.alignV||(x?x[u]||x[0]:null),"center"==v?c.y=m+s/2-c.h/2:"bottom"==v?c.y=m+s-c.h:"stretch"==v&&(c.h=s),d.layoutRect(c),p+=a+y,d.recalc&&d.recalc();m+=s+b}}else if(l.w=l.minW,l.h=l.minH,e.layoutRect(l),this.recalc(e),null===e._lastRect){var P=e.parent();P&&(P._lastRect=null,P.recalc())}}})}),r(Pt,[yt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("iframe"),e.canFocus=!1,''},src:function(e){this.getEl().src=e},html:function(e,t){var n=this,r=this.getEl().contentWindow.document.body;return r?(r.innerHTML=e,t&&t()):setTimeout(function(){n.html(e)},0),this}})}),r(Ot,[yt,W],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t.addClass("widget"),t.addClass("label"),t.canFocus=!1,e.multiline&&t.addClass("autoscroll"),e.strong&&t.addClass("strong")},initLayoutRect:function(){var e=this,n=e._super();if(e.settings.multiline){var r=t.getSize(e.getEl());r.width>n.maxW&&(n.minW=n.maxW,e.addClass("multiline")),e.getEl().style.width=n.minW+"px",n.startMinH=n.h=n.minH=Math.min(n.maxH,t.getSize(e.getEl()).height)}return n},repaint:function(){var e=this;return e.settings.multiline||(e.getEl().style.lineHeight=e.layoutRect().h+"px"),e._super()},text:function(e){var t=this;return t._rendered&&e&&this.innerHtml(t.encode(e)),t._super(e)},renderHtml:function(){var e=this,t=e.settings.forId;return'"}})}),r(It,[q,J],function(e,t){return e.extend({Defaults:{role:"toolbar",layout:"flow"},init:function(e){var t=this;t._super(e),t.addClass("toolbar")},postRender:function(){var e=this;return e.items().addClass("toolbar-item"),e.keyNav=new t({root:e,enableLeftRight:!0}),e._super()}})}),r(zt,[It],function(e){return e.extend({Defaults:{role:"menubar",containerCls:"menubar",defaults:{type:"menubutton"}}})}),r(Ft,[bt,U,zt],function(e,t,n){function r(e,t){for(;e;){if(t===e)return!0;e=e.parentNode}return!1}var i=e.extend({init:function(e){var t=this;t._renderOpen=!0,t._super(e),t.addClass("menubtn"),e.fixedWidth&&t.addClass("fixed-width"),t.aria("haspopup",!0),t.hasPopup=!0},showMenu:function(){var e=this,n=e.settings,r;return e.menu&&e.menu.visible()?e.hideMenu():(e.menu||(r=n.menu||[],r.length?r={type:"menu",items:r}:r.type=r.type||"menu",e.menu=t.create(r).parent(e).renderTo(e.getContainerElm()),e.fire("createmenu"),e.menu.reflow(),e.menu.on("cancel",function(t){t.control===e.menu&&e.focus()}),e.menu.on("show hide",function(t){t.control==e.menu&&e.activeMenu("show"==t.type)}).fire("show"),e.aria("expanded",!0)),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),void e.menu.moveRel(e.getEl(),e.isRtl()?["br-tr","tr-br"]:["bl-tl","tl-bl"]))},hideMenu:function(){var e=this;e.menu&&(e.menu.items().each(function(e){e.hideMenu&&e.hideMenu()}),e.menu.hide(),e.aria("expanded",!1))},activeMenu:function(e){this.toggleClass("active",e)},renderHtml:function(){var e=this,t=e._id,r=e.classPrefix,i=e.settings.icon?r+"ico "+r+"i-"+e.settings.icon:"";return e.aria("role",e.parent()instanceof n?"menuitem":"button"),'
'},postRender:function(){var e=this;return e.on("click",function(t){t.control===e&&r(t.target,e.getEl())&&(e.showMenu(),t.keyboard&&e.menu.items()[0].focus())}),e.on("mouseenter",function(t){var n=t.control,r=e.parent(),o;n&&r&&n instanceof i&&n.parent()==r&&(r.items().filter("MenuButton").each(function(e){e.hideMenu&&e!=n&&(e.menu&&e.menu.visible()&&(o=!0),e.hideMenu())}),o&&(n.focus(),n.showMenu()))}),e._super()},text:function(e){var t=this,n,r;if(t._rendered)for(r=t.getEl("open").getElementsByTagName("span"),n=0;n'+("-"!==o?' ":"")+("-"!==o?''+o+"":"")+(l?'
'+l+"
":"")+(r.menu?'
':"")+""},postRender:function(){var e=this,t=e.settings,n=t.textStyle;if("function"==typeof n&&(n=n.call(this)),n){var r=e.getEl("text");r&&r.setAttribute("style",n)}return e._super()},remove:function(){this._super(),this.menu&&this.menu.remove()}})}),r(Ut,[X,J,Vt,f],function(e,t,n,r){var i=e.extend({Defaults:{defaultType:"menuitem",border:1,layout:"stack",role:"menu"},init:function(e){var i=this;if(e.autohide=!0,e.constrainToViewport=!0,e.itemDefaults)for(var o=e.items,a=o.length;a--;)o[a]=r.extend({},e.itemDefaults,o[a]);i._super(e),i.addClass("menu"),i.keyNav=new t({root:i,enableUpDown:!0,enableLeftRight:!0,leftAction:function(){i.parent()instanceof n&&i.keyNav.cancel()},onCancel:function(){i.fire("cancel",{},!1),i.hide()}})},repaint:function(){return this.toggleClass("menu-align",!0),this._super(),this.getEl().style.height="",this.getEl("body").style.height="",this},cancel:function(){var e=this;e.hideAll(),e.fire("cancel"),e.fire("select")},hideAll:function(){var e=this;return this.find("menuitem").exec("hideMenu"),e._super()},preRender:function(){var e=this;return e.items().each(function(t){var n=t.settings;return n.icon||n.selectable?(e._hasIcons=!0,!1):void 0}),e._super()}});return i}),r(qt,[xt],function(e){return e.extend({Defaults:{classes:"radio",role:"radio"}})}),r(jt,[yt,j],function(e,t){return e.extend({renderHtml:function(){var e=this,t=e.classPrefix;return e.addClass("resizehandle"),"both"==e.settings.direction&&e.addClass("resizehandle-both"),e.canFocus=!1,'
'},postRender:function(){var e=this;e._super(),e.resizeDragHelper=new t(this._id,{start:function(){e.fire("ResizeStart")},drag:function(t){"both"!=e.settings.direction&&(t.deltaX=0),e.fire("Resize",t)},stop:function(){e.fire("ResizeEnd")}})},remove:function(){return this.resizeDragHelper&&this.resizeDragHelper.destroy(),this._super()}})}),r($t,[yt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("spacer"),e.canFocus=!1,'
'}})}),r(Kt,[Ft,W],function(e,t){return e.extend({Defaults:{classes:"widget btn splitbtn",role:"splitbutton"},repaint:function(){var e=this,n=e.getEl(),r=e.layoutRect(),i,o;return e._super(),i=n.firstChild,o=n.lastChild,t.css(i,{width:r.w-t.getSize(o).width,height:r.h-2}),t.css(o,{height:r.h-2}),e},activeMenu:function(e){var n=this;t.toggleClass(n.getEl().lastChild,n.classPrefix+"active",e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"";return'
'},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(e){var n=e.target;if(e.control==this)for(;n;){if("BUTTON"==n.nodeName&&-1==n.className.indexOf("open"))return e.stopImmediatePropagation(),void t.call(this,e);n=n.parentNode}}),delete e.settings.onclick,e._super()}})}),r(Yt,[Ht],function(e){return e.extend({Defaults:{containerClass:"stack-layout",controlClass:"stack-layout-item",endClass:"break"}})}),r(Gt,[K,W],function(e,t){return e.extend({lastIdx:0,Defaults:{layout:"absolute",defaults:{type:"panel"}},activateTab:function(e){this.activeTabId&&t.removeClass(this.getEl(this.activeTabId),this.classPrefix+"active"),this.activeTabId="t"+e,t.addClass(this.getEl("t"+e),this.classPrefix+"active"),e!=this.lastIdx&&(this.items()[this.lastIdx].hide(),this.lastIdx=e),this.items()[e].show().fire("showtab"),this.reflow()},renderHtml:function(){var e=this,t=e._layout,n="",r=e.classPrefix;return e.preRender(),t.preRender(e),e.items().each(function(t,i){n+='
'+e.encode(t.settings.title)+"
"}),'
'+n+'
'+t.renderHtml(e)+"
"},postRender:function(){var e=this;e._super(),e.settings.activeTab=e.settings.activeTab||0,e.activateTab(e.settings.activeTab),this.on("click",function(t){var n=t.target.parentNode;if(t.target.parentNode.id==e._id+"-head")for(var r=n.childNodes.length;r--;)n.childNodes[r]==t.target&&e.activateTab(r)})},initLayoutRect:function(){var e=this,n,r,i;r=t.getSize(e.getEl("head")).width,r=0>r?0:r,i=0,e.items().each(function(t,n){r=Math.max(r,t.layoutRect().minW),i=Math.max(i,t.layoutRect().minH),e.settings.activeTab!=n&&t.hide()}),e.items().each(function(e){e.settings.x=0,e.settings.y=0,e.settings.w=r,e.settings.h=i,e.layoutRect({x:0,y:0,w:r,h:i})});var o=t.getSize(e.getEl("head")).height;return e.settings.minWidth=r,e.settings.minHeight=i+o,n=e._super(),n.deltaH+=o,n.innerH=n.h-n.deltaH,n}})}),r(Xt,[yt,W],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t._value=e.value||"",t.addClass("textbox"),e.multiline?t.addClass("multiline"):t.on("keydown",function(e){13==e.keyCode&&t.parents().reverse().each(function(t){return e.preventDefault(),t.hasEventListeners("submit")&&t.toJSON?(t.fire("submit",{data:t.toJSON()}),!1):void 0})})},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl().disabled=e),t._super(e)},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().value=e),t):t._rendered?t.getEl().value:t._value},repaint:function(){var e=this,t,n,r,i=0,o=0,a;t=e.getEl().style,n=e._layoutRect,a=e._lastRepaintRect||{};var s=document;return!e.settings.multiline&&s.all&&(!s.documentMode||s.documentMode<=8)&&(t.lineHeight=n.h-o+"px"),r=e._borderBox,i=r.left+r.right+8,o=r.top+r.bottom+(e.settings.multiline?8:0),n.x!==a.x&&(t.left=n.x+"px",a.x=n.x),n.y!==a.y&&(t.top=n.y+"px",a.y=n.y),n.w!==a.w&&(t.width=n.w-i+"px",a.w=n.w),n.h!==a.h&&(t.height=n.h-o+"px",a.h=n.h),e._lastRepaintRect=a,e.fire("repaint",{},!1),e},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.encode(e._value,!1),i="";return"spellcheck"in n&&(i+=' spellcheck="'+n.spellcheck+'"'),n.maxLength&&(i+=' maxlength="'+n.maxLength+'"'),n.size&&(i+=' size="'+n.size+'"'),n.subtype&&(i+=' type="'+n.subtype+'"'),e.disabled()&&(i+=' disabled="disabled"'),n.multiline?'":'"},postRender:function(){var e=this;return t.on(e.getEl(),"change",function(t){e.fire("change",t)}),e._super()},remove:function(){t.off(this.getEl()),this._super()}})}),r(Jt,[W],function(e){return function(t){var n=this,r;n.show=function(i){return n.hide(),r=!0,window.setTimeout(function(){r&&t.appendChild(e.createFragment('
'))},i||0),n},n.hide=function(){var e=t.lastChild;return e&&-1!=e.className.indexOf("throbber")&&e.parentNode.removeChild(e),r=!1,n}}}),a([l,c,d,u,f,p,m,h,g,v,y,b,C,x,w,_,N,E,k,S,T,R,A,B,L,H,M,D,P,O,I,z,F,W,V,U,q,j,$,K,Y,G,X,J,Q,Z,et,tt,nt,rt,it,ot,at,st,lt,ct,dt,ut,ft,pt,mt,ht,gt,vt,yt,bt,Ct,xt,wt,_t,Nt,Et,kt,St,Tt,Rt,At,Bt,Lt,Ht,Mt,Dt,Pt,Ot,It,zt,Ft,Wt,Vt,Ut,qt,jt,$t,Kt,Yt,Gt,Xt,Jt])}(this); \ No newline at end of file diff --git a/plugins/web_gui/static/plugins/xcharts/LICENSE b/plugins/web_gui/static/plugins/xcharts/LICENSE new file mode 100644 index 0000000..b5bdbb0 --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2012 tenXer, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/web_gui/static/plugins/xcharts/README.md b/plugins/web_gui/static/plugins/xcharts/README.md new file mode 100644 index 0000000..6b9ff6e --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/README.md @@ -0,0 +1,17 @@ +# xCharts [![Build Status](https://secure.travis-ci.org/tenXer/xcharts.png?branch=master)](http://travis-ci.org/tenxer/xcharts) + +[xCharts](http://tenxer.github.com/xcharts/) is a D3-based library for building custom charts and graphs. Written and maintained by [tenXer](https://www.tenxer.com). + +## Documentation + +View the [xCharts site](http://tenxer.github.com/xcharts/) for everything you need. + +## License + +Copyright (c) 2012 tenXer, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/web_gui/static/plugins/xcharts/xcharts.css b/plugins/web_gui/static/plugins/xcharts/xcharts.css new file mode 100644 index 0000000..8d8c760 --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/xcharts.css @@ -0,0 +1,283 @@ +.xchart .line { + stroke-width: 3px; + fill: none; +} +.xchart .fill { + stroke-width: 0; +} +.xchart circle { + stroke: #FFF; + stroke-width: 3px; +} +.xchart .axis .domain { + fill: none; +} +.xchart .axis .tick line { + stroke: #EEE; + stroke-width: 1px; +} +.xchart .axis text { + font-family: Helvetica, Arial, Verdana, sans-serif; + fill: #666; + font-size: 12px; +} +.xchart .color0 .line { + stroke: #3880aa; +} +.xchart .color0 .line .fill { + pointer-events: none; +} +.xchart .color0 rect, +.xchart .color0 circle { + fill: #3880aa; +} +.xchart .color0 .fill { + fill: rgba(56, 128, 170, 0.1); +} +.xchart .color0.comp .line { + stroke: #89bbd8; +} +.xchart .color0.comp rect { + fill: #89bbd8; +} +.xchart .color0.comp .fill { + display: none; +} +.xchart .color0.comp circle, +.xchart .color0.comp .pointer { + fill: #89bbd8; +} +.xchart .color1 .line { + stroke: #4da944; +} +.xchart .color1 .line .fill { + pointer-events: none; +} +.xchart .color1 rect, +.xchart .color1 circle { + fill: #4da944; +} +.xchart .color1 .fill { + fill: rgba(77, 169, 68, 0.1); +} +.xchart .color1.comp .line { + stroke: #9dd597; +} +.xchart .color1.comp rect { + fill: #9dd597; +} +.xchart .color1.comp .fill { + display: none; +} +.xchart .color1.comp circle, +.xchart .color1.comp .pointer { + fill: #9dd597; +} +.xchart .color2 .line { + stroke: #f26522; +} +.xchart .color2 .line .fill { + pointer-events: none; +} +.xchart .color2 rect, +.xchart .color2 circle { + fill: #f26522; +} +.xchart .color2 .fill { + fill: rgba(242, 101, 34, 0.1); +} +.xchart .color2.comp .line { + stroke: #f9b99a; +} +.xchart .color2.comp rect { + fill: #f9b99a; +} +.xchart .color2.comp .fill { + display: none; +} +.xchart .color2.comp circle, +.xchart .color2.comp .pointer { + fill: #f9b99a; +} +.xchart .color3 .line { + stroke: #c6080d; +} +.xchart .color3 .line .fill { + pointer-events: none; +} +.xchart .color3 rect, +.xchart .color3 circle { + fill: #c6080d; +} +.xchart .color3 .fill { + fill: rgba(198, 8, 13, 0.1); +} +.xchart .color3.comp .line { + stroke: #f8555a; +} +.xchart .color3.comp rect { + fill: #f8555a; +} +.xchart .color3.comp .fill { + display: none; +} +.xchart .color3.comp circle, +.xchart .color3.comp .pointer { + fill: #f8555a; +} +.xchart .color4 .line { + stroke: #672d8b; +} +.xchart .color4 .line .fill { + pointer-events: none; +} +.xchart .color4 rect, +.xchart .color4 circle { + fill: #672d8b; +} +.xchart .color4 .fill { + fill: rgba(103, 45, 139, 0.1); +} +.xchart .color4.comp .line { + stroke: #a869ce; +} +.xchart .color4.comp rect { + fill: #a869ce; +} +.xchart .color4.comp .fill { + display: none; +} +.xchart .color4.comp circle, +.xchart .color4.comp .pointer { + fill: #a869ce; +} +.xchart .color5 .line { + stroke: #ce1797; +} +.xchart .color5 .line .fill { + pointer-events: none; +} +.xchart .color5 rect, +.xchart .color5 circle { + fill: #ce1797; +} +.xchart .color5 .fill { + fill: rgba(206, 23, 151, 0.1); +} +.xchart .color5.comp .line { + stroke: #f075cb; +} +.xchart .color5.comp rect { + fill: #f075cb; +} +.xchart .color5.comp .fill { + display: none; +} +.xchart .color5.comp circle, +.xchart .color5.comp .pointer { + fill: #f075cb; +} +.xchart .color6 .line { + stroke: #d9ce00; +} +.xchart .color6 .line .fill { + pointer-events: none; +} +.xchart .color6 rect, +.xchart .color6 circle { + fill: #d9ce00; +} +.xchart .color6 .fill { + fill: rgba(217, 206, 0, 0.1); +} +.xchart .color6.comp .line { + stroke: #fff75a; +} +.xchart .color6.comp rect { + fill: #fff75a; +} +.xchart .color6.comp .fill { + display: none; +} +.xchart .color6.comp circle, +.xchart .color6.comp .pointer { + fill: #fff75a; +} +.xchart .color7 .line { + stroke: #754c24; +} +.xchart .color7 .line .fill { + pointer-events: none; +} +.xchart .color7 rect, +.xchart .color7 circle { + fill: #754c24; +} +.xchart .color7 .fill { + fill: rgba(117, 76, 36, 0.1); +} +.xchart .color7.comp .line { + stroke: #c98c50; +} +.xchart .color7.comp rect { + fill: #c98c50; +} +.xchart .color7.comp .fill { + display: none; +} +.xchart .color7.comp circle, +.xchart .color7.comp .pointer { + fill: #c98c50; +} +.xchart .color8 .line { + stroke: #2eb9b4; +} +.xchart .color8 .line .fill { + pointer-events: none; +} +.xchart .color8 rect, +.xchart .color8 circle { + fill: #2eb9b4; +} +.xchart .color8 .fill { + fill: rgba(46, 185, 180, 0.1); +} +.xchart .color8.comp .line { + stroke: #86e1de; +} +.xchart .color8.comp rect { + fill: #86e1de; +} +.xchart .color8.comp .fill { + display: none; +} +.xchart .color8.comp circle, +.xchart .color8.comp .pointer { + fill: #86e1de; +} +.xchart .color9 .line { + stroke: #0e2e42; +} +.xchart .color9 .line .fill { + pointer-events: none; +} +.xchart .color9 rect, +.xchart .color9 circle { + fill: #0e2e42; +} +.xchart .color9 .fill { + fill: rgba(14, 46, 66, 0.1); +} +.xchart .color9.comp .line { + stroke: #2477ab; +} +.xchart .color9.comp rect { + fill: #2477ab; +} +.xchart .color9.comp .fill { + display: none; +} +.xchart .color9.comp circle, +.xchart .color9.comp .pointer { + fill: #2477ab; +} diff --git a/plugins/web_gui/static/plugins/xcharts/xcharts.js b/plugins/web_gui/static/plugins/xcharts/xcharts.js new file mode 100644 index 0000000..582407a --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/xcharts.js @@ -0,0 +1,1158 @@ +/*! +xCharts v0.3.0 Copyright (c) 2012, tenXer, Inc. All Rights Reserved. +@license MIT license. http://github.com/tenXer/xcharts for details +*/ + +(function () { + +var xChart, + _vis = {}, + _scales = {}, + _visutils = {}; +(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);function getInsertionPoint(zIndex) { + return _.chain(_.range(zIndex, 10)).reverse().map(function (z) { + return 'g[data-index="' + z + '"]'; + }).value().join(', '); +} + +function colorClass(el, i) { + var c = el.getAttribute('class'); + return ((c !== null) ? c.replace(/color\d+/g, '') : '') + ' color' + i; +} + +_visutils = { + getInsertionPoint: getInsertionPoint, + colorClass: colorClass +}; +var local = this, + defaultSpacing = 0.25; + +function _getDomain(data, axis) { + return _.chain(data) + .pluck('data') + .flatten() + .pluck(axis) + .uniq() + .filter(function (d) { + return d !== undefined && d !== null; + }) + .value() + .sort(d3.ascending); +} + +_scales.ordinal = function (data, axis, bounds, extents) { + var domain = _getDomain(data, axis); + return d3.scale.ordinal() + .domain(domain) + .rangeRoundBands(bounds, defaultSpacing); +}; + +_scales.linear = function (data, axis, bounds, extents) { + return d3.scale.linear() + .domain(extents) + .nice() + .rangeRound(bounds); +}; + +_scales.exponential = function (data, axis, bounds, extents) { + return d3.scale.pow() + .exponent(0.65) + .domain(extents) + .nice() + .rangeRound(bounds); +}; + +_scales.time = function (data, axis, bounds, extents) { + return d3.time.scale() + .domain(_.map(extents, function (d) { return new Date(d); })) + .range(bounds); +}; + +function _extendDomain(domain, axis) { + var min = domain[0], + max = domain[1], + diff, + e; + + if (min === max) { + e = Math.max(Math.round(min / 10), 4); + min -= e; + max += e; + } + + diff = max - min; + min = (min) ? min - (diff / 10) : min; + min = (domain[0] > 0) ? Math.max(min, 0) : min; + max = (max) ? max + (diff / 10) : max; + max = (domain[1] < 0) ? Math.min(max, 0) : max; + + return [min, max]; +} + +function _getExtents(options, data, xType, yType) { + var extents, + nData = _.chain(data) + .pluck('data') + .flatten() + .value(); + + extents = { + x: d3.extent(nData, function (d) { return d.x; }), + y: d3.extent(nData, function (d) { return d.y; }) + }; + + _.each([xType, yType], function (type, i) { + var axis = (i) ? 'y' : 'x', + extended; + extents[axis] = d3.extent(nData, function (d) { return d[axis]; }); + if (type === 'ordinal') { + return; + } + + _.each([axis + 'Min', axis + 'Max'], function (minMax, i) { + if (type !== 'time') { + extended = _extendDomain(extents[axis]); + } + + if (options.hasOwnProperty(minMax) && options[minMax] !== null) { + extents[axis][i] = options[minMax]; + } else if (type !== 'time') { + extents[axis][i] = extended[i]; + } + }); + }); + + return extents; +} + +_scales.xy = function (self, data, xType, yType) { + var o = self._options, + extents = _getExtents(o, data, xType, yType), + scales = {}, + horiz = [o.axisPaddingLeft, self._width], + vert = [self._height, o.axisPaddingTop], + xScale, + yScale; + + _.each([xType, yType], function (type, i) { + var axis = (i === 0) ? 'x' : 'y', + bounds = (i === 0) ? horiz : vert, + fn = xChart.getScale(type); + scales[axis] = fn(data, axis, bounds, extents[axis]); + }); + + return scales; +}; +(function () { + var zIndex = 2, + selector = 'g.bar', + insertBefore = _visutils.getInsertionPoint(zIndex); + + function postUpdateScale(self, scaleData, mainData, compData) { + self.xScale2 = d3.scale.ordinal() + .domain(d3.range(0, mainData.length)) + .rangeRoundBands([0, self.xScale.rangeBand()], 0.08); + } + + function enter(self, storage, className, data, callbacks) { + var barGroups, bars, + yZero = self.yZero; + + barGroups = self._g.selectAll(selector + className) + .data(data, function (d) { + return d.className; + }); + + barGroups.enter().insert('g', insertBefore) + .attr('data-index', zIndex) + .style('opacity', 0) + .attr('class', function (d, i) { + var cl = _.uniq((className + d.className).split('.')).join(' '); + return cl + ' bar ' + _visutils.colorClass(this, i); + }) + .attr('transform', function (d, i) { + return 'translate(' + self.xScale2(i) + ',0)'; + }); + + bars = barGroups.selectAll('rect') + .data(function (d) { + return d.data; + }, function (d) { + return d.x; + }); + + bars.enter().append('rect') + .attr('width', 0) + .attr('rx', 3) + .attr('ry', 3) + .attr('x', function (d) { + return self.xScale(d.x) + (self.xScale2.rangeBand() / 2); + }) + .attr('height', function (d) { + return Math.abs(yZero - self.yScale(d.y)); + }) + .attr('y', function (d) { + return (d.y < 0) ? yZero : self.yScale(d.y); + }) + .on('mouseover', callbacks.mouseover) + .on('mouseout', callbacks.mouseout) + .on('click', callbacks.click); + + storage.barGroups = barGroups; + storage.bars = bars; + } + + function update(self, storage, timing) { + var yZero = self.yZero; + + storage.barGroups + .attr('class', function (d, i) { + return _visutils.colorClass(this, i); + }) + .transition().duration(timing) + .style('opacity', 1) + .attr('transform', function (d, i) { + return 'translate(' + self.xScale2(i) + ',0)'; + }); + + storage.bars.transition().duration(timing) + .attr('width', self.xScale2.rangeBand()) + .attr('x', function (d) { + return self.xScale(d.x); + }) + .attr('height', function (d) { + return Math.abs(yZero - self.yScale(d.y)); + }) + .attr('y', function (d) { + return (d.y < 0) ? yZero : self.yScale(d.y); + }); + } + + function exit(self, storage, timing) { + storage.bars.exit() + .transition().duration(timing) + .attr('width', 0) + .remove(); + storage.barGroups.exit() + .transition().duration(timing) + .style('opacity', 0) + .remove(); + } + + function destroy(self, storage, timing) { + var band = (self.xScale2) ? self.xScale2.rangeBand() / 2 : 0; + delete self.xScale2; + storage.bars + .transition().duration(timing) + .attr('width', 0) + .attr('x', function (d) { + return self.xScale(d.x) + band; + }); + } + + _vis.bar = { + postUpdateScale: postUpdateScale, + enter: enter, + update: update, + exit: exit, + destroy: destroy + }; +}()); +(function () { + + var zIndex = 3, + selector = 'g.line', + insertBefore = _visutils.getInsertionPoint(zIndex); + + function enter(self, storage, className, data, callbacks) { + var inter = self._options.interpolation, + x = function (d, i) { + if (!self.xScale2 && !self.xScale.rangeBand) { + return self.xScale(d.x); + } + return self.xScale(d.x) + (self.xScale.rangeBand() / 2); + }, + y = function (d) { return self.yScale(d.y); }, + line = d3.svg.line() + .x(x) + .interpolate(inter), + area = d3.svg.area() + .x(x) + .y1(self.yZero) + .interpolate(inter), + container, + fills, + paths; + + function datum(d) { + return [d.data]; + } + + container = self._g.selectAll(selector + className) + .data(data, function (d) { + return d.className; + }); + + container.enter().insert('g', insertBefore) + .attr('data-index', zIndex) + .attr('class', function (d, i) { + var cl = _.uniq((className + d.className).split('.')).join(' '); + return cl + ' line ' + _visutils.colorClass(this, i); + }); + + fills = container.selectAll('path.fill') + .data(datum); + + fills.enter().append('path') + .attr('class', 'fill') + .style('opacity', 0) + .attr('d', area.y0(y)); + + paths = container.selectAll('path.line') + .data(datum); + + paths.enter().append('path') + .attr('class', 'line') + .style('opacity', 0) + .attr('d', line.y(y)); + + storage.lineContainers = container; + storage.lineFills = fills; + storage.linePaths = paths; + storage.lineX = x; + storage.lineY = y; + storage.lineA = area; + storage.line = line; + } + + function update(self, storage, timing) { + storage.lineContainers + .attr('class', function (d, i) { + return _visutils.colorClass(this, i); + }); + + storage.lineFills.transition().duration(timing) + .style('opacity', 1) + .attr('d', storage.lineA.y0(storage.lineY)); + + storage.linePaths.transition().duration(timing) + .style('opacity', 1) + .attr('d', storage.line.y(storage.lineY)); + } + + function exit(self, storage) { + storage.linePaths.exit() + .style('opacity', 0) + .remove(); + storage.lineFills.exit() + .style('opacity', 0) + .remove(); + + storage.lineContainers.exit() + .remove(); + } + + function destroy(self, storage, timing) { + storage.linePaths.transition().duration(timing) + .style('opacity', 0); + storage.lineFills.transition().duration(timing) + .style('opacity', 0); + } + + _vis.line = { + enter: enter, + update: update, + exit: exit, + destroy: destroy + }; +}()); +(function () { + var line = _vis.line; + + function enter(self, storage, className, data, callbacks) { + var circles; + + line.enter(self, storage, className, data, callbacks); + + circles = storage.lineContainers.selectAll('circle') + .data(function (d) { + return d.data; + }, function (d) { + return d.x; + }); + + circles.enter().append('circle') + .style('opacity', 0) + .attr('cx', storage.lineX) + .attr('cy', storage.lineY) + .attr('r', 5) + .on('mouseover', callbacks.mouseover) + .on('mouseout', callbacks.mouseout) + .on('click', callbacks.click); + + storage.lineCircles = circles; + } + + function update(self, storage, timing) { + line.update.apply(null, _.toArray(arguments)); + + storage.lineCircles.transition().duration(timing) + .style('opacity', 1) + .attr('cx', storage.lineX) + .attr('cy', storage.lineY); + } + + function exit(self, storage) { + storage.lineCircles.exit() + .remove(); + line.exit.apply(null, _.toArray(arguments)); + } + + function destroy(self, storage, timing) { + line.destroy.apply(null, _.toArray(arguments)); + if (!storage.lineCircles) { + return; + } + storage.lineCircles.transition().duration(timing) + .style('opacity', 0); + } + + _vis['line-dotted'] = { + enter: enter, + update: update, + exit: exit, + destroy: destroy + }; +}()); +(function () { + var line = _vis['line-dotted']; + + function enter(self, storage, className, data, callbacks) { + line.enter(self, storage, className, data, callbacks); + } + + function _accumulate_data(data) { + function reduce(memo, num) { + return memo + num.y; + } + + var nData = _.map(data, function (set) { + var i = set.data.length, + d = _.clone(set.data); + set = _.clone(set); + while (i) { + i -= 1; + // Need to clone here, otherwise we are actually setting the same + // data onto the original data set. + d[i] = _.clone(set.data[i]); + d[i].y0 = set.data[i].y; + d[i].y = _.reduce(_.first(set.data, i), reduce, set.data[i].y); + } + return _.extend(set, { data: d }); + }); + + return nData; + } + + function _resetData(self) { + if (!self.hasOwnProperty('cumulativeOMainData')) { + return; + } + self._mainData = self.cumulativeOMainData; + delete self.cumulativeOMainData; + self._compData = self.cumulativeOCompData; + delete self.cumulativeOCompData; + } + + function preUpdateScale(self, data) { + _resetData(self); + self.cumulativeOMainData = self._mainData; + self._mainData = _accumulate_data(self._mainData); + self.cumulativeOCompData = self._compData; + self._compData = _accumulate_data(self._compData); + } + + function destroy(self, storage, timing) { + _resetData(self); + line.destroy.apply(null, _.toArray(arguments)); + } + + _vis.cumulative = { + preUpdateScale: preUpdateScale, + enter: enter, + update: line.update, + exit: line.exit, + destroy: destroy + }; +}()); +var emptyData = [[]], + defaults = { + // User interaction callbacks + mouseover: function (data, i) {}, + mouseout: function (data, i) {}, + click: function (data, i) {}, + + // Padding between the axes and the contents of the chart + axisPaddingTop: 0, + axisPaddingRight: 0, + axisPaddingBottom: 5, + axisPaddingLeft: 20, + + // Padding around the edge of the chart (space for axis labels, etc) + paddingTop: 0, + paddingRight: 0, + paddingBottom: 20, + paddingLeft: 60, + + // Axis tick formatting + tickHintX: 10, + tickFormatX: function (x) { return x; }, + tickHintY: 10, + tickFormatY: function (y) { return y; }, + + // Min/Max Axis Values + xMin: null, + xMax: null, + yMin: null, + yMax: null, + + // Pre-format input data + dataFormatX: function (x) { return x; }, + dataFormatY: function (y) { return y; }, + + unsupported: function (selector) { + d3.select(selector).text('SVG is not supported on your browser'); + }, + + // Callback functions if no data + empty: function (self, selector, d) {}, + notempty: function (self, selector) {}, + + timing: 750, + + // Line interpolation + interpolation: 'monotone', + + // Data sorting + sortX: function (a, b) { + return (!a.x && !b.x) ? 0 : (a.x < b.x) ? -1 : 1; + } + }; + +// What/how should the warning/error be presented? +function svgEnabled() { + var d = document; + return (!!d.createElementNS && + !!d.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect); +} + +/** + * Creates a new chart + * + * @param string type The drawing type for the main data + * @param array data Data to render in the chart + * @param string selector CSS Selector for the parent element for the chart + * @param object options Optional. See `defaults` for options + * + * Examples: + * var data = { + * "main": [ + * { + * "data": [ + * { + * "x": "2012-08-09T07:00:00.522Z", + * "y": 68 + * }, + * { + * "x": "2012-08-10T07:00:00.522Z", + * "y": 295 + * }, + * { + * "x": "2012-08-11T07:00:00.522Z", + * "y": 339 + * }, + * ], + * "className": ".foo" + * } + * ], + * "xScale": "ordinal", + * "yScale": "linear", + * "comp": [ + * { + * "data": [ + * { + * "x": "2012-08-09T07:00:00.522Z", + * "y": 288 + * }, + * { + * "x": "2012-08-10T07:00:00.522Z", + * "y": 407 + * }, + * { + * "x": "2012-08-11T07:00:00.522Z", + * "y": 459 + * } + * ], + * "className": ".comp.comp_foo", + * "type": "line-arrowed" + * } + * ] + * }, + * myChart = new Chart('bar', data, '#chart'); + * + */ +function xChart(type, data, selector, options) { + var self = this, + resizeLock; + + self._options = options = _.defaults(options || {}, defaults); + + if (svgEnabled() === false) { + return options.unsupported(selector); + } + + self._selector = selector; + self._container = d3.select(selector); + self._drawSvg(); + self._mainStorage = {}; + self._compStorage = {}; + + data = _.clone(data); + if (type && !data.type) { + data.type = type; + } + + self.setData(data); + + d3.select(window).on('resize.for.' + selector, function () { + if (resizeLock) { + clearTimeout(resizeLock); + } + resizeLock = setTimeout(function () { + resizeLock = null; + self._resize(); + }, 500); + }); +} + +/** + * Add a visualization type + * + * @param string type Unique key/name used with setType + * @param object vis object map of vis methods + */ +xChart.setVis = function (type, vis) { + if (_vis.hasOwnProperty(type)) { + throw 'Cannot override vis type "' + type + '".'; + } + _vis[type] = vis; +}; + +/** + * Get a clone of a visualization + * Useful for extending vis functionality + * + * @param string type Unique key/name of the vis + */ +xChart.getVis = function (type) { + if (!_vis.hasOwnProperty(type)) { + throw 'Vis type "' + type + '" does not exist.'; + } + + return _.clone(_vis[type]); +}; + +xChart.setScale = function (name, fn) { + if (_scales.hasOwnProperty(name)) { + throw 'Scale type "' + name + '" already exists.'; + } + + _scales[name] = fn; +}; + +xChart.getScale = function (name) { + if (!_scales.hasOwnProperty(name)) { + throw 'Scale type "' + name + '" does not exist.'; + } + return _scales[name]; +}; + +xChart.visutils = _visutils; + +_.defaults(xChart.prototype, { + /** + * Set or change the drawing type for the main data. + * + * @param string type Must be an available drawing type + * + */ + setType: function (type, skipDraw) { + var self = this; + + if (self._type && type === self._type) { + return; + } + + if (!_vis.hasOwnProperty(type)) { + throw 'Vis type "' + type + '" is not defined.'; + } + + if (self._type) { + self._destroy(self._vis, self._mainStorage); + } + + self._type = type; + self._vis = _vis[type]; + if (!skipDraw) { + self._draw(); + } + }, + + /** + * Set and update the data for the chart. Optionally skip drawing. + * + * @param object data New data. See new xChart example for format + * + */ + setData: function (data) { + var self = this, + o = self._options, + nData = _.clone(data); + + if (!data.hasOwnProperty('main')) { + throw 'No "main" key found in given chart data.'; + } + + switch (data.type) { + case 'bar': + // force the xScale to be ordinal + data.xScale = 'ordinal'; + break; + case undefined: + data.type = self._type; + break; + } + + o.xMin = (isNaN(parseInt(data.xMin, 10))) ? o.xMin : data.xMin; + o.xMax = (isNaN(parseInt(data.xMax, 10))) ? o.xMax : data.xMax; + o.yMin = (isNaN(parseInt(data.yMin, 10))) ? o.yMin : data.yMin; + o.yMax = (isNaN(parseInt(data.yMax, 10))) ? o.yMax : data.yMax; + + if (self._vis) { + self._destroy(self._vis, self._mainStorage); + } + + self.setType(data.type, true); + + function _mapData(set) { + var d = _.map(_.clone(set.data), function (p) { + var np = _.clone(p); + if (p.hasOwnProperty('x')) { + np.x = o.dataFormatX(p.x); + } + if (p.hasOwnProperty('y')) { + np.y = o.dataFormatY(p.y); + } + return np; + }).sort(o.sortX); + return _.extend(_.clone(set), { data: d }); + } + + nData.main = _.map(nData.main, _mapData); + self._mainData = nData.main; + self._xScaleType = nData.xScale; + self._yScaleType = nData.yScale; + + if (nData.hasOwnProperty('comp')) { + nData.comp = _.map(nData.comp, _mapData); + self._compData = nData.comp; + } else { + self._compData = []; + } + + self._draw(); + }, + + /** + * Change the scale of an axis + * + * @param string axis Name of an axis. One of 'x' or 'y' + * @param string type Name of the scale type + * + */ + setScale: function (axis, type) { + var self = this; + + switch (axis) { + case 'x': + self._xScaleType = type; + break; + case 'y': + self._yScaleType = type; + break; + default: + throw 'Cannot change scale of unknown axis "' + axis + '".'; + } + + self._draw(); + }, + + /** + * Create the SVG element and g container. Resize if necessary. + */ + _drawSvg: function () { + var self = this, + c = self._container, + options = self._options, + width = parseInt(c.style('width').replace('px', ''), 10), + height = parseInt(c.style('height').replace('px', ''), 10), + svg, + g, + gScale; + + svg = c.selectAll('svg') + .data(emptyData); + + svg.enter().append('svg') + // Inherit the height and width from the parent element + .attr('height', height) + .attr('width', width) + .attr('class', 'xchart'); + + svg.transition() + .attr('width', width) + .attr('height', height); + + g = svg.selectAll('g') + .data(emptyData); + + g.enter().append('g') + .attr( + 'transform', + 'translate(' + options.paddingLeft + ',' + options.paddingTop + ')' + ); + + gScale = g.selectAll('g.scale') + .data(emptyData); + + gScale.enter().append('g') + .attr('class', 'scale'); + + self._svg = svg; + self._g = g; + self._gScale = gScale; + + self._height = height - options.paddingTop - options.paddingBottom - + options.axisPaddingTop - options.axisPaddingBottom; + self._width = width - options.paddingLeft - options.paddingRight - + options.axisPaddingLeft - options.axisPaddingRight; + }, + + /** + * Resize the visualization + */ + _resize: function (event) { + var self = this; + + self._drawSvg(); + self._draw(); + }, + + /** + * Draw the x and y axes + */ + _drawAxes: function () { + if (this._noData) { + return; + } + var self = this, + o = self._options, + t = self._gScale.transition().duration(o.timing), + xTicks = o.tickHintX, + yTicks = o.tickHintY, + bottom = self._height + o.axisPaddingTop + o.axisPaddingBottom, + zeroLine = d3.svg.line().x(function (d) { return d; }), + zLine, + zLinePath, + xAxis, + xRules, + yAxis, + yRules, + labels; + + xRules = d3.svg.axis() + .scale(self.xScale) + .ticks(xTicks) + .tickSize(-self._height) + .tickFormat(o.tickFormatX) + .orient('bottom'); + + xAxis = self._gScale.selectAll('g.axisX') + .data(emptyData); + + xAxis.enter().append('g') + .attr('class', 'axis axisX') + .attr('transform', 'translate(0,' + bottom + ')'); + + xAxis.call(xRules); + + labels = self._gScale.selectAll('.axisX g')[0]; + if (labels.length > (self._width / 80)) { + labels.sort(function (a, b) { + var r = /translate\(([^,)]+)/; + a = a.getAttribute('transform').match(r); + b = b.getAttribute('transform').match(r); + return parseFloat(a[1], 10) - parseFloat(b[1], 10); + }); + + d3.selectAll(labels) + .filter(function (d, i) { + return i % (Math.ceil(labels.length / xTicks) + 1); + }) + .remove(); + } + + yRules = d3.svg.axis() + .scale(self.yScale) + .ticks(yTicks) + .tickSize(-self._width - o.axisPaddingRight - o.axisPaddingLeft) + .tickFormat(o.tickFormatY) + .orient('left'); + + yAxis = self._gScale.selectAll('g.axisY') + .data(emptyData); + + yAxis.enter().append('g') + .attr('class', 'axis axisY') + .attr('transform', 'translate(0,0)'); + + t.selectAll('g.axisY') + .call(yRules); + + // zero line + zLine = self._gScale.selectAll('g.axisZero') + .data([[]]); + + zLine.enter().append('g') + .attr('class', 'axisZero'); + + zLinePath = zLine.selectAll('line') + .data([[]]); + + zLinePath.enter().append('line') + .attr('x1', 0) + .attr('x2', self._width + o.axisPaddingLeft + o.axisPaddingRight) + .attr('y1', self.yZero) + .attr('y2', self.yZero); + + zLinePath.transition().duration(o.timing) + .attr('y1', self.yZero) + .attr('y2', self.yZero); + }, + + /** + * Update the x and y scales (used when drawing) + * + * Optional methods in drawing types: + * preUpdateScale + * postUpdateScale + * + * Example implementation in vis type: + * + * function postUpdateScale(self, scaleData, mainData, compData) { + * self.xScale2 = d3.scale.ordinal() + * .domain(d3.range(0, mainData.length)) + * .rangeRoundBands([0, self.xScale.rangeBand()], 0.08); + * } + * + */ + _updateScale: function () { + var self = this, + _unionData = function () { + return _.union(self._mainData, self._compData); + }, + scaleData = _unionData(), + vis = self._vis, + scale, + min; + + delete self.xScale; + delete self.yScale; + delete self.yZero; + + if (vis.hasOwnProperty('preUpdateScale')) { + vis.preUpdateScale(self, scaleData, self._mainData, self._compData); + } + + // Just in case preUpdateScale modified + scaleData = _unionData(); + scale = _scales.xy(self, scaleData, self._xScaleType, self._yScaleType); + + self.xScale = scale.x; + self.yScale = scale.y; + + min = self.yScale.domain()[0]; + self.yZero = (min > 0) ? self.yScale(min) : self.yScale(0); + + if (vis.hasOwnProperty('postUpdateScale')) { + vis.postUpdateScale(self, scaleData, self._mainData, self._compData); + } + }, + + /** + * Create (Enter) the elements for the vis + * + * Required method + * + * Example implementation in vis type: + * + * function enter(self, data, callbacks) { + * var foo = self._g.selectAll('g.foobar') + * .data(data); + * foo.enter().append('g') + * .attr('class', 'foobar'); + * self.foo = foo; + * } + */ + _enter: function (vis, storage, data, className) { + var self = this, + callbacks = { + click: self._options.click, + mouseover: self._options.mouseover, + mouseout: self._options.mouseout + }; + self._checkVisMethod(vis, 'enter'); + vis.enter(self, storage, className, data, callbacks); + }, + + /** + * Update the elements opened by the select method + * + * Required method + * + * Example implementation in vis type: + * + * function update(self, timing) { + * self.bars.transition().duration(timing) + * .attr('width', self.xScale2.rangeBand()) + * .attr('height', function (d) { + * return self.yScale(d.y); + * }); + * } + */ + _update: function (vis, storage) { + var self = this; + self._checkVisMethod(vis, 'update'); + vis.update(self, storage, self._options.timing); + }, + + /** + * Remove or transition out the elements that no longer have data + * + * Required method + * + * Example implementation in vis type: + * + * function exit(self) { + * self.bars.exit().remove(); + * } + */ + _exit: function (vis, storage) { + var self = this; + self._checkVisMethod(vis, 'exit'); + vis.exit(self, storage, self._options.timing); + }, + + /** + * Destroy the current vis type (transition to new type) + * + * Required method + * + * Example implementation in vis type: + * + * function destroy(self, timing) { + * self.bars.transition().duration(timing) + * attr('height', 0); + * delete self.bars; + * } + */ + _destroy: function (vis, storage) { + var self = this; + self._checkVisMethod(vis, 'destroy'); + try { + vis.destroy(self, storage, self._options.timing); + } catch (e) {} + }, + + /** + * Draw the visualization + */ + _draw: function () { + var self = this, + o = self._options, + comp, + compKeys; + + self._noData = _.flatten(_.pluck(self._mainData, 'data') + .concat(_.pluck(self._compData, 'data'))).length === 0; + + self._updateScale(); + self._drawAxes(); + + self._enter(self._vis, self._mainStorage, self._mainData, '.main'); + self._exit(self._vis, self._mainStorage); + self._update(self._vis, self._mainStorage); + + comp = _.chain(self._compData).groupBy(function (d) { + return d.type; + }); + compKeys = comp.keys(); + + // Find old comp vis items and remove any that no longer exist + _.each(self._compStorage, function (d, key) { + if (-1 === compKeys.indexOf(key).value()) { + var vis = _vis[key]; + self._enter(vis, d, [], '.comp.' + key.replace(/\W+/g, '')); + self._exit(vis, d); + } + }); + + comp.each(function (d, key) { + var vis = _vis[key], storage; + if (!self._compStorage.hasOwnProperty(key)) { + self._compStorage[key] = {}; + } + storage = self._compStorage[key]; + self._enter(vis, storage, d, '.comp.' + key.replace(/\W+/g, '')); + self._exit(vis, storage); + self._update(vis, storage); + }); + + if (self._noData) { + o.empty(self, self._selector, self._mainData); + } else { + o.notempty(self, self._selector); + } + }, + + /** + * Ensure drawing method exists + */ + _checkVisMethod: function (vis, method) { + var self = this; + if (!vis[method]) { + throw 'Required method "' + method + '" not found on vis type "' + + self._type + '".'; + } + } +}); +if (typeof define === 'function' && define.amd && typeof define.amd === 'object') { + define(function () { + return xChart; + }); + return; +} + +window.xChart = xChart; + +}()); diff --git a/plugins/web_gui/static/plugins/xcharts/xcharts.min.css b/plugins/web_gui/static/plugins/xcharts/xcharts.min.css new file mode 100644 index 0000000..d5912ea --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/xcharts.min.css @@ -0,0 +1 @@ +.xchart .line{stroke-width:3px;fill:none}.xchart .fill{stroke-width:0}.xchart circle{stroke:#FFF;stroke-width:3px}.xchart .axis .domain{fill:none}.xchart .axis .tick line{stroke:#EEE;stroke-width:1px}.xchart .axis text{font-family:Helvetica,Arial,Verdana,sans-serif;fill:#666;font-size:12px}.xchart .color0 .line{stroke:#3880aa}.xchart .color0 .line .fill{pointer-events:none}.xchart .color0 rect,.xchart .color0 circle{fill:#3880aa}.xchart .color0 .fill{fill:rgba(56,128,170,0.1)}.xchart .color0.comp .line{stroke:#89bbd8}.xchart .color0.comp rect{fill:#89bbd8}.xchart .color0.comp .fill{display:none}.xchart .color0.comp circle,.xchart .color0.comp .pointer{fill:#89bbd8}.xchart .color1 .line{stroke:#4da944}.xchart .color1 .line .fill{pointer-events:none}.xchart .color1 rect,.xchart .color1 circle{fill:#4da944}.xchart .color1 .fill{fill:rgba(77,169,68,0.1)}.xchart .color1.comp .line{stroke:#9dd597}.xchart .color1.comp rect{fill:#9dd597}.xchart .color1.comp .fill{display:none}.xchart .color1.comp circle,.xchart .color1.comp .pointer{fill:#9dd597}.xchart .color2 .line{stroke:#f26522}.xchart .color2 .line .fill{pointer-events:none}.xchart .color2 rect,.xchart .color2 circle{fill:#f26522}.xchart .color2 .fill{fill:rgba(242,101,34,0.1)}.xchart .color2.comp .line{stroke:#f9b99a}.xchart .color2.comp rect{fill:#f9b99a}.xchart .color2.comp .fill{display:none}.xchart .color2.comp circle,.xchart .color2.comp .pointer{fill:#f9b99a}.xchart .color3 .line{stroke:#c6080d}.xchart .color3 .line .fill{pointer-events:none}.xchart .color3 rect,.xchart .color3 circle{fill:#c6080d}.xchart .color3 .fill{fill:rgba(198,8,13,0.1)}.xchart .color3.comp .line{stroke:#f8555a}.xchart .color3.comp rect{fill:#f8555a}.xchart .color3.comp .fill{display:none}.xchart .color3.comp circle,.xchart .color3.comp .pointer{fill:#f8555a}.xchart .color4 .line{stroke:#672d8b}.xchart .color4 .line .fill{pointer-events:none}.xchart .color4 rect,.xchart .color4 circle{fill:#672d8b}.xchart .color4 .fill{fill:rgba(103,45,139,0.1)}.xchart .color4.comp .line{stroke:#a869ce}.xchart .color4.comp rect{fill:#a869ce}.xchart .color4.comp .fill{display:none}.xchart .color4.comp circle,.xchart .color4.comp .pointer{fill:#a869ce}.xchart .color5 .line{stroke:#ce1797}.xchart .color5 .line .fill{pointer-events:none}.xchart .color5 rect,.xchart .color5 circle{fill:#ce1797}.xchart .color5 .fill{fill:rgba(206,23,151,0.1)}.xchart .color5.comp .line{stroke:#f075cb}.xchart .color5.comp rect{fill:#f075cb}.xchart .color5.comp .fill{display:none}.xchart .color5.comp circle,.xchart .color5.comp .pointer{fill:#f075cb}.xchart .color6 .line{stroke:#d9ce00}.xchart .color6 .line .fill{pointer-events:none}.xchart .color6 rect,.xchart .color6 circle{fill:#d9ce00}.xchart .color6 .fill{fill:rgba(217,206,0,0.1)}.xchart .color6.comp .line{stroke:#fff75a}.xchart .color6.comp rect{fill:#fff75a}.xchart .color6.comp .fill{display:none}.xchart .color6.comp circle,.xchart .color6.comp .pointer{fill:#fff75a}.xchart .color7 .line{stroke:#754c24}.xchart .color7 .line .fill{pointer-events:none}.xchart .color7 rect,.xchart .color7 circle{fill:#754c24}.xchart .color7 .fill{fill:rgba(117,76,36,0.1)}.xchart .color7.comp .line{stroke:#c98c50}.xchart .color7.comp rect{fill:#c98c50}.xchart .color7.comp .fill{display:none}.xchart .color7.comp circle,.xchart .color7.comp .pointer{fill:#c98c50}.xchart .color8 .line{stroke:#2eb9b4}.xchart .color8 .line .fill{pointer-events:none}.xchart .color8 rect,.xchart .color8 circle{fill:#2eb9b4}.xchart .color8 .fill{fill:rgba(46,185,180,0.1)}.xchart .color8.comp .line{stroke:#86e1de}.xchart .color8.comp rect{fill:#86e1de}.xchart .color8.comp .fill{display:none}.xchart .color8.comp circle,.xchart .color8.comp .pointer{fill:#86e1de}.xchart .color9 .line{stroke:#0e2e42}.xchart .color9 .line .fill{pointer-events:none}.xchart .color9 rect,.xchart .color9 circle{fill:#0e2e42}.xchart .color9 .fill{fill:rgba(14,46,66,0.1)}.xchart .color9.comp .line{stroke:#2477ab}.xchart .color9.comp rect{fill:#2477ab}.xchart .color9.comp .fill{display:none}.xchart .color9.comp circle,.xchart .color9.comp .pointer{fill:#2477ab} \ No newline at end of file diff --git a/plugins/web_gui/static/plugins/xcharts/xcharts.min.js b/plugins/web_gui/static/plugins/xcharts/xcharts.min.js new file mode 100644 index 0000000..4788a98 --- /dev/null +++ b/plugins/web_gui/static/plugins/xcharts/xcharts.min.js @@ -0,0 +1,5 @@ +/*! +xCharts v0.3.0 Copyright (c) 2012, tenXer, Inc. All Rights Reserved. +@license MIT license. http://github.com/tenXer/xcharts for details +*/ +(function(){var xChart,_vis={},_scales={},_visutils={};(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);function getInsertionPoint(zIndex){return _.chain(_.range(zIndex,10)).reverse().map(function(z){return'g[data-index="'+z+'"]'}).value().join(", ")}function colorClass(el,i){var c=el.getAttribute("class");return(c!==null?c.replace(/color\d+/g,""):"")+" color"+i}_visutils={getInsertionPoint:getInsertionPoint,colorClass:colorClass};var local=this,defaultSpacing=.25;function _getDomain(data,axis){return _.chain(data).pluck("data").flatten().pluck(axis).uniq().filter(function(d){return d!==undefined&&d!==null}).value().sort(d3.ascending)}_scales.ordinal=function(data,axis,bounds,extents){var domain=_getDomain(data,axis);return d3.scale.ordinal().domain(domain).rangeRoundBands(bounds,defaultSpacing)};_scales.linear=function(data,axis,bounds,extents){return d3.scale.linear().domain(extents).nice().rangeRound(bounds)};_scales.exponential=function(data,axis,bounds,extents){return d3.scale.pow().exponent(.65).domain(extents).nice().rangeRound(bounds)};_scales.time=function(data,axis,bounds,extents){return d3.time.scale().domain(_.map(extents,function(d){return new Date(d)})).range(bounds)};function _extendDomain(domain,axis){var min=domain[0],max=domain[1],diff,e;if(min===max){e=Math.max(Math.round(min/10),4);min-=e;max+=e}diff=max-min;min=min?min-diff/10:min;min=domain[0]>0?Math.max(min,0):min;max=max?max+diff/10:max;max=domain[1]<0?Math.min(max,0):max;return[min,max]}function _getExtents(options,data,xType,yType){var extents,nData=_.chain(data).pluck("data").flatten().value();extents={x:d3.extent(nData,function(d){return d.x}),y:d3.extent(nData,function(d){return d.y})};_.each([xType,yType],function(type,i){var axis=i?"y":"x",extended;extents[axis]=d3.extent(nData,function(d){return d[axis]});if(type==="ordinal"){return}_.each([axis+"Min",axis+"Max"],function(minMax,i){if(type!=="time"){extended=_extendDomain(extents[axis])}if(options.hasOwnProperty(minMax)&&options[minMax]!==null){extents[axis][i]=options[minMax]}else if(type!=="time"){extents[axis][i]=extended[i]}})});return extents}_scales.xy=function(self,data,xType,yType){var o=self._options,extents=_getExtents(o,data,xType,yType),scales={},horiz=[o.axisPaddingLeft,self._width],vert=[self._height,o.axisPaddingTop],xScale,yScale;_.each([xType,yType],function(type,i){var axis=i===0?"x":"y",bounds=i===0?horiz:vert,fn=xChart.getScale(type);scales[axis]=fn(data,axis,bounds,extents[axis])});return scales};(function(){var zIndex=2,selector="g.bar",insertBefore=_visutils.getInsertionPoint(zIndex);function postUpdateScale(self,scaleData,mainData,compData){self.xScale2=d3.scale.ordinal().domain(d3.range(0,mainData.length)).rangeRoundBands([0,self.xScale.rangeBand()],.08)}function enter(self,storage,className,data,callbacks){var barGroups,bars,yZero=self.yZero;barGroups=self._g.selectAll(selector+className).data(data,function(d){return d.className});barGroups.enter().insert("g",insertBefore).attr("data-index",zIndex).style("opacity",0).attr("class",function(d,i){var cl=_.uniq((className+d.className).split(".")).join(" ");return cl+" bar "+_visutils.colorClass(this,i)}).attr("transform",function(d,i){return"translate("+self.xScale2(i)+",0)"});bars=barGroups.selectAll("rect").data(function(d){return d.data},function(d){return d.x});bars.enter().append("rect").attr("width",0).attr("rx",3).attr("ry",3).attr("x",function(d){return self.xScale(d.x)+self.xScale2.rangeBand()/2}).attr("height",function(d){return Math.abs(yZero-self.yScale(d.y))}).attr("y",function(d){return d.y<0?yZero:self.yScale(d.y)}).on("mouseover",callbacks.mouseover).on("mouseout",callbacks.mouseout).on("click",callbacks.click);storage.barGroups=barGroups;storage.bars=bars}function update(self,storage,timing){var yZero=self.yZero;storage.barGroups.attr("class",function(d,i){return _visutils.colorClass(this,i)}).transition().duration(timing).style("opacity",1).attr("transform",function(d,i){return"translate("+self.xScale2(i)+",0)"});storage.bars.transition().duration(timing).attr("width",self.xScale2.rangeBand()).attr("x",function(d){return self.xScale(d.x)}).attr("height",function(d){return Math.abs(yZero-self.yScale(d.y))}).attr("y",function(d){return d.y<0?yZero:self.yScale(d.y)})}function exit(self,storage,timing){storage.bars.exit().transition().duration(timing).attr("width",0).remove();storage.barGroups.exit().transition().duration(timing).style("opacity",0).remove()}function destroy(self,storage,timing){var band=self.xScale2?self.xScale2.rangeBand()/2:0;delete self.xScale2;storage.bars.transition().duration(timing).attr("width",0).attr("x",function(d){return self.xScale(d.x)+band})}_vis.bar={postUpdateScale:postUpdateScale,enter:enter,update:update,exit:exit,destroy:destroy}})();(function(){var zIndex=3,selector="g.line",insertBefore=_visutils.getInsertionPoint(zIndex);function enter(self,storage,className,data,callbacks){var inter=self._options.interpolation,x=function(d,i){if(!self.xScale2&&!self.xScale.rangeBand){return self.xScale(d.x)}return self.xScale(d.x)+self.xScale.rangeBand()/2},y=function(d){return self.yScale(d.y)},line=d3.svg.line().x(x).interpolate(inter),area=d3.svg.area().x(x).y1(self.yZero).interpolate(inter),container,fills,paths;function datum(d){return[d.data]}container=self._g.selectAll(selector+className).data(data,function(d){return d.className});container.enter().insert("g",insertBefore).attr("data-index",zIndex).attr("class",function(d,i){var cl=_.uniq((className+d.className).split(".")).join(" ");return cl+" line "+_visutils.colorClass(this,i)});fills=container.selectAll("path.fill").data(datum);fills.enter().append("path").attr("class","fill").style("opacity",0).attr("d",area.y0(y));paths=container.selectAll("path.line").data(datum);paths.enter().append("path").attr("class","line").style("opacity",0).attr("d",line.y(y));storage.lineContainers=container;storage.lineFills=fills;storage.linePaths=paths;storage.lineX=x;storage.lineY=y;storage.lineA=area;storage.line=line}function update(self,storage,timing){storage.lineContainers.attr("class",function(d,i){return _visutils.colorClass(this,i)});storage.lineFills.transition().duration(timing).style("opacity",1).attr("d",storage.lineA.y0(storage.lineY));storage.linePaths.transition().duration(timing).style("opacity",1).attr("d",storage.line.y(storage.lineY))}function exit(self,storage){storage.linePaths.exit().style("opacity",0).remove();storage.lineFills.exit().style("opacity",0).remove();storage.lineContainers.exit().remove()}function destroy(self,storage,timing){storage.linePaths.transition().duration(timing).style("opacity",0);storage.lineFills.transition().duration(timing).style("opacity",0)}_vis.line={enter:enter,update:update,exit:exit,destroy:destroy}})();(function(){var line=_vis.line;function enter(self,storage,className,data,callbacks){var circles;line.enter(self,storage,className,data,callbacks);circles=storage.lineContainers.selectAll("circle").data(function(d){return d.data},function(d){return d.x});circles.enter().append("circle").style("opacity",0).attr("cx",storage.lineX).attr("cy",storage.lineY).attr("r",5).on("mouseover",callbacks.mouseover).on("mouseout",callbacks.mouseout).on("click",callbacks.click);storage.lineCircles=circles}function update(self,storage,timing){line.update.apply(null,_.toArray(arguments));storage.lineCircles.transition().duration(timing).style("opacity",1).attr("cx",storage.lineX).attr("cy",storage.lineY)}function exit(self,storage){storage.lineCircles.exit().remove();line.exit.apply(null,_.toArray(arguments))}function destroy(self,storage,timing){line.destroy.apply(null,_.toArray(arguments));if(!storage.lineCircles){return}storage.lineCircles.transition().duration(timing).style("opacity",0)}_vis["line-dotted"]={enter:enter,update:update,exit:exit,destroy:destroy}})();(function(){var line=_vis["line-dotted"];function enter(self,storage,className,data,callbacks){line.enter(self,storage,className,data,callbacks)}function _accumulate_data(data){function reduce(memo,num){return memo+num.y}var nData=_.map(data,function(set){var i=set.data.length,d=_.clone(set.data);set=_.clone(set);while(i){i-=1;d[i]=_.clone(set.data[i]);d[i].y0=set.data[i].y;d[i].y=_.reduce(_.first(set.data,i),reduce,set.data[i].y)}return _.extend(set,{data:d})});return nData}function _resetData(self){if(!self.hasOwnProperty("cumulativeOMainData")){return}self._mainData=self.cumulativeOMainData;delete self.cumulativeOMainData;self._compData=self.cumulativeOCompData;delete self.cumulativeOCompData}function preUpdateScale(self,data){_resetData(self);self.cumulativeOMainData=self._mainData;self._mainData=_accumulate_data(self._mainData);self.cumulativeOCompData=self._compData;self._compData=_accumulate_data(self._compData)}function destroy(self,storage,timing){_resetData(self);line.destroy.apply(null,_.toArray(arguments))}_vis.cumulative={preUpdateScale:preUpdateScale,enter:enter,update:line.update,exit:line.exit,destroy:destroy}})();var emptyData=[[]],defaults={mouseover:function(data,i){},mouseout:function(data,i){},click:function(data,i){},axisPaddingTop:0,axisPaddingRight:0,axisPaddingBottom:5,axisPaddingLeft:20,paddingTop:0,paddingRight:0,paddingBottom:20,paddingLeft:60,tickHintX:10,tickFormatX:function(x){return x},tickHintY:10,tickFormatY:function(y){return y},xMin:null,xMax:null,yMin:null,yMax:null,dataFormatX:function(x){return x},dataFormatY:function(y){return y},unsupported:function(selector){d3.select(selector).text("SVG is not supported on your browser")},empty:function(self,selector,d){},notempty:function(self,selector){},timing:750,interpolation:"monotone",sortX:function(a,b){return!a.x&&!b.x?0:a.xself._width/80){labels.sort(function(a,b){var r=/translate\(([^,)]+)/;a=a.getAttribute("transform").match(r);b=b.getAttribute("transform").match(r);return parseFloat(a[1],10)-parseFloat(b[1],10)});d3.selectAll(labels).filter(function(d,i){return i%(Math.ceil(labels.length/xTicks)+1)}).remove()}yRules=d3.svg.axis().scale(self.yScale).ticks(yTicks).tickSize(-self._width-o.axisPaddingRight-o.axisPaddingLeft).tickFormat(o.tickFormatY).orient("left");yAxis=self._gScale.selectAll("g.axisY").data(emptyData);yAxis.enter().append("g").attr("class","axis axisY").attr("transform","translate(0,0)");t.selectAll("g.axisY").call(yRules);zLine=self._gScale.selectAll("g.axisZero").data([[]]);zLine.enter().append("g").attr("class","axisZero");zLinePath=zLine.selectAll("line").data([[]]);zLinePath.enter().append("line").attr("x1",0).attr("x2",self._width+o.axisPaddingLeft+o.axisPaddingRight).attr("y1",self.yZero).attr("y2",self.yZero);zLinePath.transition().duration(o.timing).attr("y1",self.yZero).attr("y2",self.yZero)},_updateScale:function(){var self=this,_unionData=function(){return _.union(self._mainData,self._compData)},scaleData=_unionData(),vis=self._vis,scale,min;delete self.xScale;delete self.yScale;delete self.yZero;if(vis.hasOwnProperty("preUpdateScale")){vis.preUpdateScale(self,scaleData,self._mainData,self._compData)}scaleData=_unionData();scale=_scales.xy(self,scaleData,self._xScaleType,self._yScaleType);self.xScale=scale.x;self.yScale=scale.y;min=self.yScale.domain()[0];self.yZero=min>0?self.yScale(min):self.yScale(0);if(vis.hasOwnProperty("postUpdateScale")){vis.postUpdateScale(self,scaleData,self._mainData,self._compData)}},_enter:function(vis,storage,data,className){var self=this,callbacks={click:self._options.click,mouseover:self._options.mouseover,mouseout:self._options.mouseout};self._checkVisMethod(vis,"enter");vis.enter(self,storage,className,data,callbacks)},_update:function(vis,storage){var self=this;self._checkVisMethod(vis,"update");vis.update(self,storage,self._options.timing)},_exit:function(vis,storage){var self=this;self._checkVisMethod(vis,"exit");vis.exit(self,storage,self._options.timing)},_destroy:function(vis,storage){var self=this;self._checkVisMethod(vis,"destroy");try{vis.destroy(self,storage,self._options.timing)}catch(e){}},_draw:function(){var self=this,o=self._options,comp,compKeys;self._noData=_.flatten(_.pluck(self._mainData,"data").concat(_.pluck(self._compData,"data"))).length===0;self._updateScale();self._drawAxes();self._enter(self._vis,self._mainStorage,self._mainData,".main");self._exit(self._vis,self._mainStorage);self._update(self._vis,self._mainStorage);comp=_.chain(self._compData).groupBy(function(d){return d.type});compKeys=comp.keys();_.each(self._compStorage,function(d,key){if(-1===compKeys.indexOf(key).value()){var vis=_vis[key];self._enter(vis,d,[],".comp."+key.replace(/\W+/g,""));self._exit(vis,d)}});comp.each(function(d,key){var vis=_vis[key],storage;if(!self._compStorage.hasOwnProperty(key)){self._compStorage[key]={}}storage=self._compStorage[key];self._enter(vis,storage,d,".comp."+key.replace(/\W+/g,""));self._exit(vis,storage);self._update(vis,storage)});if(self._noData){o.empty(self,self._selector,self._mainData)}else{o.notempty(self,self._selector)}},_checkVisMethod:function(vis,method){var self=this;if(!vis[method]){throw'Required method "'+method+'" not found on vis type "'+self._type+'".'}}});if(typeof define==="function"&&define.amd&&typeof define.amd==="object"){define(function(){return xChart});return}window.xChart=xChart})(); \ No newline at end of file diff --git a/plugins/web_gui/static/restart.html b/plugins/web_gui/static/restart.html new file mode 100644 index 0000000..57eae96 --- /dev/null +++ b/plugins/web_gui/static/restart.html @@ -0,0 +1,37 @@ + + + + + StarryPy + + + + + + + + + + + + +
+
+
+
+
+ {% if handler.error_message == "" %} +

Server has been restarted!

+ {% else %} +

{{ handler.error_message }}

+ {% end %} +
+
+
+
+
+ + \ No newline at end of file diff --git a/plugins/web_gui/web_gui.py b/plugins/web_gui/web_gui.py new file mode 100644 index 0000000..38ef623 --- /dev/null +++ b/plugins/web_gui/web_gui.py @@ -0,0 +1,325 @@ +#:coding=utf-8: +import os +import logging +import json +import tornado.web +import tornado.websocket +import subprocess +from datetime import datetime +from twisted.internet import reactor +from plugins.core.player_manager import UserLevels +from tornado.ioloop import PeriodicCallback + + +class BaseHandler(tornado.web.RequestHandler): + + def get_current_user(self): + return self.get_secure_cookie("player") + + +class LoginHandler(BaseHandler): + + def initialize(self): + self.failed_login = False + + def get(self): + self.render("login.html") + + def post(self): + self.login_user = self.player_manager.get_by_name(self.get_argument("name", strip=False)) + if self.login_user is None: + self.login_user = self.player_manager.get_by_org_name(self.get_argument("name", strip=False)) + + if self.login_user is None or self.get_argument("password") != self.ownerpassword: + self.failed_login = True + self.render("login.html") + else: + self.set_secure_cookie("player", self.get_argument("name", strip=False)) + self.factory.broadcast("An admin has joined the server through Web-GUI.", 0, self.get_argument("name", strip=False)) + self.failed_login = False + self.redirect(self.get_argument("next", "/")) + + +class RestartHandler(BaseHandler): + + def initialize(self): + self.web_gui_user = self.player_manager.get_by_name(self.get_current_user()) + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + if self.web_gui_user.access_level == self.levels["OWNER"]: + self.error_message = "" + self.render("restart.html") + subprocess.call(self.restart_script, shell=True) + else: + self.error_message = "Only owners can restart the server!" + self.render("restart.html") + + +class LogoutHandler(BaseHandler): + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.clear_cookie("player") + self.redirect("/login") + + +class IndexHandler(BaseHandler): + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("index.html") + + +class DashboardHandler(BaseHandler): + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("ajax/dashboard.html") + + +class PlayerListHandler(BaseHandler): + + def initialize(self): + self.playerlist = self.player_manager.all() + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("ajax/playerlist.html") + + +class PlayerOnlineSideBarHandler(BaseHandler): + + def initialize(self): + self.playerlistonline = self.player_manager.who() + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("ajax/playersonline.html") + + +class PlayerOnlineListHandler(BaseHandler): + + def initialize(self): + self.playerlistonline = self.player_manager.who() + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("ajax/playerlistonline.html") + + +class PlayerEditHandler(BaseHandler): + + def initialize(self): + self.web_gui_user = self.player_manager.get_by_name(self.get_current_user()) + self.error_message = "" + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.edit_player = self.player_manager.get_by_name(self.get_argument("playername", strip=False)) + try: + self.error_message = self.get_argument("error_message") + except tornado.web.MissingArgumentError: + self.error_message = "" + self.render("ajax/playeredit.html") + + @tornado.web.authenticated + @tornado.web.asynchronous + def post(self): + self.edit_player = self.player_manager.get_by_name(self.get_argument("old_playername", strip=False)) + if self.web_gui_user.access_level > self.edit_player.access_level: + if self.edit_player.access_level != self.get_argument("access_level"): + self.edit_player.access_level = self.get_argument("access_level") + if self.get_argument("playername", strip=False) != "" and self.edit_player.name != self.get_argument("playername", strip=False): + if self.edit_player.org_name is None: + self.edit_player.org_name = self.edit_player.name + self.edit_player.name = self.get_argument("playername", strip=False) + else: + error_message = "You are not allowed to change this users' data!" + self.redirect("ajax/playeredit.html?playername={n}&error_message={e}".format( + n=self.get_argument("playername", strip=False), e=error_message)) + self.render("ajax/playeredit.html") + + +class PlayerQuickMenuHandler(BaseHandler): + + def initialize(self): + self.edit_player = self.player_manager.get_by_name(self.get_argument("playername", strip=False)) + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("ajax/playerquickmenu.html") + + +class PlayerActionHandler(BaseHandler): + + def initialize(self): + self.web_gui_user = self.player_manager.get_by_name(self.get_current_user()) + self.edit_player = self.player_manager.get_by_name(self.get_argument("info", strip=False)) + + @tornado.web.authenticated + @tornado.web.asynchronous + def post(self): + if self.get_argument("action") == "delete": + if self.web_gui_user.access_level == self.levels["OWNER"]: + if self.edit_player is not None: + self.player_manager.delete(self.edit_player) + response = json.dumps({"status": "OK", "msg": "Player was deleted"}) + else: + response = json.dumps({"status": "ERROR", "msg": "Player not found!"}) + else: + response = json.dumps({"status": "ERROR", "msg": "You don't have permission to do this."}) + elif self.get_argument("action") == "ban": + if self.web_gui_user.access_level >= self.levels["ADMIN"]: + self.player_manager.ban(self.edit_player.ip) + if self.edit_player.logged_in: + protocol = self.factory.protocols[self.edit_player.protocol] + protocol.transport.loseConnection() + response = json.dumps({"status": "OK", "msg": "IP was banned"}) + else: + response = json.dumps({"status": "ERROR", "msg": "You don't have permission to do this."}) + elif self.get_argument("action") == "unban": + if self.web_gui_user.access_level >= self.levels["ADMIN"]: + self.player_manager.unban(self.edit_player.ip) + response = json.dumps({"status": "OK", "msg": "IP was unbanned"}) + else: + response = json.dumps({"status": "ERROR", "msg": "You don't have permission to do this."}) + elif self.get_argument("action") == "kick": + if self.web_gui_user.access_level >= self.levels["ADMIN"]: + if self.edit_player.logged_in: + protocol = self.factory.protocols[self.edit_player.protocol] + protocol.transport.loseConnection() + response = json.dumps({"status": "OK", "msg": "Player was kicked."}) + else: + response = json.dumps({"status": "ERROR", "msg": "Player not online."}) + else: + response = json.dumps({"status": "ERROR", "msg": "You don't have permission to do this."}) + else: + response = json.dumps({"status": "ERROR", "msg": "Invalid action."}) + self.finish(response) + + +class AdminStopHandler(BaseHandler): + + def initialize(self): + self.web_gui_user = self.player_manager.get_by_name(self.get_current_user()) + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + if self.web_gui_user.access_level == self.levels["OWNER"]: + self.error_message = "" + self.render("adminstop.html") + reactor.stop() + else: + self.error_message = "Only owners can stop the server!" + self.render("adminstop.html") + + +class WebSocketChatHandler(tornado.websocket.WebSocketHandler): + + def initialize(self): + self.clients = [] + self.callback = PeriodicCallback(self.update_chat, 500) + self.web_gui_user = self.player_manager.get_by_name(self.get_secure_cookie("player")) + + def open(self, *args): + self.clients.append(self) + for msg in self.messages_log: + self.write_message(msg) + self.callback.start() + + def on_message(self, message): + messagejson = json.loads(message) + + self.messages.append(message) + self.messages_log.append(message) + self.factory.broadcast("^yellow;<{d}> <^red;{u}^yellow;> {m}".format( + d=datetime.now().strftime("%H:%M"), u=self.web_gui_user.name, m=messagejson["message"]), 0, "") + + def update_chat(self): + if len(self.messages) > 0: + for message in sorted(self.messages): + for client in self.clients: + client.write_message(message) + del self.messages[0:len(self.messages)] + + def on_close(self): + self.clients.remove(self) + self.callback.stop() + + +class WebChatJsHandler(BaseHandler): + + @tornado.web.authenticated + @tornado.web.asynchronous + def get(self): + self.render("js/webgui.chat.js") + + +class WebGuiApp(tornado.web.Application): + def __init__(self, port, ownerpassword, playermanager, factory, cookie_secret, messages, messages_log, + restart_script): + logging.getLogger('tornado.general').addHandler(logging.FileHandler(self.config["log_path"])) + logging.getLogger('tornado.application').addHandler(logging.FileHandler(self.config["log_path"])) + logging.getLogger('tornado.access').addHandler(logging.FileHandler(self.config["log_path_access"])) + + BaseHandler.factory = factory + BaseHandler.player_manager = playermanager + BaseHandler.messages = messages + BaseHandler.messages_log = messages_log + BaseHandler.restart_script = restart_script + BaseHandler.ownerpassword = ownerpassword + BaseHandler.levels = UserLevels.ranks + WebChatJsHandler.wsport = port + WebSocketChatHandler.factory = factory + WebSocketChatHandler.player_manager = playermanager + WebSocketChatHandler.messages = messages + WebSocketChatHandler.messages_log = messages_log + + handlers = [ + (r"/login", LoginHandler), + (r"/logout", LogoutHandler), + (r"/restart", RestartHandler), + (r'/chat', WebSocketChatHandler), + (r'/stopserver', AdminStopHandler), + (r'/ajax/playerlistonline.html', PlayerOnlineListHandler), + (r'/ajax/playerlist.html', PlayerListHandler), + (r'/ajax/playeredit.html', PlayerEditHandler), + (r'/ajax/playerquickmenu.html', PlayerQuickMenuHandler), + (r'/ajax/playersonline.html', PlayerOnlineSideBarHandler), + (r'/ajax/dashboard.html', DashboardHandler), + (r'/ajax/playeraction', PlayerActionHandler), + (r'/js/webgui.chat.js', WebChatJsHandler), + (r'/index.html', IndexHandler), + (r'/', IndexHandler), + (r'/ajax/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/ajax')}), + (r'/css/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/css')}), + (r'/js/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/js')}), + (r'/plugins/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/plugins')}), + (r'/img/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/img')}), + (r'/images/(.*)', tornado.web.StaticFileHandler, + {'path': os.path.join(os.path.dirname(__file__), 'static/images')}) + ] + settings = dict( + template_path=os.path.join(os.path.dirname(__file__), "static"), + login_url="/login", + cookie_secret=cookie_secret, + xsrf_cookies=True, + debug=True + ) + tornado.web.Application.__init__(self, handlers, **settings) + self.listen(port) \ No newline at end of file diff --git a/plugins/web_gui/web_gui_plugin.py b/plugins/web_gui/web_gui_plugin.py new file mode 100644 index 0000000..b3c642f --- /dev/null +++ b/plugins/web_gui/web_gui_plugin.py @@ -0,0 +1,88 @@ +#:coding=utf-8: +import os +import string +import random +import json +from datetime import datetime +from base_plugin import BasePlugin +from plugins.core.player_manager import PlayerManager +from packets import chat_sent, client_connect +from . import web_gui +import tornado.ioloop +from tornado.platform.twisted import TwistedIOLoop +TwistedIOLoop().install() + + +class WebGuiPlugin(BasePlugin, PlayerManager): + name = "web_gui" + depends = ['player_manager'] + + def __init__(self): + super(WebGuiPlugin, self).__init__() + try: + self.port = int(self.config.plugin_config['port']) + except (AttributeError, ValueError): + self.port = 8083 + self.ownerpassword = self.config.plugin_config['ownerpassword'] + self.restart_script = self.config.plugin_config['restart_script'] + if self.config.plugin_config['cookie_token'] == "" or not self.config.plugin_config['remember_cookie_token']: + self.cookie_token = self.config.plugin_config['cookie_token'] = self.generate_cookie_token() + else: + self.cookie_token = self.config.plugin_config['cookie_token'] + self.messages = [] + self.messages_log = [] + + def activate(self): + super(WebGuiPlugin, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + web_gui.WebGuiApp.config = self.config.plugin_config + self.web_gui_app = web_gui.WebGuiApp(port=self.port, ownerpassword=self.ownerpassword, + playermanager=self.player_manager, factory=self.factory, + cookie_secret=self.cookie_token, messages=self.messages, + messages_log=self.messages_log, restart_script=self.restart_script) + self.logger.info("WebGUI listening on port {p}".format(p=self.port)) + self.gui_instance = tornado.ioloop.IOLoop.instance() + + def deactivate(self): + super(WebGuiPlugin, self).deactivate() + self.gui_instance.stop() + + def generate_cookie_token(self): + chars = string.ascii_letters + string.digits + random.seed(os.urandom(1024)) + return "".join(random.choice(chars) for i in range(64)) + + def on_chat_sent(self, data): + parsed = chat_sent().parse(data.data) + msgdate = datetime.now().strftime("[%H:%M:%S]") + message = json.dumps({"msgdate": msgdate, "author": self.protocol.player.name, + "message": parsed.message.decode("utf-8")}) + self.messages.append(message) + self.messages_log.append(message) + + return True + + def on_client_connect(self, data): + parsed = client_connect().parse(data.data) + msgdate = datetime.now().strftime("[%H:%M:%S]") + connect_player = self.player_manager.get_by_org_name(parsed.name.decode("utf-8")) + try: + if self.player_manager.check_bans(connect_player.ip): + msgtxt = "Banned Player {p} tried to join the server.".format(p=connect_player.name) + else: + msgtxt = "Player {p} has joined the server.".format(p=connect_player.name) + except AttributeError: + msgtxt = "New player has joined the server." + message = json.dumps({"msgdate": msgdate, "author": "SERVER", "message": msgtxt}) + self.messages.append(message) + self.messages_log.append(message) + + return True + + def on_client_disconnect(self, data): + if self.protocol.player is not None: + msgdate = datetime.now().strftime("[%H:%M:%S]") + msgtxt = "Player {p} has left the server.".format(p=self.protocol.player.name.encode("utf-8")) + message = json.dumps({"msgdate": msgdate, "author": "SERVER", "message": msgtxt}) + self.messages.append(message) + self.messages_log.append(message) From 210a4f7e148a6f4fee12043e1efb5208a03e481f Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Thu, 5 Feb 2015 04:02:23 -0500 Subject: [PATCH 69/81] People were skipping the wrapper by changing their character name with starcheat. The current solution to this matter, is that if a changed-named player connects, it uses their original name, and possibly adds a _ to the end as well. Each time they reconnect to the same-running server session, another _ will be added to the end of the name. While not ideal, this is better than skipping the wrapper. --- plugins/core/player_manager/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index d51d7e2..ec984e5 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -251,7 +251,6 @@ def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): if player.name != name: logger.info("Detected username change.") player.name = name - self.protocol.player.name = name if ip not in player.ips: player.ips.append(IPAddress(ip=ip)) player.ip = ip From 10859273aab1d59435f75d1af71d45c9bddea2e1 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Thu, 5 Feb 2015 04:10:19 -0500 Subject: [PATCH 70/81] Starter items got skipped for some reason. Fixing. --- plugins/starteritems/__init__.py | 1 + plugins/starteritems/starteritems_plugin.py | 40 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 plugins/starteritems/__init__.py create mode 100644 plugins/starteritems/starteritems_plugin.py diff --git a/plugins/starteritems/__init__.py b/plugins/starteritems/__init__.py new file mode 100644 index 0000000..b77a5e8 --- /dev/null +++ b/plugins/starteritems/__init__.py @@ -0,0 +1 @@ +from starteritems_plugin import StarterItems \ No newline at end of file diff --git a/plugins/starteritems/starteritems_plugin.py b/plugins/starteritems/starteritems_plugin.py new file mode 100644 index 0000000..a89647e --- /dev/null +++ b/plugins/starteritems/starteritems_plugin.py @@ -0,0 +1,40 @@ +from base_plugin import SimpleCommandPlugin +from utility_functions import give_item_to_player +from plugins.core.player_manager import permissions, UserLevels + + +class StarterItems(SimpleCommandPlugin): + """ + Welcomes new players by giving them a bunch of items. + """ + name = "starteritems_plugin" + depends = ["command_dispatcher", "player_manager"] + commands = ["starteritems"] + auto_activate = True + + def activate(self): + super(StarterItems, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + + @permissions(UserLevels.GUEST) + def starteritems(self, data): + """Gives you some starter items (only once).\nSyntax: /starteritems""" + try: + my_storage = self.protocol.player.storage + except AttributeError: + return + if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": + my_storage['given_starter_items'] = "True" + self.give_items() + self.send_greetings() + self.logger.info("Gave starter items to %s.", self.protocol.player.name) + self.protocol.player.storage = my_storage + else: + self.protocol.send_chat_message("^red;You have already received a starter pack :O") + + def give_items(self): + for item in self.config.plugin_config["items"]: + give_item_to_player(self.protocol, item[0], item[1]) + + def send_greetings(self): + self.protocol.send_chat_message(self.config.plugin_config["message"]) From b25b52aada747282fdf60871f19f5e848355f5c8 Mon Sep 17 00:00:00 2001 From: kharidiron Date: Fri, 6 Feb 2015 11:31:11 -0800 Subject: [PATCH 71/81] added tornado Needed for the web_gui. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 33ecf33..f156fe6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ sqlalchemy twisted enum34 construct -nose \ No newline at end of file +tornado +nose From d6d96b849967d18f5e7a0178968484bfe58ebf90 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 06:12:34 -0500 Subject: [PATCH 72/81] Some packet updating and cleanup. --- packets/data_types.py | 2 + packets/packet_types.py | 169 ++++++++++++++++++++++++---------------- 2 files changed, 102 insertions(+), 69 deletions(-) diff --git a/packets/data_types.py b/packets/data_types.py index 6570456..0d819c4 100644 --- a/packets/data_types.py +++ b/packets/data_types.py @@ -108,6 +108,8 @@ def _parse(self, stream, context): elif x == 3: flag = Flag("").parse_stream(stream) return Field("", 16).parse_stream(stream).encode("hex") + elif x == 4: + return star_string().parse_stream(stream) def _build(self, obj, stream, context): if len(obj) == 32: _write_stream(stream, 1, chr(3)) diff --git a/packets/packet_types.py b/packets/packet_types.py index 67f7097..ff7fac4 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -91,16 +91,7 @@ def _encode(self, obj, context): def _decode(self, obj, context): return obj.encode("hex") - -# may need to be corrected. new version only has hash, uses ByteArray -handshake_response = lambda name="handshake_response": Struct(name, - star_string("claim_response"), - star_string("hash")) - -# small correction. added proper context. may need to check if this is correct (need double. used bfloat64). -universe_time_update = lambda name="universe_time": Struct(name, - #VLQ("unknown")) - BFloat64("universe_time")) +# ---------- Utility constructs --------- packet = lambda name="base_packet": Struct(name, Byte("id"), @@ -112,27 +103,86 @@ def _decode(self, obj, context): Byte("id"), SignedVLQ("payload_size")) +connection = lambda name="connection": Struct(name, + GreedyRange(Byte("compressed_data"))) + +celestial_coordinate = lambda name="celestial_coordinate": Struct(name, + SBInt32("x"), + SBInt32("y"), + SBInt32("z"), + SBInt32("planet"), + SBInt32("satellite")) + +projectile = DictVariant("projectile") + +# --------------------------------------- + +# ----- Primary connection sequence ----- + +# (0) - ProtocolVersion : S -> C protocol_version = lambda name="protocol_version": Struct(name, UBInt32("server_build")) -connection = lambda name="connection": Struct(name, - GreedyRange(Byte("compressed_data"))) +# (7) - ClientConnect : C -> S +client_connect = lambda name="client_connect": Struct(name, + VLQ("asset_digest_length"), + String("asset_digest", + lambda ctx: ctx.asset_digest_length), + Flag("uuid_exists"), + If(lambda ctx: ctx.uuid_exists is True, + HexAdapter(Field("uuid", 16)) + ), + star_string("name"), + star_string("species"), + StarByteArray("ship_data"), + UBInt32("ship_level"), + UBInt32("max_fuel"), + VLQ("capabilities_length"), + Array(lambda ctx: ctx.capabilities_length, + Struct("capabilities", + star_string("value"))), + star_string("account")) -# may need to be corrected. new version only has salt, uses ByteArray +# (3) - HandshakeChallenge : S -> C handshake_challenge = lambda name="handshake_challenge": Struct(name, - star_string("claim_message"), - star_string("salt"), - SBInt32("round_count")) + StarByteArray("salt")) + +# (9) - HandshakeResponse : C -> S +handshake_response = lambda name="handshake_response": Struct(name, + star_string("hash")) -# Needs to be corrected to include 'celestial information' as well as proper reject -# sucess handling. +# (2) - ConnectResponse : S -> C connect_response = lambda name="connect_response": Struct(name, Flag("success"), VLQ("client_id"), star_string("reject_reason"), - GreedyRange(star_string("celestial_info"))) + Flag("celestial_info_exists"), + If(lambda ctx: ctx.celestial_info_exists, + Struct( + "celestial_data", + SBInt32("planet_orbital_levels"), + SBInt32("satellite_orbital_levels"), + SBInt32("chunk_size"), + SBInt32("xy_min"), + SBInt32("xy_max"), + SBInt32("z_min"), + SBInt32("z_max")))) + +# --------------------------------------- + +# (1) - ServerDisconnect +server_disconnect = lambda name="server_disconnect": Struct(name, + star_string("reason")) + +# (5) - UniverseTimeUpdate +universe_time_update = lambda name="universe_time": Struct(name, + BFloat64("universe_time")) + +# (8) - ClientDisconnectRequest +client_disconnect_request = lambda name="client_disconnect_request": Struct(name, + Byte("data")) -# corrected. needs testing +# (4) - ChatReceived chat_received = lambda name="chat_received": Struct(name, Enum(Byte("mode"), CHANNEL=0, @@ -144,7 +194,7 @@ def _decode(self, obj, context): star_string("name"), star_string("message")) -# corrected. shouldn't need too much testing +# (12) - ChatSent chat_sent = lambda name="chat_sent": Struct(name, star_string("message"), Enum(Byte("send_mode"), @@ -158,39 +208,7 @@ def _decode(self, obj, context): message=message, send_mode=send_mode)) -# quite a bit of guesswork and hackery here with the ship_upgrades. -client_connect = lambda name="client_connect": Struct(name, - VLQ("asset_digest_length"), - String("asset_digest", - lambda ctx: ctx.asset_digest_length), - Flag("uuid_exists"), - If(lambda ctx: ctx.uuid_exists is True, - HexAdapter(Field("uuid", 16)) - ), - star_string("name"), - star_string("species"), - StarByteArray("ship_data"), - UBInt32("ship_level"), - UBInt32("max_fuel"), - VLQ("capabilities"), - star_string("account")) - -server_disconnect = lambda name="server_disconnect": Struct(name, - star_string("reason")) - -client_disconnect_request = lambda name="client_disconnect_request": Struct(name, - Byte("data")) - -celestial_request = lambda name="celestial_request": Struct(name, - GreedyRange(star_string("requests"))) - -celestial_coordinate = lambda name="celestial_coordinate": Struct(name, - SBInt32("x"), - SBInt32("y"), - SBInt32("z"), - SBInt32("planet"), - SBInt32("satellite")) - +# (10) - PlayerWarp player_warp = lambda name="player_warp": Struct(name, Enum(UBInt8("warp_type"), WARP_TO=0, @@ -205,6 +223,7 @@ def _decode(self, obj, context): warp_type=t, world_id=world_id)) +# (11) - FlyShip fly_ship = lambda name="fly_ship": Struct(name, celestial_coordinate()) @@ -217,22 +236,39 @@ def _decode(self, obj, context): planet=planet, satellite=satellite))) -# partially correct. Needs work on dungeon ID value +# (13) - CelestialRequest +celestial_request = lambda name="celestial_request": Struct(name, + GreedyRange(star_string("requests"))) + +# (14) - ClientContextUpdate +client_context_update = lambda name="client_context": Struct(name, + VLQ("length"), + Byte("arguments"), + Array(lambda ctx: ctx.arguments, + Struct("key", + Variant("value")))) + +# (15) - WorldStart world_start = lambda name="world_start": Struct(name, - Variant("planet"), # rename to templateData? + Variant("planet"), StarByteArray("sky_data"), StarByteArray("weather_data"), - #dungeon id stuff here + # Dungeon ID stuff here BFloat32("x"), BFloat32("y"), Variant("world_properties"), UBInt32("client_id"), Flag("local_interpolation")) +# (16) - WorldStop world_stop = lambda name="world_stop": Struct(name, star_string("status")) -# I THINK this is ok. Will test later. +# (17) - CentralStructureUpdate +central_structure_update = lambda name="central_structure_update": Struct(name, + Variant("structureData")) + +# (23) - GiveItem give_item = lambda name="give_item": Struct(name, star_string("name"), VLQ("count"), @@ -246,6 +282,7 @@ def _decode(self, obj, context): variant_type=7, description='')) +# (52) - UpdateWorldProperties update_world_properties = lambda name="world_properties": Struct(name, UBInt8("count"), Array(lambda ctx: ctx.count, @@ -258,6 +295,7 @@ def _decode(self, obj, context): count=len(dictionary), properties=[Container(key=k, value=Container(type="SVLQ", data=v)) for k, v in dictionary.items()])) +# (45) - EntityCreate entity_create = Struct("entity_create", GreedyRange( Struct("entity", @@ -266,18 +304,11 @@ def _decode(self, obj, context): String("entity", lambda ctx: ctx.entity_size), SignedVLQ("entity_id")))) +# (46) - EntityUpdate entity_update = lambda name="entity_update": Struct(name, UBInt32("entity_id"), StarByteArray("delta")) -client_context_update = lambda name="client_context": Struct(name, - VLQ("length"), - Byte("arguments"), - Array(lambda ctx: ctx.arguments, - Struct("key", - Variant("value")))) - -central_structure_update = lambda name="central_structure_update": Struct(name, - Variant("structureData")) - -projectile = DictVariant("projectile") +# (53) - Heartbeat +heartbeat = lambda name="heartbeat": Structure(name, + UBInt64("remote_step")) From 13aa707af292a643734c909bbaa992b592ee6599 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 06:15:39 -0500 Subject: [PATCH 73/81] Setup a shared-secret auth system for mod+ users to prevent user spoofing issues. May need more testing and code cleanup. --- config/config.json.default | 1 + plugins/core/player_manager/manager.py | 14 ++++++++++++-- plugins/core/player_manager/plugin.py | 12 +++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 90b2d88..cef21ff 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -1,4 +1,5 @@ { + "admin_ss": "tester", "bind_address": "", "bind_port": 21025, "chat_prefix": "#", diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index ec984e5..7460472 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -148,6 +148,7 @@ class Player(Base): last_seen = Column(DateTime) access_level = Column(Integer) logged_in = Column(Boolean) + admin_logged_in = Column(Boolean) protocol = Column(String) client_id = Column(Integer) ip = Column(String) @@ -215,6 +216,7 @@ def __init__(self, config): with _autoclosing_session(self.sessionmaker) as session: for player in session.query(Player).filter_by(logged_in=True).all(): player.logged_in = False + player.admin_logged_in = False player.protocol = None session.commit() @@ -233,7 +235,7 @@ def _cache_and_return_from_session(self, session, record, collection=False): return to_return - def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): + def fetch_or_create(self, uuid, name, org_name, admin_logged_in, ip, protocol=None): with _autoclosing_session(self.sessionmaker) as session: if session.query(Player).filter_by(uuid=uuid, logged_in=True).first(): raise AlreadyLoggedIn @@ -256,12 +258,14 @@ def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): player.ip = ip player.protocol = protocol player.last_seen = datetime.datetime.now() + player.admin_logged_in = admin_logged_in else: logger.info("Adding new player with name: %s" % name) player = Player(uuid=uuid, name=name, org_name=org_name, last_seen=datetime.datetime.now(), access_level=int(UserLevels.GUEST), logged_in=False, + admin_logged_in=False, protocol=protocol, client_id=-1, ip=ip, @@ -385,7 +389,13 @@ def wrapper(f): @wraps(f) def wrapped_function(self, *args, **kwargs): if self.protocol.player.access_level >= level: - return f(self, *args, **kwargs) + if level >= UserLevels.MODERATOR: + if self.protocol.player.admin_logged_in == 0: + self.protocol.send_chat_message("^red;You're not logged in, so I can't let you do that.^yellow;") + else: + return f(self, *args, **kwargs) + else: + return f(self, *args, **kwargs) else: self.protocol.send_chat_message("You are not authorized to do this.") return False diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 62e4ca1..1762374 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -22,6 +22,7 @@ def activate(self): self.l_call = LoopingCall(self.check_logged_in) self.l_call.start(1, now=False) self.regexes = self.config.plugin_config['name_removal_regexes'] + self.adminss = self.config.plugin_config['admin_ss'] def deactivate(self): del self.player_manager @@ -30,6 +31,7 @@ def check_logged_in(self): for player in self.player_manager.who(): if player.protocol not in self.factory.protocols.keys(): player.logged_in = False + player.admin_logged_in = False def on_client_connect(self, data): client_data = client_connect().parse(data.data) @@ -59,11 +61,17 @@ def on_client_connect(self, data): #original_name += rnd_append #client_data.name += rnd_append + if client_data.account == self.adminss: + admin_login = True + else: + admin_login = False + original_name = client_data.name client_data.name = changed_name self.protocol.player = self.player_manager.fetch_or_create( name=client_data.name, org_name=original_name, + admin_logged_in = admin_login, uuid=str(client_data.uuid), ip=self.protocol.transport.getPeer().host, protocol=self.protocol.id, @@ -115,10 +123,7 @@ def on_connect_response(self, data): return True def after_world_start(self, data): - # may need to add more world types in here world_start = packets.world_start().parse(data.data) - # self.logger.debug("World start: %s", world_start) # debug world packets - # self.logger.debug("World start raw: %s", data.data.encode('hex')) # debug world packets if 'ship.maxFuel' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True @@ -139,6 +144,7 @@ def on_client_disconnect_request(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: self.logger.info("Player disconnected: %s", self.protocol.player.name) self.protocol.player.logged_in = False + self.protocol.player.admin_logged_in = False return True @permissions(UserLevels.REGISTERED) From 336972110f48eab57f6c6a0909294495790713f6 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 06:34:37 -0500 Subject: [PATCH 74/81] oops, forgot to fix the config.json file. --- config/config.json.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json.default b/config/config.json.default index cef21ff..2fc90b4 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -1,5 +1,4 @@ { - "admin_ss": "tester", "bind_address": "", "bind_port": 21025, "chat_prefix": "#", @@ -197,6 +196,7 @@ "auto_activate": true }, "player_manager": { + "admin_ss": "tester", "auto_activate": true, "name_removal_regexes": [ "\\^#[\\w]+;", From d743169f83c7a96445ddb91f14b41c83ec63ca08 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 16:52:12 -0500 Subject: [PATCH 75/81] Cleanup after changing my vim flow --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8697f98..65a156f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ nosetests.xml # Vim mess cleanup *.swp +*.un~ From 1296719f28cec24a86983a1f01e78f55b8f42506 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 16:53:01 -0500 Subject: [PATCH 76/81] More cleanup for vim --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 65a156f..5032ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ nosetests.xml # Vim mess cleanup *.swp *.un~ +.ropeproject/ From d90d15427910e66437d66dfad8d409acf8df9e6e Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 17:14:48 -0500 Subject: [PATCH 77/81] Change logging to use rotating log files, kept for 5 days, rotated at midnight. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5032ac7..a7e4d21 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ nosetests.xml *.db *.db-journal *.log +*.log.* .idea/ # Vim mess cleanup From 24807b79a475337823cd41444599a9f5172e5265 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Sat, 7 Feb 2015 17:15:17 -0500 Subject: [PATCH 78/81] helps if I add the file... --- server.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server.py b/server.py index 11a23ee..87b466f 100644 --- a/server.py +++ b/server.py @@ -3,6 +3,7 @@ #import gettext import locale import logging +import logging.handlers from uuid import uuid4 import sys import socket @@ -24,11 +25,11 @@ VERSION = "1.4.4" -VDEBUG_LVL = 9 +VDEBUG_LVL = 9 logging.addLevelName(VDEBUG_LVL, "VDEBUG") def vdebug(self, message, *args, **kws): if self.isEnabledFor(VDEBUG_LVL): - self._log(VDEBUG_LVL, message, args, **kws) + self._log(VDEBUG_LVL, message, args, **kws) logging.Logger.vdebug = vdebug def port_check(upstream_hostname, upstream_port): @@ -715,7 +716,7 @@ def init_localization(): log_level = "VDEBUG" else: log_level = logging.INFO - + print('Setup console logging...') console_handle = logging.StreamHandler(sys.stdout) console_handle.setLevel(log_level) @@ -723,7 +724,7 @@ def init_localization(): console_handle.setFormatter(log_format) print('Setup file-based logging...') - logfile_handle = logging.FileHandler("server.log") + logfile_handle = logging.handlers.TimedRotatingFileHandler("server.log", when='midnight', interval=5, backupCount=4) logfile_handle.setLevel(log_level) logger.addHandler(logfile_handle) logfile_handle.setFormatter(log_format) From 269a2c39307d43a2e2e7f5fdcbb9e68a45fc4867 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 10 Feb 2015 23:28:17 -0500 Subject: [PATCH 79/81] Added more packet types... used some of these to fix the issues with planet_protect. --- base_plugin.py | 6 + packets/packet_types.py | 118 +++++++++++++++--- .../planet_protect/planet_protect_plugin.py | 38 ++++-- plugins/warpy_plugin/warpy_plugin.py | 6 - server.py | 13 +- 5 files changed, 150 insertions(+), 31 deletions(-) diff --git a/base_plugin.py b/base_plugin.py index 4cbd2bf..d5ac26a 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -161,6 +161,9 @@ def on_entity_update(self, data): def on_entity_destroy(self, data): return True + def on_hit_request(self, data): + return True + def on_status_effect_request(self, data): return True @@ -311,6 +314,9 @@ def after_entity_update(self, data): def after_entity_destroy(self, data): return True + def after_hit_request(self, data): + return True + def after_status_effect_request(self, data): return True diff --git a/packets/packet_types.py b/packets/packet_types.py index ff7fac4..88ee24c 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -79,6 +79,22 @@ class EntityType(IntEnum): NPC = 8 +class InteractionType(IntEnum): + NONE = 0 + OPEN_COCKPIT_INTERFACE = 1 + OPEN_CONTAINER = 2 + SIT_DOWN = 3 + OPEN_CRAFTING_INTERFACE = 4 + PLAY_CINEMATIC = 5 + OPEN_SONGBOOK_INTERFACE = 6 + OPEN_NPC_CRAFTING_INTERFACE = 7 + OPEN_NPC_BOUNTY_INTERFACE = 8 + OPEN_AI_INTERFACE = 9 + OPEN_TELEPORT_DIALOG = 10 + SHOW_POPUP = 11 + SCRIPT_CONSOLE = 12 + + class PacketOutOfOrder(Exception): pass @@ -282,6 +298,94 @@ def _decode(self, obj, context): variant_type=7, description='')) +# (38) - SwapInContainer +swap_in_container = lambda name="swap_in_container": Struct(name, + VLQ("entity_id"), # Where are we putting stuff + star_string("item_name"), + VLQ("count"), + Byte("variant_type"), + StarByteArray("item_description"), + VLQ("slot")) + +# (24) - SwapInContainerResult - aka what item is selected / in our hand (does +# not mean wielding) +swap_in_container_result = lambda name="swap_in_container_result": Struct(name, + star_string("item_name"), + VLQ("count"), + Byte("variant_type"), + GreedyRange(StarByteArray("item_description"))) + +# (34) - SpawnEntity +spawn_entity = lambda name="spawn_entity": Struct(name, + GreedyRange( + Struct("entity", + Byte("entity_type"), + VLQ("payload_size"), + String("payload", lambda ctx: ctx.payload_size)))) + +# (45) - EntityCreate +entity_create = lambda name="entity_create": Struct(name, + GreedyRange( + Struct("entity", + Byte("entity_type"), + VLQ("payload_size"), + String("payload", lambda ctx: ctx.payload_size), + VLQ("entity_id")))) + +# (46) - EntityUpdate +entity_update = lambda name="entity_update": Struct(name, + UBInt32("entity_id"), + StarByteArray("delta")) + +# (47) - EntityDestroy +entity_destroy = lambda name="entity_destroy": Struct(name, + UBInt32("entity_id"), + Flag("death")) + +# (30) - EntityInteract +entity_interact = lambda name="entity_interact": Struct(name, + UBInt32("source_entity_id"), + BFloat32("source_x"), + BFloat32("source_y"), + UBInt32("target_entity_id")) + +# (26) - EntityInteractResult +entity_interact_result = lambda name="entity_interact_result": Struct(name, + UBInt32("interaction_type"), + UBInt32("target_entity_id"), + Variant("entity_data")) + +# (48) - HitRequest +hit_request = lambda name="hit_request": Struct(name, + UBInt32("source_entity_id"), + UBInt32("target_entity_id")) + +# (DamageRequest) - DamageRequest +damage_request = lambda name="damage_request": Struct(name, + UBInt32("source_entity_id"), + UBInt32("target_entity_id"), + UBint8("hit_type"), + UBInt8("damage_type"), + BFloat32("damage"), + BFloat32("knockback_x"), + BFloat32("knockback_y"), + UBInt32("source_entity_id_wut"), + star_string("damage_source_kind"), + GreedyReange(star_string("stuats_effects")) + ) + +# (50) - DamageNotification +damage_notification = lambda name="damage_notification": Struct(name, + UBInt32("source_entity_id"), + UBInt32("source_entity_id_wut"), + UBInt32("target_entity_id"), + VLQ("x"), + VLQ("y"), + VLQ("damage"), + star_string("damage_kind"), + star_string("target_material"), + Flag("killed")) + # (52) - UpdateWorldProperties update_world_properties = lambda name="world_properties": Struct(name, UBInt8("count"), @@ -295,20 +399,6 @@ def _decode(self, obj, context): count=len(dictionary), properties=[Container(key=k, value=Container(type="SVLQ", data=v)) for k, v in dictionary.items()])) -# (45) - EntityCreate -entity_create = Struct("entity_create", - GreedyRange( - Struct("entity", - Byte("entity_type"), - VLQ("entity_size"), - String("entity", lambda ctx: ctx.entity_size), - SignedVLQ("entity_id")))) - -# (46) - EntityUpdate -entity_update = lambda name="entity_update": Struct(name, - UBInt32("entity_id"), - StarByteArray("delta")) - # (53) - Heartbeat heartbeat = lambda name="heartbeat": Structure(name, UBInt64("remote_step")) diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 1d4548f..1ae345d 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -1,7 +1,7 @@ import re from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import UserLevels, permissions -from packets import entity_create, EntityType, star_string +from packets import entity_create, EntityType, star_string, entity_interact_result, InteractionType from utility_functions import extract_name @@ -168,13 +168,37 @@ def on_entity_create(self, data): if name in self.player_planets[self.protocol.player.planet]: return True else: - entities = entity_create.parse(data.data) + entities = entity_create().parse(data.data) for entity in entities.entity: + self.logger.vdebug("Entity Type: %s", entity.entity_type) if entity.entity_type == EntityType.PROJECTILE: - if self.block_all: return False - p_type = star_string("").parse(entity.entity) + self.logger.vdebug("projectile detected") + if self.block_all: + return False + p_type = star_string("").parse(entity.payload) + self.logger.vdebug("projectile: %s", p_type) if p_type in self.blacklist: - self.logger.info( - "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", - self.protocol.player.org_name, p_type) + self.logger.vdebug("%s",self.blacklist) + if p_type in ['water']: + self.logger.vdebug( + "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", + self.protocol.player.org_name, p_type) + else: + self.logger.info( + "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", + self.protocol.player.org_name, p_type) return False + + def on_entity_interact_result(self, data): + """Chest protection""" + if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.ADMIN: + self.logger.vdebug("User %s attmepted to interact on a protected planet.", self.protocol.player.name) + name = self.protocol.player.org_name + if name in self.player_planets[self.protocol.player.planet]: + return True + else: + entity = entity_interact_result().parse(data.data) + if entity.interaction_type == InteractionType.OPEN_CONTAINER: + self.logger.vdebug("User %s attmepted to open container ID %s", self.protocol.player.name, entity.target_entity_id) + self.logger.vdebug("This is not permitted.") + return False diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 8268638..971d4c2 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -18,12 +18,6 @@ def activate(self): super(Warpy, self).activate() self.player_manager = self.plugins['player_manager'].player_manager - ## Warp debugging - #def on_player_warp(self, data): - # self.logger.debug("Warp packet: %s", data.data.encode('hex')) - # warp_data = player_warp().parse(data.data) - # self.logger.debug("Warp packet: %s", warp_data) - @permissions(UserLevels.MODERATOR) def warp(self, name): """Warps you to a player's ship (or player to player).\nSyntax: /warp [player] (to player)""" diff --git a/server.py b/server.py index 87b466f..d447132 100644 --- a/server.py +++ b/server.py @@ -115,7 +115,7 @@ def __init__(self): packets.Packets.ENTITY_CREATE: self.entity_create, # 45 packets.Packets.ENTITY_UPDATE: self.entity_update, # 46 packets.Packets.ENTITY_DESTROY: self.entity_destroy, # 47 - packets.Packets.HIT_REQUEST: lambda x: True, # 48 + packets.Packets.HIT_REQUEST: self.hit_request, # 48 packets.Packets.DAMAGE_REQUEST: lambda x: True, # 49 packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, # 50 packets.Packets.CALL_SCRIPTED_ENTITY: lambda x: True, # 51 @@ -152,8 +152,8 @@ def string_received(self, packet): """ if 53 >= packet.id: # DEBUG - print all packet IDs going to client - #if packet.id not in [14, 44, 46, 53]: - # logger.info("From Client: %s", packet.id) + if packet.id not in [14, 44, 45, 46, 47, 51, 53]: + logger.info("From Client: %s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) @@ -342,6 +342,10 @@ def entity_update(self, data): def entity_destroy(self, data): return True + @route + def hit_request(self, data): + return True + @route def status_effect_request(self, data): return True @@ -543,7 +547,8 @@ def string_received(self, packet): """ try: # DEBUG - print all packet IDs coming from client - # logger.info("From Server: %s", packet.id) + if packet.id not in [5, 14, 25, 45, 46, 47, 51, 53]: + logger.info("From Server: %s", packet.id) if self.server_protocol.handle_starbound_packets( packet): self.server_protocol.write(packet.original_data) From 4124334b5bace69245f89ba1c5eb2df28beb9645 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 10 Feb 2015 23:29:32 -0500 Subject: [PATCH 80/81] Commented out some of my debug lines. --- server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.py b/server.py index d447132..4113bdf 100644 --- a/server.py +++ b/server.py @@ -152,8 +152,8 @@ def string_received(self, packet): """ if 53 >= packet.id: # DEBUG - print all packet IDs going to client - if packet.id not in [14, 44, 45, 46, 47, 51, 53]: - logger.info("From Client: %s", packet.id) + # if packet.id not in [14, 44, 45, 46, 47, 51, 53]: + # logger.info("From Client: %s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) @@ -547,8 +547,8 @@ def string_received(self, packet): """ try: # DEBUG - print all packet IDs coming from client - if packet.id not in [5, 14, 25, 45, 46, 47, 51, 53]: - logger.info("From Server: %s", packet.id) + # if packet.id not in [5, 14, 25, 45, 46, 47, 51, 53]: + # logger.info("From Server: %s", packet.id) if self.server_protocol.handle_starbound_packets( packet): self.server_protocol.write(packet.original_data) From f5e3fae3bb59a42b5d614b76fee359b71ebd0e98 Mon Sep 17 00:00:00 2001 From: Kharidiron Date: Tue, 10 Feb 2015 23:33:10 -0500 Subject: [PATCH 81/81] bring config.json.default up to speed. --- config/config.json.default | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/config/config.json.default b/config/config.json.default index 2fc90b4..45f36a6 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -94,23 +94,10 @@ "bad_packets": [ "CONNECT_WIRE", "DISCONNECT_ALL_WIRES", - "SWAP_IN_CONTAINER", "DAMAGE_TILE", "DAMAGE_TILE_GROUP", "MODIFY_TILE_LIST" ], - "bad_packets_mild": [ - "CONNECT_WIRE", - "DISCONNECT_ALL_WIRES", - "DAMAGE_TILE", - "DAMAGE_TILE_GROUP", - "MODIFY_TILE_LIST" - ], - "bad_packets_ship": [ - "ENTITY_INTERACT", - "OPEN_CONTAINER", - "CLOSE_CONTAINER" - ], "blacklist": [ "bomb", "bombblockexplosion",