S-C-Link_client.js

// ==UserScript==
const gameConfig = {
    type: "S-C-Link_client",
    title: "S-C-Link 客户端",
    doc: `EasyBox3Lib的附属库,用于通过服务端渲染客户端内容,需要安装EasyBox3Lib。EasyBox3Lib的安装见帮助链接`,
    help: "https://qndm.github.io/EasyBox3Lib",
    file: true,
    isClient: true
}
// ==UserScript==

/**
 * S-C-Link 库(客户端)  
 * 用于通过服务端渲染客户端内容的库  
 * 依赖EasyBox3Lib 0.1.6  
 * (为什么使用`Game`开头的API?因为GUI是新岛专属())
 * @author qndm
 * @module S-C-Link_client
 * @version 0.0.2
 * @license MIT
 */
// ----- S-C-Link_client Start -----
/**
 * 节点样式
 * @typedef NodeStyle
 * @property {string | number} textXAlignment 若节点为文本,则为节点的水平对齐方式
 * @property {string | number} textYAlignment 若节点为文本,则为节点的垂直对齐方式
 * @property {RGBColor} textColor 若节点为文本,则为文字颜色 
 * @property {RGBAColor} backgroundColor 节点背景颜色
 * @property {RGBAColor} placeholderColor 若节点是输入框,表示占位文本背景颜色
 * @property {number | null} zIndex 节点的层级,用于确定节点的渲染顺序。
 * @property {number} textFontSize 若节点为文本,则为文字字号 
 * @property {number} imageOpacity 若节点是图片,表示图片不透明度,范围0~1
 */
/**
 * 打包后的节点
 * @typedef PackedNode
 * @property {"renderMessage" | "removeMessage"} protocols 事件协议,打包节点所需的协议只有`"renderMessage"`和`"removeMessage"`
 * @property {number} id 节点序号
 * @property {?string} name 节点名称,只在`"renderMessage"`协议下使用
 * @property {?object} data 节点自定义数量,只在`"renderMessage"`协议下使用
 * @property {?NodeStyle} style 节点样式,只在`"renderMessage"`协议下使用
 * @property {?string} content 节点内容,只在`"renderMessage"`协议下使用
 * @property {?number} pointerEventBehavior 界面元素对指针事件的行为方式,只在`"renderMessage"`协议下使用
 * @property {?string} autoResize 节点自动调整尺寸的方式,只在`"renderMessage"`协议下使用
 * @property {?boolean} visible 节点的可见性,只在`"renderMessage"`协议下使用
 * @property {?string} placeholder 节点占位文本内容,只在`"renderMessage"`协议下使用
 * @property {?Coord2} size 节点大小,只在`"renderMessage"`协议下使用
 * @property {?Coord2} position 节点位置,只在`"renderMessage"`协议下使用
 * @property {?Vector2} anchor 节点锚点,只在`"renderMessage"`协议下使用
 * @property {?number} parent 父节点id
 * @property {?PackedNode[]} childern 子节点,只在`"renderMessage"`协议下使用
 * @property {?NodeType} type 节点类型,只在`"renderMessage"`协议下使用
 * @property {?PackedNodeEvent} events 打包后的节点事件
 */
/**
 * 打包后的数据,用于发送给服务端  
 * 要求有`protocols`属性指定协议
 * @typedef {PackedNode | PackedClientEvent} PackedData
 */
/**
 * 打包后的节点事件
 * @typedef PackedNodeEvent
 * @property {boolean} onPress 是否监听当节点被按下时,触发的事件
 * @property {boolean} onRelease 是否监听当节点被松开时,触发的事件
 * @property {boolean} onFocus 是否监听当节点为输入框,聚焦时触发的事件
 * @property {boolean} onBlur 是否监听当节点为输入框,失去焦点时触发的事件
 */
/**
 * 当节点事件被触发时,发送到服务端的数据
 * @typedef PackedClientEvent
 * @property {"triggeredEvent"} protocols 事件协议,触发事件的协议为`"triggeredEvent"`
 * @property {?PackedNode} node 节点
 * @property {EventName} eventName 触发的事件类型
 */
