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

This commit is contained in:
YouWei Zhao 2017-12-22 22:39:57 +08:00
commit 30f8fccef0
31 changed files with 1402 additions and 663 deletions

View File

@ -2,4 +2,4 @@
Prop3=19,2
[InternetShortcut]
IDList=
URL=http://ckcz123.github.io/mota-js/
URL=https://ckcz123.github.io/mota-js/

View File

@ -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魔塔样板
## 联系我们

View File

@ -1 +0,0 @@
python _server.py

View File

@ -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()

126
_server/fsTest_cs.html Normal file
View File

@ -0,0 +1,126 @@
<!doctype html>
<html>
<head><meta charset="utf-8"></head>
<body>
<script>
(function(){
fs = {};
var postsomething = function (data,_ip,callback) {
//callback:function(err, data)
//data:字符串
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
switch(xhr.readyState){
case 4 :
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (Boolean(callback)){
if (xhr.responseText.slice(0,6)=='error:'){
callback(xhr.responseText,null);
} else {
callback(null,xhr.responseText);
}
}
//printf(xhr.responseText)
}else{
if (Boolean(callback))callback(xhr.status,null);
//printf('error:' + xhr.status+'<br>'+(xhr.responseText||''));
}
break;
}
}
xhr.open('post',_ip);
xhr.setRequestHeader('Content-Type','text/plain');
if(typeof(data)==typeof([][0]) || data==null)data=JSON.stringify({1:2});
xhr.send(data);
}
fs.readFile = function (filename,encoding,callback) {
if (typeof(filename)!=typeof(''))
throw 'Type Error in fs.readFile';
if (encoding=='utf-8'){
//读文本文件
//filename:支持"/"做分隔符
//callback:function(err, data)
//data:字符串
var data='';
data+='type=utf8&';
data+='name='+filename;
postsomething(data,'/readFile',callback);
return;
}
if (encoding=='base64'){
//读二进制文件
//filename:支持"/"做分隔符
//callback:function(err, data)
//data:base64字符串
var data='';
data+='type=base64&';
data+='name='+filename;
postsomething(data,'/readFile',callback);
return;
}
throw 'Type Error in fs.readFile';
}
fs.writeFile = function (filename,datastr,encoding,callback) {
if (typeof(filename)!=typeof('') || typeof(datastr)!=typeof(''))
throw 'Type Error in fs.writeFile';
if (encoding=='utf-8'){
//写文本文件
//filename:支持"/"做分隔符
//callback:function(err)
//datastr:字符串
var data='';
data+='type=utf8&';
data+='name='+filename;
data+='&value='+datastr;
postsomething(data,'/writeFile',callback);
return;
}
if (encoding=='base64'){
//写二进制文件
//filename:支持"/"做分隔符
//callback:function(err)
//datastr:base64字符串
var data='';
data+='type=base64&';
data+='name='+filename;
data+='&value='+datastr;
postsomething(data,'/writeFile',callback);
return;
}
throw 'Type Error in fs.writeFile';
}
fs.readdir = function (path, callback) {
//callback:function(err, data)
//path:支持"/"做分隔符,不以"/"结尾
//data:[filename1,filename2,..] filename是字符串,只包含文件不包含目录
if (typeof(path)!=typeof(''))
throw 'Type Error in fs.readdir';
var data='';
data+='name='+path;
postsomething(data,'/listFile',function(err, data){callback(err,JSON.parse(data))});
return;
}
})();
</script>
<script>
fs.writeFile('_test.txt','123中a文bc','utf-8',function(e,d){console.log(e);console.log(d);})
setTimeout(function() {
fs.writeFile('_test_bin.txt','abc=','base64',function(e,d){console.log(e);console.log(d);})
}, 1000);
setTimeout(function() {
fs.readFile('_test.txt','utf-8',function(e,d){console.log(e);console.log(d);})
}, 2000);
setTimeout(function() {
fs.readFile('_test_bin.txt','base64',function(e,d){console.log(e);console.log(d);})
}, 3000);
setTimeout(function() {
fs.readdir('.',function(e,d){console.log(e);console.log(d);})
}, 4000);
</script>
</body>
</html>

6
_server/vendor/vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -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#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。

View File

@ -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": [

