From de7c9abf80590746145cc157001cf54ef88d0141 Mon Sep 17 00:00:00 2001 From: ckcz123 Date: Thu, 29 Jul 2021 11:55:03 +0800 Subject: [PATCH] =?UTF-8?q?getBlockOpacity=20&=20=E5=BD=95=E5=83=8F?= =?UTF-8?q?=E5=AE=B9=E9=94=99=20&=20flag=E5=B8=8C=E8=85=8A=E5=AD=97?= =?UTF-8?q?=E6=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _docs/api.md | 16 ++++++++-------- _server/CodeMirror/defs.js | 12 ++++++------ _server/MotaAction.g4 | 4 ++-- _server/MotaActionParser.js | 16 ++++++++-------- _server/editor.js | 2 +- _server/editor_blockly.js | 4 ++-- _server/editor_ui.js | 2 +- libs/actions.js | 22 +++++++++++++++++++--- libs/control.js | 10 ++++++++++ libs/events.js | 2 +- libs/maps.js | 19 +++++++++++++------ libs/utils.js | 15 ++++++++++----- 12 files changed, 81 insertions(+), 43 deletions(-) diff --git a/_docs/api.md b/_docs/api.md index f6f162bc..6c1968a5 100644 --- a/_docs/api.md +++ b/_docs/api.md @@ -15,7 +15,7 @@ core.js中只有很少的几个函数,主要是游戏开始前的初始化等 但是,core中定义了很多游戏运行时的状态,这些状态很多都会被使用到。 ```text -core.__SIZE__, core.__PIXELS__ +core.__SIZE__, core.__PIXELS__ 游戏窗口大小;对于13x13的游戏而言这两个值分别是13和416,15x15来说分别是15和480。 @@ -73,7 +73,7 @@ core.bigmap core.bigmap.width (当前地图的宽度) core.bigmap.height (当前地图的高度) core.bigmap.offsetX (当前地图针对窗口左上角的偏移像素x) -core.bigmap.offsetX (当前地图针对窗口左上角的偏移像素y) +core.bigmap.offsetY (当前地图针对窗口左上角的偏移像素y) core.bigmap.tempCanvas (一个临时画布,可以用来临时绘制很多东西) @@ -1557,6 +1557,9 @@ floorId: 地图id,不填视为当前地图 showDisable: 隐藏点是否不返回null,true表示不返回null 返回值:图块数字,该点无图块则返回null +getBlockOpacity: fn(x: number, y: number, floorId?: string, showDisable?: bool) -> number +判定某个点的不透明度。如果该点无图块则返回null。 + getFgMapArray: fn(floorId?: string, noCache?: bool) -> [[number]] 生成前景层矩阵 例如:core.getFgMapArray('MT0'); // 生成主塔0层的前景层矩阵,使用缓存 @@ -1581,9 +1584,6 @@ floorId: 地图id,不填视为当前地图 showDisable: 可选,true表示隐藏的图块也会被表示出来 返回值:事件层矩阵,注意对其阵元的访问是[y][x] -getMapBlockOpacity: fn(floorId?: string, x?: number, y?: number, flags?: ?) -> bool -获得某个点的不透明度 - getMapBlocksObj: fn(floorId?: string, noCache?: bool) 以x,y的形式返回每个点的事件 @@ -1727,12 +1727,12 @@ x: 横坐标 y: 纵坐标 floorId: 地图id,不填视为当前地图 +setBlockOpacity: fn(opacity?: number, x?: number, y?: number, floorId?: string) +设置某个点图块的不透明度 + setMapBlockDisabled: fn(floorId?: string, x?: number, y?: number, disabled?: bool) 设置某个点图块的强制启用或禁用状态 -setMapBlockOpacity: fn(floorId?: string, x?: number, y?: number, opacity?: bool) -设置某个点图块的不透明度 - showBgFgMap: fn(name?: string, loc?: [number]|[[number]], floorId?: string, callback?: fn()) 显示前景/背景地图 diff --git a/_server/CodeMirror/defs.js b/_server/CodeMirror/defs.js index ee1975ac..2cd13ce2 100644 --- a/_server/CodeMirror/defs.js +++ b/_server/CodeMirror/defs.js @@ -3161,13 +3161,9 @@ var terndefs_f6783a0a_522d_417e_8407_94c67b692e50 = [ "!doc": "设置某个点图块的强制启用或禁用状态", "!type": "fn(floorId?: string, x?: number, y?: number, disabled?: bool)" }, - "getMapBlockOpacity": { - "!doc": "获得某个点图块的不透明度", - "!type": "fn(floorId?: string, x?: number, y?: number, flags?: ?) -> number" - }, - "setMapBlockOpacity": { + "setBlockOpacity": { "!doc": "设置某个点图块的不透明度", - "!type": "fn(floorId?: string, x?: number, y?: number, opacity?: number)" + "!type": "fn(opacity?: number, x?: number, y?: number, floorId?: string)" }, "decompressMap": { "!doc": "解压缩地图", @@ -3237,6 +3233,10 @@ var terndefs_f6783a0a_522d_417e_8407_94c67b692e50 = [ "!doc": "判定某个点的图块数字
x: 横坐标
y: 纵坐标
floorId: 地图id,不填视为当前地图
showDisable: 隐藏点是否不返回null,true表示不返回null
返回值:图块数字,该点无图块则返回null", "!type": "fn(x: number, y: number, floorId?: string, showDisable?: bool) -> number" }, + "getBlockOpacity": { + "!doc": "获得某个点图块的不透明度", + "!type": "fn(x?: number, y?: number, floorId?: string, showDisable?: bool) -> number" + }, "loadFloor": { "!doc": "从文件或存档中加载某个楼层", "!type": "fn(floorId?: string, map?: ?)" diff --git a/_server/MotaAction.g4 b/_server/MotaAction.g4 index 4b9659eb..bd662c1d 100644 --- a/_server/MotaAction.g4 +++ b/_server/MotaAction.g4 @@ -3654,7 +3654,7 @@ return ['core.getBlockId('+PosString_0+','+PosString_1+')', Blockly.JavaScript.O blockNumber_e - : '图块数字:' Int ',' Int + : '图块数字:' PosString ',' PosString /* blockNumber_e @@ -3673,7 +3673,7 @@ return ['core.getBlockNumber('+PosString_0+','+PosString_1+')', Blockly.JavaScri blockCls_e - : '图块类别:' Int ',' Int + : '图块类别:' PosString ',' PosString /* blockCls_e diff --git a/_server/MotaActionParser.js b/_server/MotaActionParser.js index 05532f6e..df120334 100644 --- a/_server/MotaActionParser.js +++ b/_server/MotaActionParser.js @@ -1174,7 +1174,7 @@ ActionParser.prototype.matchId = function(args) { } // id列表 var Id_List = MotaActionBlocks['Id_List'].options; // [["变量", "flag"], ...] - match=new RegExp('^('+Id_List.map(function(v){return v[1]}).join('|')+'):([a-zA-Z0-9_\\u4E00-\\u9FCC]+)$').exec(args[0]) + match=new RegExp('^('+Id_List.map(function(v){return v[1]}).join('|')+'):([a-zA-Z0-9_\\u4E00-\\u9FCC\\u3040-\\u30FF\\u2160-\\u216B\\u0391-\\u03C9]+)$').exec(args[0]) if(match){ if (match[1] == 'status' || match[1] == 'item') { match[2] = MotaActionFunctions.replaceToName_token(match[2]); @@ -1460,7 +1460,7 @@ MotaActionFunctions.FontString_pre = function (FontString) { } MotaActionFunctions.pattern=MotaActionFunctions.pattern||{}; -MotaActionFunctions.pattern.id=/^(flag|global|temp):([a-zA-Z0-9_\u4E00-\u9FCC]+)$/; +MotaActionFunctions.pattern.id=/^(flag|global|temp):([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)$/; MotaActionFunctions.pattern.idWithoutFlag=/^[0-9a-zA-Z_][0-9a-zA-Z_\-:]*$/; MotaActionFunctions.pattern.colorRe=/^[0-9 ]+,[0-9 ]+,[0-9 ]+(,[0-9. ]+)?$/; MotaActionFunctions.pattern.fontRe=/^(italic )?(bold )?(\d+)px ([a-zA-Z0-9_\u4E00-\u9FCC]+)$/; @@ -1488,7 +1488,7 @@ MotaActionFunctions.pattern.replaceStatusList = [ MotaActionFunctions.pattern.replaceItemList = []; for (var id in core.material.items) { var name = core.material.items[id].name; - if (id && name && name != '新物品' && /^[a-zA-Z0-9_\u4E00-\u9FCC]+$/.test(name)) { + if (id && name && name != '新物品' && /^[a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+$/.test(name)) { MotaActionFunctions.pattern.replaceItemList.push([id, name]); } } @@ -1496,7 +1496,7 @@ MotaActionFunctions.pattern.replaceStatusList = [ MotaActionFunctions.pattern.replaceEnemyList = []; for (var id in core.material.enemys) { var name = core.material.enemys[id].name; - if (id && name && name != '新敌人' && /^[a-zA-Z0-9_\u4E00-\u9FCC]+$/.test(name)) { + if (id && name && name != '新敌人' && /^[a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+$/.test(name)) { MotaActionFunctions.pattern.replaceEnemyList.push([id, name]); } } @@ -1592,14 +1592,14 @@ MotaActionFunctions.replaceFromName = function (str) { MotaActionFunctions.pattern.replaceStatusList.forEach(function (v) { map[v[1]] = v[0]; list.push(v[1]); }); - str = str.replace(new RegExp("状态[::](" + list.join("|") + ")(?:$|(?=[^\\w\\u4e00-\\u9fa5]))", "g"), function (a, b) { + str = str.replace(new RegExp("状态[::](" + list.join("|") + ")(?:$|(?=[^a-zA-Z0-9_\\u4E00-\\u9FCC\\u3040-\\u30FF\\u2160-\\u216B\\u0391-\\u03C9]))", "g"), function (a, b) { return map[b] ? ("status:" + map[b]) : b; }).replace(/状态[::]/g, "status:"); map = {}; list = []; MotaActionFunctions.pattern.replaceItemList.forEach(function (v) { map[v[1]] = v[0]; list.push(v[1]); }); - str = str.replace(new RegExp("物品[::](" + list.join("|") + ")(?:$|(?=[^\\w\\u4e00-\\u9fa5]))", "g"), function (a, b) { + str = str.replace(new RegExp("物品[::](" + list.join("|") + ")(?:$|(?=[^a-zA-Z0-9_\\u4E00-\\u9FCC\\u3040-\\u30FF\\u2160-\\u216B\\u0391-\\u03C9]))", "g"), function (a, b) { return map[b] ? ("item:" + map[b]) : b; }).replace(/物品[::]/g, "item:"); str = str.replace(/变量[::]/g, "flag:").replace(/独立开关[::]/g, "switch:").replace(/全局存储[::]/g, "global:"); @@ -1608,7 +1608,7 @@ MotaActionFunctions.replaceFromName = function (str) { MotaActionFunctions.pattern.replaceEnemyList.forEach(function (v) { map[v[1]] = v[0]; list.push(v[1]); }); - str = str.replace(new RegExp("(enemy:|怪物[::])(" + list.join("|") + ")(?:$|(?=[^\\w\\u4e00-\\u9fa5]))", "g"), function (a, b, c, d) { + str = str.replace(new RegExp("(enemy:|怪物[::])(" + list.join("|") + ")(?:$|(?=[^a-zA-Z0-9_\\u4E00-\\u9FCC\\u3040-\\u30FF\\u2160-\\u216B\\u0391-\\u03C9]))", "g"), function (a, b, c, d) { return map[c] ? ("enemy:" + map[c]) : c; }).replace(/怪物[::]/g, "enemy:"); @@ -1616,7 +1616,7 @@ MotaActionFunctions.replaceFromName = function (str) { MotaActionFunctions.pattern.replaceEnemyValueList.forEach(function (v) { map[v[1]] = v[0]; list.push(v[1]); }); - str = str.replace(new RegExp("enemy:([a-zA-Z0-9_]+)[::](" + list.join("|") + ")(?:$|(?=[^\\w\\u4e00-\\u9fa5]))", "g"), function (a, b, c, d) { + str = str.replace(new RegExp("enemy:([a-zA-Z0-9_]+)[::](" + list.join("|") + ")(?:$|(?=[^a-zA-Z0-9_\\u4E00-\\u9FCC\\u3040-\\u30FF\\u2160-\\u216B\\u0391-\\u03C9]))", "g"), function (a, b, c, d) { return map[c] ? ("enemy:" + b + ":" + map[c]) : c; }).replace(/(enemy:[a-zA-Z0-9_]+)[::]/g, '$1:'); diff --git a/_server/editor.js b/_server/editor.js index 333e938c..883d57a5 100644 --- a/_server/editor.js +++ b/_server/editor.js @@ -874,7 +874,7 @@ editor.prototype.setSelectBoxFromInfo=function(thisevent, scrollTo){ } editor.prototype.addUsedFlags = function (s) { - s.replace(/flag:([a-zA-Z0-9_\u4E00-\u9FCC]+)/g, function (s0, s1) { + s.replace(/flag:([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)/g, function (s0, s1) { editor.used_flags[s1] = true; return s0; }); s.replace(/flags\.([a-zA-Z_]\w*)/g, function (s0, s1) { diff --git a/_server/editor_blockly.js b/_server/editor_blockly.js index b9d8e421..7706820b 100644 --- a/_server/editor_blockly.js +++ b/_server/editor_blockly.js @@ -528,7 +528,7 @@ editor_blockly = function () { if (index >= 0) { var ch = content.charAt(index); var before = content.substring(0, index), token = content.substring(index+1); - if (/^[a-zA-Z0-9_\u4E00-\u9FCC]*$/.test(token)) { + if (/^[a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]*$/.test(token)) { if (before.endsWith("状态") || (ch == ':' && before.endsWith("status"))) { var list = Object.keys(core.status.hero); if (before.endsWith("状态") && MotaActionFunctions) { @@ -871,7 +871,7 @@ editor_blockly = function () { awesomplete.prefix = value; for (var i = index - 1; i>=0; i--) { var c = value.charAt(i); - if (!/^[a-zA-Z0-9_\u4E00-\u9FCC]$/.test(c)) { + if (!/^[a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]$/.test(c)) { awesomplete.prefix = value.substring(i+1); break; } diff --git a/_server/editor_ui.js b/_server/editor_ui.js index d40f35a0..59202259 100644 --- a/_server/editor_ui.js +++ b/_server/editor_ui.js @@ -689,7 +689,7 @@ editor_ui_wrapper = function (editor) { while (true) { index = obj.indexOf(flag, index + 1); if (index < 0) return false; - if (!/^[a-zA-Z0-9_\u4E00-\u9FCC]$/.test(obj.charAt(index + length))) return true; + if (!/^[a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]$/.test(obj.charAt(index + length))) return true; } } diff --git a/libs/actions.js b/libs/actions.js index 95444d31..38912e20 100644 --- a/libs/actions.js +++ b/libs/actions.js @@ -499,7 +499,15 @@ actions.prototype._sys_onmove_choices = function (x, y) { switch (core.status.event.id) { case 'action': - if (core.status.event.data.type != 'choices') break; + if (core.status.event.data.tyoe == 'choices') { + this._onMoveChoices(x, y); + return true; + } + if (core.status.event.data.type == 'confirm') { + this._onMoveConfirmBox(x, y); + return true; + } + break; case 'selectShop': case 'switchs': case 'switchs-sounds': @@ -986,7 +994,11 @@ actions.prototype._onMoveConfirmBox = function (x, y) { if (core.status.event.selection != 0) { core.status.event.selection = 0; core.playSound('光标移动'); - core.ui.drawConfirmBox(core.status.event.ui, core.status.event.data.yes, core.status.event.data.no); + if (core.status.event.id == 'action') { + core.ui.drawConfirmBox(core.status.event.ui.text); + } else { + core.ui.drawConfirmBox(core.status.event.ui, core.status.event.data.yes, core.status.event.data.no); + } } return; } @@ -994,7 +1006,11 @@ actions.prototype._onMoveConfirmBox = function (x, y) { if (core.status.event.selection != 1) { core.status.event.selection = 1; core.playSound('光标移动'); - core.ui.drawConfirmBox(core.status.event.ui, core.status.event.data.yes, core.status.event.data.no); + if (core.status.event.id == 'action') { + core.ui.drawConfirmBox(core.status.event.ui.text); + } else { + core.ui.drawConfirmBox(core.status.event.ui, core.status.event.data.yes, core.status.event.data.no); + } } return; } diff --git a/libs/control.js b/libs/control.js index f9fc5ba9..44d106a0 100644 --- a/libs/control.js +++ b/libs/control.js @@ -40,6 +40,7 @@ control.prototype._init = function () { this.registerReplayAction("moveDirectly", this._replayAction_moveDirectly); this.registerReplayAction("key", this._replayAction_key); this.registerReplayAction("click", this._replayAction_click); + this.registerReplayAction("ignoreInput", this._replayAction_ignoreInput); // --- 注册系统的resize this.registerResize("gameGroup", this._resize_gameGroup); this.registerResize("canvas", this._resize_canvas); @@ -1829,6 +1830,15 @@ control.prototype._replayAction_click = function (action) { return true; } +control.prototype._replayAction_ignoreInput = function (action) { + if (action.indexOf('input:') == 0 || action.indexOf('input2:') == 0 || action.indexOf('choices:') == 0 || action.indexOf('random:') == 0) { + console.warn('警告!录像播放中出现了未知的 ' + action + '!'); + core.replay(); + return true; + } + return false; +} + // ------ 存读档相关 ------ // ////// 自动存档 ////// diff --git a/libs/events.js b/libs/events.js index c2b91747..d79f0cd3 100644 --- a/libs/events.js +++ b/libs/events.js @@ -1396,7 +1396,7 @@ events.prototype._action_setBlockOpacity = function (data, x, y, prefix) { } else { data.loc.forEach(function (t) { - core.setMapBlockOpacity(data.floorId, t[0], t[1], data.opacity); + core.setBlockOpacity(data.opacity, t[0], t[1], data.floorId); }); core.doAction(); } diff --git a/libs/maps.js b/libs/maps.js index c9c28573..29a9e9a1 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -129,7 +129,7 @@ maps.prototype.extractBlocksForUI = function (map, flags) { var event = (floor.events || {})[j + "," + i]; if (event != null && event.enable === false) continue; } - var opacity = this.getMapBlockOpacity(floorId, j, i, flags); + var opacity = this._getBlockOpacityFromFlag(floorId, j, i, flags); if (opacity == null) { // 检查初始不透明度 var event = (floor.events || {})[j + "," + i]; @@ -186,7 +186,7 @@ maps.prototype.initBlock = function (x, y, id, addInfo, eventFloor) { var opacity = null; if (eventFloor != null) { disable = this.isMapBlockDisabled(eventFloor.floorId, x, y); - opacity = this.getMapBlockOpacity(eventFloor.floorId, x, y); + opacity = this._getBlockOpacityFromFlag(eventFloor.floorId, x, y); } var block = {'x': x, 'y': y, 'id': id}; if (disable != null) block.disable = disable; @@ -318,8 +318,7 @@ maps.prototype._processInvalidMap = function (mapArr, width, height) { return map; } -////// 获得某个点的不透明度 ////// -maps.prototype.getMapBlockOpacity = function (floorId, x, y, flags) { +maps.prototype._getBlockOpacityFromFlag = function (floorId, x, y, flags) { if (flags == null) flags = (core.status.hero || {}).flags; if (flags == null) return null; var __opacity__ = flags.__opacity__ || {}; @@ -331,7 +330,7 @@ maps.prototype.getMapBlockOpacity = function (floorId, x, y, flags) { } ////// 设置某个点的不透明度 ////// -maps.prototype.setMapBlockOpacity = function (floorId, x, y, opacity) { +maps.prototype.setBlockOpacity = function (opacity, x, y, floorId) { if (window.flags == null) return; floorId = floorId || core.status.floorId; if (!floorId) return; @@ -1716,6 +1715,14 @@ maps.prototype.getBlockCls = function (x, y, floorId, showDisable) { return block == null ? null : block.event.cls; } +////// 获得某个点的不透明度 ////// +maps.prototype.getBlockOpacity = function (x, y, floorId, showDisable) { + var block = core.getBlock(x, y, floorId, showDisable); + if (block == null) return null; + if (block.opacity == null) return 1.0; + return block.opacity == null ? 1.0 : block.opacity; +} + ////// 获得某个图块或素材的信息,包括 ID,cls,图片,坐标,faceIds 等等 ////// maps.prototype.getBlockInfo = function (block) { if (!block) return null; @@ -2544,7 +2551,7 @@ maps.prototype._animateBlock_doAnimate = function (loc, list, type, time, callba else if (type == 'hide') core.hideBlock(t[0], t[1]); else if (type == 'remove') core.removeBlock(t[0], t[1]); else { - core.setMapBlockOpacity(null, t[0], t[1], type); + core.setBlockOpacity(type, t[0], t[1]); core.showBlock(t[0], t[1]); } }); diff --git a/libs/utils.js b/libs/utils.js index 671fcadf..02f1402f 100644 --- a/libs/utils.js +++ b/libs/utils.js @@ -108,11 +108,11 @@ utils.prototype.replaceValue = function (value) { if (value.indexOf('item:') >= 0) value = value.replace(/item:([a-zA-Z0-9_]+)/g, "core.itemCount('$1')"); if (value.indexOf('flag:') >= 0 || value.indexOf('flag:') >= 0) - value = value.replace(/flag[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B]+)/g, "core.getFlag('$1', 0)"); + value = value.replace(/flag[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)/g, "core.getFlag('$1', 0)"); //if (value.indexOf('switch:' >= 0)) // value = value.replace(/switch:([a-zA-Z0-9_]+)/g, "core.getFlag('" + (prefix || ":f@x@y") + "@$1', 0)"); if (value.indexOf('global:') >= 0 || value.indexOf('global:') >= 0) - value = value.replace(/global[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B]+)/g, "core.getGlobal('$1', 0)"); + value = value.replace(/global[::]([a-zA-Z0-9_\u4E00-\u9FCC\u3040-\u30FF\u2160-\u216B\u0391-\u03C9]+)/g, "core.getGlobal('$1', 0)"); if (value.indexOf('enemy:')>=0) value = value.replace(/enemy:([a-zA-Z0-9_]+)[\.:]([a-zA-Z0-9_]+)/g, "core.material.enemys['$1'].$2"); if (value.indexOf('blockId:')>=0) @@ -330,16 +330,21 @@ utils.prototype.getGlobal = function (key, defaultValue) { var action = core.status.replay.toReplay.shift(); if (action.indexOf("input2:") == 0) { value = JSON.parse(core.decodeBase64(action.substring(7))); + core.setFlag('__global__' + key, value); + core.status.route.push("input2:" + core.encodeBase64(JSON.stringify(value))); } else { - core.control._replay_error(action); - return core.getLocalStorage(key, defaultValue); + // 录像兼容性:尝试从flag和localStorage获得 + // 注意这里不再二次记录 input2: 到录像 + core.status.replay.toReplay.unshift(action); + value = core.getFlag('__global__' + key, core.getLocalStorage(key, defaultValue)); } } else { value = core.getLocalStorage(key, defaultValue); + core.setFlag('__global__' + key, value); + core.status.route.push("input2:" + core.encodeBase64(JSON.stringify(value))); } - core.status.route.push("input2:" + core.encodeBase64(JSON.stringify(value))); return value; }