/**
 * 事件类型
 * @enum {string}
 */
const EventName = {
    onPress: "onPress",
    onRelease: "onRelease",
    onFocus: "onFocus",
    onBlur: "onBlur",
    onLockChange: "onLockChange",
    onLockError: "onLockError",
}
/**
 * @type {Map<number, NodeControllers>}
 */
var nodeMap = new Map();
/**
 * 二维向量  
 * 大部分和三维向量相同
 */
class Vector2 {
    x = 0;
    y = 0;
    /**
     * 定义一个二维向量
     * @param {number} x 二维向量`x`的值(水平方向)
     * @param {number} y 二维向量`y`的值(竖直方向)
     */
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    set(x, y) {
        this.x = x;
        this.y = y;
        return this;
    }
    clone() {
        return new Vector2(this.x, this.y);
    }
    copy(v) {
        this.x = v.x;
        this.y = v.y;
        return this;
    }
    add(v) {
        return new Vector2(this.x + v.x, this.y / v.y);
    }
    sub(v) {
        return new Vector2(this.x - v.x, this.y - v.y);
    }
    mul(v) {
        return new Vector2(this.x * v.x, this.y * v.y);
    }
    div(v) {
        return new Vector2(this.x / v.x, this.y / v.y);
    }
    addEq(v) {
        this.x += v.x;
        this.y += v.y;
        return this;
    }
    subEq(v) {
        this.x -= v.x;
        this.y -= v.y;
        return this;
    }
    mulEq(v) {
        this.x *= v.x;
        this.y *= v.y;
        return this;
    }
    divEq(v) {
        this.x /= v.x;
        this.y /= v.y;
        return this;
    }
    pow(n) {
        return new Vector2(Math.pow(this.x, n), Math.pow(this.y, n));
    }
    distance(v) {
        return Math.sqrt(Math.pow(v.x - this.x, 2) + Math.pow(v.y - this.y, 2));
    }
    mag() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    min(v) {
        return new Vector2(Math.min(this.x, v.x), Math.min(this.y, v.y));
    }
    max(v) {
        return new Vector2(Math.max(this.x, v.x), Math.max(this.y, v.y));
    }
    /**
     * 归一化函数
     * @returns {Vector2}
     */
    normalize() {
        let max = Math.max(this.x, this.y);
        return new Vector2(this.x / max, this.y / max);
    }
    scale(n) {
        return new Vector2(this.x * n, this.y * n);
    }
    toString() {
        return JSON.stringify(this);
    }
    towards(v) {
        return new Vector2(v.x - this.x, v.y - this.y);
    }
    equals(v, tolerance = 0.0001) {
        return Math.abs(v.x - this.x) <= tolerance && Math.abs(v.y - this.y) <= tolerance;
    }
    lerp(v) {
        return this.add(v).scale(0.5);
    }
    static fromVec2(v) {
        return new Vector2(v.x, v.y);
    }
    setVec2(vec2) {
        vec2.x = this.x;
        vec2.y = this.y;
    }
}
/**
 * 三维向量  
 * 大部分和二维向量相同
 */
