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)

+## 目录结构
+
+``` 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`中定义,请勿对已有的属性进行修改。
+怪物可以有特殊属性,每个怪物可以有多个自定义属性。
-
+怪物的特殊属性所对应的数字(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 @@

中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。
+
衰弱怪让勇士衰弱后,攻防会暂时下降一定的数值(直到衰弱状态解除恢复);这个下降的数值同在`data.js`中的values定义。

@@ -54,17 +89,11 @@
领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。
-
+
-出于游戏性能的考虑,我们不可能每走一步都对领域和夹击进行检查。因此我们需要在本楼层的 checkBlock 中指明哪些点可能会触发领域和夹击事件,在这些点才会对领域和夹击进行检查和处理。
+请注意如果吸血和领域同时存在,则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。**
-
-
-!> **请注意这里的`"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 @@

+### 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工具生成素材
-
+如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。
-### 使用自定义地形(路面、墙壁等)
+
-你需要自行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”,运行之。
+
+
+
+* “启动游戏”按钮将打开一个网页,你能在里面看到现在游戏的效果。
+* “地图编辑器”允许你以可视化的方式进行编辑地图。
+* “便捷PS工具”能让你很方便的对自定义素材进行添加。参见[自定义素材](personalization#自定义素材)。
+* “地图生成器”能让你从已有的截图(如RMXP项目)中立刻生成可被本样板识别的地图数据。
+* “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。
+* “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。
+
## 新建剧本
类似于RMXP,本塔每层楼都是一个“剧本”,剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本,并生成实际的地图供游戏使用。
@@ -30,43 +42,48 @@
## 绘制地图
-遗憾的是,我们的样板是没有像RMXP那样有着很好的UI界面,供大家直接进行绘图可视化操作的。然而,我们仍然可以利用已有的RMXP和魔塔样板,绘制好地图,然后利用目录中的“地图生成器”来转成样板所识别的格式。
+有两种绘制地图的方式:从头绘制地图;从RMXP中导入已有的地图。
-首先,我们打开RMXP和魔塔样板,来到绘制地图页面。
+### 从头绘制地图
-
+我们直接打开“地图编辑器”,可以看到一个可视化的UI界面。
-然后,任意绘制一张地图。
+
-在这里我们就以1层小塔的地图为例。你也可以任意绘制自己的地图。
+然后可以在上面任意进行绘制地图。
+
+!> **如果地图的数字和ID未被定义,则会进行提示:数字和ID未被定义!此时可能需要手动在icons.js和maps.js中定义对应的数字和ID。请参见[自定义素材](personalization#自定义素材)。**
+
+绘制地图完毕后,点击"导出地图",即可在左边看到对应的JSON数组,并且已经复制到了剪切板。将其粘贴到剧本中的map位置即可。
+
+
+
+### 从RMXP导入已有的地图
+
+如果我们想复刻一个现有的,已经被RMXP所制作的塔,也有很便捷的方式,那就是用到我们的“地图生成器”。
+
+首先,我们打开RMXP和对应的项目,可以看到它的地图。

-(我把原塔素材改成了经典样式,但是本质上是一样的。)
-
-请注意,我们无需编辑任何事件。只需要让地图“看起来长成这个样子”就行。
-
-当绘制好需要的地图后,我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。
+我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。

截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是13x13。(如果地图小于13*13,请用星空或墙壁填充到13x13)。**
-确认地图的图片文件已经复制到剪切板后,我们打开工具中的“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。
+确认地图的图片文件已经复制到剪切板后,我们打开“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。

-如果有识别不一致的存在,即生成的地图和实际的地图不符,则需要在左边的输入框内实际手动修改,然后再点“图片生成”即可。有关每个数字对应的图块名称,请参见images目录下的`meaning.txt`
+然后点击“复制地图”,即可将地图数据复制到剪切板。
+
+!> **如果有识别不一致的存在,即生成的地图和实际的地图不符,我们可以在地图编辑器中粘贴,再可视化进行编辑。**
!> **地图生成器默认只支持经典素材。如果有自定义素材需求(例如原版的1层小塔那种素材),请参见[自定义素材](personalization#自定义素材)。**
!> **请确保截图范围刚好为13x13,并且保证每个位置的像素都是32x32。**
-经过确认,生成的地图和原始地图保持一致后,点击“复制地图”,然后粘贴到刚刚剧本文件里的maps中。
-
-
-
-通过这种在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图,直接替换即可 √
-破甲、反击、净化等效果放全局变量 √