diff --git a/HTML5魔塔样板使用指南.url b/HTML5魔塔样板使用指南.url index a346778c..823d8f8d 100644 --- a/HTML5魔塔样板使用指南.url +++ b/HTML5魔塔样板使用指南.url @@ -2,4 +2,4 @@ Prop3=19,2 [InternetShortcut] IDList= -URL=http://ckcz123.github.io/mota-js/ +URL=https://ckcz123.github.io/mota-js/ diff --git a/README.md b/README.md index 9fdc6774..fa46e8d3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,61 @@ -# H5 魔塔样板 +# HTML5 魔塔样板 ## 简介 HTML5 canvas制作的魔塔样板,支持全平台游戏! **即使完全不会编程的用户,按照模板和说明文档也能很快做出一个魔塔游戏!** -* [Demo / 样板效果](http://ckcz123.com/games/template/) -* [Docs / 使用文档说明](http://ckcz123.github.io/mota-js) +* [Demo / 样板效果](https://ckcz123.com/games/template/) +* [Docs / 使用文档说明](https://ckcz123.github.io/mota-js) ![样板](./docs/img/sample0.png) +## 目录结构 + +``` bash +├── /_server/ # 为可视化地图编辑器提供一些支持的目录 +├── /docs/ # 文档目录 +├── /images/ # 所有图片素材目录 +│ ├─ /常用素材/ # 可以被直接替换的素材 +│ └─ *.png # 对应的某个具体的图片素材 +├── /libs/ # JS源代码目录 +│ ├─ /floors/ # 剧本文件,记录了每个地图的数据和事件 +│ ├─ core.js # 系统核心文件 +│ ├─ data.js # 记录了勇士的初始化信息、各个全局变量和全局Flag值 +│ ├─ enemys.js # 记录了怪物的信息,包括怪物的数据和特殊属性、伤害计算公式、临界值计算等。 +│ ├─ events.js # 处理事件的文件,所有自定义事件都会在此文件中进行处理 +│ ├─ icons.js # 记录了图标信息,将元件的ID和images目录下的素材图标对应起来 +│ ├─ items.js # 记录了道具的信息,包括道具说明、道具效果等。 +│ ├─ maps.js # 记录了地图信息,负责将数字与元件的ID一一对应起来。 +│ └─ ui.js # UI绘制信息,主要负责绘制各个UI窗口。 +├── /sounds/ # 音效目录 +├── /常用工具/ # 一些常用工具,可以辅助造塔 +│ ├─ JS代码压缩工具.exe # 能对Javascript代码进行压缩和整合,从而减少IO请求量。 http://github.com/ckcz123/JSCompressor/ +│ ├─ 便捷PS工具.exe # 能只用复制和粘贴来快速对素材进行PS操作。 http://github.com/ckcz123/ps/ +│ ├─ 地图生成器.exe # 能从一张截图识别出来具体的数字数组,方便复刻已有的塔。 http://github.com/ckcz123/map_generator/ +│ └─ 伤害和临界值计算器.exe # 一个能帮助计算怪物的伤害和临界值的小工具。 http://github.com/ckcz123/magic-tower-calculator/ +├── drawMapGUI.html # 可视化地图编辑工具,能简单地在界面上绘制地图 +├── index.html # 主程序,游戏的入口 +├── main.js # JS程序的入口,将动态对所需JS进行加载 +├── style.css # 游戏所需要用到的样式表 +└── 启动服务.exe # 一个本地的HTTP服务器,也能支撑前端的一些POST请求从而能拓展JS的IO功能。 http://github.com/ckcz123/mota-js-server/ +``` + ## 更新说明 +### 2017.12.21 + +* [x] 新增:本地HTTP服务器。 +* [x] 新增:可视化地图编辑工具。 +* [x] 新增:便捷PS工具。 +* [x] 移除了meaning.txt,现在“地图生成器”将直接从js文件中读取数字和图块对应关系。 +* [x] 新增:对Autotile图块的支持。 +* [x] 新增:怪物支持多种属性;添加仇恨属性。 +* [x] 移除了不再支持的checkBlock,现在对于领域和夹击无需再手动指定可能的点了。 +* [x] 新增:单向箭头、感叹号(单次通行)的支持。 +* [x] 新增:更多的默认素材,现在对于大多数地图风格无需P图,直接替换即可。 +* [x] 添加部分自定义事件,部分细节优化,一些已知的Bug进行了修复。 + ### 2017.12.16 * [x] 新增:战斗过程显示,可以在设置中关闭 @@ -24,7 +68,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! ### 2017.12.9 -* 发布初版HTML5魔塔样板 +* [x] 发布初版HTML5魔塔样板 ## 联系我们 diff --git a/[start server.cmd b/[start server.cmd deleted file mode 100644 index 814d7527..00000000 --- a/[start server.cmd +++ /dev/null @@ -1 +0,0 @@ -python _server.py \ No newline at end of file diff --git a/_server.py b/_server.py deleted file mode 100644 index e2955bc1..00000000 --- a/_server.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- -import json -import socket -import threading -import os -import time - -def httpserver(port = 7945): - import http.server - http.server.test(http.server.SimpleHTTPRequestHandler,port=port) - -def voidfunc(*a,**k): - pass -sysecho=print -mecho=voidfunc - -homepage='homepage' -strtemplate='HTTP/1.0 302 Move temporarily\r\nContent-Length: 0\r\nLocation: {urlstr}\r\n\r\n' #{urlstr} - -def mainget(urlstr): - funcAfter=lambda:0 - if False and urlstr == '/': - sysecho(''.join([ - 'GET / ',addr[0],':',str(addr[1]) - ])) - return (200,homepage,funcAfter) - if True: - return (strtemplate.format(urlstr='//127.0.0.1:7945'+urlstr),'',funcAfter) - return (404,'404') - -def mainpost(urlstr,body): - funcAfter=lambda:0 - if urlstr == '/': - out='name not match' - try: - op=json.loads(body) - name=op['name'] - op['func'] - op['args'] - except Exception as e: - return (200,'error format') - if name=='readUTF8file' and op['func']=='open': - with open('./'+op['args'][0],encoding='utf-8') as fid: - out=fid.read() - if name=='writeUTF8file' and op['func']=='open': - with open('./'+op['args'][0],'w',encoding='utf-8') as fid: - out=str(fid.write(op['args'][1])) - return (200,out,funcAfter) - return (403,'no service this url') - -def mainparse(header,body): - funcAfter=lambda:0 - for _tmp in [1]: - if header[:3]=='GET': - urlstr=header.split(' ',2)[1] - mainre = mainget(urlstr) - if len(mainre)==2: - header,body=mainre - else: - header,body,funcAfter=mainre - break - if header[:4]=='POST': - urlstr=header.split(' ',2)[1] - mainre = mainpost(urlstr,body) - if len(mainre)==2: - header,body=mainre - else: - header,body,funcAfter=mainre - break - if header=='': - header,body= (403,'') - break - header,body= (403,'') - body=body.encode('utf-8') - if type(header)==int: - codeDict={200:'200 OK',302:'302 Move temporarily',403:'403 Forbidden',404:'404 Not Found'} - header=('HTTP/1.0 {statu}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: '.format(statu=codeDict[header])+str(len(body))+'\r\nAccess-Control-Allow-Origin: *\r\n\r\n') - #\r\nAccess-Control-Allow-Origin: * null : to test in chrome - header=header.encode('utf-8') - return (header,body,funcAfter) - -def tcplink(sock, addr): - mecho('\n\nAccept new connection from %s:%s...' % addr) - tempbuffer = [''] - data='' - header='' - body='' - while True: - d = sock.recv(512) - if d: - d=d.decode('utf-8') - tempbuffer.append(d) - tempend=tempbuffer[-1][-4:]+d - if '\r\n\r\n' in tempend: - headend=True - data=''.join(tempbuffer) - header, body = data.split('\r\n\r\n', 1) - if header[:3]=='GET': - tempbuffer=[] - break - tempbuffer=[body] - a=int(header.split('Content-Length:',1)[1].split('\r\n',1)[0])-len(body.encode('utf-8'))#str.len not equal byte.len - while a>0: - tempbuffer.append(sock.recv(min(a,512)).decode('utf-8')) - a=a-min(a,512) - break - else: - break - mecho('recv end\n===') - body = ''.join(tempbuffer) - mecho(header) - mecho('---') - if len(body)>250: - mecho(body[:100]) - mecho('...\n') - mecho(body[-100:]) - else: - mecho(body) - if True: - header,body,funcAfter=mainparse(header,body) - mecho('===\nsend start\n') - sock.send(header) - sock.send(body) - mecho('\nsend end\n===') - sock.close() - mecho('Connection from %s:%s closed.' % addr) - funcAfter() - -if __name__ == '__main__': - out = threading.Thread(target=httpserver) - out.start() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('127.0.0.1', 80)) - s.listen(500) - sysecho('Waiting for connection...') - os.popen('explorer http://127.0.0.1:7945/drawMapGUI.html') - while True: - sock, addr = s.accept() - t = threading.Thread(target=tcplink, args=(sock, addr)) - t.start() \ No newline at end of file diff --git a/_server/fsTest_cs.html b/_server/fsTest_cs.html new file mode 100644 index 00000000..0ae73da0 --- /dev/null +++ b/_server/fsTest_cs.html @@ -0,0 +1,126 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/element.md b/docs/element.md index 228551ac..668c0c2d 100644 --- a/docs/element.md +++ b/docs/element.md @@ -18,6 +18,8 @@ 如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](personalization#自定义道具效果)的说明。 +**有关轻按,在data.js的系统变量中有定义。如果`enableGentleClick`为true,则鼠标(触摸屏)通过双击勇士,键盘通过空格可达到轻按效果,即不向前移动而获得前方物品。** + ## 门 本塔支持6种门,黄蓝红绿铁花。前五种门需要有对应的钥匙打开,花门只能通过调用`openDoor`事件进行打开。 @@ -31,9 +33,41 @@ 本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。 如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。 -怪物可以由特殊属性,每个怪物最多只能有一个特殊属性。怪物的特殊属性所对应的数字(special)在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。 +怪物可以有特殊属性,每个怪物可以有多个自定义属性。 -![怪物属性](./img/getspecialtext.png) +怪物的特殊属性所对应的数字(special)在下面的`getSpecialText`中定义,请勿对已有的属性进行修改。 + +``` js +enemys.prototype.getSpecialText = function (enemyId) { + if (enemyId == undefined) return ""; + var special = this.enemys[enemyId].special; + var text = []; + if (this.hasSpecial(special, 1)) text.push("先攻"); + if (this.hasSpecial(special, 2)) text.push("魔攻"); + if (this.hasSpecial(special, 3)) text.push("坚固"); + if (this.hasSpecial(special, 4)) text.push("2连击"); + if (this.hasSpecial(special, 5)) text.push("3连击"); + if (this.hasSpecial(special, 6)) text.push("4连击"); + if (this.hasSpecial(special, 7)) text.push("破甲"); + if (this.hasSpecial(special, 8)) text.push("反击"); + if (this.hasSpecial(special, 9)) text.push("净化"); + if (this.hasSpecial(special, 10)) text.push("模仿"); + if (this.hasSpecial(special, 11)) text.push("吸血"); + if (this.hasSpecial(special, 12)) text.push("中毒"); + if (this.hasSpecial(special, 13)) text.push("衰弱"); + if (this.hasSpecial(special, 14)) text.push("诅咒"); + if (this.hasSpecial(special, 15)) text.push("领域"); + if (this.hasSpecial(special, 16)) text.push("夹击"); + if (this.hasSpecial(special, 17)) text.push("仇恨"); + return text.join(" "); +} +``` + +如果需要双属性,则采用100x+y的写法。例如 103 则视为同时拥有1和3的属性,即先攻且坚固。同理1314为衰弱诅咒双属性。 + +如果需要三属性,则采用10000x+100y+z的写法。例如71116视为同时拥有7,11和16的属性,即破甲、吸血、夹击。 + +四个乃至更多的属性以此类推。 怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。 @@ -46,6 +80,7 @@ ![怪物吸血](./img/blood.png) 中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 + 衰弱怪让勇士衰弱后,攻防会暂时下降一定的数值(直到衰弱状态解除恢复);这个下降的数值同在`data.js`中的values定义。 ![debuff](./img/debuff.png) @@ -54,17 +89,11 @@ 领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 -![怪物属性](./img/domainenemy.png) +![怪物领域](./img/domainenemy.png) -出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。 +请注意如果吸血和领域同时存在,则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。** -![检查夹击和领域](./img/checkblock.png) - -!> **请注意这里的`"x,y"`代表该点的横坐标为x,纵坐标为y;即从左到右第x列,从上到下的第y行(从0开始计算)。** - -本塔不支持阻击、激光、仇恨、自爆、退化等属性。 - -(这些属性基本都太恶心了,恶心到都不想加上去) +本塔暂不支持阻击、激光、自爆、退化等属性。 如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 diff --git a/docs/event.md b/docs/event.md index a64a5d09..37599924 100644 --- a/docs/event.md +++ b/docs/event.md @@ -215,6 +215,18 @@ ![调试](./img/eventdebug.png) +### tip:显示一段提示文字 + +`{"type": "tip"}`可以在左上角显示一段提示文字。 + +``` js +"x,y": [ // 实际执行的事件列表 + {"type": "tip", "text": "这段话将在左上角以气泡形式显示"} +] +``` + +值得注意的是,提示的text内容是可以使用`${ }`来计算表达式的值的。 + ### setValue:设置勇士的某个属性、道具个数,或某个变量/Flag的值 `{"type": "setValue"}` 能修改勇士的某个属性、道具个数、或某个自定义变量或`Flag`的值。 @@ -245,6 +257,7 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam ] ``` +另外注意一点的是,如果hp被设置成了0或以下,将触发lose事件,直接死亡。 ### show: 将一个禁用事件启用 @@ -392,8 +405,8 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "openDoor", "loc": [3,6], "floorId": "MT1"}, // 打开MT1层的[3,6]位置的门 - {"type": "openDoor", "loc": [3,6]}, // 如果是本层则可省略floorId + {"type": "openDoor", "loc": [3,6], "floorId": "MT1"}, // 打开MT1层的[3,6]位置的门 + {"type": "openDoor", "loc": [3,6]}, // 如果是本层则可省略floorId ] ``` @@ -426,6 +439,8 @@ direction为可选的,指定的话将使勇士的朝向变成该方向 time为可选的,指定的话将作为楼层切换动画的时间。 +!> **changeFloor到达一个新的楼层,将不会执行firstArrive事件!如有需求请在到达点设置自定义事件,然后使用type: trigger立刻调用之。** + ### changePos: 当前位置切换/勇士转向 有时候我们不想要楼层切换的动画效果,而是直接让勇士从A点到B点。 @@ -454,14 +469,15 @@ time为可选的,指定的话将作为楼层切换动画的时间。 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "setFg", "color": [255,255,255], "time": 1000}, // 更改画面色调为纯白,动画时间1000毫秒 - {"type": "setFg", "color": [0,0,0]}, // 更改画面色调为纯黑,不指定动画时间(使用默认时间) + {"type": "setFg", "color": [255,255,255,0.6], "time": 1000}, // 更改画面色调为纯白,不透明度0.6,动画时间1000毫秒 + {"type": "setFg", "color": [0,0,0]}, // 更改画面色调为纯黑,不透明度1,不指定动画时间(使用默认时间) {"type": "setFg"} // 如果不指定color则恢复原样。 ] ``` -color为需要更改画面色调的颜色。它是一个三元数组,分别指定目标颜色的R,G,B值。 -- 常见颜色: 纯黑[0,0,0],纯白[255,255,255],纯红[255,255,0],等等。 +color为需要更改画面色调的颜色。它是一个数组,分别指定目标颜色的R,G,B,A值。 +- 常见RGB颜色: 纯黑[0,0,0],纯白[255,255,255],纯红[255,0,0],等等。 +- 第四元为Alpha值,即不透明度,为一个0到1之间的数。可以不指定,则默认为Alpha=1 如果color不指定则恢复原样。 @@ -475,7 +491,7 @@ time为可选的,如果指定,则会作为更改画面色调的时间。 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "move", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + {"type": "move", "time": 750, "loc": [x,y], "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),loc为位置可选,steps为移动数组 {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 ], "immediateHide": true }, //immediateHide可选,制定为true则立刻消失,否则渐变消失 @@ -484,13 +500,15 @@ time为可选的,如果指定,则会作为更改画面色调的时间。 time选项必须指定,为每移动一步所需要用到的时间。 +loc为需要移动的事件位置。可以省略,如果省略则移动本事件。 + steps为一个数组,其每一项为一个 `{"direction" : xxx, "value": n}`,表示该步是向xxx方向移动n步。 如果只移动一步可以直接简单的写方向字符串(`up/left/down/right`)。 immediateHide为一个可选项,代表该事件移动完毕后是否立刻消失。如果该项指定了并为true,则移动完毕后直接消失,否则以动画效果消失。 -值得注意的是,当调用move事件时,实际上是使事件脱离了原始地点。为了避免冲突,规定:move事件会自动调用hide事件。 +值得注意的是,当调用move事件时,实际上是使事件脱离了原始地点。为了避免冲突,规定:move事件会自动调用该点的hide事件。 换句话说,当move事件被调用后,该点本身的事件将被禁用。 @@ -516,6 +534,25 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate 即,在移动的到达点指定一个初始禁用的相同NPC,然后move事件中指定immediateHide使立刻消失,并show该到达点坐标使其立刻显示(看起来就像没有消失),然后就可以触发目标点的事件了。 +### moveHero:移动勇士 + +如果我们需要移动勇士,可以使用`{"type": "moveHero"}`。 + +下面是该事件常见的写法: + +``` js +"x,y": [ // 实际执行的事件列表 + {"type": "moveHero", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 + "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 + ]}, +] +``` + +可以看到,和上面的move事件几乎完全相同,除了不能指定loc,且少了immediateHide选项。 + +不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。 + ### playSound: 播放音效 使用playSound可以立刻播放一个音效。 @@ -524,6 +561,8 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate 值得注意的是,如果是额外添加进文件的音效,则需在main.js中this.sounds里加载它。 +!> 自定义添加的音效名请勿包含`-`,否则将无法正常使用! + 由于考虑到用户流量问题,每个额外音效最好不超过20KB。 ### win: 获得胜利 @@ -600,7 +639,7 @@ choices是一个很麻烦的事件,它将弹出一个列表供用户进行选 其大致写法如下: ``` js -"x, y": [ // 实际执行的事件列表 +"x,y": [ // 实际执行的事件列表 {"type": "choices", "text": "...", // 提示文字 "choices": [ {"text": "选项1文字", "action": [ diff --git a/docs/img/mapgui.png b/docs/img/mapgui.png new file mode 100644 index 00000000..d66c73ef Binary files /dev/null and b/docs/img/mapgui.png differ diff --git a/docs/img/ps.png b/docs/img/ps.png new file mode 100644 index 00000000..9e0837e3 Binary files /dev/null and b/docs/img/ps.png differ diff --git a/docs/img/server.png b/docs/img/server.png new file mode 100644 index 00000000..878f6d47 Binary files /dev/null and b/docs/img/server.png differ diff --git a/docs/personalization.md b/docs/personalization.md index f388de04..3fd37899 100644 --- a/docs/personalization.md +++ b/docs/personalization.md @@ -6,99 +6,100 @@ 所有素材的图片都在`images`目录下。 - `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。 -- `enemys.png` 为所有怪物的图片。地图生成器中对应的数字,从上至下依次是会从201开始计算(即,绿色史莱姆为201,小蝙蝠为205,依次类推)。请注意,动画效果为两帧,一般是原始四帧中的1和3。(四帧中12相同,34相同,因此只取1和3即可) -- `heros.png`为勇士行走图。 +- `autotile.png` 为Autotile块。全塔只支持一种Autotile,其ID为20。 +- `enemys.png` 为所有怪物的图片。其对应的数字,从上至下依次是会从201开始计算(即,绿色史莱姆为201,小蝙蝠为205,依次类推)。请注意,动画效果为两帧,一般是原始四帧中的1和3。(四帧中12相同,34相同,因此只取1和3即可) +- `heros.png` 为勇士行走图。 - `items.png` 为所有道具的图标。 - `npcs.png` 为所有NPC的图标,也是两帧。 - `terrains.png` 为所有地形的图标。 系统会读取`icon.js`文件,并获取每个ID对应的图标所在的位置。 -### 地图生成器使用自定义素材 +### 使用预定义的素材 -地图生成器可以将数字和图标一一对应,从数字生成图标或从图标生成数字。 +在images目录的“默认素材”下给定了若干预定义的自定义素材。包括野外(草地),星空,木板等等都已经被预先给定。 -在使用自定义素材后,我们可以使用地图生成器来识别新的素材。打开同目录下的`meaning.txt`,按照已有的方式来增加或编辑内容即可。 +如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。 -第一列是地图生成器中的数字,第二列是它所在的文件名,第三列是坐标。 +### 使用便捷PS工具生成素材 -![地图含义](./img/mapmean.png) +如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。 -### 使用自定义地形(路面、墙壁等) +![便捷PS工具](img/ps.png) -你需要自行P一张图,里面是你需要的地形素材。然后将其覆盖`terrains.png`文件,请注意所有元件的位置需要完全相同对应。 +我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。 -岩浆、星空、传送门、箭头、门的开启动画、暗墙的开启动画在`animates.png`中,可能也需要对应进行修改。 +用这种方式,我们能极快地替换或素材,包括需要新增的怪物。 -请打开`items.js`中,对比素材的位置。 +### 添加素材到游戏 -另外需要注意的一点是,本样板不支持`Autotile`,换句话说野外地图(草地等)这种自然风可能不会太适合。 +在使用地图编辑器编辑的过程中,我们有可能会出现“该数字和ID未被定义”的错误提示。 -### 使用自定义道具图标 +这是因为,该素材没有被定义,无法被游戏所识别。 -如果你觉得已有的道具图标不够,想自己添加图标也是可以的。 +#### 新添加自定义地形(路面、墙壁等) -如果`items.png`中不存在你需要的图标,则可以自己P一张图,将你需要的图标覆盖到某个用不到的图标上。或者也可以接着后面向下拉伸。 +如果你在terrains.png中新增了一行: -所有道具必须是`32x32`像素。 +1. 指定一个唯一的英文ID,不能和terrains中现有的重复。 +2. 进入icons.js,在terrains分类下进行添加(对应图标在图片上的位置,即index) +3. 指定一个数字,在maps.js的getBlock下类似进行添加。 +``` js +if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网 +if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网 +if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水 +// 可以在此处类似添加数字-ID对应关系,但不能和任何已有的数字重复 +// autotile: 20 +if (id == 20) tmp.event = {'cls': 'autotile', 'id': 'autotile', 'noPass': true}; // autotile +``` -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 +#### 新添加道具 -要在系统中启用你的图标,你需要自己指定一个道具的ID(不能和任何已有的重名),然后进行如下操作: -1. 在`items.js`中道具的定义列表中编辑,修改你的自定义道具的名称,类型,说明文字等。 - - 即捡即用类道具的cls为items,消耗类道具的cls为tools,永久类道具的cls为constants。 -2. 在`icon.js`中,找到items一栏,往里面添加你的图标位置。 -3. 在`maps.js`中,找到对应位置,往里面添加自己的数字和道具的一一对应关系。(该数字可任意指定,不能和已有的冲突),类似下面这样 +如果你需要新增一个未被定义的道具: + +1. 指定一个唯一的英文ID,不能和items中现有的重复。 +2. 进入icons.js,在items分类下进行添加(对应图标在图片上的位置,即index) +3. 指定一个数字,在maps.js的getBlock下类似进行添加。 +4. 在items.js中仿照其他道具,来添加道具的信息。 ``` js if (id == 63) tmp.event = {'cls': 'items', 'id': 'moneyPocket'} // 金钱袋 if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋 if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤 -// 可以在这里添加自己的数字-道具对应关系 -``` +// 可以在这里添加自己的数字-ID对应关系,但不能和任何已有的数字重复 +``` 有关如何自行实现一个道具的效果,参见[自定义道具效果](#自定义道具效果)。 -### 使用自定义怪物图标 +#### 新添加怪物 -如果你觉得已有的怪物图标不够,也可以自行向后添加你需要的怪物。 +如果我们需要新添加怪物,请在enemys.png中新增一行,然后复制粘贴上四帧怪物图的**1和3帧**。 -在`enemys.js`中,直接向后P图,并将你需要的怪物的两帧动画放到正确的位置(注意:普通四帧动画的1和3帧)。 +然后执行如下操作: -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 +1. 指定一个唯一的英文ID,不能和enemys中现有的重复。 +2. 进入icons.js,在enemys分类下进行添加(对应图标在图片上的位置,即index) +3. 在maps.js的getBlock下继续进行添加。请注意其ID为200开始的顺序,即如果新增一行为261,依次类推 +4. 在items.js中仿照其他道具,来添加道具的信息。 -要在系统中启用你的图标,你需要自己指定一个怪物的ID(不能和任何已有的重名),然后进行如下操作: - -1. 在`enemys.js`中,向怪物的定义列表中,添加一个怪物。 -2. 在`icon.js`中,找到enemys一栏,往里面添加你的图标位置。 -3. 在`maps.js`中,找到对应位置,往里面添加自己的数字和怪物的一一对应关系。一般对于怪物而言,对应的数字就是`200+`位置。 +``` js +if (id == 258) tmp.event = {'cls': 'enemys', 'id': 'octopus'}; +if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'fairy'}; +if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'}; +// 在此依次添加,数字要求是递增的 +``` 有关如何自行实现一个怪物的特殊属性或伤害计算公式,参见[怪物的特殊属性](#怪物的特殊属性)。 -### 使用自定义NPC图标 +#### 新添加NPC -同上,你也可以自定义NPC图标。只需要将你NPC的两帧动画接到`npcs.js`中即可。 +类似同上,给NPC指定ID,在icons.js中指定ID-index关系,在maps.js中指定ID-数字的关系,即可。 -P图完毕后,可以在地图生成器中加入对应的数字和图标的对应关系。 +### 地图生成器使用自定义素材 -要在系统中启用你的图标,你需要自己指定一个NPC的ID(不能和任何已有的重名),然后进行如下操作: +地图生成器是直接从js文件中读取数字-图标对应关系的。 -1. 在`icon.js`中,找到npcs一栏,往里面添加你的图标位置。 -2. 在`maps.js`中,找到对应位置,往里面添加自己的数字和NPC的一一对应关系。 - -### 使用自定义勇士图标 - -我们同样可以使用自定义的勇士图标,比如小可绒之类。 - -直接拿一个勇士的行走图覆盖`hero.png`即可。 - -**支持任何行走大图,如`32x32`, `48x32`, `64x32`等等。** - -  - -通过上述这几种方式,我们可以修改素材图片(使用自定义素材),指定数字并放入地图生成器中,然后在系统中进行启用。 - -!> **请注意:除了勇士行走图外,其他所有素材强制要求必须是`32x32`的,不然可能会造成不可预料的后果。** +因此,在你修改了icons.js和maps.js两个文件,也就是将素材添加到游戏后,地图生成器的对应关系也将同步更新。 ## 自定义道具效果 @@ -167,24 +168,26 @@ if (itemId === 'shield5') { ``` js enemys.prototype.getExtraDamage = function (monster) { var extra_damage = 0; - if (monster.special == 11) { // 吸血 + if (this.hasSpecial(monster.special, 11)) { // 吸血 // 吸血的比例 extra_damage = core.status.hero.hp * monster.value; if (core.hasFlag("shield5")) extra_damage = 0; // 如果存在神圣盾,则免疫吸血 extra_damage = parseInt(extra_damage); } - return extra_damage; -} +// ... 下略 ``` -3. 免疫领域、夹击效果:在`events.js`中,找到checkBlock函数,并编辑成如果有神圣盾标记,则直接返回。 +3. 免疫领域、夹击效果:在`core.js`中,找到updateCheckBlock函数,并编辑成如果有神圣盾标记,则直接返回。 ``` js -////// 检查领域、夹击事件 ////// -events.prototype.checkBlock = function (x,y) { - if (core.hasFlag("shield5")) return; // 如果拥有神圣盾立刻返回,免疫领域和夹击 - var damage = 0; - // 获得四个方向的怪物 - var directions = [[0,-1],[-1,0],[0,1],[1,0]]; // 上,左,下,右 - var enemys = [null,null,null,null]; +// 更新领域、显伤点 +core.prototype.updateCheckBlock = function() { + if (!core.isset(core.status.thisMap)) return; + if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); + core.status.checkBlock = []; + if (core.hasItem('shield5')) return; // 如拥有神圣盾则直接返回 + for (var x=0;x<13;x++) { + for (var y=0;y<13;y++) { + // 计算(x,y)点伤害 + var damage = 0; // ... 下略 ``` 4. 如果有更高的需求,例如想让吸血效果变成一半(如异空间),则还是在上面这些地方进行对应的修改即可。 @@ -199,15 +202,28 @@ events.prototype.checkBlock = function (x,y) { 因此无敌属性可以这样设置: -``` js -// 我们需要对无敌属性指定一个数字,比如18 +先给无敌属性指定一个数字,例如18,在getSpecialText中定义 +``` js +// ... 上略 + if (this.hasSpecial(special, 15)) text.push("领域"); + if (this.hasSpecial(special, 16)) text.push("夹击"); + if (this.hasSpecial(special, 17)) text.push("仇恨"); + if (this.hasSpecial(special, 18)) text.push("无敌"); // 添加无敌的显示 + return text.join(" "); +} + +``` + +然后修改calDamage,如果无敌属性且勇士没有拥有十字架,则立刻返回无穷大。 + +``` js enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) { - if (mon_special==18 && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 + if (this.hasSpecial(mon_special, 18) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 return 999999999; // 返回无限大 // 魔攻 - if (mon_special == 2) hero_def = 0; + if (this.hasSpecial(mon_special, 2)) hero_def = 0; // ... 下略 ``` diff --git a/docs/start.md b/docs/start.md index 0e9f64f6..127fd819 100644 --- a/docs/start.md +++ b/docs/start.md @@ -6,14 +6,26 @@ 你需要有满足如下条件才能进行制作: -- Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“地图生成器.exe”即可) +- Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) - 任一款现代浏览器。强烈推荐Chrome。 - 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 ([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) -- RPG Maker XP,任一个魔塔样板(推荐魔塔样板7630) 只要满足了上述条件,你就可以开始做自己的塔啦! +## 启动HTTP服务 + +在根目录下有一个“启动服务.exe”,运行之。 + +![启动服务](img/server.png) + +* “启动游戏”按钮将打开一个网页,你能在里面看到现在游戏的效果。 +* “地图编辑器”允许你以可视化的方式进行编辑地图。 +* “便捷PS工具”能让你很方便的对自定义素材进行添加。参见[自定义素材](personalization#自定义素材)。 +* “地图生成器”能让你从已有的截图(如RMXP项目)中立刻生成可被本样板识别的地图数据。 +* “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。 +* “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。 + ## 新建剧本 类似于RMXP,本塔每层楼都是一个“剧本”,剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本,并生成实际的地图供游戏使用。 @@ -30,43 +42,48 @@ ## 绘制地图 -遗憾的是,我们的样板是没有像RMXP那样有着很好的UI界面,供大家直接进行绘图可视化操作的。然而,我们仍然可以利用已有的RMXP和魔塔样板,绘制好地图,然后利用目录中的“地图生成器”来转成样板所识别的格式。 +有两种绘制地图的方式:从头绘制地图;从RMXP中导入已有的地图。 -首先,我们打开RMXP和魔塔样板,来到绘制地图页面。 +### 从头绘制地图 -![绘制地图](./img/rmxp1.png) +我们直接打开“地图编辑器”,可以看到一个可视化的UI界面。 -然后,任意绘制一张地图。 +![地图编辑器](img/mapgui.png) -在这里我们就以1层小塔的地图为例。你也可以任意绘制自己的地图。 +然后可以在上面任意进行绘制地图。 + +!> **如果地图的数字和ID未被定义,则会进行提示:数字和ID未被定义!此时可能需要手动在icons.js和maps.js中定义对应的数字和ID。请参见[自定义素材](personalization#自定义素材)。** + +绘制地图完毕后,点击"导出地图",即可在左边看到对应的JSON数组,并且已经复制到了剪切板。将其粘贴到剧本中的map位置即可。 + +![地图数组](./img/maparray.png) + +### 从RMXP导入已有的地图 + +如果我们想复刻一个现有的,已经被RMXP所制作的塔,也有很便捷的方式,那就是用到我们的“地图生成器”。 + +首先,我们打开RMXP和对应的项目,可以看到它的地图。 ![绘制地图](./img/rmxp2.png) -(我把原塔素材改成了经典样式,但是本质上是一样的。) - -请注意,我们无需编辑任何事件。只需要让地图“看起来长成这个样子”就行。 - -当绘制好需要的地图后,我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 +我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 ![绘制地图](./img/rmxp3.png) 截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是13x13。(如果地图小于13*13,请用星空或墙壁填充到13x13)。** -确认地图的图片文件已经复制到剪切板后,我们打开工具中的“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 +确认地图的图片文件已经复制到剪切板后,我们打开“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 ![生成地图](./img/map1.png) -如果有识别不一致的存在,即生成的地图和实际的地图不符,则需要在左边的输入框内实际手动修改,然后再点“图片生成”即可。有关每个数字对应的图块名称,请参见images目录下的`meaning.txt` +然后点击“复制地图”,即可将地图数据复制到剪切板。 + +!> **如果有识别不一致的存在,即生成的地图和实际的地图不符,我们可以在地图编辑器中粘贴,再可视化进行编辑。** !> **地图生成器默认只支持经典素材。如果有自定义素材需求(例如原版的1层小塔那种素材),请参见[自定义素材](personalization#自定义素材)。** !> **请确保截图范围刚好为13x13,并且保证每个位置的像素都是32x32。** -经过确认,生成的地图和原始地图保持一致后,点击“复制地图”,然后粘贴到刚刚剧本文件里的maps中。 - -![地图数组](./img/maparray.png) - -通过这种在RMXP中画图,截图复制,再用地图生成器识别的方式,我们成功将我们需要的地图变成了样板可识别的格式。 ## 录入数据 diff --git a/drawMapGUI.html b/drawMapGUI.html index 4f8b2686..cf4e9356 100644 --- a/drawMapGUI.html +++ b/drawMapGUI.html @@ -134,9 +134,9 @@ padding: 0 5px; display: inline-block; - font-size: 11px; + font-size: 14px; font-weight: 400; - text-transform: uppercase; + /* text-transform: uppercase; */ letter-spacing: 0; overflow: hidden; cursor: pointer; @@ -274,7 +274,7 @@