class Vector3 {
    x = 0;
    y = 0;
    z = 0;
    /**
     * 定义一个三维向量
     * @param {number} x 三维向量`x`的值(左右方向)
     * @param {number} y 三维向量`y`的值(竖直方向)
     * @param {number} z 三维向量`z`的值(前后方向)
     */
    constructor(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    set(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
        return this;
    }
    clone() {
        return new Vector3(this.x, this.y, this.z);
    }
    copy(v) {
        this.x = v.x;
        this.y = v.y;
        this.z = v.z;
        return this;
    }
    add(v) {
        return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);
    }
    sub(v) {
        return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
    }
    mul(v) {
        return new Vector3(this.x * v.x, this.y * v.y, this.z * v.z);
    }
    div(v) {
        return new Vector3(this.x / v.x, this.y / v.y, this.z / v.z);
    }
    addEq(v) {
        this.x += v.x;
        this.y += v.y;
        this.z += v.z;
        return this;
    }
    subEq(v) {
        this.x -= v.x;
        this.y -= v.y;
        this.z -= v.z;
        return this;
    }
    mulEq(v) {
        this.x *= v.x;
        this.y *= v.y;
        this.z *= v.z;
        return this;
    }
    divEq(v) {
        this.x /= v.x;
        this.y /= v.y;
        this.z /= v.z;
        return this;
    }
    pow(n) {
        return new Vector3(Math.pow(this.x, n), Math.pow(this.y, n), Math.pow(this.z, n));
    }
    distance(v) {
        return Math.sqrt(Math.pow(v.x - this.x, 2) + Math.pow(v.y - this.y, 2) + Math.pow(v.z - this.z, 2));
    }
    mag() {
        return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
    }
    min(v) {
        return new Vector2(Math.min(this.x, v.x), Math.min(this.y, v.y), Math.min(this.z, v.z));
    }
    max(v) {
        return new Vector2(Math.max(this.x, v.x), Math.max(this.y, v.y), Math.max(this.z, v.z));
    }
    /**
     * 归一化函数
     * @returns {Vector2}
     */
    normalize() {
        let max = Math.max(this.x, this.y, this.z);
        return new Vector3(this.x / max, this.y / max, this.z / max);
    }
    scale(n) {
        return new Vector3(this.x * n, this.y * n, this.z * n);
    }
    toString() {
        return JSON.stringify(this);
    }
    towards(v) {
        return new Vector3(v.x - this.x, v.y - this.y, v.z - this.z);
    }
    equals(v, tolerance = 0.0001) {
        return Math.abs(v.x - this.x) <= tolerance && Math.abs(v.y - this.y) <= tolerance && Math.abs(v.z - this.z) <= tolerance;
    }
    lerp(v) {
        return this.add(v).scale(0.5);
    }
    setVec3(vec3) {
        vec3.x = this.x;
        vec3.y = this.y;
        vec3.z = this.z;
    }
    static fromVec3(v) {
        return new Vector3(v.x, v.y, v.z);
    }
}

/**
 * 图像映射中区域的坐标  
 * 值为`offset`(绝对坐标)和`scale`(占父元素空间的百分比)之和
 */
class Coord2 {
    offset = new Vector2(0, 0);
    scale = new Vector2(0, 0);
    constructor(offset, scale = new Vector2(0, 0)) {
        this.offset = offset;
        this.scale = scale;
    }
    /**
     * 设置`offset.x`的值
     * @param {number} value
     */
    set x(value) {
        this.offset.x = value;
    }
    /**
     * 设置`offset.y`的值
     * @param {number} value
     */
    set y(value) {
        this.offset.y = value;
    }
    set(offset, scale) {
        this.offset = offset;
        this.scale = scale;
    }
    copy(c) {
        this.offset = c.offset.clone();
        this.scale = c.scale.clone();
    }
    clone() {
        return new Coord2(this.offset.clone(), this.scale.clone());
    }
    setCoord2(c) {
        this.offset.setVec2(c.offset);
        this.scale.setVec2(c.scale);
    }
    static fromCoord2(c) {
        return new Coord2(Vector2.fromVec2(c.offset), Vector2.fromVec2(c.scale));
    }
}
/**
 * RGB颜色
 */
