commit 3f45b47256affcf5bd643b3ef769f895a1707a6c Author: whr12386 <2654095812@qq.com> Date: Tue Jan 14 12:25:24 2025 +0800 上传文件至 / Signed-off-by: whr12386 <2654095812@qq.com> diff --git a/editor-mobile.html b/editor-mobile.html new file mode 100644 index 0000000..fa982ec --- /dev/null +++ b/editor-mobile.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+ + + + + + + 保留楼层属性 +
+ +
+
+ + + + +
+ + +
+
+
+

追加素材

+
+

+ + + + + + 自动注册 +

+

+ 色相: + +

+
+ + + + +
+
1
+
2
+
3
+
4
+
+
+
+
+
+

地图选点       +

+
+

0,0

+
+ + + + + + + + +
条目注释
+
+
+
+
+

图块属性         +

+
+
+
+ + + + + + + + +
条目注释
+
+
+ + + + +
+
+
+ + + +
+ + + +
+
+ + + + +
+
+
+
+

楼层属性         +

+
+
+ + + + + + + + +
条目注释
+
+
+ + +
+
+ 修改地图大小:宽,高, + 偏移x y + +
+
+
+
+

全塔属性       +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+
+

事件编辑器    + + + + + + +
+ + +
+ + + + 开启中文名替换 + + 展开值块逻辑运算 + +

+
+
+
+
+ +
+
+
+ +
+
+ + + + + + + 语法检查 + + 字体大小 + + 字体加粗 + +
+ +
+
+

脚本编辑     +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+

公共事件         +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+

插件编写         +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+
+
+
+ + + +
+ +
+ + +
+
+
+
+ + + +
+ + +
+ + + 通行度 + + + +
+ + + + + +
+ + + + + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor.html b/editor.html new file mode 100644 index 0000000..9d27fab --- /dev/null +++ b/editor.html @@ -0,0 +1,485 @@ + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+ + + + + + + + 保留楼层属性 +
+
+ + + + +
+ + +
+
+
+

追加素材

+
+

+ + + + + +   自动注册 +

+

从V2.7.1开始,你可以直接将素材图片拖到对应的素材区,将自动追加并注册。同时,4x4的道具素材已支持快速追加一次16个。

+

+ 色相: + +

+
+ + + + +
+
1
+
2
+
3
+
4
+
+
+
+
+
+

地图选点       +

+
+

0,0

+
+ + + + + + + + +
条目注释
+
+
+
+
+

图块属性         +

+
+
+
+ + + + + + + + +
条目注释
+
+
+ + + + +
+
+
+ + + +
+ + + +
+
+ + + + +
+
+
+
+

楼层属性         +

+
+
+ + + + + + + + +
条目注释
+
+
+ + +
+
+ 修改地图大小:宽,高, + 偏移x y + +
+
+
+
+

全塔属性       +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+
+

事件编辑器    + + + + + +
+ + +
+ + + + 开启中文名替换 + + 展开值块逻辑运算 + +

+
+
+
+
+ +
+
+
+ +
+
+ + + + + + + 语法检查 + + 字体大小 + + 字体加粗 + +
+ +
+
+

脚本编辑     +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+

公共事件         +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+

插件编写         +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+
+
+
+ + + +
+
+
+ + 通行度 + +
+ + 线 + 矩形 + tile平铺 + 填充 + + +
+ + 背景层 + 事件层 + 前景层 + +
+
+ + + + + +
+ + + + + + +
+
+
+

(Ctrl+滚轮放缩,右键置顶)

+
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..538efc2 --- /dev/null +++ b/index.html @@ -0,0 +1,208 @@ + + + + + + + + + HTML5魔塔 + + + + + + + + + + + + +
+
+ +
+ + +
+

请稍候...

+ +
+
+
+
+
+

资源即将开始加载

+

HTML5魔塔游戏平台,享受更多魔塔游戏:
https://h5mota.com/

+
+ + +
+
+ 开始游戏 + 载入游戏 + 录像回放 +
+
+
+
+
+

+

+

+
+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ + + + +
+
+ + + +
+
+ + + +
+ + + +
+
+ + + + + + + + + + + + + + + + +

+
+
+
+
+ + + + + + + + + + 此浏览器不支持HTML5 + +
+
+
+
+

请输入文字...

