From 4bdf0962fe9c6f22edd0291bb993ddb36ffaf1ce Mon Sep 17 00:00:00 2001 From: liangtongchuan Date: Mon, 1 Mar 2021 22:09:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=9A=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E6=8E=A5=E6=94=B6=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95=EF=BC=9B=E6=89=93?= =?UTF-8?q?=E5=BC=80=E4=B9=8B=E5=89=8D=E5=85=B3=E9=97=AD=E7=9A=84=20protob?= =?UTF-8?q?uf=EF=BC=9B=E4=BF=AE=E6=94=B9pinus=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game-server/config/serverProtos.ts | 24 +- game-server/test/PinusWSClient.js | 729 +++++++++++++++++++++++ game-server/test/PinusWSClient.ts | 901 +++++++++++++++++++++++++++++ game-server/test/comBattle.test.ts | 9 + 4 files changed, 1651 insertions(+), 12 deletions(-) create mode 100644 game-server/test/PinusWSClient.js create mode 100644 game-server/test/PinusWSClient.ts diff --git a/game-server/config/serverProtos.ts b/game-server/config/serverProtos.ts index 62ba1533c..73669425d 100644 --- a/game-server/config/serverProtos.ts +++ b/game-server/config/serverProtos.ts @@ -5,18 +5,18 @@ module.exports = { 'required string target': 3 }, // TODO 检查 protobuf 和测试冲突的原因 - // 'onItemUpdate': { - // 'message Data': { - // "message Good": { - // 'required uInt32 id': 1, - // 'required uInt32 count': 2, - // }, - // 'repeated Good goods': 1, - // }, - // 'required string msg': 1, - // 'required uInt32 code': 2, - // 'required Data data': 3 - // }, + 'onItemUpdate': { + 'message Data': { + "message Good": { + 'required uInt32 id': 1, + 'required uInt32 count': 2, + }, + 'repeated Good goods': 1, + }, + 'required string msg': 1, + 'required uInt32 code': 2, + 'required Data data': 3 + }, 'onPlayerCeUpdate': { 'message Data': { "message Hero": { diff --git a/game-server/test/PinusWSClient.js b/game-server/test/PinusWSClient.js new file mode 100644 index 000000000..9350ecc10 --- /dev/null +++ b/game-server/test/PinusWSClient.js @@ -0,0 +1,729 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const egret = require("./ByteArray"); +const WebSocket = require("ws"); +var PinusWSClientEvent; +(function (PinusWSClientEvent) { + PinusWSClientEvent["EVENT_IO_ERROR"] = "io-error"; + PinusWSClientEvent["EVENT_CLOSE"] = "close"; + PinusWSClientEvent["EVENT_KICK"] = "onKick"; + PinusWSClientEvent["EVENT_HEART_BEAT_TIMEOUT"] = "heartbeat timeout"; +})(PinusWSClientEvent = exports.PinusWSClientEvent || (exports.PinusWSClientEvent = {})); +class PinusWSClient { + constructor() { + this.JS_WS_CLIENT_TYPE = 'js-websocket'; + this.JS_WS_CLIENT_VERSION = '0.0.5'; + this.RES_OK = 200; + this.RES_FAIL = 500; + this.RES_OLD_CLIENT = 501; + this.socket = null; + this.callbacks = {}; + this.handlers = {}; + // Map from request id to route + this.routeMap = {}; + this.heartbeatInterval = 0; + this.heartbeatTimeout = 0; + this.nextHeartbeatTimeout = 0; + this.gapThreshold = 100; + this.heartbeatId = null; + this.heartbeatTimeoutId = null; + this.handshakeCallback = null; + this.initCallback = null; + this._callbacks = {}; + this.reqId = 0; + this.socket = null; + this.callbacks = {}; + this.handlers = {}; + // Map from request id to route + this.routeMap = {}; + this._message = new Message(this.routeMap); + this._package = new Package(); + this.heartbeatInterval = 0; + this.heartbeatTimeout = 0; + this.nextHeartbeatTimeout = 0; + this.gapThreshold = 100; + this.heartbeatId = null; + this.heartbeatTimeoutId = null; + this.handshakeCallback = null; + this.handshakeBuffer = { + 'sys': { + type: this.JS_WS_CLIENT_TYPE, + version: this.JS_WS_CLIENT_VERSION + }, + 'user': {} + }; + this.initCallback = null; + this.reqId = 0; + this.handlers[Package.TYPE_HANDSHAKE] = this.handshake; + this.handlers[Package.TYPE_HEARTBEAT] = this.heartbeat; + this.handlers[Package.TYPE_DATA] = this.onData; + this.handlers[Package.TYPE_KICK] = this.onKick; + } + init(params, cb) { + console.log('init', params); + this.initCallback = cb; + let host = params.host; + let port = params.port; + // + // var url = 'ws://' + host; + // if(port) { + // url += ':' + port; + // } + this.handshakeBuffer.user = params.user; + this.handshakeCallback = params.handshakeCallback; + this.initWebSocket(host, port, cb); + } + initWebSocket(host, port, cb) { + console.log('[Pinus] connect to:', host, port); + let url = 'ws://' + host; + if (port) { + url += ':' + port; + } + let socket = new WebSocket(url); + socket.binaryType = 'arraybuffer'; + socket.onopen = (event) => { + this.onConnect(); + }; + socket.onmessage = (event) => { + this.onMessage(event); + }; + socket.onerror = (event) => { + this.onIOError(event); + }; + socket.onclose = (event) => { + this.onClose(event); + }; + this.socket = socket; + } + on(event, fn) { + (this._callbacks[event] = this._callbacks[event] || []).push(fn); + } + request(route, msg, cb) { + if (arguments.length === 2 && typeof msg === 'function') { + cb = msg; + msg = {}; + } + else { + msg = msg || {}; + } + route = route || msg.route; + if (!route) { + return; + } + this.reqId++; + if (this.reqId > 127) { + this.reqId = 1; + } + let reqId = this.reqId; + if (PinusWSClient.DEBUG) { + console.log(`REQUEST:route:${route} , reqId:${reqId}, msg:${msg}`); + } + this.sendMessage(reqId, route, msg); + this.callbacks[reqId] = cb; + this.routeMap[reqId] = route; + } + notify(route, msg) { + this.sendMessage(0, route, msg); + } + onMessage(event) { + this.processPackage(this._package.decode(new egret.ByteArray(event.data))); + } + sendMessage(reqId, route, msg) { + let byte; + byte = this._message.encode(reqId, route, msg); + byte = this._package.encode(Package.TYPE_DATA, byte); + this.send(byte); + } + onConnect() { + console.log('[Pinus] connect success'); + this.send(this._package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(this.handshakeBuffer)))); + } + onClose(e) { + console.error('[Pinus] connect close:', e); + // this.emit(Pinus.EVENT_CLOSE,e); + } + onIOError(e) { + // this.emit(Pinus.EVENT_IO_ERROR, e); + console.error('socket error: ', e); + } + onKick(event) { + // this.emit(PinusWSClient.EVENT_KICK,event); + } + onData(data) { + // probuff decode + let msg = this._message.decode(data); + if (msg.id > 0) { + msg.route = this.routeMap[msg.id]; + delete this.routeMap[msg.id]; + if (!msg.route) { + return; + } + } + // msg.body = this.deCompose(msg); + this.processMessage(msg); + } + processMessage(msg) { + if (!msg.id) { + // server push message + if (PinusWSClient.DEBUG) { + console.log(`EVENT: Route:${msg.route} Msg:${msg.body}`); + } + this.emit(msg.route, msg.body); + return; + } + if (PinusWSClient.DEBUG) { + console.log(`RESPONSE: Id:${msg.id} Msg:${msg.body}`); + } + // if have a id then find the callback function with the request + let cb = this.callbacks[msg.id]; + delete this.callbacks[msg.id]; + if (typeof cb !== 'function') { + return; + } + if (msg.body && msg.body.code === 500) { + let obj = { 'code': 500, 'desc': '服务器内部错误', 'key': 'INTERNAL_ERROR' }; + msg.body.error = obj; + } + cb(msg.body); + return; + } + heartbeat(data) { + if (!this.heartbeatInterval) { + // no heartbeat + return; + } + let obj = this._package.encode(Package.TYPE_HEARTBEAT); + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + if (this.heartbeatId) { + // already in a heartbeat interval + return; + } + let self = this; + self.heartbeatId = setTimeout(function () { + self.heartbeatId = null; + self.send(obj); + self.nextHeartbeatTimeout = Date.now() + self.heartbeatTimeout; + self.heartbeatTimeoutId = setTimeout(self.heartbeatTimeoutCb.bind(self, data), self.heartbeatTimeout); + }, self.heartbeatInterval); + } + heartbeatTimeoutCb(data) { + let gap = this.nextHeartbeatTimeout - Date.now(); + if (gap > this.gapThreshold) { + this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb.bind(this, data), gap); + } + else { + console.error('server heartbeat timeout', data); + // this.emit(PinusWSClient.EVENT_HEART_BEAT_TIMEOUT,data); + this._disconnect(); + } + } + off(event, fn) { + this.removeAllListeners(event, fn); + } + removeAllListeners(event, fn) { + // all + if (0 === arguments.length) { + this._callbacks = {}; + return; + } + // specific event + let callbacks = this._callbacks[event]; + if (!callbacks) { + return; + } + // remove all handlers + if (event && !fn) { + delete this._callbacks[event]; + return; + } + // remove specific handler + let i = this.index(callbacks, fn._off || fn); + if (~i) { + callbacks.splice(i, 1); + } + return; + } + index(arr, obj) { + if ([].indexOf) { + return arr.indexOf(obj); + } + for (let i = 0; i < arr.length; ++i) { + if (arr[i] === obj) + return i; + } + return -1; + } + disconnect() { + this._disconnect(); + } + _disconnect() { + console.warn('[Pinus] client disconnect ...'); + if (this.socket) + this.socket.close(); + this.socket = null; + if (this.heartbeatId) { + clearTimeout(this.heartbeatId); + this.heartbeatId = null; + } + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + } + processPackage(msg) { + this.handlers[msg.type].apply(this, [msg.body]); + } + handshake(resData) { + let data = JSON.parse(Protocol.strdecode(resData)); + if (data.code === this.RES_OLD_CLIENT) { + // this.emit(PinusWSClient.EVENT_IO_ERROR, 'client version not fullfill'); + return; + } + if (data.code !== this.RES_OK) { + // this.emit(PinusWSClient.EVENT_IO_ERROR, 'handshake fail'); + return; + } + this.handshakeInit(data); + let obj = this._package.encode(Package.TYPE_HANDSHAKE_ACK); + this.send(obj); + if (this.initCallback) { + this.initCallback(data); + this.initCallback = null; + } + } + handshakeInit(data) { + if (data.sys) { + Routedic.init(data.sys.dict); + Protobuf.init(data.sys.protos); + } + if (data.sys && data.sys.heartbeat) { + this.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval + this.heartbeatTimeout = this.heartbeatInterval * 2; // max heartbeat timeout + } + else { + this.heartbeatInterval = 0; + this.heartbeatTimeout = 0; + } + if (typeof this.handshakeCallback === 'function') { + this.handshakeCallback(data.user); + } + } + send(byte) { + if (this.socket) { + this.socket.send(byte.buffer); + } + } + // private deCompose(msg){ + // return JSON.parse(Protocol.strdecode(msg.body)); + // } + emit(event, ...args) { + let params = [].slice.call(arguments, 1); + let callbacks = this._callbacks[event]; + if (callbacks) { + callbacks = callbacks.slice(0); + for (let i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, params); + } + } + return this; + } +} +exports.PinusWSClient = PinusWSClient; +PinusWSClient.DEBUG = false; +class Package { + encode(type, body) { + let length = body ? body.length : 0; + let buffer = new egret.ByteArray(); + buffer.writeByte(type & 0xff); + buffer.writeByte((length >> 16) & 0xff); + buffer.writeByte((length >> 8) & 0xff); + buffer.writeByte(length & 0xff); + if (body) + buffer.writeBytes(body, 0, body.length); + return buffer; + } + decode(buffer) { + let type = buffer.readUnsignedByte(); + let len = (buffer.readUnsignedByte() << 16 | buffer.readUnsignedByte() << 8 | buffer.readUnsignedByte()) >>> 0; + let body; + if (buffer.bytesAvailable >= len) { + body = new egret.ByteArray(); + if (len) + buffer.readBytes(body, 0, len); + } + else { + console.log('[Package] no enough length for current type:', type); + } + return { type: type, body: body, length: len }; + } +} +Package.TYPE_HANDSHAKE = 1; +Package.TYPE_HANDSHAKE_ACK = 2; +Package.TYPE_HEARTBEAT = 3; +Package.TYPE_DATA = 4; +Package.TYPE_KICK = 5; +class Message { + constructor(routeMap) { + this.routeMap = routeMap; + } + encode(id, route, msg) { + let buffer = new egret.ByteArray(); + let type = id ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; + let byte = Protobuf.encode(route, msg) || Protocol.strencode(JSON.stringify(msg)); + let rot = Routedic.getID(route) || route; + buffer.writeByte((type << 1) | ((typeof (rot) === 'string') ? 0 : 1)); + if (id) { + // 7.x + do { + let tmp = id % 128; + let next = Math.floor(id / 128); + if (next !== 0) { + tmp = tmp + 128; + } + buffer.writeByte(tmp); + id = next; + } while (id !== 0); + // 5.x + // var len:Array = []; + // len.push(id & 0x7f); + // id >>= 7; + // while(id > 0) + // { + // len.push(id & 0x7f | 0x80); + // id >>= 7; + // } + // + // for (var i:int = len.length - 1; i >= 0; i--) + // { + // buffer.writeByte(len[i]); + // } + } + if (rot) { + if (typeof rot === 'string') { + buffer.writeByte(rot.length & 0xff); + buffer.writeUTFBytes(rot); + } + else { + buffer.writeByte((rot >> 8) & 0xff); + buffer.writeByte(rot & 0xff); + } + } + if (byte) { + buffer.writeBytes(byte); + } + return buffer; + } + decode(buffer) { + // parse flag + let flag = buffer.readUnsignedByte(); + let compressRoute = flag & Message.MSG_COMPRESS_ROUTE_MASK; + let type = (flag >> 1) & Message.MSG_TYPE_MASK; + let route; + // parse id + let id = 0; + if (type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE) { + // 7.x + let i = 0; + let m; + do { + m = buffer.readUnsignedByte(); + id = id + ((m & 0x7f) * Math.pow(2, (7 * i))); + i++; + } while (m >= 128); + // 5.x + // var byte:int = buffer.readUnsignedByte(); + // id = byte & 0x7f; + // while(byte & 0x80) + // { + // id <<= 7; + // byte = buffer.readUnsignedByte(); + // id |= byte & 0x7f; + // } + } + // parse route + if (type === Message.TYPE_REQUEST || type === Message.TYPE_NOTIFY || type === Message.TYPE_PUSH) { + if (compressRoute) { + route = buffer.readUnsignedShort(); + } + else { + let routeLen = buffer.readUnsignedByte(); + route = routeLen ? buffer.readUTFBytes(routeLen) : ''; + } + } + else if (type === Message.TYPE_RESPONSE) { + route = this.routeMap[id]; + } + if (!id && !(typeof (route) === 'string')) { + route = Routedic.getName(route); + } + let body = Protobuf.decode(route, buffer) || JSON.parse(Protocol.strdecode(buffer)); + return { id: id, type: type, route: route, body: body }; + } +} +Message.MSG_FLAG_BYTES = 1; +Message.MSG_ROUTE_CODE_BYTES = 2; +Message.MSG_ID_MAX_BYTES = 5; +Message.MSG_ROUTE_LEN_BYTES = 1; +Message.MSG_ROUTE_CODE_MAX = 0xffff; +Message.MSG_COMPRESS_ROUTE_MASK = 0x1; +Message.MSG_TYPE_MASK = 0x7; +Message.TYPE_REQUEST = 0; +Message.TYPE_NOTIFY = 1; +Message.TYPE_RESPONSE = 2; +Message.TYPE_PUSH = 3; +class Protocol { + static strencode(str) { + let buffer = new egret.ByteArray(); + buffer.length = str.length; + buffer.writeUTFBytes(str); + return buffer; + } + static strdecode(byte) { + return byte.readUTFBytes(byte.bytesAvailable); + } +} +class Protobuf { + static init(protos) { + this._clients = protos && protos.client || {}; + this._servers = protos && protos.server || {}; + } + static encode(route, msg) { + let protos = this._clients[route]; + if (!protos) + return null; + return this.encodeProtos(protos, msg); + } + static decode(route, buffer) { + let protos = this._servers[route]; + if (!protos) + return null; + return this.decodeProtos(protos, buffer); + } + static encodeProtos(protos, msg) { + let buffer = new egret.ByteArray(); + for (let name in msg) { + if (protos[name]) { + let proto = protos[name]; + switch (proto.option) { + case 'optional': + case 'required': + buffer.writeBytes(this.encodeTag(proto.type, proto.tag)); + this.encodeProp(msg[name], proto.type, protos, buffer); + break; + case 'repeated': + if (!!msg[name] && msg[name].length > 0) { + this.encodeArray(msg[name], proto, protos, buffer); + } + break; + } + } + } + return buffer; + } + static decodeProtos(protos, buffer) { + let msg = {}; + while (buffer.bytesAvailable) { + let head = this.getHead(buffer); + let name = protos.__tags[head.tag]; + switch (protos[name].option) { + case 'optional': + case 'required': + msg[name] = this.decodeProp(protos[name].type, protos, buffer); + break; + case 'repeated': + if (!msg[name]) { + msg[name] = []; + } + this.decodeArray(msg[name], protos[name].type, protos, buffer); + break; + } + } + return msg; + } + static encodeTag(type, tag) { + let value = this.TYPES[type] !== undefined ? this.TYPES[type] : 2; + return this.encodeUInt32((tag << 3) | value); + } + static getHead(buffer) { + let tag = this.decodeUInt32(buffer); + return { type: tag & 0x7, tag: tag >> 3 }; + } + static encodeProp(value, type, protos, buffer) { + switch (type) { + case 'uInt32': + buffer.writeBytes(this.encodeUInt32(value)); + break; + case 'int32': + case 'sInt32': + buffer.writeBytes(this.encodeSInt32(value)); + break; + case 'float': + // Float32Array + let floats = new egret.ByteArray(); + floats.endian = egret.Endian.LITTLE_ENDIAN; + floats.writeFloat(value); + buffer.writeBytes(floats); + break; + case 'double': + let doubles = new egret.ByteArray(); + doubles.endian = egret.Endian.LITTLE_ENDIAN; + doubles.writeDouble(value); + buffer.writeBytes(doubles); + break; + case 'string': + buffer.writeBytes(this.encodeUInt32(value.length)); + buffer.writeUTFBytes(value); + break; + default: + let proto = protos.__messages[type] || this._clients['message ' + type]; + if (!!proto) { + let buf = this.encodeProtos(proto, value); + buffer.writeBytes(this.encodeUInt32(buf.length)); + buffer.writeBytes(buf); + } + break; + } + } + static decodeProp(type, protos, buffer) { + switch (type) { + case 'uInt32': + return this.decodeUInt32(buffer); + case 'int32': + case 'sInt32': + return this.decodeSInt32(buffer); + case 'float': + let floats = new egret.ByteArray(); + buffer.readBytes(floats, 0, 4); + floats.endian = egret.Endian.LITTLE_ENDIAN; + let float = buffer.readFloat(); + return floats.readFloat(); + case 'double': + let doubles = new egret.ByteArray(); + buffer.readBytes(doubles, 0, 8); + doubles.endian = egret.Endian.LITTLE_ENDIAN; + return doubles.readDouble(); + case 'string': + let length = this.decodeUInt32(buffer); + return buffer.readUTFBytes(length); + default: + let proto = protos && (protos.__messages[type] || this._servers['message ' + type]); + if (proto) { + let len = this.decodeUInt32(buffer); + let buf; + if (len) { + buf = new egret.ByteArray(); + buffer.readBytes(buf, 0, len); + } + return len ? Protobuf.decodeProtos(proto, buf) : false; + } + break; + } + } + static isSimpleType(type) { + return (type === 'uInt32' || + type === 'sInt32' || + type === 'int32' || + type === 'uInt64' || + type === 'sInt64' || + type === 'float' || + type === 'double'); + } + static encodeArray(array, proto, protos, buffer) { + let isSimpleType = this.isSimpleType; + if (isSimpleType(proto.type)) { + buffer.writeBytes(this.encodeTag(proto.type, proto.tag)); + buffer.writeBytes(this.encodeUInt32(array.length)); + let encodeProp = this.encodeProp; + for (let i = 0; i < array.length; i++) { + encodeProp(array[i], proto.type, protos, buffer); + } + } + else { + let encodeTag = this.encodeTag; + for (let j = 0; j < array.length; j++) { + buffer.writeBytes(encodeTag(proto.type, proto.tag)); + this.encodeProp(array[j], proto.type, protos, buffer); + } + } + } + static decodeArray(array, type, protos, buffer) { + let isSimpleType = this.isSimpleType; + let decodeProp = this.decodeProp; + if (isSimpleType(type)) { + let length = this.decodeUInt32(buffer); + for (let i = 0; i < length; i++) { + array.push(this.decodeProp(type, protos, buffer)); + } + } + else { + array.push(this.decodeProp(type, protos, buffer)); + } + } + static encodeUInt32(n) { + let result = new egret.ByteArray(); + do { + let tmp = n % 128; + let next = Math.floor(n / 128); + if (next !== 0) { + tmp = tmp + 128; + } + result.writeByte(tmp); + n = next; + } while (n !== 0); + return result; + } + static decodeUInt32(buffer) { + let n = 0; + for (let i = 0; i < buffer.length; i++) { + let m = buffer.readUnsignedByte(); + n = n + ((m & 0x7f) * Math.pow(2, (7 * i))); + if (m < 128) { + return n; + } + } + return n; + } + static encodeSInt32(n) { + n = n < 0 ? (Math.abs(n) * 2 - 1) : n * 2; + return this.encodeUInt32(n); + } + static decodeSInt32(buffer) { + let n = this.decodeUInt32(buffer); + let flag = ((n % 2) === 1) ? -1 : 1; + n = ((n % 2 + n) / 2) * flag; + return n; + } +} +Protobuf.TYPES = { + uInt32: 0, + sInt32: 0, + int32: 0, + double: 1, + string: 2, + message: 2, + float: 5 +}; +Protobuf._clients = {}; +Protobuf._servers = {}; +class Routedic { + static init(dict) { + this._names = dict || {}; + let _names = this._names; + let _ids = this._ids; + for (let name in _names) { + _ids[_names[name]] = name; + } + } + static getID(name) { + return this._names[name]; + } + static getName(id) { + return this._ids[id]; + } +} +Routedic._ids = {}; +Routedic._names = {}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGludXNXU0NsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9QaW51c1dTQ2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EscUNBQXFDO0FBQ3JDLGdDQUFnQztBQUVoQyxJQUFZLGtCQUtYO0FBTEQsV0FBWSxrQkFBa0I7SUFDekIsaURBQTJCLENBQUE7SUFDM0IsMkNBQXFCLENBQUE7SUFDckIsMkNBQXFCLENBQUE7SUFDckIsb0VBQThDLENBQUE7QUFDbkQsQ0FBQyxFQUxXLGtCQUFrQixHQUFsQiwwQkFBa0IsS0FBbEIsMEJBQWtCLFFBSzdCO0FBRUQsTUFBYSxhQUFhO0lBcUN0QjtRQWpDUSxzQkFBaUIsR0FBRyxjQUFjLENBQUM7UUFDbkMseUJBQW9CLEdBQUcsT0FBTyxDQUFDO1FBRS9CLFdBQU0sR0FBVyxHQUFHLENBQUM7UUFDckIsYUFBUSxHQUFXLEdBQUcsQ0FBQztRQUN2QixtQkFBYyxHQUFXLEdBQUcsQ0FBQztRQUc3QixXQUFNLEdBQWMsSUFBSSxDQUFDO1FBQ3pCLGNBQVMsR0FBUSxFQUFFLENBQUM7UUFDcEIsYUFBUSxHQUFRLEVBQUUsQ0FBQztRQUMzQiwrQkFBK0I7UUFDdkIsYUFBUSxHQUFRLEVBQUUsQ0FBQztRQUVuQixzQkFBaUIsR0FBVyxDQUFDLENBQUM7UUFDOUIscUJBQWdCLEdBQVcsQ0FBQyxDQUFDO1FBQzdCLHlCQUFvQixHQUFXLENBQUMsQ0FBQztRQUNqQyxpQkFBWSxHQUFXLEdBQUcsQ0FBQztRQUMzQixnQkFBVyxHQUFRLElBQUksQ0FBQztRQUN4Qix1QkFBa0IsR0FBUSxJQUFJLENBQUM7UUFFL0Isc0JBQWlCLEdBQVEsSUFBSSxDQUFDO1FBRTlCLGlCQUFZLEdBQWEsSUFBSSxDQUFDO1FBRTlCLGVBQVUsR0FBUSxFQUFFLENBQUM7UUFFckIsVUFBSyxHQUFXLENBQUMsQ0FBQztRQVF0QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNuQixJQUFJLENBQUMsU0FBUyxHQUFHLEVBQUUsQ0FBQztRQUNwQixJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsQ0FBQztRQUNuQiwrQkFBK0I7UUFDL0IsSUFBSSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUM7UUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLE9BQU8sRUFBRSxDQUFDO1FBRzlCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUFDLENBQUM7UUFDM0IsSUFBSSxDQUFDLGdCQUFnQixHQUFHLENBQUMsQ0FBQztRQUMxQixJQUFJLENBQUMsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO1FBQzlCLElBQUksQ0FBQyxZQUFZLEdBQUcsR0FBRyxDQUFDO1FBQ3hCLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxJQUFJLENBQUM7UUFFL0IsSUFBSSxDQUFDLGlCQUFpQixHQUFHLElBQUksQ0FBQztRQUU5QixJQUFJLENBQUMsZUFBZSxHQUFHO1lBQ25CLEtBQUssRUFBRTtnQkFDSCxJQUFJLEVBQUUsSUFBSSxDQUFDLGlCQUFpQjtnQkFDNUIsT0FBTyxFQUFFLElBQUksQ0FBQyxvQkFBb0I7YUFDckM7WUFDRCxNQUFNLEVBQUUsRUFDUDtTQUNKLENBQUM7UUFFRixJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQztRQUV6QixJQUFJLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQztRQUVmLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDdkQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUN2RCxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQy9DLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDbkQsQ0FBQztJQUdNLElBQUksQ0FBQyxNQUFXLEVBQUUsRUFBWTtRQUNqQyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUM1QixJQUFJLENBQUMsWUFBWSxHQUFHLEVBQUUsQ0FBQztRQUN2QixJQUFJLElBQUksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO1FBQ3ZCLElBQUksSUFBSSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUM7UUFDdkIsRUFBRTtRQUNGLDRCQUE0QjtRQUM1QixhQUFhO1FBQ2IseUJBQXlCO1FBQ3pCLElBQUk7UUFFSixJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDO1FBQ3hDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxNQUFNLENBQUMsaUJBQWlCLENBQUM7UUFDbEQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7SUFDTyxhQUFhLENBQUMsSUFBWSxFQUFFLElBQVksRUFBRSxFQUFZO1FBQzFELE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBRS9DLElBQUksR0FBRyxHQUFHLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFDekIsSUFBSSxJQUFJLEVBQUU7WUFDTixHQUFHLElBQUksR0FBRyxHQUFHLElBQUksQ0FBQztTQUNyQjtRQUNELElBQUksTUFBTSxHQUFHLElBQUksU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2hDLE1BQU0sQ0FBQyxVQUFVLEdBQUcsYUFBYSxDQUFDO1FBQ2xDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxLQUFLLEVBQUUsRUFBRTtZQUN0QixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7UUFDckIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxDQUFDLFNBQVMsR0FBRyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQ3pCLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDMUIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxDQUFDLE9BQU8sR0FBRyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQ3ZCLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDMUIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxDQUFDLE9BQU8sR0FBRyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQ3ZCLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDeEIsQ0FBQyxDQUFDO1FBQ0YsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDekIsQ0FBQztJQU9NLEVBQUUsQ0FBQyxLQUFhLEVBQUUsRUFBc0I7UUFDM0MsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3JFLENBQUM7SUFDTSxPQUFPLENBQUMsS0FBYSxFQUFFLEdBQVEsRUFBRSxFQUFZO1FBQ2hELElBQUksU0FBUyxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksT0FBTyxHQUFHLEtBQUssVUFBVSxFQUFFO1lBQ3JELEVBQUUsR0FBRyxHQUFHLENBQUM7WUFDVCxHQUFHLEdBQUcsRUFBRSxDQUFDO1NBQ1o7YUFBTTtZQUNILEdBQUcsR0FBRyxHQUFHLElBQUksRUFBRSxDQUFDO1NBQ25CO1FBQ0QsS0FBSyxHQUFHLEtBQUssSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDO1FBQzNCLElBQUksQ0FBQyxLQUFLLEVBQUU7WUFDUixPQUFPO1NBQ1Y7UUFFRCxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDYixJQUFJLElBQUksQ0FBQyxLQUFLLEdBQUcsR0FBRyxFQUFFO1lBQ2xCLElBQUksQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDO1NBQ2xCO1FBQ0QsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUd2QixJQUFJLGFBQWEsQ0FBQyxLQUFLLEVBQUU7WUFDckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsS0FBSyxZQUFZLEtBQUssU0FBUyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1NBQ3RFO1FBRUQsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBRXBDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQzNCLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2pDLENBQUM7SUFFTSxNQUFNLENBQUMsS0FBYSxFQUFFLEdBQVE7UUFDakMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQ3BDLENBQUM7SUFFTyxTQUFTLENBQUMsS0FBMEY7UUFDeEcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxTQUFTLENBQWMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUU1RixDQUFDO0lBQ08sV0FBVyxDQUFDLEtBQWEsRUFBRSxLQUFhLEVBQUUsR0FBUTtRQUN0RCxJQUFJLElBQXFCLENBQUM7UUFFMUIsSUFBSSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDL0MsSUFBSSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFFckQsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUVwQixDQUFDO0lBRU8sU0FBUztRQUNiLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLENBQUMsQ0FBQztRQUN2QyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEVBQUUsUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0SCxDQUFDO0lBRU8sT0FBTyxDQUFDLENBQU07UUFDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyx3QkFBd0IsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUMzQyxrQ0FBa0M7SUFDdEMsQ0FBQztJQUVPLFNBQVMsQ0FBQyxDQUFNO1FBQ3BCLHNDQUFzQztRQUN0QyxPQUFPLENBQUMsS0FBSyxDQUFDLGdCQUFnQixFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7SUFFTyxNQUFNLENBQUMsS0FBYTtRQUN4Qiw2Q0FBNkM7SUFDakQsQ0FBQztJQUNPLE1BQU0sQ0FBQyxJQUFTO1FBQ3BCLGlCQUFpQjtRQUNqQixJQUFJLEdBQUcsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVyQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFO1lBQ1osR0FBRyxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNsQyxPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQzdCLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFO2dCQUNaLE9BQU87YUFDVjtTQUNKO1FBRUQsa0NBQWtDO1FBRWxDLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFN0IsQ0FBQztJQUVPLGNBQWMsQ0FBQyxHQUFRO1FBQzNCLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFO1lBQ1Qsc0JBQXNCO1lBRXRCLElBQUksYUFBYSxDQUFDLEtBQUssRUFBRTtnQkFDckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsR0FBRyxDQUFDLEtBQUssUUFBUSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQzthQUM1RDtZQUVELGtDQUFrQztZQUNsQyxPQUFPO1NBQ1Y7UUFDRCxJQUFJLGFBQWEsQ0FBQyxLQUFLLEVBQUU7WUFDckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsR0FBRyxDQUFDLEVBQUUsUUFBUSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztTQUN6RDtRQUVELGdFQUFnRTtRQUNoRSxJQUFJLEVBQUUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUVoQyxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQzlCLElBQUksT0FBTyxFQUFFLEtBQUssVUFBVSxFQUFFO1lBQzFCLE9BQU87U0FDVjtRQUNELElBQUksR0FBRyxDQUFDLElBQUksSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxHQUFHLEVBQUU7WUFDbkMsSUFBSSxHQUFHLEdBQVEsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLGdCQUFnQixFQUFFLENBQUM7WUFDM0UsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLEdBQUcsR0FBRyxDQUFDO1NBQ3hCO1FBQ0QsRUFBRSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNiLE9BQU87SUFDWCxDQUFDO0lBRU8sU0FBUyxDQUFDLElBQVM7UUFFdkIsSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsRUFBRTtZQUN6QixlQUFlO1lBQ2YsT0FBTztTQUNWO1FBRUQsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3ZELElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQ3pCLFlBQVksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FBQztZQUN0QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO1NBQ2xDO1FBRUQsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFO1lBQ2xCLGtDQUFrQztZQUNsQyxPQUFPO1NBQ1Y7UUFFRCxJQUFJLElBQUksR0FBRyxJQUFJLENBQUM7UUFDaEIsSUFBSSxDQUFDLFdBQVcsR0FBRyxVQUFVLENBQUM7WUFDMUIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUM7WUFDeEIsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUVmLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDO1lBQy9ELElBQUksQ0FBQyxrQkFBa0IsR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDMUcsQ0FBQyxFQUFFLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0lBQy9CLENBQUM7SUFDTyxrQkFBa0IsQ0FBQyxJQUFTO1FBQ2hDLElBQUksR0FBRyxHQUFHLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDakQsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLFlBQVksRUFBRTtZQUN6QixJQUFJLENBQUMsa0JBQWtCLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1NBQ3ZGO2FBQU07WUFDSCxPQUFPLENBQUMsS0FBSyxDQUFDLDBCQUEwQixFQUFFLElBQUksQ0FBQyxDQUFDO1lBQ2hELDBEQUEwRDtZQUMxRCxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7U0FDdEI7SUFDTCxDQUFDO0lBQ00sR0FBRyxDQUFDLEtBQWMsRUFBRSxFQUFhO1FBQ3BDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDdkMsQ0FBQztJQUNNLGtCQUFrQixDQUFDLEtBQWMsRUFBRSxFQUFhO1FBQ25ELE1BQU07UUFDTixJQUFJLENBQUMsS0FBSyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3hCLElBQUksQ0FBQyxVQUFVLEdBQUcsRUFBRSxDQUFDO1lBQ3JCLE9BQU87U0FDVjtRQUVELGlCQUFpQjtRQUNqQixJQUFJLFNBQVMsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDWixPQUFPO1NBQ1Y7UUFFRCxzQkFBc0I7UUFDdEIsSUFBSSxLQUFLLElBQUksQ0FBQyxFQUFFLEVBQUU7WUFDZCxPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDOUIsT0FBTztTQUNWO1FBRUQsMEJBQTBCO1FBQzFCLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFHLEVBQVUsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDLENBQUM7UUFDdEQsSUFBSSxDQUFDLENBQUMsRUFBRTtZQUNKLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1NBQzFCO1FBQ0QsT0FBTztJQUNYLENBQUM7SUFDTyxLQUFLLENBQUMsR0FBUSxFQUFFLEdBQVE7UUFDNUIsSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFO1lBQ1osT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQzNCO1FBRUQsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLEVBQUU7WUFDakMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRztnQkFDZCxPQUFPLENBQUMsQ0FBQztTQUNoQjtRQUNELE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDZCxDQUFDO0lBQ00sVUFBVTtRQUNiLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBQ08sV0FBVztRQUNmLE9BQU8sQ0FBQyxJQUFJLENBQUMsK0JBQStCLENBQUMsQ0FBQztRQUU5QyxJQUFJLElBQUksQ0FBQyxNQUFNO1lBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNyQyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztRQUNuQixJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUU7WUFDbEIsWUFBWSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUMvQixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztTQUMzQjtRQUVELElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQ3pCLFlBQVksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FBQztZQUN0QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO1NBQ2xDO0lBRUwsQ0FBQztJQUNPLGNBQWMsQ0FBQyxHQUFRO1FBQzNCLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUNwRCxDQUFDO0lBQ08sU0FBUyxDQUFDLE9BQVk7UUFFMUIsSUFBSSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDbkQsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxjQUFjLEVBQUU7WUFDbkMsMEVBQTBFO1lBQzFFLE9BQU87U0FDVjtRQUVELElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsTUFBTSxFQUFFO1lBQzNCLDZEQUE2RDtZQUM3RCxPQUFPO1NBQ1Y7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRXpCLElBQUksR0FBRyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO1FBQzNELElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDZixJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUU7WUFDbkIsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN4QixJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQztTQUM1QjtJQUNMLENBQUM7SUFDTyxhQUFhLENBQUMsSUFBUztRQUUzQixJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUU7WUFDVixRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDN0IsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1NBQ2xDO1FBQ0QsSUFBSSxJQUFJLENBQUMsR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFO1lBQ2hDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsQ0FBRyxxQkFBcUI7WUFDM0UsSUFBSSxDQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUFDLENBQUMsQ0FBUSx3QkFBd0I7U0FDdEY7YUFBTTtZQUNILElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUFDLENBQUM7WUFDM0IsSUFBSSxDQUFDLGdCQUFnQixHQUFHLENBQUMsQ0FBQztTQUM3QjtRQUVELElBQUksT0FBTyxJQUFJLENBQUMsaUJBQWlCLEtBQUssVUFBVSxFQUFFO1lBQzlDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDckM7SUFDTCxDQUFDO0lBQ08sSUFBSSxDQUFDLElBQXFCO1FBQzlCLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRTtZQUNiLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztTQUNqQztJQUNMLENBQUM7SUFDRCwyQkFBMkI7SUFDM0Isc0RBQXNEO0lBQ3RELElBQUk7SUFDSSxJQUFJLENBQUMsS0FBYSxFQUFFLEdBQUcsSUFBVztRQUN0QyxJQUFJLE1BQU0sR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDekMsSUFBSSxTQUFTLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUV2QyxJQUFJLFNBQVMsRUFBRTtZQUNYLFNBQVMsR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQy9CLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLEdBQUcsR0FBRyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxHQUFHLEVBQUUsRUFBRSxDQUFDLEVBQUU7Z0JBQ2xELFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO2FBQ3BDO1NBQ0o7UUFFRCxPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDOztBQTVZTCxzQ0ErWUM7QUE3WVUsbUJBQUssR0FBWSxLQUFLLENBQUM7QUErWWxDLE1BQU0sT0FBTztJQU9GLE1BQU0sQ0FBQyxJQUFZLEVBQUUsSUFBc0I7UUFDOUMsSUFBSSxNQUFNLEdBQVcsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFNUMsSUFBSSxNQUFNLEdBQW9CLElBQUksS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ3BELE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQzlCLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUM7UUFDeEMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQztRQUN2QyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQztRQUVoQyxJQUFJLElBQUk7WUFBRSxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRWxELE9BQU8sTUFBTSxDQUFDO0lBQ2xCLENBQUM7SUFDTSxNQUFNLENBQUMsTUFBdUI7UUFFakMsSUFBSSxJQUFJLEdBQVcsTUFBTSxDQUFDLGdCQUFnQixFQUFFLENBQUM7UUFDN0MsSUFBSSxHQUFHLEdBQVcsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLEVBQUUsSUFBSSxFQUFFLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixFQUFFLElBQUksQ0FBQyxHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRXZILElBQUksSUFBcUIsQ0FBQztRQUUxQixJQUFJLE1BQU0sQ0FBQyxjQUFjLElBQUksR0FBRyxFQUFFO1lBQzlCLElBQUksR0FBRyxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUM3QixJQUFJLEdBQUc7Z0JBQUUsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1NBQzNDO2FBQ0k7WUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLDhDQUE4QyxFQUFFLElBQUksQ0FBQyxDQUFDO1NBQ3JFO1FBRUQsT0FBTyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUM7SUFDbkQsQ0FBQzs7QUFuQ00sc0JBQWMsR0FBVyxDQUFDLENBQUM7QUFDM0IsMEJBQWtCLEdBQVcsQ0FBQyxDQUFDO0FBQy9CLHNCQUFjLEdBQVcsQ0FBQyxDQUFDO0FBQzNCLGlCQUFTLEdBQVcsQ0FBQyxDQUFDO0FBQ3RCLGlCQUFTLEdBQVcsQ0FBQyxDQUFDO0FBa0NqQyxNQUFNLE9BQU87SUFpQlQsWUFBb0IsUUFBYTtRQUFiLGFBQVEsR0FBUixRQUFRLENBQUs7SUFFakMsQ0FBQztJQUVNLE1BQU0sQ0FBQyxFQUFVLEVBQUUsS0FBYSxFQUFFLEdBQVE7UUFDN0MsSUFBSSxNQUFNLEdBQW9CLElBQUksS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBRXBELElBQUksSUFBSSxHQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztRQUVuRSxJQUFJLElBQUksR0FBb0IsUUFBUSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLElBQUksUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFFbkcsSUFBSSxHQUFHLEdBQVEsUUFBUSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLENBQUM7UUFFOUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFdEUsSUFBSSxFQUFFLEVBQUU7WUFDSixNQUFNO1lBQ04sR0FBRztnQkFDQyxJQUFJLEdBQUcsR0FBVyxFQUFFLEdBQUcsR0FBRyxDQUFDO2dCQUMzQixJQUFJLElBQUksR0FBVyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxHQUFHLENBQUMsQ0FBQztnQkFFeEMsSUFBSSxJQUFJLEtBQUssQ0FBQyxFQUFFO29CQUNaLEdBQUcsR0FBRyxHQUFHLEdBQUcsR0FBRyxDQUFDO2lCQUNuQjtnQkFFRCxNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUV0QixFQUFFLEdBQUcsSUFBSSxDQUFDO2FBQ2IsUUFBUSxFQUFFLEtBQUssQ0FBQyxFQUFFO1lBRW5CLE1BQU07WUFDTixxQ0FBcUM7WUFDckMsc0NBQXNDO1lBQ3RDLDJCQUEyQjtZQUMzQiwrQkFBK0I7WUFDL0IsbUJBQW1CO1lBQ25CLGlEQUFpRDtZQUNqRCwrQkFBK0I7WUFDL0IsbUJBQW1CO1lBQ25CLEVBQUU7WUFDRiwrREFBK0Q7WUFDL0QsbUJBQW1CO1lBQ25CLCtDQUErQztZQUMvQyxtQkFBbUI7U0FDdEI7UUFFRCxJQUFJLEdBQUcsRUFBRTtZQUNMLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxFQUFFO2dCQUN6QixNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQUM7Z0JBQ3BDLE1BQU0sQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUM7YUFDN0I7aUJBQ0k7Z0JBQ0QsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQztnQkFDcEMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUM7YUFDaEM7U0FDSjtRQUVELElBQUksSUFBSSxFQUFFO1lBQ04sTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUMzQjtRQUVELE9BQU8sTUFBTSxDQUFDO0lBQ2xCLENBQUM7SUFFTSxNQUFNLENBQUMsTUFBdUI7UUFDakMsYUFBYTtRQUNiLElBQUksSUFBSSxHQUFXLE1BQU0sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQzdDLElBQUksYUFBYSxHQUFXLElBQUksR0FBRyxPQUFPLENBQUMsdUJBQXVCLENBQUM7UUFDbkUsSUFBSSxJQUFJLEdBQVcsQ0FBQyxJQUFJLElBQUksQ0FBQyxDQUFDLEdBQUcsT0FBTyxDQUFDLGFBQWEsQ0FBQztRQUN2RCxJQUFJLEtBQVUsQ0FBQztRQUVmLFdBQVc7UUFDWCxJQUFJLEVBQUUsR0FBVyxDQUFDLENBQUM7UUFDbkIsSUFBSSxJQUFJLEtBQUssT0FBTyxDQUFDLFlBQVksSUFBSSxJQUFJLEtBQUssT0FBTyxDQUFDLGFBQWEsRUFBRTtZQUNqRSxNQUFNO1lBQ04sSUFBSSxDQUFDLEdBQVcsQ0FBQyxDQUFDO1lBQ2xCLElBQUksQ0FBUyxDQUFDO1lBQ2QsR0FBRztnQkFDQyxDQUFDLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixFQUFFLENBQUM7Z0JBQzlCLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQzlDLENBQUMsRUFBRSxDQUFDO2FBQ1AsUUFBUSxDQUFDLElBQUksR0FBRyxFQUFFO1lBRW5CLE1BQU07WUFDTiwyREFBMkQ7WUFDM0QsbUNBQW1DO1lBQ25DLG9DQUFvQztZQUNwQyxtQkFBbUI7WUFDbkIsK0JBQStCO1lBQy9CLHVEQUF1RDtZQUN2RCx3Q0FBd0M7WUFDeEMsbUJBQW1CO1NBQ3RCO1FBRUQsY0FBYztRQUNkLElBQUksSUFBSSxLQUFLLE9BQU8sQ0FBQyxZQUFZLElBQUksSUFBSSxLQUFLLE9BQU8sQ0FBQyxXQUFXLElBQUksSUFBSSxLQUFLLE9BQU8sQ0FBQyxTQUFTLEVBQUU7WUFFN0YsSUFBSSxhQUFhLEVBQUU7Z0JBQ2YsS0FBSyxHQUFHLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO2FBQ3RDO2lCQUNJO2dCQUNELElBQUksUUFBUSxHQUFXLE1BQU0sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO2dCQUNqRCxLQUFLLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7YUFDekQ7U0FDSjthQUNJLElBQUksSUFBSSxLQUFLLE9BQU8sQ0FBQyxhQUFhLEVBQUU7WUFDckMsS0FBSyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDN0I7UUFFRCxJQUFJLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEtBQUssUUFBUSxDQUFDLEVBQUU7WUFDdkMsS0FBSyxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7U0FDbkM7UUFFRCxJQUFJLElBQUksR0FBUSxRQUFRLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUV6RixPQUFPLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDO0lBQzVELENBQUM7O0FBbklhLHNCQUFjLEdBQVcsQ0FBQyxDQUFDO0FBQzNCLDRCQUFvQixHQUFXLENBQUMsQ0FBQztBQUNqQyx3QkFBZ0IsR0FBVyxDQUFDLENBQUM7QUFDN0IsMkJBQW1CLEdBQVcsQ0FBQyxDQUFDO0FBRWhDLDBCQUFrQixHQUFXLE1BQU0sQ0FBQztBQUVwQywrQkFBdUIsR0FBVyxHQUFHLENBQUM7QUFDdEMscUJBQWEsR0FBVyxHQUFHLENBQUM7QUFFbkMsb0JBQVksR0FBVyxDQUFDLENBQUM7QUFDekIsbUJBQVcsR0FBVyxDQUFDLENBQUM7QUFDeEIscUJBQWEsR0FBVyxDQUFDLENBQUM7QUFDMUIsaUJBQVMsR0FBVyxDQUFDLENBQUM7QUF5SGpDLE1BQU0sUUFBUTtJQUVILE1BQU0sQ0FBQyxTQUFTLENBQUMsR0FBVztRQUMvQixJQUFJLE1BQU0sR0FBb0IsSUFBSSxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUM7UUFDcEQsTUFBTSxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDO1FBQzNCLE1BQU0sQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUIsT0FBTyxNQUFNLENBQUM7SUFDbEIsQ0FBQztJQUVNLE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBcUI7UUFDekMsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUNsRCxDQUFDO0NBQ0o7QUFDRCxNQUFNLFFBQVE7SUFhVixNQUFNLENBQUMsSUFBSSxDQUFDLE1BQVc7UUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxNQUFNLElBQUksTUFBTSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7UUFDOUMsSUFBSSxDQUFDLFFBQVEsR0FBRyxNQUFNLElBQUksTUFBTSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUM7SUFDbEQsQ0FBQztJQUVELE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBYSxFQUFFLEdBQVE7UUFFakMsSUFBSSxNQUFNLEdBQVEsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUV2QyxJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBRXpCLE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUVELE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBYSxFQUFFLE1BQXVCO1FBRWhELElBQUksTUFBTSxHQUFRLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFdkMsSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPLElBQUksQ0FBQztRQUV6QixPQUFPLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFDTyxNQUFNLENBQUMsWUFBWSxDQUFDLE1BQVcsRUFBRSxHQUFRO1FBQzdDLElBQUksTUFBTSxHQUFvQixJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUVwRCxLQUFLLElBQUksSUFBSSxJQUFJLEdBQUcsRUFBRTtZQUNsQixJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRTtnQkFDZCxJQUFJLEtBQUssR0FBUSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBRTlCLFFBQVEsS0FBSyxDQUFDLE1BQU0sRUFBRTtvQkFDbEIsS0FBSyxVQUFVLENBQUM7b0JBQ2hCLEtBQUssVUFBVTt3QkFDWCxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQzt3QkFDekQsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUM7d0JBQ3ZELE1BQU07b0JBQ1YsS0FBSyxVQUFVO3dCQUNYLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTs0QkFDckMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQzt5QkFDdEQ7d0JBQ0QsTUFBTTtpQkFDYjthQUNKO1NBQ0o7UUFFRCxPQUFPLE1BQU0sQ0FBQztJQUNsQixDQUFDO0lBQ0QsTUFBTSxDQUFDLFlBQVksQ0FBQyxNQUFXLEVBQUUsTUFBdUI7UUFDcEQsSUFBSSxHQUFHLEdBQVEsRUFBRSxDQUFDO1FBRWxCLE9BQU8sTUFBTSxDQUFDLGNBQWMsRUFBRTtZQUMxQixJQUFJLElBQUksR0FBUSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3JDLElBQUksSUFBSSxHQUFXLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBRTNDLFFBQVEsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRTtnQkFDekIsS0FBSyxVQUFVLENBQUM7Z0JBQ2hCLEtBQUssVUFBVTtvQkFDWCxHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztvQkFDL0QsTUFBTTtnQkFDVixLQUFLLFVBQVU7b0JBQ1gsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRTt3QkFDWixHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO3FCQUNsQjtvQkFDRCxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztvQkFDL0QsTUFBTTthQUNiO1NBQ0o7UUFFRCxPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7SUFFRCxNQUFNLENBQUMsU0FBUyxDQUFDLElBQVksRUFBRSxHQUFXO1FBQ3RDLElBQUksS0FBSyxHQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFMUUsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFDRCxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQXVCO1FBQ2xDLElBQUksR0FBRyxHQUFXLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFNUMsT0FBTyxFQUFFLElBQUksRUFBRSxHQUFHLEdBQUcsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxFQUFFLENBQUM7SUFDOUMsQ0FBQztJQUNELE1BQU0sQ0FBQyxVQUFVLENBQUMsS0FBVSxFQUFFLElBQVksRUFBRSxNQUFXLEVBQUUsTUFBdUI7UUFDNUUsUUFBUSxJQUFJLEVBQUU7WUFDVixLQUFLLFFBQVE7Z0JBQ1QsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLE1BQU07WUFDVixLQUFLLE9BQU8sQ0FBQztZQUNiLEtBQUssUUFBUTtnQkFDVCxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztnQkFDNUMsTUFBTTtZQUNWLEtBQUssT0FBTztnQkFDUixlQUFlO2dCQUNmLElBQUksTUFBTSxHQUFvQixJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDcEQsTUFBTSxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQztnQkFDM0MsTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDekIsTUFBTSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDMUIsTUFBTTtZQUNWLEtBQUssUUFBUTtnQkFDVCxJQUFJLE9BQU8sR0FBb0IsSUFBSSxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ3JELE9BQU8sQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUM7Z0JBQzVDLE9BQU8sQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQzNCLE1BQU0sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQzNCLE1BQU07WUFDVixLQUFLLFFBQVE7Z0JBQ1QsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO2dCQUNuRCxNQUFNLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUM1QixNQUFNO1lBQ1Y7Z0JBQ0ksSUFBSSxLQUFLLEdBQVEsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsQ0FBQztnQkFDN0UsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFO29CQUNULElBQUksR0FBRyxHQUFvQixJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztvQkFDM0QsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO29CQUNqRCxNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2lCQUMxQjtnQkFDRCxNQUFNO1NBQ2I7SUFDTCxDQUFDO0lBRUQsTUFBTSxDQUFDLFVBQVUsQ0FBQyxJQUFZLEVBQUUsTUFBVyxFQUFFLE1BQXVCO1FBQ2hFLFFBQVEsSUFBSSxFQUFFO1lBQ1YsS0FBSyxRQUFRO2dCQUNULE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNyQyxLQUFLLE9BQU8sQ0FBQztZQUNiLEtBQUssUUFBUTtnQkFDVCxPQUFPLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDckMsS0FBSyxPQUFPO2dCQUNSLElBQUksTUFBTSxHQUFvQixJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDcEQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUMvQixNQUFNLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDO2dCQUMzQyxJQUFJLEtBQUssR0FBVyxNQUFNLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ3ZDLE9BQU8sTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQzlCLEtBQUssUUFBUTtnQkFDVCxJQUFJLE9BQU8sR0FBb0IsSUFBSSxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ3JELE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztnQkFDaEMsT0FBTyxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQztnQkFDNUMsT0FBTyxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDaEMsS0FBSyxRQUFRO2dCQUNULElBQUksTUFBTSxHQUFXLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQy9DLE9BQU8sTUFBTSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUN2QztnQkFDSSxJQUFJLEtBQUssR0FBUSxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUM7Z0JBQ3pGLElBQUksS0FBSyxFQUFFO29CQUNQLElBQUksR0FBRyxHQUFXLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7b0JBQzVDLElBQUksR0FBb0IsQ0FBQztvQkFDekIsSUFBSSxHQUFHLEVBQUU7d0JBQ0wsR0FBRyxHQUFHLElBQUksS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO3dCQUM1QixNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRSxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7cUJBQ2pDO29CQUVELE9BQU8sR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO2lCQUMxRDtnQkFDRCxNQUFNO1NBQ2I7SUFDTCxDQUFDO0lBRUQsTUFBTSxDQUFDLFlBQVksQ0FBQyxJQUFZO1FBQzVCLE9BQU8sQ0FDSCxJQUFJLEtBQUssUUFBUTtZQUNqQixJQUFJLEtBQUssUUFBUTtZQUNqQixJQUFJLEtBQUssT0FBTztZQUNoQixJQUFJLEtBQUssUUFBUTtZQUNqQixJQUFJLEtBQUssUUFBUTtZQUNqQixJQUFJLEtBQUssT0FBTztZQUNoQixJQUFJLEtBQUssUUFBUSxDQUNwQixDQUFDO0lBQ04sQ0FBQztJQUNELE1BQU0sQ0FBQyxXQUFXLENBQUMsS0FBaUIsRUFBRSxLQUFVLEVBQUUsTUFBVyxFQUFFLE1BQXVCO1FBQ2xGLElBQUksWUFBWSxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUM7UUFDckMsSUFBSSxZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQzFCLE1BQU0sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ3pELE1BQU0sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztZQUNuRCxJQUFJLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO1lBQ2pDLEtBQUssSUFBSSxDQUFDLEdBQVcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO2dCQUMzQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO2FBQ3BEO1NBQ0o7YUFBTTtZQUNILElBQUksU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7WUFDL0IsS0FBSyxJQUFJLENBQUMsR0FBVyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7Z0JBQzNDLE1BQU0sQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BELElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO2FBQ3pEO1NBQ0o7SUFDTCxDQUFDO0lBQ0QsTUFBTSxDQUFDLFdBQVcsQ0FBQyxLQUFpQixFQUFFLElBQVksRUFBRSxNQUFXLEVBQUUsTUFBdUI7UUFDcEYsSUFBSSxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztRQUNyQyxJQUFJLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO1FBRWpDLElBQUksWUFBWSxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQ3BCLElBQUksTUFBTSxHQUFXLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDL0MsS0FBSyxJQUFJLENBQUMsR0FBVyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtnQkFDckMsS0FBSyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO2FBQ2hEO1NBQ0o7YUFBTTtZQUNILEtBQUssQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztTQUNoRDtJQUNMLENBQUM7SUFFRCxNQUFNLENBQUMsWUFBWSxDQUFDLENBQVM7UUFDekIsSUFBSSxNQUFNLEdBQW9CLElBQUksS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBRXBELEdBQUc7WUFDQyxJQUFJLEdBQUcsR0FBVyxDQUFDLEdBQUcsR0FBRyxDQUFDO1lBQzFCLElBQUksSUFBSSxHQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDO1lBRXZDLElBQUksSUFBSSxLQUFLLENBQUMsRUFBRTtnQkFDWixHQUFHLEdBQUcsR0FBRyxHQUFHLEdBQUcsQ0FBQzthQUNuQjtZQUVELE1BQU0sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDdEIsQ0FBQyxHQUFHLElBQUksQ0FBQztTQUNaLFFBQ00sQ0FBQyxLQUFLLENBQUMsRUFBRTtRQUVoQixPQUFPLE1BQU0sQ0FBQztJQUNsQixDQUFDO0lBQ0QsTUFBTSxDQUFDLFlBQVksQ0FBQyxNQUF1QjtRQUN2QyxJQUFJLENBQUMsR0FBVyxDQUFDLENBQUM7UUFFbEIsS0FBSyxJQUFJLENBQUMsR0FBVyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDNUMsSUFBSSxDQUFDLEdBQVcsTUFBTSxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDMUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM1QyxJQUFJLENBQUMsR0FBRyxHQUFHLEVBQUU7Z0JBQ1QsT0FBTyxDQUFDLENBQUM7YUFDWjtTQUNKO1FBQ0QsT0FBTyxDQUFDLENBQUM7SUFDYixDQUFDO0lBQ0QsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFTO1FBQ3pCLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRTFDLE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQ0QsTUFBTSxDQUFDLFlBQVksQ0FBQyxNQUF1QjtRQUN2QyxJQUFJLENBQUMsR0FBVyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRTFDLElBQUksSUFBSSxHQUFXLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFNUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQztRQUU3QixPQUFPLENBQUMsQ0FBQztJQUNiLENBQUM7O0FBM1BNLGNBQUssR0FBUTtJQUNoQixNQUFNLEVBQUUsQ0FBQztJQUNULE1BQU0sRUFBRSxDQUFDO0lBQ1QsS0FBSyxFQUFFLENBQUM7SUFDUixNQUFNLEVBQUUsQ0FBQztJQUNULE1BQU0sRUFBRSxDQUFDO0lBQ1QsT0FBTyxFQUFFLENBQUM7SUFDVixLQUFLLEVBQUUsQ0FBQztDQUNYLENBQUM7QUFDYSxpQkFBUSxHQUFRLEVBQUUsQ0FBQztBQUNuQixpQkFBUSxHQUFRLEVBQUUsQ0FBQztBQW9QdEMsTUFBTSxRQUFRO0lBSVYsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFTO1FBQ2pCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN6QixJQUFJLE1BQU0sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ3pCLElBQUksSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUM7UUFDckIsS0FBSyxJQUFJLElBQUksSUFBSSxNQUFNLEVBQUU7WUFDckIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQztTQUM3QjtJQUNMLENBQUM7SUFFRCxNQUFNLENBQUMsS0FBSyxDQUFDLElBQVk7UUFDckIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFDRCxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQVU7UUFDckIsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7O0FBakJjLGFBQUksR0FBUSxFQUFFLENBQUM7QUFDZixlQUFNLEdBQVEsRUFBRSxDQUFDIn0= \ No newline at end of file diff --git a/game-server/test/PinusWSClient.ts b/game-server/test/PinusWSClient.ts new file mode 100644 index 000000000..87d1c17bf --- /dev/null +++ b/game-server/test/PinusWSClient.ts @@ -0,0 +1,901 @@ + +import * as egret from './ByteArray'; +import * as WebSocket from 'ws'; + +export enum PinusWSClientEvent { + EVENT_IO_ERROR = 'io-error', + EVENT_CLOSE = 'close', + EVENT_KICK = 'onKick', + EVENT_HEART_BEAT_TIMEOUT = 'heartbeat timeout' +} + +export class PinusWSClient { + + static DEBUG: boolean = false; + + private JS_WS_CLIENT_TYPE = 'js-websocket'; + private JS_WS_CLIENT_VERSION = '0.0.5'; + + private RES_OK: number = 200; + private RES_FAIL: number = 500; + private RES_OLD_CLIENT: number = 501; + + + private socket: WebSocket = null; + private callbacks: any = {}; + private handlers: any = {}; + // Map from request id to route + private routeMap: any = {}; + + private heartbeatInterval: number = 0; + private heartbeatTimeout: number = 0; + private nextHeartbeatTimeout: number = 0; + private gapThreshold: number = 100; + private heartbeatId: any = null; + private heartbeatTimeoutId: any = null; + + private handshakeCallback: any = null; + private handshakeBuffer: any; + private initCallback: Function = null; + + private _callbacks: any = {}; + + private reqId: number = 0; + + + private _package: IPackage; + private _message: IMessage; + + constructor() { + + this.socket = null; + this.callbacks = {}; + this.handlers = {}; + // Map from request id to route + this.routeMap = {}; + this._message = new Message(this.routeMap); + this._package = new Package(); + + + this.heartbeatInterval = 0; + this.heartbeatTimeout = 0; + this.nextHeartbeatTimeout = 0; + this.gapThreshold = 100; + this.heartbeatId = null; + this.heartbeatTimeoutId = null; + + this.handshakeCallback = null; + + this.handshakeBuffer = { + 'sys': { + type: this.JS_WS_CLIENT_TYPE, + version: this.JS_WS_CLIENT_VERSION + }, + 'user': { + } + }; + + this.initCallback = null; + + this.reqId = 0; + + this.handlers[Package.TYPE_HANDSHAKE] = this.handshake; + this.handlers[Package.TYPE_HEARTBEAT] = this.heartbeat; + this.handlers[Package.TYPE_DATA] = this.onData; + this.handlers[Package.TYPE_KICK] = this.onKick; + } + + + public init(params: any, cb: Function): void { + console.log('init', params); + this.initCallback = cb; + let host = params.host; + let port = params.port; + // + // var url = 'ws://' + host; + // if(port) { + // url += ':' + port; + // } + + this.handshakeBuffer.user = params.user; + this.handshakeCallback = params.handshakeCallback; + this.initWebSocket(host, port, cb); + } + private initWebSocket(host: string, port: number, cb: Function): void { + console.log('[Pinus] connect to:', host, port); + + let url = 'ws://' + host; + if (port) { + url += ':' + port; + } + let socket = new WebSocket(url); + socket.binaryType = 'arraybuffer'; + socket.onopen = (event) => { + this.onConnect(); + }; + socket.onmessage = (event) => { + this.onMessage(event); + }; + socket.onerror = (event) => { + this.onIOError(event); + }; + socket.onclose = (event) => { + this.onClose(event); + }; + this.socket = socket; + } + + + public on(event: PinusWSClientEvent.EVENT_IO_ERROR, fn: (err: Error) => void): void; + public on(event: PinusWSClientEvent.EVENT_CLOSE, fn: (err: Error) => void): void; + public on(event: PinusWSClientEvent.EVENT_KICK, fn: (err: Error) => void): void; + public on(event: PinusWSClientEvent.EVENT_HEART_BEAT_TIMEOUT, fn: (err: Error) => void): void; + public on(route: string, fn: (msg: any) => void) : void; + public on(event: string, fn: (msg: any) => void) { + (this._callbacks[event] = this._callbacks[event] || []).push(fn); + } + public request(route: string, msg: any, cb: Function) { + if (arguments.length === 2 && typeof msg === 'function') { + cb = msg; + msg = {}; + } else { + msg = msg || {}; + } + route = route || msg.route; + if (!route) { + return; + } + + this.reqId++; + if (this.reqId > 127) { + this.reqId = 1; + } + let reqId = this.reqId; + + + if (PinusWSClient.DEBUG) { + console.log(`REQUEST:route:${route} , reqId:${reqId}, msg:${msg}`); + } + + this.sendMessage(reqId, route, msg); + + this.callbacks[reqId] = cb; + this.routeMap[reqId] = route; + } + + public notify(route: string, msg: any): void { + this.sendMessage(0, route, msg); + } + + private onMessage(event: { data: string | Buffer | ArrayBuffer | Buffer[]; type: string; target: WebSocket }): void { + this.processPackage(this._package.decode(new egret.ByteArray(event.data))); + + } + private sendMessage(reqId: number, route: string, msg: any) { + let byte: egret.ByteArray; + + byte = this._message.encode(reqId, route, msg); + byte = this._package.encode(Package.TYPE_DATA, byte); + + this.send(byte); + + } + + private onConnect(): void { + console.log('[Pinus] connect success'); + this.send(this._package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(this.handshakeBuffer)))); + } + + private onClose(e: any): void { + console.error('[Pinus] connect close:', e); + // this.emit(Pinus.EVENT_CLOSE,e); + } + + private onIOError(e: any): void { + // this.emit(Pinus.EVENT_IO_ERROR, e); + console.error('socket error: ', e); + } + + private onKick(event: string) { + // this.emit(PinusWSClient.EVENT_KICK,event); + } + private onData(data: any) { + // probuff decode + let msg = this._message.decode(data); + + if (msg.id > 0) { + msg.route = this.routeMap[msg.id]; + delete this.routeMap[msg.id]; + if (!msg.route) { + return; + } + } + + // msg.body = this.deCompose(msg); + + this.processMessage(msg); + + } + + private processMessage(msg: any) { + if (!msg.id) { + // server push message + + if (PinusWSClient.DEBUG) { + console.log(`EVENT: Route:${msg.route} Msg:${msg.body}`); + } + + // this.emit(msg.route, msg.body); + return; + } + if (PinusWSClient.DEBUG) { + console.log(`RESPONSE: Id:${msg.id} Msg:${msg.body}`); + } + + // if have a id then find the callback function with the request + let cb = this.callbacks[msg.id]; + + delete this.callbacks[msg.id]; + if (typeof cb !== 'function') { + return; + } + if (msg.body && msg.body.code === 500) { + let obj: any = { 'code': 500, 'desc': '服务器内部错误', 'key': 'INTERNAL_ERROR' }; + msg.body.error = obj; + } + cb(msg.body); + return; + } + + private heartbeat(data: any) { + + if (!this.heartbeatInterval) { + // no heartbeat + return; + } + + let obj = this._package.encode(Package.TYPE_HEARTBEAT); + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + + if (this.heartbeatId) { + // already in a heartbeat interval + return; + } + + let self = this; + self.heartbeatId = setTimeout(function () { + self.heartbeatId = null; + self.send(obj); + + self.nextHeartbeatTimeout = Date.now() + self.heartbeatTimeout; + self.heartbeatTimeoutId = setTimeout(self.heartbeatTimeoutCb.bind(self, data), self.heartbeatTimeout); + }, self.heartbeatInterval); + } + private heartbeatTimeoutCb(data: any) { + let gap = this.nextHeartbeatTimeout - Date.now(); + if (gap > this.gapThreshold) { + this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb.bind(this, data), gap); + } else { + console.error('server heartbeat timeout', data); + // this.emit(PinusWSClient.EVENT_HEART_BEAT_TIMEOUT,data); + this._disconnect(); + } + } + public off(event?: string, fn?: Function) { + this.removeAllListeners(event, fn); + } + public removeAllListeners(event?: string, fn?: Function) { + // all + if (0 === arguments.length) { + this._callbacks = {}; + return; + } + + // specific event + let callbacks = this._callbacks[event]; + if (!callbacks) { + return; + } + + // remove all handlers + if (event && !fn) { + delete this._callbacks[event]; + return; + } + + // remove specific handler + let i = this.index(callbacks, (fn as any)._off || fn); + if (~i) { + callbacks.splice(i, 1); + } + return; + } + private index(arr: any, obj: any) { + if ([].indexOf) { + return arr.indexOf(obj); + } + + for (let i = 0; i < arr.length; ++i) { + if (arr[i] === obj) + return i; + } + return -1; + } + public disconnect(): void { + this._disconnect(); + } + private _disconnect(): void { + console.warn('[Pinus] client disconnect ...'); + + if (this.socket) this.socket.close(); + this.socket = null; + if (this.heartbeatId) { + clearTimeout(this.heartbeatId); + this.heartbeatId = null; + } + + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + + } + private processPackage(msg: any): void { + this.handlers[msg.type].apply(this, [msg.body]); + } + private handshake(resData: any) { + + let data = JSON.parse(Protocol.strdecode(resData)); + if (data.code === this.RES_OLD_CLIENT) { + // this.emit(PinusWSClient.EVENT_IO_ERROR, 'client version not fullfill'); + return; + } + + if (data.code !== this.RES_OK) { + // this.emit(PinusWSClient.EVENT_IO_ERROR, 'handshake fail'); + return; + } + + this.handshakeInit(data); + + let obj = this._package.encode(Package.TYPE_HANDSHAKE_ACK); + this.send(obj); + if (this.initCallback) { + this.initCallback(data); + this.initCallback = null; + } + } + private handshakeInit(data: any): void { + + if (data.sys) { + Routedic.init(data.sys.dict); + Protobuf.init(data.sys.protos); + } + if (data.sys && data.sys.heartbeat) { + this.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval + this.heartbeatTimeout = this.heartbeatInterval * 2; // max heartbeat timeout + } else { + this.heartbeatInterval = 0; + this.heartbeatTimeout = 0; + } + + if (typeof this.handshakeCallback === 'function') { + this.handshakeCallback(data.user); + } + } + private send(byte: egret.ByteArray): void { + if (this.socket) { + this.socket.send(byte.buffer); + } + } + // private deCompose(msg){ + // return JSON.parse(Protocol.strdecode(msg.body)); + // } + private emit(event: string, ...args: any[]) { + let params = [].slice.call(arguments, 1); + let callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (let i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, params); + } + } + + return this; + } + + +} + +class Package implements IPackage { + static TYPE_HANDSHAKE: number = 1; + static TYPE_HANDSHAKE_ACK: number = 2; + static TYPE_HEARTBEAT: number = 3; + static TYPE_DATA: number = 4; + static TYPE_KICK: number = 5; + + public encode(type: number, body?: egret.ByteArray) { + let length: number = body ? body.length : 0; + + let buffer: egret.ByteArray = new egret.ByteArray(); + buffer.writeByte(type & 0xff); + buffer.writeByte((length >> 16) & 0xff); + buffer.writeByte((length >> 8) & 0xff); + buffer.writeByte(length & 0xff); + + if (body) buffer.writeBytes(body, 0, body.length); + + return buffer; + } + public decode(buffer: egret.ByteArray) { + + let type: number = buffer.readUnsignedByte(); + let len: number = (buffer.readUnsignedByte() << 16 | buffer.readUnsignedByte() << 8 | buffer.readUnsignedByte()) >>> 0; + + let body: egret.ByteArray; + + if (buffer.bytesAvailable >= len) { + body = new egret.ByteArray(); + if (len) buffer.readBytes(body, 0, len); + } + else { + console.log('[Package] no enough length for current type:', type); + } + + return { type: type, body: body, length: len }; + } +} + +class Message implements IMessage { + + public static MSG_FLAG_BYTES: number = 1; + public static MSG_ROUTE_CODE_BYTES: number = 2; + public static MSG_ID_MAX_BYTES: number = 5; + public static MSG_ROUTE_LEN_BYTES: number = 1; + + public static MSG_ROUTE_CODE_MAX: number = 0xffff; + + public static MSG_COMPRESS_ROUTE_MASK: number = 0x1; + public static MSG_TYPE_MASK: number = 0x7; + + static TYPE_REQUEST: number = 0; + static TYPE_NOTIFY: number = 1; + static TYPE_RESPONSE: number = 2; + static TYPE_PUSH: number = 3; + + constructor(private routeMap: any) { + + } + + public encode(id: number, route: string, msg: any) { + let buffer: egret.ByteArray = new egret.ByteArray(); + + let type: number = id ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; + + let byte: egret.ByteArray = Protobuf.encode(route, msg) || Protocol.strencode(JSON.stringify(msg)); + + let rot: any = Routedic.getID(route) || route; + + buffer.writeByte((type << 1) | ((typeof (rot) === 'string') ? 0 : 1)); + + if (id) { + // 7.x + do { + let tmp: number = id % 128; + let next: number = Math.floor(id / 128); + + if (next !== 0) { + tmp = tmp + 128; + } + + buffer.writeByte(tmp); + + id = next; + } while (id !== 0); + + // 5.x + // var len:Array = []; + // len.push(id & 0x7f); + // id >>= 7; + // while(id > 0) + // { + // len.push(id & 0x7f | 0x80); + // id >>= 7; + // } + // + // for (var i:int = len.length - 1; i >= 0; i--) + // { + // buffer.writeByte(len[i]); + // } + } + + if (rot) { + if (typeof rot === 'string') { + buffer.writeByte(rot.length & 0xff); + buffer.writeUTFBytes(rot); + } + else { + buffer.writeByte((rot >> 8) & 0xff); + buffer.writeByte(rot & 0xff); + } + } + + if (byte) { + buffer.writeBytes(byte); + } + + return buffer; + } + + public decode(buffer: egret.ByteArray): any { + // parse flag + let flag: number = buffer.readUnsignedByte(); + let compressRoute: number = flag & Message.MSG_COMPRESS_ROUTE_MASK; + let type: number = (flag >> 1) & Message.MSG_TYPE_MASK; + let route: any; + + // parse id + let id: number = 0; + if (type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE) { + // 7.x + let i: number = 0; + let m: number; + do { + m = buffer.readUnsignedByte(); + id = id + ((m & 0x7f) * Math.pow(2, (7 * i))); + i++; + } while (m >= 128); + + // 5.x + // var byte:int = buffer.readUnsignedByte(); + // id = byte & 0x7f; + // while(byte & 0x80) + // { + // id <<= 7; + // byte = buffer.readUnsignedByte(); + // id |= byte & 0x7f; + // } + } + + // parse route + if (type === Message.TYPE_REQUEST || type === Message.TYPE_NOTIFY || type === Message.TYPE_PUSH) { + + if (compressRoute) { + route = buffer.readUnsignedShort(); + } + else { + let routeLen: number = buffer.readUnsignedByte(); + route = routeLen ? buffer.readUTFBytes(routeLen) : ''; + } + } + else if (type === Message.TYPE_RESPONSE) { + route = this.routeMap[id]; + } + + if (!id && !(typeof (route) === 'string')) { + route = Routedic.getName(route); + } + + let body: any = Protobuf.decode(route, buffer) || JSON.parse(Protocol.strdecode(buffer)); + + return { id: id, type: type, route: route, body: body }; + } + +} +class Protocol { + + public static strencode(str: string): egret.ByteArray { + let buffer: egret.ByteArray = new egret.ByteArray(); + buffer.length = str.length; + buffer.writeUTFBytes(str); + return buffer; + } + + public static strdecode(byte: egret.ByteArray): string { + return byte.readUTFBytes(byte.bytesAvailable); + } +} +class Protobuf { + static TYPES: any = { + uInt32: 0, + sInt32: 0, + int32: 0, + double: 1, + string: 2, + message: 2, + float: 5 + }; + private static _clients: any = {}; + private static _servers: any = {}; + + static init(protos: any): void { + this._clients = protos && protos.client || {}; + this._servers = protos && protos.server || {}; + } + + static encode(route: string, msg: any): egret.ByteArray { + + let protos: any = this._clients[route]; + + if (!protos) return null; + + return this.encodeProtos(protos, msg); + } + + static decode(route: string, buffer: egret.ByteArray): any { + + let protos: any = this._servers[route]; + + if (!protos) return null; + + return this.decodeProtos(protos, buffer); + } + private static encodeProtos(protos: any, msg: any): egret.ByteArray { + let buffer: egret.ByteArray = new egret.ByteArray(); + + for (let name in msg) { + if (protos[name]) { + let proto: any = protos[name]; + + switch (proto.option) { + case 'optional': + case 'required': + buffer.writeBytes(this.encodeTag(proto.type, proto.tag)); + this.encodeProp(msg[name], proto.type, protos, buffer); + break; + case 'repeated': + if (!!msg[name] && msg[name].length > 0) { + this.encodeArray(msg[name], proto, protos, buffer); + } + break; + } + } + } + + return buffer; + } + static decodeProtos(protos: any, buffer: egret.ByteArray): any { + let msg: any = {}; + + while (buffer.bytesAvailable) { + let head: any = this.getHead(buffer); + let name: string = protos.__tags[head.tag]; + + switch (protos[name].option) { + case 'optional': + case 'required': + msg[name] = this.decodeProp(protos[name].type, protos, buffer); + break; + case 'repeated': + if (!msg[name]) { + msg[name] = []; + } + this.decodeArray(msg[name], protos[name].type, protos, buffer); + break; + } + } + + return msg; + } + + static encodeTag(type: number, tag: number): egret.ByteArray { + let value: number = this.TYPES[type] !== undefined ? this.TYPES[type] : 2; + + return this.encodeUInt32((tag << 3) | value); + } + static getHead(buffer: egret.ByteArray): any { + let tag: number = this.decodeUInt32(buffer); + + return { type: tag & 0x7, tag: tag >> 3 }; + } + static encodeProp(value: any, type: string, protos: any, buffer: egret.ByteArray): void { + switch (type) { + case 'uInt32': + buffer.writeBytes(this.encodeUInt32(value)); + break; + case 'int32': + case 'sInt32': + buffer.writeBytes(this.encodeSInt32(value)); + break; + case 'float': + // Float32Array + let floats: egret.ByteArray = new egret.ByteArray(); + floats.endian = egret.Endian.LITTLE_ENDIAN; + floats.writeFloat(value); + buffer.writeBytes(floats); + break; + case 'double': + let doubles: egret.ByteArray = new egret.ByteArray(); + doubles.endian = egret.Endian.LITTLE_ENDIAN; + doubles.writeDouble(value); + buffer.writeBytes(doubles); + break; + case 'string': + buffer.writeBytes(this.encodeUInt32(value.length)); + buffer.writeUTFBytes(value); + break; + default: + let proto: any = protos.__messages[type] || this._clients['message ' + type]; + if (!!proto) { + let buf: egret.ByteArray = this.encodeProtos(proto, value); + buffer.writeBytes(this.encodeUInt32(buf.length)); + buffer.writeBytes(buf); + } + break; + } + } + + static decodeProp(type: string, protos: any, buffer: egret.ByteArray): any { + switch (type) { + case 'uInt32': + return this.decodeUInt32(buffer); + case 'int32': + case 'sInt32': + return this.decodeSInt32(buffer); + case 'float': + let floats: egret.ByteArray = new egret.ByteArray(); + buffer.readBytes(floats, 0, 4); + floats.endian = egret.Endian.LITTLE_ENDIAN; + let float: number = buffer.readFloat(); + return floats.readFloat(); + case 'double': + let doubles: egret.ByteArray = new egret.ByteArray(); + buffer.readBytes(doubles, 0, 8); + doubles.endian = egret.Endian.LITTLE_ENDIAN; + return doubles.readDouble(); + case 'string': + let length: number = this.decodeUInt32(buffer); + return buffer.readUTFBytes(length); + default: + let proto: any = protos && (protos.__messages[type] || this._servers['message ' + type]); + if (proto) { + let len: number = this.decodeUInt32(buffer); + let buf: egret.ByteArray; + if (len) { + buf = new egret.ByteArray(); + buffer.readBytes(buf, 0, len); + } + + return len ? Protobuf.decodeProtos(proto, buf) : false; + } + break; + } + } + + static isSimpleType(type: string): boolean { + return ( + type === 'uInt32' || + type === 'sInt32' || + type === 'int32' || + type === 'uInt64' || + type === 'sInt64' || + type === 'float' || + type === 'double' + ); + } + static encodeArray(array: Array, proto: any, protos: any, buffer: egret.ByteArray): void { + let isSimpleType = this.isSimpleType; + if (isSimpleType(proto.type)) { + buffer.writeBytes(this.encodeTag(proto.type, proto.tag)); + buffer.writeBytes(this.encodeUInt32(array.length)); + let encodeProp = this.encodeProp; + for (let i: number = 0; i < array.length; i++) { + encodeProp(array[i], proto.type, protos, buffer); + } + } else { + let encodeTag = this.encodeTag; + for (let j: number = 0; j < array.length; j++) { + buffer.writeBytes(encodeTag(proto.type, proto.tag)); + this.encodeProp(array[j], proto.type, protos, buffer); + } + } + } + static decodeArray(array: Array, type: string, protos: any, buffer: egret.ByteArray): void { + let isSimpleType = this.isSimpleType; + let decodeProp = this.decodeProp; + + if (isSimpleType(type)) { + let length: number = this.decodeUInt32(buffer); + for (let i: number = 0; i < length; i++) { + array.push(decodeProp(type, protos, buffer)); + } + } else { + array.push(decodeProp(type, protos, buffer)); + } + } + + static encodeUInt32(n: number): egret.ByteArray { + let result: egret.ByteArray = new egret.ByteArray(); + + do { + let tmp: number = n % 128; + let next: number = Math.floor(n / 128); + + if (next !== 0) { + tmp = tmp + 128; + } + + result.writeByte(tmp); + n = next; + } + while (n !== 0); + + return result; + } + static decodeUInt32(buffer: egret.ByteArray): number { + let n: number = 0; + + for (let i: number = 0; i < buffer.length; i++) { + let m: number = buffer.readUnsignedByte(); + n = n + ((m & 0x7f) * Math.pow(2, (7 * i))); + if (m < 128) { + return n; + } + } + return n; + } + static encodeSInt32(n: number): egret.ByteArray { + n = n < 0 ? (Math.abs(n) * 2 - 1) : n * 2; + + return this.encodeUInt32(n); + } + static decodeSInt32(buffer: egret.ByteArray): number { + let n: number = this.decodeUInt32(buffer); + + let flag: number = ((n % 2) === 1) ? -1 : 1; + + n = ((n % 2 + n) / 2) * flag; + + return n; + } + +} +class Routedic { + private static _ids: any = {}; + private static _names: any = {}; + + static init(dict: any): void { + this._names = dict || {}; + let _names = this._names; + let _ids = this._ids; + for (let name in _names) { + _ids[_names[name]] = name; + } + } + + static getID(name: string) { + return this._names[name]; + } + static getName(id: number) { + return this._ids[id]; + } +} + +interface IMessage { + /** + * encode + * @param id + * @param route + * @param msg + * @return ByteArray + */ + encode(id: number, route: string, msg: any): egret.ByteArray; + + /** + * decode + * @param buffer + * @return Object + */ + decode(buffer: egret.ByteArray): any; +} +interface IPackage { + + encode(type: number, body?: egret.ByteArray): egret.ByteArray; + + decode(buffer: egret.ByteArray): any; +} + diff --git a/game-server/test/comBattle.test.ts b/game-server/test/comBattle.test.ts index 666f7e041..259f84719 100644 --- a/game-server/test/comBattle.test.ts +++ b/game-server/test/comBattle.test.ts @@ -73,6 +73,15 @@ describe('寻宝创建队伍', function() { }); it('两个玩家匹配到结算', function(done) { + pinusClient.on('onTeammateAct', (msg) => { + console.log('onTeammateAct: ', msg); + }); + pinusClient.on('onComBtlStart', (msg) => { + console.log('onComBtlStart: ', msg); + }); + pinusClient.on('onTeamJoin', (msg) => { + console.log('onTeamJoin: ', msg); + }); pinusClient.request('battle.comBattleHandler.createTeam', createTeamParms, (res) => { checkSuccessResponse(res); expect(res.data.teamCode).to.be.a('string');