class RGBColor {
    r = 0;
    g = 0;
    b = 0;
    constructor(r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }
    set(r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }
    copy(c) {
        this.r = c.r;
        this.g = c.g;
        this.b = c.b;
    }
    clone() {
        return new RGBColor(this.r, this.g, this.b);
    }
    add(c) {
        return new RGBColor(this.r + c.r, this.g + c.g, this.b + c.b);
    }
    sub(c) {
        return new RGBColor(this.r - c.r, this.g - c.g, this.b - c.b);
    }
    mul(c) {
        return new RGBColor(this.r * c.r, this.g * c.g, this.b * c.b);
    }
    div(c) {
        return new RGBColor(this.r / c.r, this.g / c.g, this.b / c.b);
    }
    addEq(c) {
        this.r += c.r;
        this.g += c.g;
        this.b += c.b;
        return this;
    }
    subEq(c) {
        this.r -= c.r;
        this.g -= c.g;
        this.b -= c.b;
        return this;
    }
    mulEq(c) {
        this.r *= c.r;
        this.g *= c.g;
        this.b *= c.b;
        return this;
    }
    divEq(c) {
        this.r /= c.r;
        this.g /= c.g;
        this.b /= c.b;
        return this;
    }
    scale(n) {
        return new RGBColor(this.r * n, this.g * n, this.b * n);
    }
    toRGB255() {
        return this.scale(255);
    }
    /**
     * 归一化函数
     * @returns {Vector2}
     */
    normalize() {
        let max = Math.max(this.r, this.g, this.b);
        return new RGBColor(this.r / max, this.g / max, this.b / max);
    }
    setVec3(vec3) {
        vec3.x = this.r;
        vec3.y = this.g;
        vec3.z = this.b;
    }
    equals(c, tolerance = 0.0001) {
        return Math.abs(c.r - this.r) <= tolerance && Math.abs(c.g - this.g) <= tolerance && Math.abs(c.b - this.b) <= tolerance;
    }
    lerp(c) {
        return this.add(c).scale(0.5);
    }
    toString() {
        return JSON.stringify(this);
    }
    toRGBA() {
        return new RGBAColor(this.r, this.g, this.b, 1);
    }
    static fromRGB255(r, g, b) {
        return new RGBColor(r / 255, g / 255, b / 255);
    }
    /**
     * 从十六进制转换颜色  
     * 要求有6位(不包括`#`),例子:`123456`、`AABBCC`、`#FEDCBA`
     * @param {string} hex 十六进制颜色
     */
    static fromHEX(hex) {
        var s;
        if (hex.length < 6)
            throwError(`无效的HEX:${hex}`);
        if (hex.startsWith('#'))
            s = hex.slice(1);
        else
            s = hex;
        return new RGBColor(parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16));
    }
    static fromRGB(c) {
        return new RGBColor(c.r, c.g, c.b);
    }
}
/**
 * RGBA颜色
 */
class RGBAColor {
    r = 0;
    g = 0;
    b = 0;
    a = 1;
    constructor(r, g, b, a) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
    }
    set(r, g, b, a) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
    }
    copy(c) {
        this.r = c.r;
        this.g = c.g;
        this.b = c.b;
        this.a = c.a;
    }
    clone() {
        return new RGBAColor(this.r, this.g, this.b, this.a);
    }
    add(c) {
        return new RGBAColor(this.r + c.r, this.g + c.g, this.b + c.b, this.a + c.a);
    }
    sub(c) {
        return new RGBAColor(this.r - c.r, this.g - c.g, this.b - c.b, this.a - c.a);
    }
    mul(c) {
        return new RGBAColor(this.r * c.r, this.g * c.g, this.b * c.b, this.a * c.a);
    }
    div(c) {
        return new RGBAColor(this.r / c.r, this.g / c.g, this.b / c.b, this.a / c.a);
    }
    addEq(c) {
        this.r += c.r;
        this.g += c.g;
        this.b += c.b;
        this.a += c.a;
        return this;
    }
    subEq(c) {
        this.r -= c.r;
        this.g -= c.g;
        this.b -= c.b;
        this.a -= c.a;
        return this;
    }
    mulEq(c) {
        this.r *= c.r;
        this.g *= c.g;
        this.b *= c.b;
        this.a *= c.a;
        return this;
    }
    divEq(c) {
        this.r /= c.r;
        this.g /= c.g;
        this.b /= c.b;
        this.a /= c.a;
        return this;
    }
    scale(n) {
        return new RGBAColor(this.r * n, this.g * n, this.b * n, this.a * n);
    }
    toRGBA255() {
        return this.scale(255);
    }
    /**
     * 归一化函数
     * @returns {Vector2}
     */
    normalize() {
        let max = Math.max(this.r, this.g, this.b, this.a);
        return new RGBAColor(this.r / max, this.g / max, this.b / max, this.a / max);
    }
    setVec3(vec3) {
        vec3.x = this.r;
        vec3.y = this.g;
        vec3.z = this.b;
    }
    equals(c, tolerance = 0.0001) {
        return Math.abs(c.r - this.r) <= tolerance && Math.abs(c.g - this.g) <= tolerance && Math.abs(c.b - this.b) <= tolerance && Math.abs(c.a - this.a) <= tolerance;
    }
    lerp(c) {
        return this.add(c).scale(0.5);
    }
    toString() {
        return JSON.stringify(this);
    }
    toRGB() {
        return new RGBColor(this.r, this.g, this.a);
    }
    static fromRGBA255(r, g, b, a) {
        return new RGBAColor(r / 255, g / 255, b / 255, a / 255);
    }
    /**
     * 从十六进制转换颜色  
     * 要求有8位(不包括`#`),例子:`12345678`、`AABBCCFF`、`#FEDCBA98`
     * @param {string} hex 十六进制颜色
     */
    static fromHEX(hex) {
        var s;
        if (hex.length < 8)
            throwError(`无效的HEX:${hex}`);
        if (hex.startsWith('#'))
            s = hex.slice(1);
        else
            s = hex;
        return new RGBAColor(parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16), parseInt(s.slice(6, 8), 16));
    }
    static fromRGBA(c) {
        return new RGBAColor(c.r, c.g, c.b, c.a);
    }
}
/**
 * 一个节点控制器  
 * 即使节点被移除,控制器仍然会保留
 */