+ + + +
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..96da102 Binary files /dev/null and b/logo.png differ diff --git a/main.js b/main.js new file mode 100644 index 0000000..004f76f --- /dev/null +++ b/main.js @@ -0,0 +1,996 @@ +/// +function main() { + //------------------------ 用户修改内容 ------------------------// + + this.version = '2.10.3'; // 游戏版本号;如果更改了游戏内容建议修改此version以免造成缓存问题。 + + this.useCompress = false; // 是否使用压缩文件 + // 当你即将发布你的塔时,请使用“JS代码压缩工具”将所有js代码进行压缩,然后将这里的useCompress改为true。 + // 请注意,只有useCompress是false时才会读取floors目录下的文件,为true时会直接读取libs目录下的floors.min.js文件。 + // 如果要进行剧本的修改请务必将其改成false。 + + this.bgmRemote = false; // 是否采用远程BGM + this.bgmRemoteRoot = 'https://h5mota.com/music/'; // 远程BGM的根目录 + + this.isCompetition = false; // 是否是比赛模式 + + this.savePages = 1000; // 存档页数,每页可存5个;默认为1000页5000个存档 + this.criticalUseLoop = 1; // 循环临界的分界 + + //------------------------ 用户修改内容 END ------------------------// + + this.dom = { + body: document.body, + gameGroup: document.getElementById('gameGroup'), + mainTips: document.getElementById('mainTips'), + musicBtn: document.getElementById('musicBtn'), + enlargeBtn: document.createElement('img'), + startPanel: document.getElementById('startPanel'), + startTop: document.getElementById('startTop'), + startTopProgressBar: document.getElementById('startTopProgressBar'), + startTopProgress: document.getElementById('startTopProgress'), + startTopLoadTips: document.getElementById('startTopLoadTips'), + startBackground: document.getElementById('startBackground'), + startLogo: document.getElementById('startLogo'), + startButtonGroup: document.getElementById('startButtonGroup'), + floorMsgGroup: document.getElementById('floorMsgGroup'), + logoLabel: document.getElementById('logoLabel'), + versionLabel: document.getElementById('versionLabel'), + floorNameLabel: document.getElementById('floorNameLabel'), + statusBar: document.getElementById('statusBar'), + status: document.getElementsByClassName('status'), + toolBar: document.getElementById('toolBar'), + tools: document.getElementsByClassName('tools'), + gameCanvas: document.getElementsByClassName('gameCanvas'), + gif: document.getElementById('gif'), + gif2: document.getElementById('gif2'), + gameDraw: document.getElementById('gameDraw'), + startButtons: document.getElementById('startButtons'), + playGame: document.getElementById('playGame'), + loadGame: document.getElementById('loadGame'), + replayGame: document.getElementById('replayGame'), + levelChooseButtons: document.getElementById('levelChooseButtons'), + data: document.getElementById('data'), + statusLabels: document.getElementsByClassName('statusLabel'), + statusTexts: document.getElementsByClassName('statusText'), + floorCol: document.getElementById('floorCol'), + nameCol: document.getElementById('nameCol'), + lvCol: document.getElementById('lvCol'), + hpmaxCol: document.getElementById('hpmaxCol'), + hpCol: document.getElementById('hpCol'), + manaCol: document.getElementById('manaCol'), + atkCol: document.getElementById('atkCol'), + defCol: document.getElementById('defCol'), + mdefCol: document.getElementById('mdefCol'), + moneyCol: document.getElementById('moneyCol'), + expCol: document.getElementById('expCol'), + upCol: document.getElementById('upCol'), + keyCol: document.getElementById('keyCol'), + pzfCol: document.getElementById('pzfCol'), + debuffCol: document.getElementById('debuffCol'), + skillCol: document.getElementById('skillCol'), + hard: document.getElementById('hard'), + statusCanvas: document.getElementById('statusCanvas'), + statusCanvasCtx: document + .getElementById('statusCanvas') + .getContext('2d'), + inputDiv: document.getElementById('inputDiv'), + inputMessage: document.getElementById('inputMessage'), + inputBox: document.getElementById('inputBox'), + inputYes: document.getElementById('inputYes'), + inputNo: document.getElementById('inputNo'), + next: document.getElementById('next') + }; + this.mode = 'play'; + this.loadList = [ + 'loader', + 'control', + 'utils', + 'items', + 'icons', + 'maps', + 'enemys', + 'events', + 'actions', + 'data', + 'ui', + 'extensions', + 'core' + ]; + this.pureData = [ + 'data', + 'enemys', + 'icons', + 'maps', + 'items', + 'functions', + 'events', + 'plugins' + ]; + this.materials = [ + 'animates', + 'enemys', + 'items', + 'npcs', + 'terrains', + 'enemy48', + 'npc48', + 'icons' + ]; + + this.statusBar = { + image: { + floor: document.getElementById('img-floor'), + name: document.getElementById('img-name'), + lv: document.getElementById('img-lv'), + hpmax: document.getElementById('img-hpmax'), + hp: document.getElementById('img-hp'), + mana: document.getElementById('img-mana'), + atk: document.getElementById('img-atk'), + def: document.getElementById('img-def'), + mdef: document.getElementById('img-mdef'), + money: document.getElementById('img-money'), + exp: document.getElementById('img-exp'), + up: document.getElementById('img-up'), + skill: document.getElementById('img-skill'), + book: document.getElementById('img-book'), + fly: document.getElementById('img-fly'), + toolbox: document.getElementById('img-toolbox'), + keyboard: document.getElementById('img-keyboard'), + shop: document.getElementById('img-shop'), + save: document.getElementById('img-save'), + load: document.getElementById('img-load'), + settings: document.getElementById('img-settings'), + btn1: document.getElementById('img-btn1'), + btn2: document.getElementById('img-btn2'), + btn3: document.getElementById('img-btn3'), + btn4: document.getElementById('img-btn4'), + btn5: document.getElementById('img-btn5'), + btn6: document.getElementById('img-btn6'), + btn7: document.getElementById('img-btn7'), + btn8: document.getElementById('img-btn8') + }, + icons: { + floor: 0, + name: null, + lv: 1, + hpmax: 2, + hp: 3, + atk: 4, + def: 5, + mdef: 6, + money: 7, + exp: 8, + up: 9, + book: 10, + fly: 11, + toolbox: 12, + keyboard: 13, + shop: 14, + save: 15, + load: 16, + settings: 17, + play: 18, + pause: 19, + stop: 20, + speedDown: 21, + speedUp: 22, + rewind: 23, + equipbox: 24, + mana: 25, + skill: 26, + btn1: 27, + btn2: 28, + btn3: 29, + btn4: 30, + btn5: 31, + btn6: 32, + btn7: 33, + btn8: 34 + }, + floor: document.getElementById('floor'), + name: document.getElementById('name'), + lv: document.getElementById('lv'), + hpmax: document.getElementById('hpmax'), + hp: document.getElementById('hp'), + mana: document.getElementById('mana'), + atk: document.getElementById('atk'), + def: document.getElementById('def'), + mdef: document.getElementById('mdef'), + money: document.getElementById('money'), + exp: document.getElementById('exp'), + up: document.getElementById('up'), + skill: document.getElementById('skill'), + yellowKey: document.getElementById('yellowKey'), + blueKey: document.getElementById('blueKey'), + redKey: document.getElementById('redKey'), + greenKey: document.getElementById('greenKey'), + poison: document.getElementById('poison'), + weak: document.getElementById('weak'), + curse: document.getElementById('curse'), + pickaxe: document.getElementById('pickaxe'), + bomb: document.getElementById('bomb'), + fly: document.getElementById('fly'), + hard: document.getElementById('hard') + }; + this.floors = {}; + this.canvas = {}; + + this.__VERSION__ = '2.10.3'; + this.__VERSION_CODE__ = 512; +} + +main.prototype.init = function (mode, callback) { + for (var i = 0; i < main.dom.gameCanvas.length; i++) { + main.canvas[main.dom.gameCanvas[i].id] = + main.dom.gameCanvas[i].getContext('2d'); + } + main.mode = mode; + + main.loadJs('project', main.pureData, function () { + var mainData = data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main; + for (var ii in mainData) main[ii] = mainData[ii]; + + main.dom.startLogo.style = main.styles.startLogoStyle; + main.dom.startButtonGroup.style = main.styles.startButtonsStyle; + main.levelChoose = main.levelChoose || []; + main.levelChoose.forEach(function (value) { + var span = document.createElement('span'); + span.setAttribute('class', 'startButton'); + span.innerText = value.title || ''; + (function (span, str_) { + span.onclick = function () { + core.events.startGame(str_); + }; + })(span, value.name || ''); + main.dom.levelChooseButtons.appendChild(span); + }); + main.createOnChoiceAnimation(); + main.importFonts(main.fonts); + + main.loadJs('libs', main.loadList, function () { + main.core = core; + + for (i = 0; i < main.loadList.length; i++) { + var name = main.loadList[i]; + if (name === 'core') continue; + main.core[name] = new window[name](); + } + + main.loadFloors(function () { + var coreData = {}; + [ + 'dom', + 'statusBar', + 'canvas', + 'images', + 'tilesets', + 'materials', + 'animates', + 'bgms', + 'sounds', + 'floorIds', + 'floors', + 'floorPartitions' + ].forEach(function (t) { + coreData[t] = main[t]; + }); + main.core.init(coreData, callback); + main.core.resize(); + // 自动放缩最大化 + if (!main.replayChecking) { + return; + if (core.getLocalStorage('autoScale') == null) { + core.setLocalStorage('autoScale', true); + } + if ( + core.getLocalStorage('autoScale') && + !core.domStyle.isVertical + ) { + try { + if (main.core) { + var index = + main.core.domStyle.availableScale.indexOf( + core.domStyle.scale + ); + main.core.control.setDisplayScale( + main.core.domStyle.availableScale.length - + 1 - + index + ); + if ( + !main.core.isPlaying() && + main.core.flags.enableHDCanvas + ) { + main.core.domStyle.ratio = Math.max( + window.devicePixelRatio || 1, + main.core.domStyle.scale + ); + main.core.resize(); + } + requestAnimationFrame(function () { + var style = getComputedStyle( + main.dom.gameGroup + ); + var height = parseFloat(style.height); + if (height > window.innerHeight * 0.95) { + main.core.control.setDisplayScale(-1); + if ( + !main.core.isPlaying() && + main.core.flags.enableHDCanvas + ) { + main.core.domStyle.ratio = Math.max( + window.devicePixelRatio || 1, + main.core.domStyle.scale + ); + main.core.resize(); + } + } + }); + } + } catch (e) { + console.error(e); + } + } + } + }); + }); + }); +}; + +////// 动态加载所有核心JS文件 ////// +main.prototype.loadJs = function (dir, loadList, callback) { + // 加载js + main.setMainTipsText('正在加载核心js文件...'); + + if (this.useCompress) { + main.loadMod(dir, dir, function () { + callback(); + }); + } else { + var instanceNum = 0; + for (var i = 0; i < loadList.length; i++) { + main.loadMod(dir, loadList[i], function (modName) { + main.setMainTipsText(modName + '.js 加载完毕'); + instanceNum++; + if (instanceNum === loadList.length) { + callback(); + } + }); + } + } +}; + +////// 加载某一个JS文件 ////// +main.prototype.loadMod = function (dir, modName, callback, onerror) { + var script = document.createElement('script'); + var name = modName; + script.src = + dir + + '/' + + modName + + (this.useCompress ? '.min' : '') + + '.js?v=' + + this.version; + script.onload = function () { + callback(name); + }; + main.dom.body.appendChild(script); +}; + +////// 动态加载所有楼层(剧本) ////// +main.prototype.loadFloors = function (callback) { + // 加载js + main.setMainTipsText('正在加载楼层文件...'); + if (this.useCompress) { + // 读取压缩文件 + var script = document.createElement('script'); + script.src = 'project/floors.min.js?v=' + this.version; + main.dom.body.appendChild(script); + script.onload = function () { + main.dom.mainTips.style.display = 'none'; + callback(); + }; + return; + } + + // 高层塔优化 + var script = document.createElement('script'); + script.src = + '__all_floors__.js?v=' + + this.version + + '&id=' + + main.floorIds.join(','); + script.onload = function () { + main.dom.mainTips.style.display = 'none'; + main.supportBunch = true; + callback(); + }; + script.onerror = + script.onabort = + script.ontimeout = + function (e) { + // console.clear(); + for (var i = 0; i < main.floorIds.length; i++) { + main.loadFloor(main.floorIds[i], function (modName) { + main.setMainTipsText( + '楼层 ' + modName + '.js 加载完毕' + ); + if ( + Object.keys(main.floors).length === + main.floorIds.length + ) { + main.dom.mainTips.style.display = 'none'; + callback(); + } + }); + } + }; + main.dom.body.appendChild(script); +}; + +////// 加载某一个楼层 ////// +main.prototype.loadFloor = function (floorId, callback) { + var script = document.createElement('script'); + script.src = 'project/floors/' + floorId + '.js?v=' + this.version; + main.dom.body.appendChild(script); + script.onload = function () { + callback(floorId); + }; +}; + +////// 加载过程提示 ////// +main.prototype.setMainTipsText = function (text) { + main.dom.mainTips.innerHTML = text; +}; + +main.prototype.log = function (e, error) { + if (e) { + if (error) return console.error(e); + if (main.core && main.core.platform && !main.core.platform.isPC) { + console.log(e.stack || e.toString()); + } else { + console.log(e); + } + } +}; + +main.prototype.createOnChoiceAnimation = function () { + var borderColor = + main.dom.startButtonGroup.style.caretColor || 'rgb(255, 215, 0)'; + // get rgb value + var rgb = + /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*\d+\s*)?\)$/.exec( + borderColor + ); + if (rgb != null) { + var value = rgb[1] + ', ' + rgb[2] + ', ' + rgb[3]; + var style = document.createElement('style'); + style.type = 'text/css'; + var keyFrames = + 'onChoice { ' + + '0% { border-color: rgba(' + + value + + ', 0.9); } ' + + '50% { border-color: rgba(' + + value + + ', 0.3); } ' + + '100% { border-color: rgba(' + + value + + ', 0.9); } ' + + '}'; + style.innerHTML = + '@-webkit-keyframes ' + keyFrames + ' @keyframes ' + keyFrames; + document.body.appendChild(style); + } +}; + +////// 选项 ////// +main.prototype.selectButton = function (index) { + var select = function (children) { + index = (index + children.length) % children.length; + for (var i = 0; i < children.length; ++i) { + children[i].classList.remove('onChoiceAnimate'); + } + children[index].classList.add('onChoiceAnimate'); + if (main.selectedButton == index) { + children[index].click(); + } else { + main.selectedButton = index; + } + }; + + if (core.dom.startPanel.style.display != 'block') return; + + if (main.dom.startButtons.style.display == 'block') { + select(main.dom.startButtons.children); + } else if (main.dom.levelChooseButtons.style.display == 'block') { + select(main.dom.levelChooseButtons.children); + } +}; + +////// 创建字体 ////// +main.prototype.importFonts = function (fonts) { + if (!(fonts instanceof Array) || fonts.length == 0) return; + var style = document.createElement('style'); + style.type = 'text/css'; + var html = ''; + fonts.forEach(function (font) { + html += + '@font-face { font-family: "' + + font + + '"; src: url("project/fonts/' + + font + + '.ttf") format("truetype"); }'; + }); + style.innerHTML = html; + document.body.appendChild(style); +}; + +main.prototype.listen = function () { + ////// 窗口大小变化时 ////// + window.onresize = function () { + try { + main.core.resize(); + } catch (ee) { + console.error(ee); + } + }; + + ////// 在界面上按下某按键时 ////// + main.dom.body.onkeydown = function (e) { + if (main.editorOpened) return; + try { + if (main.dom.inputDiv.style.display == 'block') return; + if ( + main.core && + (main.core.isPlaying() || main.core.status.lockControl) + ) + main.core.onkeyDown(e); + } catch (ee) { + console.error(ee); + } + }; + + ////// 在界面上放开某按键时 ////// + main.dom.body.onkeyup = function (e) { + if (main.editorOpened) return; + try { + if ( + main.dom.startPanel.style.display == 'block' && + (main.dom.startButtons.style.display == 'block' || + main.dom.levelChooseButtons.style.display == 'block') + ) { + if (e.keyCode == 38 || e.keyCode == 33) + // up/pgup + main.selectButton((main.selectedButton || 0) - 1); + else if (e.keyCode == 40 || e.keyCode == 34) + // down/pgdn + main.selectButton((main.selectedButton || 0) + 1); + else if (e.keyCode == 67 || e.keyCode == 13 || e.keyCode == 32) + // C/Enter/Space + main.selectButton(main.selectedButton); + else if ( + e.keyCode == 27 && + main.dom.levelChooseButtons.style.display == 'block' + ) { + // ESC + main.core.showStartAnimate(true); + } + e.stopPropagation(); + return; + } + if (main.dom.inputDiv.style.display == 'block') { + if (e.keyCode == 13) { + setTimeout(function () { + main.dom.inputYes.click(); + }, 50); + } else if (e.keyCode == 27) { + setTimeout(function () { + main.dom.inputNo.click(); + }, 50); + } + return; + } + if ( + main.core && + main.core.isPlaying && + main.core.status && + (main.core.isPlaying() || main.core.status.lockControl) + ) + main.core.onkeyUp(e); + } catch (ee) { + console.error(ee); + } + }; + + [main.dom.startButtons, main.dom.levelChooseButtons].forEach(function ( + dom + ) { + dom.onmousemove = function (e) { + for (var i = 0; i < dom.children.length; ++i) { + if ( + dom.children[i] == e.target && + i != (main.selectedButton || 0) + ) { + main.selectButton(i); + } + } + }; + }); + + ////// 开始选择时 ////// + main.dom.body.onselectstart = function () { + return false; + }; + + ////// 鼠标按下时 ////// + main.dom.data.onmousedown = function (e) { + try { + e.stopPropagation(); + var loc = main.core.actions._getClickLoc(e.clientX, e.clientY); + if (loc == null) return; + main.core.ondown(loc); + } catch (ee) { + console.error(ee); + } + }; + + ////// 鼠标移动时 ////// + main.dom.data.onmousemove = function (e) { + try { + e.stopPropagation(); + var loc = main.core.actions._getClickLoc(e.clientX, e.clientY); + if (loc == null) return; + main.core.onmove(loc); + } catch (ee) { + console.error(ee); + } + }; + + ////// 鼠标放开时 ////// + main.dom.data.onmouseup = function (e) { + try { + e.stopPropagation(); + var loc = main.core.actions._getClickLoc(e.clientX, e.clientY); + if (loc == null) return; + main.core.onup(loc); + } catch (ee) { + console.error(ee); + } + }; + + ////// 鼠标滑轮滚动时 ////// + main.dom.data.onmousewheel = function (e) { + try { + if (e.wheelDelta) main.core.onmousewheel(Math.sign(e.wheelDelta)); + else if (e.detail) main.core.onmousewheel(Math.sign(e.detail)); + } catch (ee) { + console.error(ee); + } + }; + + ////// 手指在触摸屏开始触摸时 ////// + main.dom.data.ontouchstart = function (e) { + try { + e.preventDefault(); + var loc = main.core.actions._getClickLoc( + e.targetTouches[0].clientX, + e.targetTouches[0].clientY + ); + if (loc == null) return; + main.lastTouchLoc = loc; + main.core.ondown(loc); + } catch (ee) { + console.error(ee); + } + }; + + ////// 手指在触摸屏上移动时 ////// + main.dom.data.ontouchmove = function (e) { + try { + e.preventDefault(); + var loc = main.core.actions._getClickLoc( + e.targetTouches[0].clientX, + e.targetTouches[0].clientY + ); + if (loc == null) return; + main.lastTouchLoc = loc; + main.core.onmove(loc); + } catch (ee) { + console.error(ee); + } + }; + + ////// 手指离开触摸屏时 ////// + main.dom.data.ontouchend = function (e) { + try { + e.preventDefault(); + if (main.lastTouchLoc == null) return; + var loc = main.lastTouchLoc; + delete main.lastTouchLoc; + main.core.onup(loc); + } catch (e) { + console.error(e); + } + }; + + main.dom.statusCanvas.onclick = function (e) { + try { + e.preventDefault(); + main.core.onStatusBarClick(e); + } catch (e) { + console.error(e); + } + }; + + ////// 点击状态栏中的怪物手册时 ////// + main.statusBar.image.book.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.triggerReplay(); + return; + } + + if (main.core.isPlaying()) main.core.openBook(true); + }; + + ////// 点击状态栏中的楼层传送器/装备栏时 ////// + main.statusBar.image.fly.onclick = function (e) { + e.stopPropagation(); + + // 播放录像时 + if (core.isReplaying()) { + core.stopReplay(); + return; + } + + if (main.core.isPlaying()) { + if (!main.core.flags.equipboxButton) { + main.core.useFly(true); + } else { + main.core.openEquipbox(true); + } + } + }; + + ////// 点击状态栏中的工具箱时 ////// + main.statusBar.image.toolbox.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.rewindReplay(); + return; + } + + if (main.core.isPlaying()) { + main.core.openToolbox(core.status.event.id != 'equipbox'); + } + }; + + ////// 双击状态栏中的工具箱时 ////// + main.statusBar.image.toolbox.ondblclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + return; + } + + if (main.core.isPlaying()) main.core.openEquipbox(true); + }; + + ////// 点击状态栏中的虚拟键盘时 ////// + main.statusBar.image.keyboard.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.control._replay_book(); + return; + } + + if (main.core.isPlaying()) main.core.openKeyBoard(true); + }; + + ////// 点击状态栏中的快捷商店时 ////// + main.statusBar.image.shop.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.control._replay_viewMap(); + return; + } + + if (main.core.isPlaying()) main.core.openQuickShop(true); + }; + + ////// 点击金币时也可以开启快捷商店 ////// + main.statusBar.image.money.onclick = function (e) { + e.stopPropagation(); + + if (main.core.isPlaying()) main.core.openQuickShop(true); + }; + + ////// 点击楼梯图标也可以浏览地图 ////// + main.statusBar.image.floor.onclick = function (e) { + e.stopPropagation(); + + if ( + main.core && + main.core.isPlaying() && + !core.isMoving() && + !core.status.lockControl + ) { + core.ui._drawViewMaps(); + } + }; + + ////// 点击状态栏中的存档按钮时 ////// + main.statusBar.image.save.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.speedDownReplay(); + return; + } + + if (main.core.isPlaying()) main.core.save(true); + }; + + ////// 点击状态栏中的读档按钮时 ////// + main.statusBar.image.load.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.speedUpReplay(); + return; + } + + if (main.core.isPlaying()) main.core.load(true); + }; + + ////// 点击状态栏中的系统菜单时 ////// + main.statusBar.image.settings.onclick = function (e) { + e.stopPropagation(); + + if (core.isReplaying()) { + core.control._replay_SL(); + return; + } + + if (main.core.isPlaying()) main.core.openSettings(true); + }; + + ////// 点击工具栏时 ////// + main.dom.hard.onclick = function () { + main.core.control.setToolbarButton(!core.domStyle.toolbarBtn); + }; + + ////// 手机端的按钮1-7 ////// + main.statusBar.image.btn1.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 49, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn2.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 50, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn3.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 51, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn4.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 52, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn5.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 53, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn6.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 54, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn7.onclick = function (e) { + e.stopPropagation(); + main.core.onkeyUp({ + keyCode: 55, + altKey: core.getLocalStorage('altKey') + }); + }; + + main.statusBar.image.btn8.onclick = function (e) { + e.stopPropagation(); + if (core.getLocalStorage('altKey')) { + core.removeLocalStorage('altKey'); + core.drawTip('Alt模式已关闭。'); + main.statusBar.image.btn8.style.filter = ''; + } else { + core.setLocalStorage('altKey', true); + core.drawTip('Alt模式已开启;此模式下1~7按钮视为Alt+1~7。'); + main.statusBar.image.btn8.style.filter = 'sepia(1) contrast(1.5)'; + } + }; + + ////// 点击“开始游戏”时 ////// + main.dom.playGame.onclick = function () { + main.dom.startButtons.style.display = 'none'; + main.core.control.checkBgm(); + + if (main.levelChoose.length == 0) { + core.events.startGame(''); + } else { + main.dom.levelChooseButtons.style.display = 'block'; + main.selectedButton = null; + main.selectButton(0); + } + }; + + ////// 点击“载入游戏”时 ////// + main.dom.loadGame.onclick = function () { + main.core.control.checkBgm(); + main.core.load(); + }; + + ////// 点击“录像回放”时 ////// + main.dom.replayGame.onclick = function () { + main.core.control.checkBgm(); + main.core.chooseReplayFile(); + }; + + main.dom.musicBtn.onclick = function () { + try { + if (main.core) main.core.triggerBgm(); + } catch (ee) { + console.error(ee); + } + }; + + window.onblur = function () { + if (main.core && main.core.control) { + try { + main.core.control.checkAutosave(); + } catch (e) {} + } + }; + + main.dom.inputYes.onclick = function () { + main.dom.inputDiv.style.display = 'none'; + var func = core.platform.successCallback; + core.platform.successCallback = core.platform.errorCallback = null; + if (func) func(main.dom.inputBox.value); + }; + + main.dom.inputNo.onclick = function () { + main.dom.inputDiv.style.display = 'none'; + var func = core.platform.errorCallback; + core.platform.successCallback = core.platform.errorCallback = null; + if (func) func(null); + }; +}; //listen end + +var main = new main(); diff --git a/runtime.d.ts b/runtime.d.ts new file mode 100644 index 0000000..8833c22 --- /dev/null +++ b/runtime.d.ts @@ -0,0 +1,3052 @@ +/** + * @file runtime.d.ts 运行时的类型标注 + * @author 秋橙 & tocque + */ + +interface TextContentConfig { + left?: number + top?: number + maxWidth?: number + color?: rgbarray | string + align?: 'left' | 'center' | 'right' + fontSize: number + lineHeight?: number + time?: number + font?: string + letterSpacing?: number + bold?: boolean + italic?: boolean +} + +type direction = 'up' | 'down' | 'left' | 'right' +type move = 'forward' | direction +type loc = { direction: direction, x: number, y: number } +type rgbarray = [number, number, number, number] + +type Events = MotaAction[] | string + +type Block = { + x: number, + y: number, + id: number, + event: { + cls: string, + id: string, + [key: string]: any + } +} + +type frameObj = { + angle: number + index: number + mirror: number + opacity: number + x: number + y: number + zoom: number +} + +type CtxRefer = string | CanvasRenderingContext2D | HTMLCanvasElement | HTMLImageElement + +type Animate = { + frame: number + frames: frameObj[][] + images: HTMLImageElement[] + ratio: number + se: string +} + +type Floor = { + title: string, + ratio: number +} + +type ResolvedMap = { + floorId: string + afterBattle: { [x: string]: Events } + afterOpenDoor: { [x: string]: Events } + afterGetItem: { [x: string]: Events } + autoEvent: Event + beforeBattle: { [x: string]: Events } + canFlyFrom: boolean + canFltTo: boolean + canUseQuickShop: boolean + cannotMove: Object + cannotMoveIn: Object + cannotViewMap: boolean + changeFloor: { + [x: string]: { + floorId: ':before' | ':after' | ':now' | string + loc?: [number, number] + stair?: 'upFloor' | 'downFloor' | ':symmetry' | ':symmetry_x' | ':symmetry_y' | 'flyPoint' + direction?: 'left' | 'right' | 'up' | 'down' | ':left' | ':right' | ':back' | ':hero' | ':backhero' + time?: number + ignoreChangeFloor?: boolean + } + } + defaultGround: string + bgm: string | Array + bgmap: number[][] + /** 事件层 */ + map: number[][] + fgmap: number[][] + width: number + height: number + images: Array<{ + canvas: 'bg' | 'auto' | 'fg' + name: string + x: number + y: number + reverse?: ':x' | ':y' | ':o' + disable?: boolean + sx?: number + sy?: number + w?: number + h?: number + frame?: number + }> + name: string + ratio: number + title: string + weather: [string, number] + blocks: Array +} + +type Enemy = { + id: string + name: string + displayIdInBook: string + special: number | number[] + hp: number + atk: number + def: number + money: number + exp: number + point: number + [key: string]: any +} + +type Item = { + cls: string + [key: string]: any +} + +type Save = { + +} + +type MotaAction = { + type: string, + [key: string]: any +} | string + +type SystemFlags = { + enableXxx: boolean + flyNearStair: boolean + steelDoorWithoutKey: boolean + betweenAttackMax: boolean + ignoreChangeFloor: boolean + disableShopOnDamage: boolean + blurFg: boolean +} + +type event = { type: string, [key: string]: any } + +type step = 'up' | 'down' | 'left' | 'right' | 'forward' | 'backward' + +type HeroStatus = { + equipment: [] + lv: number + name: string + hp: number + hpmax: number + mana: number + manamax: number + atk: number + def: number + mdef: number + money: number + exp: number + loc: { + direction: direction + x: number + y: number + } + items: { + keys: { [key: string]: number } + constants: { [key: string]: number } + tools: { [key: string]: number } + equips: { [key: string]: number } + } + flags: { [key: string]: any } + steps: number + statistics: { + battle: number + battleDamage: number + currTime: number + exp: number + extraDamage: number + hp: number + ignoreSteps: number + money: number + moveDirectly: number + poisonDamage: number + start: number + totalTime: number + } + [key: string]: any +} + +type gameStatus = { + played: boolean + gameOver: boolean + + /** 当前勇士状态信息。例如core.status.hero.atk就是当前勇士的攻击力数值 */ + hero: HeroStatus + + /** 当前层的floorId */ + floorId: string + /** 获得所有楼层的地图信息 */ + maps: { [key: string]: ResolvedMap } + /** 获得当前楼层信息,等价于core.status.maps[core.status.floorId] */ + thisMap: ResolvedMap + bgmaps: { [key: string]: number[][] } + fgmaps: { [key: string]: number[][] } + mapBlockObjs: { [key: string]: any } + /** 显伤伤害 */ + checkBlock: { + ambush: { [x: string]: [number, number, string, direction] } + repulse: { [x: string]: [number, number, string, direction] } + damage: { [x: string]: number } + needCache: boolean + type: { [x: string]: { [x: string]: boolean } } + cache: { + [s: string]: { + hp_buff: number + atk_buff: number + def_buff: number + guards: Array<[number, number, string]> + } + } + } + damage: { + posX: number + posY: number + data: Array<{ + [x: string]: { + text: string + px: number + py: number + color: string | Array + } + }> + extraData: Array<{ + [x: string]: { + text: string + px: number + py: number + color: string | Array + alpha: number + } + }> + } + + lockControl: boolean + + /** 勇士移动状态 */ + heroMoving: number + heroStop: boolean + + // 自动寻路相关 + automaticRoute: { + autoHeroMove: boolean + autoStep: number + movedStep: number + destStep: number + destX: any + destY: any + offsetX: any + offsetY: any + autoStepRoutes: [] + moveStepBeforeStop: [] + lastDirection: any + cursorX: any + cursorY: any + moveDirectly: boolean + }, + + // 按下键的时间:为了判定双击 + downTime: number + ctrlDown: boolean + + // 路线&回放 + route: string[], + replay: { + replaying: boolean + pausing: boolean + /** 正在某段动画中 */animate: boolean + toReplay: string[] + totalList: string[] + speed: number + steps: number + save: [] + } + + // event事件 + shops: {} + event: { + id: string + data: any + selection: any + ui: any + interval: number + } + autoEvents: Events + textAttribute: { + position: string + offset: number + title: rgbarray + background: rgbarray + text: rgbarray + titlefont: number + textfont: number + bold: boolean + time: number + letterSpacing: number + animateTime: number + }, + globalAttribute: { + equipName: string[] + statusLeftBackground: string + statusTopBackground: string + toolsBackground: string + borderColor: string + statusBarColor: string + floorChangingStyle: string + font: string + } + curtainColor: null + + // 动画 + globalAnimateObjs: [] + floorAnimateObjs: [] + boxAnimateObjs: [] + autotileAnimateObjs: [] + globalAnimateStatus: number + animateObjs: [] +} + +/** @file control.js 主要用来进行游戏控制,比如行走控制、自动寻路、存读档等等游戏核心内容。 */ +declare class control { + + /** + * 开启调试模式, 此模式下可以按Ctrl键进行穿墙, 并忽略一切事件。 + * 此模式下不可回放录像和上传成绩。 + */ + debug(): void + + /** + * 刷新状态栏和地图显伤 + * 2.9.1优化:非必须立刻执行的刷新(一般是伤害相关的除外)的延迟到下一动画帧执行 + * @param doNotCheckAutoEvents 是否不检查自动事件 + * @param immediate 是否立刻刷新,而非延迟到下一动画帧刷新 + */ + updateStatusBar(doNotCheckAutoEvents?: boolean, immediate?: boolean): void + + /** 删除某个flag/变量 */ + removeFlag(name: string): void + + /** 设置某个独立开关 */ + setSwitch(x: number, y: number, floorId: string, name: string, value: any): void + + /** 获得某个独立开关 */ + getSwitch(x: number, y: number, floorId: string, name: string, defaultValue: any): any + + /** 增加某个独立开关 */ + addSwitch(x: number, y: number, floorId: string, name: string, value: any): void + + /** 判定某个独立开关 */ + hasSwitch(x: number, y: number, floorId: string, name: string): boolean + + /** 删除独立开关 */ + removeSwitch(x: number, y: number, floorId: string, name: string): boolean + + /** 设置大地图的偏移量 */ + setGameCanvasTranslate(canvasId: string, x: number, y: number): void + + /** 更新大地图的可见区域 */ + updateViewport(): void + + /** 立刻聚集所有的跟随者 */ + gatherFollowers(): void + + /** 回放下一个操作 */ + replay(): void + + /** + * 进入标题画面 + * @example core.showStartAnimate(); // 重启游戏但不重置bgm + * @param noAnimate 可选,true表示不由黑屏淡入而是立即亮屏 + * @param callback 可选,完全亮屏后的回调函数 + */ + showStartAnimate(noAnimate?: boolean, callback?: () => void): void + + /** + * 淡出标题画面 + * @example core.hideStartAnimate(core.startGame); // 淡出标题画面并开始新游戏,跳过难度选择 + * @param callback 标题画面完全淡出后的回调函数 + */ + hideStartAnimate(callback?: () => void): void + + /** + * 半自动寻路,用于鼠标或手指拖动 + * @example core.setAutomaticRoute(0, 0, [{direction: "right", x: 4, y: 9}, {direction: "right", x: 5, y: 9}, {direction: "right", x: 6, y: 9}, {direction: "up", x: 6, y: 8}]); + * @param destX 鼠标或手指的起拖点横坐标 + * @param destY 鼠标或手指的起拖点纵坐标 + * @param stepPostfix 拖动轨迹的数组表示,每项为一步的方向和目标点。 + */ + setAutomaticRoute(destX: number, destY: number, stepPostfix: Array<{ direction: direction, x: number, y: number }>): void + + /** + * 连续行走 + * @example core.setAutoHeroMove([{direction: "up", step: 1}, {direction: "left", step: 3}, {direction: "right", step: 3}, {direction: "up", step: 9}]); // 上左左左右右右上9 + * @param steps 压缩的步伐数组,每项表示朝某方向走多少步 + */ + setAutoHeroMove(steps: Array<{ direction: direction, step: number }>): void + + /** + * 尝试前进一步,如果面前不可被踏入就会直接触发该点事件 + * @example core.moveAction(core.doAction); // 尝试前进一步,然后继续事件处理。常用于在事件流中让主角像自由行动时一样前进一步,可以照常触发moveOneStep(跑毒和计步)和面前的事件(包括但不限于阻激夹域捕) + * @param callback 走一步后的回调函数,可选 + */ + moveAction(callback?: () => void): void + + /** + * 连续前进,不撞南墙不回头 + * @example core.moveHero(); // 连续前进 + * @param direction 可选,如果设置了就会先转身到该方向 + * @param callback 可选,如果设置了就只走一步 + */ + moveHero(direction?: direction, callback?: () => void): void + + /** + * 等待主角停下 + * @example core.waitHeroToStop(core.vibrate); // 等待主角停下,然后视野左右抖动1秒 + * @param callback 主角停止后的回调函数 + */ + waitHeroToStop(callback?: () => void): void + + /** + * 主角转向并计入录像,不会导致跟随者聚集,会导致视野重置到以主角为中心 + * @example core.turnHero(); // 主角顺时针旋转90°,即单击主角或按下Z键的效果 + * @param direction 主角的新朝向,可为 up, down, left, right, :left, :right, :back 七种之一 + */ + turnHero(direction?: direction): void + + /** + * 尝试瞬移,如果该点有图块/事件/阻激夹域捕则会瞬移到它旁边再走一步(不可踏入的话当然还是触发该点事件),这一步的方向优先和瞬移前主角的朝向一致 + * @example core.tryMoveDirectly(6, 0); // 尝试瞬移到地图顶部的正中央,以样板0层为例,实际效果是瞬移到了上楼梯下面一格然后向上走一步并触发上楼事件 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + */ + tryMoveDirectly(destX: number, destY: number): void + + /** + * 绘制主角和跟随者并重置视野到以主角为中心 + * @example core.drawHero(); // 原地绘制主角的静止帧 + * @param status 绘制状态,一般用stop + * @param offset 相对主角逻辑位置的偏移量,不填视为无偏移 + * @param frame 绘制第几帧 + */ + drawHero(status?: 'stop' | 'leftFoot' | 'rightFoot', offset?: number, frame?: number): void + + /** + * 获取主角面前第n格的横坐标 + * @example core.closeDoor(core.nextX(), core.nextY(), 'yellowDoor', core.turnHero); // 在主角面前关上一扇黄门,然后主角顺时针旋转90° + * @param n 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + */ + nextX(n?: number): number + + /** + * 获取主角面前第n格的纵坐标 + * @example core.jumpHero(core.nextX(2), core.nextY(2)); // 主角向前跃过一格,即跳跃靴道具的使用效果 + * @param n 目标格与主角的距离,面前为正数,背后为负数,脚下为0,不填视为1 + */ + nextY(n?: number): number + + /** + * 判定主角是否身处某个点的锯齿领域(取曼哈顿距离) + * @example core.nearHero(6, 6, 6); // 判定主角是否身处点(6,6)的半径为6的锯齿领域 + * @param x 领域的中心横坐标 + * @param y 领域的中心纵坐标 + * @param n 领域的半径,不填视为1 + */ + nearHero(x: number, y: number, n?: number): boolean + + /** + * 请不要直接使用该函数,请使用core.updateStatusBar()代替!重算并绘制地图显伤 + * @example core.updateDamage(); // 更新当前地图的显伤,绘制在显伤层(废话) + * @param floorId 地图id,不填视为当前地图。预览地图时填写 + * @param ctx 绘制到的画布,如果填写了就会画在该画布而不是显伤层 + */ + updateDamage(floorId?: string, ctx?: CanvasRenderingContext2D): void + + /** 仅重绘地图显伤 */ + drawDamage(ctx?: CanvasRenderingContext2D): void + + /** + * 设置主角的某个属性 + * @example core.setStatus('loc', {x : 0, y : 0, direction : 'up'}); // 设置主角位置为地图左上角,脸朝上 + * @param name 属性的英文名,其中'x'、'y'和'direction'会被特殊处理为 core.setHeroLoc(name, value),其他的('exp'被视为'exp')会直接对 core.status.hero[name] 赋值 + * @param value 属性的新值 + */ + setStatus(name: K, value: HeroStatus[K]): void + + /** + * 增减主角的某个属性,等价于core.setStatus(name, core.getStatus(name) + value) + * @example core.addStatus('name', '酱'); // 在主角的名字后加一个“酱”字 + * @param name 属性的英文名 + * @param value 属性的增量,请注意旧量和增量中只要有一个是字符串就会把两者连起来成为一个更长的字符串 + */ + addStatus(name: K, value: HeroStatus[K]): void + + /** + * 读取主角的某个属性,不包括百分比修正 + * @example core.getStatus('loc'); // 读取主角的坐标和朝向 + * @param name 属性的英文名,其中'x'、'y'和'direction'会被特殊处理为 core.getHeroLoc(name),其他的('exp'被视为'exp')会直接读取 core.status.hero[name] + * @returns 属性值 + */ + getStatus(name: K): HeroStatus[K] + + /** + * 计算主角的某个属性,包括百分比修正 + * @example core.getRealStatus('atk'); // 计算主角的攻击力,包括百分比修正。战斗使用的就是这个值 + * @param name 属性的英文名,请注意只能用于数值类属性哦,否则乘法会得到NaN + */ + getRealStatus(name: K): HeroStatus[K] + + /** 获得某个状态的名字 */ + getStatusLabel(name: K): string + + /** + * 设置主角某个属性的百分比修正倍率,初始值为1, + * 倍率存放在flag: '__'+name+'_buff__' 中 + * @example core.setBuff('atk', 0.5); // 主角能发挥出的攻击力减半 + * @param name 属性的英文名,请注意只能用于数值类属性哦,否则随后的乘法会得到NaN + * @param value 新的百分比修正倍率,不填(效果上)视为1 + */ + setBuff(name: K, value?: HeroStatus[K]): void + + /** + * 增减主角某个属性的百分比修正倍率,加减法叠加和抵消。等价于 core.setBuff(name, core.getBuff(name) + value) + * @example core.addBuff('atk', -0.1); // 主角获得一层“攻击力减一成”的负面效果 + * @param name 属性的英文名,请注意只能用于数值类属性哦,否则随后的乘法会得到NaN + * @param value 倍率的增量 + */ + addBuff(name: K, value: HeroStatus[K]): void + + /** + * 读取主角某个属性的百分比修正倍率,初始值为1 + * @example core.getBuff('atk'); // 主角当前能发挥出多大比例的攻击力 + * @param name 属性的英文名 + */ + getBuff(name: HeroStatus[K]): number + + /** + * 获得或移除毒衰咒效果 + * @param action 获得还是移除,'get'为获得,'remove'为移除 + * @param type 要获得或移除的毒衰咒效果 + */ + triggerDebuff(action: string, type: string | string[]): void + + /** + * 设置勇士位置 + * 值得注意的是,这句话虽然会使勇士改变位置,但并不会使界面重新绘制; + * 如需立刻重新绘制地图还需调用:core.clearMap('hero'); core.drawHero(); 来对界面进行更新。 + * @example core.setHeroLoc('x', 5) // 将勇士当前位置的横坐标设置为5。 + * @param name 要设置的坐标属性 + * @param value 新值 + * @param noGather 是否聚集跟随者 + */ + setHeroLoc(name: 'x' | 'y', value: number, noGather?: boolean): void + setHeroLoc(name: 'direction', value: direction, noGather?: boolean): void + + /** + * 读取主角的位置和/或朝向 + * @example core.getHeroLoc(); // 读取主角的位置和朝向 + * @param name 要读取横坐标还是纵坐标还是朝向还是都读取 + * @returns name ? core.status.hero.loc[name] : core.status.hero.loc + */ + getHeroLoc(): { x: number, y: number, direction: direction } + getHeroLoc(name: 'x' | 'y'): number + getHeroLoc(name: 'direction'): direction + + /** + * 根据级别的数字获取对应的名称,后者定义在全塔属性 + * @example core.getLvName(); // 获取主角当前级别的名称,如“下级佣兵” + * @param lv 级别的数字,不填则视为主角当前的级别 + * @returns 级别的名称,如果不存在就还是返回数字 + */ + getLvName(lv?: number): string | number + + /** + * 获得下次升级需要的经验值。 + * 升级扣除模式下会返回经验差值;非扣除模式下会返回总共需要的经验值。 + * 如果无法进行下次升级,返回null。 + */ + getNextLvUpNeed(): number + + /** + * 设置一个flag变量 + * @example core.setFlag('poison', true); // 令主角中毒 + * @param name 变量名,支持中文 + * @param value 变量的新值,不填或填null视为删除 + */ + setFlag(name: string, value?: any): void + + /** + * 增减一个flag变量,等价于 core.setFlag(name, core.getFlag(name, 0) + value) + * @example core.addFlag('hatred', 1); // 增加1点仇恨值 + * @param name 变量名,支持中文 + * @param value 变量的增量 + */ + addFlag(name: string, value: number | string): void + + /** + * 读取一个flag变量 + * @param name 变量名,支持中文 + * @param defaultValue 当变量不存在时的返回值,可选(事件流中默认填0)。 + * @returns flags[name] ?? defaultValue + */ + getFlag(name: string, defaultValue?: any): any + + /** + * 判定一个flag变量是否存在且不为false、0、''、null、undefined和NaN + * @example core.hasFlag('poison'); // 判断主角当前是否中毒 + * @param name 变量名,支持中文 + * @returns !!core.getFlag(name) + */ + hasFlag(name: string): boolean + + /** + * 设置天气,不计入存档。如需长期生效请使用core.events._action_setWeather()函数 + * @example core.setWeather('fog', 10); // 设置十级大雾天 + * @param type 新天气的类型,不填视为无天气 + * @param level 新天气(晴天除外)的级别,必须为不大于10的正整数,不填视为5 + */ + setWeather(type?: 'rain' | 'snow' | 'sun' | 'fog' | 'cloud' | string, level?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10): void + + /** 注册一个天气 */ + registerWeather(name: string, initFunc: (level: number) => void, frameFunc?: (timestamp: number, level: number) => void): void + + /** 注销一个天气 */ + unregisterWeather(name: string): void; + + /** + * 更改画面色调,不计入存档。如需长期生效请使用core.events._action_setCurtain()函数 + * @example core.setCurtain(); // 恢复画面色调,用时四分之三秒 + * @param color 一行三列(第四列视为1)或一行四列(第四列若大于1则会被视为1,第四列若为负数则会被视为0)的颜色数组,不填视为[0, 0, 0, 0] + * @param time 渐变时间,单位为毫秒。不填视为750ms,负数视为0(无渐变,立即更改) + * @param callback 更改完毕后的回调函数,可选。事件流中常取core.doAction + */ + setCurtain(color?: [number, number, number, number?], time?: number, moveMode?: string, callback?: () => void): void + + /** + * 画面闪烁 + * @example core.screenFlash([255, 0, 0, 1], 3); // 红屏一闪而过 + * @param color 一行三列(第四列视为1)或一行四列(第四列若大于1则会被视为1,第四列若填负数则会被视为0)的颜色数组,必填 + * @param time 单次闪烁时长,实际闪烁效果为先花其三分之一的时间渐变到目标色调,再花剩余三分之二的时间渐变回去 + * @param times 闪烁的总次数,不填或填0都视为1 + * @param callback 闪烁全部完毕后的回调函数,可选 + */ + screenFlash(color: [number, number, number, number?], time: number, times?: number, moveMode?: string, callback?: () => void): void + + /** + * 播放背景音乐,中途开播但不计入存档且只会持续到下次场景切换。如需长期生效请将背景音乐的文件名赋值给flags.__bgm__ + * @example core.playBgm('bgm.mp3', 30); // 播放bgm.mp3,并跳过前半分钟 + * @param bgm 背景音乐的文件名,支持全塔属性中映射前的中文名 + * @param startTime 跳过前多少秒,不填则不跳过 + */ + playBgm(bgm: string, startTime?: number): void + + /** + * 注册一个 animationFrame + * @param name 名称,可用来作为注销使用 + * @param needPlaying 是否只在游戏运行时才执行(在标题界面不执行) + * @param func 要执行的函数,或插件中的函数名;可接受timestamp(从页面加载完毕到当前所经过的时间)作为参数 + */ + registerAnimationFrame(name: string, needPlaying: boolean, func?: (timestamp: number) => void): void + + /** 注销一个animationFrame */ + unregisterAnimationFrame(name: string): void + + /** 游戏是否已经开始 */ + isPlaying(): boolean + + /** 清除游戏状态和数据 */ + clearStatus(): void + + /** 清除自动寻路路线 */ + clearAutomaticRouteNode(x?: any, y?: any): void + + /** 停止自动寻路操作 */ + stopAutomaticRoute(): void + + /** 保存剩下的寻路,并停止 */ + saveAndStopAutomaticRoute(): void + + /** 继续剩下的自动寻路操作 */ + continueAutomaticRoute(): void + + /** 清空剩下的自动寻路列表 */ + clearContinueAutomaticRoute(callback?: () => any): void + + /** 设置行走的效果动画 */ + setHeroMoveInterval(callback?: () => any): void + + /** 每移动一格后执行的事件 */ + moveOneStep(callback?: () => any): void + + /** 当前是否正在移动 */ + isMoving(): boolean + + /** 瞬间移动 */ + moveDirectly(destX?: any, destY?: any, ignoreSteps?: any): void + + /** 改变勇士的不透明度 */ + setHeroOpacity(opacity?: number, moveMode?: string, time?: any, callback?: () => any): void + + /** 加减画布偏移 */ + addGameCanvasTranslate(x?: number, y?: number): void + + /** + * 设置视野范围 + * px,py: 左上角相对大地图的像素坐标,不需要为32倍数 + */ + setViewport(px?: number, py?: number): void + + /** 移动视野范围 */ + moveViewport(x: number, y: number, moveMode?: string, time?: number, callback?: () => any): void + + /** 更新跟随者坐标 */ + updateFollowers(): void + + /** 更新领域、夹击、阻击的伤害地图 */ + updateCheckBlock(floorId?: string): void + + /** 检查并执行领域、夹击、阻击事件 */ + checkBlock(): void + + /** 选择录像文件 */ + chooseReplayFile(): void + + /** 开始播放 */ + startReplay(list?: any): void + + /** 更改播放状态 */ + triggerReplay(): void + + /** 暂停播放 */ + pauseReplay(): void + + /** 恢复播放 */ + resumeReplay(): void + + /** 单步播放 */ + stepReplay(): void + + /** 加速播放 */ + speedUpReplay(): void + + /** 减速播放 */ + speedDownReplay(): void + + /** 设置播放速度 */ + setReplaySpeed(speed?: number): void + + /** 停止播放 */ + stopReplay(force?: boolean): void + + /** 回退 */ + rewindReplay(): void + + /** 是否正在播放录像 */ + isReplaying(): boolean + + /** + * 注册一个录像行为 + * @param name 自定义名称,可用于注销使用 + * @param func 具体执行录像的函数,可为一个函数或插件中的函数名; + * 需要接受一个action参数,代表录像回放时的下一个操作 + * func返回true代表成功处理了此录像行为,false代表没有处理此录像行为。 + */ + registerReplayAction(name: string, func: (action?: string) => boolean): void + + /** 注销一个录像行为 */ + unregisterReplayAction(name: string): void + + /** 自动存档 */ + autosave(removeLast?: any): void + + /** 实际进行自动存档 */ + checkAutosave(): void + + /** 实际进行存读档事件 */ + doSL(id?: string, type?: any): void + + /** 同步存档到服务器 */ + syncSave(type?: any): void + + /** 从服务器加载存档 */ + syncLoad(): void + + /** 存档到本地 */ + saveData(): any + + /** 从本地读档 */ + loadData(data?: any, callback?: () => any): any + + /** 获得某个存档内容 */ + getSave(index?: any, callback?: () => any): any + + /** 获得某些存档内容 */ + getSaves(ids?: any, callback?: () => any): any + + /** 获得所有存档内容 */ + getAllSaves(callback?: () => any): any + + /** 获得所有存在存档的存档位 */ + getSaveIndexes(callback?: () => any): any + + /** 判断某个存档位是否存在存档 */ + hasSave(index?: number): boolean + + /** 删除某个存档 */ + removeSave(index?: number, callback?: () => any): void + + /** 从status中获得属性,如果不存在则从勇士属性中获取 */ + getStatusOrDefault(status?: any, name?: string): any + + /** 从status中获得实际属性(增幅后的),如果不存在则从勇士属性中获取 */ + getRealStatusOrDefault(status?: any, name?: string): any + + /** 获得勇士原始属性(无装备和衰弱影响) */ + getNakedStatus(name?: string): any + + /** 锁定用户控制,常常用于事件处理 */ + lockControl(): void + + /** 解锁用户控制 */ + unlockControl(): void + + /** 清空录像折叠信息 */ + clearRouteFolding(): void + + /** 检查录像折叠信息 */ + checkRouteFolding(): void + + /** 获得映射文件名 */ + getMappedName(name?: string): string + + /** 暂停背景音乐的播放 */ + pauseBgm(): void + + /** 恢复背景音乐的播放 */ + resumeBgm(resumeTime?: number): void + + /** 设置背景音乐的播放速度和音调 */ + setBgmSpeed(speed: number, usePitch?: boolean): void + + /** 设置音乐图标的显隐状态 */ + setMusicBtn(): void + + /** 开启或关闭背景音乐的播放 */ + triggerBgm(): void + + /** 播放一个音效 */ + playSound(sound: string, pitch?: number, callback?: () => any): number + + /** 停止(所有)音频 */ + stopSound(id?: number): void + + /** 获得正在播放的所有(指定)音效的id列表 */ + getPlayingSounds(name?: string): Array + + /** 检查bgm状态 */ + checkBgm(): void + + /** 设置屏幕放缩 */ + setDisplayScale(delta: number): void + + /** 清空状态栏 */ + clearStatusBar(): void + + /** 显示状态栏 */ + showStatusBar(): void + + /** 隐藏状态栏 */ + hideStatusBar(showToolbox?: boolean): void + + /** 更新状态栏的勇士图标 */ + updateHeroIcon(name: string): void + + /** 改变工具栏为按钮1-8 */ + setToolbarButton(useButton?: boolean): void + + /** + * 注册一个resize函数 + * @param name 名称,可供注销使用 + * @param func 可以是一个函数,或者是插件中的函数名;可以接受obj参数,详见resize函数。 + */ + registerResize(name: string, func: (obj: any) => void): void + + /** 注销一个resize函数 */ + unregisterResize(name: string): void + + /** 屏幕分辨率改变后重新自适应 */ + resize(): void +} + +/**@file events.js将处理所有和事件相关的操作。 */ +declare class events { + + /** + * 开始新游戏 + * @example core.startGame('咸鱼乱撞', 0, ''); // 开始一局咸鱼乱撞难度的新游戏,随机种子为0 + * @param hard 难度名,会显示在左下角(横屏)或右下角(竖屏) + * @param seed 随机种子,相同的种子保证了录像的可重复性 + * @param route 经由base64压缩后的录像,用于从头开始的录像回放 + * @param callback 回调函数,可选 + */ + startGame(hard: string, seed: number, route: string, callback?: () => void): void + + /** + * 游戏结束 + * @example core.gameOver(); // 游戏失败 + * @param ending 结局名,省略表示失败 + * @param fromReplay true表示在播放录像,可选 + * @param norank true表示不计入榜单,可选 + */ + gameOver(ending?: string, fromReplay?: boolean, norank?: boolean): void + + /** + * 战斗,如果填写了坐标就会删除该点的敌人并触发战后事件 + * @example core.battle('greenSlime'); // 和从天而降的绿头怪战斗(如果打得过) + * @param id 敌人id,必填 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param force true表示强制战斗,可选 + * @param callback 回调函数,可选 + */ + battle(id: string, x?: number, y?: number, force?: boolean, callback?: () => void): void + + /** + * 开门(包括三种基础墙) + * @example core.openDoor(0, 0, true, core.jumpHero); // 打开左上角的门,需要钥匙,然后主角原地跳跃半秒 + * @param x 门的横坐标 + * @param y 门的纵坐标 + * @param needKey true表示需要钥匙,会导致机关门打不开 + * @param callback 门完全打开后或打不开时的回调函数,可选 + */ + openDoor(x: number, y: number, needKey?: boolean, callback?: () => void): void + + /** + * 获得道具并提示,如果填写了坐标就会删除该点的该道具 + * @example core.getItem('book'); // 获得敌人手册并提示 + * @param id 道具id,必填 + * @param num 获得的数量,不填视为1,填了就别填坐标了 + * @param x 道具的横坐标,可选 + * @param y 道具的纵坐标,可选 + * @param callback 回调函数,可选 + */ + getItem(id: string, num?: number, x?: number, y?: number, callback?: () => void): void + + /** + * 场景切换 + * @example core.changeFloor('MT0'); // 传送到主塔0层,主角坐标和朝向不变,黑屏时间取用户设置值 + * @param floorId 传送的目标地图id,可以填':before'和':after'分别表示楼下或楼上 + * @param stair 传送的位置,可以填':now'(保持不变,可省略),':symmetry'(中心对称),':symmetry_x'(左右对称),':symmetry_y'(上下对称)或图块id(该图块最好在目标层唯一,一般为'downFloor'或'upFloor') + * @param heroLoc 传送的坐标(如果填写了,就会覆盖上述的粗略目标位置)和传送后主角的朝向(不填表示保持不变) + * @param time 传送的黑屏时间,单位为毫秒。不填为用户设置值 + * @param callback 黑屏结束后的回调函数,可选 + */ + changeFloor(floorId: string, stair?: string, heroLoc?: { x?: number, y?: number, direction?: direction }, time?: number, callback?: () => void): void + + /** + * 执行下一个事件指令,常作为回调 + * @example core.setCurtain([0,0,0,1], undefined, null, core.doAction); // 事件中的原生脚本,配合勾选“不自动执行下一个事件”来达到此改变色调只持续到下次场景切换的效果 + * @param keepUI true表示不清除UI画布和选择光标 + */ + doAction(keepUI?: true): void + + /** + * 插入一段事件;此项不可插入公共事件,请用 core.insertCommonEvent + * @example core.insertAction('一段文字'); // 插入一个显示文章 + * @param action 单个事件指令,或事件指令数组 + * @param x 新的当前点横坐标,可选 + * @param y 新的当前点纵坐标,可选 + * @param callback 新的回调函数,可选 + * @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头 + */ + insertAction(action: Events, x?: number, y?: number, callback?: () => void, addToLast?: boolean): void + + /** + * 设置一项敌人属性并计入存档 + * @example core.setEnemy('greenSlime', 'def', 0); // 把绿头怪的防御设为0 + * @param id 敌人id + * @param name 属性的英文缩写 + * @param value 属性的新值,可选 + * @param operator 操作符,可选 + * @param prefix 独立开关前缀,一般不需要,下同 + */ + setEnemy(id: string, name: K, value?: Enemy[K], operator?: string, prefix?: string): void + + /** 设置某个点的敌人属性 */ + setEnemyOnPoint(x: number, y: number, floorId: string, name: K, value?: Enemy[K], operator?: string, prefix?: string): void + + /** 重置某个点的敌人属性 */ + resetEnemyOnPoint(x: number, y: number, floorId?: string): void + + /** 将某个点已经设置的敌人属性移动到其他点 */ + moveEnemyOnPoint(fromX: number, fromY: number, toX: number, toY: number, floorId?: string): void + + /** + * 设置一项楼层属性并刷新状态栏 + * @example core.setFloorInfo('ratio', 2, 'MT0'); // 把主塔0层的血瓶和宝石变为双倍效果 + * @param name 要求改的属性名 + * @param values 属性的新值 + * @param floorId 楼层id,不填视为当前层 + * @param prefix 独立开关前缀,一般不需要,下同 + */ + setFloorInfo(name: K, values?: Floor[K] | boolean | number | string | [number, number] | [string, number?] | Array, floorId?: string, prefix?: string): void + + /** + * 设置一个系统开关 + * @example core.setGlobalFlag('steelDoorWithoutKey', true); // 使全塔的所有铁门都不再需要钥匙就能打开 + * @param name 系统开关的英文名 + * @param value 开关的新值,您可以用!core.flags[name]简单地表示将此开关反转 + */ + setGlobalFlag(name: keyof SystemFlags, value: boolean): void + + /** + * 关门,目标点必须为空地 + * @example core.closeDoor(0, 0, 'yellowWall', core.jumpHero); // 在左上角关掉一堵黄墙,然后主角原地跳跃半秒 + * @param x 横坐标 + * @param y 纵坐标 + * @param id 门的id,也可以用三种基础墙 + * @param callback 门完全关上后的回调函数,可选 + */ + closeDoor(x: number, y: number, id: string, callback?: () => void): void + + /** + * 显示一张图片 + * @example core.showImage(1, core.material.images.images['winskin.png'], [0,0,128,128], [0,0,416,416], 0.5, 1000); // 裁剪winskin.png的最左边128×128px,放大到铺满整个视野,1秒内淡入到50%透明,编号为1 + * @param code 图片编号,为不大于50的正整数,加上100后就是对应画布层的z值,较大的会遮罩较小的,注意色调层的z值为125,UI层为140 + * @param image 图片文件名(可以是全塔属性中映射前的中文名)或图片对象(见上面的例子) + * @param sloc 一行且至多四列的数组,表示从原图裁剪的左上角坐标和宽高,可选 + * @param loc 一行且至多四列的数组,表示图片在视野中的左上角坐标和宽高,可选 + * @param opacityVal 不透明度,为小于1的正数。不填视为1 + * @param time 淡入时间,单位为毫秒。不填视为0 + * @param callback 图片完全显示出来后的回调函数,可选 + */ + showImage(code: number, image: string | HTMLImageElement, sloc?: Array, loc?: Array, opacityVal?: number, time?: number, callback?: () => void): void + + /** + * 隐藏一张图片 + * @example core.hideImage(1, 1000, core.jumpHero); // 1秒内淡出1号图片,然后主角原地跳跃半秒 + * @param code 图片编号 + * @param time 淡出时间,单位为毫秒 + * @param callback 图片完全消失后的回调函数,可选 + */ + hideImage(code: number, time?: number, callback?: () => void): void + + /** + * 移动一张图片并/或改变其透明度 + * @example core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明 + * @param code 图片编号 + * @param to 新的左上角坐标,省略表示原地改变透明度 + * @param opacityVal 新的透明度,省略表示不变 + * @param moveMode 移动模式 + * @param time 移动用时,单位为毫秒。不填视为1秒 + * @param callback 图片移动完毕后的回调函数,可选 + */ + moveImage(code: number, to?: [number?, number?], opacityVal?: number, moveMode?: string, time?: number, callback?: () => void): void + + /** + * 旋转一张图片 + * @param code 图片编号 + * @param center 旋转中心像素(以屏幕为基准);不填视为图片本身中心 + * @param angle 旋转角度;正数为顺时针,负数为逆时针 + * @param moveMode 旋转模式 + * @param time 移动用时,单位为毫秒。不填视为1秒 + * @param callback 图片移动完毕后的回调函数,可选 + */ + rotateImage(code: number, center?: [number?, number?], angle?: number, moveMode?: string, time?: number, callback?: () => void): void + + /** 放缩一张图片 */ + scaleImage(code: number, center?: [Number?, number?], scale?: number, moveMode?: string, time?: number, callback?: () => void): void + + /** + * 绘制一张动图或擦除所有动图 + * @example core.showGif(); // 擦除所有动图 + * @param name 动图文件名,可以是全塔属性中映射前的中文名 + * @param x 动图在视野中的左上角横坐标 + * @param y 动图在视野中的左上角纵坐标 + */ + showGif(name?: string, x?: number, y?: number): void + + /** + * 调节bgm的音量 + * @example core.setVolume(0, 100, core.jumpHero); // 0.1秒内淡出bgm,然后主角原地跳跃半秒 + * @param value 新的音量,为0或不大于1的正数。注意系统设置中是这个值的平方根的十倍 + * @param time 渐变用时,单位为毫秒。不填或小于100毫秒都视为0 + * @param callback 渐变完成后的回调函数,可选 + */ + setVolume(value: number, time?: number, callback?: () => void): void + + /** + * 视野抖动 + * @example core.vibrate(); // 视野左右抖动1秒 + * @param direction 抖动方向 + * @param time 抖动时长,单位为毫秒 + * @param speed 抖动速度 + * @param power 抖动幅度 + * @param callback 抖动平息后的回调函数,可选 + */ + vibrate(direction?: string, time?: number, speed?: number, power?: number, callback?: () => void): void + + /** + * 强制移动主角(包括后退),这个函数的作者已经看不懂这个函数了 + * @example core.eventMoveHero(['forward'], 125, core.jumpHero); // 主角强制前进一步,用时1/8秒,然后主角原地跳跃半秒 + * @param steps 步伐数组,注意后退时跟随者的行为会很难看 + * @param time 每步的用时,单位为毫秒。0或不填则取主角的移速,如果后者也不存在就取0.1秒 + * @param callback 移动完毕后的回调函数,可选 + */ + eventMoveHero(steps: step[], time?: number, callback?: () => void): void + + /** + * 主角跳跃,跳跃勇士。ex和ey为目标点的坐标,可以为null表示原地跳跃。time为总跳跃时间。 + * @example core.jumpHero(); // 主角原地跳跃半秒 + * @param ex 跳跃后的横坐标 + * @param ey 跳跃后的纵坐标 + * @param time 跳跃时长,单位为毫秒。不填视为半秒 + * @param callback 跳跃完毕后的回调函数,可选 + */ + jumpHero(ex?: number, ey?: number, time?: number, callback?: () => void): void + + /** + * 更改主角行走图 + * @example core.setHeroIcon('npc48.png', true); // 把主角从阳光变成样板0层左下角的小姐姐,但不立即刷新 + * @param name 新的行走图文件名,可以是全塔属性中映射前的中文名。映射后会被存入core.status.hero.image + * @param noDraw true表示不立即刷新(刷新会导致大地图下视野重置到以主角为中心) + */ + setHeroIcon(name: string, noDraw?: boolean): void + + /** + * 尝试使用一个道具 + * @example core.tryUseItem('pickaxe'); // 尝试使用破墙镐 + * @param itemId 道具id,其中敌人手册、传送器和飞行器会被特殊处理 + */ + tryUseItem(itemId: string): void + + /** 初始化游戏 */ + resetGame(hero?: HeroStatus, hard?: any, floorId?: string, maps?: any, values?: any): void + + /** 游戏获胜事件 */ + win(reason?: string, norank?: boolean, noexit?: boolean): void + + /** 游戏失败事件 */ + lose(reason?: string): void + + /** 重新开始游戏;此函数将回到标题页面 */ + restart(): void + + /** 询问是否需要重新开始 */ + confirmRestart(): void + + /** + * 注册一个系统事件 + * @param type 事件名 + * @param func 为事件的处理函数,可接受(data,callback)参数 + */ + registerSystemEvent(type: string, func: (data?: any, callback?: () => void) => void): void + + /** 注销一个系统事件 */ + unregisterSystemEvent(type: string): void + + /** 执行一个系统事件 */ + doSystemEvent(type: string, data?: any, callback?: () => any): void + + /** + * 触发(x,y)点的系统事件;会执行该点图块的script属性,同时支持战斗(会触发战后)、道具(会触发道具后)、楼层切换等等 + */ + trigger(x?: number, y?: number, callback?: () => any): void + + /** 战斗前触发的事件 */ + beforeBattle(enemyId?: string, x?: number, y?: number): void + + /** 战斗结束后触发的事件 */ + afterBattle(enemyId?: string, x?: number, y?: number): void + + /** 开一个门后触发的事件 */ + afterOpenDoor(doorId?: string, x?: number, y?: number): void + + /** 获得一个道具后的shij */ + afterGetItem(id?: string, x?: number, y?: number, isGentleClick?: boolean): void + + /** + * 轻按获得面前的物品或周围唯一物品 + * @param noRoute 若为true则不计入录像 + */ + getNextItem(noRoute?: boolean): void + + /** 楼层转换中 */ + changingFloor(floorId?: string, heroLoc?: any): void + + /** 转换楼层结束的事件 */ + afterChangeFloor(floorId?: string): void + + /** 是否到达过某个楼层 */ + hasVisitedFloor(floorId?: string): boolean + + /** 到达某楼层 */ + visitFloor(floorId?: string): void + + /** 推箱子 */ + pushBox(data?: any): void + + /** 推箱子后的事件 */ + afterPushBox(): void + + /** 当前是否在冰上 */ + onSki(number?: number): boolean + + /** + * 注册一个自定义事件 + * @param type 事件类型 + * @param func 事件的处理函数,可接受(data, x, y, prefix)参数 + * data为事件内容,x和y为当前点坐标(可为null),prefix为当前点前缀 + */ + registerEvent(type: string, func: (data: any, x?: number, y?: number, prefix?: string) => void): void + + /** 注销一个自定义事件 */ + unregisterEvent(type: string): void + + /** 执行一个自定义事件 */ + doEvent(data?: any, x?: number, y?: number, prefix?: any): void + + /** 直接设置事件列表 */ + setEvents(list?: any, x?: number, y?: number, callback?: () => any): void + + /** 开始执行一系列自定义事件 */ + startEvents(list?: any, x?: number, y?: number, callback?: () => any): void + + /** + * 插入一个公共事件 + * @example core.insertCommonEvent('加点事件', [3]); + * @param name 公共事件名;如果公共事件不存在则直接忽略 + * @param args 参数列表,为一个数组,将依次赋值给 flag:arg1, flag:arg2, ... + * @param x 新的当前点横坐标,可选 + * @param y 新的当前点纵坐标,可选 + * @param callback 新的回调函数,可选 + * @param addToLast 插入的位置,true表示插入到末尾,否则插入到开头 + */ + insertCommonEvent(name?: string, args?: any, x?: number, y?: number, callback?: () => any, addToLast?: boolean): void + + /** 获得一个公共事件 */ + getCommonEvent(name: string): any + + /** 恢复一个事件 */ + recoverEvents(data?: any): void + + /** 检测自动事件 */ + checkAutoEvents(): void + + /** 当前是否在执行某个自动事件 */ + autoEventExecuting(symbol?: string, value?: any): boolean + + /** 当前是否执行过某个自动事件 */ + autoEventExecuted(symbol?: string, value?: any): boolean + + /** 将当前点坐标入栈 */ + pushEventLoc(x?: number, y?: number, floorId?: string): boolean + + /** 将当前点坐标入栈 */ + popEventLoc(): any + + /** 预编辑事件 */ + precompile(data?: any): any + + /** 点击怪物手册时的打开操作 */ + openBook(fromUserAction?: boolean): void + + /** 点击楼层传送器时的打开操作 */ + useFly(fromUserAction?: boolean): void + + /** 飞往某一层 */ + flyTo(toId?: string, callback?: () => boolean): void + + /** 点击存档按钮时的打开操作 */ + save(fromUserAction?: boolean): void + + /** 点击读档按钮时的打开操作 */ + load(fromUserAction?: boolean): void + + /** 点击装备栏时的打开操作 */ + openEquipbox(fromUserAction?: boolean): void + + /** 点击工具栏时的打开操作 */ + openToolbox(fromUserAction?: boolean): void + + /** 点击快捷商店按钮时的打开操作 */ + openQuickShop(fromUserAction?: boolean): void + + /** 点击虚拟键盘时的打开操作 */ + openKeyBoard(fromUserAction?: boolean): void + + /** 点击设置按钮时的操作 */ + openSettings(fromUserAction?: boolean): void + + /** 当前是否有未处理完毕的异步事件(不包含动画和音效) */ + hasAsync(): boolean + + /** 立刻停止所有异步事件 */ + stopAsync(): void + + /** + * 跟随 + * @param name 要跟随的一个合法的4x4的行走图名称,需要在全塔属性注册 + */ + follow(name: string): void + + /** + * 取消跟随 + * @param name 取消跟随的行走图,不填则取消全部跟随者 + */ + unfollow(name?: string): void + + /** 数值操作 */ + setValue(name: string, operator: string, value: any, prefix?: string): void + + /** 数值增减 */ + addValue(name: string, value: any, prefix?: string): void + + /** 设置全塔属性 */ + setGlobalAttribute(name: string, value: any): void + + /** 设置剧情文本的属性 */ + setTextAttribute(data: any): void + + /** 清除对话框 */ + clearTextBox(code: number): void + + /** 移动对话框 */ + moveTextBox(code: number, loc: [number], relative: boolean, moveMode?: string, time?: number, callback?: () => any): void + + /** 设置文件别名 */ + setNameMap(name: string, value?: string): void + + /** 检查升级事件 */ + checkLvUp(): void +} + +/** @file actions.js 定义了玩家的操作控制 */ +declare class actions { + /** + * 此函数将注册一个用户交互行为。 + * @param action 要注册的交互类型,如 ondown, onclick, keyDown 等等。 + * @param name 你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 + * @param func 执行函数。 + * 如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 + * @param priority 优先级;优先级高的将会被执行。此项可不填,默认为0 + */ + registerAction(action: string, name: string, func: string | ((...params: any) => void), priority?: number): void + + /** 注销一个用户交互行为 */ + unregisterAction(action: string, name: string): void + + /** 执行一个用户交互行为 */ + doRegisteredAction(action: string, ...params: any): void + + /** 按下某个键时 */ + onkeyDown(e: KeyboardEvent): void + + /** 放开某个键时 */ + onkeyUp(e: KeyboardEvent): void + + /** 按住某个键时 */ + pressKey(keyCode: number): void + + /** 根据按下键的code来执行一系列操作 */ + keyDown(keyCode: number): void + + /** 根据放开键的code来执行一系列操作 */ + keyUp(keyCode: number, altKey?: boolean, fromReplay?: boolean): void + + /** 点击(触摸)事件按下时 */ + ondown(loc: number[]): void + + /** 当在触摸屏上滑动时 */ + onmove(loc: number[]): void + + /** 当点击(触摸)事件放开时 */ + onup(loc: number[]): void + + /** 具体点击屏幕上(x,y)点时,执行的操作 */ + onclick(x: number, y: number, px: number, py: number, stepPostfix?: any): void + + /** 滑动鼠标滚轮时的操作 */ + onmousewheel(direct: 1 | -1): void + + /** 长按Ctrl键时 */ + keyDownCtrl(): void + + /** 长按 */ + longClick(x: number, y: number, px: number, py: number, fromEvent?: boolean): void + + /** 点击自绘状态栏时 */ + onStatusBarClick(e?: MouseEvent): void +} + +/** @file enemys.js 定义了一系列和敌人相关的API函数。 */ +declare class enemys { + + /** + * 判定某种特殊属性的有无 + * @example core.hasSpecial('greenSlime', 1) // 判定绿头怪有无先攻属性 + * @param special 敌人id或敌人对象或正整数数组或自然数 + * @param test 待检查的属性编号 + * @returns 若special为数组或数且含有test或相等、或special为敌人id或对象且具有此属性,则返回true + */ + hasSpecial(special: number | number[] | string | Enemy, test: number): boolean + + /** + * 获得某种敌人的全部特殊属性名称 + * @example core.getSpecialText('greenSlime') // ['先攻', '3连击', '破甲', '反击'] + * @param enemy 敌人id或敌人对象,如core.material.enemys.greenSlime + * @returns 字符串数组 + */ + getSpecialText(enemy: string | Enemy): string[] + + /** + * 获得某种敌人的某种特殊属性的介绍 + * @example core.getSpecialHint('bat', 1) // '先攻:怪物首先攻击' + * @param enemy 敌人id或敌人对象,用于确定属性的具体数值,否则可选 + * @param special 属性编号,可以是该敌人没有的属性 + * @returns 属性的介绍,以属性名加中文冒号开头 + */ + getSpecialHint(enemy: string | Enemy, special: number): string + + /** 获得某个敌人的某项属性值 */ + getEnemyValue(enemy: string | Enemy, name: string, x?: number, y?: number, floorId?: string): any + + /** + * 判定主角当前能否打败某只敌人 + * @example core.canBattle('greenSlime',0,0,'MT0') // 能否打败主塔0层左上角的绿头怪(假设有) + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param floorId 敌人所在的地图,可选 + * @returns true表示可以打败,false表示无法打败 + */ + canBattle(enemy: string | Enemy, x?: number, y?: number, floorId?: string): boolean + + /** + * 获得某只敌人对主角的总伤害 + * @example core.getDamage('greenSlime',0,0,'MT0') // 绿头怪的总伤害 + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param floorId 敌人所在的地图,可选 + * @returns 总伤害,如果因为没有破防或无敌怪等其他原因无法战斗,则返回null + */ + getDamage(enemy: string | Enemy, x?: number, y?: number, floorId?: string): number + + /** + * 获得某只敌人的地图显伤,包括颜色 + * @example core.getDamageString('greenSlime', 0, 0, 'MT0') // 绿头怪的地图显伤 + * @param enemy 敌人id或敌人对象 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param floorId 敌人所在的地图,可选 + * @returns damage: 表示伤害值或为'???',color: 形如'#RrGgBb' + */ + getDamageString(enemy: string | Enemy, x?: number, y?: number, floorId?: string): { + damage: string, + color: string + } + + /** + * 获得某只敌人接下来的若干个临界及其减伤,算法基于useLoop开关选择回合法或二分法 + * @example core.nextCriticals('greenSlime', 9, 0, 0, 'MT0') // 绿头怪接下来的9个临界 + * @param enemy 敌人id或敌人对象 + * @param number 要计算的临界数量,可选,默认为1 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param floorId 敌人所在的地图,可选 + * @returns 两列的二维数组,每行表示一个临界及其减伤 + */ + nextCriticals(enemy: string | Enemy, number?: number, x?: number, y?: number, floorId?: string): number[][] + + /** + * 计算再加若干点防御能使某只敌人对主角的总伤害降低多少 + * @example core.nextCriticals('greenSlime', 10, 0, 0, 'MT0') // 再加10点防御能使绿头怪的伤害降低多少 + * @param enemy 敌人id或敌人对象 + * @param k 假设主角增加的防御力,可选,默认为1 + * @param x 敌人的横坐标,可选 + * @param y 敌人的纵坐标,可选 + * @param floorId 敌人所在的地图,可选 + * @returns 总伤害的减少量 + */ + getDefDamage(enemy: string | Enemy, k?: number, x?: number, y?: number, floorId?: string): number + + /** + * 获得某张地图的敌人集合,用于手册绘制 + * @example core.getCurrentEnemys('MT0') // 主塔0层的敌人集合 + * @param floorId 地图id,可选 + * @returns 敌人集合,按伤害升序排列,支持多朝向怪合并 + */ + getCurrentEnemys(floorId?: string): Enemy[] + + /** + * 检查某些楼层是否还有漏打的(某种)敌人 + * @example core.hasEnemyLeft('greenSlime', ['sample0', 'sample1']) // 样板0层和1层是否有漏打的绿头怪 + * @param enemyId 敌人id,可选,默认为任意敌人 + * @param floorId 地图id或其数组,可选,默认为当前地图 + * @returns true表示有敌人被漏打,false表示敌人已死光 + */ + hasEnemyLeft(enemyId?: string, floorId?: string | string[]): boolean + + /** 获得所有怪物原始数据的一个副本 */ + getEnemys(): any + + /** 获得所有特殊属性定义 */ + getSpecials(): [number, string | ((enemy: Enemy) => string), string | ((enemy: Enemy) => string), + string | [number, number, number, number?], number?][] + + /** 获得所有特殊属性的颜色 */ + getSpecialColor(enemy: string | Enemy): Array + + /** 获得所有特殊属性的额外标记 */ + getSpecialFlag(enemy: string | Enemy): Array + + /** 获得怪物真实属性 */ + getEnemyInfo(enemy: string | Enemy, hero?: any, x?: number, y?: number, floorId?: string): { + hp: number + def: number + atk: number + money: number + exp: number + point: number + special: number | number[] + guards: Array<[number, number, string]> + [x: string]: any + } + + /** 获得战斗伤害信息(实际伤害计算函数) */ + getDamageInfo(enemy: string | Enemy, hero?: any, x?: number, y?: number, floorId?: string): { + mon_hp: number + mon_atk: number + mon_def: number + init_damage: number + per_damage: number + hero_per_damage: number + turn: number + damage: number + [x: string]: any + } +} + +/** @file maps.js负责一切和地图相关的处理内容 */ +declare class maps { + + /** + * 根据图块id得到数字(地图矩阵中的值) + * @example core.getNumberById('yellowWall'); // 1 + * @param id 图块id + * @returns 图块的数字,定义在project\maps.js(请注意和project\icons.js中的“图块索引”相区分!) + */ + getNumberById(id: string): number + + /** + * 生成事件层矩阵 + * @example core.getMapArray('MT0'); // 生成主塔0层的事件层矩阵,隐藏的图块视为0 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 可选,true表示隐藏的图块也会被表示出来 + * @returns 事件层矩阵,注意对其阵元的访问是[y][x] + */ + getMapArray(floorId?: string, noCache?: boolean): number[][] + + /** 判定图块的事件层数字;不存在为0 */ + getMapNumber(floorId?: string, noCache?: boolean): number + + /** + * 生成背景层矩阵 + * @example core.getBgMapArray('MT0'); // 生成主塔0层的背景层矩阵,使用缓存 + * @param floorId 地图id,不填视为当前地图 + * @param noCache 可选,true表示不使用缓存 + * @returns 背景层矩阵,注意对其阵元的访问是[y][x] + */ + getBgMapArray(floorId?: string, noCache?: boolean): number[][] + + /** + * 生成前景层矩阵 + * @example core.getFgMapArray('MT0'); // 生成主塔0层的前景层矩阵,使用缓存 + * @param floorId 地图id,不填视为当前地图 + * @param noCache 可选,true表示不使用缓存 + * @returns 前景层矩阵,注意对其阵元的访问是[y][x] + */ + getFgMapArray(floorId?: string, noCache?: boolean): number[][] + + /** + * 判定背景层的一个位置是什么 + * @example core.getBgNumber(); // 判断主角脚下的背景层图块的数字 + * @param x 横坐标,不填为当前勇士坐标 + * @param y 纵坐标,不填为当前勇士坐标 + * @param floorId 地图id,不填视为当前地图 + * @param 可选,true表示不使用缓存而强制重算 + */ + getBgNumber(x?: number, y?: number, floorId?: string, noCache?: boolean): number + + /** + * 判定前景层的一个位置是什么 + * @example core.getFgNumber(); // 判断主角脚下的前景层图块的数字 + * @param x 横坐标,不填为当前勇士坐标 + * @param y 纵坐标,不填为当前勇士坐标 + * @param floorId 地图id,不填视为当前地图 + * @param 可选,true表示不使用缓存而强制重算 + */ + getFgNumber(x?: number, y?: number, floorId?: string, noCache?: boolean): number + + /** + * 可通行性判定 + * @example core.generateMovableArray(); // 判断当前地图主角从各点能向何方向移动 + * @param floorId 地图id,不填视为当前地图 + * @returns 从各点可移动方向的三维数组 + */ + generateMovableArray(floorId?: string): Array>> + + /** + * 单点单朝向的可通行性判定 + * @exmaple core.canMoveHero(); // 判断主角是否可以前进一步 + * @param x 起点横坐标,不填视为主角当前的 + * @param y 起点纵坐标,不填视为主角当前的 + * @param direction 移动的方向,不填视为主角面对的方向 + * @param floorId 地图id,不填视为当前地图 + * @returns true表示可移动,false表示不可移动 + */ + canMoveHero(x?: number, y?: number, direction?: direction, floorId?: string): boolean + + /** + * 能否瞬移到某点,并求出节约的步数。 + * @example core.canMoveDirectly(0, 0); // 能否瞬移到地图左上角 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + * @returns 正数表示节约的步数,-1表示不可瞬移 + */ + canMoveDirectly(destX: number, destY: number): number + + /** + * 自动寻路 + * @example core.automaticRoute(0, 0); // 自动寻路到地图左上角 + * @param destX 目标点的横坐标 + * @param destY 目标点的纵坐标 + * @returns 每步走完后主角的loc属性组成的一维数组 + */ + automaticRoute(destX: number, destY: number): Array<{ direction: direction, x: number, y: number }> + + /** + * 地图绘制 + * @example core.drawMap(); // 绘制当前地图 + * @param floorId 地图id,省略表示当前楼层 + * @param callback 重绘完毕后的回调函数,可选 + */ + drawMap(floorId?: string, callback?: () => void): void + + /** + * 重绘地图 + */ + redrawMap(): void + + /** + * 绘制背景层(含贴图,其与背景层矩阵的绘制顺序可通过复写此函数来改变) + * @example core.drawBg(); // 绘制当前地图的背景层 + * @param floorId 地图id,不填视为当前地图 + * @param ctx 某画布的ctx,用于绘制缩略图,一般不需要 + */ + drawBg(floorId?: string, ctx?: CanvasRenderingContext2D): void + + /** + * 绘制事件层 + * @example core.drawEvents(); // 绘制当前地图的事件层 + * @param floorId 地图id,不填视为当前地图 + * @param blocks 一般不需要 + * @param ctx 某画布的ctx,用于绘制缩略图,一般不需要 + */ + drawEvents(floorId?: string, blocks?: Block[], ctx?: CanvasRenderingContext2D): void + + /** + * 绘制前景层(含贴图,其与前景层矩阵的绘制顺序可通过复写此函数来改变) + * @example core.drawFg(); // 绘制当前地图的前景层 + * @param floorId 地图id,不填视为当前地图 + * @param ctx 某画布的ctx,用于绘制缩略图,一般不需要 + */ + drawFg(floorId?: string, ctx?: CanvasRenderingContext2D): void + + /** + * 绘制缩略图 + * @example core.drawThumbnail(); // 绘制当前地图的缩略图 + * @param floorId 地图id,不填视为当前地图 + * @param blocks 一般不需要 + * @param options 额外的绘制项,可选。可以增绘主角位置和朝向、采用不同于游戏中的主角行走图、增绘显伤、提供flags用于存读档 + */ + drawThumbnail(floorId?: string, blocks?: Block[], options?: { + heroLoc?: [number, number] + heroIcon?: string + /** 是否绘制显伤 */ + damage?: boolean + /** 存读档时使用,可以无视 */ + flags?: { [x: string]: any } + ctx?: CtxRefer + x?: number + y?: number + /** 绘制大小 */ + size?: number + /** 绘制全图 */ + all?: boolean + /** 绘制的视野中心 */ + centerX?: number + /** 绘制的视野中心 */ + centerY?: number + /** 存读档时使用,可以无视 */ + noHD: boolean + }): void + + /** + * 判定某个点是否不可被踏入(不基于主角生命值和图块cannotIn属性) + * @example core.noPass(0, 0); // 判断地图左上角能否被踏入 + * @param x 目标点的横坐标 + * @param y 目标点的纵坐标 + * @param floorId 目标点所在的地图id,不填视为当前地图 + * @returns true表示可踏入 + */ + noPass(x: number, y: number, floorId?: string): boolean + + /** + * 判定某个点的图块id + * @example if(core.getBlockId(x1, y1) != 'greenSlime' && core.getBlockId(x2, y2) != 'redSlime') core.openDoor(x3, y3); // 一个简单的机关门事件,打败或炸掉这一对绿头怪和红头怪就开门 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否不返回null,true表示不返回null + * @returns 图块id,该点无图块则返回null + */ + getBlockId(x: number, y: number, floorId?: string, showDisable?: boolean): string | null + + /** 判定某个点的图块数字;空图块为0 */ + getBlockNumber(x: number, y: number, floorId?: string, showDisable?: boolean): number + + /** + * 判定某个点的图块类型 + * @example if(core.getBlockCls(x1, y1) != 'enemys' && core.getBlockCls(x2, y2) != 'enemy48') core.openDoor(x3, y3); // 另一个简单的机关门事件,打败或炸掉这一对不同身高的敌人就开门 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否不返回null,true表示不返回null + * @returns 图块类型,即“地形、四帧动画、矮敌人、高敌人、道具、矮npc、高npc、自动元件、额外地形”之一 + */ + getBlockCls(x: number, y: number, floorId?: string, showDisable?: boolean): 'terrains' | 'animates' | 'enemys' | 'enemy48' | 'items' | 'npcs' | 'npc48' | 'autotile' | 'tileset' | null + + /** + * 搜索图块, 支持通配符 + * @example core.searchBlock('*Door'); // 搜索当前地图的所有门 + * @param id 图块id,支持星号表示任意多个(0个起)字符 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否计入,true表示计入 + * @returns 一个详尽的数组,一般只用到其长度 + */ + searchBlock(id: string, floorId?: string | Array, showDisable?: boolean): Array<{ floorId: string, index: number, x: number, y: number, block: Block }> + + /** + * 根据给定的筛选函数搜索全部满足条件的图块 + * @example core.searchBlockWithFilter(function (block) { return block.event.id.endsWith('Door'); }); // 搜索当前地图的所有门 + * @param blockFilter 筛选函数,可接受block输入,应当返回一个boolean值 + * @param floorId 地图id,不填视为当前地图 + * @param showDisable 隐藏点是否计入,true表示计入 + * @returns 一个详尽的数组 + */ + searchBlockWithFilter(blockFilter: (block: Block) => boolean, floorId?: string | Array, showDisable?: boolean): Array<{ floorId: string, index: number, x: number, y: number, block: Block }> + + /** + * 显示(隐藏或显示的)图块,此函数将被“显示事件”指令和勾选了“不消失”的“移动/跳跃事件”指令(如阻击怪)的终点调用 + * @example core.showBlock(0, 0); // 显示地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + showBlock(x: number, y: number, floorId?: string): void + + /** + * 隐藏一个图块,对应于「隐藏事件」且不删除 + * @example core.hideBlock(0, 0); // 隐藏地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + hideBlock(x: number, y: number, floorId?: string): void + + /** + * 删除一个图块,对应于「隐藏事件」并同时删除 + * @example core.removeBlock(0, 0); // 尝试删除地图左上角的图块 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + removeBlock(x: number, y: number, floorId?: string): void + + /** + * 转变图块 + * @example core.setBlock(1, 0, 0); // 把地图左上角变成黄墙 + * @param number 新图块的数字(也支持纯数字字符串如'1')或id + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + setBlock(number: number | string, x: number, y: number, floorId?: string): void + + /** + * 批量替换图块 + * @example core.replaceBlock(21, 22, core.floorIds); // 把游戏中地上当前所有的黄钥匙都变成蓝钥匙 + * @param fromNumber 旧图块的数字 + * @param toNumber 新图块的数字 + * @param floorId 地图id或其数组,不填视为当前地图 + */ + replaceBlock(fromNumber: number, toNumber: number, floorId?: string | Array): void + + /** + * 转变图层块 + * @example core.setBgFgBlock('bg', 167, 6, 6); // 把当前地图背景层的中心块改为滑冰 + * @param name 背景还是前景 + * @param number 新图层块的数字(也支持纯数字字符串如'1')或id + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 地图id,不填视为当前地图 + */ + setBgFgBlock(name: 'bg' | 'fg', number: number | string, x: number, y: number, floorId?: string): void + + /** + * 移动图块 + * @example core.moveBlock(0, 0, ['down']); // 令地图左上角的图块下移一格,用时半秒,再花半秒淡出 + * @param x 起点的横坐标 + * @param y 起点的纵坐标 + * @param steps 步伐数组 + * @param time 单步和淡出用时,单位为毫秒。不填视为半秒 + * @param keep 是否不淡出,true表示不淡出 + * @param callback 移动或淡出后的回调函数,可选 + */ + moveBlock(x: number, y: number, steps: step[], time?: number, keep?: boolean, callback?: () => void): void + + /** + * 跳跃图块;从V2.7开始不再有音效 + * @example core.jumpBlock(0, 0, 0, 0); // 令地图左上角的图块原地跳跃半秒,再花半秒淡出 + * @param sx 起点的横坐标 + * @param sy 起点的纵坐标 + * @param ex 终点的横坐标 + * @param ey 终点的纵坐标 + * @param time 单步和淡出用时,单位为毫秒。不填视为半秒 + * @param keep 是否不淡出,true表示不淡出 + * @param callback 落地或淡出后的回调函数,可选 + */ + jumpBlock(sx: number, sy: number, ex: number, ey: number, time?: number, keep?: boolean, callback?: () => void): void + + /** + * 播放动画,注意即使指定了主角的坐标也不会跟随主角移动,如有需要请使用core.drawHeroAnimate(name, callback)函数 + * @example core.drawAnimate('attack', core.nextX(), core.nextY(), false, core.vibrate); // 在主角面前一格播放普攻动画,动画停止后视野左右抖动1秒 + * @param name 动画文件名,不含后缀 + * @param x 绝对横坐标 + * @param y 绝对纵坐标 + * @param alignWindow 是否是相对窗口的坐标 + * @param callback 动画停止后的回调函数,可选 + * @returns 一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + */ + drawAnimate(name: string, x: number, y: number, alignWindow: boolean, callback?: () => void): number + + /** + * 播放跟随勇士的动画 + * @param name 动画名 + * @param callback 动画停止后的回调函数,可选 + * @returns 一个数字,可作为core.stopAnimate()的参数来立即停止播放(届时还可选择是否执行此次播放的回调函数) + */ + drawHeroAnimate(name: string, callback?: () => void): number + + /** + * 立刻停止一个动画播放 + * @param id 播放动画的编号,即drawAnimate或drawHeroAnimate返回值 + * @param doCallback 是否执行该动画的回调函数 + */ + stopAnimate(id?: number, doCallback?: boolean): void + + /** 获得当前正在播放的所有(指定)动画的id列表 */ + getPlayingAnimates(name?: string): Array + + /** 加载某个楼层(从剧本或存档中) */ + loadFloor(floorId: string, map?: any): ResolvedMap + + /** 根据需求解析出blocks */ + extractBlocks(map?: any): void + + /** 根据需求为UI解析出blocks */ + extractBlocks(map?: any, flags?: any): void + + /** 根据数字获得图块 */ + getBlockByNumber(number: number): Block + + /** 根据ID获得图块 */ + getBlockById(id: string): Block + + /** 获得当前事件点的ID */ + getIdOfThis(id?: string): string + + /** 初始化一个图块 */ + initBlock(x?: number, y?: number, id?: string | number, addInfo?: boolean, eventFloor?: any): Block + + /** 压缩地图 */ + compressMap(mapArr?: any, floorId?: string): object + + /** 解压缩地图 */ + decompressMap(mapArr?: any, floorId?: string): object + + /** 将当前地图重新变成数字,以便于存档 */ + saveMap(floorId?: string): any + + /** 将存档中的地图信息重新读取出来 */ + loadMap(data?: any, floorId?: string, flags?: any): object + + /** 更改地图画布的尺寸 */ + resizeMap(floorId?: string): void + + /** 以x,y的形式返回每个点的事件 */ + getMapBlocksObj(floorId?: string, noCache?: boolean): object + + /** 获得某些点可否通行的信息 */ + canMoveDirectlyArray(locs?: any): object + + /** 绘制一个图块 */ + drawBlock(block?: any, animate?: any): void + + /** 生成groundPattern */ + generateGroundPattern(floorId?: string): void + + /** 某个点是否存在NPC */ + npcExists(x?: number, y?: number, floorId?: string): boolean + + /** 某个点是否存在(指定的)地形 */ + terrainExists(x?: number, y?: number, id?: string, floorId?: string): boolean + + /** 某个点是否存在楼梯 */ + stairExists(x?: number, y?: number, floorId?: string): boolean + + /** 当前位置是否在楼梯边;在楼传平面塔模式下对箭头也有效 */ + nearStair(): boolean + + /** 某个点是否存在(指定的)怪物 */ + enemyExists(x?: number, y?: number, id?: string, floorId?: string): boolean + + /** 获得某个点的block */ + getBlock(x?: number, y?: number, floorId?: string, showDisable?: boolean): Block + + /** 获得某个图块或素材的信息,包括ID,cls,图片,坐标,faceIds等等 */ + getBlockInfo(block?: any): any + + /** 获得某个图块对应行走图朝向向下的那一项的id;如果不存在行走图绑定则返回自身id */ + getFaceDownId(block?: any): string + + /** 根据图块的索引来隐藏图块 */ + hideBlockByIndex(index?: any, floorId?: string): void + + /** 一次性隐藏多个block */ + hideBlockByIndexes(indexes?: any, floorId?: string): void + + /** 根据block的索引(尽可能)删除该块 */ + removeBlockByIndex(index?: any, floorId?: string): void + + /** 一次性删除多个block */ + removeBlockByIndexes(indexes?: any, floorId?: string): void + + /** 显示前景/背景地图 */ + showBgFgMap(name?: string, loc?: any, floorId?: string, callback?: () => any): void + + /** 隐藏前景/背景地图 */ + hideBgFgMap(name?: string, loc?: any, floorId?: string, callback?: () => any): void + + /** 显示一个楼层贴图 */ + showFloorImage(loc?: any, floorId?: string, callback?: () => any): void + + /** 隐藏一个楼层贴图 */ + hideFloorImage(loc?: any, floorId?: string, callback?: () => any): void + + /** 动画形式转变某点图块 */ + animateSetBlock(number?: number | string, x?: number, y?: number, floorId?: string, time?: number, callback?: () => any): void + + /** 动画形式同时转变若干点图块 */ + animateSetBlocks(number?: number | string, locs?: any, floorId?: string, time?: number, callback?: () => any): void + + /** 事件转向 */ + turnBlock(direction?: string, x?: number, y?: number, floorId?: string): void + + /** 重置地图 */ + resetMap(floorId?: string | string[]): void + + /** 显示/隐藏某个块时的动画效果 */ + animateBlock(loc?: any, type?: any, time?: any, callback?: () => any): void + + /** 添加一个全局动画 */ + addGlobalAnimate(block?: any): void + + /** 删除一个或所有全局动画 */ + removeGlobalAnimate(x?: number, y?: number, name?: string): void + + /** 绘制UI层的box动画 */ + drawBoxAnimate(): void +} + +/** @file loader.js 主要负责资源的加载 */ +declare class loader { + /** 加载一系列图片 */ + loadImages(dir: any, names: any, toSave: any, callback?: () => any): any + + /** 加载某一张图片 */ + loadImage(dir: any, imgName?: any, callback?: () => any): any + + /** 从zip中加载一系列图片 */ + loadImagesFromZip(url?: any, names?: any, toSave?: any, onprogress?: any, onfinished?: any): any + + /** 加载一个音乐 */ + loadOneMusic(name?: string): any + + /** 加载一个音效 */ + loadOneSound(name?: string): any + + /** 加载一个bgm */ + loadBgm(name?: string): any + + /** 释放一个bgm的缓存 */ + freeBgm(name?: string): any +} + +/** @file items.js 主要负责一切和道具相关的内容。 */ +declare class items { + + /** + * 即捡即用类的道具获得时的效果 + * @example core.getItemEffect('redPotion', 10) // 执行获得10瓶红血的效果 + * @param itemId 道具id + * @param itemNum 道具数量,可选,默认为1 + */ + getItemEffect(itemId: string, itemNum?: number): void + + /** + * 即捡即用类的道具获得时的额外提示 + * @example core.getItemEffectTip(redPotion) // (获得 红血瓶)',生命+100' + * @param itemId 道具id + * @returns 图块属性itemEffectTip的内容 + */ + getItemEffectTip(itemId: string): string + + /** + * 使用一个道具 + * @example core.useItem('pickaxe', true) // 使用破墙镐,不计入录像,无回调 + * @param itemId 道具id + * @param noRoute 是否不计入录像,快捷键使用的请填true,否则可省略 + * @param callback 道具使用完毕或使用失败后的回调函数 + */ + useItem(itemId: string, noRoute?: boolean, callback?: () => void): void + + /** + * 检查能否使用某种道具 + * @example core.canUseItem('pickaxe') // 能否使用破墙镐 + * @param itemId 道具id + * @returns true表示可以使用 + */ + canUseItem(itemId: string): boolean + + /** + * 统计某种道具的持有量 + * @example core.itemCount('yellowKey') // 持有多少把黄钥匙 + * @param itemId 道具id + * @returns 该种道具的持有量,不包括已穿戴的装备 + */ + itemCount(itemId: string): number + + /** + * 检查主角是否持有某种道具(不包括已穿戴的装备) + * @example core.hasItem('yellowKey') // 主角是否持有黄钥匙 + * @param itemId 道具id + * @returns true表示持有 + */ + hasItem(itemId: string): boolean + + /** + * 检查主角是否穿戴着某件装备 + * @example core.hasEquip('sword5') // 主角是否装备了神圣剑 + * @param itemId 装备id + * @returns true表示已装备 + */ + hasEquip(itemId: string): boolean + + /** + * 检查主角某种类型的装备目前是什么 + * @example core.getEquip(1) // 主角目前装备了什么盾牌 + * @param equipType 装备类型,自然数 + * @returns 装备id,null表示未穿戴 + */ + getEquip(equipType: number): string + + /** + * 设置某种道具的持有量 + * @example core.setItem('yellowKey', 3) // 设置黄钥匙为3把 + * @param itemId 道具id + * @param itemNum 新的持有量,可选,自然数,默认为0 + */ + setItem(itemId: string, itemNum?: number): void + + /** + * 静默增减某种道具的持有量 不会更新游戏画面或是显示提示 + * @example core.addItem('yellowKey', -2) // 没收两把黄钥匙 + * @param itemId 道具id + * @param itemNum 增加量,负数表示没收 + */ + addItem(itemId: string, itemNum?: number): void + + /** + * 判定某件装备的类型 + * @example core.getEquipTypeById('shield5') // 1(盾牌) + * @param equipId 装备id + * @returns 类型编号,自然数 + */ + getEquipTypeById(equipId: string): number | string + + /** + * 检查能否穿上某件装备 + * @example core.canEquip('sword5', true) // 主角可以装备神圣剑吗,如果不能会有提示 + * @param equipId 装备id + * @param hint 无法穿上时是否提示(比如是因为未持有还是别的什么原因) + * @returns true表示可以穿上,false表示无法穿上 + */ + canEquip(equipId: string, hint: boolean): boolean + + /** + * 尝试穿上某件背包里的装备并提示 + * @example core.loadEquip('sword5') // 尝试装备上背包里的神圣剑,无回调 + * @param equipId 装备id + * @param callback 穿戴成功或失败后的回调函数 + */ + loadEquip(equipId: string, callback?: () => void): void + + /** + * 脱下某个类型的装备 + * @example core.unloadEquip(1) // 卸下盾牌,无回调 + * @param equipType 装备类型编号,自然数 + * @param callback 卸下装备后的回调函数 + */ + unloadEquip(equipType: number, callback?: () => void): void + + /** + * 比较两件(类型可不同)装备的优劣 + * @example core.compareEquipment('sword5', 'shield5') // 比较神圣剑和神圣盾的优劣 + * @param compareEquipId 装备甲的id + * @param beComparedEquipId 装备乙的id + * @returns 两装备的各属性差,甲减乙,0省略 + */ + compareEquipment(compareEquipId: string, beComparedEquipId: string): { [key: string]: number } + + /** + * 保存当前套装 + * @example core.quickSaveEquip(1) // 将当前套装保存为1号套装 + * @param index 套装编号,自然数 + */ + quickSaveEquip(index: number): void + + /** + * 快速换装 + * @example core.quickLoadEquip(1) // 快速换上1号套装 + * @param index 套装编号,自然数 + */ + quickLoadEquip(index: number): void + + /** 获得所有道具 */ + getItems(): void + + /** 删除某个物品 */ + removeItem(itemId?: string, itemNum?: number): void + + /** 根据类型获得一个可用的装备孔 */ + getEquipTypeByName(name?: string): void + + /** + * 设置某个装备的属性并计入存档 + * @example core.setEquip('sword1', 'value', 'atk', 300, '+='); // 设置铁剑的攻击力数值再加300 + * @param equipId 装备id + * @param valueType 增幅类型,只能是value(数值)或percentage(百分比) + * @param name 要修改的属性名称,如atk + * @param value 要修改到的属性数值 + * @param operator 操作符,可选,如+=表示在原始值上增加 + * @param prefix 独立开关前缀,一般不需要 + */ + setEquip(equipId: string, valueType: string, name: string, value: any, operator?: string, prefix?: string): void +} + +/** @file ui.js 主要用来进行UI窗口的绘制,如对话框、怪物手册、楼传器、存读档界面等等。*/ +declare class ui { + + /** + * 根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。 + * 也可以传画布的context自身,则返回自己。 + */ + getContextByName(canvas: CtxRefer): CanvasRenderingContext2D + + /** + * 清空某个画布图层 + * name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。(下同) + * 如果name也可以是'all',若为all则为清空所有系统画布。 + */ + clearMap(name: CtxRefer, x?: number, y?: number, w?: number, h?: number): void + + /** + * 在某个画布上绘制一段文字 + * @param text 要绘制的文本 + * @param style 绘制的样式 + * @param font 绘制的字体 + */ + fillText(name: CtxRefer, text: string, x: number, y: number, style?: string, font?: string, maxWidth?: number): void + + /** + * 在某个画布上绘制一个描黑边的文字 + * @param text 要绘制的文本 + * @param style 绘制的样式 + * @param strokeStyle 绘制的描边颜色 + * @param font 绘制的字体 + */ + fillBoldText(name: CtxRefer, text: string, x: number, y: number, style?: string, strokeStyle?: string, font?: string, maxWidth?: number, lineWidth?: number): void + + /** + * 绘制一个矩形。style可选为绘制样式 + * @param style 绘制的样式 + * @param angle 旋转角度,弧度制 + */ + fillRect(name: CtxRefer, x: number, y: number, width: number, height: number, style?: string, angle?: number): void + + /** + * 绘制一个矩形的边框 + * @param style 绘制的样式 + */ + strokeRect(name: CtxRefer, x: number, y: number, width: number, height: number, style: string, lineWidth?: number, angle?: number): void + + /** + * 动态创建一个画布。name为要创建的画布名,如果已存在则会直接取用当前存在的。 + * x,y为创建的画布相对窗口左上角的像素坐标,width,height为创建的长宽。 + * zIndex为创建的纵向高度(关系到画布之间的覆盖),z值高的将覆盖z值低的;系统画布的z值可在个性化中查看。 + * 返回创建的画布的context,也可以通过core.dymCanvas[name]调用 + */ + createCanvas(name: string, x: number, y: number, width: number, height: number, zIndex: number): CanvasRenderingContext2D + + /** 重新定位一个自定义画布 */ + relocateCanvas(name: string, x: number, y: number, useDelta: boolean): void + + /** + * 重新设置一个自定义画布的大小 + * @param styleOnly 是否只修改style,而不修改元素上的长宽,如果是true,会出现模糊现象 + * @param isTempCanvas 是否是临时画布,如果填true,会将临时画布修改为高清画布 + */ + resizeCanvas(name: string, x?: number, y?: number, styleOnly?: boolean, isTempCanvas?: boolean): void + + /** 设置一个自定义画布的旋转角度 */ + rotateCanvas(name: CtxRefer, angle: number, centerX?: number, centerY?: number): void + + /** 删除一个自定义画布 */ + deleteCanvas(name: string | ((name: string) => boolean)): void + + /** 清空所有的自定义画布 */ + deleteAllCanvas(): void + + /** + * 在一个画布上绘制图片 + * 后面的8个坐标参数与canvas的drawImage的八个参数完全相同。 + * 请查看 http://www.w3school.com.cn/html5/canvas_drawimage.asp 了解更多。 + * @param name 可以是系统画布之一,也可以是任意自定义动态创建的画布名 画布名称或者画布的context + * @param image 要绘制的图片,可以是一个全塔属性中定义的图片名(会从images中去获取),图片本身,或者一个画布。 + */ + drawImage(name: CtxRefer, + image: CanvasImageSource | string, dx: number, dy: number): void + drawImage(name: CtxRefer, + image: CanvasImageSource | string, dx: number, dy: number, dw: number, dh: number): void + drawImage(name: CtxRefer, + image: CanvasImageSource | string, + sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void + + /** 根据最大宽度自动缩小字体 */ + setFontForMaxWidth(name: string | CanvasRenderingContext2D, text: string, maxWidth: number, font?: any): string + + /** 在某个canvas上绘制一个圆角矩形 */ + fillRoundRect(name: string | CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, style?: string, angle?: number): void + + /** 在某个canvas上绘制一个圆角矩形的边框 */ + strokeRoundRect(name: string | CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, style?: string, lineWidth?: number, angle?: number): void + + /** 在某个canvas上绘制一个多边形 */ + fillPolygon(name: string | CanvasRenderingContext2D, nodes?: any, style?: string): void + + /** 在某个canvas上绘制一个多边形的边框 */ + strokePolygon(name: string | CanvasRenderingContext2D, nodes?: any, style?: string, lineWidth?: number): void + + /** 在某个canvas上绘制一个椭圆 */ + fillEllipse(name: string | CanvasRenderingContext2D, x: number, y: number, a: number, b: number, angle?: number, style?: any): void + + /** 在某个canvas上绘制一个圆 */ + fillCircle(name: string | CanvasRenderingContext2D, x: number, y: number, r: number, style?: string): void + + /** 在某个canvas上绘制一个椭圆的边框 */ + strokeEllipse(name: string | CanvasRenderingContext2D, x: number, y: number, a: number, b: number, angle?: number, style?: string, lineWidth?: number): void + + /** 在某个canvas上绘制一个圆的边框 */ + strokeCircle(name: string | CanvasRenderingContext2D, x: number, y: number, r: any, style?: string, lineWidth?: number): void + + /** 在某个canvas上绘制一个扇形 */ + fillArc(name: string | CanvasRenderingContext2D, x: number, y: number, r: number, start: number, end: number, style?: string): void + + /** 在某个canvas上绘制一段弧 */ + strokeArc(name: string | CanvasRenderingContext2D, x: number, y: number, r: number, start: number, end: number, style?: string, lineWidth?: number): void + + /** 保存某个canvas状态 */ + saveCanvas(name: string | CanvasRenderingContext2D): void + + /** 加载某个canvas状态 */ + loadCanvas(name: string | CanvasRenderingContext2D): void + + /** 设置某个canvas的baseline */ + setTextBaseline(name: string | CanvasRenderingContext2D, baseline: any): void + + /** 字符串自动换行的分割 */ + splitLines(name: string | CanvasRenderingContext2D, text: string, maxWidth?: number, font?: string): void + + /** 在某个canvas上绘制一个图标 */ + drawIcon(name: string | CanvasRenderingContext2D, id: string, x: number, y: number, w?: number, h?: number, frame?: number): void + + /** 结束一切事件和绘制,关闭UI窗口,返回游戏进程 */ + closePanel(): void + + /** 清空UI层内容 */ + clearUI(): void + + /** + * 左上角绘制一段提示 + * @param text 要提示的文字内容,支持 ${} 语法 + * @param id 要绘制的图标ID + * @param frame 要绘制图标的第几帧 + */ + drawTip(text: string, id?: string, frame?: number): void + + /** 地图中间绘制一段文字 */ + drawText(contents: string, callback?: () => any): void + + /** 自绘选择光标 */ + drawUIEventSelector(code: number, background: string, x: number, y: number, w: number, h: number, z?: number): void + + /** 清除一个或多个选择光标 */ + clearUIEventSelector(code: number | number[]): void + + /** 绘制一个确认框 */ + drawConfirmBox(text: string, yesCallback?: () => void, noCallback?: () => void): void + + /** 绘制WindowSkin */ + drawWindowSkin(background: any, ctx: string | CanvasRenderingContext2D, x: number, y: number, w: string, h: string, direction?: any, px?: any, py?: any): void + + /** 绘制一个背景图,可绘制winskin或纯色背景;支持小箭头绘制 */ + drawBackground(left: string, top: string, right: string, bottom: string, posInfo?: any): void + + /** + * 绘制一段文字到某个画布上面 + * @param ctx 要绘制到的画布 + * @param content 要绘制的内容;转义字符只允许保留 \n, \r[...], \i[...], \c[...], \d, \e + * @param config 绘制配置项,目前暂时包含如下内容(均为可选) + * left, top:起始点位置;maxWidth:单行最大宽度;color:默认颜色;align:左中右 + * fontSize:字体大小;lineHeight:行高;time:打字机间隔;font:默认字体名 + * @returns 绘制信息 + */ + drawTextContent(ctx: string | CanvasRenderingContext2D, content: string, config: TextContentConfig): any + + /** 获得某段文字的预计绘制高度;参见 drawTextContent */ + getTextContentHeight(content: string, config?: any): void + + /** 绘制一个对话框 */ + drawTextBox(content: string, config?: any): void + + /** 绘制滚动字幕 */ + drawScrollText(content: string, time: number, lineHeight?: number, callback?: () => any): void + + /** 文本图片化 */ + textImage(content: string, lineHeight?: number): any + + /** 绘制一个选项界面 */ + drawChoices(content: string, choices: any): void + + /** 绘制等待界面 */ + drawWaiting(text: string): void + + /** 绘制分页 */ + drawPagination(page?: any, totalPage?: any, y?: number): void + + /** 绘制怪物手册 */ + drawBook(index?: any): void + + /** 绘制楼层传送器 */ + drawFly(page?: any): void + + /** 获得所有应该在道具栏显示的某个类型道具 */ + getToolboxItems(cls: string): string[] + + /** 绘制状态栏 */ + drawStatusBar(): void + + /** 绘制灯光效果 */ + drawLight(name: string | CanvasRenderingContext2D, color?: any, lights?: any, lightDec?: number): void + + /** 在某个canvas上绘制一条线 */ + drawLine(name: string | CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, style?: string, lineWidth?: number): void + + /** 在某个canvas上绘制一个箭头 */ + drawArrow(name: string | CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, style?: string, lineWidth?: number): void + + /** 设置某个canvas的文字字体 */ + setFont(name: string | CanvasRenderingContext2D, font: string): void + + /** 设置某个canvas的线宽度 */ + setLineWidth(name: string | CanvasRenderingContext2D, lineWidth: number): void + + /** 设置某个canvas的alpha值;返回设置之前画布的不透明度。 */ + setAlpha(name: string | CanvasRenderingContext2D, alpha: number): number + + /** 设置某个canvas的filter属性 */ + setFilter(name: string | CanvasRenderingContext2D, filter: any): void + + /** 设置某个canvas的透明度;尽量不要使用本函数,而是全部换成setAlpha实现 */ + setOpacity(name: string | CanvasRenderingContext2D, opacity: number): void + + /** 设置某个canvas的绘制属性(如颜色等) */ + setFillStyle(name: string | CanvasRenderingContext2D, style: string): void + + /** 设置某个canvas边框属性 */ + setStrokeStyle(name: string | CanvasRenderingContext2D, style: string): void + + /** 设置某个canvas的对齐 */ + setTextAlign(name: string | CanvasRenderingContext2D, align: string): void + + /** 计算某段文字的宽度 */ + calWidth(name: string | CanvasRenderingContext2D, text: string, font?: string): number +} + +/** 工具类 主要用来进行一些辅助函数的计算 */ +declare class utils { + + /** + * 将一段文字中的${}(表达式)进行替换。 + * @example core.replaceText('衬衫的价格是${status:hp}镑${item:yellowKey}便士。'); // 把主角的生命值和持有的黄钥匙数量代入这句话 + * @param text 模板字符串,可以使用${}计算js表达式,支持“状态、物品、变量、独立开关、全局存储、图块id、图块类型、敌人数据、装备id”等量参与运算 + * @returns 替换完毕后的字符串 + */ + replaceText(text: string, prefix?: string): string + + /** + * 对一个表达式中的特殊规则进行替换,如status:xxx等。 + * @example core.replaceValue('衬衫的价格是${status:hp}镑${item:yellowKey}便士。'); // 把这两个冒号表达式替换为core.getStatus('hp')和core.itemCount('yellowKey')这样的函数调用 + * @param value 模板字符串,注意独立开关不会被替换 + * @returns 替换完毕后的字符串 + */ + replaceValue(value: string): string + + /** + * 计算一个表达式的值,支持status:xxx等的计算。 + * @example core.calValue('status:hp + status:def'); // 计算主角的生命值加防御力 + * @param value 待求值的表达式 + * @param prefix 独立开关前缀,一般可省略 + * @returns 求出的值 + */ + calValue(value: string, prefix?: string): any + + /** + * 将b(可以是另一个数组)插入数组a的开头,此函数用于弥补a.unshift(b)中b只能是单项的不足。 + * @example core.unshift(todo, {type: 'unfollow'}); // 在事件指令数组todo的开头插入“取消所有跟随者”指令 + * @param a 原数组 + * @param b 待插入的新首项或前缀数组 + * @returns 插入完毕后的新数组,它是改变原数组a本身得到的 + */ + unshift(a: any[], b: any): any[] + + /** + * 将b(可以是另一个数组)插入数组a的末尾,此函数用于弥补a.push(b)中b只能是单项的不足。 + * @example core.push(todo, {type: 'unfollow'}); // 在事件指令数组todo的末尾插入“取消所有跟随者”指令 + * @param a 原数组 + * @param b 待插入的新末项或后缀数组 + * @returns 插入完毕后的新数组,它是改变原数组a本身得到的 + */ + push(a: any[], b: any): any[] + + /** + * 设置一个全局存储,适用于global:xxx,录像播放时将忽略此函数。 + * @example core.setBlobal('一周目已通关', true); // 设置全局存储“一周目已通关”为true,方便二周目游戏中的新要素。 + * @param key 全局变量名称,支持中文 + * @param value 全局变量的新值,不填或null表示清除此全局存储 + */ + setGlobal(key: string, value?: any): void + + /** + * 读取一个全局存储,适用于global:xxx,支持录像。 + * @example if (core.getGlobal('一周目已通关', false) === true) core.getItem('dagger'); // 二周目游戏进行到此处时会获得一把屠龙匕首 + * @param key 全局变量名称,支持中文 + * @param defaultValue 可选,当此全局变量不存在或值为null、undefined时,用此值代替 + * @returns 全局变量的值 + */ + getGlobal(key: string, defaultValue?: any): any + + /** + * 深拷贝一个对象(函数将原样返回) + * @example core.clone(core.status.hero, (name, value) => (name == 'items' || typeof value == 'number'), false); // 深拷贝主角的属性和道具 + * @param data 待拷贝对象 + * @param filter 过滤器,可选,表示data为数组或对象时拷贝哪些项或属性,true表示拷贝 + * @param recursion 过滤器是否递归,可选。true表示过滤器也被递归 + * @returns 拷贝的结果,注意函数将原样返回 + */ + clone(data?: T, filter?: (name: string, value: any) => boolean, recursion?: boolean): T + + /** 深拷贝一个1D或2D的数组 */ + cloneArray(data?: Array | Array>): Array | Array> + + /** + * 等比例切分一张图片 + * @example core.splitImage(core.material.images.images['npc48.png'], 32, 48); // 把npc48.png切分成若干32×48px的小人 + * @param image 图片名(支持映射前的中文名)或图片对象(参见上面的例子),获取不到时返回[] + * @param width 子图的宽度,单位为像素。原图总宽度必须是其倍数,不填视为32 + * @param height 子图的高度,单位为像素。原图总高度必须是其倍数,不填视为正方形 + * @returns 子图组成的数组,在原图中呈先行后列,从左到右、从上到下排列。 + */ + splitImage(image?: string | HTMLImageElement, width?: number, height?: number): HTMLImageElement[] + + /** + * 大数字格式化,单位为10000的倍数(w,e,z,j,g),末尾四舍五入 + * @example core.formatBigNumber(123456789, false); // "12346w" + * @param x 原数字 + * @param onMap 可选,true表示用于地图显伤,结果总字符数最多为5,否则最多为6 + * @returns 格式化结果 + */ + formatBigNumber(x: number, onMap?: boolean): string + + /** 变速移动 */ + applyEasing(mode?: string): (number) => number; + + /** + * 颜色数组转十六进制 + * @example core.arrayToRGB([102, 204, 255]); // "#66ccff",加载画面的宣传色 + * @param color 一行三列的数组,各元素必须为不大于255的自然数 + * @returns 该颜色的十六进制表示,使用小写字母 + */ + arrayToRGB(color: [number, number, number]): string + + /** + * 颜色数组转字符串 + * @example core.arrayToRGBA([102, 204, 255]); // "rgba(102,204,255,1)" + * @param color 一行三列或一行四列的数组,前三个元素必须为不大于255的自然数。第四个元素(如果有)必须为0或不大于1的数字,第四个元素不填视为1 + * @returns 该颜色的字符串表示 + */ + arrayToRGBA(color: [number, number, number, number]): string + + /** + * 录像一压,其结果会被再次base64压缩 + * @example core.encodeRoute(core.status.route); // 一压当前录像 + * @param route 原始录像,自定义内容(不予压缩,原样写入)必须由0-9A-Za-z和下划线、冒号组成,所以中文和数组需要用JSON.stringify预处理再base64压缩才能交由一压 + * @returns 一压的结果 + */ + encodeRoute(route: string[]): string + + /** + * 录像解压的最后一步,即一压的逆过程 + * @example core.decodeRoute(core.encodeRoute(core.status.route)); // 一压当前录像再解压-_-| + * @param route 录像解压倒数第二步的结果,即一压的结果 + * @returns 原始录像 + */ + decodeRoute(route: string): string[] + + /** + * 判断一个值是否不为null,undefined和NaN + * @example core.isset(0/0); // false,因为0/0等于NaN + * @param v 待测值,可选 + * @returns false表示待测值为null、undefined、NaN或未填写,true表示为其他值。即!(v == null || v != v) + */ + isset(v?: any): boolean + + /** + * 判定一个数组是否为另一个数组的前缀,用于录像接续播放。请注意函数名没有大写字母 + * @example core.subarray(['ad', '米库', '小精灵', '小破草', '小艾'], ['ad', '米库', '小精灵']); // ['小破草', '小艾'] + * @param a 可能的母数组,不填或比b短将返回null + * @param b 可能的前缀,不填或比a长将返回null + * @returns 如果b不是a的前缀将返回null,否则将返回a去掉此前缀后的剩余数组 + */ + subarray(a?: any[], b?: any[]): any[] | null + + /** + * 判定array是不是一个数组,以及element是否在该数组中。 + * @param array 可能的数组,不为数组或不填将导致返回值为false + * @param element 待查找的元素 + * @returns 如果array为数组且具有element这项,就返回true,否则返回false + */ + inArray(array?: any, element?: any): boolean + + /** + * 将x限定在[a,b]区间内,注意a和b可交换 + * @example core.clamp(1200, 1, 1000); // 1000 + * @param x 原始值,!x为true时x一律视为0 + * @param a 下限值,大于b将导致与b交换 + * @param b 上限值,小于a将导致与a交换 + */ + clamp(x: number, a: number, b: number): number + + /** + * 填写非自绘状态栏 + * @example core.setStatusBarInnerHTML('hp', core.status.hero.hp, 'color: #66CCFF'); // 更新状态栏中的主角生命,使用加载画面的宣传色 + * @param name 状态栏项的名称,如'hp', 'atk', 'def'等。必须是core.statusBar中的一个合法项 + * @param value 要填写的内容,大数字会被格式化为至多6个字符,无中文的内容会被自动设为斜体 + * @param css 额外的css样式,可选。如更改颜色等 + */ + setStatusBarInnerHTML(name: string, value: any, css?: string): void + + /** + * 求字符串的国标码字节数,也可用于等宽字体下文本的宽度测算。请注意样板的默认字体Verdana不是等宽字体 + * @example core.strlen('无敌ad'); // 6 + * @param str 待测字符串 + * @returns 字符串的国标码字节数,每个汉字为2,每个ASCII字符为1 + */ + strlen(str: string): number + + /** + * 通配符匹配,用于搜索图块等批量处理。 + * @example core.playSound(core.matchWildcard('*Key', itemId) ? 'item.mp3' : 'door.mp3'); // 判断捡到的是钥匙还是别的道具,从而播放不同的音效 + * @param pattern 模式串,每个星号表示任意多个(0个起)字符 + * @param string 待测串 + * @returns true表示匹配成功,false表示匹配失败 + */ + matchWildcard(pattern: string, string: string): boolean + + /** + * base64加密 + * @example core.encodeBase64('If you found this note in a small wooden box with a heart on it'); // "SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0" + * @param str 明文 + * @returns 密文 + */ + encodeBase64(str: string): string + + /** + * base64解密 + * @example core.decodeBase64('SWYgeW91IGZvdW5kIHRoaXMgbm90ZSBpbiBhIHNtYWxsIHdvb2RlbiBib3ggd2l0aCBhIGhlYXJ0IG9uIGl0'); // "If you found this note in a small wooden box with a heart on it" + * @param str 密文 + * @returns 明文 + */ + decodeBase64(str: string): string + + /** + * 不支持SL的随机数 + * @exmaple 1 + core.rand(6); // 随机生成一个小于7的正整数,模拟骰子的效果 + * @param num 填正数表示生成小于num的随机自然数,否则生成小于1的随机正数 + * @returns 随机数,即使读档也不会改变结果 + */ + rand(num?: number): number + + /** + * 支持SL的随机数,并计入录像 + * @exmaple 1 + core.rand2(6); // 随机生成一个小于7的正整数,模拟骰子的效果 + * @param num 正整数,0或不填会被视为2147483648 + * @returns 属于 [0, num) 的随机数 + */ + rand2(num?: number): number + + /** + * 弹窗请求下载一个文本文件 + * @example core.download('route.txt', core.status.route); // 弹窗请求下载录像 + * @param filename 文件名 + * @param content 文件内容 + */ + download(filename: string, content: string | String[]): void + + /** + * 显示确认框,类似core.drawConfirmBox() + * @example core.myconfirm('重启游戏?', core.restart); // 弹窗询问玩家是否重启游戏 + * @param hint 弹窗的内容 + * @param yesCallback 确定后的回调函数 + * @param noCallback 取消后的回调函数,可选 + */ + myconfirm(hint: string, yesCallback: () => void, noCallback?: () => void): void + + /** + * 判定深层相等, 会逐层比较每个元素 + * @example core.same(['1', 2], ['1', 2]); // true + */ + same(a?: any, b?: any): boolean + + /** + * 尝试请求读取一个本地文件内容 [异步] + * @param success 成功后的回调 + * @param error 失败后的回调 + * @param readType 不设置则以文本读取,否则以DataUrl形式读取 + */ + readFile(success, error, readType): void + + /** + * 文件读取完毕后的内容处理 [异步] + * @param content + */ + readFileContent(content): void + + /** + * 尝试复制一段文本到剪切板。 + */ + copy(data: string): void + + /** + * 发送一个HTTP请求 [异步] + * @param type 请求类型 + * @param url 目标地址 + * @param formData 如果是POST请求则为表单数据 + * @param success 成功后的回调 + * @param error 失败后的回调 + */ + http(type: 'GET' | 'POST', url: string, formData: FormData, success: () => void, error: () => void): void + + /** 获得浏览器唯一的guid */ + getGuid(): string + + /** 解压缩一个数据 */ + decompress(value: any): any + + /** 设置本地存储 */ + setLocalStorage(key: string, value?: any): void + + /** 获得本地存储 */ + getLocalStorage(key: string, defaultValue?: any): any + + /** 移除本地存储 */ + removeLocalStorage(key: string): void + + /** 往数据库写入一段数据 */ + setLocalForage(key: string, value?: any, successCallback?: () => void, errorCallback?: () => void): void + + /** 从数据库读出一段数据 */ + getLocalForage(key: string, defaultValue?: any, successCallback?: (data: any) => void, errorCallback?: () => void): void + + /** 移除数据库的数据 */ + removeLocalForage(key: string, successCallback?: () => void, errorCallback?: () => void): void + + /** 格式化日期为字符串 */ + formatDate(date: Date): string + + /** 格式化日期为最简字符串 */ + formatDate2(date: Date): string + + /** 格式化时间 */ + formatTime(time: number): string + + /** 两位数显示 */ + setTwoDigits(x: number): string + + /** 格式化文件大小 */ + formatSize(size: number): string + + /** 访问浏览器cookie */ + getCookie(name: string): string + + /** + * 计算应当转向某个方向 + * @param turn 转向的方向 + * @param direction 当前方向 + */ + turnDirection(turn: 'up' | 'down' | 'left' | 'right' | ':left' | ':right' | ':back', direction?: string): string + + /** 是否满足正则表达式 */ + matchRegex(pattern: string, string: string): string + + /** 让用户输入一段文字 */ + myprompt(hint: string, value: string, callback?: (data?: string) => any): void + + /** 动画显示某对象 */ + showWithAnimate(obj?: any, speed?: number, callback?: () => any): void + + /** 动画使某对象消失 */ + hideWithAnimate(obj?: any, speed?: number, callback?: () => any): void + + /** 解压一段内容 */ + unzip(blobOrUrl?: any, success?: (data: any) => void, error?: (error: string) => void, convertToText?: boolean, onprogress?: (loaded: number, total: number) => void): void +} + +/** 和图标相关的函数 */ +declare class icons { + + /** 获得所有图标类型 */ + getIcons(): void + + /** 根据ID获得其类型 */ + getClsFromId(id?: string): string + + /** 获得所有图标的ID */ + getAllIconIds(): void + + /** 根据图块数字或ID获得所在的tileset和坐标信息 */ + getTilesetOffset(id?: string): void +} + +declare class plugin { + +} + +type core = { + /** 地图可视部分大小 */ + readonly __SIZE__: number; + /** 地图像素 */ + readonly __PIXELS__: number; + /** 地图像素的一半 */ + readonly __HALF_SIZE__: number; + /** 游戏素材 */ + readonly material: { + readonly animates: { [key: string]: Animate }, + readonly images: { + airwall: HTMLImageElement + animates: HTMLImageElement + enemys: HTMLImageElement + enemy48: HTMLImageElement + items: HTMLImageElement + npcs: HTMLImageElement + npc48: HTMLImageElement + terrains: HTMLImageElement + autotile: { [x: string]: HTMLImageElement } + images: { [x: string]: HTMLImageElement } + tilesets: { [x: string]: HTMLImageElement } + }, + readonly bgms: { [key: string]: HTMLAudioElement }, + readonly sounds: { [key: string]: HTMLAudioElement }, + readonly ground: CanvasRenderingContext2D + /** + * 怪物信息 + * @example core.material.enemys.greenSlime // 获得绿色史莱姆的属性数据 + */ + readonly enemys: { [key: string]: Enemy }, + /** 道具信息 */ + readonly items: { [key: string]: Item } + readonly icons: { [key: string]: { [key: string]: number } }, + } + readonly timeout: { + turnHeroTimeout: any, + onDownTimeout: any, + sleepTimeout: any, + } + readonly interval: { + heroMoveInterval: any, + onDownInterval: any, + } + readonly animateFrame: { + totalTime: number + totalTimeStart: number + globalAnimate: boolean, + globalTime: number + selectorTime: number + selectorUp: boolean, + animateTime: number + moveTime: number + lastLegTime: number + leftLeg: boolean, + readonly weather: { + time: number + type: any + nodes: [], + data: any + fog: any, + cloud: any, + }, + readonly tips: { + time: number + offset: number + list: [], + lastSize: number + }, + readonly asyncId: {} + } + readonly musicStatus: { + audioContext: AudioContext, + /** 是否播放BGM */bgmStatus: boolean + /** 是否播放SE */soundStatus: boolean + /** 正在播放的BGM */playingBgm: string + /** 上次播放的bgm */lastBgm: string + gainNode: GainNode, + /** 正在播放的SE */playingSounds: {} + /** 音量 */volume: number + /** 缓存BGM内容 */cachedBgms: string[] + /** 缓存的bgm数量 */cachedBgmCount: number + } + readonly platform: { + /** 是否http */isOnline: boolean + /** 是否是PC */isPC: boolean + /** 是否是Android */isAndroid: boolean + /** 是否是iOS */isIOS: boolean + string: string + /** 是否是微信 */isWeChat: boolean + /** 是否是QQ */isQQ: boolean + /** 是否是Chrome */isChrome: boolean + /** 是否支持复制到剪切板 */supportCopy: boolean + + fileInput: null + /** 是否支持FileReader */fileReader: null + /** 读取成功 */successCallback: null + /** 读取失败 */errorCallback: null + } + readonly dom: { [key: string]: HTMLElement } + /** dom样式 */ + readonly domStyle: { + scale: number, + isVertical: boolean, + showStatusBar: boolean, + toolbarBtn: boolean, + } + readonly bigmap: { + canvas: string[], + offsetX: number // in pixel + offsetY: number + posX: number + posY: number + width: number // map width and height + height: number + v2: boolean + threshold: number + extend: number + scale: number + tempCanvas: CanvasRenderingContext2D // A temp canvas for drawing + cacheCanvas: CanvasRenderingContext2D + } + readonly saves: { + saveIndex: number + readonly ids: { [key: number]: boolean } + autosave: { + data: Save[] + max: number + storage: true + time: number + updated: boolean + } + favorite: [] + readonly favoriteName: {} + } + readonly initStatus: gameStatus; + readonly dymCanvas: { [key: string]: CanvasRenderingContext2D } + /** 游戏状态 */ + readonly status: gameStatus + + /** + * 获得所有楼层的信息 + * @example core.floors[core.status.floorId].events // 获得本楼层的所有自定义事件 + */ + readonly floors: { [key: string]: ResolvedMap } + readonly floorIds: string[] + + readonly statusBar: { + readonly icons: { [x: string]: HTMLImageElement } + } + + readonly materials: string[] + + readonly control: control + readonly loader: loader + readonly events: events + readonly enemys: enemys + readonly items: items + readonly maps: maps + readonly ui: ui + readonly utils: utils + readonly icons: icons + readonly actions: actions + readonly plugin: plugin + +} & control & events & loader & enemys & items & maps & ui & utils & icons & actions & plugin + +type main = { + editorOpened: boolean + readonly core: core + readonly dom: { [key: string]: HTMLElement } + /** 游戏版本,发布后会被随机,请勿使用该属性 */ + readonly version: string + readonly useCompress: boolean + readonly savePages: number + readonly mode: 'play' | 'editor' + readonly statusBar: { + images: { [x: string]: HTMLElement } + icons: { [x: string]: number | null | undefined } + [x: string]: HTMLElement | object + } + readonly __VERSION__: string + readonly __VERSION_CODE__: number + readonly images: string[] + + /** 输出内容(极不好用,建议换成console,我甚至不知道样板为什么会有这个东西)*/ + log(e: string | Error, error: boolean): void +} + +declare class Sprite { + + x: number + y: number + width: number + height: number + zIndex: number + reference: 'game' | 'window' + canvas: HTMLCanvasElement + context: CanvasRenderingContext2D + name: string + readonly count: number + + /** 创建一个sprite画布 + * @param reference 参考系,游戏画面或者窗口 + * @param name 可选,sprite的名称,方便通过core.dymCanvas获取 + */ + constructor(x: number, y: number, w: number, h: number, z: number, reference?: 'game' | 'window', name?: string) + + /** 初始化 */ + init(): void + + /** 设置css特效 */ + setCss(css: string): Sprite + + /** + * 移动sprite + * @param isDelta 是否是相对位置,如果是,那么sprite会相对于原先的位置进行移动 + */ + move(x: number, y: number, isDelta?: boolean): Sprite + + /** + * 重新设置sprite的大小 + * @param {boolean} styleOnly 是否只修改css效果,如果是,那么将会不高清,如果不是,那么会清空画布 + */ + resize(w: number, h: number, styleOnly?: boolean): Sprite + + /** 旋转画布 */ + rotate(angle: number, cx?: number, cy?: number): Sprite + + /** 擦除画布 */ + clear(x: number, y: number, w?: number, h?: number): Sprite + + /** 删除 */ + destroy(): void + + /** 添加事件监听器 */ + addEventListener: HTMLCanvasElement['addEventListener'] + + /** 删除事件监听器 */ + removeEventListenr: HTMLCanvasElement['addEventListener'] +} + +declare let main: main +declare let core: core +declare let flags: { [x: string]: any } +declare let hero: HeroStatus \ No newline at end of file diff --git a/runtime.min.d.ts b/runtime.min.d.ts new file mode 100644 index 0000000..5b81aad --- /dev/null +++ b/runtime.min.d.ts @@ -0,0 +1 @@ +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= \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..b1eb035 --- /dev/null +++ b/server.js @@ -0,0 +1,651 @@ +const http = require('http'); +const fs = require('fs/promises'); +const fss = require('fs'); +const path = require('path'); + +const name = (() => { + const data = fss.readFileSync('./project/data.js', 'utf-8'); + const json = JSON.parse( + data + .split(/(\n|\r\n)/) + .slice(1) + .join('\n') + ); + return json.firstData.name; +})(); + +/** 核心服务器 */ +const server = http.createServer(); + +/** 是否需要重新加载浏览器 */ +let needReload = true; + +/** 热重载信息 */ +let hotReloadData = ''; + +/** 是否已经启动了热重载模块 */ +let watched = false; + +/** 是否已经启动了录像调试模块 */ +let replayed = false; + +/** 监听端口 */ +let port = 3000; +const next = () => { + server.listen(port, '127.0.0.1'); + server.on('error', () => { + console.log(`${port}端口已被占用`); + port++; + next(); + }); +}; +next(); + +let repStart; + +const listenedFloors = []; + +// ----- GET file + +/** + * 请求文件 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + * @param {string} path + */ +async function getFile(req, res, path) { + try { + const data = await fs.readFile(path); + if (path.endsWith('.js')) + res.writeHead(200, { 'Content-type': 'text/javascript' }); + if (path.endsWith('.css')) + res.writeHead(200, { 'Content-type': 'text/css' }); + if (path.endsWith('.html')) + res.writeHead(200, { 'Content-type': 'text/html' }); + return res.end(data), true; + } catch { + return false; + } +} + +/** + * 高层塔优化及动画加载 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + * @param {string[]} ids + * @param {string} suffix 后缀名 + * @param {string} dir 文件夹路径 + * @param {string} join 分隔符 + */ +async function getAll(req, res, ids, suffix, dir, join) { + let data = {}; + const tasks = ids.map(v => { + return new Promise(res => { + const d = path.resolve(__dirname, `${dir}${v}${suffix}`); + try { + fs.readFile(d).then(vv => { + data[v] = vv; + res(`${v} pack success.`); + }); + } catch { + throw new ReferenceError(`The file ${d} does not exists.`); + } + }); + }); + await Promise.all(tasks); + const result = ids.map(v => data[v]); + return res.end(result.join(join)), true; +} + +// ----- 样板的fs功能 + +/** + * 获取POST的数据 + * @param {http.IncomingMessage} req + */ +async function getPostData(req) { + let data = ''; + await new Promise(res => { + req.on('data', chunk => { + data += chunk.toString(); + }); + req.on('end', res); + }); + return data; +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function readDir(req, res) { + const data = await getPostData(req); + const dir = path.resolve(__dirname, data.toString().slice(5)); + try { + const info = await fs.readdir(dir); + res.end(JSON.stringify(info)); + } catch (e) { + console.error(e); + res.end(`error: Read dir ${dir} fail. Does the dir exists?`); + } +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function mkdir(req, res) { + const data = await getPostData(req); + const dir = path.resolve(__dirname, data.toString().slice(5)); + try { + await fs.mkdir(dir); + } catch (e) {} + res.end(); +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function readFile(req, res) { + const data = (await getPostData(req)).toString(); + const dir = path.resolve(__dirname, data.split('&name=')[1]); + try { + const type = /^type=(utf8|base64)/.exec(data)[0]; + const info = await fs.readFile(dir, { encoding: type.slice(5) }); + res.end(info); + } catch (e) { + res.end(); + } +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function writeFile(req, res) { + const data = (await getPostData(req)).toString(); + const name = data.split('&name=')[1].split('&value=')[0]; + const dir = path.resolve(__dirname, name); + try { + const type = /^type=(utf8|base64)/.exec(data)[0].slice(5); + const value = /&value=[^]+/.exec(data)[0].slice(7); + await fs.writeFile(dir, value, { encoding: type }); + testWatchFloor(name); + } catch (e) { + console.error(e); + res.end( + `error: Write file ${dir} fail. Does the parent folder exists?` + ); + } + res.end(); +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function rm(req, res) { + const data = (await getPostData(req)).toString(); + const dir = path.resolve(__dirname, data.slice(5)); + try { + await fs.rm(dir); + } catch (e) { + console.error(e); + res.end(`error: Remove file ${dir} fail. Does this file exists?`); + } + res.end(); +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function moveFile(req, res) { + const data = (await getPostData(req)).toString(); + const info = data.split('&dest='); + const src = path.resolve(__dirname, info[0].slice(4)); + const dest = info[1]; + try { + const data = await fs.readFile(src); + await fs.writeFile(path.resolve(__dirname, dest), data); + await fs.rm(src); + } catch (e) { + console.error(e); + res.end(`error: Move file ${dir} fail.`); + } + res.end(); +} + +/** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function writeMultiFiles(req, res) { + const data = (await getPostData(req)).toString(); + const names = /name=[^]+&value=/.exec(data)[0].slice(5, -7).split(';'); + const value = /&value=[^]+/.exec(data)[0].slice(7).split(';'); + + const tasks = names.map((v, i) => { + try { + return new Promise(res => { + fs.writeFile( + path.resolve(__dirname, v), + value[i], + 'base64' // 多文件是base64写入的 + ).then(v => { + testWatchFloor(v); + res(`write ${v} success.`); + }); + }); + } catch { + console.error(e); + res.end(`error: Write multi files fail.`); + } + }); + await Promise.all(tasks); + res.end(); +} + +// ----- extract path & utils + +/** + * 解析路径 + * @param {...string} dirs + * @returns {Promise} + */ +async function extract(...dirs) { + const res = []; + const tasks = dirs.map(v => { + return new Promise(resolve => { + if (v.endsWith('/')) { + // 匹配路径 + const dir = path.resolve(__dirname, v.slice(0, -1)); + fs.readdir(dir).then(files => { + const all = files + .filter(v => v !== 'thirdparty') // 排除第三方库 + .map(vv => v + vv); + + res.push(...all); + resolve('success'); + }); + } else if (/\/\*.\w+$/.test(v)) { + // 匹配文件夹中的后缀名 + const suffix = /\.\w+$/.exec(v)[0]; + const d = v.split(`/*${suffix}`)[0]; + const dir = path.resolve(__dirname, d); + fs.readdir(dir).then(files => { + const all = files + .filter(v => v.endsWith(suffix)) + .map(v => `${d === '' ? '' : d + '/'}${v}`); + + res.push(...all); + resolve('success'); + }); + } else { + res.push(v); + resolve('success'); + } + }); + }); + await Promise.all(tasks); + return res; +} + +// ----- hot reload + +/** + * 监听文件变化 + */ +async function watch() { + // 需要重新加载的文件 + const refresh = await extract('main.js', 'index.html', 'libs/'); + const option = { + interval: 1000 + }; + refresh.forEach(v => { + const dir = path.resolve(__dirname, v); + fss.watchFile(dir, option, () => { + needReload = true; + console.log(`change: ${v}`); + }); + }); + + // css 热重载 + const css = await extract('/*.css'); + css.forEach(v => { + const dir = path.resolve(__dirname, v); + fss.watchFile(dir, option, () => { + hotReloadData += `@@css:${v}`; + console.log(`css hot reload: ${v}`); + }); + }); + + // 楼层 热重载 + // 注意这里要逐个监听,并通过创建文件来监听文件改变 + const floors = await extract('project/floors/*.js'); + floors.forEach(v => { + watchOneFloor(v.slice(15)); + }); + + // 脚本编辑 及 插件 热重载 + const scripts = await extract('project/functions.js', 'project/plugins.js'); + scripts.forEach(v => { + const dir = path.resolve(__dirname, v); + const type = v.split('/').at(-1).slice(0, -3); + fss.watchFile(dir, option, () => { + hotReloadData += `@@script:${type}`; + console.log(`script hot reload: ${type}.js`); + }); + }); + + // 数据热重载 + const datas = (await extract('project/*.js')).filter( + v => !v.endsWith('functions.js') && !v.endsWith('plugins.js') + ); + datas.forEach(v => { + const dir = path.resolve(__dirname, v); + const type = v.split('/').at(-1).slice(0, -3); + fss.watchFile(dir, option, () => { + hotReloadData += `@@data:${type}`; + console.log(`data hot reload: ${type}`); + }); + }); +} + +/** + * 检测是否是楼层文件并进行监听 + * @param {string} url 要测试的路径 + */ +function testWatchFloor(url) { + if (/project(\/|\\)floors(\/|\\).*\.js/.test(url)) { + const f = url.slice(15); + if (!listenedFloors.includes(f.slice(0, -3))) { + watchOneFloor(f); + } + } +} + +/** + * 监听一个楼层文件 + * @param {string} file 要监听的文件 + */ +function watchOneFloor(file) { + if (!/.*\.js/.test(file)) return; + const f = file.slice(0, -3); + listenedFloors.push(file.slice(0, -3)); + fss.watchFile(`project/floors/${file}`, { interval: 1000 }, () => { + const floorId = f; + if (hotReloadData.includes(`@@floor:${floorId}`)) return; + hotReloadData += `@@floor:${floorId}`; + console.log(`floor hot reload: ${floorId}`); + }); +} + +/** + * 修改部分文件后重新加载及热重载 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +function reload(req, res, hot = false) { + req.on('data', chunk => { + if (chunk.toString() === 'test' && !watched) { + watch(); + watched = true; + console.log(`服务器热重载模块已开始服务`); + } + }); + + req.on('end', () => { + if (!hot) { + res.end(`${needReload}`); + needReload = false; + } else { + res.end(hotReloadData); + hotReloadData = ''; + } + }); +} + +// ----- replay debugger + +/** + * 录像调试 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +function replay(req, res) { + req.on('data', async chunk => { + if (chunk.toString() === 'test' && !replayed) { + replayed = true; + try { + await fs.mkdir(path.resolve(__dirname, '_replay')); + await fs.mkdir(path.resolve(__dirname, '_replay/status')); + await fs.mkdir(path.resolve(__dirname, '_replay/save')); + } catch {} + + try { + await fs.readFile( + path.resolve(__dirname, '_replay/.info'), + 'utf-8' + ); + } catch { + await fs.writeFile( + path.resolve(__dirname, '_replay/.info'), + `{ + "cnt": 0 +}`, + 'utf-8' + ); + } + const data = fss.readFileSync( + path.resolve(__dirname, '_replay/.info'), + 'utf-8' + ); + repStart = Number(JSON.parse(data).cnt); + console.log(`服务器录像调试模块已开始服务`); + } + }); + + req.on('end', () => { + res.end(); + }); +} + +/** + * 获取未占用的状态栏位 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function replayCnt() { + const data = `{ + "cnt": ${++repStart} +}`; + fss.writeFileSync(path.resolve(__dirname, '_replay/.info'), data, 'utf-8'); + + return repStart; +} + +/** + * 写入 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function replayWrite(req, res) { + const data = await getPostData(req); + const n = await replayCnt(); + + if (isNaN(n)) res.end('@error'); + + await Promise.all([ + fs.writeFile( + path.resolve(__dirname, '_replay/.info'), + `{ + "cnt": ${n + 1} +}`, + 'utf-8' + ), + fs.writeFile( + path.resolve(__dirname, `_replay/status/${n}.rep`), + data, + 'utf-8' + ) + ]); + + res.end(n.toString()); +} + +/** + * 比对录像与本地数据 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function replayCheck(req, res) { + const ans = await getPostData(req); + const [n, data] = ans.split('@-|-@'); + + const local = ( + await fs.readFile( + path.resolve(__dirname, `_replay/status/${n}.rep`), + 'utf-8' + ) + ) + .split('@---@') + .map(v => JSON.parse(v)); + const rep = data.split('@---@').map(v => JSON.parse(v)); + + if (local.length !== rep.length) return res.end('false'); + + const check = (a, b) => { + if (a === b) return true; + if (typeof a !== typeof b) return false; + if (typeof a === 'object' && a !== null) { + for (const j in a) { + if (j === 'statistics' || j === 'timeout') continue; // 忽略统计信息 + const aa = a[j]; + const bb = b[j]; + if (!check(aa, bb)) { + return false; + } + } + return true; + } + if ( + typeof a === 'boolean' || + typeof a === 'number' || + typeof a === 'string' || + typeof a === 'symbol' || + typeof a === 'undefined' || + typeof a === 'bigint' || + a === null + ) { + return a === b; + } + return true; + }; + + for (let i = 0; i < local.length; i++) { + const a = local[i]; + const b = rep[i]; + if (!check(a, b)) return res.end('false'); + } + + res.end('true'); +} + +/** + * 获取本地属性 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function replayGet(req, res, dir) { + const ans = Number(await getPostData(req)); + + const data = await fs.readFile( + path.resolve(__dirname, `_replay/${dir}/${ans}.rep`) + ); + res.end(data); +} + +/** + * 录像回放存档 + * @param {http.IncomingMessage} req + * @param {http.ServerResponse & {req: http.IncomingMessage;}} res + */ +async function replaySave(req, res) { + const data = await getPostData(req); + const [cnt, save] = data.split('@-|-@'); + + if (isNaN(Number(cnt))) { + console.log('Invalid input of save cnt'); + res.end('@error: 不合法的录像存档信息'); + } + + await fs.writeFile( + path.resolve(__dirname, `_replay/save/${cnt}.rep`), + save, + 'utf-8' + ); + + res.end('success'); +} + +// ----- server + +server.on('listening', () => { + console.log(`服务已启动,编辑器地址:http://127.0.0.1:${port}/editor.html`); + console.log(`游戏地址:http://127.0.0.1:${port}`); +}); + +// 处理请求 +server.on('request', async (req, res) => { + /** @type {string} */ + const p = req.url.replace(`/games/${name}`, '').replace('/all/', '/'); + + if (req.method === 'GET') { + const dir = path + .resolve(__dirname, p === '/' ? 'index.html' : p.slice(1)) + .split('?v=')[0]; + + if (await getFile(req, res, dir)) return; + + if (p.startsWith('/__all_floors__.js')) { + const all = p.split('&id=')[1].split(','); + res.writeHead(200, { 'Content-type': 'text/javascript' }); + return await getAll(req, res, all, '.js', 'project/floors/', '\n'); + } + + if (p.startsWith('/__all_animates__')) { + const all = p.split('&id=')[1].split(','); + return await getAll( + req, + res, + all, + '.animate', + 'project/animates/', + '@@@~~~###~~~@@@' + ); + } + } + + if (req.method === 'POST') { + if (p === '/listFile') return await readDir(req, res); + if (p === '/makeDir') return await mkdir(req, res); + if (p === '/readFile') return await readFile(req, res); + if (p === '/writeFile') return await writeFile(req, res); + if (p === '/deleteFile') return await rm(req, res); + if (p === '/moveFile') return await moveFile(req, res); + if (p === '/writeMultiFiles') return await writeMultiFiles(req, res); + if (p === '/reload') return reload(req, res); + if (p === '/hotReload') return reload(req, res, true); + if (p === '/replay') return replay(req, res); + if (p === '/replayWrite') return await replayWrite(req, res); + if (p === '/replayCheck') return await replayCheck(req, res); + if (p === '/replayGet') return await replayGet(req, res, 'status'); + if (p === '/replaySave') return await replaySave(req, res); + if (p === '/replayGetSave') return await replayGet(req, res, 'save'); + } + + res.statusCode = 404; + res.end(); +}); diff --git a/server.py b/server.py new file mode 100644 index 0000000..445988b --- /dev/null +++ b/server.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- + +# HTML5魔塔样板,启动服务Python版 +# 需要安装Python环境,并 pip install flask 安装Flask库 +# 运行方式:python server.py 或 python3 server.py + +import sys +import json +import os +import shutil +import base64 + +isPy3 = sys.version_info > (3, 0) + +def p(s): # s is unicode in py2 and str in py3 + if isPy3: print(s) + else: print(s.decode('utf-8')) +p("") + +try: + from flask import Flask, request, Response, abort + import mimetypes + import socket +except: + p("需要flask才可使用本服务。\n安装方式:%s install flask" % ("pip3" if isPy3 else "pip")) + exit(1) + +app = Flask(__name__, static_folder='__static__') +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 + +@app.after_request +def add_header(r): + r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + r.headers["Pragma"] = "no-cache" + r.headers["Expires"] = "0" + r.headers['Cache-Control'] = 'public, max-age=0' + return r + +def is_sub(filename): + try: + return (os.path.realpath(filename) + os.sep).startswith(os.path.realpath(".") + os.sep) + except: + return True + +def get_mimetype(path): + return mimetypes.guess_type(path)[0] or 'application/octet-stream' + +def get_file(path): + if not os.path.isfile(path): + if path.startswith('_saves/'): + return '' + abort(404) + return None + if not is_sub(path): + abort(403) + with open(path, 'rb') as f: + content = f.read() # str in py2 and bytes in py3 + return content + +@app.route('/', methods=['GET']) +def root(): + return static_file('index.html') + +@app.route('/__all_floors__.js', methods=['GET']) +def all_floors(): + ids = request.args.get('id', '').split(',') + if len(ids) == 0: + abort(404) + return None + content = [] + for id in ids: + v = get_file('project/floors/%s.js' % id) + if isPy3: v = str(v, encoding = 'utf-8') + content.append(v) + return Response('\n'.join(content), mimetype = 'text/javascript') + +@app.route('/__all_animates__', methods=['GET']) +def all_animates(): + ids = request.args.get('id', '').split(',') + if len(ids) == 0: + abort(404) + return None + content = [] + for id in ids: + animate = 'project/animates/%s.animate' % id + if os.path.exists(animate): + v = get_file(animate) + if isPy3: v = str(v, encoding = 'utf-8') + content.append(v) + else: content.append('') + return '@@@~~~###~~~@@@'.join(content) + +@app.route('/favicon.ico', methods=['GET']) +def favicon(): + return '' + +@app.route('/', methods=['GET']) +def static_file(path): + if os.path.isdir(path): + if not path.endswith('/'): path += '/' + path += 'index.html' + if not os.path.isfile(path): + abort(404) + return None + mimetype = get_mimetype(path) + response = Response(get_file(path), mimetype = mimetype) + if mimetype.startswith('audio/'): response.headers["Accept-Ranges"] = "bytes" + return response + +def process_request(): + data = request.get_data() # str in py2 and bytes in py3 + if isPy3: data = str(data, encoding = 'utf-8') + params = data.split("&") + d = {} + for one in params: + index = one.find("=") + if index >= 0: + d[one[:index]] = one[index+1:] + return d # str in py2 & py3 + +@app.route('/readFile', methods=['POST']) +def readFile(): + data = process_request() + tp = data.get('type', 'base64') + filename = data.get('name', None) + content = get_file(filename) + return content if tp == 'utf8' or content is None else base64.b64encode(content) + +@app.route('/writeFile', methods=['POST']) +def writeFile(): + data = process_request() + tp = data.get('type', 'base64') + filename = data.get('name', None) + if not is_sub(filename): + abort(403) + return + value = data.get('value', '') + if isPy3: value = value.encode('utf-8') + if tp == 'base64': value = base64.b64decode(value) + with open(filename, 'wb') as f: + f.write(value) # str in py2 and bytes in py3 + return str(len(value)) + +@app.route('/writeMultiFiles', methods=['POST']) +def writeMultiFiles(): + data = process_request() + filenames = data.get('name', '').split(';') + values = data.get('value', '').split(';') + l = 0 + for i in range(len(filenames)): + if i >= len(values): + break + filename = filenames[i] + value = values[i].encode('utf-8') if isPy3 else values[i] + value = base64.b64decode(value) + if not is_sub(filename): + abort(403) + return + with open(filename, 'wb') as f: + f.write(value) + l += len(value) + return str(l) + +@app.route('/listFile', methods=['POST']) +def listFile(): + data = process_request() + filename = data.get('name', None) + if filename is None or not os.path.isdir(filename): + abort(404) + return + if not is_sub(filename): + abort(403) + return + files = [f + for f in os.listdir(filename) + if os.path.isfile(os.path.join(filename, f))] + return "[" + ", ".join(['"'+f+'"' for f in files]) + "]" + +@app.route('/makeDir', methods=['POST']) +def makeDir(): + data = process_request() + filename = data.get('name', None) + if filename is None or not is_sub(filename): + abort(403) + return + if not os.path.exists(filename): + os.makedirs(filename) + return 'Success' + +@app.route('/moveFile', methods=['POST']) +def moveFile(): + data = process_request() + src = data.get('src', None) + dest = data.get('dest', None) + if src is None or dest is None or not is_sub(src) or not is_sub(dest): + abort(403) + return + if not os.path.exists(src): + abort(404) + return + if src == dest: + return 'Success' + if os.path.exists(dest): + os.remove(dest) + os.rename(src, dest) + return 'Success' + +@app.route('/deleteFile', methods=['POST']) +def deleteFile(): + data = process_request() + name = data.get('name', None) + if name is None or not is_sub(name): + abort(403) + return + if os.path.isfile(name): + os.remove(name) + elif os.path.isdir(name): + shutil.rmtree(name) + return 'Success' + +@app.route('/games/upload.php', methods=['POST']) +def upload(): + return '' + +def port_used(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = True + try: + sock.bind(("0.0.0.0", port)) + result = False + except: + pass + sock.close() + return result + +if __name__ == '__main__': + port = 1055 + while port_used(port): + port += 1 + if port > 1055: + p("默认的1055端口已被占用,自动选择%d端口。请注意,不同端口下的存档等信息都是不共用的。\n" % port) + p("服务已启动...\n游戏地址:http://127.0.0.1:%d/\n编辑器地址:http://127.0.0.1:%d/editor.html\n" % (port, port)) + app.run(host = '0.0.0.0', port = port, debug = False) diff --git a/stars.css b/stars.css new file mode 100644 index 0000000..2b761a0 --- /dev/null +++ b/stars.css @@ -0,0 +1,70 @@ +html { + height: 100%; + overflow: hidden; + background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%); + } + #stars { + width: 1px; + height: 1px; + background-color: transparent; + animation: star_anti 50s linear infinite; + box-shadow: 1804px 1265px #FFF , 365px 332px #FFF , 86px 1888px #FFF , 1888px 484px #FFF , 199px 1489px #FFF , 1459px 1010px #FFF , 807px 388px #FFF , 855px 558px #FFF , 83px 1095px #FFF , 1418px 377px #FFF , 677px 886px #FFF , 862px 1709px #FFF , 1058px 1085px #FFF , 50px 1772px #FFF , 1941px 1544px #FFF , 377px 900px #FFF , 184px 712px #FFF , 1797px 1928px #FFF , 507px 1861px #FFF , 1849px 19px #FFF , 1399px 200px #FFF , 972px 497px #FFF , 795px 1109px #FFF , 746px 970px #FFF , 1524px 972px #FFF , 1631px 389px #FFF , 1026px 1016px #FFF , 1295px 862px #FFF , 1258px 1876px #FFF , 791px 189px #FFF , 1519px 45px #FFF , 592px 1405px #FFF , 620px 130px #FFF , 1044px 1171px #FFF , 37px 1578px #FFF , 1589px 86px #FFF , 1024px 528px #FFF , 1613px 568px #FFF , 912px 1175px #FFF , 1177px 133px #FFF , 67px 1641px #FFF , 1168px 357px #FFF , 310px 1873px #FFF , 1187px 573px #FFF , 308px 1839px #FFF , 565px 24px #FFF , 1691px 1555px #FFF , 1384px 1551px #FFF , 179px 861px #FFF , 1850px 1966px #FFF , 1169px 1979px #FFF , 1182px 1522px #FFF , 616px 751px #FFF , 1083px 908px #FFF , 684px 766px #FFF , 67px 955px #FFF , 1813px 1714px #FFF , 1256px 1413px #FFF , 332px 803px #FFF , 1670px 1921px #FFF , 362px 211px #FFF , 1513px 423px #FFF , 1304px 1145px #FFF , 1292px 1168px #FFF , 611px 802px #FFF , 1297px 575px #FFF , 540px 1289px #FFF , 1551px 1678px #FFF , 1545px 237px #FFF , 423px 138px #FFF , 1088px 28px #FFF , 642px 1637px #FFF , 429px 1293px #FFF , 1276px 1900px #FFF , 1168px 1696px #FFF , 847px 837px #FFF , 151px 1395px #FFF , 1490px 75px #FFF , 1588px 131px #FFF , 1739px 1358px #FFF , 709px 624px #FFF , 343px 502px #FFF , 1342px 1690px #FFF , 175px 1722px #FFF , 964px 1299px #FFF , 892px 1326px #FFF , 519px 1142px #FFF , 1014px 193px #FFF , 1181px 360px #FFF , 325px 139px #FFF , 482px 1199px #FFF , 613px 8px #FFF , 1976px 1125px #FFF , 346px 60px #FFF , 1565px 818px #FFF , 268px 1590px #FFF , 213px 1666px #FFF , 800px 464px #FFF , 974px 1825px #FFF , 1066px 23px #FFF , 1995px 1499px #FFF , 666px 1130px #FFF , 1074px 1710px #FFF , 1636px 1483px #FFF , 1379px 1509px #FFF , 1221px 887px #FFF , 1857px 964px #FFF , 1046px 993px #FFF , 1875px 643px #FFF , 1504px 1607px #FFF , 1065px 641px #FFF , 1095px 752px #FFF , 566px 1737px #FFF , 1972px 1778px #FFF , 146px 1517px #FFF , 1923px 588px #FFF , 557px 881px #FFF , 1885px 1950px #FFF , 1739px 1598px #FFF , 1048px 501px #FFF , 1316px 705px #FFF , 1900px 1697px #FFF , 1187px 917px #FFF , 1688px 1025px #FFF , 648px 1634px #FFF , 1002px 572px #FFF , 603px 1995px #FFF , 215px 693px #FFF , 688px 1374px #FFF , 1389px 1166px #FFF , 1310px 1140px #FFF , 245px 587px #FFF , 845px 63px #FFF , 296px 1646px #FFF , 792px 350px #FFF , 756px 1493px #FFF , 1553px 1079px #FFF , 850px 66px #FFF , 963px 1904px #FFF , 81px 207px #FFF , 1776px 1634px #FFF , 1759px 521px #FFF , 1761px 1536px #FFF , 601px 1485px #FFF , 898px 153px #FFF , 48px 648px #FFF , 1644px 1109px #FFF , 1974px 60px #FFF , 1278px 653px #FFF , 616px 432px #FFF , 1179px 1849px #FFF , 739px 677px #FFF , 808px 1850px #FFF , 1104px 827px #FFF , 984px 888px #FFF , 1027px 44px #FFF , 1462px 1105px #FFF , 902px 1486px #FFF , 769px 441px #FFF , 431px 1195px #FFF , 4px 764px #FFF , 562px 7px #FFF , 952px 1744px #FFF , 822px 971px #FFF , 1016px 1804px #FFF , 1429px 1161px #FFF , 328px 1568px #FFF , 101px 746px #FFF , 649px 1484px #FFF , 1903px 569px #FFF , 733px 871px #FFF , 1554px 505px #FFF , 1076px 642px #FFF , 609px 641px #FFF , 996px 149px #FFF , 1595px 758px #FFF , 14px 1083px #FFF , 261px 767px #FFF , 1274px 1517px #FFF , 1412px 215px #FFF , 1651px 879px #FFF , 284px 1633px #FFF , 1439px 287px #FFF , 1717px 270px #FFF , 1107px 1063px #FFF , 1521px 1831px #FFF , 656px 1702px #FFF , 25px 230px #FFF , 1958px 1615px #FFF , 646px 675px #FFF , 1201px 343px #FFF , 1918px 1064px #FFF , 1932px 609px #FFF , 1203px 900px #FFF , 10px 575px #FFF , 1582px 1828px #FFF , 1184px 462px #FFF , 1px 1619px #FFF , 1440px 1071px #FFF , 1844px 1913px #FFF , 376px 1054px #FFF , 1883px 1236px #FFF , 571px 493px #FFF , 354px 1701px #FFF , 747px 60px #FFF , 11px 1142px #FFF , 1136px 1891px #FFF , 1682px 473px #FFF , 1537px 1520px #FFF , 902px 836px #FFF , 1313px 395px #FFF , 534px 341px #FFF , 230px 1614px #FFF , 14px 1387px #FFF , 1296px 1765px #FFF , 1064px 1270px #FFF , 761px 975px #FFF , 1855px 335px #FFF , 198px 110px #FFF , 1660px 598px #FFF , 1022px 933px #FFF , 518px 356px #FFF , 19px 865px #FFF , 471px 830px #FFF , 758px 358px #FFF , 541px 1652px #FFF , 320px 926px #FFF , 425px 1826px #FFF , 659px 353px #FFF , 708px 778px #FFF , 862px 641px #FFF , 475px 1362px #FFF , 1326px 1449px #FFF , 446px 802px #FFF , 391px 1169px #FFF , 496px 39px #FFF , 1534px 934px #FFF , 1822px 1809px #FFF , 1454px 237px #FFF , 187px 1555px #FFF , 1069px 1977px #FFF , 1880px 1508px #FFF , 279px 418px #FFF , 1938px 1980px #FFF , 1304px 530px #FFF , 1763px 187px #FFF , 1945px 1642px #FFF , 311px 1490px #FFF , 770px 1598px #FFF , 263px 330px #FFF , 1733px 1771px #FFF , 978px 34px #FFF , 325px 1776px #FFF , 873px 1460px #FFF , 365px 33px #FFF , 913px 1999px #FFF , 667px 1021px #FFF , 27px 572px #FFF , 950px 1858px #FFF , 448px 1205px #FFF , 1302px 1138px #FFF , 1269px 932px #FFF , 480px 132px #FFF , 770px 1871px #FFF , 952px 654px #FFF , 623px 90px #FFF , 419px 1683px #FFF , 930px 794px #FFF , 1327px 1651px #FFF , 769px 1536px #FFF , 895px 90px #FFF , 599px 1268px #FFF , 1645px 919px #FFF , 1672px 1080px #FFF , 1637px 1259px #FFF , 243px 1182px #FFF , 1509px 457px #FFF , 1374px 1469px #FFF , 751px 137px #FFF , 1097px 1008px #FFF , 1979px 1381px #FFF , 981px 1825px #FFF , 928px 1930px #FFF , 632px 422px #FFF , 812px 341px #FFF , 1077px 1832px #FFF , 203px 1452px #FFF , 664px 1531px #FFF , 1203px 57px #FFF , 1654px 1203px #FFF , 491px 174px #FFF , 1507px 735px #FFF , 964px 896px #FFF , 52px 1718px #FFF , 1435px 26px #FFF , 753px 635px #FFF , 890px 1847px #FFF , 42px 1353px #FFF , 717px 72px #FFF , 1845px 1212px #FFF , 344px 867px #FFF , 418px 855px #FFF , 899px 1124px #FFF , 1798px 1582px #FFF , 1774px 760px #FFF , 908px 1567px #FFF , 1647px 1210px #FFF , 299px 82px #FFF , 1179px 1317px #FFF , 938px 1580px #FFF , 82px 921px #FFF , 657px 1596px #FFF , 892px 1264px #FFF , 1161px 819px #FFF , 607px 1447px #FFF , 605px 679px #FFF , 1642px 595px #FFF , 1963px 525px #FFF , 1656px 1591px #FFF , 1467px 1743px #FFF , 167px 1420px #FFF , 471px 492px #FFF , 1077px 932px #FFF , 774px 1282px #FFF , 799px 701px #FFF , 400px 258px #FFF , 235px 1937px #FFF , 894px 562px #FFF , 1277px 907px #FFF , 435px 1360px #FFF , 507px 1253px #FFF , 1022px 833px #FFF , 351px 773px #FFF , 1126px 1969px #FFF , 1382px 1620px #FFF , 411px 59px #FFF , 187px 906px #FFF , 644px 1364px #FFF , 1721px 1451px #FFF , 1879px 1390px #FFF , 1396px 318px #FFF , 1002px 891px #FFF , 1930px 1454px #FFF , 1952px 496px #FFF , 1308px 1325px #FFF , 343px 475px #FFF , 285px 373px #FFF , 1329px 1591px #FFF , 901px 1875px #FFF , 966px 254px #FFF , 1624px 1577px #FFF , 371px 589px #FFF , 1918px 1494px #FFF , 841px 589px #FFF , 873px 1657px #FFF , 970px 1697px #FFF , 1354px 975px #FFF , 807px 1099px #FFF , 384px 1608px #FFF , 1600px 1739px #FFF , 110px 1310px #FFF , 687px 1611px #FFF , 324px 394px #FFF , 1267px 224px #FFF , 1122px 1919px #FFF , 1753px 578px #FFF , 611px 479px #FFF , 1494px 475px #FFF , 1595px 368px #FFF , 304px 1379px #FFF , 1663px 87px #FFF , 1789px 1471px #FFF , 941px 1861px #FFF , 287px 657px #FFF , 1882px 217px #FFF , 1766px 1960px #FFF , 144px 966px #FFF , 872px 943px #FFF , 1705px 1909px #FFF , 1318px 1173px #FFF , 1856px 1549px #FFF , 1722px 1482px #FFF , 196px 594px #FFF , 355px 1182px #FFF , 1242px 112px #FFF , 226px 344px #FFF , 674px 895px #FFF , 210px 2px #FFF , 1224px 488px #FFF , 220px 617px #FFF , 1857px 1348px #FFF , 426px 1026px #FFF , 1370px 720px #FFF , 109px 440px #FFF , 1940px 1575px #FFF , 978px 1443px #FFF , 308px 614px #FFF , 1392px 1351px #FFF , 635px 1231px #FFF , 1132px 616px #FFF , 756px 342px #FFF , 1968px 765px #FFF , 1020px 1877px #FFF , 1998px 1325px #FFF , 1296px 1303px #FFF , 1817px 223px #FFF , 1184px 907px #FFF , 546px 845px #FFF , 51px 705px #FFF , 1421px 735px #FFF , 1255px 700px #FFF , 249px 1908px #FFF , 1701px 351px #FFF , 173px 1658px #FFF , 1088px 1476px #FFF , 1930px 1787px #FFF , 689px 1312px #FFF , 615px 1006px #FFF , 1870px 1229px #FFF , 1900px 546px #FFF , 1416px 141px #FFF , 1983px 945px #FFF , 1104px 1351px #FFF , 426px 701px #FFF , 431px 1597px #FFF , 893px 456px #FFF , 1976px 1914px #FFF , 1538px 673px #FFF , 916px 1386px #FFF , 304px 138px #FFF , 1038px 681px #FFF , 1349px 1740px #FFF , 1231px 552px #FFF , 35px 1435px #FFF , 588px 652px #FFF , 793px 575px #FFF , 542px 926px #FFF , 1252px 25px #FFF , 831px 332px #FFF , 718px 283px #FFF , 1327px 1952px #FFF , 1019px 704px #FFF , 888px 1117px #FFF , 1107px 1378px #FFF , 532px 505px #FFF , 1070px 552px #FFF , 346px 645px #FFF , 63px 1783px #FFF , 775px 879px #FFF , 165px 160px #FFF , 788px 1225px #FFF , 1562px 1520px #FFF , 56px 1522px #FFF , 439px 498px #FFF , 1988px 1521px #FFF , 254px 1363px #FFF , 1162px 816px #FFF , 219px 386px #FFF , 1789px 1315px #FFF , 1090px 1415px #FFF , 1361px 315px #FFF , 825px 1306px #FFF , 92px 548px #FFF , 1501px 1946px #FFF , 350px 1735px #FFF , 459px 1533px #FFF , 1417px 931px #FFF , 1849px 174px #FFF , 220px 1084px #FFF , 1357px 209px #FFF , 1974px 358px #FFF , 90px 808px #FFF , 1247px 765px #FFF , 1878px 725px #FFF , 1415px 87px #FFF , 1253px 943px #FFF , 1455px 1919px #FFF , 1321px 337px #FFF , 1210px 1600px #FFF , 1855px 1575px #FFF , 325px 936px #FFF , 1118px 892px #FFF , 703px 294px #FFF , 89px 891px #FFF , 239px 1548px #FFF , 280px 262px #FFF , 1401px 555px #FFF , 1092px 1638px #FFF , 673px 1207px #FFF , 1469px 1358px #FFF , 1253px 1986px #FFF , 1249px 1040px #FFF , 253px 484px #FFF , 1163px 775px #FFF , 426px 162px #FFF , 721px 1761px #FFF , 369px 510px #FFF , 702px 1599px #FFF , 1883px 483px #FFF , 680px 1604px #FFF , 870px 1599px #FFF , 976px 1808px #FFF , 916px 477px #FFF , 1223px 1636px #FFF , 506px 993px #FFF , 898px 1284px #FFF , 1013px 290px #FFF , 1189px 78px #FFF , 25px 588px #FFF , 960px 861px #FFF , 28px 526px #FFF , 959px 681px #FFF , 1426px 1329px #FFF , 294px 557px #FFF , 1907px 1320px #FFF , 1289px 1627px #FFF , 124px 451px #FFF , 967px 653px #FFF , 892px 1460px #FFF , 537px 1385px #FFF , 197px 1954px #FFF , 1543px 302px #FFF , 747px 1953px #FFF , 995px 1630px #FFF , 1423px 1221px #FFF , 1075px 983px #FFF , 1556px 1739px #FFF , 1068px 1425px #FFF , 81px 550px #FFF , 1668px 523px #FFF , 1158px 438px #FFF , 401px 1795px #FFF , 537px 1072px #FFF , 1px 326px #FFF , 249px 118px #FFF , 832px 1544px #FFF , 240px 153px #FFF , 651px 1077px #FFF , 1656px 542px #FFF , 1102px 606px #FFF , 1583px 788px #FFF , 1205px 1842px #FFF , 1657px 1793px #FFF , 1848px 1464px #FFF , 1285px 1395px #FFF , 662px 1227px #FFF , 1790px 134px #FFF , 577px 263px #FFF , 383px 702px #FFF , 1728px 1953px #FFF , 417px 57px #FFF , 1390px 574px #FFF , 1024px 287px #FFF , 1969px 753px #FFF , 1239px 1036px #FFF , 1063px 1313px #FFF , 1784px 1519px #FFF , 1665px 682px #FFF , 806px 1437px #FFF , 394px 917px #FFF , 904px 666px #FFF , 801px 1280px #FFF , 1392px 1930px #FFF , 1611px 1386px #FFF , 1809px 1507px #FFF , 1720px 1300px #FFF , 1721px 1287px #FFF , 969px 240px #FFF , 3px 1070px #FFF , 1198px 538px #FFF , 1416px 1001px #FFF , 1665px 1265px #FFF , 1010px 1275px #FFF , 772px 978px #FFF , 1980px 980px #FFF , 1283px 1573px #FFF , 444px 516px #FFF , 875px 737px #FFF , 258px 716px #FFF , 1698px 758px #FFF , 644px 238px #FFF , 19px 876px #FFF , 355px 1327px #FFF , 1602px 1846px #FFF , 548px 534px #FFF , 1498px 1473px #FFF , 1389px 1136px #FFF , 174px 771px #FFF , 955px 1931px #FFF , 403px 371px #FFF , 1502px 794px #FFF , 117px 876px #FFF , 536px 778px #FFF , 67px 393px #FFF , 119px 1918px #FFF , 1912px 1663px #FFF , 1141px 245px #FFF , 1105px 130px #FFF , 1218px 1608px #FFF , 662px 1502px #FFF , 1907px 927px #FFF , 521px 109px #FFF , 1885px 362px #FFF , 1785px 1935px #FFF , 781px 427px #FFF , 1446px 1991px #FFF , 164px 1539px #FFF , 1807px 1795px #FFF , 1922px 890px #FFF , 1245px 933px #FFF , 446px 450px #FFF , 1743px 79px #FFF , 1959px 310px #FFF , 1348px 749px #FFF , 1954px 128px #FFF , 1980px 1030px #FFF , 1850px 302px #FFF , 1074px 922px #FFF , 174px 403px #FFF , 1579px 733px #FFF , 653px 1958px #FFF , 1511px 1943px #FFF , 1037px 741px #FFF , 602px 1384px #FFF , 103px 402px #FFF , 1722px 1417px #FFF , 1732px 1916px #FFF , 1743px 1803px #FFF , 381px 721px #FFF , 964px 1700px #FFF , 1070px 341px #FFF , 1376px 1258px #FFF , 1884px 570px #FFF , 940px 280px #FFF , 1484px 1658px #FFF , 1806px 1875px #FFF , 1054px 917px #FFF , 1672px 103px #FFF , 783px 574px #FFF , 98px 347px #FFF , 555px 1136px #FFF , 1403px 1237px #FFF , 1203px 339px #FFF , 572px 35px #FFF , 932px 1783px #FFF , 1527px 1850px #FFF , 1959px 1109px #FFF , 892px 623px #FFF , 211px 1388px #FFF , 1581px 1806px #FFF , 868px 1053px #FFF , 1243px 1997px #FFF , 1004px 522px #FFF , 1241px 1707px #FFF , 376px 282px #FFF , 537px 878px #FFF , 1948px 979px #FFF , 532px 688px #FFF , 273px 958px #FFF , 581px 927px #FFF , 1060px 887px #FFF , 486px 1467px #FFF , 1122px 1834px #FFF , 1650px 1763px #FFF , 532px 302px #FFF , 314px 1111px #FFF , 1888px 683px #FFF , 1856px 1040px #FFF , 1780px 1338px #FFF , 24px 1564px #FFF , 1096px 1808px #FFF , 1202px 1968px #FFF , 214px 992px #FFF , 728px 515px #FFF , 247px 278px #FFF , 1670px 45px #FFF , 442px 1579px #FFF , 1143px 30px #FFF , 612px 72px #FFF , 1177px 1303px #FFF , 1898px 1255px #FFF , 378px 1667px #FFF , 326px 1929px #FFF , 1257px 766px #FFF , 1363px 1170px #FFF , 1090px 1667px #FFF , 711px 293px #FFF , 249px 1406px #FFF , 1589px 565px #FFF , 1451px 29px #FFF , 1171px 1459px #FFF , 1294px 1214px #FFF , 342px 942px #FFF , 1945px 353px #FFF , 741px 1185px #FFF , 894px 1453px #FFF , 593px 1584px #FFF , 518px 630px #FFF , 393px 756px #FFF , 34px 608px #FFF; + } + #stars:after { + content: " "; + position: absolute; + top: 2000px; + width: 1px; + height: 1px; + background: transparent; + box-shadow: 1804px 1265px #FFF , 365px 332px #FFF , 86px 1888px #FFF , 1888px 484px #FFF , 199px 1489px #FFF , 1459px 1010px #FFF , 807px 388px #FFF , 855px 558px #FFF , 83px 1095px #FFF , 1418px 377px #FFF , 677px 886px #FFF , 862px 1709px #FFF , 1058px 1085px #FFF , 50px 1772px #FFF , 1941px 1544px #FFF , 377px 900px #FFF , 184px 712px #FFF , 1797px 1928px #FFF , 507px 1861px #FFF , 1849px 19px #FFF , 1399px 200px #FFF , 972px 497px #FFF , 795px 1109px #FFF , 746px 970px #FFF , 1524px 972px #FFF , 1631px 389px #FFF , 1026px 1016px #FFF , 1295px 862px #FFF , 1258px 1876px #FFF , 791px 189px #FFF , 1519px 45px #FFF , 592px 1405px #FFF , 620px 130px #FFF , 1044px 1171px #FFF , 37px 1578px #FFF , 1589px 86px #FFF , 1024px 528px #FFF , 1613px 568px #FFF , 912px 1175px #FFF , 1177px 133px #FFF , 67px 1641px #FFF , 1168px 357px #FFF , 310px 1873px #FFF , 1187px 573px #FFF , 308px 1839px #FFF , 565px 24px #FFF , 1691px 1555px #FFF , 1384px 1551px #FFF , 179px 861px #FFF , 1850px 1966px #FFF , 1169px 1979px #FFF , 1182px 1522px #FFF , 616px 751px #FFF , 1083px 908px #FFF , 684px 766px #FFF , 67px 955px #FFF , 1813px 1714px #FFF , 1256px 1413px #FFF , 332px 803px #FFF , 1670px 1921px #FFF , 362px 211px #FFF , 1513px 423px #FFF , 1304px 1145px #FFF , 1292px 1168px #FFF , 611px 802px #FFF , 1297px 575px #FFF , 540px 1289px #FFF , 1551px 1678px #FFF , 1545px 237px #FFF , 423px 138px #FFF , 1088px 28px #FFF , 642px 1637px #FFF , 429px 1293px #FFF , 1276px 1900px #FFF , 1168px 1696px #FFF , 847px 837px #FFF , 151px 1395px #FFF , 1490px 75px #FFF , 1588px 131px #FFF , 1739px 1358px #FFF , 709px 624px #FFF , 343px 502px #FFF , 1342px 1690px #FFF , 175px 1722px #FFF , 964px 1299px #FFF , 892px 1326px #FFF , 519px 1142px #FFF , 1014px 193px #FFF , 1181px 360px #FFF , 325px 139px #FFF , 482px 1199px #FFF , 613px 8px #FFF , 1976px 1125px #FFF , 346px 60px #FFF , 1565px 818px #FFF , 268px 1590px #FFF , 213px 1666px #FFF , 800px 464px #FFF , 974px 1825px #FFF , 1066px 23px #FFF , 1995px 1499px #FFF , 666px 1130px #FFF , 1074px 1710px #FFF , 1636px 1483px #FFF , 1379px 1509px #FFF , 1221px 887px #FFF , 1857px 964px #FFF , 1046px 993px #FFF , 1875px 643px #FFF , 1504px 1607px #FFF , 1065px 641px #FFF , 1095px 752px #FFF , 566px 1737px #FFF , 1972px 1778px #FFF , 146px 1517px #FFF , 1923px 588px #FFF , 557px 881px #FFF , 1885px 1950px #FFF , 1739px 1598px #FFF , 1048px 501px #FFF , 1316px 705px #FFF , 1900px 1697px #FFF , 1187px 917px #FFF , 1688px 1025px #FFF , 648px 1634px #FFF , 1002px 572px #FFF , 603px 1995px #FFF , 215px 693px #FFF , 688px 1374px #FFF , 1389px 1166px #FFF , 1310px 1140px #FFF , 245px 587px #FFF , 845px 63px #FFF , 296px 1646px #FFF , 792px 350px #FFF , 756px 1493px #FFF , 1553px 1079px #FFF , 850px 66px #FFF , 963px 1904px #FFF , 81px 207px #FFF , 1776px 1634px #FFF , 1759px 521px #FFF , 1761px 1536px #FFF , 601px 1485px #FFF , 898px 153px #FFF , 48px 648px #FFF , 1644px 1109px #FFF , 1974px 60px #FFF , 1278px 653px #FFF , 616px 432px #FFF , 1179px 1849px #FFF , 739px 677px #FFF , 808px 1850px #FFF , 1104px 827px #FFF , 984px 888px #FFF , 1027px 44px #FFF , 1462px 1105px #FFF , 902px 1486px #FFF , 769px 441px #FFF , 431px 1195px #FFF , 4px 764px #FFF , 562px 7px #FFF , 952px 1744px #FFF , 822px 971px #FFF , 1016px 1804px #FFF , 1429px 1161px #FFF , 328px 1568px #FFF , 101px 746px #FFF , 649px 1484px #FFF , 1903px 569px #FFF , 733px 871px #FFF , 1554px 505px #FFF , 1076px 642px #FFF , 609px 641px #FFF , 996px 149px #FFF , 1595px 758px #FFF , 14px 1083px #FFF , 261px 767px #FFF , 1274px 1517px #FFF , 1412px 215px #FFF , 1651px 879px #FFF , 284px 1633px #FFF , 1439px 287px #FFF , 1717px 270px #FFF , 1107px 1063px #FFF , 1521px 1831px #FFF , 656px 1702px #FFF , 25px 230px #FFF , 1958px 1615px #FFF , 646px 675px #FFF , 1201px 343px #FFF , 1918px 1064px #FFF , 1932px 609px #FFF , 1203px 900px #FFF , 10px 575px #FFF , 1582px 1828px #FFF , 1184px 462px #FFF , 1px 1619px #FFF , 1440px 1071px #FFF , 1844px 1913px #FFF , 376px 1054px #FFF , 1883px 1236px #FFF , 571px 493px #FFF , 354px 1701px #FFF , 747px 60px #FFF , 11px 1142px #FFF , 1136px 1891px #FFF , 1682px 473px #FFF , 1537px 1520px #FFF , 902px 836px #FFF , 1313px 395px #FFF , 534px 341px #FFF , 230px 1614px #FFF , 14px 1387px #FFF , 1296px 1765px #FFF , 1064px 1270px #FFF , 761px 975px #FFF , 1855px 335px #FFF , 198px 110px #FFF , 1660px 598px #FFF , 1022px 933px #FFF , 518px 356px #FFF , 19px 865px #FFF , 471px 830px #FFF , 758px 358px #FFF , 541px 1652px #FFF , 320px 926px #FFF , 425px 1826px #FFF , 659px 353px #FFF , 708px 778px #FFF , 862px 641px #FFF , 475px 1362px #FFF , 1326px 1449px #FFF , 446px 802px #FFF , 391px 1169px #FFF , 496px 39px #FFF , 1534px 934px #FFF , 1822px 1809px #FFF , 1454px 237px #FFF , 187px 1555px #FFF , 1069px 1977px #FFF , 1880px 1508px #FFF , 279px 418px #FFF , 1938px 1980px #FFF , 1304px 530px #FFF , 1763px 187px #FFF , 1945px 1642px #FFF , 311px 1490px #FFF , 770px 1598px #FFF , 263px 330px #FFF , 1733px 1771px #FFF , 978px 34px #FFF , 325px 1776px #FFF , 873px 1460px #FFF , 365px 33px #FFF , 913px 1999px #FFF , 667px 1021px #FFF , 27px 572px #FFF , 950px 1858px #FFF , 448px 1205px #FFF , 1302px 1138px #FFF , 1269px 932px #FFF , 480px 132px #FFF , 770px 1871px #FFF , 952px 654px #FFF , 623px 90px #FFF , 419px 1683px #FFF , 930px 794px #FFF , 1327px 1651px #FFF , 769px 1536px #FFF , 895px 90px #FFF , 599px 1268px #FFF , 1645px 919px #FFF , 1672px 1080px #FFF , 1637px 1259px #FFF , 243px 1182px #FFF , 1509px 457px #FFF , 1374px 1469px #FFF , 751px 137px #FFF , 1097px 1008px #FFF , 1979px 1381px #FFF , 981px 1825px #FFF , 928px 1930px #FFF , 632px 422px #FFF , 812px 341px #FFF , 1077px 1832px #FFF , 203px 1452px #FFF , 664px 1531px #FFF , 1203px 57px #FFF , 1654px 1203px #FFF , 491px 174px #FFF , 1507px 735px #FFF , 964px 896px #FFF , 52px 1718px #FFF , 1435px 26px #FFF , 753px 635px #FFF , 890px 1847px #FFF , 42px 1353px #FFF , 717px 72px #FFF , 1845px 1212px #FFF , 344px 867px #FFF , 418px 855px #FFF , 899px 1124px #FFF , 1798px 1582px #FFF , 1774px 760px #FFF , 908px 1567px #FFF , 1647px 1210px #FFF , 299px 82px #FFF , 1179px 1317px #FFF , 938px 1580px #FFF , 82px 921px #FFF , 657px 1596px #FFF , 892px 1264px #FFF , 1161px 819px #FFF , 607px 1447px #FFF , 605px 679px #FFF , 1642px 595px #FFF , 1963px 525px #FFF , 1656px 1591px #FFF , 1467px 1743px #FFF , 167px 1420px #FFF , 471px 492px #FFF , 1077px 932px #FFF , 774px 1282px #FFF , 799px 701px #FFF , 400px 258px #FFF , 235px 1937px #FFF , 894px 562px #FFF , 1277px 907px #FFF , 435px 1360px #FFF , 507px 1253px #FFF , 1022px 833px #FFF , 351px 773px #FFF , 1126px 1969px #FFF , 1382px 1620px #FFF , 411px 59px #FFF , 187px 906px #FFF , 644px 1364px #FFF , 1721px 1451px #FFF , 1879px 1390px #FFF , 1396px 318px #FFF , 1002px 891px #FFF , 1930px 1454px #FFF , 1952px 496px #FFF , 1308px 1325px #FFF , 343px 475px #FFF , 285px 373px #FFF , 1329px 1591px #FFF , 901px 1875px #FFF , 966px 254px #FFF , 1624px 1577px #FFF , 371px 589px #FFF , 1918px 1494px #FFF , 841px 589px #FFF , 873px 1657px #FFF , 970px 1697px #FFF , 1354px 975px #FFF , 807px 1099px #FFF , 384px 1608px #FFF , 1600px 1739px #FFF , 110px 1310px #FFF , 687px 1611px #FFF , 324px 394px #FFF , 1267px 224px #FFF , 1122px 1919px #FFF , 1753px 578px #FFF , 611px 479px #FFF , 1494px 475px #FFF , 1595px 368px #FFF , 304px 1379px #FFF , 1663px 87px #FFF , 1789px 1471px #FFF , 941px 1861px #FFF , 287px 657px #FFF , 1882px 217px #FFF , 1766px 1960px #FFF , 144px 966px #FFF , 872px 943px #FFF , 1705px 1909px #FFF , 1318px 1173px #FFF , 1856px 1549px #FFF , 1722px 1482px #FFF , 196px 594px #FFF , 355px 1182px #FFF , 1242px 112px #FFF , 226px 344px #FFF , 674px 895px #FFF , 210px 2px #FFF , 1224px 488px #FFF , 220px 617px #FFF , 1857px 1348px #FFF , 426px 1026px #FFF , 1370px 720px #FFF , 109px 440px #FFF , 1940px 1575px #FFF , 978px 1443px #FFF , 308px 614px #FFF , 1392px 1351px #FFF , 635px 1231px #FFF , 1132px 616px #FFF , 756px 342px #FFF , 1968px 765px #FFF , 1020px 1877px #FFF , 1998px 1325px #FFF , 1296px 1303px #FFF , 1817px 223px #FFF , 1184px 907px #FFF , 546px 845px #FFF , 51px 705px #FFF , 1421px 735px #FFF , 1255px 700px #FFF , 249px 1908px #FFF , 1701px 351px #FFF , 173px 1658px #FFF , 1088px 1476px #FFF , 1930px 1787px #FFF , 689px 1312px #FFF , 615px 1006px #FFF , 1870px 1229px #FFF , 1900px 546px #FFF , 1416px 141px #FFF , 1983px 945px #FFF , 1104px 1351px #FFF , 426px 701px #FFF , 431px 1597px #FFF , 893px 456px #FFF , 1976px 1914px #FFF , 1538px 673px #FFF , 916px 1386px #FFF , 304px 138px #FFF , 1038px 681px #FFF , 1349px 1740px #FFF , 1231px 552px #FFF , 35px 1435px #FFF , 588px 652px #FFF , 793px 575px #FFF , 542px 926px #FFF , 1252px 25px #FFF , 831px 332px #FFF , 718px 283px #FFF , 1327px 1952px #FFF , 1019px 704px #FFF , 888px 1117px #FFF , 1107px 1378px #FFF , 532px 505px #FFF , 1070px 552px #FFF , 346px 645px #FFF , 63px 1783px #FFF , 775px 879px #FFF , 165px 160px #FFF , 788px 1225px #FFF , 1562px 1520px #FFF , 56px 1522px #FFF , 439px 498px #FFF , 1988px 1521px #FFF , 254px 1363px #FFF , 1162px 816px #FFF , 219px 386px #FFF , 1789px 1315px #FFF , 1090px 1415px #FFF , 1361px 315px #FFF , 825px 1306px #FFF , 92px 548px #FFF , 1501px 1946px #FFF , 350px 1735px #FFF , 459px 1533px #FFF , 1417px 931px #FFF , 1849px 174px #FFF , 220px 1084px #FFF , 1357px 209px #FFF , 1974px 358px #FFF , 90px 808px #FFF , 1247px 765px #FFF , 1878px 725px #FFF , 1415px 87px #FFF , 1253px 943px #FFF , 1455px 1919px #FFF , 1321px 337px #FFF , 1210px 1600px #FFF , 1855px 1575px #FFF , 325px 936px #FFF , 1118px 892px #FFF , 703px 294px #FFF , 89px 891px #FFF , 239px 1548px #FFF , 280px 262px #FFF , 1401px 555px #FFF , 1092px 1638px #FFF , 673px 1207px #FFF , 1469px 1358px #FFF , 1253px 1986px #FFF , 1249px 1040px #FFF , 253px 484px #FFF , 1163px 775px #FFF , 426px 162px #FFF , 721px 1761px #FFF , 369px 510px #FFF , 702px 1599px #FFF , 1883px 483px #FFF , 680px 1604px #FFF , 870px 1599px #FFF , 976px 1808px #FFF , 916px 477px #FFF , 1223px 1636px #FFF , 506px 993px #FFF , 898px 1284px #FFF , 1013px 290px #FFF , 1189px 78px #FFF , 25px 588px #FFF , 960px 861px #FFF , 28px 526px #FFF , 959px 681px #FFF , 1426px 1329px #FFF , 294px 557px #FFF , 1907px 1320px #FFF , 1289px 1627px #FFF , 124px 451px #FFF , 967px 653px #FFF , 892px 1460px #FFF , 537px 1385px #FFF , 197px 1954px #FFF , 1543px 302px #FFF , 747px 1953px #FFF , 995px 1630px #FFF , 1423px 1221px #FFF , 1075px 983px #FFF , 1556px 1739px #FFF , 1068px 1425px #FFF , 81px 550px #FFF , 1668px 523px #FFF , 1158px 438px #FFF , 401px 1795px #FFF , 537px 1072px #FFF , 1px 326px #FFF , 249px 118px #FFF , 832px 1544px #FFF , 240px 153px #FFF , 651px 1077px #FFF , 1656px 542px #FFF , 1102px 606px #FFF , 1583px 788px #FFF , 1205px 1842px #FFF , 1657px 1793px #FFF , 1848px 1464px #FFF , 1285px 1395px #FFF , 662px 1227px #FFF , 1790px 134px #FFF , 577px 263px #FFF , 383px 702px #FFF , 1728px 1953px #FFF , 417px 57px #FFF , 1390px 574px #FFF , 1024px 287px #FFF , 1969px 753px #FFF , 1239px 1036px #FFF , 1063px 1313px #FFF , 1784px 1519px #FFF , 1665px 682px #FFF , 806px 1437px #FFF , 394px 917px #FFF , 904px 666px #FFF , 801px 1280px #FFF , 1392px 1930px #FFF , 1611px 1386px #FFF , 1809px 1507px #FFF , 1720px 1300px #FFF , 1721px 1287px #FFF , 969px 240px #FFF , 3px 1070px #FFF , 1198px 538px #FFF , 1416px 1001px #FFF , 1665px 1265px #FFF , 1010px 1275px #FFF , 772px 978px #FFF , 1980px 980px #FFF , 1283px 1573px #FFF , 444px 516px #FFF , 875px 737px #FFF , 258px 716px #FFF , 1698px 758px #FFF , 644px 238px #FFF , 19px 876px #FFF , 355px 1327px #FFF , 1602px 1846px #FFF , 548px 534px #FFF , 1498px 1473px #FFF , 1389px 1136px #FFF , 174px 771px #FFF , 955px 1931px #FFF , 403px 371px #FFF , 1502px 794px #FFF , 117px 876px #FFF , 536px 778px #FFF , 67px 393px #FFF , 119px 1918px #FFF , 1912px 1663px #FFF , 1141px 245px #FFF , 1105px 130px #FFF , 1218px 1608px #FFF , 662px 1502px #FFF , 1907px 927px #FFF , 521px 109px #FFF , 1885px 362px #FFF , 1785px 1935px #FFF , 781px 427px #FFF , 1446px 1991px #FFF , 164px 1539px #FFF , 1807px 1795px #FFF , 1922px 890px #FFF , 1245px 933px #FFF , 446px 450px #FFF , 1743px 79px #FFF , 1959px 310px #FFF , 1348px 749px #FFF , 1954px 128px #FFF , 1980px 1030px #FFF , 1850px 302px #FFF , 1074px 922px #FFF , 174px 403px #FFF , 1579px 733px #FFF , 653px 1958px #FFF , 1511px 1943px #FFF , 1037px 741px #FFF , 602px 1384px #FFF , 103px 402px #FFF , 1722px 1417px #FFF , 1732px 1916px #FFF , 1743px 1803px #FFF , 381px 721px #FFF , 964px 1700px #FFF , 1070px 341px #FFF , 1376px 1258px #FFF , 1884px 570px #FFF , 940px 280px #FFF , 1484px 1658px #FFF , 1806px 1875px #FFF , 1054px 917px #FFF , 1672px 103px #FFF , 783px 574px #FFF , 98px 347px #FFF , 555px 1136px #FFF , 1403px 1237px #FFF , 1203px 339px #FFF , 572px 35px #FFF , 932px 1783px #FFF , 1527px 1850px #FFF , 1959px 1109px #FFF , 892px 623px #FFF , 211px 1388px #FFF , 1581px 1806px #FFF , 868px 1053px #FFF , 1243px 1997px #FFF , 1004px 522px #FFF , 1241px 1707px #FFF , 376px 282px #FFF , 537px 878px #FFF , 1948px 979px #FFF , 532px 688px #FFF , 273px 958px #FFF , 581px 927px #FFF , 1060px 887px #FFF , 486px 1467px #FFF , 1122px 1834px #FFF , 1650px 1763px #FFF , 532px 302px #FFF , 314px 1111px #FFF , 1888px 683px #FFF , 1856px 1040px #FFF , 1780px 1338px #FFF , 24px 1564px #FFF , 1096px 1808px #FFF , 1202px 1968px #FFF , 214px 992px #FFF , 728px 515px #FFF , 247px 278px #FFF , 1670px 45px #FFF , 442px 1579px #FFF , 1143px 30px #FFF , 612px 72px #FFF , 1177px 1303px #FFF , 1898px 1255px #FFF , 378px 1667px #FFF , 326px 1929px #FFF , 1257px 766px #FFF , 1363px 1170px #FFF , 1090px 1667px #FFF , 711px 293px #FFF , 249px 1406px #FFF , 1589px 565px #FFF , 1451px 29px #FFF , 1171px 1459px #FFF , 1294px 1214px #FFF , 342px 942px #FFF , 1945px 353px #FFF , 741px 1185px #FFF , 894px 1453px #FFF , 593px 1584px #FFF , 518px 630px #FFF , 393px 756px #FFF , 34px 608px #FFF; + } + #stars2 { + width: 2px; + height: 2px; + background: transparent; + animation: star_anti 100s linear infinite; + box-shadow: 114px 658px #FFF , 236px 768px #FFF , 1130px 1503px #FFF , 486px 592px #FFF , 1353px 1407px #FFF , 1583px 1741px #FFF , 450px 1479px #FFF , 1845px 327px #FFF , 1520px 361px #FFF , 580px 1699px #FFF , 1277px 1233px #FFF , 1697px 943px #FFF , 568px 1135px #FFF , 1273px 263px #FFF , 788px 126px #FFF , 1834px 1911px #FFF , 1147px 1652px #FFF , 651px 567px #FFF , 79px 1897px #FFF , 1590px 666px #FFF , 1362px 566px #FFF , 275px 367px #FFF , 556px 479px #FFF , 1063px 476px #FFF , 1337px 1119px #FFF , 1780px 1109px #FFF , 1323px 1655px #FFF , 1740px 1165px #FFF , 525px 60px #FFF , 1513px 1484px #FFF , 708px 280px #FFF , 429px 475px #FFF , 563px 1360px #FFF , 1580px 697px #FFF , 1702px 1164px #FFF , 1649px 1952px #FFF , 1580px 1812px #FFF , 70px 1190px #FFF , 1100px 98px #FFF , 1232px 1896px #FFF , 851px 1047px #FFF , 851px 30px #FFF , 596px 1486px #FFF , 666px 526px #FFF , 1855px 1342px #FFF , 80px 531px #FFF , 248px 1804px #FFF , 1990px 263px #FFF , 1796px 1640px #FFF , 1502px 862px #FFF , 1780px 488px #FFF , 1881px 1191px #FFF , 1063px 876px #FFF , 1614px 1073px #FFF , 1414px 666px #FFF , 1865px 289px #FFF , 687px 352px #FFF , 1329px 1312px #FFF , 279px 136px #FFF , 475px 756px #FFF , 1177px 435px #FFF , 1264px 921px #FFF , 467px 1496px #FFF , 391px 1359px #FFF , 666px 1083px #FFF , 1526px 1251px #FFF , 594px 564px #FFF , 991px 525px #FFF , 1511px 875px #FFF , 1935px 1049px #FFF , 1471px 1430px #FFF , 959px 604px #FFF , 1685px 72px #FFF , 1505px 1876px #FFF , 509px 1627px #FFF , 1065px 978px #FFF , 1860px 884px #FFF , 1038px 464px #FFF , 1051px 106px #FFF , 1056px 728px #FFF , 1953px 45px #FFF , 1483px 638px #FFF , 559px 845px #FFF , 1184px 922px #FFF , 1320px 1117px #FFF , 1572px 747px #FFF , 1971px 43px #FFF , 665px 13px #FFF , 1457px 1153px #FFF , 848px 154px #FFF , 1039px 1837px #FFF , 878px 795px #FFF , 1286px 1705px #FFF , 1946px 1143px #FFF , 1114px 1166px #FFF , 1747px 874px #FFF , 1894px 636px #FFF , 1316px 541px #FFF , 1953px 1620px #FFF , 1446px 1773px #FFF , 974px 833px #FFF , 1814px 1211px #FFF , 102px 335px #FFF , 327px 1868px #FFF , 348px 548px #FFF , 353px 1540px #FFF , 1212px 1872px #FFF , 1968px 129px #FFF , 1531px 644px #FFF , 1939px 559px #FFF , 1397px 1876px #FFF , 1446px 1446px #FFF , 1721px 603px #FFF , 924px 1171px #FFF , 1086px 1954px #FFF , 1798px 310px #FFF , 21px 1595px #FFF , 1462px 1948px #FFF , 149px 1752px #FFF , 804px 318px #FFF , 1262px 636px #FFF , 1051px 100px #FFF , 392px 560px #FFF , 654px 1236px #FFF , 1889px 1159px #FFF , 498px 394px #FFF , 522px 1889px #FFF , 1198px 579px #FFF , 1437px 1866px #FFF , 1049px 1064px #FFF , 286px 921px #FFF , 993px 1790px #FFF , 1557px 1997px #FFF , 1525px 532px #FFF , 481px 1561px #FFF , 790px 683px #FFF , 141px 17px #FFF , 1202px 28px #FFF , 518px 1927px #FFF , 90px 1677px #FFF , 1258px 370px #FFF , 1379px 1536px #FFF , 607px 474px #FFF , 163px 139px #FFF , 1025px 1359px #FFF , 815px 845px #FFF , 231px 1212px #FFF , 192px 806px #FFF , 313px 1946px #FFF , 1132px 1808px #FFF , 624px 767px #FFF , 379px 722px #FFF , 733px 1847px #FFF , 628px 1517px #FFF , 1559px 929px #FFF , 234px 397px #FFF , 1230px 1231px #FFF , 849px 726px #FFF , 1148px 786px #FFF , 546px 1533px #FFF , 477px 822px #FFF , 1325px 480px #FFF , 972px 383px #FFF , 334px 958px #FFF , 1032px 664px #FFF , 1781px 40px #FFF , 38px 1335px #FFF , 1634px 1691px #FFF , 1061px 680px #FFF , 1319px 304px #FFF , 82px 1776px #FFF , 1302px 509px #FFF , 1231px 746px #FFF , 1264px 1509px #FFF , 980px 495px #FFF , 1153px 1381px #FFF , 1981px 1918px #FFF , 70px 113px #FFF , 390px 736px #FFF , 1882px 1925px #FFF , 1380px 1326px #FFF , 257px 1681px #FFF , 860px 998px #FFF , 518px 1136px #FFF , 168px 905px #FFF , 500px 1882px #FFF , 1012px 1572px #FFF , 349px 1916px #FFF , 905px 1339px #FFF , 1940px 1803px #FFF , 23px 1159px #FFF , 9px 1559px #FFF , 1658px 776px #FFF , 820px 1361px #FFF , 171px 983px #FFF , 580px 1902px #FFF , 1268px 263px #FFF , 1734px 994px #FFF , 1872px 29px #FFF , 1475px 435px #FFF; + } + #stars2:after { + content: " "; + position: absolute; + top: 2000px; + width: 2px; + height: 2px; + background: transparent; + box-shadow: 114px 658px #FFF , 236px 768px #FFF , 1130px 1503px #FFF , 486px 592px #FFF , 1353px 1407px #FFF , 1583px 1741px #FFF , 450px 1479px #FFF , 1845px 327px #FFF , 1520px 361px #FFF , 580px 1699px #FFF , 1277px 1233px #FFF , 1697px 943px #FFF , 568px 1135px #FFF , 1273px 263px #FFF , 788px 126px #FFF , 1834px 1911px #FFF , 1147px 1652px #FFF , 651px 567px #FFF , 79px 1897px #FFF , 1590px 666px #FFF , 1362px 566px #FFF , 275px 367px #FFF , 556px 479px #FFF , 1063px 476px #FFF , 1337px 1119px #FFF , 1780px 1109px #FFF , 1323px 1655px #FFF , 1740px 1165px #FFF , 525px 60px #FFF , 1513px 1484px #FFF , 708px 280px #FFF , 429px 475px #FFF , 563px 1360px #FFF , 1580px 697px #FFF , 1702px 1164px #FFF , 1649px 1952px #FFF , 1580px 1812px #FFF , 70px 1190px #FFF , 1100px 98px #FFF , 1232px 1896px #FFF , 851px 1047px #FFF , 851px 30px #FFF , 596px 1486px #FFF , 666px 526px #FFF , 1855px 1342px #FFF , 80px 531px #FFF , 248px 1804px #FFF , 1990px 263px #FFF , 1796px 1640px #FFF , 1502px 862px #FFF , 1780px 488px #FFF , 1881px 1191px #FFF , 1063px 876px #FFF , 1614px 1073px #FFF , 1414px 666px #FFF , 1865px 289px #FFF , 687px 352px #FFF , 1329px 1312px #FFF , 279px 136px #FFF , 475px 756px #FFF , 1177px 435px #FFF , 1264px 921px #FFF , 467px 1496px #FFF , 391px 1359px #FFF , 666px 1083px #FFF , 1526px 1251px #FFF , 594px 564px #FFF , 991px 525px #FFF , 1511px 875px #FFF , 1935px 1049px #FFF , 1471px 1430px #FFF , 959px 604px #FFF , 1685px 72px #FFF , 1505px 1876px #FFF , 509px 1627px #FFF , 1065px 978px #FFF , 1860px 884px #FFF , 1038px 464px #FFF , 1051px 106px #FFF , 1056px 728px #FFF , 1953px 45px #FFF , 1483px 638px #FFF , 559px 845px #FFF , 1184px 922px #FFF , 1320px 1117px #FFF , 1572px 747px #FFF , 1971px 43px #FFF , 665px 13px #FFF , 1457px 1153px #FFF , 848px 154px #FFF , 1039px 1837px #FFF , 878px 795px #FFF , 1286px 1705px #FFF , 1946px 1143px #FFF , 1114px 1166px #FFF , 1747px 874px #FFF , 1894px 636px #FFF , 1316px 541px #FFF , 1953px 1620px #FFF , 1446px 1773px #FFF , 974px 833px #FFF , 1814px 1211px #FFF , 102px 335px #FFF , 327px 1868px #FFF , 348px 548px #FFF , 353px 1540px #FFF , 1212px 1872px #FFF , 1968px 129px #FFF , 1531px 644px #FFF , 1939px 559px #FFF , 1397px 1876px #FFF , 1446px 1446px #FFF , 1721px 603px #FFF , 924px 1171px #FFF , 1086px 1954px #FFF , 1798px 310px #FFF , 21px 1595px #FFF , 1462px 1948px #FFF , 149px 1752px #FFF , 804px 318px #FFF , 1262px 636px #FFF , 1051px 100px #FFF , 392px 560px #FFF , 654px 1236px #FFF , 1889px 1159px #FFF , 498px 394px #FFF , 522px 1889px #FFF , 1198px 579px #FFF , 1437px 1866px #FFF , 1049px 1064px #FFF , 286px 921px #FFF , 993px 1790px #FFF , 1557px 1997px #FFF , 1525px 532px #FFF , 481px 1561px #FFF , 790px 683px #FFF , 141px 17px #FFF , 1202px 28px #FFF , 518px 1927px #FFF , 90px 1677px #FFF , 1258px 370px #FFF , 1379px 1536px #FFF , 607px 474px #FFF , 163px 139px #FFF , 1025px 1359px #FFF , 815px 845px #FFF , 231px 1212px #FFF , 192px 806px #FFF , 313px 1946px #FFF , 1132px 1808px #FFF , 624px 767px #FFF , 379px 722px #FFF , 733px 1847px #FFF , 628px 1517px #FFF , 1559px 929px #FFF , 234px 397px #FFF , 1230px 1231px #FFF , 849px 726px #FFF , 1148px 786px #FFF , 546px 1533px #FFF , 477px 822px #FFF , 1325px 480px #FFF , 972px 383px #FFF , 334px 958px #FFF , 1032px 664px #FFF , 1781px 40px #FFF , 38px 1335px #FFF , 1634px 1691px #FFF , 1061px 680px #FFF , 1319px 304px #FFF , 82px 1776px #FFF , 1302px 509px #FFF , 1231px 746px #FFF , 1264px 1509px #FFF , 980px 495px #FFF , 1153px 1381px #FFF , 1981px 1918px #FFF , 70px 113px #FFF , 390px 736px #FFF , 1882px 1925px #FFF , 1380px 1326px #FFF , 257px 1681px #FFF , 860px 998px #FFF , 518px 1136px #FFF , 168px 905px #FFF , 500px 1882px #FFF , 1012px 1572px #FFF , 349px 1916px #FFF , 905px 1339px #FFF , 1940px 1803px #FFF , 23px 1159px #FFF , 9px 1559px #FFF , 1658px 776px #FFF , 820px 1361px #FFF , 171px 983px #FFF , 580px 1902px #FFF , 1268px 263px #FFF , 1734px 994px #FFF , 1872px 29px #FFF , 1475px 435px #FFF; + } + #stars3 { + width: 3px; + height: 3px; + background: transparent; + box-shadow: 519px 875px #FFF , 1497px 751px #FFF , 1256px 88px #FFF , 1168px 1791px #FFF , 1884px 109px #FFF , 1465px 451px #FFF , 450px 370px #FFF , 1560px 703px #FFF , 1788px 1997px #FFF , 1047px 963px #FFF , 1281px 119px #FFF , 439px 96px #FFF , 164px 1956px #FFF , 1360px 930px #FFF , 1387px 347px #FFF , 1073px 1970px #FFF , 1296px 284px #FFF , 25px 1602px #FFF , 455px 944px #FFF , 1177px 738px #FFF , 633px 1142px #FFF , 1730px 1079px #FFF , 1283px 1606px #FFF , 674px 1186px #FFF , 513px 166px #FFF , 1077px 636px #FFF , 1811px 580px #FFF , 971px 1789px #FFF , 694px 1756px #FFF , 703px 1138px #FFF , 1290px 942px #FFF , 351px 1509px #FFF , 1904px 790px #FFF , 68px 819px #FFF , 1097px 362px #FFF , 1035px 331px #FFF , 180px 940px #FFF , 1776px 1229px #FFF , 1487px 781px #FFF , 1131px 1765px #FFF , 1684px 536px #FFF , 939px 367px #FFF , 1102px 1481px #FFF , 741px 887px #FFF , 167px 1132px #FFF , 1756px 529px #FFF , 608px 758px #FFF , 541px 1025px #FFF , 1976px 505px #FFF , 1349px 1257px #FFF , 815px 1388px #FFF , 505px 1351px #FFF , 33px 1945px #FFF , 861px 1695px #FFF , 678px 1360px #FFF , 1615px 727px #FFF , 1138px 726px #FFF , 30px 293px #FFF , 1624px 1044px #FFF , 683px 1242px #FFF , 1781px 1758px #FFF , 906px 1328px #FFF , 1066px 1764px #FFF , 1568px 664px #FFF , 1027px 1876px #FFF , 775px 1099px #FFF , 1605px 208px #FFF , 730px 837px #FFF , 1475px 1482px #FFF , 871px 1759px #FFF , 1240px 15px #FFF , 1987px 705px #FFF , 302px 1049px #FFF , 475px 1015px #FFF , 1843px 1296px #FFF , 493px 631px #FFF , 1613px 164px #FFF , 1863px 156px #FFF , 1479px 423px #FFF , 202px 1499px #FFF , 886px 969px #FFF , 904px 930px #FFF , 1853px 535px #FFF , 726px 914px #FFF , 435px 1205px #FFF , 1732px 1824px #FFF , 1212px 667px #FFF , 499px 31px #FFF , 552px 594px #FFF , 1715px 1814px #FFF , 775px 908px #FFF , 1949px 921px #FFF , 1267px 718px #FFF , 1830px 1960px #FFF , 338px 1325px #FFF , 466px 1120px #FFF , 140px 1675px #FFF , 1919px 664px #FFF , 1136px 771px #FFF , 1888px 1302px #FFF; + animation: star_anti 150s linear infinite; + } + #stars3:after { + content: " "; + position: absolute; + top: 2000px; + width: 3px; + height: 3px; + background: transparent; + box-shadow: 519px 875px #FFF , 1497px 751px #FFF , 1256px 88px #FFF , 1168px 1791px #FFF , 1884px 109px #FFF , 1465px 451px #FFF , 450px 370px #FFF , 1560px 703px #FFF , 1788px 1997px #FFF , 1047px 963px #FFF , 1281px 119px #FFF , 439px 96px #FFF , 164px 1956px #FFF , 1360px 930px #FFF , 1387px 347px #FFF , 1073px 1970px #FFF , 1296px 284px #FFF , 25px 1602px #FFF , 455px 944px #FFF , 1177px 738px #FFF , 633px 1142px #FFF , 1730px 1079px #FFF , 1283px 1606px #FFF , 674px 1186px #FFF , 513px 166px #FFF , 1077px 636px #FFF , 1811px 580px #FFF , 971px 1789px #FFF , 694px 1756px #FFF , 703px 1138px #FFF , 1290px 942px #FFF , 351px 1509px #FFF , 1904px 790px #FFF , 68px 819px #FFF , 1097px 362px #FFF , 1035px 331px #FFF , 180px 940px #FFF , 1776px 1229px #FFF , 1487px 781px #FFF , 1131px 1765px #FFF , 1684px 536px #FFF , 939px 367px #FFF , 1102px 1481px #FFF , 741px 887px #FFF , 167px 1132px #FFF , 1756px 529px #FFF , 608px 758px #FFF , 541px 1025px #FFF , 1976px 505px #FFF , 1349px 1257px #FFF , 815px 1388px #FFF , 505px 1351px #FFF , 33px 1945px #FFF , 861px 1695px #FFF , 678px 1360px #FFF , 1615px 727px #FFF , 1138px 726px #FFF , 30px 293px #FFF , 1624px 1044px #FFF , 683px 1242px #FFF , 1781px 1758px #FFF , 906px 1328px #FFF , 1066px 1764px #FFF , 1568px 664px #FFF , 1027px 1876px #FFF , 775px 1099px #FFF , 1605px 208px #FFF , 730px 837px #FFF , 1475px 1482px #FFF , 871px 1759px #FFF , 1240px 15px #FFF , 1987px 705px #FFF , 302px 1049px #FFF , 475px 1015px #FFF , 1843px 1296px #FFF , 493px 631px #FFF , 1613px 164px #FFF , 1863px 156px #FFF , 1479px 423px #FFF , 202px 1499px #FFF , 886px 969px #FFF , 904px 930px #FFF , 1853px 535px #FFF , 726px 914px #FFF , 435px 1205px #FFF , 1732px 1824px #FFF , 1212px 667px #FFF , 499px 31px #FFF , 552px 594px #FFF , 1715px 1814px #FFF , 775px 908px #FFF , 1949px 921px #FFF , 1267px 718px #FFF , 1830px 1960px #FFF , 338px 1325px #FFF , 466px 1120px #FFF , 140px 1675px #FFF , 1919px 664px #FFF , 1136px 771px #FFF , 1888px 1302px #FFF; + } + .text { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%); + color: #fff; + letter-spacing: 4px; + font-size: 20px; + } + @keyframes star_anti { + 0% { + transform: translateY(0px) + } + 100% { + transform: translateY(-2000px) + } + } \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..4860604 --- /dev/null +++ b/styles.css @@ -0,0 +1,507 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background-color: #000; + overflow: hidden; +} + +#gameGroup { + position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + background-color: #000; +} + +#mainTips { + color: #fff; + font-size: 0.8em; + position: fixed; + top: 10px; + left: 10px; + z-index: 370; +} + +#musicBtn { + position: fixed; + bottom: 3px; + right: 3px; + cursor: pointer; + z-index: 400; + display: none; +} + +#startPanel { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background-color: #fff; + overflow: hidden; + z-index: 300; +} + +#startTop { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background-color: #000000; + z-index: 350; +} + +#startTopProgressBar { + width: 90%; + height: 5%; + margin: 0 5%; + position: absolute; + top: 5%; + background-color: #fff; + z-index: 15; +} + +#startTopProgress { + width: 0%; + height: 100%; + background-color: #666; +} + +#startTopLoadTips { + color: #fff; + font-size: 0.6em; + position: absolute; + top: 10%; + left: 5%; + z-index: 15; +} + +#startTopHint { + color: #66CCFF; + position: absolute; + bottom: 0; + left: 5%; + z-index: 15; + font-size: 1.1em; +} + +#startBackground { + position:absolute; + top:50%; + left:50%; + height: 100%; + width: auto; + transform:translate(-50%,-50%); + z-index: 260; +} + + #startLogo { + user-select: none; + position: absolute; + z-index: 290; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + margin-top: 8%; + max-width: 100%; + text-align: center; + font: bold 4em STXingkai; +} + +#startTitle { + user-select: none; + position: absolute; + z-index: 280; +} + +#startButtonGroup { + width: auto; + position: absolute; + text-align: center; + font-size: 1.4em; + display: none; + z-index: 310; + bottom: 0; + margin-bottom: 5%; + left: 50%; + transform: translateX(-50%); + padding: 15px 25px; + min-width: 20%; + /* default value */ + background-color: #32369F; + opacity: 0.85; + color: #FFFFFF; + border: #FFFFFF 2px solid; + caret-color: #FFD700; + border-radius: 10px; +} + +#startButtons { + display: block; +} + +#levelChooseButtons { + display: none; +} + +.startButton { + width: 100%; + margin: 0; + font-weight: bold; + display: block; + cursor: pointer; + padding: 4px 0; + border-color: transparent; + border-width: 2px; + border-style: solid; + border-radius: 6px; +} + +.onChoiceAnimate { + animation: onChoice 2s ease-in-out 0s infinite normal none running; +} + +#floorMsgGroup { + top: 3px; + right: 3px; + position: absolute; + text-align: center; + display: none; + color: #fff; + background-color: #000; + z-index: 230; +} + +#logoLabel { + margin-top: 8%; + font: bold 3em STXingkai; + margin-left: auto; + margin-right: auto; +} + +#versionLabel { + margin-top: -3%; + font-size: 1.2em; + font-weight: bold; +} + +#floorNameLabel { + margin-top: 30px; + font-size: 1.6em; + font-weight: bold; +} + +#statusBar { + position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + background: url(project/materials/ground.png) repeat; + z-index: 185; + display: none; + top: 0; + left: 0; + padding: 3px; +} +#statusBar .status{ + user-select: none; + position: relative; + display: block; + float: left; + width: 100%; +} +.status img{ + vertical-align: middle; + width: auto; + height: 100%; + max-height: 1.6em; +} +#statusBar span{ + user-select: none; + font: bold italic 1.1em Verdana; + display: inline; +} +#statusBar p { + user-select: none; + display: inline-block; + vertical-align: middle; + width: 60%; + margin: 0; + color: white; + font: bold italic 1.1em Verdana; + white-space: nowrap; +} +#toolBar { + position: absolute; + background: url(project/materials/ground.png) repeat; + z-index: 210; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + display: none; + padding: 3px; +} +#toolBar .tools{ + position: relative; + display: block; + float: left; +} + +p#hard { + user-select: none; + width: 6em; + vertical-align: middle; + display: inline-block; + color: red; + font: bold normal 1.1em "Arial Black"; + text-align: center; + margin: 0 6px 6px 0; + word-break: keep-all; +} + +span#poison, span#weak, span#curse, span#pickaxe, span#bomb, span#fly { + user-select: none; + font-style: normal; + font-size: 1em; +} + +p#name { + font-style: normal; +} + +.gameCanvas { + position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +#gif { + z-index: 20; + position: absolute; + overflow: hidden; +} + +#gif2 { + z-index: 90; + position: absolute; + overflow: hidden; +} + +#gameDraw { + position: absolute; + background: #000000; + overflow: hidden; + z-index: 185; +} + +#bg { + z-index: 10; +} + +#event { + z-index: 30; +} + +#hero { + z-index: 40; +} + +#event2 { + z-index: 50; +} + +#fg { + z-index: 60; +} + +#damage { + z-index: 65; +} + +#animate { + z-index: 70; +} + +#curtain { + z-index: 125; +} + +#ui { + z-index: 140; +} + +#data { + z-index: 170; +} + +#inputDiv { + display: none; + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + background: rgba(127,127,127,0.6); + z-index: 2000 +} + +#inputDialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -55%); + background: white; + width: 250px; + min-height: 50px; +} + +#inputMessage { + word-wrap: break-word; + text-align: left; + margin-left: 8%; + margin-right: 5%; +} + +#inputBox { + margin-left: 8%; + width: 80%; + margin-bottom: 10px; + padding: 5px 3px; + border: 1px solid; + background: #F0F0F0; +} + +#inputYes { + margin-bottom: 15px; + margin-left: 8%; +} + +#inputNo { + float:right; + margin-right: 10%; +} + +#_selector, ._uievent_selector { + animation: selector 2s ease-in-out 0s infinite normal none running; +} + +@-webkit-keyframes selector { + 0% { opacity: 0.95; } + 50% { opacity: 0.55; } + 100% { opacity: 0.95; } +} + +@keyframes selector { + 0% { opacity: 0.95; } + 50% { opacity: 0.55; } + 100% { opacity: 0.95; } +} + +#next { + width: 5px; + height: 5px; + display: none; + position: absolute; + transform: rotate(45deg); + border-bottom-width: 4px; + border-bottom-style: solid; + border-right-width: 4px; + border-right-style: solid; + -webkit-animation: next .5s ease-in-out alternate infinite; + animation: next .5s ease-in-out alternate infinite; + left: 0; + top: 0; + opacity: 0.7; + z-index: 169; +} + +@-webkit-keyframes next { + 100% { + transform: rotate(45deg) translate(-3px, -3px); + } +} +@keyframes next { + 100% { + transform: rotate(45deg) translate(-3px, -3px); + } +} + +#startImageBackgroundDiv { + display: none; + width: 100%; + height: 100%; + position: fixed; + z-index: 10000; +} + +#startImageDiv { + width: 100%; + height: 100%; + position: fixed; + background: black; + opacity: 1; +} + +#startImageLogo { + opacity: 0; + max-width: 60%; + max-height: 60%; + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.startImageAnimation { + -webkit-animation: startImage 4s ease-in-out 1s alternate 1; + animation: startImage 4s ease-in-out 1s alternate 1; +} + +@-webkit-keyframes startImage { + 0% { opacity: 0; } + 60% { opacity: 1; } + 100% { opacity: 0; } +} + +@keyframes startImage { + 0% { opacity: 0; } + 60% { opacity: 1; } + 100% { opacity: 0; } +} + +.startImageDivAnimation { + -webkit-animation: startImageDivDisappear 2s ease-in-out 5s alternate 1; + animation: startImageDivDisappear 2s ease-in-out 5s alternate 1; +} + +@-webkit-keyframes startImageDivDisappear { + 0% { opacity: 1 } + 100% { opacity: 0 } +} + +@keyframes startImageDivDisappear { + 0% { opacity: 1 } + 100% { opacity: 0 } +} + +#ui-editor { + z-index: 9999; + position: absolute; + overflow: visible; + height: 100%; +} + +@font-face { + font-family: Fira Code; + src: url(../src/fonts/FiraCode-Regular.ttf); +} + +/* 注释下面这三行以开启抗锯齿 */ +.anti-aliasing { + image-rendering: pixelated; +} \ No newline at end of file diff --git a/启动服务.exe b/启动服务.exe new file mode 100644 index 0000000..349d1de Binary files /dev/null and b/启动服务.exe differ