BIN
docs/img/mapgui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
docs/img/ps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
docs/img/server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -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`等等。**
&nbsp;
通过上述这几种方式,我们可以修改素材图片(使用自定义素材),指定数字并放入地图生成器中,然后在系统中进行启用。
!> **请注意:除了勇士行走图外,其他所有素材强制要求必须是`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;
// ... 下略
```

View File

@ -6,14 +6,26 @@
你需要有满足如下条件才能进行制作:
- Windows 8以上操作系统Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“地图生成器.exe”即可
- Windows 8以上操作系统Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可
- 任一款现代浏览器。强烈推荐Chrome。
- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如WebStormVSCode或者至少也要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中画图截图复制再用地图生成器识别的方式我们成功将我们需要的地图变成了样板可识别的格式。
## 录入数据

View File

@ -4,109 +4,342 @@
<meta charset="utf-8">
<style>
html,body,div,img{margin:0;padding:0;}
body{
font-family: -apple-system,"Helvetica Neue",Helvetica,Arial,"PingFang SC","Hiragino Sans GB","WenQuanYi Micro Hei","Microsoft Yahei",sans-serif;
background-color: #F5F5F5;
}
::-webkit-scrollbar {
width: 5px;
}
.main {
max-width: 100%;
min-height: 500px;
margin: 0 auto;
}
#left, #mid, #right{
border-radius: 2px;
box-sizing: border-box;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
#left{
position: absolute;
left: 10px;
left: 5px;
top: 10px;
width: 416px;
width: 435px;
height: 400px;
border: 1px solid rgb(238, 13, 13);
}
#left #pout{
#editArea{
position: absolute;
display: block;
width: 100%;
height: 100%;
left: 10px;
top: 10px;
height: 70%;
left: 0;
top: 0;
/* padding: 10px 5px; */
box-sizing: border-box;
}
#pout{
display: block;
width: 410px;
height: 100%;
box-sizing: border-box;
margin-left: 22px;
margin-top: 21px;
line-height: 20px;
font-size: 13.3333px;
font-family: monospace;
}
#editTip{
position: absolute;
width: 100%;
height: 80px;
bottom:10px;
left: 10px;
}
#editArea p{
margin: 10px;
display: block;
width: 70%;
line-height: 20px;
text-align: left;
font-size: 14px;
}
#editTip .btn{
float: right;
margin-right: 20px;
margin-top: 5px;
}
#mid{
position: absolute;
left: 448px;
top: 0;
top: 10px;
width: 440px;
height: 630px;
}
.map {
position: absolute;
left: 20px;
top: 21px;
width: 416px;
height: 600px;
border: 1px solid rgb(238, 13, 13);
height: 416px;
}
#mid .tools{
position: absolute;
width: 100%;
height: 200px;
width: 425px;
height: 180px;
left: 0;
top: 448px;
bottom: 0;
border-top: 1px solid #ccc;
padding: 10px 5px;
margin-left: 8px;;
box-sizing: border-box;
}
.btn{
width: 80px;
height: 30px;
margin: 10px;
#tip{
float: right;
width: 50%;
height: 95%;
padding: 5px 10px 10px 10px;
margin-right: 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
font-size: 15px;
line-height: 16px;
}
.files {
width: 50%;
height: 120px;
/* padding: 10px; */
margin-top: 15px;
}
.files .input{
display: block;
max-width: 150px;
height: 20px;
padding: 6px 12px;
font-size: 14px;
margin-top: 10px;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 3px;
/* background-color: green; */
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
.input{
width: 100px;
margin: 10px;
#printOut{
margin-top: 10px;
height: 20px;
}
.btn {
width: 80px;
border-radius: 2px;
line-height: 30px;
margin: 0;
min-width: 50px;
padding: 0 5px;
display: inline-block;
font-size: 14px;
font-weight: 400;
/* text-transform: uppercase; */
letter-spacing: 0;
overflow: hidden;
cursor: pointer;
text-decoration: none;
text-align: center;
vertical-align: middle;
border: 0;
background: rgba(158,158,158,.2);
box-shadow: 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.2), 0 1px 3px 0 rgba(0,0,0,.12);
color: #fff;
background-color: #26A69A;
}
.btn:hover {
background-color: #009688;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
#right{
position: absolute;
left: 896px;
top: 0;
width: 450px;
height: 600px;
border: 1px solid rgb(238, 13, 13);
left: 900px;
top: 10px;
width: 440px;
height: 630px;
/* border: 1px solid rgb(238, 13, 13); */
}
#iconLib{
position: absolute;
width: 435px;
height: 620px;
left: 5px;
top: 5px;
overflow:auto;
}
.gameCanvas {
position: absolute;
}
#dataSelection{
position: absolute;
top:0;
left:320px;
/* top:0;
left:320px; */
z-index:75;
width:32px;height:32px;
margin:-2px 0 0 -2px;
padding:0;
/* display: none; */
background-color:rgba(255, 255, 255, 0.0);
border: 2px solid rgb(87, 198, 232);
box-shadow: 0px 0px 2px rgb(87, 198, 232);
border: 2px solid #30DFF3;
box-shadow: 0px 0px 2px #30DFF3;
}
.warnText{
color: #D50000;
font-weight: 700;
font-size: 14px;
}
.infoText{
color: #2196F3;
}
.successText{
color: #00897B
}
table, td {
border: 1px solid #fff;
color: #fff;
}
table.col{
position: relative;
text-align: center;
border-collapse: collapse;
}
table.col td{
background-color: #4DB6AC;
}
#arrColMark td{
width: 16px;
}
#arrColMark {
top: 2px;
left: 36px;
width: 385px;
height: 16px;
font-size: 13px;
}
#mapColMark {
top: 2px;
left: 19px;
width: 418px;
height: 16px;
font-size: 13px;
}
#mapColMark td{
width: 29px;
}
#mapColMark td:hover .colBlock{
position: absolute;
top: 19px;
height: 416px;
width: 32px;
z-index: 100;
background-color: rgba(38,166,154,.5);
}
table.row{
position: relative;
text-align: right;
vertical-align:middle;
border-collapse: collapse;
}
table.row td{
background-color: #A1887F;
}
#arrRowMark{
top: 5px;
left: 2px;
width: 16px;
height: 262px;
font-size: 12px;
}
#mapRowMark{
top: 1px;
left: 2px;
width: 16px;
height: 416px;
font-size: 12px;
}
#mapRowMark td{
height: 29px;
}
#mapRowMark td:hover .rowBlock{
position: absolute;
left: 18px;
height: 32px;
width: 416px;
z-index: 100;
background-color: rgba(121,85,72,.5);
}
/* for vue dom */
[v-cloak] {
display: none !important;
}
</style>
</head>
<body>
<div class="main">
<div id="left">
<p id='pout'>可以在console中通过printf(str)来改变这里的值</p>
<table class="col" id='arrColMark'></table>
<table class="row" id='arrRowMark'></table>
<div id="editArea" v-cloak>
<textarea cols="10" rows="10" id="pout" v-model="mapArr"></textarea>
<p class="warnText" v-if="error">{{ errors[error-1] }}</p>
</div>
<div id="editTip" v-cloak>
<input class='btn' type="button" value="复制地图" v-on:click="copyMap"/>
</div>
</div>
<div id="mid">
<div class="map">
<table class="col" id='mapColMark'></table>
<table class="row" id='mapRowMark'></table>
<div class="map" id="mapEdit">
<canvas class='gameCanvas' id='bg' width='416' height='416' style='z-index:1'></canvas>
<canvas class='gameCanvas' id='eventLayer' width='416' height='416' style='z-index:2'></canvas>
<canvas class='gameCanvas' id='ui' width='416' height='416' style='z-index:100'></canvas>
</div>
<div class="tools">
<input class='btn' type="button" value="exportMap" onclick="exportMap()" style="top:600px;left:50px"/>
<input class='btn' type="button" value="read" onclick="readUTF8file(pin.value)" style="top:600px;left:150px"/>
<input class='btn' type="button" value="write" onclick="writeUTF8file(pin.value,pout.innerText)" style="top:600px;left:200px"/>
<div id="tip" v-cloak >
<div v-if="isSelectedBlock" >
<p v-if="hasId">图块编号:<span class="infoText">{{ infos['idnum'] }}</span></p>
<p v-if="hasId">图块ID<span class="infoText">{{ infos['id'] }}</span></p>
<p v-else class="warnText">该图块无对应的数字或ID存在请先前往icons.js和maps.js中进行定义</p>
<p>图块所在素材:<span class="infoText">{{ infos['images'] }}</span></p>
<p>图块索引:<span class="infoText">{{ infos['y'] }}</span></p>
</div>
<div v-else>
<p v-if="whichShow" v-bind:class="[ (whichShow%2) ? 'warnText' : 'successText']">{{ mapMsg }}</p>
</div>
</div>
<input class='btn' id='clear' type="button" value="清除地图" v-on:click="clearMap"/>
<input class='btn' type="button" value="导出地图" id="exportM" v-on:click="exportMap"/>
<input class='input' id='pin' style='top:630px;left:12px;width:200px;height:20px' value="文件名"/>
<div class="files">
<input class='btn' type="button" value="read" onclick="readUTF8file(pin.value)" />
<input class='btn' type="button" value="write" onclick="writeUTF8file(pin.value,pout.innerText)" />
<input class='input' id='pin' placeholder="请输入文件名"/>
<div id="printOut"></div>
</div>
</div>
</div>
<div id="right">
<canvas class='gameCanvas' id='data' width='416' height='416' style='z-index:0'></canvas>
<div id='dataSelection'></div>
<div id="iconLib">
<canvas class='gameCanvas' id='data' width='416' height='416' style='z-index:0'></canvas>
<div id="selectBox">
<div id='dataSelection' v-show="isSelected" v-cloak></div>
</div>
</div>
</div>
</div>
@ -114,35 +347,55 @@
<script>main={'instance':{}}</script>
<script src='libs/icons.js'></script>
<script src='libs/maps.js'></script>
<script src='_server/vendor/vue.min.js'></script>
<!-- <script src="https://unpkg.com/vue"></script> -->
<script>
main.instance.icons.init();//不知道为什么,需要手动init,明明maps是正常的
icons=main.instance.icons.getIcons();
ids=[];
for(var ii=0;ii<=400;ii++)ids[ii]=main.instance.maps.getBlock(0,0,ii);
var ids = [], indexs = []
ids.push({'idnum':0,'id':'ground','images':'terrains','y':0});
for(var ii=0;ii<=400;ii++){
if('event' in ids[ii]){
var id =ids[ii].event.id;
var cls =ids[ii].event.cls;
if(id=='autotile'){ids[ii]={'idnum':ii,'id':'autotile','images':'autotile','y':0};continue;}
if (id in icons[cls])ids[ii]={'idnum':ii,'id':id,'images':cls,'y':icons[cls][id]};
var point = 0
for(var i=0; i<400; i++){
var indexBlock = main.instance.maps.getBlock(0,0,i);
indexs[i] = [];
if('event' in indexBlock){
var id = indexBlock.event.id;
var indexId = indexBlock.id;
if(id=='autotile'){
ids.push({'idnum':indexId,'id':'autotile','images':'autotile','y':0});
point++;
indexs[i].push(point);
continue;
}
var allCls = Object.keys(icons);
for(var j=0; j<allCls.length; j++){
if(id in icons[allCls[j]] ){
ids.push({'idnum':indexId,'id':id,'images':allCls[j],'y':icons[allCls[j]][id]});
point++;
indexs[i].push(point);
}
}
}
else ids[ii]=null;
}
ids[0]={'idnum':0,'id':'ground','images':'terrains','y':0}
console.log(icons);
console.log(ids);
// ids
// {'idnum':20,'id':'autotile','images':'autotile','y':0}
// {'idnum':21,'id':'yellowKey','images':'items','y':0}
// {'idnum':22,'id':'blueKey','images':'items','y':1}
//var cxt = eventLayer.getContext("2d");
//cxt.drawImage(core.material.images['spriter_name'], sx*32, sy*32, 32, 32, xx*32, yy*32, 32, 32);
// 生成定位编号
var colNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<td>'+i+'<div class="colBlock" style="left:'+(i*32+1)+'px;"></div></td>';
colNum += tpl;
}
arrColMark.innerHTML = '<tr>'+colNum+'</tr>';
mapColMark.innerHTML = '<tr>'+colNum+'</tr>';
var rowNum = ' ';
for(var i=0; i<13; i++){
var tpl = '<tr><td>'+i+'<div class="rowBlock" style="top:'+(i*32+1)+'px;"></div></td></tr>';
rowNum += tpl;
}
arrRowMark.innerHTML = rowNum;
mapRowMark.innerHTML = rowNum;
</script>
@ -299,10 +552,9 @@
return 0;
}
updateMap = function () {
//clearGrass();
// console.log(map)
for (var xx = 0; xx <= fullX; xx++) {
for (var yy = 0; yy <= fullY; yy++) {
if (!isGrass(xx, yy)) continue;
@ -341,7 +593,15 @@
for (var yy = 0; yy <= fullY; yy++) {
if (isGrass(xx, yy)) continue;
var mapxy=map[m(xx,yy)];
if (typeof(mapxy) == typeof(-1) || typeof(mapxy) == typeof([][0]))continue;
if (typeof(mapxy) == typeof(-1)){
if(mapxy == 0) cxt.clearRect(xx*32, yy*32, 32, 32);
continue;
}
else if(typeof(mapxy) == typeof([][0])) {//未定义块画红块
cxt.fillStyle = 'red';
cxt.fillRect(xx*32, yy*32, 32, 32);
continue;
}
//context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height)
cxt.clearRect(xx*32, yy*32, 32, 32);
cxt.drawImage(core.material.images[mapxy.images], 0, mapxy.y*32, 32, 32, xx*32, yy*32, 32, 32);
@ -359,7 +619,7 @@
var prefix='<span class="result">',postfix='</span>';
if (weak){prefix='<span class="weakresult">';}
if (typeof(str)==="undefined")str='';
pout.innerHTML=prefix+String(str)+postfix;
printOut.innerHTML=prefix+String(str)+postfix;
}
drawInitData = function(){
@ -417,8 +677,8 @@
function eToLoc(e) {
var loc = {
'x': document.documentElement.scrollLeft+e.clientX - mid.offsetLeft,
'y': document.documentElement.scrollTop+e.clientY - mid.offsetTop,
'x': document.documentElement.scrollLeft+e.clientX - mid.offsetLeft-mapEdit.offsetLeft,
'y': document.documentElement.scrollTop+e.clientY - mid.offsetTop-mapEdit.offsetTop,
'size': 32
};
return loc; }//返回可用的组件内坐标
@ -444,6 +704,11 @@
}//用于鼠标移出canvas时的自动清除状态
ui.onmousedown = function (e) {
if(!selectBox.isSelected) {
tip.whichShow = 1;
return;
}
holdingPath = 1;
mouseOutCheck = 2;
setTimeout(clear1);
@ -457,6 +722,10 @@
}
ui.onmousemove = function (e) {
if(!selectBox.isSelected) {
// tip.whichShow = 1;
return;
}
if (holdingPath == 0) { return; }
mouseOutCheck = 2;
@ -482,48 +751,89 @@
}
ui.onmouseup = function (e) {
if(!selectBox.isSelected) {
tip.whichShow = 1;
return;
}
holdingPath = 0;
e.stopPropagation();
var loc = eToLoc(e);
if (stepPostfix.length) {
console.log(stepPostfix);
preMapData = JSON.parse(JSON.stringify(map));
currDrawData.pos = JSON.parse(JSON.stringify(stepPostfix));
currDrawData.info = JSON.parse(JSON.stringify(info));
reDo = null;
// console.log(stepPostfix);
for (var ii = 0; ii < stepPostfix.length; ii++)
map[m(stepPostfix[ii].x, stepPostfix[ii].y)] = info;
map[fullX + 1 + fullY * (fullX + 1)] = -2;
console.log(map);
// console.log(map);
updateMap();
holdingPath = 0;
stepPostfix = [];
uc.clearRect(0, 0, 416, 416);
}
}
var preMapData = {};
var currDrawData = {
pos: [],
info: {}
};
var reDo = null;
document.body.onkeydown = function(e) {
// 禁止快捷键的默认行为
if( e.ctrlKey && ( e.keyCode == 90 || e.keyCode == 89 ) )
e.preventDefault();
//Ctrl+z 撤销上一步undo
if(e.keyCode == 90 && e.ctrlKey && preMapData && currDrawData.pos.length){
map = JSON.parse(JSON.stringify(preMapData));
updateMap();
reDo = JSON.parse(JSON.stringify(currDrawData));
currDrawData = {pos: [],info: {}};
preMapData = null;
}
//Ctrl+y 重做一步redo
if(e.keyCode == 89 && e.ctrlKey && reDo && reDo.pos.length){
preMapData = JSON.parse(JSON.stringify(map));
for(var j=0; j<reDo.pos.length;j++)
map[m(reDo.pos[j].x, reDo.pos[j].y)] = JSON.parse(JSON.stringify(reDo.info));
info=ids[20];//autotile
map[fullX + 1 + fullY * (fullX + 1)] = -2;
updateMap();
currDrawData = JSON.parse(JSON.stringify(reDo));
reDo = null;
}
}
// info=ids[indexs[20][0]];//autotile
data.onmousedown = function (e) {
e.stopPropagation();
var loc = {
'x': document.documentElement.scrollLeft + e.clientX - right.offsetLeft,
'y': document.documentElement.scrollTop + right.scrollTop+e.clientY - right.offsetTop,
'x': document.documentElement.scrollLeft + e.clientX - right.offsetLeft-iconLib.offsetLeft,
'y': document.documentElement.scrollTop + e.clientY + iconLib.scrollTop - right.offsetTop-iconLib.offsetTop,
'size': 32
};
pos = locToPos(loc);
console.log(pos);
for (var spriter in widthsX){
if(pos.x>=widthsX[spriter][1] && pos.x<widthsX[spriter][2]){
pos.x=widthsX[spriter][1];
pos.images=widthsX[spriter][0];
pos.x=widthsX[spriter][1];
pos.images=widthsX[spriter][0];
if(pos.images=='autotile'){
pos.y=0;
}else if((pos.y+1)*32>core.material.images[pos.images].height)pos.y=~~(core.material.images[pos.images].height/32)-1;
pos.y=0;
}else if((pos.y+1)*32>core.material.images[pos.images].height)
pos.y=~~(core.material.images[pos.images].height/32)-1;
dataSelection.style.left=pos.x*32+'px';
dataSelection.style.top=pos.y*32+'px';
selectBox.isSelected = true;
// console.log(pos,core.material.images[pos.images].height)
dataSelection.style.left = pos.x*32 +'px';
dataSelection.style.top = pos.y*32 +'px';
info={'images':pos.images,'y':pos.y};
for (var ii=0;ii<ids.length;ii++){
if(ids[ii]==null)continue;
if(pos.images==ids[ii].images && pos.y==ids[ii].y)info=ids[ii];
if(pos.images==ids[ii].images && pos.y==ids[ii].y)
info = ids[ii];
}
printf(JSON.stringify(info))
tip.infos = JSON.parse(JSON.stringify(info));
}
}
}
@ -592,26 +902,251 @@
postsomething(JSON.stringify(data),callback);
}
function exportMap(callback){
var filestr='';
for (var yy = 0; yy < 13; yy++){
filestr+='['
for (var xx = 0; xx < 13; xx++) {
var mapxy=map[m(xx,yy)];
if(typeof(mapxy)==typeof({})){
if ('idnum' in mapxy)mapxy=mapxy.idnum;
else mapxy='!!?';
}
mapxy=String(mapxy);
mapxy=Array(Math.max(4-mapxy.length,0)).join(' ')+mapxy;
filestr+=mapxy+','
}
filestr+='],\n'
}
printf('<pre>'+filestr+'\n</pre><p>已复制到剪贴板</p>'+'<textarea cols="1" rows="1" id="poutTmp" style="opacity: 0;">'+filestr+'\n</textarea><br><br><br>');
poutTmp.select();
document.execCommand("Copy");
}
</script>
<script type="text/javascript">
document.body.onmousedown = function(e){
selectBox.isSelected = false;
info = {};
canSelectAg = false;
}
var exportM = new Vue({
el: '#exportM',
methods: {
exportMap: function(){
if(editArea.error) {
tip.whichShow = 3;
return;
}
var filestr='';
for (var yy = 0; yy < 13; yy++){
filestr+='['
for (var xx = 0; xx < 13; xx++) {
var mapxy=map[m(xx,yy)];
if(typeof(mapxy)==typeof({})){
if ('idnum' in mapxy)mapxy=mapxy.idnum;
else {
// mapxy='!!?';
tip.whichShow = 3;
return;
}
}else if(typeof(mapxy)=='undefined'){
tip.whichShow = 3;
return;
}
mapxy=String(mapxy);
mapxy=Array(Math.max(4-mapxy.length,0)).join(' ')+mapxy;
filestr+=mapxy+(xx==12?'':',')
}
filestr += ']'+(yy==12?'':',\n');
}
pout.value = filestr;
tip.whichShow = 2;
}
}
})
var editArea = new Vue({
el: '#editArea',
data: {
mapArr: '',
errors: [ // 编号1,2,3,4
"格式错误!请使用正确格式(13*13数组如不清楚可先点击生成地图查看正确格式)",
"当前有未定义ID在地图区域显示红块请修改ID或者到icons.js和maps.js中进行定义",
"ID越界在地图区域显示红块当前编辑器暂时支持编号小于400请修改编号",
// "发生错误!",
],
error: 0,
formatTimer: null,
},
watch: {
mapArr: function (val, oldval) {
var that = this;
if(val=='') return;
if(that.formatArr()){
that.error = 0;
clearTimeout(that.formatTimer);
setTimeout(function(){
that.drawMap();
tip.whichShow = 8
}, 1000);
that.formatTimer = setTimeout(function(){
pout.value = that.formatArr();
}, 5000); //5s后再格式化不然光标跳到最后很烦
}else{
that.error = 1;
}
},
error: function(){
console.log(editArea.mapArr);
}
},
methods: {
drawMap: function(){
var that = this;
var mapArray = that.mapArr.split(/\D+/).join(' ').trim().split(' ')
for(var i=0; i<mapArray.length; i++){
var num = parseInt(mapArray[i]);
if(num == 0 )
map[i] = 0;
else if(num >= 400){
that.error = 3;
map[i] = undefined;
}else if(indexs[num][0] == undefined){
that.error = 2;
map[i] = undefined;
}else map[i] = ids[[indexs[num][0]]];
}
updateMap();
},
formatArr: function(){
var formatArrStr = '';
if(this.mapArr.split(/\D+/).join(' ').trim().split(' ').length != 169) return false;
var arr = this.mapArr.replace(/\s+/g, '').split('],[');
if(arr.length != 13) return ;
for(var i =0; i<13; i++){
var a = [];
formatArrStr +='[';
if(i==0||i==12) a = arr[i].split(/\D+/).join(' ').trim().split(' ');
else a = arr[i].split(/\D+/);
if(a.length != 13){
formatArrStr = '';
return ;
}
for(var k=0; k<13; k++){
var num = parseInt(a[k]);
formatArrStr += Array(Math.max(4-String(num).length,0)).join(' ')+num+(k==12?'':',');
}
formatArrStr += ']'+(i==12?'':',\n');
}
return formatArrStr;
}
}
});
var editTip = new Vue({
el: '#editTip',
data: {
err: ''
},
methods: {
copyMap: function(){
tip.whichShow = 0;
if(pout.value.trim() != ''){
if(editArea.error) {
this.err = editArea.errors[editArea.error-1];
tip.whichShow = 5
return;
}
try{
pout.select();
document.execCommand("Copy");
tip.whichShow = 6;
}catch(e){
this.err= e;
tip.whichShow = 5;
}
}else{
tip.whichShow = 7;
}
}
},
})
var clear = new Vue({
el: '#clear',
methods: {
clearMap: function(){
map[fullX + 1 + fullY * (fullX + 1)] = -2;
for(var ii=0;ii<fullX + 1 + fullY * (fullX + 1);ii++)map[ii]=0;
ec = eventLayer.getContext('2d');
ec.clearRect(0, 0, 416, 416);
clearTimeout(editArea.formatTimer);
clearTimeout(tip.timer);
pout.value = '';
editArea.mapArr = '';
tip.whichShow = 4;
editArea.error = 0;
}
}
})
var tip = new Vue({
el: '#tip',
data: {
infos: ids[indexs[20][0]],
hasId: true,
isSelectedBlock: false,
geneMapSuccess: false,
timer: null,
msgs: [ //分别编号1,2,3,4,5,6,7,8奇数警告偶数成功
"当前未选择任何图块,请先在右边选择要画的图块!",
"生成地图成功!可点击复制按钮复制地图数组到剪切板",
"生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!",
"地图清除成功!",
"复制失败!",
"复制成功!可直接粘贴到楼层文件的地图数组中。",
"复制失败!当前还没有数据",
"修改成功!可点击复制按钮复制地图数组到剪切板"
],
mapMsg: '',
whichShow: 0,
},
watch: {
infos: {
handler: function(val, oldval){
if(typeof(val) != 'undefined'){
this.infos = val;
if('id' in val){
this.hasId = true;
}else{
this.hasId = false;
}
}
},
deep: true
},
whichShow: function(){
var that = this;
that.mapMsg = '';
that.msgs[4] = "复制失败!"+editTip.err;
clearTimeout(that.timer);
if(that.whichShow){
that.mapMsg = that.msgs[that.whichShow-1];
that.timer = setTimeout(function() {
if(!(that.whichShow%2))
that.whichShow = 0;
}, 5000); //5秒后自动清除successwarn不清除
}
}
}
})
var selectBox = new Vue({
el: '#selectBox',
data: {
isSelected: false
},
watch: {
isSelected: function(){
tip.isSelectedBlock = this.isSelected;
tip.whichShow = 0;
clearTimeout(tip.timer);
}
}
})
</script>
</body>
</html>

View File

@ -1,116 +0,0 @@
# 此文件是每个数字所代表的意思,可被地图生成器读取和使用
# 可以模仿此格式在后面写上任意被识别内容
# 文件格式:
# 编号,图标所在的图片名,图标在图片上的索引从上到下第几个从0开始计算
# 注意中间以小逗号分开;不要有空格;后面可以加任意"#"代表注释
### 0-20 地形 ###
0,terrains,0 # 路面;此项必须在第一条。
1,terrains,1 # 黄色墙(经典墙)
2,terrains,2 # 白色墙
3,terrains,3 # 蓝色墙
4,terrains,4 # 星空
5,terrains,5 # 岩浆
6,terrains,6 # 冰面
7,terrains,15 # 蓝色商店左
8,terrains,16 # 蓝色商店右
9,terrains,17 # 红色商店左
10,terrains,18 # 红色商店右
11,animates,23 # 血网(经过受到伤害)
12,animates,24 # 毒网(经过中毒)
13,animates,25 # 衰网(经过衰弱)
14,animates,26 # 咒网(经过诅咒)
15,animates,31 # 水
# 可自行往后添加
20,autotile,0 # Autotile
### 21-80 物品 ###
# 消耗品
21,items,0 # 黄钥匙
22,items,1 # 蓝钥匙
23,items,2 # 红钥匙
24,items,3 # 绿钥匙
25,items,4 # 铁门钥匙
26,items,6 # 大黄门钥匙(钥匙盒)
27,items,16 # 红宝石
28,items,17 # 蓝宝石
29,items,18 # 绿宝石
30,items,19 # 黄宝石
31,items,20 # 红血瓶
32,items,21 # 蓝血瓶
33,items,22 # 绿血瓶
34,items,23 # 黄血瓶
35,items,50 # 铁剑
36,items,55 # 铁盾
37,items,51 # 银剑
38,items,56 # 银盾
39,items,52 # 骑士剑
40,items,57 # 骑士盾
41,items,53 # 圣剑
42,items,58 # 圣盾
43,items,54 # 神圣剑
44,items,59 # 神圣盾
# 特殊道具
45,items,9 # 怪物手册
46,items,12 # 楼层传送器
47,items,45 # 破墙镐
48,items,44 # 破冰镐
49,items,43 # 炸弹
50,items,13 # 中心对称飞行器
51,items,15 # 上楼器
52,items,14 # 下楼器
53,items,11 # 幸运金币
54,items,41 # 冰冻徽章
55,items,40 # 十字架
56,items,29 # 圣水
57,items,8 # 地震卷轴
58,items,24 # 解毒药水
59,items,25 # 解衰药水
60,items,27 # 解咒药水
61,items,28 # 万能药水
62,items,42 # 屠龙匕首
63,items,46 # 金钱袋
64,items,47 # 绿鞋
65,items,48 # 圣锤
# 可自行往后添加
### 81-120 门、楼梯、传送门 ###
81,terrains,9 # 黄门
82,terrains,10 # 蓝门
83,terrains,11 # 红门
84,terrains,12 # 绿门
85,terrains,13 # 机关门
86,terrains,14 # 铁门
87,terrains,8 # 上楼梯
88,terrains,7 # 下楼梯
89,animates,21 # 传送门
90,animates,19 # 星空传送
91,animates,30 # 上箭头传送
92,animates,28 # 左箭头传送
93,animates,27 # 下箭头传送
94,animates,29 # 右箭头传送
### 121-150 NPC ###
121,npcs,0 # 经典老人
122,npcs,1 # 经典商人
123,npcs,2 # 小偷
124,npcs,3 # 仙子
125,npcs,4 # 神秘男老人
126,npcs,5 # 神秘女老人
127,npcs,6 # 老头
128,npcs,7 # 小孩
129,npcs,8 # 木牌
130,npcs,9 # 经验商店
131,npcs,10 # 金币商店
132,npcs,11 # 公主
# 可以添加更多的NPC图标
# 在此可以继续添加更多的事件,比如单向箭头、感叹号开关、箱子等等
### 201-300 怪物 ###
# 将会按照enemys.png顺序依次读取怪物
# 如201绿色史莱姆202红色史莱姆依次类推

View File

@ -21,7 +21,8 @@ function core() {
'events': {}
}
this.timeout = {
'getItemTipTimeout': null
'getItemTipTimeout': null,
'turnHeroTimeout': null,
}
this.interval = {
'twoAnimate': null,
@ -56,6 +57,7 @@ function core() {
'floorId': null,
'thisMap': null,
'maps': null,
'checkBlock': [], // 显伤伤害
// 勇士状态;自动寻路相关
'heroMoving': false,
@ -106,7 +108,6 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds
core[key] = coreData[key];
}
core.flags = core.clone(core.data.flags);
core.flags.battleAnimate = core.getLocalStorage('battleAnimate', core.flags.battleAnimate);
core.values = core.clone(core.data.values);
core.firstData = core.data.getFirstData();
core.initStatus.shops = core.firstData.shops;
@ -296,7 +297,7 @@ core.prototype.clearStatus = function() {
core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight);
}
core.prototype.resetStatus = function(hero, hard, floorId, maps) {
core.prototype.resetStatus = function(hero, hard, floorId, flags, maps) {
// 停止各个Timeout和Interval
for (var i in core.interval) {
@ -314,6 +315,8 @@ core.prototype.resetStatus = function(hero, hard, floorId, maps) {
// 初始化人物属性
core.status.hero = core.clone(hero);
core.status.hard = hard;
if (core.isset(flags))
core.flags = core.clone(flags);
// 保存页面
core.status.savePage = core.getLocalStorage('savePage', 0);
@ -324,7 +327,7 @@ core.prototype.resetStatus = function(hero, hard, floorId, maps) {
core.prototype.startGame = function (hard, callback) {
console.log('开始游戏');
core.resetStatus(core.firstData.hero, hard, core.firstData.floorId, core.initStatus.maps);
core.resetStatus(core.firstData.hero, hard, core.firstData.floorId, core.flags, core.initStatus.maps);
core.changeFloor(core.status.floorId, null, core.firstData.hero.loc, null, function() {
core.setHeroMoveTriggerInterval();
@ -501,6 +504,10 @@ core.prototype.keyUp = function(keyCode) {
if (core.status.heroStop)
core.ui.drawQuickShop(true);
break;
case 32: // SPACE
if (!core.status.lockControl && core.status.heroStop)
core.getNextItem();
break;
case 37: // UP
break;
case 38: // DOWN
@ -520,12 +527,23 @@ core.prototype.keyUp = function(keyCode) {
}
break;
case 50: // 快捷键2
if (core.status.heroStop && core.hasItem('bomb')) {
if (core.canUseItem('bomb')) {
core.useItem('bomb');
if (core.status.heroStop) {
if (core.hasItem('bomb')) {
if (core.canUseItem('bomb')) {
core.useItem('bomb');
}
else {
core.drawTip('当前不能使用炸弹');
}
}
else {
core.drawTip('当前不能使用炸弹');
else if (core.hasItem('hammer')) {
if (core.canUseItem('hammer')) {
core.useItem('hammer');
}
else {
core.drawTip('当前不能使用圣锤');
}
}
}
break;
@ -626,7 +644,7 @@ core.prototype.getClickLoc = function (x, y) {
}
core.prototype.onclick = function (x, y, stepPostfix) {
console.log("Click: (" + x + "," + y + ")");
// console.log("Click: (" + x + "," + y + ")");
// 非游戏屏幕内
if (x<0 || y<0 || x>12 || y>12) return;
@ -649,6 +667,12 @@ core.prototype.onclick = function (x, y, stepPostfix) {
return;
}
// 开关
if (core.status.event.id == 'switchs') {
core.events.clickSwitchs(x,y);
return;
}
// 设置
if (core.status.event.id == 'settings') {
core.events.clickSettings(x,y);
@ -745,14 +769,14 @@ core.prototype.onmousewheel = function (direct) {
}
// 怪物手册
if (core.status.event.id == 'book') {
if (core.status.lockControl && core.status.event.id == 'book') {
if (direct==1) core.ui.drawEnemyBook(core.status.event.data - 1);
if (direct==-1) core.ui.drawEnemyBook(core.status.event.data + 1);
return;
}
// 存读档
if (core.status.event.id == 'save' || core.status.event.id == 'load') {
if (core.status.lockControl && (core.status.event.id == 'save' || core.status.event.id == 'load')) {
if (direct==1) core.ui.drawSLPanel(core.status.event.data - 1);
if (direct==-1) core.ui.drawSLPanel(core.status.event.data + 1);
return;
@ -807,7 +831,18 @@ core.prototype.setAutomaticRoute = function (destX, destY, stepPostfix) {
return;
}
if (destX == core.status.hero.loc.x && destY == core.status.hero.loc.y && stepPostfix.length==0) {
core.turnHero();
if (core.timeout.turnHeroTimeout==null) {
core.timeout.turnHeroTimeout = setTimeout(function() {
core.turnHero();
clearTimeout(core.timeout.turnHeroTimeout);
core.timeout.turnHeroTimeout = null;
}, 250);
}
else {
clearTimeout(core.timeout.turnHeroTimeout);
core.timeout.turnHeroTimeout = null;
core.getNextItem();
}
return;
}
var step = 0;
@ -960,14 +995,16 @@ core.prototype.automaticRoute = function (destX, destY) {
if ( (scan[direction].x + scan[nextArrow].x) == 0 && (scan[direction].y + scan[nextArrow].y) == 0 ) continue;
}
// 绕过亮灯(因为只有一次通行机会很宝贵)
if(nextId == "light") deepAdd=50;
if(nextId == "light") deepAdd=100;
// 绕过路障
if (nextId.substring(nextId.length-3)=="Net") deepAdd=100;
if (nextId.substring(nextId.length-3)=="Net") deepAdd=core.values.lavaDamage;
// 绕过血瓶
if (!core.flags.potionWhileRouting && nextId.substring(nextId.length-6)=="Potion") deepAdd=20;
// 绕过可能的夹击点
if (nextBlock.block.event.trigger == 'checkBlock') deepAdd=200;
// if (nextBlock.block.event.trigger == 'checkBlock') deepAdd=200;
}
if (core.status.checkBlock[nid]>0)
deepAdd = core.status.checkBlock[nid];
if (nx == destX && ny == destY) {
route[nid] = direction;
@ -1138,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;
});
@ -1415,6 +1453,7 @@ core.prototype.afterBattle = function(id, x, y, callback) {
hint += ",经验+" + core.material.enemys[id].experience;
core.drawTip(hint);
core.updateCheckBlockMap();
// 打完怪物,触发事件
core.events.afterBattle(id,x,y,callback);
@ -1491,6 +1530,8 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback)
core.setHeroLoc('x', heroLoc.x);
core.setHeroLoc('y', heroLoc.y);
core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop');
core.updateCheckBlockMap();
core.updateCheckBlock();
core.updateFg();
}, 15)
});
@ -1992,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];
@ -2028,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) {
@ -2144,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;n<blocks.length;n++) {
var block = blocks[n];
if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) {
if (block.event.cls=='enemys') {
var enemy = core.enemys.getEnemys(block.event.id);
if (core.isset(enemy)) {
var value=0;
if (core.enemys.hasSpecial(enemy.special, 15)) // 领域
value += enemy.value;
if (core.enemys.hasSpecial(enemy.special, 16)) // 夹击
value += 1000000 * block.id;
core.status.checkBlockMap[13*block.x+block.y] = value;
}
}
}
}
}
// 更新领域、显伤点
core.prototype.updateCheckBlock = function() {
if (!core.isset(core.status.thisMap)) return;
if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap();
core.status.checkBlock = [];
for (var x=0;x<13;x++) {
for (var y=0;y<13;y++) {
// 计算(x,y)点伤害
var damage = 0;
if (!core.enemyExists(x,y)) { // 如果该点本身不存在怪物(打死怪物会调用更新)
// 领域
[[-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) {
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);
@ -2178,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);
@ -2242,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;
}
@ -2254,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);
}
}
}
}
}
@ -2341,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');
@ -2368,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;
@ -2845,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()
};
@ -2862,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) {
@ -3038,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')) {
@ -3051,6 +3247,7 @@ core.prototype.updateStatusBar = function () {
} else {
core.statusBar.image.fly.style.opacity = 0.3;
}
core.updateCheckBlock();
core.updateFg();
}

View File

@ -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则寻路算法会自动尽量绕过血瓶
}

View File

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

View File

@ -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 () {
@ -350,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))
@ -451,8 +413,10 @@ events.prototype.disableQuickShop = function (shopId) {
}
////// 降低难度 //////
/*
events.prototype.decreaseHard = function() {
core.drawTip("本塔不支持降低难度!");
/*
if (core.status.hard == 0) {
core.drawTip("当前已是难度0不能再降低难度了");
return;
@ -470,8 +434,8 @@ events.prototype.decreaseHard = function() {
}, function () {
core.ui.drawSettings(false);
});
*/
}
*/
////// 能否使用快捷商店 //////
events.prototype.canUseQuickShop = function(shopIndex) {
@ -506,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) {
@ -604,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) {
}
@ -822,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();

View File

@ -27,14 +27,6 @@ main.floors.MT0 = {
},
"afterOpenDoor": { // 开完门后可能触发的事件列表
},
"checkBlock": [
/****** 领域、夹击检查事件 ******/
// 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件
// 另外如果该点已经存在events事件或changeFloor事件即上面有相同点位置定义则会被覆盖
// afterBattle, afterGetItem, afterOpenDoor则不受影响仍能正常工作
// 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******|
]
}
}

View File

@ -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"
]
}
}

View File

@ -30,7 +30,7 @@ main.floors.sample1 = {
"4,10": [ // 走到中间时的提示
"\t[样板提示]本层楼将会对各类事件进行介绍。",
"左边是一个仿50层的陷阱做法上方是商店、快捷商店的使用方法右上是一个典型的杀怪开门的例子右下是各类可能的NPC事件。",
"本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)\nopenShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调",
"本样板目前支持的事件列表大致有:\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则不受影响仍能正常工作
// 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******|
]
}
}

View File

@ -365,14 +365,6 @@ main.floors.sample2 = {
},
"afterOpenDoor": { // 开完门后可能触发的事件列表
},
"checkBlock": [
/****** 领域、夹击检查事件 ******/
// 所有可能的领域、夹击点均需要在这里给出,否则将不会触发检查事件
// 另外如果该点已经存在events事件或changeFloor事件即上面有相同点位置定义则会被覆盖
// afterBattle, afterGetItem, afterOpenDoor则不受影响仍能正常工作
// 所以 |****** 强烈要求可能的夹击、领域点不要存在自定义事件!! ******|
]
}
}

View File

@ -144,7 +144,7 @@ icons.prototype.init = function () {
'poisonZombie': 55,
'magicDragon': 56,
'octopus': 57,
'fairy': 58,
'darkFairy': 58,
'greenKnight': 59,
},
'items': {

View File

@ -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) {

View File

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

View File

@ -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,7 @@ 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 = [];

BIN
启动服务.exe Normal file

Binary file not shown.

BIN
常用工具/Jint.dll Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +0,0 @@
新增:本地服务器
新增:可视化地图编辑工具
新增便捷P图工具 √
新增支持Autotile √
新增:怪物支持多属性 √
新增:单向箭头、感叹号 √
新增:勇士支持移动;其他事件支持移动 √
快捷道具使用1破2炸3飞读档改为D键 √
更多的默认素材无需P图直接替换即可 √
破甲、反击、净化等效果放全局变量 √
细节优化和Bug修复 √