class NodeControllers {
    /**
     * 节点id
     * @type {number}
     */
    id = -1;
    /**
     * 对应节点
     * @type {UiBox | UiInput | UiText | UiImage | null}
     */
    _uiNode = null;
    /**
     * 父节点控制器
     * @type {NodeControllers}
     */
    parent = null;
    /**
     * 创建一个节点控制器
     * @param {PackedNode | 0} packedData 服务端传过来的打包的数据,要求`"renderMessage"`协议。若为`0`,则表示根节点
     */
    constructor(packedData) {
        if (packedData === 0) {
            this.id = 0;
            this._uiNode = ui;
        } else {
            if (packedData.protocols !== "renderMessage")
                throwError("协议错误 要求:\"renderMessage\"");
            this.id = packedData.id;
            this.render(packedData);
        }
    }
    /**
     * 渲染节点
     * @param {PackedNode} packedData 服务端传过来的打包的数据,要求`"renderMessage"`协议
     */
    render(packedData) {
        if (this.id === 0)
            throw '不能渲染根节点';
        if (packedData.protocols !== "renderMessage")
            throwError("协议错误 要求:\"renderMessage\"");
        this.parent = nodeMap.get(packedData.parent);
        if (this._uiNode === null) {
            switch (packedData.type) {
                case "box":
                    this._uiNode = UiBox.create();
                    break;
                case "text":
                    this._uiNode = UiText.create();
                    break;
                case "image":
                    this._uiNode = UiImage.create();
                    break;
                case "input":
                    this._uiNode = UiInput.create();
                    break;
                default:
                    throwError(`未知节点类型:${packedData.type}`);
            }
        }
        this._uiNode.parent = this.parent._uiNode;
        this._uiNode.anchor.copy(packedData.anchor);
        this._uiNode.position.offset.copy(packedData.position.offset);
        this._uiNode.position.scale.copy(packedData.position.scale);
        this._uiNode.backgroundColor.copy(packedData.style.backgroundColor);
        this._uiNode.backgroundOpacity = packedData.style.backgroundColor.a;
        Coord2.fromCoord2(packedData.size).setCoord2(this._uiNode.size);
        this._uiNode.zIndex = packedData.style.zIndex;
        this._uiNode.autoResize = packedData.autoResize;
        this._uiNode.visible = packedData.visible;
        this._uiNode.pointerEventBehavior = packedData.pointerEventBehavior;
        switch (packedData.type) {
            case "text":
                this._uiNode.textContent = packedData.content;
                this._uiNode.textFontSize = packedData.style.textFontSize;
                this._uiNode.textColor.copy(packedData.style.textColor);
                switch (packedData.style.textXAlignment) {
                    case -1:
                        this._uiNode.textXAlignment = "Left";
                        break;
                    case 0:
                        this._uiNode.textXAlignment = "Center";
                        break;
                    case 1:
                        this._uiNode.textXAlignment = "Right";
                        break;
                    default:
                        if (typeof this._uiNode.textXAlignment === "string")
                            this._uiNode.textXAlignment = packedData.style.textXAlignment;
                        else
                            throwError(`未知的对齐方式:${JSON.stringify(packedData.style.textXAlignment)}`);
                }
                switch (packedData.style.textYAlignment) {
                    case -1:
                        this._uiNode.textYAlignment = "Top";
                        break;
                    case 0:
                        this._uiNode.textYAlignment = "Center";
                        break;
                    case 1:
                        this._uiNode.textYAlignment = "Bottom";
                        break;
                    default:
                        if (typeof this._uiNode.textYAlignment === "string")
                            this._uiNode.textYAlignment = packedData.style.textYAlignment;
                        else
                            throwError(`未知的对齐方式:${JSON.stringify(packedData.style.textXAlignment)}`);
                }
                break;
            case "image":
                this._uiNode.image = packedData.content;
                this._uiNode.imageOpacity = packedData.style.imageOpacity;
                break;
            case "input":
                this._uiNode.textContent = packedData.content;
                this._uiNode.textFontSize = packedData.style.textFontSize;
                this._uiNode.textColor.copy(packedData.style.textColor);
                switch (packedData.style.textXAlignment) {
                    case -1:
                        this._uiNode.textXAlignment = "Left";
                        break;
                    case 0:
                        this._uiNode.textXAlignment = "Center";
                        break;
                    case 1:
                        this._uiNode.textXAlignment = "Right";
                        break;
                    default:
                        if (typeof this._uiNode.textXAlignment === "string")
                            this._uiNode.textXAlignment = packedData.style.textXAlignment;
                        else
                            throwError(`未知的对齐方式:${JSON.stringify(packedData.style.textXAlignment)}`);
                }
                switch (packedData.style.textYAlignment) {
                    case -1:
                        this._uiNode.textYAlignment = "Top";
                        break;
                    case 0:
                        this._uiNode.textYAlignment = "Center";
                        break;
                    case 1:
                        this._uiNode.textYAlignment = "Bottom";
                        break;
                    default:
                        if (typeof this._uiNode.textYAlignment === "string")
                            this._uiNode.textYAlignment = packedData.style.textYAlignment;
                        else
                            throwError(`未知的对齐方式:${JSON.stringify(packedData.style.textYAlignment)}`);
                }
                this._uiNode.placeholder = packedData.placeholder;
                this._uiNode.placeholderColor.copy(packedData.style.placeholderColor);
                this._uiNode.placeholderOpacity = packedData.style.placeholderColor.a;
                break;
            case "box":
                break;
            default:
                throwError(`未知节点类型:${packedData.type}`);
                break;
        }
        try {
            if (packedData.events.onPress) {
                this._uiNode.events.on("pointerdown", (event) => {
                    /**
                     * @type {PackedClientEvent}
                     */
                    const packedData = { protocols: "triggeredEvent", eventName: EventName.onPress, node: this._pack() };
                    remoteChannel.sendServerEvent(packedData);
                });
                //return '注册onPress';
            } else {
                //this._uiNode.events.removeAll("pointerdown"); //由于Client API存在bug,暂时注释此代码
            }
            if (packedData.events.onRelease) {
                this._uiNode.events.on("pointerup", (event) => {
                    /**
                     * @type {PackedClientEvent}
                     */
                    const packedData = { protocols: "triggeredEvent", eventName: EventName.onRelease, node: this._pack() };
                    remoteChannel.sendServerEvent(packedData);
                });
            } else {
                //this._uiNode.events.removeAll("pointerup"); //由于Client API存在bug,暂时注释此代码
            }
            if (packedData.type == 'input' || packedData.type == 'text') {
                if (packedData.events.onFocus) {
                    this._uiNode.events.on("focus", () => {
                        /**
                         * @type {PackedClientEvent}
                         */
                        const packedData = { protocols: "triggeredEvent", eventName: EventName.onFocus, node: this._pack() };
                        remoteChannel.sendServerEvent(packedData);
                    });
                } else {
                    //this._uiNode.events.removeAll("focus"); //由于Client API存在bug,暂时注释此代码
                }
                if (packedData.events.onBlur) {
                    this._uiNode.events.on("blur", () => {
                        /**
                         * @type {PackedClientEvent}
                         */
                        const packedData = { protocols: "triggeredEvent", eventName: EventName.onBlur, node: this._pack() };
                        remoteChannel.sendServerEvent(packedData);
                    });
                } else {
                    //this._uiNode.events.removeAll("blur"); //由于Client API存在bug,暂时注释此代码
                }
            }
        } catch (e) {
            throwError(e.stack);
        }
    }
    remove() {
        if (this.id === 0)
            throwError('不能删除根节点');
        this._uiNode.parent = undefined;
        this._uiNode = null;
        nodeMap.delete(this.id);
    }
    /**
     * 完全重新渲染节点  
     * 节点会被移除并被重新创建  
     * 其子节点不会被移除,而是移动到新节点中  
     * 可以通过此方法完成不同类型节点的转换
     * @param {PackedNode} packedData 服务端传过来的打包的节点,要求`"renderMessage"`协议
     */
    reload(packedData) {
        const childern = this._uiNode.children;
        this._uiNode = undefined;
        this.render(packedData);
        childern.forEach(node => node.parent = this._uiNode);
    }
    /**
     * 打包节点,供事件使用
     * @returns {PackedNode}
     */
    _pack() {
        return {
            protocols: "packedNode",
            id: this.id,
            name: this.name,
            data: this.data,
            style: {
                textXAlignment: this._uiNode?.textXAlignment,
                textYAlignment: this._uiNode?.textXAlignment,
                textColor: this._uiNode?.textColor,
                backgroundColor: this._uiNode.backgroundColor,
                placeholderColor: this._uiNode?.placeholderColor,
                zIndex: this._uiNode.zIndex,
                textFontSize: this._uiNode?.textFontSize,
                imageOpacity: this._uiNode?.imageOpacity
            },
            pointerEventBehavior: this._uiNode.pointerEventBehavior,
            autoResize: this._uiNode.autoResize,
            placeholder: this._uiNode?.placeholder,
            size: this._uiNode.size,
            position: this._uiNode.position,
            anchor: this._uiNode.anchor,
            visible: this._uiNode.visible,
            parent: this.parent.id,
            content: this._uiNode?.textContent ?? this._uiNode?.image,
            type: this.type
        }
    }
}
/**
 * 抛出错误  
 * 会在页面上创建文本节点  
 * 可以通过点击节点关闭
 * @param  {...string} error 要抛出的错误
 */