{{ errors[error-1] }}

- +
@@ -299,8 +299,8 @@

{{ mapMsg }}

- - + +
@@ -795,7 +795,6 @@ 'size': 32 }; pos = locToPos(loc); - for (var spriter in widthsX){ if(pos.x>=widthsX[spriter][1] && pos.x0) + deepAdd = core.status.checkBlock[nid]; if (nx == destX && ny == destY) { route[nid] = direction; @@ -1049,86 +1102,29 @@ core.prototype.setHeroMoveInterval = function (direction, x, y, callback) { } core.status.heroMoving = true; var moveStep = 0; + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; core.interval.heroMoveInterval = window.setInterval(function () { - switch (direction) { - case 'up': - moveStep -= 4; - if (moveStep == -4 || moveStep == -8 || moveStep == -12 || moveStep == -16) { - core.drawHero(direction, x, y, 'leftFoot', 0, moveStep); - } - else if (moveStep == -20 || moveStep == -24 || moveStep == -28 || moveStep == -32) { - core.drawHero(direction, x, y, 'rightFoot', 0, moveStep); - } - if (moveStep == -32) { - core.setHeroLoc('y', '--'); - core.moveOneStep(); - if (core.status.heroStop) { - core.drawHero(direction, x, y - 1, 'stop'); - } - if (core.isset(callback)) { - callback(); - } - } - break; - case 'left': - moveStep -= 4; - if (moveStep == -4 || moveStep == -8 || moveStep == -12 || moveStep == -16) { - core.drawHero(direction, x, y, 'leftFoot', moveStep); - } - else if (moveStep == -20 || moveStep == -24 || moveStep == -28 || moveStep == -32) { - core.drawHero(direction, x, y, 'rightFoot', moveStep); - } - if (moveStep == -32) { - core.setHeroLoc('x', '--'); - core.moveOneStep(); - if (core.status.heroStop) { - core.drawHero(direction, x - 1, y, 'stop'); - } - if (core.isset(callback)) { - callback(); - } - } - break; - case 'down': - moveStep+=4; - if(moveStep == 4 || moveStep == 8 || moveStep == 12 || moveStep == 16) { - core.drawHero(direction, x, y, 'leftFoot', 0, moveStep); - } - else if(moveStep == 20 || moveStep == 24 ||moveStep == 28 || moveStep == 32) { - core.drawHero(direction, x, y, 'rightFoot', 0, moveStep); - } - if (moveStep == 32) { - core.setHeroLoc('y', '++'); - core.moveOneStep(); - if (core.status.heroStop) { - core.drawHero(direction, x, y + 1, 'stop'); - } - if (core.isset(callback)) { - callback(); - } - } - break; - case 'right': - moveStep+=4; - if(moveStep == 4 || moveStep == 8 || moveStep == 12 || moveStep == 16) { - core.drawHero(direction, x, y, 'leftFoot', moveStep); - } - else if(moveStep == 20 || moveStep == 24 ||moveStep == 28 || moveStep == 32) { - core.drawHero(direction, x, y, 'rightFoot', moveStep); - } - if (moveStep == 32) { - core.setHeroLoc('x', '++'); - core.moveOneStep(); - if (core.status.heroStop) { - core.drawHero(direction, x + 1, y, 'stop'); - } - if (core.isset(callback)) { - callback(); - } - } - break; + moveStep++; + if (moveStep<=4) { + core.drawHero(direction, x, y, 'leftFoot', 4*moveStep*scan[direction].x, 4*moveStep*scan[direction].y); } - }, 10); + else if (moveStep<=8) { + core.drawHero(direction, x, y, 'rightFoot', 4*moveStep*scan[direction].x, 4*moveStep*scan[direction].y); + } + if (moveStep==8) { + core.setHeroLoc('x', x+scan[direction].x); + core.setHeroLoc('y', y+scan[direction].y); + core.moveOneStep(); + if (core.status.heroStop) + core.drawHero(direction, core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); + if (core.isset(callback)) callback(); + } + }, 12.5); } core.prototype.setHeroMoveTriggerInterval = function () { @@ -1179,6 +1175,7 @@ core.prototype.setHeroMoveTriggerInterval = function () { core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); } core.trigger(core.getHeroLoc('x'), core.getHeroLoc('y')); + core.checkBlock(); clearInterval(core.interval.heroMoveInterval); core.status.heroMoving = false; }); @@ -1242,6 +1239,66 @@ core.prototype.moveHero = function (direction) { core.status.heroStop = false; } +core.prototype.eventMoveHero = function(steps, time, callback) { + + time = time || 100; + + core.clearMap('ui', 0, 0, 416, 416); + core.setAlpha('ui', 1.0); + + // 要运行的轨迹:将steps展开 + var moveSteps=[]; + steps.forEach(function (e) { + if (typeof e=="string") { + moveSteps.push(e); + } + else { + if (!core.isset(e.value)) { + moveSteps.push(e.direction) + } + else { + for (var i=0;i= core.values.animateSpeed * 2 / animateValue) { + animateCurrent++; + animateTime = 0; + if (animateCurrent>=animateValue) animateCurrent=0; + } + // 已经移动完毕,消失 if (moveSteps.length==0) { if (immediateHide) opacityVal=0; else opacityVal -= 0.06; core.setOpacity('data', opacityVal); core.clearMap('data', nowX, nowY, 32, 32); - core.canvas.data.drawImage(blockImage, 0, blockIcon * 32, 32, 32, nowX, nowY, 32, 32); + core.canvas.data.drawImage(blockImage, animateCurrent * 32, blockIcon * 32, 32, 32, nowX, nowY, 32, 32); if (opacityVal<=0) { clearInterval(animate); core.loadCanvas('data'); @@ -1900,7 +1972,7 @@ core.prototype.moveBlock = function(x,y,steps,time,immediateHide,callback) { nowY+=scan[moveSteps[0]].y*2; core.clearMap('data', nowX-32, nowY-32, 96, 96); // 绘制 - core.canvas.data.drawImage(blockImage, 0, blockIcon * 32, 32, 32, nowX, nowY, 32, 32); + core.canvas.data.drawImage(blockImage, animateCurrent * 32, blockIcon * 32, 32, 32, nowX, nowY, 32, 32); if (step==16) { // 该移动完毕,继续 step=0; @@ -1961,6 +2033,7 @@ core.prototype.addBlock = function(x,y,floodId) { // 本身是禁用事件,启用之 if (core.isset(block.enable) && !block.enable) { block.enable = true; + core.updateCheckBlockMap(); // 在本层,添加动画 if (floodId == core.status.floorId && core.isset(block.event)) { blockIcon = core.material.icons[block.event.cls][block.event.id]; @@ -1997,24 +2070,22 @@ core.prototype.removeBlockById = function (index, floorId) { var blocks = core.status.maps[floorId].blocks; var x=blocks[index].x, y=blocks[index].y; - // 检查该点是否是checkBlock - if (core.floors[floorId].checkBlock.indexOf(x+","+y)>=0) { - blocks[index] = {'x': x, 'y': y, 'event': {'cls': 'terrains', 'id': 'ground', 'noPass': false, 'trigger': 'checkBlock'}}; - return; - } - // 检查该点是否存在事件 var event = core.floors[floorId].events[x+","+y]; if (!core.isset(event)) event = core.floors[floorId].changeFloor[x+","+y]; + var shouldUpdateBlockMap = blocks[index].event.cls == 'enemys'; // 不存在事件,直接删除 if (!core.isset(event)) { blocks.splice(index,1); + if (shouldUpdateBlockMap) + core.updateCheckBlockMap(); return; } - blocks[index].enable = false; + if (shouldUpdateBlockMap) + core.updateCheckBlockMap(); } core.prototype.removeBlockByIds = function (floorId, ids) { @@ -2113,32 +2184,154 @@ core.prototype.drawBoxAnimate = function (background) { } } +core.prototype.updateCheckBlockMap = function() { + // 更新领域、夹击地图 + core.status.checkBlockMap = []; + var blocks = core.status.thisMap.blocks; + for (var n=0;n12 || ny<0 || ny>12) return; + if (core.status.checkBlockMap[13*nx+ny]%1000000>0) { + damage += core.status.checkBlockMap[13*nx+ny] % 1000000; + } + }) + + var leftValue = core.status.hero.hp - damage; + if (leftValue>1) { + var has = false; + // 夹击 + if (x>0 && x<12) { + var id1=parseInt(core.status.checkBlockMap[13*(x-1)+y]/1000000), + id2=parseInt(core.status.checkBlockMap[13*(x+1)+y]/1000000); + if (id1>0 && id1==id2) + has = true; + } + if (y>0 && y<12) { + var id1=parseInt(core.status.checkBlockMap[13*x+y-1]/1000000), + id2=parseInt(core.status.checkBlockMap[13*x+y+1]/1000000); + if (id1>0 && id1==id2) + has = true; + } + if (has) { + damage += parseInt((leftValue+1) / 2); + } + } + } + core.status.checkBlock[13*x+y] = damage; + } + } +} + +core.prototype.checkBlock = function () { + // 检查领域、夹击事件 + var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); + if (core.status.checkBlock[13*x+y]>0) { + core.status.hero.hp -= core.status.checkBlock[13*x+y]; + if (core.hasBetweenAttack(x,y)) { + core.drawTip('受到夹击,生命变成一半'); + } + else if (core.hasZone(x,y)) { + core.drawTip('受到领域伤害'+core.status.checkBlock[13*x+y]+'点'); + } + if (core.status.hero.hp<=0) { + core.status.hero.hp=0; + core.updateStatusBar(); + core.events.lose('zone'); + return; + } + core.updateStatusBar(); + } +} + +core.prototype.hasZone = function (x,y) { + if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); + var isZone = false; + // 领域 + [[-1,0],[0,-1],[1,0],[0,1]].forEach(function (dir) { + var nx = x+dir[0], ny = y+dir[1]; + if (nx<0 || nx>12 || ny<0 || ny>12) return; + if (core.status.checkBlockMap[13*nx+ny]%1000000>0) { + isZone = true; + } + }) + return isZone; +} + +core.prototype.hasBetweenAttack = function(x,y) { + if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); + // 夹击 + if (x>0 && x<12) { + var id1=parseInt(core.status.checkBlockMap[13*(x-1)+y]/1000000), + id2=parseInt(core.status.checkBlockMap[13*(x+1)+y]/1000000); + if (id1>0 && id1==id2) + return true; + } + if (y>0 && y<12) { + var id1=parseInt(core.status.checkBlockMap[13*x+y-1]/1000000), + id2=parseInt(core.status.checkBlockMap[13*x+y+1]/1000000); + if (id1>0 && id1==id2) + return true; + } +} + core.prototype.setFg = function(color, time, callback) { time = time || 750; core.setOpacity('fg', 1); - var fromAlpha = 0; - if (core.isset(core.status.event.data.currentColor)) { - fromAlpha = 1; - } - else { - core.status.event.data.currentColor = [0,0,0]; + var reset = false; + + if (!core.isset(core.status.event.data.currentColor)) { + core.status.event.data.currentColor = [0,0,0,0]; } var fromColor = core.status.event.data.currentColor; - var toAlpha = 1; if (!core.isset(color)) { - color = [0,0,0]; - toAlpha=0; + color = [0,0,0,0]; + reset = true; } + if (color.length==3) { + color.push(1); + } + if (color[3]<0) color[3]=0; + if (color[3]>1) color[3]=1; var step=0; var changeAnimate = setInterval(function() { step++; core.clearMap('fg', 0, 0, 416, 416); - var nowAlpha = fromAlpha+(toAlpha-fromAlpha)*step/25; + var nowAlpha = fromColor[3]+(color[3]-fromColor[3])*step/25; var nowR = parseInt(fromColor[0]+(color[0]-fromColor[0])*step/25); var nowG = parseInt(fromColor[1]+(color[1]-fromColor[1])*step/25); var nowB = parseInt(fromColor[2]+(color[2]-fromColor[2])*step/25); @@ -2147,12 +2340,12 @@ core.prototype.setFg = function(color, time, callback) { if (nowB<0) nowB=0; if (nowB>255) nowB=255; core.setAlpha('fg', nowAlpha); - var toRGB = "#"+nowR.toString(16)+nowG.toString(16)+nowB.toString(16); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1) core.fillRect('fg', 0, 0, 416, 416, toRGB); if (step>=25) { clearInterval(changeAnimate); - if (toAlpha==0) { + if (reset) { core.clearMap('fg', 0, 0, 416, 416); delete core.status.event.data.currentColor; core.setAlpha('fg', 1); @@ -2211,7 +2404,8 @@ core.prototype.updateFg = function () { // 如果存在颜色 if (core.isset(core.status.event.data) && core.isset(core.status.event.data.currentColor)) { var color=core.status.event.data.currentColor; - core.fillRect("fg",0,0,416,416,"#"+color[0].toString(16)+color[1].toString(16)+color[2].toString(16)); + core.setAlpha('fg', color[3]); + core.fillRect("fg",0,0,416,416,"#"+((1<<24)+(color[0]<<16)+(color[1]<<8)+color[2]).toString(16).slice(1)); return; } @@ -2223,32 +2417,54 @@ core.prototype.updateFg = function () { if (!core.hasItem('book')) return; core.setFont('fg', "bold 11px Arial"); var hero_hp = core.status.hero.hp; - for (var b = 0; b < mapBlocks.length; b++) { - var x = mapBlocks[b].x, y = mapBlocks[b].y; - if (core.isset(mapBlocks[b].event) && mapBlocks[b].event.cls == 'enemys' && mapBlocks[b].event.trigger=='battle' + if (core.flags.displayEnemyDamage) { + core.canvas.fg.textAlign = 'left'; + for (var b = 0; b < mapBlocks.length; b++) { + var x = mapBlocks[b].x, y = mapBlocks[b].y; + if (core.isset(mapBlocks[b].event) && mapBlocks[b].event.cls == 'enemys' && mapBlocks[b].event.trigger == 'battle' && !(core.isset(mapBlocks[b].enable) && !mapBlocks[b].enable)) { - var id = mapBlocks[b].event.id; + var id = mapBlocks[b].event.id; - var damage = core.enemys.getDamage(id); - var color = "#000000"; - if (damage <= 0) color = '#00FF00'; - else if (damage < hero_hp / 3) color = '#FFFFFF'; - else if (damage < hero_hp * 2 / 3) color = '#FFFF00'; - else if (damage < hero_hp) color = '#FF7F00'; - else color = '#FF0000'; + var damage = core.enemys.getDamage(id); + var color = "#000000"; + if (damage <= 0) color = '#00FF00'; + else if (damage < hero_hp / 3) color = '#FFFFFF'; + else if (damage < hero_hp * 2 / 3) color = '#FFFF00'; + else if (damage < 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"; - core.setFillStyle('fg', '#000000'); - core.canvas.fg.fillText(damage, 32 * x + 2, 32 * (y + 1) - 2); - core.canvas.fg.fillText(damage, 32 * x, 32 * (y + 1) - 2); - core.canvas.fg.fillText(damage, 32 * x + 2, 32 * (y + 1)); - core.canvas.fg.fillText(damage, 32 * x, 32 * (y + 1)); + core.setFillStyle('fg', '#000000'); + core.canvas.fg.fillText(damage, 32 * x + 2, 32 * (y + 1) - 2); + core.canvas.fg.fillText(damage, 32 * x, 32 * (y + 1) - 2); + core.canvas.fg.fillText(damage, 32 * x + 2, 32 * (y + 1)); + core.canvas.fg.fillText(damage, 32 * x, 32 * (y + 1)); - core.setFillStyle('fg', color); - core.canvas.fg.fillText(damage, 32 * x + 1, 32 * (y + 1) - 1); + core.setFillStyle('fg', color); + core.canvas.fg.fillText(damage, 32 * x + 1, 32 * (y + 1) - 1); + } + } + } + // 如果是领域&夹击 + if (core.flags.displayExtraDamage) { + core.canvas.fg.textAlign = 'center'; + for (var x=0;x<13;x++) { + for (var y=0;y<13;y++) { + var damage = core.status.checkBlock[13*x+y]; + if (damage>0) { + core.setFillStyle('fg', '#000000'); + core.canvas.fg.fillText(damage, 32 * x + 17, 32 * (y + 1) - 13); + core.canvas.fg.fillText(damage, 32 * x + 15, 32 * (y + 1) - 15); + core.canvas.fg.fillText(damage, 32 * x + 17, 32 * (y + 1) - 15); + core.canvas.fg.fillText(damage, 32 * x + 15, 32 * (y + 1) - 13); + + core.setFillStyle('fg', '#FF7F00'); + core.canvas.fg.fillText(damage, 32 * x + 16, 32 * (y + 1) - 14); + } + } } } } @@ -2310,6 +2526,16 @@ core.prototype.addItem = function (itemId, itemNum) { core.status.hero.items[itemCls][itemId] += itemNum; } +core.prototype.getNextItem = function() { + if (!core.status.heroStop || !core.flags.enableGentleClick) return; + var nextX = core.nextX(), nextY = core.nextY(); + var block = core.getBlock(nextX, nextY); + if (block==null) return; + if (block.block.event.trigger=='getItem') { + core.getItem(block.block.event.id, 1, nextX, nextY); + } +} + core.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { // core.getItemAnimate(itemId, itemNum, itemX, itemY); core.playSound('item', 'ogg'); @@ -2337,6 +2563,7 @@ core.prototype.drawTip = function (text, itemIcon) { core.setFont('data', "16px Arial"); core.saveCanvas('data'); core.setOpacity('data', 0); + core.canvas.data.textAlign = 'left'; if (!core.isset(itemIcon)) { textX = 16; textY = 18; @@ -2814,6 +3041,7 @@ core.prototype.saveData = function(dataId) { 'hard': core.status.hard, 'maps': core.maps.save(core.status.maps), 'shops': {}, + 'flags': core.flags, 'version': core.firstData.version, "time": new Date().getTime() }; @@ -2831,7 +3059,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, data.flags, core.maps.load(data.maps)); // load shop times for (var shop in core.status.shops) { @@ -3007,7 +3235,6 @@ core.prototype.updateStatusBar = function () { core.statusBar.weak.innerHTML = core.hasFlag('weak')?"衰":""; core.statusBar.curse.innerHTML = core.hasFlag('curse')?"咒":""; } - core.statusBar.hard.innerHTML = core.status.hard; if (core.hasItem('book')) { @@ -3020,6 +3247,7 @@ core.prototype.updateStatusBar = function () { } else { core.statusBar.image.fly.style.opacity = 0.3; } + core.updateCheckBlock(); core.updateFg(); } diff --git a/libs/data.js b/libs/data.js index f298e063..d91af1c6 100644 --- a/libs/data.js +++ b/libs/data.js @@ -111,12 +111,12 @@ data.prototype.init = function() { 'breakArmor': 0.9, // 破甲的比例(战斗前,怪物附加角色防御的x%作为伤害) 'counterAttack': 0.1, // 反击的比例(战斗时,怪物每回合附加角色攻击的x%作为伤害,无视角色防御) 'purify': 3, // 净化的比例(战斗前,怪物附加勇士魔防的x倍作为伤害) + 'hatred': 2, // 仇恨属性中,每杀死一个怪物获得的仇恨值 /****** 系统相关 ******/ 'animateSpeed': 500, // 动画时间 } // 系统FLAG,在游戏运行中中请不要修改它。 this.flags = { - /****** 角色状态相关 ******/ "enableMDef": true, // 是否涉及勇士的魔防值;如果此项为false,则状态栏不会显示勇士的魔防值 "enableExperience": true, // 是否涉及经验值;如果此项为false,则状态栏和怪物手册均将不会显示经验值 @@ -124,11 +124,14 @@ data.prototype.init = function() { /****** 道具相关 ******/ "flyNearStair": true, // 是否需要在楼梯边使用传送器 "pickaxeFourDirections": true, // 使用破墙镐是否四个方向都破坏;如果false则只破坏面前的墙壁 + "bombFourDirections": true, // 使用炸弹是否四个方向都会炸;如果false则只炸面前的怪物(即和圣锤等价) "bigKeyIsBox": false, // 如果此项为true,则视为钥匙盒,红黄蓝钥匙+1;若为false,则视为大黄门钥匙 - /****** 系统相关 ******/ "startDirectly": false, // 点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面 - "battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中关闭 + "battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中开关 + "displayEnemyDamage": true, // 是否地图怪物显伤;用户可以手动在菜单栏中开关 + "displayExtraDamage": false, // 是否地图高级显伤(领域、夹击等);用户可以手动在菜单栏中开关 + "enableGentleClick": true, // 是否允许轻触(获得面前物品) "portalWithoutTrigger": true, // 经过楼梯、传送门时是否能“穿透”。穿透的意思是,自动寻路得到的的路径中间经过了楼梯,行走时是否触发楼层转换事件 "potionWhileRouting": false, // 寻路算法是否经过血瓶;如果该项为false,则寻路算法会自动尽量绕过血瓶 } diff --git a/libs/enemys.js b/libs/enemys.js index f9e0d716..a5e8b71f 100644 --- a/libs/enemys.js +++ b/libs/enemys.js @@ -63,7 +63,7 @@ enemys.prototype.init = function () { 'poisonZombie': {'name': '绿兽人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 13, 'experience': 0, 'special': 12}, 'magicDragon': {'name': '魔龙', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'octopus': {'name': '血影', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'fairy': {'name': '仙子', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, + 'darkFairy': {'name': '仙子', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'greenKnight': {'name': '强盾骑士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, } } @@ -76,7 +76,7 @@ enemys.prototype.getEnemys = function (enemyId) { } enemys.prototype.hasSpecial = function (special, test) { - return parseInt(special/1000) == test || special % 1000 == test; + return special!=0 && (special%100 == test || this.hasSpecial(parseInt(special/100), test)); } enemys.prototype.getSpecialText = function (enemyId) { @@ -99,6 +99,7 @@ enemys.prototype.getSpecialText = function (enemyId) { if (this.hasSpecial(special, 14)) text.push("诅咒"); if (this.hasSpecial(special, 15)) text.push("领域"); if (this.hasSpecial(special, 16)) text.push("夹击"); + if (this.hasSpecial(special, 17)) text.push("仇恨"); return text.join(" "); } @@ -118,6 +119,9 @@ enemys.prototype.getExtraDamage = function (monster) { 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); + } return extra_damage; } diff --git a/libs/events.js b/libs/events.js index b00e6e0e..fac9f95e 100644 --- a/libs/events.js +++ b/libs/events.js @@ -34,11 +34,6 @@ events.prototype.init = function () { if (core.isset(callback)) callback(); }, - "checkBlock": function (data, core, callback) { - core.events.checkBlock(data.x, data.y); - if (core.isset(callback)) - callback(); - }, "changeLight": function (data, core, callback) { core.events.changeLight(data.x, data.y); if (core.isset(callback)) @@ -112,53 +107,9 @@ events.prototype.lose = function(reason) { }) } - -////// 检查领域、夹击事件 ////// -events.prototype.checkBlock = function (x,y) { - var damage = 0; - // 获得四个方向的怪物 - var directions = [[0,-1],[-1,0],[0,1],[1,0]]; // 上,左,下,右 - var enemys = [null,null,null,null]; - for (var i in directions) { - var block = core.getBlock(x+directions[i][0], y+directions[i][1]); - if (block==null) continue; - // 是怪物 - if (block.block.event.cls=='enemys') - enemys[i]=core.material.enemys[block.block.event.id]; - } - - // 领域 - for (var i in enemys) { - if (enemys[i]!=null && core.enemys.hasSpecial(enemys[i].special, 15)) { - damage+=enemys[i].value; - } - } - if (damage>0) - core.drawTip('受到领域伤害'+damage+'点'); - core.status.hero.hp-=damage; - if (core.status.hero.hp<=0) { - core.status.hero.hp=0; - core.updateStatusBar(); - core.events.lose('zone'); - return; - } - - // 夹击 - var has=false; - if (enemys[0]!=null && enemys[2]!=null && enemys[0].id==enemys[2].id && core.enemys.hasSpecial(enemys[0].special, 16)) - has=true; - if (enemys[1]!=null && enemys[3]!=null && enemys[1].id==enemys[3].id && core.enemys.hasSpecial(enemys[1].special, 16)) - has=true; - if (has && core.status.hero.hp>1) { // 1血夹击不死 - core.status.hero.hp = parseInt(core.status.hero.hp/2); - core.drawTip('受到夹击,生命变成一半'); - } - core.updateStatusBar(); -} - ////// 转换楼层结束的事件 ////// events.prototype.afterChangeFloor = function (floorId) { - if (!core.hasFlag("visited_"+floorId)) { + if (!core.isset(core.status.event.id) && !core.hasFlag("visited_"+floorId)) { this.doEvents(core.floors[floorId].firstArrive); core.setFlag("visited_"+floorId, true); } @@ -212,6 +163,9 @@ events.prototype.doAction = function() { case "text": // 文字/对话 core.ui.drawTextBox(data.data); break; + case "tip": + core.drawTip(core.replaceText(data.text)); + core.events.doAction(); case "show": // 显示 if (core.isset(data.time) && data.time>0 && (!core.isset(data.floorId) || data.floorId==core.status.floorId)) { core.animateBlock(data.loc[0],data.loc[1],'show', data.time, function () { @@ -239,10 +193,19 @@ events.prototype.doAction = function() { else this.doAction(); break; case "move": // 移动事件 + if (core.isset(data.loc)) { + x=data.loc[0]; + y=data.loc[1]; + } core.moveBlock(x,y,data.steps,data.time,data.immediateHide,function() { core.events.doAction(); }) break; + case "moveHero": + core.eventMoveHero(data.steps,data.time,function() { + core.events.doAction(); + }); + break; case "changeFloor": // 楼层转换 var heroLoc = {"x": data.loc[0], "y": data.loc[1]}; if (core.isset(data.direction)) heroLoc.direction=data.direction; @@ -341,8 +304,16 @@ events.prototype.doAction = function() { } } catch (e) {console.log(e)} - core.updateStatusBar(); - this.doAction(); + if (core.status.hero.hp<=0) { + core.status.hero.hp=0; + core.updateStatusBar(); + core.events.lose('damage'); + + } + else { + core.updateStatusBar(); + this.doAction(); + } break; case "if": // 条件判断 if (core.calValue(data.condition)) @@ -442,8 +413,10 @@ events.prototype.disableQuickShop = function (shopId) { } ////// 降低难度 ////// -/* + events.prototype.decreaseHard = function() { + core.drawTip("本塔不支持降低难度!"); + /* if (core.status.hard == 0) { core.drawTip("当前已是难度0,不能再降低难度了"); return; @@ -461,8 +434,8 @@ events.prototype.decreaseHard = function() { }, function () { core.ui.drawSettings(false); }); + */ } -*/ ////// 能否使用快捷商店 ////// events.prototype.canUseQuickShop = function(shopIndex) { @@ -497,20 +470,24 @@ events.prototype.afterBattle = function(enemyId,x,y,callback) { // 中毒 if (core.enemys.hasSpecial(special, 12) && !core.hasFlag('poison')) { core.setFlag('poison', true); - core.updateStatusBar(); } // 衰弱 if (core.enemys.hasSpecial(special, 13) && !core.hasFlag('weak')) { core.setFlag('weak', true); core.status.hero.atk-=core.values.weakValue; core.status.hero.def-=core.values.weakValue; - core.updateStatusBar(); } // 诅咒 if (core.enemys.hasSpecial(special, 14) && !core.hasFlag('curse')) { core.setFlag('curse', true); - core.updateStatusBar(); } + // 仇恨属性:减半 + if (core.enemys.hasSpecial(special, 17)) { + core.setFlag('hatred', parseInt(core.getFlag('hatred', 0)/2)); + } + // 增加仇恨值 + core.setFlag('hatred', core.getFlag('hatred',0)+core.values.hatred); + core.updateStatusBar(); // 如果已有事件正在处理中 if (core.status.lockControl) { @@ -595,11 +572,11 @@ events.prototype.changeLight = function(x, y) { core.canvas.event.clearRect(x * 32, y * 32, 32, 32); var blockIcon = core.material.icons[block.event.cls][block.event.id]; core.canvas.event.drawImage(core.material.images[block.event.cls], 0, blockIcon * 32, 32, 32, block.x * 32, block.y * 32, 32, 32); - this.afterChangeLight(); + this.afterChangeLight(x,y); } // 改变灯后的事件 -events.prototype.afterChangeLight = function() { +events.prototype.afterChangeLight = function(x,y) { } @@ -813,21 +790,48 @@ events.prototype.clickSL = function(x,y) { } } -// 菜单栏 -events.prototype.clickSettings = function (x,y) { +events.prototype.clickSwitchs = function (x,y) { if (x<5 || x>7) return; - if (y == 3) { + if (y==4) { if (core.musicStatus.isIOS) { core.drawTip("iOS设备不支持播放音乐"); return; } core.changeSoundStatus(); + core.ui.drawSwitchs(); + } + if (y==5) { + core.flags.battleAnimate=!core.flags.battleAnimate; + core.ui.drawSwitchs(); + } + if (y==6) { + core.flags.displayEnemyDamage=!core.flags.displayEnemyDamage; + core.updateFg(); + core.ui.drawSwitchs(); + } + if (y==7) { + core.flags.displayExtraDamage=!core.flags.displayExtraDamage; + core.updateFg(); + core.ui.drawSwitchs(); + } + if (y==8) { core.ui.drawSettings(false); } +} + +// 菜单栏 +events.prototype.clickSettings = function (x,y) { + if (x<5 || x>7) return; + if (y == 3) { + core.ui.drawSwitchs(); + } if (y==4) { + /* core.flags.battleAnimate=!core.flags.battleAnimate; core.setLocalStorage('battleAnimate', core.flags.battleAnimate); core.ui.drawSettings(false); + */ + this.decreaseHard(); } if (y == 5) core.ui.drawQuickShop(); // if (y == 5) this.decreaseHard(); diff --git a/libs/floors/MT0.js b/libs/floors/MT0.js index 9d66ec6b..32000498 100644 --- a/libs/floors/MT0.js +++ b/libs/floors/MT0.js @@ -27,14 +27,6 @@ main.floors.MT0 = { }, "afterOpenDoor": { // 开完门后可能触发的事件列表 - }, - "checkBlock": [ - /****** 领域、夹击检查事件 ******/ - // 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件 - // 另外,如果该点已经存在events事件或changeFloor事件(即上面有相同点位置定义),则会被覆盖 - // afterBattle, afterGetItem, afterOpenDoor则不受影响(仍能正常工作)。 - // 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******| - ] - + } } diff --git a/libs/floors/sample0.js b/libs/floors/sample0.js index e3952451..5f296247 100644 --- a/libs/floors/sample0.js +++ b/libs/floors/sample0.js @@ -59,9 +59,8 @@ main.floors.sample0 = { "\t[老人,magician]模仿、吸血、中毒、衰弱、诅咒。\n\n请注意吸血怪需要设置value为吸血数值,可参见样板中黑暗大法师的写法。", {"type": "hide", "time": 500} ], - "2,3": [ // 守着第三批怪物额老人 + "2,3": [ // 守着第三批怪物的老人 "\t[老人,magician]领域、夹击。\n请注意领域怪需要设置value为伤害数值,可参见样板中初级巫师的写法。", - "\t[老人,magician]出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。\n因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。\n具体可参见本层 checkBlock 的写法。", "\t[老人,magician]夹击和领域同时发生时先计算领域,再夹击。\n自动寻路同样会尽量绕过你设置的这些点。\n\n另:本塔不支持阻击怪。", {"type": "hide", "time": 500} ], @@ -99,7 +98,7 @@ main.floors.sample0 = { ], "10,5": ["破墙镐是破面前的墙壁还是四个方向的墙壁,由data.js中的系统Flag所决定。"], "8,4": [ - "炸弹可以炸四个方向的怪物。\n如只需要炸前方怪物请使用上面的圣锤。\n不能被炸的怪物在enemys中可以定义,可参见样板里黑衣魔王和黑暗大法师的写法。", + "炸弹是只能炸面前的怪物还是四个方向的怪物,由data.js中的系统Flag所决定。\n如只能炸前方怪物则和上面的圣锤等价。\n不能被炸的怪物在enemys中可以定义,可参见样板里黑衣魔王和黑暗大法师的写法。", ], "10,4": ["“上楼”和“下楼”的目标层由 main.js 的 floorIds顺序所决定。"], "10,3": ["十字架目前未被定义,可能需要自行实现功能。\n有关如何实现一个道具功能参见doc文档。"], @@ -108,15 +107,7 @@ main.floors.sample0 = { }, "afterOpenDoor": { // 开完门后可能触发的事件列表 "11,12": ["你开了一个绿门,触发了一个afterOpenDoor事件"] - }, - "checkBlock": [ - /****** 领域、夹击检查事件 ******/ - // 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件 - // 另外,如果该点已经存在events事件或changeFloor事件(即上面有相同点位置定义),则会被覆盖 - // afterBattle, afterGetItem, afterOpenDoor则不受影响(仍能正常工作)。 - // 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******| - "1,0", "3,0", "0,1", "2,1", "4,1", "1,2", "3,2" - ] + } } diff --git a/libs/floors/sample1.js b/libs/floors/sample1.js index afb288c3..9a29892b 100644 --- a/libs/floors/sample1.js +++ b/libs/floors/sample1.js @@ -30,8 +30,8 @@ main.floors.sample1 = { "4,10": [ // 走到中间时的提示 "\t[样板提示]本层楼将会对各类事件进行介绍。", "左边是一个仿50层的陷阱做法,上方是商店、快捷商店的使用方法,右上是一个典型的杀怪开门的例子,右下是各类可能的NPC事件。", - "本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)\nopenShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调", - "move: 移动事件效果\nplaySound: 播放某个音频\nif: 条件判断\nchoices: 提供选项\nsetValue: 设置勇士属性道具,或某个变量/flag\nupdate: 更新状态栏和地图显伤\nwin: 获得胜利(游戏通关)\nlose: 游戏失败\nsleep: 等待多少毫秒\nexit: 立刻结束当前事件\nrevisit: 立刻结束事件并重新触发\nfunction: 自定义JS脚本\n更多支持的事件还在编写中,欢迎您宝贵的意见。", + "本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\ntip: 左上角显示提示\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)\nopenShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调", + "move: 移动事件效果\nmoveHero: 移动勇士效果\nplaySound: 播放某个音频\nif: 条件判断\nchoices: 提供选项\nsetValue: 设置勇士属性道具,或某个变量/flag\nupdate: 更新状态栏和地图显伤\nwin: 获得胜利(游戏通关)\nlose: 游戏失败\nsleep: 等待多少毫秒\nexit: 立刻结束当前事件\nrevisit: 立刻结束事件并重新触发\nfunction: 自定义JS脚本\n更多支持的事件还在编写中,欢迎您宝贵的意见。", "有关各事件的样例,可参见本层一些NPC的写法。\n所有事件样例本层都有介绍。\n\n一个自定义事件处理完后,需要调用{\"type\": \"hide\"}该事件才不会再次出现。", {"type": "hide"} ], @@ -299,14 +299,6 @@ main.floors.sample1 = { }, "afterOpenDoor": { // 开完门后可能触发的事件列表 - }, - "checkBlock": [ - /****** 领域、夹击检查事件 ******/ - // 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件 - // 另外,如果该点已经存在events事件或changeFloor事件(即上面有相同点位置定义),则会被覆盖 - // afterBattle, afterGetItem, afterOpenDoor则不受影响(仍能正常工作)。 - // 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******| - ] - + } } diff --git a/libs/floors/sample2.js b/libs/floors/sample2.js index b0bc470d..9dfe71b5 100644 --- a/libs/floors/sample2.js +++ b/libs/floors/sample2.js @@ -365,14 +365,6 @@ main.floors.sample2 = { }, "afterOpenDoor": { // 开完门后可能触发的事件列表 - }, - "checkBlock": [ - /****** 领域、夹击检查事件 ******/ - // 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件 - // 另外,如果该点已经存在events事件或changeFloor事件(即上面有相同点位置定义),则会被覆盖 - // afterBattle, afterGetItem, afterOpenDoor则不受影响(仍能正常工作)。 - // 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******| - ] - + } } diff --git a/libs/icons.js b/libs/icons.js index 0d54cad5..48750086 100644 --- a/libs/icons.js +++ b/libs/icons.js @@ -144,7 +144,7 @@ icons.prototype.init = function () { 'poisonZombie': 55, 'magicDragon': 56, 'octopus': 57, - 'fairy': 58, + 'darkFairy': 58, 'greenKnight': 59, }, 'items': { diff --git a/libs/items.js b/libs/items.js index bcd74911..e2a36c8a 100644 --- a/libs/items.js +++ b/libs/items.js @@ -46,7 +46,7 @@ items.prototype.init = function () { 'steelKey': {'cls': 'tools', 'name': '铁门钥匙', 'text': '可以打开一扇铁门'}, 'pickaxe': {'cls': 'tools', 'name': '破墙镐', 'text': '可以破坏勇士面前的墙'}, 'icePickaxe': {'cls': 'tools', 'name': '破冰镐', 'text': '可以破坏勇士面前的一堵冰墙'}, - 'bomb': {'cls': 'tools', 'name': '炸弹', 'text': '可以炸掉勇士四周的怪物'}, + 'bomb': {'cls': 'tools', 'name': '炸弹', 'text': '可以炸掉勇士面前的怪物'}, 'centerFly': {'cls': 'tools', 'name': '中心对称飞行器', 'text': '可以飞向当前楼层中心对称的位置'}, 'upFly': {'cls': 'tools', 'name': '上楼器', 'text': '可以飞往楼上的相同位置'}, 'downFly': {'cls': 'tools', 'name': '下楼器', 'text': '可以飞往楼下的相同位置'}, @@ -67,6 +67,8 @@ items.prototype.getItems = function () { // 面前的墙?四周的墙? if (core.flags.pickaxeFourDirections) this.items.pickaxe.text = "可以破坏勇士四周的墙"; + if (core.flags.bombFourDirections) + this.items.bomb.text = "可以炸掉勇士四周的怪物"; return this.items; } @@ -247,7 +249,8 @@ items.prototype.canUseItem = function (itemId) { if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable) && block.event.cls == 'enemys' && Math.abs(block.x-core.status.hero.loc.x)+Math.abs(block.y-core.status.hero.loc.y)<=1) { var enemy = core.material.enemys[block.event.id]; if (core.isset(enemy.bomb) && !enemy.bomb) continue; - ids.push(i); + if (core.flags.bombFourDirections || (block.x==core.nextX() && block.y==core.nextY())) + ids.push(i); } } if (ids.length>0) { diff --git a/libs/maps.js b/libs/maps.js index d10c21ee..f03c634f 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -36,8 +36,6 @@ maps.prototype.loadFloor = function (floorId, map) { } this.addEvent(block,j,i,floor.events[j+","+i]) this.addChangeFloor(block,j,i,floor.changeFloor[j+","+i]); - if (floor.checkBlock.indexOf(j+","+i)>=0) - this.addEvent(block,j,i,{"trigger":"checkBlock"}); if (core.isset(block.event)) blocks.push(block); } } @@ -78,7 +76,6 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 12) tmp.event = {'cls': 'animates', 'id': 'poisonNet', 'noPass': false, 'trigger': 'passNet'}; // 毒网 if (id == 13) tmp.event = {'cls': 'animates', 'id': 'weakNet', 'noPass': false, 'trigger': 'passNet'}; // 衰网 if (id == 14) tmp.event = {'cls': 'animates', 'id': 'curseNet', 'noPass': false, 'trigger': 'passNet'}; // 咒网 - if (id == 15) tmp.event = {'cls': 'animates', 'id': 'water', 'noPass': true}; // 水 // autotile: 20 @@ -229,7 +226,7 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 256) tmp.event = {'cls': 'enemys', 'id': 'poisonZombie'}; if (id == 257) tmp.event = {'cls': 'enemys', 'id': 'magicDragon'}; if (id == 258) tmp.event = {'cls': 'enemys', 'id': 'octopus'}; - if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'fairy'}; + if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'darkFairy'}; if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'}; return tmp; diff --git a/libs/ui.js b/libs/ui.js index d70c7551..0acbb2b3 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -302,6 +302,30 @@ ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) { core.fillText('ui', "取消", 208 + 38, top + bottom - 35); } +////// 绘制开关界面 ////// +ui.prototype.drawSwitchs = function() { + // 背景音乐、背景音效、战斗动画、怪物显伤、领域显伤、返回 + + core.status.event.id = 'switchs'; + + var background = core.canvas.ui.createPattern(core.material.ground, "repeat"); + core.clearMap('ui', 0, 0, 416, 416); + core.setAlpha('ui', 1); + core.setFillStyle('ui', background); + var left = 97, top = 64 + 32, right = 416 - 2 * left, 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', "背景音乐: " + (core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), 208, top + 56, "#FFFFFF", "bold 17px Verdana"); + // core.fillText('ui', "背景音效" + (core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), 208, top + 88, "#FFFFFF", "bold 17px Verdana") + core.fillText('ui', "战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"), 208, top + 88, "#FFFFFF", "bold 17px Verdana") + core.fillText('ui', "怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), 208, top + 120, "#FFFFFF", "bold 17px Verdana") + core.fillText('ui', "领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), 208, top + 152, "#FFFFFF", "bold 17px Verdana") + + core.fillText('ui', "返回上级菜单", 208, top + 184, "#FFFFFF", "bold 17px Verdana"); +} + /** * 绘制菜单栏 * @param need @@ -319,8 +343,8 @@ ui.prototype.drawSettings = function (need) { core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); core.canvas.ui.textAlign = "center"; - core.fillText('ui', "音乐: " + (core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), 208, top + 56, "#FFFFFF", "bold 17px Verdana"); - core.fillText('ui', '战斗过程: ' +(core.flags.battleAnimate?'[ON]':'[OFF]'), 208, top + 88, "#FFFFFF", "bold 17px Verdana") + core.fillText('ui', "系统设置", 208, top + 56, "#FFFFFF", "bold 17px Verdana"); + core.fillText('ui', "降低难度", 208, top + 88, "#FFFFFF", "bold 17px Verdana") core.fillText('ui', "快捷商店", 208, top + 120, "#FFFFFF", "bold 17px Verdana"); core.fillText('ui', "同步存档", 208, top + 152, "#FFFFFF", "bold 17px Verdana"); // core.fillText('ui', "清空存档", 208, top + 152, "#FFFFFF", "bold 17px Verdana"); @@ -358,8 +382,7 @@ ui.prototype.drawQuickShop = function (need) { core.fillText('ui', shopList[keys[i]].textInList, 208, top + 56 + 32 * i, "#FFFFFF", "bold 17px Verdana"); } - core.fillText('ui', "返回游戏", 208, top + bottom - 40); - + core.fillText('ui', "返回游戏", 208, top + bottom - 40, "#FFFFFF", "bold 17px Verdana"); } ui.prototype.drawBattleAnimate = function(monsterId, callback) { @@ -395,10 +418,15 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) { if (core.enemys.hasSpecial(mon_special, 6)) turns=5; - // 初始伤害(破甲、净化) + // 初始伤害 var initDamage = 0; if (core.enemys.hasSpecial(mon_special, 7)) initDamage+=parseInt(core.values.breakArmor * hero_def); - if (core.enemys.hasSpecial(mon_special, 9)) initDamage=parseInt(core.values.purify * hero_mdef); + if (core.enemys.hasSpecial(mon_special, 9)) initDamage+=parseInt(core.values.purify * hero_mdef); + if (core.enemys.hasSpecial(mon_special, 11)) { // 吸血 + var extraDamage = monster.value * hero_hp; + initDamage+=parseInt(extraDamage); + } + if (core.enemys.hasSpecial(mon_special, 17)) initDamage+=core.getFlag('hatred', 0); hero_mdef-=initDamage; if (hero_mdef<0) { hero_hp+=hero_mdef; @@ -425,6 +453,9 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) { core.fillRect('ui', left, top, right, bottom, '#000000'); core.setAlpha('ui', 1); core.strokeRect('ui', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); + core.clearMap('data',0,0,416,416); + core.setAlpha('data', 1); + core.setOpacity('data', 1); core.status.boxAnimateObjs = []; core.setBoxAnimate(); diff --git a/main.js b/main.js index 4e7bcec1..2305a77f 100644 --- a/main.js +++ b/main.js @@ -224,6 +224,10 @@ document.ontouchstart = function() { main.dom.data.onmousedown = function (e) { try { e.stopPropagation(); + if(e.button==1){// 把鼠标中键绑定为ESC + core.keyUp(27); + return; + } var loc = main.core.getClickLoc(e.clientX, e.clientY); if (loc == null) return; var x = parseInt(loc.x / loc.size), y = parseInt(loc.y / loc.size); diff --git a/启动服务.exe b/启动服务.exe new file mode 100644 index 00000000..e6f4a85c Binary files /dev/null and b/启动服务.exe differ diff --git a/常用工具/Jint.dll b/常用工具/Jint.dll new file mode 100644 index 00000000..437d2bb2 Binary files /dev/null and b/常用工具/Jint.dll differ diff --git a/常用工具/便捷PS工具.exe b/常用工具/便捷PS工具.exe new file mode 100644 index 00000000..ffe1447f Binary files /dev/null and b/常用工具/便捷PS工具.exe differ diff --git a/常用工具/地图生成器.exe b/常用工具/地图生成器.exe index 67eda508..541b3626 100644 Binary files a/常用工具/地图生成器.exe and b/常用工具/地图生成器.exe differ diff --git a/更新内容.txt b/更新内容.txt deleted file mode 100644 index 0c504483..00000000 --- a/更新内容.txt +++ /dev/null @@ -1,8 +0,0 @@ -新增:可视化地图编辑工具 -新增:支持Autotile √ -新增:怪物支持双属性 √ -新增:单向箭头、感叹号 √ -新增:怪物P图工具 -快捷道具使用:1破2炸3飞;读档改为D键 √ -更多的默认素材;无需P图,直接替换即可 √ -破甲、反击、净化等效果放全局变量 √