diff --git a/.idea/mota-js.xml b/.idea/mota-js.xml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/.idea/mota-js.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..e145e12f --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Zhang Chen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index db47a83d..2a03e535 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,21 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! ## 更新说明 +### 2018.1.21 V1.3.2 + +* [x] 增加录像和回放功能。 +* [x] 增加统计功能,现在能看到每部塔的游戏人数、通关人数和当前MAX了。 +* [x] 增加浏览地图功能,玩家可以快速查看每层楼的地图。 +* [x] 现在保存文件到本地,以及从本地文件读档了。 +* [x] 可以在全局开关中设置剑盾是否作为装备存在。 +* [x] 修复了部分已知Bug。 + +### 2018.1.12 V1.3.1 + +* [x] 增加虚拟键盘 +* [x] 增加自动存档(回退),A键可快速读档 +* [x] 修复几处较为严重的Bug + ### 2018.1.1 V1.3 * [x] 支持全键盘操作。 diff --git a/_server/css/editor.css b/_server/css/editor.css index 02b25b10..eb70fd55 100644 --- a/_server/css/editor.css +++ b/_server/css/editor.css @@ -47,6 +47,7 @@ body{ white-space: pre; border: 1px solid #ddd; border-radius: 2px; + overflow: auto; } #editTip{ position: absolute; @@ -68,7 +69,6 @@ body{ margin-right: 20px; margin-top: 5px; } - #mid{ position: absolute; left: 448px; diff --git a/_server/vendor/polyfill.min.js b/_server/vendor/polyfill.min.js new file mode 100644 index 00000000..80fe8dc8 --- /dev/null +++ b/_server/vendor/polyfill.min.js @@ -0,0 +1 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(){}function n(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,f._immediateFn(function(){var i=1===e._state?n.onFulfilled:n.onRejected;if(null!==i){var r;try{r=i(e._value)}catch(e){return void o(n.promise,e)}t(n.promise,r)}else(1===e._state?t:o)(n.promise,e._value)})):e._deferreds.push(n)}function t(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof f)return e._state=3,e._value=n,void i(e);if("function"==typeof t)return void r(function(e,n){return function(){e.apply(n,arguments)}}(t,n),e)}e._state=1,e._value=n,i(e)}catch(n){o(e,n)}}function o(e,n){e._state=2,e._value=n,i(e)}function i(e){2===e._state&&0===e._deferreds.length&&f._immediateFn(function(){e._handled||f._unhandledRejectionFn(e._value)});for(var t=0,o=e._deferreds.length;o>t;t++)n(e,e._deferreds[t]);e._deferreds=null}function r(e,n){var i=!1;try{e(function(e){i||(i=!0,t(n,e))},function(e){i||(i=!0,o(n,e))})}catch(e){if(i)return;i=!0,o(n,e)}}function f(e){if(!(this instanceof f))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],r(e,this)}var u=setTimeout,c=f.prototype;c.catch=function(e){return this.then(null,e)},c.then=function(t,o){var i=new this.constructor(e);return n(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(t,o,i)),i},f.all=function(e){return new f(function(n,t){function o(e,f){try{if(f&&("object"==typeof f||"function"==typeof f)){var u=f.then;if("function"==typeof u)return void u.call(f,function(n){o(e,n)},t)}i[e]=f,0==--r&&n(i)}catch(e){t(e)}}if(!e||void 0===e.length)throw new TypeError("Promise.all accepts an array");var i=Array.prototype.slice.call(e);if(0===i.length)return n([]);for(var r=i.length,f=0;i.length>f;f++)o(f,i[f])})},f.resolve=function(e){return e&&"object"==typeof e&&e.constructor===f?e:new f(function(n){n(e)})},f.reject=function(e){return new f(function(n,t){t(e)})},f.race=function(e){return new f(function(n,t){for(var o=0,i=e.length;i>o;o++)e[o].then(n,t)})},f._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){u(e,0)},f._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var a=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==a)return a;throw Error("unable to locate global object")}();a.Promise||(a.Promise=f)}); diff --git a/_server/vm.js b/_server/vm.js index 4c1644bf..d23e1c91 100644 --- a/_server/vm.js +++ b/_server/vm.js @@ -1,23 +1,14 @@ // vue 相关处理 -document.body.onmousedown = function(e){ - selectBox.isSelected = false; - editor.info = {}; -} -iconLib.onmousedown = function(e){ - e.stopPropagation(); -} var exportM = new Vue({ el: '#exportM', - + data: { + isExport: false, + }, methods: { exportMap: function(){ editor.updateMap(); - if(editArea.error) { - tip.whichShow = 3; - - return; - } + var filestr=''; for (var yy = 0; yy < 13; yy++){ filestr+='[' @@ -42,7 +33,9 @@ var exportM = new Vue({ filestr += ']'+(yy==12?'':',\n'); } pout.value = filestr; - + editArea.mapArr = filestr; + this.isExport = true; + editArea.error = 0; tip.whichShow = 2; } } @@ -64,10 +57,15 @@ var editArea = new Vue({ mapArr: function (val, oldval) { var that = this; if(val=='') return; + if(exportM.isExport){ + exportM.isExport = false; + return; + } if(that.formatArr()){ that.error = 0; - clearTimeout(that.formatTimer); + setTimeout(function(){ + that.mapArr = that.formatArr(); that.drawMap(); tip.whichShow = 8 }, 1000); @@ -107,7 +105,8 @@ var editArea = new Vue({ }, formatArr: function(){ var formatArrStr = ''; - + var that = this; + clearTimeout(that.formatTimer); if(this.mapArr.split(/\D+/).join(' ').trim().split(' ').length != 169) return false; var arr = this.mapArr.replace(/\s+/g, '').split('],['); @@ -128,7 +127,6 @@ var editArea = new Vue({ } formatArrStr += ']'+(i==12?'':',\n'); } - return formatArrStr; } } diff --git a/docs/api.md b/docs/api.md index ebd28f99..a47385d8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -35,7 +35,7 @@ main.statusBar.image.load.onclick // 点击状态栏中的读档按钮时 main.statusBar.image.settings.onclick // 点击状态栏中的系统菜单时 main.dom.playGame.onclick // 点击“开始游戏”时 main.dom.loadGame.onclick // 点击“载入游戏”时 -main.dom.aboutGame.onclick // 点击“关于本塔”时 +main.dom.replayGame.onclick // 点击“录像回放”时 main.dom.easyLevel.onclick // 点击“简单难度”时 main.dom.normalLevel.onclick // 点击“普通难度”时 main.dom.hardLevel.onclick // 点击“困难难度”时 @@ -56,6 +56,7 @@ core.hideStartAnimate // 隐藏游戏开始界面 core.setStartProgressVal // 设置加载进度条进度 core.setStartLoadTipText // 设置加载进度条提示文字 core.loader // 加载图片和音频 +core.loadAutotile // 加载Autotile core.loadImage // 加载图片 core.loadMusic // 加载音频 core.isPlaying // 游戏是否已经开始 @@ -93,6 +94,7 @@ core.setAutoHeroMove // 设置勇士的自动行走路线 core.autoHeroMove // 让勇士开始自动行走 core.setHeroMoveInterval // 设置行走的效果动画 core.setHeroMoveTriggerInterval // 设置勇士行走过程中对事件的触发检测 +core.moveAction // 实际每一步的行走过程 * core.turnHero(direction) // 设置勇士的方向(转向) core.canMoveHero // 勇士能否前往某方向 core.moveHero // 让勇士开始移动 @@ -176,18 +178,25 @@ core.getLocalStorage // 获得本地存储 core.removeLocalStorage // 移除本地存储 core.clone // 深拷贝一个对象 core.formatDate // 格式化时间为字符串 +core.formatDate2 // 格式化时间为最简字符串 core.setTwoDigits // 两位数显示 core.debug // 进入Debug模式,攻防血和钥匙都调成很高的数值 +core.replay // 开始回放 core.checkStatus // 判断当前能否进入某个事件 core.openBook // 点击怪物手册时的打开操作 core.useFly // 点击楼层传送器时的打开操作 core.openToolbox // 点击工具栏时的打开操作 +core.openQuickShop // 点击快捷商店时的打开操作 core.save // 点击保存按钮时的打开操作 core.load // 点击读取按钮时的打开操作 +core.openSettings // 点击设置按钮时的打开操作 +core.autosave // 自动存档 core.doSL // 实际进行存读档事件 core.syncSave // 存档同步操作 core.saveData // 存档到本地 core.loadData // 从本地读档 +core.encodeRoute // 将路线压缩 +core.decodeRoute // 将路线解压缩 * core.setStatus // 设置勇士属性 * core.getStatus // 获得勇士属性 core.getLvName // 获得某个等级的名称 @@ -198,6 +207,9 @@ core.insertAction // 往当前事件列表之前插入一系列事件 * core.lockControl // 锁定状态栏,常常用于事件处理 * core.unlockControl // 解锁状态栏 * core.isset // 判断某对象是否不为undefined也不会null +core.readFile // 读取一个本地文件内容 +core.download // 下载文件到本地 +core.copy // 复制一段文字到剪切板 * core.playBgm // 播放背景音乐 * core.pauseBgm // 暂停背景音乐的播放 * core.resumeBgm // 恢复背景音乐的播放 @@ -240,6 +252,7 @@ core.events.startGame // 游戏开始事件 * core.events.setInitData // 不同难度分别设置初始属性 * core.events.win // 游戏获胜事件 * core.events.lose // 游戏失败事件 +core.evens.gameOver // 游戏结束 core.events.afterChangeFloor // 转换楼层结束的事件 core.events.doEvents // 开始执行一系列自定义事件 core.events.doAction // 执行当前自定义事件列表中的下一个事件 @@ -260,6 +273,7 @@ core.events.changeLight // 改变亮灯(感叹号)的事件 * core.events.afterLoadData // 读档事件后,载入事件前,可以执行的操作 // ------ 点击事件和键盘事件的处理 ------ +core.events.longClick // 长按 core.events.keyDownCtrl // 按下Ctrl键时(快捷跳过对话) core.events.clickConfirmBox // 确认框界面时的点击操作 core.events.keyUpConfirmBox // 确认框界面时,放开某个键的操作 @@ -273,6 +287,9 @@ core.events.clickBookDetail // 怪物手册属性显示界面时的点击操作 core.events.clickFly // 楼层传送器界面时的点击操作 core.events.keyDownFly // 楼层传送器界面时,按下某个键的操作 core.events.keyUpFly // 楼层传送器界面时,放开某个键的操作 +core.events.clickViewMaps // 浏览地图界面时的点击操作 +core.events.keyDownViewMaps // 浏览地图界面时,按下某个键的操作 +core.events.keyUpViewMaps // 浏览地图界面时,放开某个键的操作 core.events.clickShop // 商店界面时的点击操作 core.events.keyDownShop // 商店界面时,按下某个键的操作 core.events.keyUpShop // 商店界面时,放开某个键的操作 @@ -295,6 +312,7 @@ core.events.keyUpSettings // 系统菜单栏界面时,放开某个键的操作 core.events.clickSyncSave // 同步存档界面时的点击操作 core.events.keyDownSyncSave // 同步存档界面时,按下某个键的操作 core.events.keyUpSyncSave // 同步存档界面时,放开某个键的操作 +core.events.clickKeyBoard // 虚拟键盘界面时的点击操作 core.events.clickAbout // “关于”界面时的点击操作 ``` @@ -341,6 +359,7 @@ core.ui.drawPagination // 绘制分页 core.ui.drawEnemyBook // 绘制怪物手册 core.ui.drawBookDetail // 绘制怪物属性的详细信息 core.ui.drawFly // 绘制楼层传送器 +core.ui.drawMaps // 绘制浏览地图界面 core.ui.drawToolbox // 绘制道具栏 core.ui.drawSLPanel // 绘制存档/读档界面 core.ui.drawThumbnail // 绘制一个缩略图 diff --git a/docs/element.md b/docs/element.md index ef764af5..19b1206b 100644 --- a/docs/element.md +++ b/docs/element.md @@ -12,9 +12,9 @@ 本塔目前支持的所有道具列表在样板0层中已全部给出。当你在样板0层中拿到某个宝物时会有提示,这里不再赘述,详见拿到该道具的说明。 -大多数宝物都有默认的效果,十字架和屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](personalization#自定义道具效果)。 +大多数宝物都有默认的效果,屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](personalization#自定义道具效果)。 -!> 请注意,本塔没有"装备"的说法,所有剑盾拿到后将立刻作为攻防数值直接加到勇士的属性上。 +如需让剑盾变成装备,可以直接在`data.js`中设置`'equipment': true`即可。 拿到道具后将触发`afterGetItem`事件,有关事件的详细介绍请参见[事件](event)。 @@ -65,6 +65,7 @@ enemys.prototype.getSpecialText = function (enemyId) { if (this.hasSpecial(special, 18)) text.push("阻击"); if (this.hasSpecial(special, 19)) text.push("自爆"); if (this.hasSpecial(special, 20)) text.push("无敌"); + if (this.hasSpecial(special, 21)) text.push("退化"); return text.join(" "); } ``` @@ -94,6 +95,8 @@ N连击怪物的special是6,且我们可以为它定义n代表实际连击数 吸血怪需要在怪物后添加value,代表吸血的比例。 +可以给吸血怪添加`'add': true`来将吸血的数值加到自身上。 + ![怪物吸血](./img/blood.png) 中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 @@ -106,7 +109,7 @@ N连击怪物的special是6,且我们可以为它定义n代表实际连击数 领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 -领域是十字伤害还是九宫格伤害由data.js中的全局变量`zoneSquare`设定。你也可以对该怪物自行进行设定。 +领域是十字伤害还是九宫格伤害由`zoneSquare`设定,如设置为true则为九宫格伤害,不指定或为false则为十字伤害。 `range`选项可选,代表该领域怪的范围,不写则默认为1。 @@ -118,6 +121,10 @@ N连击怪物的special是6,且我们可以为它定义n代表实际连击数 请注意如果吸血、领域、阻击中任何两个同时存在,则value会冲突。**因此请勿将吸血、领域或阻击放置在同一个怪物身上。** +退化怪需要在后面增加'atkValue'和'defValue'表示退化的数值。 + +![怪物退化](./img/tuihua.png) + 如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 ## 路障,楼梯,传送门 @@ -142,10 +149,6 @@ floorId指定的是目标楼层的唯一标识符(ID)。 可以指定time,指定后切换动画时长为指定的数值。 -楼梯和传送门默认可`"穿透"`。所谓穿透,就是当寻路穿过一个楼梯/传送门后,不会触发楼层传送事件,而是继续前进。通过系统Flag可以指定是否穿透,你也可以对每个传送点单独设置该项。 - -![楼层转换穿透](./img/floorset.png) - ## 背景音乐 本塔支持BGM和SE的播放。 @@ -169,6 +172,8 @@ this.sounds = [ // 在此存放所有的SE,和文件名一致 !> mid格式是通过数学方法模拟出来的音乐效果,质量可能会和实际效果差距较大。 +!> **警告!** mid格式在手机端播放可能会特别卡,仍推荐直接使用mp3/ogg来播放。 + 定义完毕后,我们可以调用`playBgm`/`playSound`事件来播放对应的音乐/音效,有关事件的详细介绍请参见[事件](event)。 **另外,考虑到用户的流量问题,将遵循如下规则:** @@ -190,17 +195,21 @@ this.sounds = [ // 在此存放所有的SE,和文件名一致 - **点任意块并拖动:** 指定寻路路线 - **单击勇士:** 转向 - **双击勇士:** 轻按(仅在轻按开关打开时有效) +- **长按任意位置:** 打开虚拟键盘 键盘操作快捷键如下: - **[CTRL]** 跳过对话 - **[X]** 打开/关闭怪物手册 - **[G]** 打开/关闭楼层传送器 +- **[A]** 读取自动存档 - **[S/D]** 打开/关闭存/读档页面 - **[K]** 打开/关闭快捷商店选择列表 - **[T]** 打开/关闭工具栏 - **[ESC]** 打开/关闭系统菜单 - **[H]** 打开帮助页面 +- **[Z]** 转向 +- **[R]** 回退 - **[SPACE]** 轻按(仅在轻按开关打开时有效) - **[1]** 快捷使用破墙镐 - **[2]** 快捷使用炸弹/圣锤 diff --git a/docs/event.md b/docs/event.md index 58d0917e..f1ef08d2 100644 --- a/docs/event.md +++ b/docs/event.md @@ -16,7 +16,8 @@ - 启用状态下,该事件才处于可见状态,可被触发、交互与处理。 - 禁用状态下该事件相当于不存在,不可见、不可被触发、不可交互。 -所有事件默认情况下都是启用的,除非指定了`enable: false`。 +所有事件默认情况下都是启用的,除非指定了`enable: false`。 + 在事件列表中使用`type: show`和`type: hide`可以将一个禁用事件启用,或将一个启用事件给禁用。 @@ -465,6 +466,8 @@ direction为可选的,指定的话将使勇士的朝向变成该方向 time为可选的,指定的话将作为楼层切换动画的时间。 +**time也可以置为0,如果为0则没有楼层切换动画。** + !> **changeFloor到达一个新的楼层,将不会执行firstArrive事件!如有需求请在到达点设置自定义事件,然后使用type: trigger立刻调用之。** ### changePos: 当前位置切换/勇士转向 @@ -844,8 +847,9 @@ events.prototype.addPoint = function (enemy) { 全局商店定义在`data.js`中,找到shops一项。 ``` js -"shops": { // 定义全局商店(即快捷商店) - "moneyShop1": { // 商店唯一ID +"shops": [ // 定义全局商店(即快捷商店) + { + "id": "moneyShop1", // 商店唯一ID "name": "贪婪之神", // 商店名称(标题) "icon": "blueShop", // 商店图标,blueShop为蓝色商店,pinkShop为粉色商店 "textInList": "1F金币商店", // 在快捷商店栏中显示的名称 @@ -870,7 +874,8 @@ events.prototype.addPoint = function (enemy) { // "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍 ] }, - "expShop1": { // 商店唯一ID + { + "id": "expShop1", // 商店唯一ID "name": "经验之神", "icon": "pinkShop", "textInList": "1F经验商店", @@ -885,15 +890,15 @@ events.prototype.addPoint = function (enemy) { {"text": "攻击+5", "need": "30", "effect": "status:atk+=5"}, {"text": "防御+5", "need": "30", "effect": "status:def+=5"}, ] - }, -}, + } +], ``` -全局商店全部定义在`data.js`中的shops一项里。 -每个全局商店有一个唯一标识符(ID),然后是一系列对该商店的定义。 +全局商店全部定义在`data.js`中的shops一项里。商店以数组形式存放,每一个商店都是其中的一个对象。 +- id 为商店的唯一标识符(ID),请确保任何两个商店的id都不相同 - name 为商店的名称(打开商店后的标题) -- icon 为商店的图标,blueShop为蓝色商店,pinkShop为粉色商店 +- icon 为商店的图标,在icons.js的npcs中定义。如woman可代表一个商人。 - textInList 为其在快捷商店栏中显示的名称,如"3楼金币商店"等 - use 为消耗的类型,是金币(money)还是经验(experience)。 - need 是一个表达式,计算商店所需要用到的数值。 @@ -967,6 +972,26 @@ events.prototype.addPoint = function (enemy) { 当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。 +## 使用炸弹后的事件 + +上面的afterBattle事件只对和怪物进行战斗后才有会被处理。 + +如果我们想在使用炸弹后也能触发一些事件(如开门),则可以在`events.js`里面的`afterUseBomb`函数进行处理: + +``` js +////// 使用炸弹/圣锤后的事件 ////// +events.prototype.afterUseBomb = function () { + // 这是一个使用炸弹也能开门的例子 + if (core.status.floorId=='xxx' && core.terrainExists(x0,y0,'specialDoor') // 某个楼层,该机关门存在 + && !core.enemyExists(x1,y1) && !core.enemyExists(x2,y2)) // 且守门的怪物都不存在 + { + core.insertAction([ // 插入事件 + {"type": "openDoor", "loc": [x0,y0]} // 开门 + ]) + } +} +``` + ## 战前剧情 有时候光战后事件`afterBattle`是不够的,我们可能还需要战前剧情,例如Boss战之前和Boss进行一段对话。 diff --git a/docs/img/blood.png b/docs/img/blood.png index 7d369e74..53d52171 100644 Binary files a/docs/img/blood.png and b/docs/img/blood.png differ diff --git a/docs/img/tuihua.png b/docs/img/tuihua.png new file mode 100644 index 00000000..de7d1836 Binary files /dev/null and b/docs/img/tuihua.png differ diff --git a/docs/start.md b/docs/start.md index 8ddde2af..49676e78 100644 --- a/docs/start.md +++ b/docs/start.md @@ -9,7 +9,7 @@ 你需要有满足如下条件才能进行制作: - Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) -- 任一款现代浏览器。强烈推荐Chrome。 +- Chrome浏览器。其他浏览器可能会导致本地服务器产生闪退等现象。 - 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 - ([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) @@ -28,6 +28,8 @@ * “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。 * “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。 +!> **警告:** 非Chrome浏览器(如Edge/IE等)下本地服务器可能表现不正常,会出现闪退等现象。请务必下载安装Chrome浏览器。 + ## 新建剧本 类似于RMXP,本塔每层楼都是一个“剧本”,剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本,并生成实际的地图供游戏使用。 @@ -147,7 +149,7 @@ 只需要修改自己用到的怪物属性即可,其他没有用到的怪物完全无所谓。 -做完后保存所有文件,然后右键,选择使用chrome浏览器打开`index.html`,就能立刻看到自己的塔并开始游戏啦!是不是很简单呢! +做完后保存所有文件,在本地服务器中“启动游戏”,就能立刻看到自己的塔并开始游戏啦!是不是很简单呢! ![保存](./img/save.png) diff --git a/drawMapGUI.html b/drawMapGUI.html index bddabf11..2a32d6c4 100644 --- a/drawMapGUI.html +++ b/drawMapGUI.html @@ -77,31 +77,34 @@ + + + - diff --git a/images/items.png b/images/items.png index c1458fe0..626b63b7 100644 Binary files a/images/items.png and b/images/items.png differ diff --git a/images/terrains.png b/images/terrains.png index 31ff78cf..15d492c3 100644 Binary files a/images/terrains.png and b/images/terrains.png differ diff --git a/images/yewai.png b/images/yewai.png deleted file mode 100644 index ca42c618..00000000 Binary files a/images/yewai.png and /dev/null differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/animates0:经典.png b/images/常用素材:如需使用请直接替换目录中的对应文件/animates0:经典.png new file mode 100644 index 00000000..c1c1cb1a Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/animates0:经典.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/animates1:旋转门.png b/images/常用素材:如需使用请直接替换目录中的对应文件/animates1:旋转门.png new file mode 100644 index 00000000..3a6cdbb5 Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/animates1:旋转门.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/animates2:四方门.png b/images/常用素材:如需使用请直接替换目录中的对应文件/animates2:四方门.png new file mode 100644 index 00000000..dead2aea Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/animates2:四方门.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/items0:经典.png b/images/常用素材:如需使用请直接替换目录中的对应文件/items0:经典.png index c1458fe0..626b63b7 100644 Binary files a/images/常用素材:如需使用请直接替换目录中的对应文件/items0:经典.png and b/images/常用素材:如需使用请直接替换目录中的对应文件/items0:经典.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/items1:圆形宝石.png b/images/常用素材:如需使用请直接替换目录中的对应文件/items1:圆形宝石.png deleted file mode 100644 index 7150fe9f..00000000 Binary files a/images/常用素材:如需使用请直接替换目录中的对应文件/items1:圆形宝石.png and /dev/null differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/items1:方块宝石.png b/images/常用素材:如需使用请直接替换目录中的对应文件/items1:方块宝石.png new file mode 100644 index 00000000..b7226d92 Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/items1:方块宝石.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/四方机关门.png b/images/常用素材:如需使用请直接替换目录中的对应文件/四方机关门.png new file mode 100644 index 00000000..5f31373f Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/四方机关门.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/宝石1.png b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石1.png new file mode 100644 index 00000000..d930ea4e Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石1.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/宝石2.png b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石2.png new file mode 100644 index 00000000..4749c79d Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石2.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/宝石3.png b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石3.png new file mode 100644 index 00000000..6a1d977a Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石3.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/宝石4.png b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石4.png new file mode 100644 index 00000000..612e1efa Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/宝石4.png differ diff --git a/images/常用素材:如需使用请直接替换目录中的对应文件/旋转机关门.png b/images/常用素材:如需使用请直接替换目录中的对应文件/旋转机关门.png new file mode 100644 index 00000000..ce9bc323 Binary files /dev/null and b/images/常用素材:如需使用请直接替换目录中的对应文件/旋转机关门.png differ diff --git a/index.html b/index.html index ae063d97..60b85214 100644 --- a/index.html +++ b/index.html @@ -32,7 +32,7 @@
开始游戏 载入游戏 - 关于本塔 + 录像回放
简单 diff --git a/libs/core.js b/libs/core.js index 145c6b9b..e5a4cafe 100644 --- a/libs/core.js +++ b/libs/core.js @@ -45,6 +45,21 @@ function core() { 'playingBgm': null, // 正在播放的BGM 'isPlaying': false, } + this.platform = { + 'isOnline': true, // 是否http + 'isPC': true, // 是否是PC + 'isAndroid': false, // 是否是Android + 'isIOS': false, // 是否是iOS + 'isWeChat': false, // 是否是微信 + 'isQQ': false, // 是否是QQ + 'isChrome': false, // 是否是Chrome + 'supportCopy': false, // 是否支持复制到剪切板 + + 'fileInput': null, // FileInput + 'fileReader': null, // 是否支持FileReader + 'successCallback': null, // 读取成功 + 'errorCallback': null, // 读取失败 + } // 样式 this.domStyle = { styles: [], @@ -78,8 +93,17 @@ function core() { 'stepPostfix': [], 'mouseOutCheck': 1, 'moveStepBeforeStop': [], + 'downTime': null, - // 勇士状态;中心对称飞行器 + // 路线&回放 + 'route': [], + 'replay': { + 'replaying': false, + 'pausing': false, + 'animate': false, // 正在某段动画中 + 'toReplay': [], + 'totalList': [] + }, // event事件 'saveIndex': null, @@ -129,7 +153,12 @@ core.prototype.init = function (dom, statusBar, canvas, images, pngs, bgms, soun } core.values = core.clone(core.data.values); core.firstData = core.data.getFirstData(); - core.initStatus.shops = core.firstData.shops; + + // core.initStatus.shops = core.firstData.shops; + core.firstData.shops.forEach(function (t) { + core.initStatus.shops[t.id] = t; + }) + core.dom.versionLabel.innerHTML = core.firstData.version; core.dom.logoLabel.innerHTML = core.firstData.title; document.title = core.firstData.title + " - HTML5魔塔"; @@ -140,7 +169,8 @@ core.prototype.init = function (dom, statusBar, canvas, images, pngs, bgms, soun core.material.icons = core.icons.getIcons(); core.material.events = core.events.getEvents(); - if (location.protocol.indexOf("http")==0) { + core.platform.isOnline = location.protocol.indexOf("http")==0; + if (core.platform.isOnline) { window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext; try { core.musicStatus.audioContext = new window.AudioContext(); @@ -150,12 +180,55 @@ core.prototype.init = function (dom, statusBar, canvas, images, pngs, bgms, soun } } - // 音效设置部分 - var isPC = true; ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"].forEach(function (t) { - if (navigator.userAgent.indexOf(t)>=0) isPC=false; + if (navigator.userAgent.indexOf(t)>=0) { + if (t=='iPhone' || t=='iPad' || t=='iPod') core.platform.isIOS = true; + if (t=='Android') core.platform.isAndroid=true; + core.platform.isPC=false; + } }); - if (isPC) { + + try { + core.platform.supportCopy = document.queryCommandSupported("copy"); + } + catch (e) { + core.platform.supportCopy = false; + } + + var chrome=/Chrome\/(\d+)\./.exec(navigator.userAgent); + if (core.isset(chrome) && parseInt(chrome[1])>=50) + core.platform.isChrome = true; + core.platform.isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); + core.platform.isQQ = /QQ/.test(navigator.userAgent); + core.platform.isWeChat = /MicroMessenger/.test(navigator.userAgent); + + if (window.FileReader) { + core.platform.fileReader = new FileReader(); + core.platform.fileReader.onload = function () { + var content=core.platform.fileReader.result; + var obj=null; + try { + obj=JSON.parse(content); + if (core.isset(obj)) { + if (core.isset(core.platform.successCallback)) + core.platform.successCallback(obj); + return; + } + } + catch (e) {} + alert("不是有效的JSON文件!"); + + if (core.isset(core.platform.errorCallback)) + core.platform.errorCallback(); + + }; + core.platform.fileReader.onerror = function () { + if (core.isset(core.platform.errorCallback)) + core.platform.errorCallback(); + } + } + + if (core.platform.isPC) { // 如果是PC端直接加载 core.musicStatus.startDirectly = true; } @@ -174,7 +247,6 @@ core.prototype.init = function (dom, statusBar, canvas, images, pngs, bgms, soun core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); core.setLocalStorage('soundStatus', core.musicStatus.soundStatus); - // switchs core.flags.battleAnimate = core.getLocalStorage('battleAnimate', core.flags.battleAnimate); core.flags.displayEnemyDamage = core.getLocalStorage('enemyDamage', core.flags.displayEnemyDamage); @@ -442,7 +514,7 @@ core.prototype.clearStatus = function() { } ////// 重置游戏状态和初始数据 ////// -core.prototype.resetStatus = function(hero, hard, floorId, maps) { +core.prototype.resetStatus = function(hero, hard, floorId, route, maps) { // 停止各个Timeout和Interval for (var i in core.interval) { @@ -460,8 +532,11 @@ core.prototype.resetStatus = function(hero, hard, floorId, maps) { // 初始化人物属性 core.status.hero = core.clone(hero); core.status.hard = hard; + // 初始化路线 + if (core.isset(route)) + core.status.route = route; // 保存的Index - core.status.saveIndex = core.getLocalStorage('saveIndex', 1); + core.status.saveIndex = core.getLocalStorage('saveIndex2', 1); core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); } @@ -470,12 +545,30 @@ core.prototype.resetStatus = function(hero, hard, floorId, maps) { core.prototype.startGame = function (hard, callback) { console.log('开始游戏'); - core.resetStatus(core.firstData.hero, hard, core.firstData.floorId, core.initStatus.maps); + core.resetStatus(core.firstData.hero, hard, core.firstData.floorId, null, core.initStatus.maps); core.changeFloor(core.status.floorId, null, core.firstData.hero.loc, null, function() { core.setHeroMoveTriggerInterval(); if (core.isset(callback)) callback(); }); + + + setTimeout(function () { + // Upload + var formData = new FormData(); + formData.append('type', 'people'); + formData.append('name', core.firstData.name); + formData.append('version', core.firstData.version); + formData.append('platform', core.platform.isPC?"PC":core.platform.isAndroid?"Android":core.platform.isIOS?"iOS":""); + formData.append('hard', hard); + formData.append('hardCode', core.getFlag('hard', 0)); + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/games/upload.php"); + xhr.send(formData); + + }) + } ////// 重新开始游戏;此函数将回到标题页面 ////// @@ -492,6 +585,7 @@ core.prototype.restart = function() { ////// 按下某个键时 ////// core.prototype.onkeyDown = function(e) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; if (!core.isset(core.status.holdingKeys))core.status.holdingKeys=[]; var isArrow={37:true,38:true,39:true,40:true}[e.keyCode] if(isArrow && !core.status.lockControl){ @@ -509,6 +603,7 @@ core.prototype.onkeyDown = function(e) { ////// 放开某个键时 ////// core.prototype.onkeyUp = function(e) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; var isArrow={37:true,38:true,39:true,40:true}[e.keyCode] if(isArrow && !core.status.lockControl){ for(var ii =0;ii0){ var stepPostfix = []; @@ -842,8 +972,16 @@ core.prototype.onup = function () { core.status.stepPostfix=[]; core.canvas.ui.clearRect(0, 0, 416,416); core.canvas.ui.restore(); - core.onclick(posx,posy,stepPostfix); - //posx,posy是寻路的目标点,stepPostfix是后续的移动 + + // 长按 + if (!core.status.lockControl && stepPostfix.length==0 && core.status.downTime!=null && new Date()-core.status.downTime>=1000) { + core.events.longClick(); + } + else { + //posx,posy是寻路的目标点,stepPostfix是后续的移动 + core.onclick(posx,posy,stepPostfix); + } + core.status.downTime=null; } } @@ -874,6 +1012,7 @@ core.prototype.getClickLoc = function (x, y) { ////// 具体点击屏幕上(x,y)点时,执行的操作 ////// core.prototype.onclick = function (x, y, stepPostfix) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; // console.log("Click: (" + x + "," + y + ")"); // 非游戏屏幕内 @@ -921,6 +1060,12 @@ core.prototype.onclick = function (x, y, stepPostfix) { return; } + // 查看地图 + if (core.status.event.id == 'viewMaps') { + core.events.clickViewMaps(x,y); + return; + } + // 开关 if (core.status.event.id == 'switchs') { core.events.clickSwitchs(x,y); @@ -963,6 +1108,11 @@ core.prototype.onclick = function (x, y, stepPostfix) { return; } + if (core.status.event.id == 'keyBoard') { + core.events.clickKeyBoard(x,y); + return; + } + // 关于 if (core.status.event.id == 'about') { core.events.clickAbout(x,y); @@ -990,6 +1140,7 @@ core.prototype.onclick = function (x, y, stepPostfix) { ////// 滑动鼠标滚轮时的操作 ////// core.prototype.onmousewheel = function (direct) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; // 向下滚动是 -1 ,向上是 1 // 楼层飞行器 @@ -1001,15 +1152,22 @@ core.prototype.onmousewheel = function (direct) { // 怪物手册 if (core.status.lockControl && core.status.event.id == 'book') { - if (direct==1) core.ui.drawBook(core.status.event.data - 1); - if (direct==-1) core.ui.drawBook(core.status.event.data + 1); + if (direct==1) core.ui.drawBook(core.status.event.data - 6); + if (direct==-1) core.ui.drawBook(core.status.event.data + 6); return; } // 存读档 if (core.status.lockControl && (core.status.event.id == 'save' || core.status.event.id == 'load')) { - if (direct==1) core.ui.drawSLPanel(core.status.event.data - 6); - if (direct==-1) core.ui.drawSLPanel(core.status.event.data + 6); + if (direct==1) core.ui.drawSLPanel(core.status.event.data - 10); + if (direct==-1) core.ui.drawSLPanel(core.status.event.data + 10); + return; + } + + // 浏览地图 + if (core.status.lockControl && core.status.event.id == 'viewMaps') { + if (direct==1) core.ui.drawMaps(core.status.event.data+1); + if (direct==-1) core.ui.drawMaps(core.status.event.data-1); return; } } @@ -1313,7 +1471,7 @@ core.prototype.stopAutoHeroMove = function () { } ////// 设置勇士的自动行走路线 ////// -core.prototype.setAutoHeroMove = function (steps, start) { +core.prototype.setAutoHeroMove = function (steps) { if (steps.length == 0) { return; } @@ -1366,6 +1524,8 @@ core.prototype.setHeroMoveInterval = function (direction, x, y, callback) { core.moveOneStep(); if (core.status.heroStop) core.drawHero(direction, core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); + clearInterval(core.interval.heroMoveInterval); + core.status.heroMoving = false; if (core.isset(callback)) callback(); } }, 12.5); @@ -1373,73 +1533,80 @@ core.prototype.setHeroMoveInterval = function (direction, x, y, callback) { ////// 设置勇士行走过程中对事件的触发检测 ////// core.prototype.setHeroMoveTriggerInterval = function () { - var direction, x, y; + core.interval.heroMoveTriggerInterval = window.setInterval(function () { + if (!core.status.heroStop) { + core.moveAction(); + } + }, 50); +} + +////// 实际每一步的行走过程 ////// +core.prototype.moveAction = function (callback) { + if (core.interval.openDoorAnimate!=null) return; // 开门判断 var scan = { 'up': {'x': 0, 'y': -1}, 'left': {'x': -1, 'y': 0}, 'down': {'x': 0, 'y': 1}, 'right': {'x': 1, 'y': 0} }; - core.interval.heroMoveTriggerInterval = window.setInterval(function () { - if (!core.status.heroStop) { - direction = core.getHeroLoc('direction'); - x = core.getHeroLoc('x'); - y = core.getHeroLoc('y'); - var noPass = core.noPass(x + scan[direction].x, y + scan[direction].y), canMove = core.canMoveHero(); - if (noPass || !canMove) { - if (canMove) // 非箭头:触发 - core.trigger(x + scan[direction].x, y + scan[direction].y); - core.drawHero(direction, x, y, 'stop'); - if (core.status.autoHeroMove) { - core.status.movedStep++; - if (core.status.destStep == core.status.movedStep) { - core.status.autoHeroMove = false; - core.status.destStep = 0; - core.status.movedStep = 0; - core.status.moveStepBeforeStop=[]; - core.stopAutomaticRoute(); - } - } - else { - core.status.heroStop = true; - } - return; + var direction = core.getHeroLoc('direction'); + var x = core.getHeroLoc('x'); + var y = core.getHeroLoc('y'); + var noPass = core.noPass(x + scan[direction].x, y + scan[direction].y), canMove = core.canMoveHero(); + if (noPass || !canMove) { + core.status.route.push(direction); + if (canMove) // 非箭头:触发 + core.trigger(x + scan[direction].x, y + scan[direction].y); + core.drawHero(direction, x, y, 'stop'); + if (core.status.autoHeroMove) { + core.status.movedStep++; + if (core.status.destStep == core.status.movedStep) { + core.status.autoHeroMove = false; + core.status.destStep = 0; + core.status.movedStep = 0; + core.status.moveStepBeforeStop=[]; + core.stopAutomaticRoute(); } - core.setHeroMoveInterval(direction, x, y, function () { - if (core.status.autoHeroMove) { - core.status.movedStep++; - if (core.status.destStep == core.status.movedStep) { - core.status.autoHeroMove = false; - core.status.destStep = 0; - core.status.movedStep = 0; - core.stopHero(); - core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); - } - } - else if (core.status.heroStop) { + } + else { + core.status.heroStop = true; + } + if (core.isset(callback)) + callback(); + } + else { + core.setHeroMoveInterval(direction, x, y, function () { + if (core.status.autoHeroMove) { + core.status.movedStep++; + if (core.status.destStep == core.status.movedStep) { + core.status.autoHeroMove = false; + core.status.destStep = 0; + core.status.movedStep = 0; + core.stopHero(); core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); } - core.trigger(core.getHeroLoc('x'), core.getHeroLoc('y')); - clearInterval(core.interval.heroMoveInterval); - core.status.heroMoving = false; - core.checkBlock(); - }); - } - }, 50); + } + else if (core.status.heroStop) { + core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); + } + core.status.route.push(direction); + core.trigger(core.getHeroLoc('x'), core.getHeroLoc('y')); + core.checkBlock(); + if (core.isset(callback)) callback(); + }); + } } -////// 设置勇士的方向(转向) ////// -core.prototype.turnHero = function(direction) { - if (core.isset(direction)) { - core.status.hero.loc.direction = direction; - } - else if (core.status.hero.loc.direction == 'up') core.status.hero.loc.direction = 'right'; +////// 转向 ////// +core.prototype.turnHero = function() { + if (core.status.hero.loc.direction == 'up') core.status.hero.loc.direction = 'right'; else if (core.status.hero.loc.direction == 'right') core.status.hero.loc.direction = 'down'; else if (core.status.hero.loc.direction == 'down') core.status.hero.loc.direction = 'left'; else if (core.status.hero.loc.direction == 'left') core.status.hero.loc.direction = 'up'; core.drawHero(core.status.hero.loc.direction, core.status.hero.loc.x, core.status.hero.loc.y, 'stop', 0, 0); core.status.automaticRoutingTemp = {'destX': 0, 'destY': 0, 'moveStep': []}; core.canvas.ui.clearRect(0, 0, 416, 416); + core.status.route.push("turn"); } ////// 勇士能否前往某方向 ////// @@ -1452,8 +1619,6 @@ core.prototype.canMoveHero = function() { if(nowIsArrow){ var nowArrow = nowId.slice(5).toLowerCase(); if (direction != nowArrow) { - // core.status.heroStop = true; - // core.turnHero(direction); return false; } } @@ -1472,8 +1637,6 @@ core.prototype.canMoveHero = function() { if(isArrow){ var nextArrow = nextId.slice(5).toLowerCase(); if ( (scan[direction].x + scan[nextArrow].x) == 0 && (scan[direction].y + scan[nextArrow].y) == 0 ) { - // core.status.heroStop = true; - // core.turnHero(direction); return false; } } @@ -1482,9 +1645,17 @@ core.prototype.canMoveHero = function() { } ////// 让勇士开始移动 ////// -core.prototype.moveHero = function (direction) { - core.setHeroLoc('direction', direction); - core.status.heroStop = false; +core.prototype.moveHero = function (direction, callback) { + if (core.isset(direction)) + core.setHeroLoc('direction', direction); + if (!core.isset(callback)) { // 如果不存在回调函数,则使用heroMoveTrigger + core.status.heroStop = false; + } + else { // 否则,只向某个方向移动一步,然后调用callback + core.moveAction(function () { + callback(); + }) + } } /////// 使用事件让勇士移动。这个函数将不会触发任何事件 ////// @@ -1521,11 +1692,14 @@ core.prototype.eventMoveHero = function(steps, time, callback) { 'right': {'x': 1, 'y': 0} }; + core.status.replay.animate=true; + var animate=window.setInterval(function() { var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); if (moveSteps.length==0) { clearInterval(animate); core.drawHero(core.getHeroLoc('direction'), x, y, 'stop'); + core.status.replay.animate=false; if (core.isset(callback)) callback(); } else { @@ -1550,6 +1724,7 @@ core.prototype.eventMoveHero = function(steps, time, callback) { ////// 每移动一格后执行的事件 ////// core.prototype.moveOneStep = function() { + core.status.hero.steps++; // 中毒状态 if (core.hasFlag('poison')) { core.status.hero.hp -= core.values.poisonDamage; @@ -1568,8 +1743,10 @@ core.prototype.waitHeroToStop = function(callback) { core.stopAutomaticRoute(); core.clearContinueAutomaticRoute(); if (core.isset(callback)) { + core.status.replay.animate=true; core.lockControl(); setTimeout(function(){ + core.status.replay.animate=false; core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); callback(); }, 30); @@ -1665,7 +1842,6 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { core.drawTip("你没有" + core.material.items[key].name); else core.drawTip("无法开启此门"); core.clearContinueAutomaticRoute(); - if (core.isset(callback)) callback(); return; } } @@ -1678,12 +1854,14 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { speed=100; } var door = core.material.icons.animates[doorId]; + core.status.replay.animate=true; core.interval.openDoorAnimate = window.setInterval(function () { state++; if (state == 4) { clearInterval(core.interval.openDoorAnimate); core.interval.openDoorAnimate=null; core.removeBlock(x, y); + core.status.replay.animate=false; core.events.afterOpenDoor(id,x,y,callback); return; } @@ -1708,7 +1886,7 @@ core.prototype.battle = function (id, x, y, force, callback) { core.clearContinueAutomaticRoute(); return; } - if (core.flags.battleAnimate) { + if (core.flags.battleAnimate&&!core.status.replay.replaying) { core.waitHeroToStop(function() { core.ui.drawBattleAnimate(id, function() { core.afterBattle(id, x, y, callback); @@ -1746,7 +1924,7 @@ core.prototype.afterBattle = function(id, x, y, callback) { if (core.flags.enableMoney) hint += ",金币+" + money; if (core.flags.enableExperience) - hint += ",经验+" + core.material.enemys[id].experience; + hint += ",经验+" + experience; core.drawTip(hint); // 打完怪物,触发事件 @@ -1767,12 +1945,14 @@ core.prototype.trigger = function (x, y) { if (core.isset(mapBlocks[b].event) && core.isset(mapBlocks[b].event.trigger)) { var trigger = mapBlocks[b].event.trigger; // 转换楼层能否穿透 + /* if (trigger=='changeFloor' && (core.status.autoHeroMove || core.status.autoStep1) - core.status.checkBlock.damage[13*x+y] += parseInt((leftHp+1)/2); + core.status.checkBlock.damage[13*x+y] += parseInt((leftHp+(core.flags.betweenAttackCeil?0:1))/2); } } } @@ -2665,107 +2872,119 @@ core.prototype.checkBlock = function () { ////// 阻击事件(动画效果) ////// core.prototype.snipe = function (snipes) { - core.waitHeroToStop(function() { - core.lockControl(); - var scan = { - 'up': {'x': 0, 'y': -1}, - 'left': {'x': -1, 'y': 0}, - 'down': {'x': 0, 'y': 1}, - 'right': {'x': 1, 'y': 0} - }; + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; - snipes.forEach(function (snipe) { - var x=snipe.x, y=snipe.y, direction = snipe.direction; - snipe.nx = x+scan[snipe.direction].x; - snipe.ny = y+scan[snipe.direction].y; + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; + snipe.nx = x+scan[snipe.direction].x; + snipe.ny = y+scan[snipe.direction].y; - core.removeGlobalAnimate(x, y); + core.removeGlobalAnimate(x, y); - var block = core.getBlock(x,y).block; + var block = core.getBlock(x,y).block; - snipe.blockIcon = core.material.icons[block.event.cls][block.event.id]; - snipe.blockImage = core.material.images[block.event.cls]; - var damage = core.enemys.getDamage(block.event.id); + snipe.blockIcon = core.material.icons[block.event.cls][block.event.id]; + snipe.blockImage = core.material.images[block.event.cls]; + var damage = core.enemys.getDamage(block.event.id); - var color = "#000000"; - if (damage <= 0) color = '#00FF00'; - else if (damage < core.status.hero.hp / 3) color = '#FFFFFF'; - else if (damage < core.status.hero.hp * 2 / 3) color = '#FFFF00'; - else if (damage < core.status.hero.hp) color = '#FF7F00'; - else color = '#FF0000'; + var color = "#000000"; + if (damage <= 0) color = '#00FF00'; + else if (damage < core.status.hero.hp / 3) color = '#FFFFFF'; + else if (damage < core.status.hero.hp * 2 / 3) color = '#FFFF00'; + else if (damage < core.status.hero.hp) color = '#FF7F00'; + else color = '#FF0000'; - if (damage >= 999999999) damage = "???"; - else if (damage > 100000) damage = (damage / 10000).toFixed(1) + "w"; + if (damage >= 999999999) damage = "???"; + else if (damage > 100000) damage = (damage / 10000).toFixed(1) + "w"; - snipe.damage = damage; - snipe.color = color; - snipe.block = core.clone(block); - }) + snipe.damage = damage; + snipe.color = color; + snipe.block = core.clone(block); + }) - var time = 500, step = 0; + var finishSnipe = function () { + snipes.forEach(function (t) { + core.removeBlock(t.x, t.y); + var nBlock = core.clone(t.block); + nBlock.x = t.nx; nBlock.y = t.ny; + core.status.thisMap.blocks.push(nBlock); + core.addGlobalAnimate(2, 32*t.nx, 32*t.ny, t.blockIcon, t.blockImage); + core.canvas.event.drawImage(t.blockImage, 0, t.blockIcon*32, 32, 32, 32*t.nx, 32*t.ny, 32, 32); + }); + core.syncGlobalAnimate(); + core.updateStatusBar(); + return; + } - var animateValue = 2; - var animateCurrent = 0; - var animateTime = 0; + if (core.status.replay.replaying) { + finishSnipe(); + } + else { + core.waitHeroToStop(function() { - core.canvas.fg.textAlign = 'left'; + core.lockControl(); - var animate=window.setInterval(function() { + var time = 500, step = 0; - step++; - animateTime += time / 16; - if (animateTime >= core.values.animateSpeed * 2 / animateValue) { - animateCurrent++; - animateTime = 0; - if (animateCurrent>=animateValue) animateCurrent=0; - } + var animateValue = 2; + var animateCurrent = 0; + var animateTime = 0; - snipes.forEach(function (snipe) { - var x=snipe.x, y=snipe.y, direction = snipe.direction; + core.canvas.fg.textAlign = 'left'; - var nowX=32*x+scan[direction].x*2*step, nowY=32*y+scan[direction].y*2*step; + var animate=window.setInterval(function() { - // 清空上一次 - core.clearMap('event', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); - core.clearMap('fg', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); - - core.canvas.event.drawImage(snipe.blockImage, animateCurrent*32, snipe.blockIcon*32, 32, 32, nowX, nowY, 32, 32); - - if (core.hasItem('book')) { - // drawFG - core.setFillStyle('fg', '#000000'); - core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 30); - core.canvas.fg.fillText(snipe.damage, nowX, nowY + 30); - core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 32); - core.canvas.fg.fillText(snipe.damage, nowX, nowY + 32); - - core.setFillStyle('fg', snipe.color); - core.canvas.fg.fillText(snipe.damage, nowX + 1, nowY + 31); + step++; + animateTime += time / 16; + if (animateTime >= core.values.animateSpeed * 2 / animateValue) { + animateCurrent++; + animateTime = 0; + if (animateCurrent>=animateValue) animateCurrent=0; } - }) + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; - if (step==16) { // 移动完毕 - clearInterval(animate); - snipes.forEach(function (t) { - core.removeBlock(t.x, t.y); - var nBlock = core.clone(t.block); - nBlock.x = t.nx; nBlock.y = t.ny; - core.status.thisMap.blocks.push(nBlock); - core.addGlobalAnimate(animateValue, 32*t.nx, 32*t.ny, t.blockIcon, t.blockImage); - }); - core.syncGlobalAnimate(); - core.updateStatusBar(); - // 不存在自定义事件 - if (core.status.event.id==null) - core.unLockControl(); - } - }, time/16); + var nowX=32*x+scan[direction].x*2*step, nowY=32*y+scan[direction].y*2*step; + + // 清空上一次 + core.clearMap('event', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + core.clearMap('fg', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + + core.canvas.event.drawImage(snipe.blockImage, animateCurrent*32, snipe.blockIcon*32, 32, 32, nowX, nowY, 32, 32); + + if (core.hasItem('book')) { + // drawFG + core.setFillStyle('fg', '#000000'); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 32); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 32); + + core.setFillStyle('fg', snipe.color); + core.canvas.fg.fillText(snipe.damage, nowX + 1, nowY + 31); + } + + }) + + if (step==16) { // 移动完毕 + clearInterval(animate); + finishSnipe(); + // 不存在自定义事件 + if (core.status.event.id==null) + core.unLockControl(); + } + }, time/16); + }); + } - - }); } ////// 更改画面色调 ////// @@ -2799,6 +3018,7 @@ core.prototype.setFg = function(color, time, callback) { } var step=0; + core.status.replay.animate=true; var changeAnimate = setInterval(function() { step++; @@ -2817,6 +3037,7 @@ core.prototype.setFg = function(color, time, callback) { if (step>=25) { clearInterval(changeAnimate); core.status.curtainColor = color; + core.status.replay.animate=false; if (core.isset(callback)) callback(); } }, time/25); @@ -2936,8 +3157,8 @@ core.prototype.removeItem = function (itemId) { } ////// 使用某个物品 ////// -core.prototype.useItem = function (itemId) { - core.items.useItem(itemId); +core.prototype.useItem = function (itemId, callback) { + core.items.useItem(itemId, callback); return; } @@ -2969,6 +3190,7 @@ core.prototype.getNextItem = function() { if (block==null) return; if (block.block.event.trigger=='getItem') { core.getItem(block.block.event.id, 1, nextX, nextY); + core.status.route.push("getNext"); } } @@ -3053,6 +3275,13 @@ core.prototype.drawTip = function (text, itemIcon) { ////// 地图中间绘制一段文字 ////// core.prototype.drawText = function (contents, callback) { if (core.isset(contents)) { + + // 合并 + if (core.isset(core.status.event)&&core.status.event.id=='action') { + core.insertAction(contents,null,null,callback); + return; + } + if (typeof contents == 'string') { contents = [{'content': contents}]; } @@ -3232,6 +3461,13 @@ core.prototype.formatDate = function(date) { +core.setTwoDigits(date.getHours())+":"+core.setTwoDigits(date.getMinutes())+":"+core.setTwoDigits(date.getSeconds()); } +////// 格式化时间为最简字符串 ////// +core.prototype.formatDate2 = function (date) { + if (!core.isset(date)) return ""; + return date.getFullYear()+core.setTwoDigits(date.getMonth()+1)+core.setTwoDigits(date.getDate()) + +core.setTwoDigits(date.getHours())+core.setTwoDigits(date.getMinutes())+core.setTwoDigits(date.getSeconds()); +} + ////// 两位数显示 ////// core.prototype.setTwoDigits = function (x) { return parseInt(x)<10?"0"+x:x; @@ -3257,6 +3493,121 @@ core.prototype.debug = function() { core.drawTip("作弊成功"); } +////// 回放 ////// +core.prototype.replay = function (list) { + + if (core.isset(list) && (list instanceof Array)) { + core.status.replay.replaying=true; + core.status.replay.toReplay = core.clone(list); + this.replay(); + return; + } + + if (!core.status.replay.replaying) return; // 没有回放 + if (core.status.replay.pausing) return; // 暂停状态 + if (core.status.replay.animate) return; // 正在某段动画中 + + if (core.status.replay.toReplay.length==0) { // 回放完毕 + core.status.replay.replaying=false; + core.insertAction("录像回放完毕!"); + return; + } + + var action=core.status.replay.toReplay.shift(); + + if (action=='up' || action=='down' || action=='left' || action=='right') { + core.moveHero(action, function () { + core.replay(); + }); + return; + } + else if (action.indexOf("item:")==0) { + var itemId = action.substring(5); + if (core.canUseItem(itemId)) { + var tools = Object.keys(core.status.hero.items.tools).sort(); + var constants = Object.keys(core.status.hero.items.constants).sort(); + var index; + if ((index=tools.indexOf(itemId))>=0 || (index=constants.indexOf(itemId)+100)>=100) { + core.ui.drawToolbox(index); + setTimeout(function () { + core.ui.closePanel(); + core.useItem(itemId, function () { + core.replay(); + }); + }, 500); + } + return; + } + } + else if (action.indexOf("fly:")==0) { + var floorId=action.substring(4); + var toIndex=core.status.hero.flyRange.indexOf(floorId); + var nowIndex=core.status.hero.flyRange.indexOf(core.status.floorId); + if (core.hasItem('fly') && toIndex>=0 && nowIndex>=0) { + core.ui.drawFly(toIndex); + setTimeout(function () { + core.ui.closePanel(); + var stair=toIndex0) { + var shop=core.status.shops[shopId]; + if (core.isset(shop) && shop.visited) { // 商店可用 + var choices = shop.choices; + var topIndex = 6 - parseInt(choices.length / 2); + core.events.openShop(shopId, false); + var shopInterval = setInterval(function () { + if (selections.length==0) { + clearInterval(shopInterval); + core.events.clickShop(6, topIndex+choices.length); + core.replay(); + return; + } + var selection = parseInt(selections.shift()); + if (isNaN(selection) || selection<0 || selection>=choices.length || !core.events.clickShop(6, topIndex+selection)) { + clearInterval(shopInterval); + core.status.replay.replaying=false; + core.drawTip("录像文件出错"); + return; + } + }, 500); + return; + } + } + } + else if (action=='turn') { + core.turnHero(); + core.replay(); + return; + } + else if (action=='getNext') { + if (core.flags.enableGentleClick && core.getBlock(core.nextX(), core.nextY())!=null) { + var nextX = core.nextX(), nextY = core.nextY(); + var block = core.getBlock(nextX, nextY); + if (block!=null && block.block.event.trigger=='getItem') { + core.getItem(block.block.event.id, 1, nextX, nextY); + core.status.route.push("getNext"); + core.replay(); + return; + } + } + } + + core.status.replay.replaying=false; + core.insertAction("录像文件出错"); + +} + ////// 判断当前能否进入某个事件 ////// core.prototype.checkStatus = function (name, need, item) { if (need && core.status.event.id == name) { @@ -3282,6 +3633,7 @@ core.prototype.checkStatus = function (name, need, item) { ////// 点击怪物手册时的打开操作 ////// core.prototype.openBook = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; if (!core.checkStatus('book', need, true)) return; core.useItem('book'); @@ -3289,6 +3641,7 @@ core.prototype.openBook = function (need) { ////// 点击楼层传送器时的打开操作 ////// core.prototype.useFly = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; if (!core.checkStatus('fly', need, true)) return; if (core.flags.flyNearStair && !core.nearStair()) { @@ -3311,43 +3664,85 @@ core.prototype.useFly = function (need) { ////// 点击工具栏时的打开操作 ////// core.prototype.openToolbox = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; if (!core.checkStatus('toolbox', need)) return; core.ui.drawToolbox(); } +////// 点击快捷商店按钮时的打开操作 ////// +core.prototype.openQuickShop = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; + if (!core.checkStatus('selectShop', need)) + return; + core.ui.drawQuickShop(); +} + ////// 点击保存按钮时的打开操作 ////// core.prototype.save = function(need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; if (!core.checkStatus('save', need)) return; - core.ui.drawSLPanel(core.status.saveIndex); + + var saveIndex = core.status.saveIndex; + var page=parseInt((saveIndex-1)/5), offset=saveIndex-5*page; + + core.ui.drawSLPanel(10*page+offset); } ////// 点击读取按钮时的打开操作 ////// core.prototype.load = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; + + var saveIndex = core.getLocalStorage('saveIndex2', 1); + var page=parseInt((saveIndex-1)/5), offset=saveIndex-5*page; // 游戏开始前读档 if (!core.isPlaying()) { core.status.event = {'id': 'load', 'data': null}; core.status.lockControl = true; core.dom.startPanel.style.display = 'none'; - core.ui.drawSLPanel(core.getLocalStorage('saveIndex', 1)); + core.ui.drawSLPanel(10*page+offset); return; } if (!core.checkStatus('load', need)) return; - core.ui.drawSLPanel(core.status.saveIndex); + core.ui.drawSLPanel(10*page+offset); +} + +////// 点击设置按钮时的操作 ////// +core.prototype.openSettings = function (need) { + if (core.isset(core.status.replay)&&core.status.replay.replaying) return; + if (!core.checkStatus('settings', need)) + return; + core.ui.drawSettings(); +} + +////// 自动存档 ////// +core.prototype.autosave = function (removeLast) { + var x; + if (removeLast) + x=core.status.route.pop(); + core.saveData("autoSave"); + if (removeLast) + core.status.route.push(x); } ////// 实际进行存读档事件 ////// core.prototype.doSL = function (id, type) { - core.status.saveIndex=id; if (type=='save') { + if (id=='autoSave') { + core.drawTip('不能覆盖自动存档!'); + return; + } if (core.saveData("save"+id)) { core.ui.closePanel(); core.drawTip('存档成功!'); - core.setLocalStorage('saveIndex', core.status.saveIndex); + if (id!="autoSave") { + core.status.saveIndex=id; + core.setLocalStorage('saveIndex2', core.status.saveIndex); + } } else { core.drawTip('存储空间不足,请覆盖已有的存档或在菜单栏中进行清理'); @@ -3355,7 +3750,7 @@ core.prototype.doSL = function (id, type) { return; } else if (type=='load') { - var data = core.getLocalStorage("save"+id, null); + var data = core.getLocalStorage(id=='autoSave'?id:"save"+id, null); if (!core.isset(data)) { core.drawTip("无效的存档"); return; @@ -3366,9 +3761,11 @@ core.prototype.doSL = function (id, type) { } core.ui.closePanel(); core.loadData(data, function() { - core.status.saveIndex=id; - core.setLocalStorage('saveIndex', core.status.saveIndex); core.drawTip("读档成功"); + if (id!="autoSave") { + core.status.saveIndex=id; + core.setLocalStorage('saveIndex2', core.status.saveIndex); + } }); return; } @@ -3386,7 +3783,7 @@ core.prototype.syncSave = function(type) { formData.append('type', 'save'); formData.append('name', core.firstData.name); var saves = []; - for (var i=1;i<=180;i++) { + for (var i=1;i<=150;i++) { var data = core.getLocalStorage("save"+i, null); if (core.isset(data)) { saves.push(data); @@ -3413,13 +3810,11 @@ core.prototype.syncSave = function(type) { core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:HTTP "+xhr.status); } }; - xhr.ontimeout = function(e) { - console.log(e); - core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+e); + xhr.ontimeout = function() { + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:Timeout"); } - xhr.onerror = function(e) { - console.log(e); - core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+e); + xhr.onerror = function() { + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:XHR Error"); } xhr.send(formData); }, function() { @@ -3458,7 +3853,7 @@ core.prototype.syncSave = function(type) { // 成功 var data=JSON.parse(response.msg); // console.log(data); - for (var i=1;i<=180;i++) { + for (var i=1;i<=150;i++) { if (i<=data.length) { core.setLocalStorage("save"+i, data[i-1]); } @@ -3483,11 +3878,11 @@ core.prototype.syncSave = function(type) { core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:HTTP "+xhr.status); } }; - xhr.ontimeout = function(e) { - core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:"+e); + xhr.ontimeout = function() { + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:Timeout"); } - xhr.onerror = function(e) { - core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:"+e); + xhr.onerror = function() { + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:XHR Error"); } xhr.send(formData); }, function() { @@ -3505,6 +3900,7 @@ core.prototype.saveData = function(dataId) { 'hero': core.clone(core.status.hero), 'hard': core.status.hard, 'maps': core.maps.save(core.status.maps), + 'route': core.encodeRoute(core.status.route), 'shops': {}, 'version': core.firstData.version, "time": new Date().getTime() @@ -3524,7 +3920,7 @@ core.prototype.saveData = function(dataId) { ////// 从本地读档 ////// core.prototype.loadData = function (data, callback) { - core.resetStatus(data.hero, data.hard, data.floorId, core.maps.load(data.maps)); + core.resetStatus(data.hero, data.hard, data.floorId, core.decodeRoute(data.route), core.maps.load(data.maps)); // load shop times for (var shop in core.status.shops) { @@ -3534,12 +3930,99 @@ core.prototype.loadData = function (data, callback) { core.events.afterLoadData(data); - core.changeFloor(data.floorId, null, data.hero.loc, null, function() { + core.changeFloor(data.floorId, null, data.hero.loc, 0, function() { core.setHeroMoveTriggerInterval(); if (core.isset(callback)) callback(); }); } +////// 加密路线 ////// +core.prototype.encodeRoute = function (route) { + var ans=""; + var lastMove = "", cnt=0; + + var items=Object.keys(core.material.items).sort(); + var shops=Object.keys(core.initStatus.shops).sort(); + route.forEach(function (t) { + if (t=='up' || t=='down' || t=='left' || t=='right') { + if (t!=lastMove && cnt>0) { + ans+=lastMove.substring(0,1).toUpperCase(); + if (cnt>1) ans+=cnt; + cnt=0; + } + lastMove=t; + cnt++; + } + else { + if (cnt>0) { + ans+=lastMove.substring(0,1).toUpperCase(); + if (cnt>1) ans+=cnt; + cnt=0; + } + if (t.indexOf('item:')==0) + ans+="I"+items.indexOf(t.substring(5)); + else if (t.indexOf('fly:')==0) + ans+="F"+core.floorIds.indexOf(t.substring(4)); + else if (t.indexOf('choices:')==0) + ans+="C"+t.substring(8); + else if (t.indexOf('shop:')==0) { + var sp=t.substring(5).split(":"); + ans+="S"+shops.indexOf(sp[0])+":"+sp[1]; + } + else if (t=='turn') + ans+='T'; + else if (t=='getNext') + ans+='G'; + else if (t.indexOf('input:')==0) + ans+="P"+t.substring(6); + } + }); + if (cnt>0) { + ans+=lastMove.substring(0,1).toUpperCase(); + if (cnt>1) ans+=cnt; + } + return ans; +} + +////// 解密路线 ////// +core.prototype.decodeRoute = function (route) { + + if (!core.isset(route)) return route; + + var ans=[], index=0; + + var getNumber = function (noparse) { + var num=""; + while (index=0:(special!=0&&(special%100==test||this.hasSpecial(parseInt(special/100), test))); + + if (special instanceof Array) { + return special.indexOf(test)>=0; + } + + if (typeof special == 'number') { + return special!=0 && (special%100==test||this.hasSpecial(parseInt(special/100), test)); + } + + return false; } ////// 获得所有特殊属性的名称 ////// @@ -108,6 +117,7 @@ enemys.prototype.getSpecialText = function (enemyId) { if (this.hasSpecial(special, 18)) text.push("阻击"); if (this.hasSpecial(special, 19)) text.push("自爆"); if (this.hasSpecial(special, 20)) text.push("无敌"); + if (this.hasSpecial(special, 21)) text.push("退化"); return text; } @@ -136,16 +146,17 @@ enemys.prototype.getSpecialHint = function (enemy, special) { case 8: return "反击:战斗时,怪物每回合附加角色攻击的"+parseInt(100*core.values.counterAttack)+"%作为伤害,无视角色防御"; case 9: return "净化:战斗前,怪物附加勇士魔防的"+core.values.purify+"倍作为伤害"; case 10: return "模仿:怪物的攻防和勇士攻防相等"; - case 11: return "吸血:战斗前,怪物首先吸取角色的"+parseInt(100*enemy.value)+"%生命作为伤害"; + case 11: return "吸血:战斗前,怪物首先吸取角色的"+parseInt(100*enemy.value)+"%生命作为伤害"+(enemy.add?",并把伤害数值加到自身生命上":""); case 12: return "中毒:战斗后,勇士陷入中毒状态,每一步损失生命"+core.values.poisonDamage+"点"; case 13: return "衰弱:战斗后,勇士陷入衰弱状态,攻防暂时下降"+core.values.weakValue+"点"; case 14: return "诅咒:战斗后,勇士陷入诅咒状态,战斗无法获得金币和经验"; case 15: return "领域:经过怪物周围"+(enemy.range||1)+"格时自动减生命"+enemy.value+"点"; case 16: return "夹击:经过两只相同的怪物中间,勇士生命值变成一半"; - case 17: return "仇恨:战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得"+core.values.hatred+"点仇恨值)"; + case 17: return "仇恨:战斗前,怪物附加之前积累的仇恨值作为伤害"+(core.flags.hatredDecrease?";战斗后,释放一半的仇恨值":"")+"。(每杀死一个怪物获得"+core.values.hatred+"点仇恨值)"; case 18: return "阻击:经过怪物的十字领域时自动减生命"+enemy.value+"点,同时怪物后退一格"; case 19: return "自爆:战斗后勇士的生命值变成1"; case 20: return "无敌:勇士无法打败怪物,除非拥有十字架"; + case 21: return "退化:战斗后勇士永久下降"+(enemy.atkValue||0)+"点攻击和"+(enemy.defValue||0)+"点防御"; default: break; } return "" @@ -154,21 +165,14 @@ enemys.prototype.getSpecialHint = function (enemy, special) { ////// 获得某个怪物的伤害 ////// enemys.prototype.getDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; - var hero_atk = core.status.hero.atk, hero_def = core.status.hero.def, hero_mdef = core.status.hero.mdef; - var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def, mon_special = monster.special; - var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, monster.n); - if (damage == 999999999) return damage; + var damage = this.calDamage(monster, core.status.hero.hp, core.status.hero.atk, core.status.hero.def, core.status.hero.mdef); + if (damage >= 999999999) return damage; return damage + this.getExtraDamage(monster); } ////// 获得某个怪物的额外伤害 ////// enemys.prototype.getExtraDamage = function (monster) { var extra_damage = 0; - if (this.hasSpecial(monster.special, 11)) { // 吸血 - // 吸血的比例 - extra_damage = core.status.hero.hp * monster.value; - extra_damage = parseInt(extra_damage); - } if (this.hasSpecial(monster.special, 17)) { // 仇恨 extra_damage += core.getFlag('hatred', 0); } @@ -178,14 +182,15 @@ enemys.prototype.getExtraDamage = function (monster) { ////// 临界值计算 ////// enemys.prototype.getCritical = function (monsterId) { var monster = core.material.enemys[monsterId]; + // 坚固、模仿怪物没有临界! if (this.hasSpecial(monster.special, 3) || this.hasSpecial(monster.special, 10)) return "???"; - var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); + + var last = this.calDamage(monster, core.status.hero.hp, core.status.hero.atk, core.status.hero.def, core.status.hero.mdef); + if (last <= 0) return 0; for (var i = core.status.hero.atk + 1; i <= monster.hp + monster.def; i++) { - var damage = this.calDamage(i, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); + var damage = this.calDamage(monster, core.status.hero.hp, i, core.status.hero.def, core.status.hero.mdef); if (damage < last) return i - core.status.hero.atk; last = damage; @@ -199,31 +204,45 @@ enemys.prototype.getCriticalDamage = function (monsterId) { if (c == '???') return '???'; if (c <= 0) return 0; var monster = core.material.enemys[monsterId]; - var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); - if (last == 999999999) return '???'; + var last = this.calDamage(monster, core.status.hero.hp, core.status.hero.atk, core.status.hero.def, core.status.hero.mdef); + if (last >= 999999999) return '???'; - return last - this.calDamage(core.status.hero.atk + c, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); + return last - this.calDamage(monster, core.status.hero.hp, core.status.hero.atk + c, core.status.hero.def, core.status.hero.mdef); } ////// 1防减伤计算 ////// enemys.prototype.getDefDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; - var nowDamage = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); - var nextDamage = this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special, monster.n); - if (nowDamage == 999999999 || nextDamage == 999999999) return "???"; + var nowDamage = this.calDamage(monster, core.status.hero.hp, core.status.hero.atk, core.status.hero.def, core.status.hero.mdef); + var nextDamage = this.calDamage(monster, core.status.hero.hp, core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef); + if (nowDamage >= 999999999 || nextDamage >= 999999999) return "???"; return nowDamage - nextDamage; } ////// 具体的伤害计算公式 ////// -enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, n) { +enemys.prototype.calDamage = function (monster, hero_hp, hero_atk, hero_def, hero_mdef) { + + var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def, mon_special = monster.special; if (this.hasSpecial(mon_special, 20) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 return 999999999; // 返回无限大 + var initDamage = 0; // 战前伤害 + + // 吸血 + if (this.hasSpecial(mon_special, 11)) { + var vampireDamage = hero_hp * monster.value; + + // 如果有神圣盾免疫吸血等可以在这里写 + + vampireDamage = parseInt(vampireDamage); + // 加到自身 + if (monster.add) // 如果加到自身 + mon_hp += vampireDamage; + + initDamage += vampireDamage; + } + // 模仿 if (this.hasSpecial(mon_special,10)) { mon_atk = hero_atk; @@ -237,28 +256,36 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo var per_damage = mon_atk - hero_def; if (per_damage < 0) per_damage = 0; - // 2连击 & 3连击 + // 2连击 & 3连击 & N连击 if (this.hasSpecial(mon_special, 4)) per_damage *= 2; if (this.hasSpecial(mon_special, 5)) per_damage *= 3; - if (this.hasSpecial(mon_special, 6)) per_damage *= (n||4); + if (this.hasSpecial(mon_special, 6)) per_damage *= (monster.n||4); var counterDamage = 0; // 反击 if (this.hasSpecial(mon_special, 8)) counterDamage += parseInt(core.values.counterAttack * hero_atk); // 先攻 - var damage = mon_special == 1 ? per_damage : 0; + if (this.hasSpecial(mon_special, 1)) + initDamage += per_damage; + // 破甲 - if (this.hasSpecial(mon_special, 7)) damage += parseInt(core.values.breakArmor * hero_def); + if (this.hasSpecial(mon_special, 7)) + initDamage += parseInt(core.values.breakArmor * hero_def); + // 净化 - if (this.hasSpecial(mon_special, 9)) damage = core.values.purify * hero_mdef; + if (this.hasSpecial(mon_special, 9)) + initDamage += parseInt(core.values.purify * hero_mdef); var turn = parseInt((mon_hp - 1) / (hero_atk - mon_def)); - var ans = damage + turn * per_damage + (turn + 1) * counterDamage; + var ans = initDamage + turn * per_damage + (turn + 1) * counterDamage; ans -= hero_mdef; - return core.flags.enableNegativeDamage?ans:Math.max(0, ans); + if (!core.flags.enableNegativeDamage) + ans=Math.max(0, ans); + + return ans; } ////// 获得当前楼层的怪物列表 ////// @@ -272,7 +299,7 @@ enemys.prototype.getCurrentEnemys = function () { if (core.isset(used[monsterId])) continue; var monster = core.material.enemys[monsterId]; - var mon_atk = monster.atk, mon_def = monster.def; + var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def; // 坚固 if (this.hasSpecial(monster.special, 3) && mon_def < core.status.hero.atk - 1) mon_def = core.status.hero.atk - 1; @@ -288,11 +315,12 @@ enemys.prototype.getCurrentEnemys = function () { enemys.push({ 'id': monsterId, 'name': monster.name, - 'hp': monster.hp, + 'hp': mon_hp, 'atk': mon_atk, 'def': mon_def, 'money': monster.money, 'experience': monster.experience, + 'point': monster.point||0, // 加点 'special': specialText, 'damage': this.getDamage(monsterId), 'critical': this.getCritical(monsterId), diff --git a/libs/events.js b/libs/events.js index cf66d51e..a7074d55 100644 --- a/libs/events.js +++ b/libs/events.js @@ -6,6 +6,7 @@ function events() { events.prototype.init = function () { this.events = { 'battle': function (data, core, callback) { + core.autosave(true); core.battle(data.event.id, data.x, data.y); if (core.isset(callback)) callback(); @@ -16,9 +17,11 @@ events.prototype.init = function () { callback(); }, 'openDoor': function (data, core, callback) { - core.openDoor(data.event.id, data.x, data.y, true); - if (core.isset(callback)) - callback(); + core.autosave(true); + core.openDoor(data.event.id, data.x, data.y, true, function () { + if (core.isset(callback)) callback(); + core.replay(); + }); }, 'changeFloor': function (data, core, callback) { var heroLoc = {}; @@ -27,7 +30,10 @@ events.prototype.init = function () { if (core.isset(data.event.data.direction)) heroLoc.direction = data.event.data.direction; core.changeFloor(data.event.data.floorId, data.event.data.stair, - heroLoc, data.event.data.time, callback); + heroLoc, data.event.data.time, function () { + if (core.isset(callback)) callback(); + core.replay(); + }); }, 'passNet': function (data, core, callback) { core.events.passNet(data); @@ -107,44 +113,136 @@ events.prototype.setInitData = function (hard) { ////// 游戏获胜事件 ////// events.prototype.win = function(reason) { + core.ui.closePanel(); + var replaying = core.status.replay.replaying; + core.status.replay.replaying=false; core.waitHeroToStop(function() { core.removeGlobalAnimate(0,0,true); core.clearMap('all'); // 清空全地图 core.drawText([ - "\t[结局2]恭喜通关!你的分数是${status:hp}。" + "\t[恭喜通关]你的分数是${status:hp}。" ], function () { - core.restart(); + core.events.gameOver(true, replaying); }) }); } ////// 游戏失败事件 ////// events.prototype.lose = function(reason) { + core.ui.closePanel(); + var replaying = core.status.replay.replaying; + core.status.replay.replaying=false; core.waitHeroToStop(function() { + core.status.replay.replaying=false; core.drawText([ "\t[结局1]你死了。\n如题。" ], function () { - core.restart(); + core.events.gameOver(false, replaying); }); }) } +////// 游戏结束 ////// +events.prototype.gameOver = function (success, fromReplay) { + + // 上传成绩 + var confirmUpload = function () { + + if (!success) { + core.restart(); + return; + } + + var doUpload = function(username) { + if (username==null) username=""; + + // upload + var formData = new FormData(); + formData.append('type', 'score'); + formData.append('name', core.firstData.name); + formData.append('version', core.firstData.version); + formData.append('platform', core.platform.isPC?"PC":core.platform.isAndroid?"Android":core.platform.isIOS?"iOS":""); + formData.append('hard', core.status.hard); + formData.append('username', username); + formData.append('lv', core.status.hero.lv); + formData.append('hp', core.status.hero.hp); + formData.append('atk', core.status.hero.atk); + formData.append('def', core.status.hero.def); + formData.append('mdef', core.status.hero.mdef); + formData.append('money', core.status.hero.money); + formData.append('experience', core.status.hero.experience); + formData.append('steps', core.status.hero.steps); + formData.append('route', core.encodeRoute(core.status.route)); + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/games/upload.php"); + xhr.send(formData); + + core.restart(); + } + + core.ui.drawConfirmBox("你想记录你的ID和成绩吗?", function () { + doUpload(prompt("请输入你的ID:")); + }, function () { + doUpload(""); + }) + + return; + } + + // 下载录像 + var confirmDownload = function () { + core.ui.closePanel(); + core.ui.drawConfirmBox("你想下载录像吗?", function () { + var obj = { + 'name': core.firstData.name, + 'version': core.firstData.version, + 'hard': core.status.hard, + 'route': core.encodeRoute(core.status.route) + } + core.download(core.firstData.name+"_"+core.formatDate2(new Date())+".h5route", JSON.stringify(obj)); + confirmUpload(); + }, function () { + confirmUpload(); + }) + } + + if (fromReplay) { + core.drawText("录像回放完毕!", function () { + core.restart(); + }); + } + else { + confirmDownload(); + } + +} + ////// 转换楼层结束的事件 ////// events.prototype.afterChangeFloor = function (floorId) { - if (!core.isset(core.status.event.id) && !core.hasFlag("visited_"+floorId)) { - this.doEvents(core.floors[floorId].firstArrive); + if (core.isset(core.status.event.id)) return; // 当前存在事件 + + if (!core.hasFlag("visited_"+floorId)) { + this.doEvents(core.floors[floorId].firstArrive, null, null, function () { + core.autosave(); + }); core.setFlag("visited_"+floorId, true); + return; } + + // 自动存档 + core.autosave(); } ////// 开始执行一系列自定义事件 ////// events.prototype.doEvents = function (list, x, y, callback) { + if (!core.isset(list)) return; + if (!(list instanceof Array)) { + list = [list]; + } + // 停止勇士 core.waitHeroToStop(function() { - if (!core.isset(list)) return; - if (!(list instanceof Array)) { - list = [list]; - } core.lockControl(); core.status.event = {'id': 'action', 'data': { 'list': core.clone(list), 'x': x, 'y': y, 'callback': callback @@ -165,6 +263,7 @@ events.prototype.doAction = function() { if (core.isset(core.status.event.data.callback)) core.status.event.data.callback(); core.ui.closePanel(); + core.replay(); return; } @@ -178,13 +277,20 @@ events.prototype.doAction = function() { // 如果是文字:显示 if (typeof data == "string") { core.status.event.data.type='text'; - core.ui.drawTextBox(data); + // 如果是正在回放中,不显示 + if (core.status.replay.replaying) + core.events.doAction(); + else + core.ui.drawTextBox(data); return; } core.status.event.data.type=data.type; switch (data.type) { case "text": // 文字/对话 - core.ui.drawTextBox(data.data); + if (core.status.replay.isreplaying) + core.events.doAction(); + else + core.ui.drawTextBox(data.data); break; case "tip": core.drawTip(core.replaceText(data.text)); @@ -270,7 +376,12 @@ events.prototype.doAction = function() { this.doAction(); break; case "openShop": // 打开一个全局商店 - core.events.openShop(data.id); + if (core.status.replay.replaying) { // 正在播放录像,简单将visited置为true + core.status.shops[data.id].visited=true; + this.doAction(); + } + else + core.events.openShop(data.id); break; case "disableShop": // 禁用一个全局商店 core.events.disableQuickShop(data.id); @@ -351,13 +462,41 @@ events.prototype.doAction = function() { this.doAction(); break; case "choices": // 提供选项 + if (core.status.replay.replaying) { + if (core.status.replay.toReplay.length==0) { // 回放完毕 + core.status.replay.replaying=false; + core.drawTip("录像回放完毕"); + } + else { + var action = core.status.replay.toReplay.shift(), index; + if (action.indexOf("choices:")==0 && ((index=parseInt(action.substring(8)))>=0) && index= 5 && x <= 7) { var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y0) { if (keycode==38) { core.status.event.selection--; - if (core.status.event.selection<0) core.status.event.selection=0; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } if (keycode==40) { core.status.event.selection++; - if (core.status.event.selection>=choices.length) core.status.event.selection=choices.length-1; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -794,6 +967,7 @@ events.prototype.keyUpAction = function (keycode) { var choices = data.choices; if (choices.length>0) { if (keycode==13 || keycode==32 || keycode==67) { + core.status.route.push("choices:"+core.status.event.selection); this.insertAction(choices[core.status.event.selection].action); this.doAction(); } @@ -870,6 +1044,7 @@ events.prototype.clickFly = function(x,y) { var index=core.status.hero.flyRange.indexOf(core.status.floorId); var stair=core.status.event.data=8) { + core.ui.drawMaps(core.status.event.data-1); + } + else { + core.clearMap('data', 0, 0, 416, 416); + core.setOpacity('data', 1); + core.ui.closePanel(); + } +} + +////// 查看地图界面时,按下某个键的操作 ////// +events.prototype.keyDownViewMaps = function (keycode) { + if (keycode==37 || keycode==38) core.ui.drawMaps(core.status.event.data+1); + else if (keycode==39 || keycode==40) core.ui.drawMaps(core.status.event.data-1); + return; +} + +////// 查看地图界面时,放开某个键的操作 ////// +events.prototype.keyUpViewMaps = function (keycode) { + if (keycode==27 || keycode==88 || keycode==13 || keycode==32 || keycode==67) { + core.clearMap('data', 0, 0, 416, 416); + core.setOpacity('data', 1); + core.ui.closePanel(); + } + return; +} + ////// 商店界面时的点击操作 ////// events.prototype.clickShop = function(x,y) { var shop = core.status.event.data.shop; @@ -899,6 +1106,9 @@ events.prototype.clickShop = function(x,y) { if (x >= 5 && x <= 7) { var topIndex = 6 - parseInt(choices.length / 2); if (y>=topIndex && y eval(use)) { core.drawTip("你的"+use_text+"不足"); - return; + return false; } - eval(use+'-='+need); + core.status.event.data.actions.push(y-topIndex); + eval(use+'-='+need); core.setStatus('money', money); core.setStatus('experience', experience); @@ -930,27 +1141,29 @@ events.prototype.clickShop = function(x,y) { } // 离开 else if (y==topIndex+choices.length) { + if (core.status.event.data.actions.length>0) { + core.status.route.push("shop:"+core.status.event.data.id+":"+core.status.event.data.actions.join("")); + } + core.status.boxAnimateObjs = []; core.setBoxAnimate(); if (core.status.event.data.fromList) core.ui.drawQuickShop(); else core.ui.closePanel(); } + else return false; } + return true; } ////// 商店界面时,按下某个键的操作 ////// events.prototype.keyDownShop = function (keycode) { - var shop = core.status.event.data.shop; - var choices = shop.choices; if (keycode==38) { core.status.event.selection--; - if (core.status.event.selection<0) core.status.event.selection=0; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } if (keycode==40) { core.status.event.selection++; - if (core.status.event.selection>choices.length) core.status.event.selection=choices.length; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -982,7 +1195,7 @@ events.prototype.clickQuickShop = function(x, y) { if (x >= 5 && x <= 7) { var topIndex = 6 - parseInt(keys.length / 2); if (y>=topIndex && ykeys.length) core.status.event.selection=keys.length; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -1141,13 +1351,17 @@ events.prototype.keyUpToolbox = function (keycode) { ////// 存读档界面时的点击操作 ////// events.prototype.clickSL = function(x,y) { + + var index=core.status.event.data; + var page = parseInt(index/10), offset=index%10; + // 上一页 if ((x == 3 || x == 4) && y == 12) { - core.ui.drawSLPanel(core.status.event.data - 6); + core.ui.drawSLPanel(10*(page-1)+offset); } // 下一页 if ((x == 8 || x == 9) && y == 12) { - core.ui.drawSLPanel(core.status.event.data + 6); + core.ui.drawSLPanel(10*(page+1)+offset); } // 返回 if (x>=10 && x<=12 && y==12) { @@ -1158,50 +1372,77 @@ events.prototype.clickSL = function(x,y) { return; } - var page=parseInt((core.status.event.data-1)/6); var index=6*page+1; if (y>=1 && y<=4) { - if (x>=1 && x<=3) core.doSL(index, core.status.event.id); - if (x>=5 && x<=7) core.doSL(index+1, core.status.event.id); - if (x>=9 && x<=11) core.doSL(index+2, core.status.event.id); + if (x>=1 && x<=3) core.doSL("autoSave", core.status.event.id); + if (x>=5 && x<=7) core.doSL(5*page+1, core.status.event.id); + if (x>=9 && x<=11) core.doSL(5*page+2, core.status.event.id); } if (y>=7 && y<=10) { - if (x>=1 && x<=3) core.doSL(index+3, core.status.event.id); - if (x>=5 && x<=7) core.doSL(index+4, core.status.event.id); - if (x>=9 && x<=11) core.doSL(index+5, core.status.event.id); + if (x>=1 && x<=3) core.doSL(5*page+3, core.status.event.id); + if (x>=5 && x<=7) core.doSL(5*page+4, core.status.event.id); + if (x>=9 && x<=11) core.doSL(5*page+5, core.status.event.id); } } ////// 存读档界面时,按下某个键的操作 ////// events.prototype.keyDownSL = function(keycode) { + + var index=core.status.event.data; + var page = parseInt(index/10), offset=index%10; + if (keycode==37) { // left - core.ui.drawSLPanel(core.status.event.data - 1); + if (offset==0) { + core.ui.drawSLPanel(10*(page-1) + 5); + } + else { + core.ui.drawSLPanel(index - 1); + } return; } if (keycode==38) { // up - core.ui.drawSLPanel(core.status.event.data - 3); + if (offset<3) { + core.ui.drawSLPanel(10*(page-1) + offset + 3); + } + else { + core.ui.drawSLPanel(index - 3); + } return; } if (keycode==39) { // right - core.ui.drawSLPanel(core.status.event.data + 1); + if (offset==5) { + core.ui.drawSLPanel(10*(page+1)+1); + } + else { + core.ui.drawSLPanel(index + 1); + } return; } if (keycode==40) { // down - core.ui.drawSLPanel(core.status.event.data + 3); + if (offset>=3) { + core.ui.drawSLPanel(10*(page+1) + offset - 3); + } + else { + core.ui.drawSLPanel(index + 3); + } return; } if (keycode==33) { // PAGEUP - core.ui.drawSLPanel(core.status.event.data - 6); + core.ui.drawSLPanel(10*(page-1) + offset); return; } if (keycode==34) { // PAGEDOWN - core.ui.drawSLPanel(core.status.event.data + 6); + core.ui.drawSLPanel(10*(page+1) + offset); return; } } ////// 存读档界面时,放开某个键的操作 ////// events.prototype.keyUpSL = function (keycode) { + + var index=core.status.event.data; + var page = parseInt(index/10), offset=index%10; + if (keycode==27 || keycode==88 || (core.status.event.id == 'save' && keycode==83) || (core.status.event.id == 'load' && keycode==68)) { core.ui.closePanel(); if (!core.isPlaying()) { @@ -1210,7 +1451,12 @@ events.prototype.keyUpSL = function (keycode) { return; } if (keycode==13 || keycode==32 || keycode==67) { - core.doSL(core.status.event.data, core.status.event.id); + if (offset==0) { + core.doSL("autoSave", core.status.event.id); + } + else { + core.doSL(5*page+offset, core.status.event.id); + } return; } } @@ -1219,7 +1465,7 @@ events.prototype.keyUpSL = function (keycode) { events.prototype.clickSwitchs = function (x,y) { if (x<5 || x>7) return; var choices = [ - "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" + "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "下载离线版本", "返回主菜单" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y=choices.length) core.status.event.selection=choices.length-1; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -1290,11 +1534,11 @@ events.prototype.keyDownSwitchs = function (keycode) { events.prototype.keyUpSwitchs = function (keycode) { if (keycode==27 || keycode==88) { core.status.event.selection=0; - core.ui.drawSettings(false); + core.ui.drawSettings(); return; } var choices = [ - "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" + "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "下载离线版本", "返回主菜单" ]; if (keycode==13 || keycode==32 || keycode==67) { var topIndex = 6 - parseInt((choices.length - 1) / 2); @@ -1307,7 +1551,7 @@ events.prototype.keyUpSwitchs = function (keycode) { events.prototype.clickSettings = function (x,y) { if (x<5 || x>7) return; var choices = [ - "系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏" + "系统设置", "快捷商店", "浏览地图", "同步存档", "重新开始", "数据统计", "操作帮助", "关于本塔", "返回游戏" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y0) { + text+="\n当前MAX为"+t.max+",最早由 "+(t.username||"匿名")+" 于"+core.formatDate(new Date(1000*t.timestamp))+"打出。"; + } + }) + core.drawText(text); + } + } + else { + core.drawText("出错啦!\n无法拉取统计信息。\n错误原因:HTTP "+xhr.status); + } + }; + xhr.ontimeout = function() { + core.drawText("出错啦!\n无法拉取统计信息。\n错误原因:Timeout"); + } + xhr.onerror = function() { + core.drawText("出错啦!\n无法拉取统计信息。\n错误原因:XHR Error"); + } + xhr.send(formData); break; case 6: + core.ui.drawHelp(); + break; + case 7: + core.ui.drawAbout(); + break; + case 8: core.ui.closePanel(); break; } @@ -1352,17 +1650,12 @@ events.prototype.clickSettings = function (x,y) { ////// 系统菜单栏界面时,按下某个键的操作 ////// events.prototype.keyDownSettings = function (keycode) { - var choices = [ - "系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏" - ]; if (keycode==38) { core.status.event.selection--; - if (core.status.event.selection<0) core.status.event.selection=0; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } if (keycode==40) { core.status.event.selection++; - if (core.status.event.selection>=choices.length) core.status.event.selection=choices.length-1; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -1374,7 +1667,7 @@ events.prototype.keyUpSettings = function (keycode) { return; } var choices = [ - "系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏" + "系统设置", "快捷商店", "浏览地图", "同步存档", "重新开始", "数据统计", "操作帮助", "关于本塔", "返回游戏" ]; if (keycode==13 || keycode==32 || keycode==67) { var topIndex = 6 - parseInt((choices.length - 1) / 2); @@ -1386,7 +1679,7 @@ events.prototype.keyUpSettings = function (keycode) { events.prototype.clickSyncSave = function (x,y) { if (x<5 || x>7) return; var choices = [ - "同步存档到服务器", "从服务器加载存档", "清空本地存档", "返回主菜单" + "同步存档到服务器", "从服务器加载存档", "存档至本地文件", "从本地文件读档", "清空所有存档", "返回主菜单" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y=choices.length) core.status.event.selection=choices.length-1; core.ui.drawChoices(core.status.event.ui.text, core.status.event.ui.choices); } } @@ -1439,11 +1770,11 @@ events.prototype.keyDownSyncSave = function (keycode) { events.prototype.keyUpSyncSave = function (keycode) { if (keycode==27 || keycode==88) { core.status.event.selection=2; - core.ui.drawSettings(false); + core.ui.drawSettings(); return; } var choices = [ - "同步存档到服务器", "从服务器加载存档", "清空本地存档", "返回主菜单" + "同步存档到服务器", "从服务器加载存档", "存档至本地文件", "从本地文件读档", "清空所有存档", "返回主菜单" ]; if (keycode==13 || keycode==32 || keycode==67) { var topIndex = 6 - parseInt((choices.length - 1) / 2); @@ -1451,6 +1782,65 @@ events.prototype.keyUpSyncSave = function (keycode) { } } +////// “虚拟键盘”界面时的点击操作 ////// +events.prototype.clickKeyBoard = function (x, y) { + if (y==3 && x>=1 && x<=11) { + core.ui.closePanel(); + core.keyUp(112+x-1); // F1-F12: 112-122 + } + if (y==4 && x>=1 && x<=10) { + core.ui.closePanel(); + core.keyUp(x==10?48:48+x); // 1-9: 49-57; 0: 48 + } + // 字母 + var lines = [ + ["Q","W","E","R","T","Y","U","I","O","P"], + ["A","S","D","F","G","H","J","K","L"], + ["Z","X","C","V","B","N","M"], + ]; + if (y==5 && x>=1 && x<=10) { + core.ui.closePanel(); + core.keyUp(lines[0][x-1].charCodeAt(0)); + } + if (y==6 && x>=1 && x<=9) { + core.ui.closePanel(); + core.keyUp(lines[1][x-1].charCodeAt(0)); + } + if (y==7 && x>=1 && x<=7) { + core.ui.closePanel(); + core.keyUp(lines[2][x-1].charCodeAt(0)); + } + if (y==8 && x>=1 && x<=11) { + core.ui.closePanel(); + if (x==1) core.keyUp(189); // - + if (x==2) core.keyUp(187); // = + if (x==3) core.keyUp(219); // [ + if (x==4) core.keyUp(221); // ] + if (x==5) core.keyUp(220); // \ + if (x==6) core.keyUp(186); // ; + if (x==7) core.keyUp(222); // ' + if (x==8) core.keyUp(188); // , + if (x==9) core.keyUp(190); // . + if (x==10) core.keyUp(191); // / + if (x==11) core.keyUp(192); // ` + } + if (y==9 && x>=1 && x<=10) { + core.ui.closePanel(); + if (x==1) core.keyUp(27); // ESC + if (x==2) core.keyUp(9); // TAB + if (x==3) core.keyUp(20); // CAPS + if (x==4) core.keyUp(16); // SHIFT + if (x==5) core.keyUp(17); // CTRL + if (x==6) core.keyUp(18); // ALT + if (x==7) core.keyUp(32); // SPACE + if (x==8) core.keyUp(8); // BACKSPACE + if (x==9) core.keyUp(13); // ENTER + if (x==10) core.keyUp(46); // DEL + } + if (y==10 && x>=9 && x<=11) + core.ui.closePanel(); +} + ////// “关于”界面时的点击操作 ////// events.prototype.clickAbout = function () { if (core.isPlaying()) diff --git a/libs/floors/sample0.js b/libs/floors/sample0.js index bbc54cd8..e4b65511 100644 --- a/libs/floors/sample0.js +++ b/libs/floors/sample0.js @@ -50,7 +50,6 @@ main.floors.sample0 = { "\t[老人,womanMagician]这些是路障、楼梯、传送门。", "\t[老人,womanMagician]血网的伤害数值、中毒后每步伤害数值、衰弱时攻防下降的数值,都在 data.js 内定义。\n\n路障同样会尽量被自动寻路绕过。", "\t[老人,womanMagician]楼梯和传送门需要在changeFloor中定义目标楼层和位置,可参见样板里已有的的写法。", - "\t[老人,womanMagician]楼梯和传送门是否可“穿透”,由data.js中的全局变量所决定,你也可以单独设置。\n穿透的意思是,自动寻路得到的路径中间经过了楼梯,行走时是否触发楼层转换事件。\n例如,下面的“下箭头”就是不能穿透的。", {"type": "hide", "time": 500} ], "2,8": [ // 守着第一批怪物的老人 @@ -75,7 +74,6 @@ main.floors.sample0 = { {"type": "hide", "time": 500} ] }, - }, "changeFloor": { // 楼层转换事件;该事件不能和上面的events有冲突(同位置点),否则会被覆盖 "6,0": {"floorId": "sample1", "stair": "downFloor"}, // 目标点:sample1层的下楼梯位置 @@ -85,8 +83,8 @@ main.floors.sample0 = { "2,12": {"floorId": "sample0", "loc": [2,12]}, "3,12": {"floorId": "sample0", "loc": [6,1], "direction": "up"}, // 切换楼层后勇士面对上方 "4,12": {"floorId": "sample0", "loc": [0,9], "direction": "left", "time": 1000}, // 切换楼层后勇士面对左边,切换动画1000ms - "5,12": {"floorId": "sample0", "loc": [6,10], "portalWithoutTrigger": false}, // 不能穿透 - "6,12": {"floorId": "sample0", "loc": [10,10], "direction": "left", "time": 1000, "portalWithoutTrigger": false}, + "5,12": {"floorId": "sample0", "loc": [6,10], "time": 0}, // time=0表示无切换时间 + "6,12": {"floorId": "sample0", "loc": [10,10], "direction": "left", "time": 1000}, }, "afterBattle": { // 战斗后可能触发的事件列表 "2,6": ["\t[ghostSkeleton]不可能,你怎么可能打败我!\n(一个打败怪物触发的事件)"] diff --git a/libs/floors/sample1.js b/libs/floors/sample1.js index ee2d9b4e..9952e4a6 100644 --- a/libs/floors/sample1.js +++ b/libs/floors/sample1.js @@ -258,18 +258,34 @@ main.floors.sample1 = { "\t[老人,womanMagician]使用 {\"type\":\"function\"} 可以写自定义的JS脚本。\n本塔支持的所有主要API会在doc文档内给出。", "\t[老人,womanMagician]例如这个例子:即将弹出一个输入窗口,然后会将你的输入结果直接加到你的攻击力上。", {"type": "function", "function": function() { // 自己写JS脚本并执行 - var value = prompt("请输入你要加攻击力的数值:"); // 弹出一个输入框让用户输入数据 - if (value!=null) { - value=parseInt(value); - if (value>0) { // 检查 - core.setStatus("atk", core.getStatus("atk")+value); - // core.updateStatusBar(); // 和下面的 {"type": "update"} 等价,立即更新状态栏和地图显伤 - core.drawTip("操作成功,攻击+"+value); // 左上角气泡提示 - core.events.insertAction([ // 往当前事件列表前插入两条事件 - {"type": "update"}, // 更新状态栏和地图显伤 - "操作成功,攻击+"+value // 对话框提示 - ]); + + // 注意一下prompt对于录像是如何处理的 + var value; + if (core.status.replay.replaying) { + var action = core.status.replay.toReplay.shift(); + if (action.indexOf("input:")==0 ) { + value=parseInt(action.substring(6)); } + else { + core.status.replay.replaying=false; + core.drawTip("录像文件出错"); + return; + } + } + else { + value = prompt("请输入你要加攻击力的数值:"); + } + value = parseInt(value)||0; + core.status.route.push("input:"+value); + + if (value>0) { // 检查 + core.setStatus("atk", core.getStatus("atk")+value); + // core.updateStatusBar(); // 和下面的 {"type": "update"} 等价,立即更新状态栏和地图显伤 + core.drawTip("操作成功,攻击+"+value); // 左上角气泡提示 + core.events.insertAction([ // 往当前事件列表前插入两条事件 + {"type": "update"}, // 更新状态栏和地图显伤 + "操作成功,攻击+"+value // 对话框提示 + ]); } }}, "\t[老人,womanMagician]具体可参见样板中本事件的写法。" diff --git a/libs/icons.js b/libs/icons.js index 4aced479..ccf3503b 100644 --- a/libs/icons.js +++ b/libs/icons.js @@ -14,23 +14,23 @@ icons.prototype.init = function () { 'ground': 0, 'grass': 1, 'grass2': 2, - 'snowGround': 3, - 'ground2': 4, - 'ground3': 5, - 'ground4': 6, - 'sand': 7, - 'ground5': 8, - 'yellowWall2': 9, - 'whiteWall2': 10, - 'blueWall2': 11, - 'blockWall': 12, - 'grayWall': 13, - 'white': 14, - 'ground6': 15, - 'soil': 16, - 'yellowWall': 17, - 'whiteWall': 18, - 'blueWall': 19, + 'yellowWall': 3, + 'whiteWall': 4, + 'blueWall': 5, + 'snowGround': 6, + 'ground2': 7, + 'ground3': 8, + 'ground4': 9, + 'sand': 10, + 'ground5': 11, + 'yellowWall2': 12, + 'whiteWall2': 13, + 'blueWall2': 14, + 'blockWall': 15, + 'grayWall': 16, + 'white': 17, + 'ground6': 18, + 'soil': 19, 'star': 20, 'lava': 21, 'ice': 22, @@ -178,11 +178,13 @@ icons.prototype.init = function () { 'bluePotion': 21, 'greenPotion': 22, 'yellowPotion': 23, + 'sword0': 60, 'sword1': 50, 'sword2': 51, 'sword3': 52, 'sword4': 53, 'sword5': 54, + 'shield0': 61, 'shield1': 55, 'shield2': 56, 'shield3': 57, diff --git a/libs/items.js b/libs/items.js index 149fb395..ccd386e5 100644 --- a/libs/items.js +++ b/libs/items.js @@ -33,6 +33,8 @@ items.prototype.init = function () { 'moneyPocket': {'cls': 'items', 'name': '金钱袋'}, // 物品 + 'sword0': {'cls': 'constants', 'name': '折断的剑', 'text': '没有任何作用的剑,相当于脱掉装备。'}, + 'shield0': {'cls': 'constants', 'name': '残破的盾', 'text': '没有任何作用的盾,相当于脱掉装备。'}, 'book': {'cls': 'constants', 'name': '怪物手册', 'text': '可以查看当前楼层各怪物属性'}, 'fly': {'cls': 'constants', 'name': '楼层传送器', 'text': '可以自由往来去过的楼层'}, 'coin': {'cls': 'constants', 'name': '幸运金币', 'text': '持有时打败怪物可得双倍金币'}, @@ -70,6 +72,19 @@ items.prototype.getItems = function () { this.items.pickaxe.text = "可以破坏勇士四周的墙"; if (core.flags.bombFourDirections) this.items.bomb.text = "可以炸掉勇士四周的怪物"; + if (core.flags.equipment) { + this.items.sword1 = {'cls': 'constants', 'name': '铁剑', 'text': '一把很普通的铁剑,攻击+'+core.values.sword1}; + this.items.sword2 = {'cls': 'constants', 'name': '银剑', 'text': '一把很普通的银剑,攻击+'+core.values.sword2}; + this.items.sword3 = {'cls': 'constants', 'name': '骑士剑', 'text': '一把很普通的骑士剑,攻击+'+core.values.sword3}; + this.items.sword4 = {'cls': 'constants', 'name': '圣剑', 'text': '一把很普通的圣剑,攻击+'+core.values.sword4}; + this.items.sword5 = {'cls': 'constants', 'name': '神圣剑', 'text': '一把很普通的神圣剑,攻击+'+core.values.sword5}; + this.items.shield1 = {'cls': 'constants', 'name': '铁盾', 'text': '一个很普通的铁盾,防御+'+core.values.shield1}; + this.items.shield2 = {'cls': 'constants', 'name': '银盾', 'text': '一个很普通的银盾,防御+'+core.values.shield2}; + this.items.shield3 = {'cls': 'constants', 'name': '骑士盾', 'text': '一个很普通的骑士盾,防御+'+core.values.shield3}; + this.items.shield4 = {'cls': 'constants', 'name': '圣盾', 'text': '一个很普通的圣盾,防御+'+core.values.shield4}; + this.items.shield5 = {'cls': 'constants', 'name': '神圣盾', 'text': '一个很普通的神圣盾,防御+'+core.values.shield5}; + } + return this.items; } @@ -118,24 +133,24 @@ items.prototype.getItemEffect = function(itemId, itemNum) { ////// “即捡即用类”道具的文字提示 ////// items.prototype.getItemEffectTip = function(itemId) { - if (itemId === 'redJewel') return ",攻击+"+core.values.redJewel; - if (itemId === 'blueJewel') return ",防御+"+core.values.blueJewel; - if (itemId === 'greenJewel') return ",魔防+"+core.values.greenJewel; + if (itemId == 'redJewel') return ",攻击+"+core.values.redJewel; + if (itemId == 'blueJewel') return ",防御+"+core.values.blueJewel; + if (itemId == 'greenJewel') return ",魔防+"+core.values.greenJewel; if (itemId == 'yellowJewel') return ",全属性提升"; - if (itemId === 'redPotion') return ",生命+"+core.values.redPotion; - if (itemId === 'bluePotion') return ",生命+"+core.values.bluePotion; - if (itemId === 'yellowPotion') return ",生命+"+core.values.yellowPotion; - if (itemId === 'greenPotion') return ",生命+"+core.values.greenPotion; - if (itemId === 'sword1') return ",攻击+"+core.values.sword1; - if (itemId === 'sword2') return ",攻击+"+core.values.sword2; - if (itemId === 'sword3') return ",攻击+"+core.values.sword3; - if (itemId === 'sword4') return ",攻击+"+core.values.sword4; - if (itemId === 'sword5') return ",攻击+"+core.values.sword5; - if (itemId === 'shield1') return ",防御+"+core.values.shield1; - if (itemId === 'shield2') return ",防御+"+core.values.shield2; - if (itemId === 'shield3') return ",防御+"+core.values.shield3; - if (itemId === 'shield4') return ",防御+"+core.values.shield4; - if (itemId === 'shield5') return ",防御+"+core.values.shield5; + if (itemId == 'redPotion') return ",生命+"+core.values.redPotion; + if (itemId == 'bluePotion') return ",生命+"+core.values.bluePotion; + if (itemId == 'yellowPotion') return ",生命+"+core.values.yellowPotion; + if (itemId == 'greenPotion') return ",生命+"+core.values.greenPotion; + if (!core.flags.equipment && itemId == 'sword1') return ",攻击+"+core.values.sword1; + if (!core.flags.equipment && itemId == 'sword2') return ",攻击+"+core.values.sword2; + if (!core.flags.equipment && itemId == 'sword3') return ",攻击+"+core.values.sword3; + if (!core.flags.equipment && itemId == 'sword4') return ",攻击+"+core.values.sword4; + if (!core.flags.equipment && itemId == 'sword5') return ",攻击+"+core.values.sword5; + if (!core.flags.equipment && itemId == 'shield1') return ",防御+"+core.values.shield1; + if (!core.flags.equipment && itemId == 'shield2') return ",防御+"+core.values.shield2; + if (!core.flags.equipment && itemId == 'shield3') return ",防御+"+core.values.shield3; + if (!core.flags.equipment && itemId == 'shield4') return ",防御+"+core.values.shield4; + if (!core.flags.equipment && itemId == 'shield5') return ",防御+"+core.values.shield5; if (itemId === 'bigKey') return ",全钥匙+1"; if (itemId === 'superPotion') return ",生命值翻倍"; if (itemId == 'moneyPocket') return ",金币+"+core.values.moneyPocket; @@ -143,8 +158,11 @@ items.prototype.getItemEffectTip = function(itemId) { } ////// 使用道具 ////// -items.prototype.useItem = function (itemId) { - if (!this.canUseItem(itemId)) return; +items.prototype.useItem = function (itemId, callback) { + if (!this.canUseItem(itemId)) { + if (core.isset(callback)) callback(); + return; + } var itemCls = core.material.items[itemId].cls; if (itemId=='book') core.ui.drawBook(0); @@ -174,6 +192,7 @@ items.prototype.useItem = function (itemId) { // 上楼器/下楼器 core.changeFloor(core.status.event.data.id, null, {'direction': core.status.hero.loc.direction, 'x': core.status.event.data.x, 'y': core.status.event.data.y}, null, function (){ core.drawTip(core.material.items[itemId].name + "使用成功"); + core.replay(); }); } if (itemId == 'poisonWine') core.setFlag('poison', false); @@ -192,12 +211,42 @@ items.prototype.useItem = function (itemId) { } core.setFlag('curse', false); } + + // 剑 + if (itemId.indexOf("sword")==0) { + var now=core.getFlag('sword', 'sword0'); // 当前装备剑的ID + core.status.hero.atk -= core.values[now]; + core.setItem(now, 1); + core.status.hero.atk += core.values[itemId]; + core.setItem(itemId, 0); + core.setFlag('sword', itemId); + core.drawTip("已装备"+core.material.items[itemId].name); + } + // 盾 + if (itemId.indexOf("shield")==0) { + var now=core.getFlag('shield', 'shield0'); + core.status.hero.def -= core.values[now]; + core.setItem(now, 1); + core.status.hero.def += core.values[itemId]; + core.setItem(itemId, 0); + core.setFlag('shield', itemId); + core.drawTip("已装备"+core.material.items[itemId].name); + } + core.updateStatusBar(); + + // 记录路线 + if (itemId!='book' && itemId!='fly') { + core.status.route.push("item:"+itemId); + } + // 道具使用完毕:删除 if (itemCls=='tools') core.status.hero.items[itemCls][itemId]--; if (core.status.hero.items[itemCls][itemId]==0) delete core.status.hero.items[itemCls][itemId]; + + if (core.isset(callback)) callback(); } ////// 当前能否使用道具 ////// @@ -363,5 +412,9 @@ items.prototype.canUseItem = function (itemId) { if (itemId=='weakWine') return core.hasFlag('weak'); if (itemId=='curseWine') return core.hasFlag('curse'); if (itemId=='superWine') return core.hasFlag('poison') || core.hasFlag('weak') || core.hasFlag('curse'); + + // 剑盾 + if (itemId.indexOf("sword")==0 || itemId.indexOf("shield")==0) return true; + return false; } diff --git a/libs/maps.js b/libs/maps.js index 1e9c72dc..dce6c382 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -68,10 +68,10 @@ maps.prototype.getBlock = function (x, y, id) { // 0-20 地形 if (id == 1) tmp.event = {'cls': 'terrains', 'id': 'yellowWall'}; // 黄墙 if (id == 2) tmp.event = {'cls': 'terrains', 'id': 'whiteWall'}; // 白墙 - if (id == 3) tmp.event = {'cls': 'terrains', 'id': 'blueWall'}; // 白墙 + if (id == 3) tmp.event = {'cls': 'terrains', 'id': 'blueWall'}; // 蓝墙 if (id == 4) tmp.event = {'cls': 'animates', 'id': 'star', 'noPass': true}; // 星空 if (id == 5) tmp.event = {'cls': 'animates', 'id': 'lava', 'noPass': true}; // 岩浆 - if (id == 6) tmp.event = {'cls': 'terrains', 'id': 'ice'}; // 岩浆 + if (id == 6) tmp.event = {'cls': 'terrains', 'id': 'ice'}; // 冰面 if (id == 7) tmp.event = {'cls': 'terrains', 'id': 'blueShop-left'}; // 蓝色商店左 if (id == 8) tmp.event = {'cls': 'terrains', 'id': 'blueShop-right'}; // 蓝色商店右 if (id == 9) tmp.event = {'cls': 'terrains', 'id': 'pinkShop-left'}; // 粉色商店左 @@ -356,16 +356,7 @@ maps.prototype.load = function (data, floorId) { } ////// 将当前地图重新变成二维数组形式 ////// -maps.prototype.getMapArray = function (maps, floorId){ - if (!core.isset(floorId)) { - var map = {}; - for (var id in maps) { - map[id] = this.getMapArray(maps, id); - } - return map; - } - - var thisFloor = maps[floorId]; +maps.prototype.getMapArray = function (blockArray){ var blocks = []; for (var x=0;x<13;x++) { @@ -374,7 +365,7 @@ maps.prototype.getMapArray = function (maps, floorId){ blocks[x].push(0); } } - thisFloor.blocks.forEach(function (block) { + blockArray.forEach(function (block) { if (!(core.isset(block.enable) && !block.enable)) blocks[block.y][block.x] = block.id; }); diff --git a/libs/ui.js b/libs/ui.js index f3635f61..1b8217af 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -257,6 +257,8 @@ ui.prototype.drawChoices = function(content, choices) { if (choices.length>0) { if (!core.isset(core.status.event.selection)) core.status.event.selection=0; + if (core.status.event.selection<0) core.status.event.selection=0; + if (core.status.event.selection>=choices.length) core.status.event.selection=choices.length-1; var len = core.canvas.ui.measureText(core.replaceText(choices[core.status.event.selection].text || choices[core.status.event.selection])).width; core.strokeRect('ui', 208-len/2-5, choice_top + 32 * core.status.event.selection - 20, len+10, 28, "#FFD700", 2); } @@ -271,7 +273,8 @@ ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) { core.status.event.data = {'yes': yesCallback, 'no': noCallback}; core.status.event.ui = text; - if (!core.isset(core.status.event.selection)) core.status.event.selection=1; + if (!core.isset(core.status.event.selection) || core.status.event.selection>1) core.status.event.selection=1; + if (core.status.event.selection<0) core.status.event.selection=0; var background = core.canvas.ui.createPattern(core.material.ground, "repeat"); core.clearMap('ui', 0, 0, 416, 416); @@ -319,29 +322,26 @@ ui.prototype.drawSwitchs = function() { var choices = [ "背景音乐:"+(core.musicStatus.bgmStatus ? "[ON]" : "[OFF]"), "背景音效:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), - "战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"), - "怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), - "领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), + "战斗动画: "+(core.flags.battleAnimate ? "[ON]" : "[OFF]"), + "怪物显伤: "+(core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), + "领域显伤: "+(core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), + "下载离线版本", "返回主菜单" ]; this.drawChoices(null, choices); - } ////// 绘制系统菜单栏 ////// -ui.prototype.drawSettings = function (need) { - if (!core.checkStatus('settings', need)) - return; +ui.prototype.drawSettings = function () { + core.status.event.id = 'settings'; this.drawChoices(null, [ - "系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏" + "系统设置", "快捷商店", "浏览地图", "同步存档", "重新开始", "数据统计", "操作帮助", "关于本塔", "返回游戏" ]); } ////// 绘制快捷商店选择栏 ////// -ui.prototype.drawQuickShop = function (need) { - if (core.isset(need) && !core.checkStatus('selectShop', need)) - return; +ui.prototype.drawQuickShop = function () { core.status.event.id = 'selectShop'; @@ -654,12 +654,17 @@ ui.prototype.drawWaiting = function(text) { core.setAlpha('ui', 1); core.setFillStyle('ui', background); - var left = 97, top = 208 - 32 - 16, right = 416 - 2 * left, bottom = 416 - 2 * top; + core.setFont('ui', 'bold 17px Verdana'); + var text_length = core.canvas.ui.measureText(text).width; + + var right = Math.max(text_length+50, 220); + var left = 208-right/2, top = 208 - 32 - 16, bottom = 416 - 2 * top; + core.fillRect('ui', left, top, right, bottom, background); core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); core.canvas.ui.textAlign = "center"; - core.fillText('ui', text, 208, top + 56, "#FFFFFF", "bold 17px Verdana"); + core.fillText('ui', text, 208, top + 56, '#FFFFFF'); } @@ -669,7 +674,7 @@ ui.prototype.drawSyncSave = function () { core.status.event.id = 'syncSave'; this.drawChoices(null, [ - "同步存档到服务器", "从服务器加载存档", "清空本地存档", "返回主菜单" + "同步存档到服务器", "从服务器加载存档", "存档至本地文件", "从本地文件读档", "清空所有存档", "返回主菜单" ]); } @@ -902,6 +907,38 @@ ui.prototype.drawFly = function(page) { this.drawThumbnail(floorId, 'ui', core.status.maps[floorId].blocks, 20, 100, 273); } +////// 绘制浏览地图界面 ////// +ui.prototype.drawMaps = function (index) { + if (!core.isset(index)) index=core.floorIds.indexOf(core.status.floorId); + + if (index<0) index=0; + if (index>=core.floorIds.length) index=core.floorIds.length-1; + + core.lockControl(); + core.status.event.id = 'viewMaps'; + core.status.event.data = index; + + var floorId = core.floorIds[index]; + + clearTimeout(core.interval.tipAnimate); + + core.clearMap('ui', 0, 0, 416, 416); + core.setAlpha('ui', 1); + this.drawThumbnail(floorId, 'ui', core.status.maps[floorId].blocks, 0, 0, 416); + + core.clearMap('data', 0, 0, 416, 416); + core.setOpacity('data', 0.2); + core.canvas.data.textAlign = 'left'; + core.setFont('data', '16px Arial'); + + var text = core.floors[floorId].title; + var textX = 16, textY = 18, width = textX + core.canvas.data.measureText(text).width + 16, height = 42; + core.fillRect('data', 5, 5, width, height, '#000'); + core.setOpacity('data', 0.5); + core.fillText('data', text, textX + 5, textY + 15, '#fff'); + +} + ////// 绘制道具栏 ////// ui.prototype.drawToolbox = function(index) { @@ -1020,16 +1057,15 @@ ui.prototype.drawToolbox = function(index) { ////// 绘制存档/读档界面 ////// ui.prototype.drawSLPanel = function(index) { if (!core.isset(index)) index=1; - if (index<=0) index=1; - if (index>180) index=180; + if (index<0) index=0; + + var page = parseInt(index/10), offset=index%10; + if (page>=30) page=29; + if (offset>5) offset=5; + index=10*page+offset; core.status.event.data=index; - var page=parseInt((index-1)/6); - - // core.status.event.data = page; - // core.status.savePage = page; - core.clearMap('ui', 0, 0, 416, 416); core.setAlpha('ui', 0.85); core.fillRect('ui', 0, 0, 416, 416, '#000000'); @@ -1040,12 +1076,11 @@ ui.prototype.drawSLPanel = function(index) { var name=core.status.event.id=='save'?"存档":"读档"; for (var i=0;i<6;i++) { - var id=6*page+i+1; - var data=core.getLocalStorage("save"+id,null); - + var id=5*page+i; + var data=core.getLocalStorage(i==0?"autoSave":"save"+id, null); if (i<3) { - core.fillText('ui', name+id, (2*i+1)*u, 35, '#FFFFFF', "bold 17px Verdana"); - core.strokeRect('ui', (2*i+1)*u-size/2, 50, size, size, id==index?'#FFD700':'#FFFFFF', id==index?6:2); + core.fillText('ui', i==0?"自动存档":name+id, (2*i+1)*u, 35, '#FFFFFF', "bold 17px Verdana"); + core.strokeRect('ui', (2*i+1)*u-size/2, 50, size, size, i==offset?'#FFD700':'#FFFFFF', i==offset?6:2); if (core.isset(data) && core.isset(data.floorId)) { this.drawThumbnail(data.floorId, 'ui', core.maps.load(data.maps, data.floorId).blocks, (2*i+1)*u-size/2, 50, size, data.hero.loc); core.fillText('ui', core.formatDate(new Date(data.time)), (2*i+1)*u, 65+size, '#FFFFFF', '10px Verdana'); @@ -1057,7 +1092,7 @@ ui.prototype.drawSLPanel = function(index) { } else { core.fillText('ui', name+id, (2*i-5)*u, 230, '#FFFFFF', "bold 17px Verdana"); - core.strokeRect('ui', (2*i-5)*u-size/2, 245, size, size, id==index?'#FFD700':'#FFFFFF', id==index?6:2); + core.strokeRect('ui', (2*i-5)*u-size/2, 245, size, size, i==offset?'#FFD700':'#FFFFFF', i==offset?6:2); if (core.isset(data) && core.isset(data.floorId)) { this.drawThumbnail(data.floorId, 'ui', core.maps.load(data.maps, data.floorId).blocks, (2*i-5)*u-size/2, 245, size, data.hero.loc); core.fillText('ui', core.formatDate(new Date(data.time)), (2*i-5)*u, 260+size, '#FFFFFF', '10px Verdana'); @@ -1091,7 +1126,7 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL } } - var mapArray = core.maps.getMapArray(core.status.maps, floorId); + var mapArray = core.maps.getMapArray(blocks); for (var b in blocks) { var block = blocks[b]; if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) { @@ -1116,6 +1151,44 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL } } +ui.prototype.drawKeyBoard = function () { + core.lockControl(); + core.status.event.id = 'keyBoard'; + + core.clearMap('ui', 0, 0, 416, 416); + + var left = 16, top = 48, right = 416 - 2 * left, bottom = 416 - 2 * top; + var background = core.canvas.ui.createPattern(core.material.ground, "repeat"); + core.fillRect('ui', left, top, right, bottom, background); + core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); + + core.canvas.ui.textAlign = "center"; + core.fillText('ui', "虚拟键盘", 208, top+35, "#FFD700", "bold 22px Verdana"); + + core.setFont('ui', '17px Verdana'); + core.setFillStyle('ui', '#FFFFFF'); + var offset = 128-9; + + var lines = [ + ["F1","F2","F3","F4","F5","F6","F7","F8","F9","10","11"], + ["1","2","3","4","5","6","7","8","9","0"], + ["Q","W","E","R","T","Y","U","I","O","P"], + ["A","S","D","F","G","H","J","K","L"], + ["Z","X","C","V","B","N","M"], + ["-","=","[","]","\\",";","'",",",".","/","`"], + ["ES","TA","CA","SH","CT","AL","SP","BS","EN","DE"] + ] + + lines.forEach(function (line) { + for (var i=0;i