function throwError(...error) {
    const errorInfo = UiText.create();
    errorInfo.textContent = `出现错误,错误信息如下:\n${'-'.repeat(50)}\n${error.join(' ')}\n${'-'.repeat(50)}\n点击可自动关闭`;
    errorInfo.textXAlignment = "Left";
    errorInfo.textYAlignment = "Top";
    errorInfo.pointerEventBehavior = 2;
    errorInfo.textColor.copy(new RGBColor(1, 0, 0).toRGB255());
    errorInfo.backgroundColor.copy(new RGBColor(0.12, 0.12, 0.12).toRGB255());
    errorInfo.backgroundOpacity = 1;
    errorInfo.size.scale.copy(new Vector2(1, 0));
    errorInfo.position.offset.copy(new Vector2(0, 0));
    errorInfo.autoResize = 'Y';
    errorInfo.name = 'errorInfo';
    errorInfo.parent = ui;
    errorInfo.events.once('pointerup', () => {
        errorInfo.parent = undefined;
    });
    throw error.join(' ');
}
/**
 * 抛出错误的另类方式
 * @param  {...string} error 要抛出的错误
 */
function BSOD(...error) {
    input.unlockPointer();
    const bsod = UiBox.create();
    bsod.pointerEventBehavior = 2;
    bsod.backgroundColor.copy(RGBColor.fromHEX('1e90ff'));
    bsod.backgroundOpacity = 1;
    bsod.size.scale.copy(new Vector2(1, 1));
    bsod.position.offset.copy(new Vector2(0, 0));
    bsod.autoResize = 'NONE';
    bsod.name = 'BSOD';
    bsod.parent = ui;
    const t1 = UiText.create();
    t1.textColor.copy(new RGBColor(1, 1, 1).toRGB255());
    t1.textFontSize = 175;
    t1.textContent = ':(';
    t1.textXAlignment = "Left";
    t1.textYAlignment = "Top";
    t1.position.offset.copy(new Vector2(140, 50));
    t1.autoResize = 'XY';
    t1.pointerEventBehavior = 1;
    t1.parent = bsod;
    const t2 = t1.clone();
    t2.position.offset.y = 300;
    t2.textFontSize = 35;
    t2.textContent = `This map ran into a problem and needs to restart. We're just collecting ${screenWidth < 2015 ? '\n' : ''}some error info, and then we'll restart for you`;
    const t3 = t2.clone();
    t3.position.offset.y = 400;
    const t4 = t3.clone();
    t4.position.offset.y = 475;
    t4.textFontSize = 17;
    t4.textContent = 'For more information about this issue and possible fixes,visit\nhttps://box3.yuque.com/staff-khn556/wupvz3';
    const t5 = t4.clone();
    t5.position.offset.x = 675;
    t5.textContent = 'if you call a support person, give them this info:';
    const errorInfo = t5.clone();
    errorInfo.position.offset.y = 500;
    errorInfo.autoWordWrap = true;
    errorInfo.autoResize = 'NONE';
    errorInfo.textFontSize = 14;
    errorInfo.textContent = error.join(' ');
    errorInfo.textLineHeight = 1;
    errorInfo.size.offset.copy(new Vector2(-700, -525));
    errorInfo.size.scale.copy(new Vector2(1, 1));
    (async () => {
        for (let i = 0; i <= 100; i += 10) {
            t3.textContent = `${i}% Completed`;
            await sleep(1000);
        }
    })();
    bsod.events.once('pointerup', () => {
        bsod.parent = undefined;
    });
    throw error.join(' ');
}
nodeMap.set(0, new NodeControllers(0));
remoteChannel.events.on("client", (/**@type {PackedData}*/event) => {
    try {
        switch (event.protocols) {
            case "renderMessage":
                if (nodeMap.has(event.id))
                    nodeMap.get(event.id).render(event);
                else
                    nodeMap.set(event.id, new NodeControllers(event));
                break;
            case "removeMessage":
                if (nodeMap.has(event.id))
                    nodeMap.get(event.id).remove();
                else
                    throw `id为 ${event.id} 的节点未被渲染过`;
                break;
            case "reloadMessage":
                if (nodeMap.has(event.id))
                    nodeMap.get(event.id).reload(event);
                else
                    throw `id为 ${event.id} 的节点未被渲染过`;
                break;
            case "bsod":
                BSOD(event.message.join(' '));
                break;
            default:
                break;
        }
    } catch (e) {
        throwError(e.stack);
    }
});
input.pointerLockEvents.on("pointerlockchange", () => {
    /**
     * @type {PackedClientEvent}
     */
    const packedData = { protocols: "triggeredEvent", eventName: EventName.onLockChange };
    remoteChannel.sendServerEvent(packedData);
});
input.pointerLockEvents.on("pointerlockerror", () => {
    /**
     * @type {PackedClientEvent}
     */
    const packedData = { protocols: "triggeredEvent", eventName: EventName.onLockError };
    remoteChannel.sendServerEvent(packedData);
});
// ----- S-C-Link_client End   -----
export { Vector2, Vector3, Coord2, RGBColor, RGBAColor, throwError, BSOD }