Merge remote-tracking branch 'refs/remotes/ckcz123/master'

This commit is contained in:
YouWei Zhao 2018-01-24 16:02:28 +08:00
commit 346f5b9d71
44 changed files with 1876 additions and 518 deletions

12
.idea/mota-js.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

29
LICENSE.md Normal file
View File

@ -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.

View File

@ -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] 支持全键盘操作。

View File

@ -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;

1
_server/vendor/polyfill.min.js vendored Normal file
View File

@ -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)});

View File

@ -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;
}
}

View File

@ -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 // 绘制一个缩略图

View File

@ -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]** 快捷使用炸弹/圣锤

View File

@ -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进行一段对话。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
docs/img/tuihua.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -9,7 +9,7 @@
你需要有满足如下条件才能进行制作:
- Windows 8以上操作系统Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可
- 任一款现代浏览器。强烈推荐Chrome。
- Chrome浏览器。其他浏览器可能会导致本地服务器产生闪退等现象
- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如WebStormVSCode或者至少也要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)

View File

@ -77,31 +77,34 @@
</div>
</div>
</div>
<script>
// 生成定位编号
(function(){
var colNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<td>'+i+'<div class="colBlock" style="left:'+(i*32+1)+'px;"></div></td>';
colNum += tpl;
}
arrColMark.innerHTML = '<tr>'+colNum+'</tr>';
mapColMark.innerHTML = '<tr>'+colNum+'</tr>';
var rowNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<tr><td>'+i+'<div class="rowBlock" style="top:'+(i*32+1)+'px;"></div></td></tr>';
rowNum += tpl;
}
arrRowMark.innerHTML = rowNum;
mapRowMark.innerHTML = rowNum;
})();
// 生成定位编号
(function(){
var colNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<td>'+i+'<div class="colBlock" style="left:'+(i*32+1)+'px;"></div></td>';
colNum += tpl;
}
arrColMark.innerHTML = '<tr>'+colNum+'</tr>';
mapColMark.innerHTML = '<tr>'+colNum+'</tr>';
var rowNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<tr><td>'+i+'<div class="rowBlock" style="top:'+(i*32+1)+'px;"></div></td></tr>';
rowNum += tpl;
}
arrRowMark.innerHTML = rowNum;
mapRowMark.innerHTML = rowNum;
})();
</script>
<!-- =========================================================== -->
<script src='_server/vendor/vue.min.js'></script>
<script src='_server/vendor/polyfill.min.js'></script>
<!-- <script src="https://cdn.bootcss.com/vue/2.5.13/vue.js"></script> -->
<script src='_server/fs.js'></script>
<!-- <script src='_server/editor_file.js'></script> -->
<script src='_server/vm.js'></script>
<script>
@ -119,7 +122,7 @@ var main={'instance':{}};
var core={};
function editor() {
this.version = "1.2";
this.version = "1.3.2";
this.material = {};
}
// 重构这一堆回调
@ -144,7 +147,8 @@ editor.prototype.init = function(){
});
Promise.all([p1, p2, p3])
.then(function([maps, icons, img]){
.then(function(results){
var maps = results[0], icons = results[1];
editor.idsInit(maps, icons); // 初始化图片素材信息
editor.drawInitData(icons); // 初始化绘图
editor.listen(); // 开始监听事件
@ -176,7 +180,6 @@ editor.prototype.loadImg = function(url){
}
})
}
editor.prototype.loadAllImgs = function(icons){
editor.material.images = {};
var imgs = Object.keys(icons);
@ -317,7 +320,6 @@ editor.prototype.drawMapBg = function(img){
bgc.drawImage(img, 0, 0, 416, 416);
}
}
editor.prototype.updateMap = function(){
var drawTile = function(ctx, x, y, tileInfo){ // 绘制一个普通块
@ -439,7 +441,6 @@ editor.prototype.updateMap = function(){
}
// 绘制地图 end
}
editor.prototype.listen = function() {
var uc = ui.getContext('2d');
@ -450,9 +451,11 @@ editor.prototype.listen = function() {
}//在格子内画一个随机色块
function eToLoc(e) {
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop
editor.loc = {
'x': document.documentElement.scrollLeft+e.clientX - mid.offsetLeft-mapEdit.offsetLeft,
'y': document.documentElement.scrollTop+e.clientY - mid.offsetTop-mapEdit.offsetTop,
'x': scrollLeft+e.clientX - mid.offsetLeft-mapEdit.offsetLeft,
'y': scrollTop+e.clientY - mid.offsetTop-mapEdit.offsetTop,
'size': 32
};
return editor.loc; }//返回可用的组件内坐标
@ -580,9 +583,11 @@ editor.prototype.listen = function() {
data.onmousedown = function (e) {
e.stopPropagation();
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop
var loc = {
'x': document.documentElement.scrollLeft + e.clientX + iconLib.scrollLeft - right.offsetLeft-iconLib.offsetLeft,
'y': document.documentElement.scrollTop + e.clientY + iconLib.scrollTop - right.offsetTop-iconLib.offsetTop,
'x': scrollLeft + e.clientX + iconLib.scrollLeft - right.offsetLeft-iconLib.offsetLeft,
'y': scrollTop + e.clientY + iconLib.scrollTop - right.offsetTop-iconLib.offsetTop,
'size': 32
};
editor.loc = loc;
@ -635,9 +640,17 @@ editor.prototype.listen = function() {
}
}
}
document.body.onmousedown = function(e){
selectBox.isSelected = false;
editor.info = {};
}
iconLib.onmousedown = function(e){
e.stopPropagation();
}
}//绑定事件
editor.prototype.locInfo = function(){
}
/*
editor.updateMap
@ -649,14 +662,37 @@ editor.info
*/
var editor = new editor();
editor.init();
editor.fs=fs;
// editor.file=editor_file;
</script>
<script type="text/javascript">
//Vue
//var listenByVue = function() {
//}
//listenByVue()
<script>
// 文件相关操作
// var promisify = function (fn, receiver) {
// return function () {
// for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
// args[_key] = arguments[_key];
// }
// return new Promise(function (resolve, reject) {
// fn.apply(receiver, [].concat(args, [function (res, err) {
// return err ? reject(err) : resolve(res);
// }]));
// });
// };
// };
// var fns = Object.keys(editor.file);
// var promiseFns = {};
// fns.forEach(function(fn){
// promiseFns[fn] = promisify(editor.file[fn], editor.file)
// });
// editor.promiseFiles = Object.assign(promiseFns);
// editor.promiseFiles.getFloorFileList(editor)
// .then(function(filelist){
// editFile4map.filelist = filelist;
// })
// .catch(function(err){
// console.log(err);
// })
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -32,7 +32,7 @@
<div id='startButtons'>
<span class='startButton' id='playGame'>开始游戏</span>
<span class='startButton' id='loadGame'>载入游戏</span>
<span class='startButton' id='aboutGame'>关于本塔</span>
<span class='startButton' id='replayGame'>录像回放</span>
</div>
<div id='levelChooseButtons'>
<span class='startButton' id='easyLevel'>简单</span>

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ data.prototype.init = function() {
this.firstData = {
"title": "魔塔样板", // 游戏名,将显示在标题页面以及切换楼层的界面中
"name": "template", // 游戏的唯一英文标识符。由英文、数字、下划线组成不能超过20个字符。
"version": "Ver 1.0.0 (Beta)", // 当前游戏版本;版本不一致的存档不能通用。
"version": "Ver 1.3.2", // 当前游戏版本;版本不一致的存档不能通用。
"floorId": "sample0", // 初始楼层ID
"hero": { // 勇士初始数据
"name": "阳光", // 勇士名;可以改成喜欢的
@ -32,17 +32,19 @@ data.prototype.init = function() {
"poison": false, // 毒
"weak": false, // 衰
"curse": false, // 咒
}
},
"steps": 0, // 行走步数统计
},
"startText": [ // 游戏开始前剧情。如果无剧情直接留一个空数组即可。
"Hi欢迎来到 HTML5 魔塔样板!\n\n本样板由艾之葵制作可以让你在不会写任何代码\n的情况下也能做出属于自己的H5魔塔",
"这里游戏开始时的剧情。\n定义在data.js的startText处。\n\n你可以在这里写上自己的内容。",
"赶快来试一试吧!"
],
"shops": { // 定义全局商店(即快捷商店)
"moneyShop1": { // 商店唯一ID
"shops": [ // 定义全局商店(即快捷商店)
{
"id": "moneyShop1", // 商店唯一ID
"name": "贪婪之神", // 商店名称(标题)
"icon": "blueShop", // 商店图标,blueShop为蓝色商店pinkShop为粉色商店
"icon": "blueShop", // 商店图标,在icons.js中的npc一项定义
"textInList": "1F金币商店", // 在快捷商店栏中显示的名称
"use": "money", // 商店所要使用的。只能是"money"或"experience"。
"need": "20+10*times*(times+1)", // 商店需要的金币/经验数值可以是一个表达式以times作为参数计算。
@ -65,7 +67,8 @@ data.prototype.init = function() {
// "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍
]
},
"expShop1": { // 商店唯一ID
{
"id": "expShop1", // 商店唯一ID
"name": "经验之神",
"icon": "pinkShop",
"textInList": "1F经验商店",
@ -80,8 +83,8 @@ data.prototype.init = function() {
{"text": "攻击+5", "need": "30", "effect": "status:atk+=5"},
{"text": "防御+5", "need": "30", "effect": "status:def+=5"},
]
},
},
}
],
"levelUp": [ // 经验升级所需要的数值,是一个数组
{}, // 第一项为初始等级可以简单留空也可以写name
@ -94,7 +97,7 @@ data.prototype.init = function() {
// effect也允许写一个function代表本次升级将会执行的操作
{"need": 40, "effect": function () {
core.drawText("恭喜升级!");
core.insertAction("恭喜升级!");
core.status.hero.hp *= 2;
core.status.hero.atk += 100;
core.status.hero.def += 100;
@ -118,6 +121,8 @@ data.prototype.init = function() {
"bluePotion": 250, // 蓝血瓶加血数值
"yellowPotion": 500, // 黄血瓶加血数值
"greenPotion": 800, // 绿血瓶加血数值
"sword0": 0, // 默认装备折断的剑的攻击力
"shield0": 0, // 默认装备残破的盾的防御力
"sword1": 10, // 铁剑加攻数值
"shield1": 10, // 铁盾加防数值
"sword2": 20, // 银剑加攻数值
@ -153,19 +158,21 @@ data.prototype.init = function() {
"pickaxeFourDirections": true, // 使用破墙镐是否四个方向都破坏如果false则只破坏面前的墙壁
"bombFourDirections": true, // 使用炸弹是否四个方向都会炸如果false则只炸面前的怪物即和圣锤等价
"bigKeyIsBox": false, // 如果此项为true则视为钥匙盒红黄蓝钥匙+1若为false则视为大黄门钥匙
"equipment": false, // 剑和盾是否直接作为装备。如果此项为true则作为装备需要在道具栏使用否则将直接加属性。
/****** 怪物相关 ******/
"enableNegativeDamage": true, // 是否支持负伤害(回血)
"zoneSquare": false, // 领域类型。如果此项为true则为九宫格伤害为false则为十字伤害
"hatredDecrease": true, // 是否在和仇恨怪战斗后减一半的仇恨值此项为false则和仇恨怪不会扣减仇恨值。
"betweenAttackCeil": false, // 夹击方式是向上取整还是向下取整。如果此项为true则为向上取整为false则为向下取整
/****** 系统相关 ******/
"startDirectly": false, // 点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面
"canOpenBattleAnimate": true, // 是否允许用户开启战斗过程如果此项为false则下面两项均强制视为false
"showBattleAnimateConfirm": true, // 是否在游戏开始时提供“是否开启战斗动画”的选项
"battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中开关
"displayEnemyDamage": true, // 是否地图怪物显伤;用户可以手动在菜单栏中开关
"displayExtraDamage": false, // 是否地图高级显伤(领域、夹击等);用户可以手动在菜单栏中开关
"displayExtraDamage": true, // 是否地图高级显伤(领域、夹击等);用户可以手动在菜单栏中开关
"enableGentleClick": true, // 是否允许轻触(获得面前物品)
"portalWithoutTrigger": true, // 经过楼梯、传送门时是否能“穿透”。穿透的意思是,自动寻路得到的的路径中间经过了楼梯,行走时是否触发楼层转换事件
"potionWhileRouting": false, // 寻路算法是否经过血瓶如果该项为false则寻路算法会自动尽量绕过血瓶
"enableViewMaps": true, // 是否支持在菜单栏中查看所有楼层的地图
}
}

View File

@ -21,11 +21,11 @@ enemys.prototype.init = function () {
'zombie': {'name': '兽人', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'zombieKnight': {'name': '兽人武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'rock': {'name': '石头人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 4, 'experience': 0, 'special': 3},
'slimeMan': {'name': '影子战士', 'hp': 100, 'atk': 0, 'def': 0, 'money': 11, 'experience': 0, 'special': 10}, // 模仿怪的攻防设为0就好
'slimeMan': {'name': '影子战士', 'hp': 100, 'atk': 0, 'def': 0, 'money': 11, 'experience': 0, 'special': [10,21], 'atkValue': 2, 'defValue': 3}, // 退化怪可以在后面写atkValue和defValue表示退化的数值
'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2, 'point': 1}, // 'point'可以在打败怪物后进行加点,详见文档说明。
'redPriest': {'name': '高级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100, 'zoneSquare': true}, // 领域怪需要加value表示领域伤害的数值zoneSquare代表是否九宫格伤害
'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200, 'range': 2}, // range可选代表领域伤害的范围不加默认为1
'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100, 'range': 2}, // 领域怪需要加value表示领域伤害的数值range可选代表领域伤害的范围不加默认为1
'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200, 'zoneSquare': true}, // zoneSquare可选代表是否九宫格伤害true为是九宫格伤害false或不设置为十字伤害
'yellowGuard': {'name': '初级卫兵', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 0},
'blueGuard': {'name': '中级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redGuard': {'name': '高级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
@ -52,7 +52,7 @@ enemys.prototype.init = function () {
'goldHornSlime': {'name': '金角怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'redKing': {'name': '红衣魔王', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'whiteKing': {'name': '白衣武士', 'hp': 100, 'atk': 120, 'def': 0, 'money': 17, 'experience': 0, 'special': 16},
'blackMagician': {'name': '黑暗大法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 12, 'experience': 0, 'special': 11, 'value': 1/3, 'bomb': false}, // 吸血怪需要在后面添加value代表吸血比例
'blackMagician': {'name': '黑暗大法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 12, 'experience': 0, 'special': 11, 'value': 1/3, 'add': true, 'bomb': false}, // 吸血怪需要在后面添加value代表吸血比例添加add: true可以将吸血的伤害加到自身
'silverSlime': {'name': '银头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 15, 'experience': 0, 'special': 14},
'swordEmperor': {'name': '剑圣', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
'whiteHornSlime': {'name': '尖角怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0},
@ -79,7 +79,16 @@ enemys.prototype.getEnemys = function (enemyId) {
////// 判断是否含有某特殊属性 //////
enemys.prototype.hasSpecial = function (special, test) {
return (special instanceof Array)?special.indexOf(test)>=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),

View File

@ -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<data.choices.length) {
//core.status.route.push("choices:"+index);
//this.insertAction(data.choices[index].action);
//this.doAction();
core.status.event.selection=index;
setTimeout(function () {
core.status.route.push("choices:"+index);
core.events.insertAction(data.choices[index].action);
core.events.doAction();
}, 500)
}
else {
core.status.replay.replaying=false;
core.drawTip("录像文件出错");
}
}
}
core.ui.drawChoices(data.text, data.choices);
break;
case "win":
core.events.win(data.reason);
core.events.win(data.reason, function () {
core.events.doAction();
});
break;
case "lose":
core.events.lose(data.reason);
core.events.lose(data.reason, function () {
core.events.doAction();
});
break;
case "function":
var func = data["function"];
@ -401,12 +540,15 @@ events.prototype.doAction = function() {
}
////// 往当前事件列表之前添加一个或多个事件 //////
events.prototype.insertAction = function (action) {
events.prototype.insertAction = function (action, x, y, callback) {
if (core.status.event.id == null) {
this.doEvents(action);
this.doEvents(action, x, y, callback);
}
else {
core.unshift(core.status.event.data.list, action)
if (core.isset(x)) core.status.event.data.x=x;
if (core.isset(y)) core.status.event.data.y=y;
if (core.isset(callback)) core.status.event.data.callback=callback;
}
}
@ -423,11 +565,15 @@ events.prototype.openShop = function(shopId, needVisited) {
shop.visited = true;
var selection = core.status.event.selection;
var actions = [];
if (core.isset(core.status.event.data) && core.isset(core.status.event.data.actions))
actions=core.status.event.data.actions;
core.ui.closePanel();
core.lockControl();
// core.status.event = {'id': 'shop', 'data': {'id': shopId, 'shop': shop}};
core.status.event.id = 'shop';
core.status.event.data = {'id': shopId, 'shop': shop};
core.status.event.data = {'id': shopId, 'shop': shop, 'actions': actions};
core.status.event.selection = selection;
// 拼词
@ -458,7 +604,7 @@ events.prototype.disableQuickShop = function (shopId) {
}
////// 能否使用快捷商店 //////
events.prototype.canUseQuickShop = function(shopIndex) {
events.prototype.canUseQuickShop = function(shopId) {
if (core.isset(core.floors[core.status.floorId].canUseQuickShop) && !core.isset(core.floors[core.status.floorId].canUseQuickShop))
return '当前不能使用快捷商店。';
@ -543,8 +689,10 @@ events.prototype.addPoint = function (enemy) {
////// 战斗结束后触发的事件 //////
events.prototype.afterBattle = function(enemyId,x,y,callback) {
var enemy = core.material.enemys[enemyId];
// 毒衰咒的处理
var special = core.material.enemys[enemyId].special;
var special = enemy.special;
// 中毒
if (core.enemys.hasSpecial(special, 12) && !core.hasFlag('poison')) {
core.setFlag('poison', true);
@ -560,13 +708,20 @@ events.prototype.afterBattle = function(enemyId,x,y,callback) {
core.setFlag('curse', true);
}
// 仇恨属性:减半
if (core.enemys.hasSpecial(special, 17)) {
if (core.flags.hatredDecrease && core.enemys.hasSpecial(special, 17)) {
core.setFlag('hatred', parseInt(core.getFlag('hatred', 0)/2));
}
// 自爆
if (core.enemys.hasSpecial(special, 19)) {
core.status.hero.hp = 1;
}
// 退化
if (core.enemys.hasSpecial(special, 21)) {
core.status.hero.atk -= (enemy.atkValue||0);
core.status.hero.def -= (enemy.defValue||0);
if (core.status.hero.atk<0) core.status.hero.atk=0;
if (core.status.hero.def<0) core.status.hero.def=0;
}
// 增加仇恨值
core.setFlag('hatred', core.getFlag('hatred',0)+core.values.hatred);
core.updateStatusBar();
@ -678,6 +833,16 @@ events.prototype.afterChangeLight = function(x,y) {
////// 使用炸弹/圣锤后的事件 //////
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]} // 开门
])
}
*/
}
@ -696,6 +861,14 @@ events.prototype.afterLoadData = function(data) {
/********** 点击事件、键盘事件 ************/
/****************************************/
////// 长按 //////
events.prototype.longClick = function () {
core.waitHeroToStop(function () {
// 绘制快捷键
core.ui.drawKeyBoard();
});
}
////// 按下Ctrl键时快捷跳过对话 //////
events.prototype.keyDownCtrl = function () {
if (core.status.event.id=='text') {
@ -756,6 +929,8 @@ events.prototype.clickAction = function (x,y) {
if (x >= 5 && x <= 7) {
var topIndex = 6 - parseInt((choices.length - 1) / 2);
if (y>=topIndex && y<topIndex+choices.length) {
// 选择
core.status.route.push("choices:"+(y-topIndex));
this.insertAction(choices[y-topIndex].action);
this.doAction();
}
@ -771,12 +946,10 @@ events.prototype.keyDownAction = function (keycode) {
if (choices.length>0) {
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<index?"upFloor":"downFloor";
var floorId=core.status.event.data;
core.status.route.push("fly:"+core.status.hero.flyRange[floorId]);
core.changeFloor(core.status.hero.flyRange[floorId], stair);
core.ui.closePanel();
}
@ -892,6 +1067,38 @@ events.prototype.keyUpFly = function (keycode) {
return;
}
////// 查看地图界面时的点击操作 //////
events.prototype.clickViewMaps = function (x,y) {
if(y<=4) {
core.ui.drawMaps(core.status.event.data+1);
}
else if (y>=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<topIndex+choices.length) {
core.status.event.selection=y-topIndex;
//this.insertAction(choices[y-topIndex].action);
//this.doAction();
var money = core.getStatus('money'), experience = core.getStatus('experience');
@ -912,11 +1122,12 @@ events.prototype.clickShop = function(x,y) {
if (need > 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 && y<topIndex+keys.length) {
var reason = core.events.canUseQuickShop(y-topIndex);
var reason = core.events.canUseQuickShop(keys[y - topIndex]);
if (core.isset(reason)) {
core.drawText(reason);
return;
@ -999,15 +1212,12 @@ events.prototype.clickQuickShop = function(x, y) {
////// 快捷商店界面时,按下某个键的操作 //////
events.prototype.keyDownQuickShop = function (keycode) {
var shopList = core.status.shops, keys = Object.keys(shopList);
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>keys.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<topIndex+choices.length) {
@ -1262,8 +1508,11 @@ events.prototype.clickSwitchs = function (x,y) {
core.ui.drawSwitchs();
break;
case 5:
window.open(core.firstData.name+".zip", "_blank");
break;
case 6:
core.status.event.selection=0;
core.ui.drawSettings(false);
core.ui.drawSettings();
break;
}
}
@ -1271,17 +1520,12 @@ events.prototype.clickSwitchs = function (x,y) {
////// 系统设置界面时,按下某个键的操作 //////
events.prototype.keyDownSwitchs = 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);
}
}
@ -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 && y<topIndex+choices.length) {
@ -1323,26 +1567,80 @@ events.prototype.clickSettings = function (x,y) {
core.ui.drawQuickShop();
break;
case 2:
if (!core.flags.enableViewMaps) {
core.drawTip("本塔不允许浏览地图!");
}
else {
core.drawText("\t[系统提示]即将进入浏览地图模式。\n\n点击地图上半部分或按[↑]键可查看前一张地图\n点击地图下半部分或按[↓]键可查看后一张地图\n点击地图中间或按[ESC]键可离开浏览地图模式", function () {
core.ui.drawMaps(core.floorIds.indexOf(core.status.floorId));
})
}
break;
case 3:
core.status.event.selection=0;
core.ui.drawSyncSave();
break;
case 3:
case 4:
core.status.event.selection=1;
core.ui.drawConfirmBox("你确定要重新开始吗?", function () {
core.ui.closePanel();
core.restart();
}, function () {
core.status.event.selection=3;
core.ui.drawSettings(false);
core.ui.drawSettings();
});
break;
case 4:
core.ui.drawHelp();
break;
case 5:
core.ui.drawAbout();
core.ui.drawWaiting("正在拉取统计信息,请稍后...");
var formData = new FormData();
formData.append('type', 'getinfo');
formData.append('name', core.firstData.name);
formData.append('version', core.firstData.version);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/games/upload.php");
xhr.onload = function(e) {
if (xhr.status==200) {
var response = JSON.parse(xhr.response);
if (response.code<0) {
core.drawText("出错啦!\n无法拉取统计信息。\n错误原因"+response.msg);
}
else {
var text="\t[本塔统计信息]";
var toAdd=false;
response.data.forEach(function (t) {
if (toAdd) text+="\n\n";
toAdd=true;
if (t.hard!='') text+=t.hard+"难度: "
text+="已有"+t.people+"人次游戏,"+t.score+"人次通关。";
if (core.isset(t.max) && t.max>0) {
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<topIndex+choices.length) {
@ -1399,18 +1692,61 @@ events.prototype.clickSyncSave = function (x,y) {
core.syncSave("load");
break;
case 2:
var saves = [];
for (var i=1;i<=150;i++) {
var data = core.getLocalStorage("save"+i, null);
if (core.isset(data)) {
saves.push(data);
}
}
var content = {
"name": core.firstData.name,
"version": core.firstData.version,
"data": saves
}
core.download(core.firstData.name+"_"+core.formatDate2(new Date())+".h5save", JSON.stringify(content));
break;
case 3:
core.readFile(function (obj) {
if (obj.name!=core.firstData.name) {
alert("存档和游戏不一致!");
return;
}
if (obj.version!=core.firstData.version) {
alert("游戏版本不一致!");
return;
}
if (!core.isset(obj.data)) {
alert("无效的存档!");
return;
}
var data=obj.data;
for (var i=1;i<=150;i++) {
if (i<=data.length) {
core.setLocalStorage("save"+i, data[i-1]);
}
else {
core.removeLocalStorage("save"+i);
}
}
core.drawText("读取成功!\n你的本地所有存档均已被覆盖。");
}, function () {
});
break;
case 4:
core.status.event.selection=1;
core.ui.drawConfirmBox("你确定要清空所有本地存档吗?", function() {
core.ui.drawConfirmBox("你确定要清空所有存档吗?", function() {
localStorage.clear();
core.drawText("\t[操作成功]你的本地所有存档已被清空。");
core.drawText("\t[操作成功]你的所有存档已被清空。");
}, function() {
core.status.event.selection=2;
core.ui.drawSyncSave(false);
})
break;
case 3:
core.status.event.selection=2;
core.ui.drawSettings(false);
case 5:
core.status.event.selection=3;
core.ui.drawSettings();
break;
}
@ -1420,17 +1756,12 @@ events.prototype.clickSyncSave = function (x,y) {
////// 同步存档界面时,按下某个键的操作 //////
events.prototype.keyDownSyncSave = 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);
}
}
@ -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())

View File

@ -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一个打败怪物触发的事件"]

View File

@ -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]具体可参见样板中本事件的写法。"

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
});

View File

@ -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<line.length;i++) {
core.fillText('ui', line[i], 48+32*i, offset);
}
offset+=32;
});
core.fillText("ui", "返回游戏", 416-80, offset-3, '#FFFFFF', 'bold 15px Verdana');
}
////// 绘制“关于”界面 //////
ui.prototype.drawAbout = function() {
@ -1151,11 +1224,13 @@ ui.prototype.drawHelp = function () {
"[CTRL] 跳过对话\n" +
"[X] 打开/关闭怪物手册\n" +
"[G] 打开/关闭楼层传送器\n" +
"[A] 读取自动存档(回退)\n" +
"[S/D] 打开/关闭存/读档页面\n" +
"[K] 打开/关闭快捷商店选择列表\n" +
"[T] 打开/关闭工具栏\n" +
"[ESC] 打开/关闭系统菜单\n" +
"[H] 打开帮助页面\n"+
"[R] 回放\n"+
"[SPACE] 轻按(仅在轻按开关打开时有效)\n" +
"[1] 快捷使用破墙镐\n" +
"[2] 快捷使用炸弹/圣锤\n" +
@ -1165,6 +1240,7 @@ ui.prototype.drawHelp = function () {
"点任意块: 寻路并移动\n"+
"点任意块并拖动: 指定寻路路线\n"+
"单击勇士: 转向\n"+
"双击勇士: 轻按(仅在轻按开关打开时有效)"
"双击勇士: 轻按(仅在轻按开关打开时有效)\n"+
"长按任意位置:打开虚拟键盘"
]);
}

42
main.js
View File

@ -2,7 +2,7 @@ function main() {
//------------------------ 用户修改内容 ------------------------//
this.version = "0.1"; // 游戏版本号如果更改了游戏内容建议修改此version以免造成缓存问题。
this.version = "1.3.2"; // 游戏版本号如果更改了游戏内容建议修改此version以免造成缓存问题。
this.useCompress = false; // 是否使用压缩文件
// 当你即将发布你的塔时请使用“JS代码压缩工具”将所有js代码进行压缩然后将这里的useCompress改为true。
@ -15,11 +15,11 @@ function main() {
this.pngs = [ // 在此存放所有可能的背景图片背景图片最好是416*416像素其他分辨率会被强制缩放成416*416
// 建议对于较大的图片,在网上使用在线的“图片压缩工具”来进行压缩,以节省流量
// 有关使用自定义背景图,请参见文档的“自定义素材”说明
"bg.png", "yewai.png", // 依次向后添加
"bg.png", // 依次向后添加
];
this.bgms = [ // 在此存放所有的bgm和文件名一致。第一项为默认播放项
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
'058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid',
'bgm.mp3', 'qianjin.mid', 'star.mid',
];
this.sounds = [ // 在此存放所有的SE和文件名一致
// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
@ -53,7 +53,7 @@ function main() {
'startButtons': document.getElementById('startButtons'),
'playGame': document.getElementById('playGame'),
'loadGame': document.getElementById('loadGame'),
'aboutGame': document.getElementById('aboutGame'),
'replayGame': document.getElementById('replayGame'),
'levelChooseButtons': document.getElementById('levelChooseButtons'),
'easyLevel': document.getElementById('easyLevel'),
'normalLevel': document.getElementById('normalLevel'),
@ -338,7 +338,7 @@ main.statusBar.image.toolbox.onclick = function () {
////// 点击状态栏中的快捷商店时 //////
main.statusBar.image.shop.onclick = function () {
if (main.core.isPlaying())
main.core.ui.drawQuickShop(true);
main.core.openQuickShop(true);
}
////// 点击状态栏中的存档按钮时 //////
@ -356,7 +356,7 @@ main.statusBar.image.load.onclick = function () {
////// 点击状态栏中的系统菜单时 //////
main.statusBar.image.settings.onclick = function () {
if (main.core.isPlaying())
main.core.ui.drawSettings(true);
main.core.openSettings(true);
}
////// 点击“开始游戏”时 //////
@ -376,9 +376,33 @@ main.dom.loadGame.onclick = function() {
main.core.load();
}
////// 点击“关于本塔”时 //////
main.dom.aboutGame.onclick = function () {
main.core.ui.drawAbout();
////// 点击“录像回放”时 //////
main.dom.replayGame.onclick = function () {
core.readFile(function (obj) {
if (obj.name!=core.firstData.name) {
alert("存档和游戏不一致!");
return;
}
if (obj.version!=core.firstData.version) {
alert("游戏版本不一致!");
return;
}
if (!core.isset(obj.route) || !core.isset(obj.hard)) {
alert("无效的录像!");
return;
}
core.dom.startPanel.style.display = 'none';
core.resetStatus(core.firstData.hero, obj.hard, core.firstData.floorId, null, core.initStatus.maps);
core.events.setInitData(obj.hard);
core.changeFloor(core.status.floorId, null, core.firstData.hero.loc, null, function() {
core.setHeroMoveTriggerInterval();
core.replay(core.decodeRoute(obj.route));
});
}, function () {
})
}
////// 点击“简单难度”时 //////

View File

@ -90,7 +90,7 @@
margin-top: 8%;
max-width: 100%;
text-align: center;
font: bold 4rem 华文行楷;
font: bold 4rem STXingkai;
}
#startTitle {
@ -147,7 +147,7 @@
#logoLabel {
margin-top: 8%;
font: bold 3rem 华文行楷;
font: bold 3rem STXingkai;
margin-left: auto;
margin-right: auto;
}
@ -169,7 +169,7 @@
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
background: url(images/ground.png) round;
background: url(images/ground.png) repeat;
z-index: 7;
display: none;
}
@ -198,7 +198,7 @@
}
#toolBar {
position: absolute;
background: url(images/ground.png) round;
background: url(images/ground.png) repeat;
z-index: 6;
box-sizing: border-box;
-moz-box-sizing: border-box;

Binary file not shown.

View File

@ -3,14 +3,17 @@
=== 全局 ===
[↑][↓][←][→] 移动勇士
[CTRL] 跳过对话
[Z] 转向
[X] 打开/关闭怪物手册
[G] 打开/关闭楼层传送器
[A] 读取自动存档
[S] 打开/关闭存档页面
[D] 打开/关闭读档页面
[K] 打开/关闭快捷商店选择列表
[T] 打开/关闭工具栏
[ESC] 打开/关闭系统菜单
[H] 打开帮助页面
[R] 回放
[SPACE] 轻按(仅在轻按开关打开时有效)
[1] 快捷使用破墙镐
[2] 快捷使用炸弹/圣锤(先检测有没有炸弹,没有再检测圣锤)

View File

@ -1,4 +1,23 @@
HTML5魔塔样板V1.3
HTML5魔塔样板V1.3.2
增加录像和回放功能。
增加统计功能现在能看到每部塔的游戏人数、通关人数和当前MAX了。
增加浏览地图功能,玩家可以快速查看每层楼的地图。
现在保存文件到本地,以及从本地文件读档了。
可以在全局开关中设置剑盾是否作为装备存在。
修复了部分已知Bug。
-----------------------------------------------------------------------
HTML5魔塔样板V1.3.1
增加虚拟键盘。
增加自动存档回退A键可快速读档。
修复几处较为严重的Bug。
-----------------------------------------------------------------------
HTML5魔塔样板V1.3
支持全键盘操作。
支持将某个图片作为某层的背景素材。