diff --git a/README.md b/README.md
index bb72521e..68a5c0d2 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,20 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏!
## 更新说明
-### 2017.12.21
+### 2017.12.31 V1.3
+
+* [x] 支持全键盘操作。
+* [x] 便捷PS工具支持更改图片色相。
+* [x] 支持经验升级(进阶/境界塔)。
+* [x] 打败怪物可以进行加点(加点塔)。
+* [x] 增加阻击、N连击等属性;在怪物手册有属性显示。
+* [x] 支持九宫格领域和大范围领域。
+* [x] 增加负伤。
+* [x] 支持各种BGM的播放。
+* [x] 支持不同层使用不同的地面素材;支持多个Autotile同时存在。
+* [x] 许多细节进行了优化,一些已知的Bug进行了修复。
+
+### 2017.12.21 V1.2
* [x] 新增:本地HTTP服务器。
* [x] 新增:可视化地图编辑工具。
@@ -57,7 +70,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏!
* [x] 新增:更多的默认素材,现在对于大多数地图风格无需P图,直接替换即可。
* [x] 添加部分自定义事件,部分细节优化,一些已知的Bug进行了修复。
-### 2017.12.16
+### 2017.12.16 V1.1
* [x] 新增:战斗过程显示,可以在设置中关闭
* [x] 新增:勇士支持48*32(大图)的行走图
@@ -67,7 +80,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏!
* [x] 增添Web的Markdown文档,移除原本的doc和pdf文档。
* [x] 修复若干Bug。
-### 2017.12.9
+### 2017.12.9 V1.0
* [x] 发布初版HTML5魔塔样板
diff --git a/_server/vm.js b/_server/vm.js
new file mode 100644
index 00000000..e198d040
--- /dev/null
+++ b/_server/vm.js
@@ -0,0 +1,266 @@
+// vue 相关处理
+
+document.body.onmousedown = function(e){
+ selectBox.isSelected = false;
+ editor.info = {};
+}
+iconLib.onmousedown = function(e){
+ e.stopPropagation();
+}
+var exportM = new Vue({
+ el: '#exportM',
+
+ methods: {
+ exportMap: function(){
+ editor.updateMap();
+ 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=editor.map[yy][xx];
+ 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(' ');
+ var mapArray = JSON.parse('['+that.mapArr+']');
+ for(var y=0; y<13; y++)
+ for(var x=0; x<13; x++){
+ var num = mapArray[y][x];
+ if(num == 0 )
+ editor.map[y][x] = 0;
+ else if(num >= 400){
+ that.error = 3;
+ editor.map[y][x] = undefined;
+ }else if(typeof(editor.indexs[num][0]) == 'undefined'){
+ that.error = 2;
+ editor.map[y][x] = undefined;
+ }else editor.map[y][x] = editor.ids[[editor.indexs[num][0]]];
+ }
+
+ editor.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(){
+ editor.mapInit();
+ clearTimeout(editArea.formatTimer);
+ clearTimeout(tip.timer);
+ pout.value = '';
+ editArea.mapArr = '';
+ tip.whichShow = 4;
+ editArea.error = 0;
+ }
+ }
+})
+var tip = new Vue({
+ el: '#tip',
+ data: {
+ infos: {},
+ hasId: true,
+ isAutotile: false,
+ isSelectedBlock: false,
+ isClearBlock: false,
+ geneMapSuccess: false,
+ timer: null,
+ msgs: [ //分别编号1,2,3,4,5,6,7,8;奇数警告,偶数成功
+ "当前未选择任何图块,请先在右边选择要画的图块!",
+ "生成地图成功!可点击复制按钮复制地图数组到剪切板",
+ "生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!",
+ "地图清除成功!",
+ "复制失败!",
+ "复制成功!可直接粘贴到楼层文件的地图数组中。",
+ "复制失败!当前还没有数据",
+ "修改成功!可点击复制按钮复制地图数组到剪切板"
+ ],
+ mapMsg: '',
+ whichShow: 0,
+ },
+ watch: {
+ infos: {
+ handler: function(val, oldval){
+ this.isClearBlock = false;
+ if(typeof(val) != 'undefined'){
+ if(val==0) {
+ this.isClearBlock = true;
+ return;
+ }
+ if('id' in val){
+ this.hasId = true;
+ }else{
+ this.hasId = false;
+ }
+ this.isAutotile = false;
+ if(val.images == "autotile" && this.hasId) this.isAutotile = true;
+ }
+ },
+ 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秒后自动清除success,warn不清除
+ }
+ }
+ }
+})
+
+var selectBox = new Vue({
+ el: '#selectBox',
+ data: {
+ isSelected: false
+ },
+ watch: {
+ isSelected: function(){
+ tip.isSelectedBlock = this.isSelected;
+ tip.whichShow = 0;
+ clearTimeout(tip.timer);
+ }
+ }
+})
+
+var bgSelect = new Vue({
+ el: '#bgSelect',
+ data: {
+ bgs: {},
+ selectedBg: 'ground'
+ },
+ watch:{
+ selectedBg: function(){
+ editor.bgY = this.bgs.indexOf(this.selectedBg);
+ editor.drawMapBg();
+ }
+ }
+})
\ No newline at end of file
diff --git a/docs/api.md b/docs/api.md
index 51973cda..d1b609f4 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -4,32 +4,39 @@
可以在chrome浏览器的控制台中(`ctrl+shift+I`,找到Console)中直接进行调用,以查看效果。
+!> **`main.js`:游戏入口。所有其他JS文件都是被此文件加载。**
+
+``` js
+
+```
+
!> **`core.js`:系统核心文件。所有核心逻辑处理都在此文件完成。**
``` js
* core.status.floorId // 获得当前层floorId
* core.status.thisMap // 获得当前层的地图信息
+* core.status.maps // 获得所有楼层的地图信息
+* core.floors // 获得所有楼层的剧本
// ------ 初始化部分 ------
core.init // 初始化
-core.showStartAnimate // 显示开始界面
-core.hideStartAnimate // 隐藏开始界面
+core.showStartAnimate // 显示游戏开始界面
+core.hideStartAnimate // 隐藏游戏开始界面
core.setStartProgressVal // 设置加载进度条进度
core.setStartLoadTipText // 设置加载进度条提示文字
core.loader // 加载图片和音频
core.loadImage // 加载图片
-core.loadSound // 加载音频
-core.loadSoundItem // 加载某一个音频
+core.loadMusic // 加载音频
core.isPlaying // 游戏是否已经开始
core.clearStatus // 清除游戏状态和数据
core.resetStatus // 重置游戏状态和初始数据
-core.startGame // 具体开始游戏
+core.startGame // 开始游戏
* core.restart // 重新开始游戏;此函数将回到标题页面
// ------ 键盘、鼠标事件 ------
core.onKeyDown // 按下某个键时
core.onKeyUp // 放开某个键时
-core.pressKey // 按住某个键不动时
+core.pressKey // 按住某个键时
core.keyDown // 根据按下键的code来执行一系列操作
core.keyUp // 根据放开键的code来执行一系列操作
core.ondown // 点击(触摸)事件按下时
@@ -37,14 +44,14 @@ core.onmove // 当在触摸屏上滑动时
core.onup // 当点击(触摸)事件放开时
core.getClickLoc // 获得点击事件相对左上角的坐标(0到12之间)
core.onclick // 具体点击屏幕上(x,y)点时,执行的操作
-core.onmousewheel // 滑动鼠标滚轮时的操作(楼层传送时可用滚轮切换楼层)
+core.onmousewheel // 滑动鼠标滚轮时的操作
// ------ 自动寻路代码相关 ------
core.clearAutomaticRouteNode // 清除自动寻路路线
core.stopAutomaticRoute // 停止自动寻路操作
core.continueAutomaticRoute // 继续剩下的自动寻路操作
-core.clearContinueAutomaticRoute // 清除剩下的自动寻路列表
-core.setAutomaticRoute // 设置一个自动寻路
+core.clearContinueAutomaticRoute // 清空剩下的自动寻路列表
+core.setAutomaticRoute // 设置自动寻路路线
core.automaticRoute // 自动寻路算法,找寻最优路径
core.fillPosWithPoint // 显示离散的寻路点
core.clearStepPostfix // 清除已经寻路过的部分
@@ -54,13 +61,15 @@ core.stopAutoHeroMove // 停止勇士的自动行走
core.setAutoHeroMove // 设置勇士的自动行走路线
core.autoHeroMove // 让勇士开始自动行走
core.setHeroMoveInterval // 设置行走的效果动画
-core.setHeroMoveTriggerInterval // 设置勇士行走过程中对途经事件的触发检测
-* core.turnHero(direction) // 设置勇士的方向(转向);如果指定了direction则会面向该方向,否则执行一个转向操作。
+core.setHeroMoveTriggerInterval // 设置勇士行走过程中对事件的触发检测
+* core.turnHero(direction) // 设置勇士的方向(转向)
+core.canMoveHero // 勇士能否前往某方向
core.moveHero // 让勇士开始移动
+core.eventMoveHero // 使用事件让勇士移动。这个函数将不会触发任何事件。
core.moveOneStep // 每移动一格后执行的事件。中毒时在这里进行扣血判断。
core.waitHeroToStop(callback) // 停止勇士的一切行动,等待勇士行动结束后,再执行callback回调函数。
core.stopHero // 停止勇士的移动状态。
-core.drawHero // 在hero层绘制勇士。
+core.drawHero // 绘制勇士。
* core.setHeroLoc(name, value) // 设置勇士的位置。name为”direction”,”x”,”y”
* core.getHeroLoc(name) // 获得勇士的位置。
* core.nextX // 获得勇士面对位置的x坐标
@@ -71,42 +80,49 @@ core.drawHero // 在hero层绘制勇士。
* core.battle(id, x, y, force, callback) // 进行战斗;force表示是否强制战斗
core.afterBattle // 战斗完毕
core.trigger(x,y) // 触发x,y点的事件
-* core.changeFloor(floorId, stair, heroLoc, time, callback) // 楼层切换floorId为目标楼层Id,stair可指定为上/下楼梯,time动画时间
-core.mapChangeAnimate // 实际切换的动画效果
-core.clearMap // 清除地图显示
+* core.changeFloor(floorId, stair, heroLoc, time, callback) // 楼层切换。floorId为目标楼层Id,stair可指定为上/下楼梯,time动画时间
+core.mapChangeAnimate // 地图切换动画效果
+core.clearMap // 清除地图
core.fillText // 在某个canvas上绘制一段文字
core.fillRect // 在某个canvas上绘制一个矩形
core.strokeRect // 在某个canvas上绘制一个矩形的边框
+core.drawLine // 在某个canvas上绘制一条线
core.setFont // 设置某个canvas的文字字体
core.setLineWidth // 设置某个canvas的线宽度
core.saveCanvas // 保存某个canvas状态
-core.loadCanvas // 读取某个canvas状态
+core.loadCanvas // 加载某个canvas状态
core.setStrokeStyle // 设置某个canvas边框属性
core.setAlpha // 设置某个canvas的alpha值
core.setOpacity // 设置某个canvas的透明度
core.setFillStyle // 设置某个canvas的绘制属性(如颜色等)
* core.drawMap(mapId, callback) // 绘制某张地图。mapId为地图Id,绘制完毕将执行callback回调函数。
+core.drawAutotile // 绘制Autotile
+core.drawAutotileBlock // 绘制Autotile的某一块
* core.noPassExists(x,y) // 某个点是否不可通行
core.noPass // 某个点是否在区域内且不可通行
* core.npcExists(x,y) // 某个点是否存在NPC
-* core.terrainExists(x,y) // 某个点是否存在指定的地形
+* core.terrainExists(x,y) // 某个点是否存在(指定的)地形
* core.stairExists(x,y) // 某个点是否存在楼梯
* core.nearStair // 当前位置是否在楼梯边
-* core.enemyExists(x,y) // 某个点是否存在怪物
+* core.enemyExists(x,y) // 某个点是否存在(指定的)怪物
* core.getBlock(x, y, floorId, needEnable) // 获得某个点的block。floorId指定目标楼层,needEnable如果为false则即使该点的事件处于禁用状态也将被返回(否则只有事件启用的点才被返回)
core.moveBlock // 显示移动某块的动画,达到{“type”:”move”}的效果
core.animateBlock // 显示/隐藏某个块时的动画效果
-core.addBlock // 将某个块从禁用变成启用状态
+core.showBlock // 将某个块从禁用变成启用状态
core.removeBlock // 将某个块从启用变成禁用状态
core.removeBlockById // 根据block的索引删除该块
core.removeBlockByIds // 一次性删除多个block
core.addGlobalAnimate // 添加一个全局动画
core.removeGlobalAnimate // 删除一个或所有全局动画
core.setGlobalAnimate // 设置全局动画的显示效果
+core.syncGlobalAnimate // 同步所有的全局动画效果
core.setBoxAnimate // 显示UI层某个box的动画(如怪物手册中怪物的动画)
core.drawBoxAnimate // 绘制UI层的box动画
-core.setFg // 色调渐变
-* core.updateFg // 更新全地图的显伤
+core.updateCheckBlock // 更新领域、夹击、阻击的伤害地图
+core.checkBlock // 检查并执行领域、夹击、阻击事件
+core.snipe // 阻击事件(动画效果)
+core.setFg // 更改画面色调
+* core.updateFg // 更新全地图显伤
* core.itemCount // 获得某个物品的个数
* core.hasItem // 是否存在某个物品
* core.setItem // 设置某个物品的个数
@@ -114,23 +130,23 @@ core.setFg // 色调渐变
* core.useItem // 使用某个物品;直接调用items.js中的useItem函数。
* core.canUseItem // 能否使用某个物品。直接调用items.js中的canUseItem函数。
* core.addItem // 增加某个物品的个数
-* core.getItem // 获得某个物品时的事件
+core.getNextItem // 获得面前的物品(轻按)
+* core.getItem // 获得某个物品
* core.drawTip // 左上角绘制一段提示
* core.drawText // 地图中间绘制一段文字
// ------ 系统机制 ------
core.replaceText // 将文字中的${和}(表达式)进行替换
core.calValue // 计算表达式的值
-core.splitText // 字符串自动换行的分割
+core.doEffect // 执行一个表达式的effect操作
+core.splitLines // 字符串自动换行的分割
core.unshift // 向某个数组前插入另一个数组或元素
core.setLocalStorage // 设置本地存储
core.getLocalStorage // 获得本地存储
core.removeLocalStorage // 移除本地存储
-core.clone // 复制一个对象
+core.clone // 深拷贝一个对象
core.formatDate // 格式化时间为字符串
core.setTwoDigits // 两位数显示
-core.win // 获胜;将直接调用events.js中的win函数
-core.lose // 失败;将直接调用events.js中的lose函数
core.debug // 进入Debug模式,攻防血和钥匙都调成很高的数值
core.checkStatus // 判断当前能否进入某个事件
core.openBook // 点击怪物手册时的打开操作
@@ -144,6 +160,7 @@ core.saveData // 存档到本地
core.loadData // 从本地读档
* core.setStatus // 设置勇士属性
* core.getStatus // 获得勇士属性
+core.getLvName // 获得某个等级的名称
* core.setFlag // 设置某个自定义变量或flag
* core.getFlag // 获得某个自定义变量或flag
* core.hasFlag // 是否存在某个自定义变量或flag,且值为true
@@ -151,17 +168,16 @@ core.insertAction // 往当前事件列表之前插入一系列事件
* core.lockControl // 锁定状态栏,常常用于事件处理
* core.unlockControl // 解锁状态栏
* core.isset // 判断某对象是否不为undefined也不会null
-* core.playSound // 播放音频
* core.playBgm // 播放背景音乐
-core.changeSoundStatus // 切换声音状态
-core.enableSound // 启用音效
-core.disableSound // 禁用音效
+* core.pauseBgm // 暂停背景音乐的播放
+* core.resumeBgm // 恢复背景音乐的播放
+* core.playSound // 播放音频
core.show // 动画显示某对象
core.hide // 动画使某对象消失
core.clearStatusBar // 清空状态栏
core.updateStatusBar // 更新状态栏
core.resize // 屏幕分辨率改变后重新自适应
-core.resetSize // 屏幕分辨率改变后重新自适应
+core.domRenderer // 渲染DOM
// ------ core.js 结束 ------
```
@@ -171,53 +187,105 @@ core.resetSize // 屏幕分辨率改变后重新自适应
!> **`enemys.js` 定义了怪物信息。**
``` js
-* core.enemys.getSpecialText // 获得特殊属性的文字
+core.enemys.init // 初始化
+* core.enemys.getEnemys // 获得一个或所有怪物数据
+* core.enemys.hasSpecial // 判断是否含有某特殊属性
+* core.enemys.getSpecialText // 获得所有特殊属性的名称
+* core.enemys.getSpecialHint // 获得每个特殊属性的说明
* core.enemys.getDamage // 获得某个怪物的伤害
-* core.enemys.getExtraDamage // 获得某个怪物的额外伤害(吸血)
-* core.enemys.getCritical // 计算某个怪物的临界值
-* core.enemys.getCriticalDamage // 计算某个怪物的临界减伤
-* core.enemys.getDefDamage // 计算某个怪物的1防减伤
-* core.enemys.calDamage // 实际的伤害计算公式
-core.enemys.getCurrentEnemys // 获得当前层剩下的的怪物列表
+* core.enemys.getExtraDamage // 获得某个怪物的额外伤害
+* core.enemys.getCritical // 临界值计算
+* core.enemys.getCriticalDamage // 临界减伤计算
+* core.enemys.getDefDamage // 1防减伤计算
+* core.enemys.calDamage // 具体的伤害计算公式
+core.enemys.getCurrentEnemys // 获得当前楼层的怪物列表
```
!> **`events.js` 定义了各个事件的处理流程。**
``` js
-* core.events.startGame // 开始游戏
-* core.events.win // 获胜
-* core.events.lose // 失败
-core.events.checkBlock // 检查领域、夹击事件
-core.events.afterChangeFloor // 楼层切换结束时的事件
+core.events.init // 初始化
+core.events.getEvents // 获得一个或所有系统事件类型
+core.events.startGame // 游戏开始事件
+* core.events.setInitData // 不同难度分别设置初始属性
+* core.events.win // 游戏获胜事件
+* core.events.lose // 游戏失败事件
+core.events.afterChangeFloor // 转换楼层结束的事件
core.events.doEvents // 开始执行一系列自定义事件
core.events.doAction // 执行当前自定义事件列表中的下一个事件
-core.events.insertAction // 往当前自定义事件列表前插入若干个事件
+core.events.insertAction // 往当前事件列表之前添加一个或多个事件
core.events.openShop // 打开一个全局商店
-core.events.disableQuickShop // 禁用一个快捷商店
+core.events.disableQuickShop // 禁用一个全局商店
* core.events.canUseQuickShop // 当前能否使用快捷商店
+* core.events.checkLvUp // 检查升级事件
* core.events.useItem // 尝试使用道具
+core.events.addPoint // 加点事件
core.events.afterBattle // 战斗结束后触发的事件
core.events.afterOpenDoor // 开一个门后触发的事件
core.events.passNet // 经过一个路障
-core.events.beforeSaveData // 即将存档前可以执行的操作
-core.events.afterLoadData // 读档后,载入事件前可以执行的操作
+core.events.changeLight // 改变亮灯(感叹号)的事件
+* core.events.afterChangeLight // 改变亮灯之后,可以触发的事件
+* core.events.afterUseBomb // 使用炸弹/圣锤后的事件
+* core.events.beforeSaveData // 即将存档前可以执行的操作
+* core.events.afterLoadData // 读档事件后,载入事件前,可以执行的操作
-// ------ 界面上的点击事件 ------
-core.events.clickAction // 自定义事件处理时,对用户点击的处理
-core.events.clickBook // 怪物手册打开时,对用户点击的处理
-core.events.clickFly // 楼层传送器打开时,对用户点击的处理
-core.events.clickShop // 全局商店打开时,对用户点击的处理
-core.events.clickQuickShop // 快捷商店选项打开时
-core.events.clickToolbox // 工具栏打开时
-core.events.clickSL // 存/读档界面打开时
-core.events.clickSettings // 设置页面打开时
+// ------ 点击事件和键盘事件的处理 ------
+core.events.keyDownCtrl // 按下Ctrl键时(快捷跳过对话)
+core.events.clickConfirmBox // 确认框界面时的点击操作
+core.events.keyUpConfirmBox // 确认框界面时,放开某个键的操作
+core.events.clickAction // 自定义事件时的点击操作
+core.events.keyDownAction // 自定义事件时,按下某个键的操作
+core.events.keyUpAction // 自定义事件时,放开某个键的操作
+core.events.clickBook // 怪物手册界面的点击操作
+core.events.keyDownBook // 怪物手册界面时,按下某个键的操作
+core.events.keyUpBook // 怪物手册界面时,放开某个键的操作
+core.events.clickBookDetail // 怪物手册属性显示界面时的点击操作
+core.events.clickFly // 楼层传送器界面时的点击操作
+core.events.keyDownFly // 楼层传送器界面时,按下某个键的操作
+core.events.keyUpFly // 楼层传送器界面时,放开某个键的操作
+core.events.clickShop // 商店界面时的点击操作
+core.events.keyDownShop // 商店界面时,按下某个键的操作
+core.events.keyUpShop // 商店界面时,放开某个键的操作
+core.events.clickQuickShop // 快捷商店界面时的点击操作
+core.events.keyDownQuickShop // 快捷商店界面时,按下某个键的操作
+core.events.keyUpQuickShop // 快捷商店界面时,放开某个键的操作
+core.events.clickToolbox // 工具栏界面时的点击操作
+core.events.clickToolboxIndex // 选择工具栏界面中某个Index后的操作
+core.events.keyDownToolbox // 工具栏界面时,按下某个键的操作
+core.events.keyUpToolbox // 工具栏界面时,放开某个键的操作
+core.events.clickSL // 存读档界面时的点击操作
+core.events.keyDownSL // 存读档界面时,按下某个键的操作
+core.events.keyUpSL // 存读档界面时,放开某个键的操作
+core.events.clickSwitchs // 系统设置界面时的点击操作
+core.events.keyDownSwitchs // 系统设置界面时,按下某个键的操作
+core.events.keyUpSwitchs // 系统设置界面时,放开某个键的操作
+core.events.clickSettings // 系统菜单栏界面时的点击事件
+core.events.keyDownSettings // 系统菜单栏界面时,按下某个键的操作
+core.events.keyUpSettings // 系统菜单栏界面时,放开某个键的操作
+core.events.clickSyncSave // 同步存档界面时的点击操作
+core.events.keyDownSyncSave // 同步存档界面时,按下某个键的操作
+core.events.keyUpSyncSave // 同步存档界面时,放开某个键的操作
+core.events.clickAbout // “关于”界面时的点击操作
```
-!> `maps.js` 定义了地图,以及每个数字所代表的意义。
+!> `icons.js` 定义了素材ID和它在图片上的索引的对应关系。
+
+!> `items.js` 定义了每个道具的名称,以及使用效果。
+
+``` js
+core.items.init // 初始化
+core.items.getItems // 获得所有道具
+core.items.getItemEffect // “即捡即用类”道具的使用效果
+core.items.getItemEffectTip // “即捡即用类”道具的文字提示
+* core.items.useItem // 使用道具
+* core.items.cauUseItem // 当前能否使用道具
+```
+
+!> `maps.js` 定义了数字-ID的对应关系。
``` js
core.maps.loadFloor // 加载某个楼层(从剧本或存档中)
-core.maps.getBlock // 将数字替换成实际的内容
+core.maps.getBlock // 数字和ID的对应关系
core.maps.addEvent // 向该楼层添加剧本的自定义事件
core.maps.addChangeFloor // 向该楼层添加剧本的楼层转换事件
core.maps.initMaps // 初始化所有地图
@@ -232,16 +300,19 @@ core.ui.closePanel // 结束一切事件和绘制,关闭UI窗口,返回游
core.ui.drawTextBox // 绘制一个对话框
core.ui.drawChoices // 绘制一个选项界面
core.ui.drawConfirmBox // 绘制一个确认/取消的警告页面
+core.ui.drawSwitchs // 绘制系统设置界面
core.ui.drawSettings // 绘制系统菜单栏
core.ui.drawQuickShop // 绘制快捷商店选择栏
-core.ui.drawBattleAnimate // 绘制战斗过程
-core.ui.drawWaiting // 绘制一个“请稍后”页面
-core.ui.drawSyncSave // 绘制存档同步选项
+core.ui.drawBattleAnimate // 绘制战斗动画
+core.ui.drawWaiting // 绘制等待界面
+core.ui.drawSyncSave // 绘制存档同步界面
core.ui.drawPagination // 绘制分页
core.ui.drawEnemyBook // 绘制怪物手册
+core.ui.drawBookDetail // 绘制怪物属性的详细信息
core.ui.drawFly // 绘制楼层传送器
core.ui.drawToolbox // 绘制道具栏
core.ui.drawSLPanel // 绘制存档/读档界面
core.ui.drawThumbnail // 绘制一个缩略图
core.ui.drawAbout // 绘制“关于”界面
+core.ui.drawHelp // 绘制帮助界面
```
diff --git a/docs/element.md b/docs/element.md
index 668c0c2d..50459e42 100644
--- a/docs/element.md
+++ b/docs/element.md
@@ -40,14 +40,15 @@
``` js
enemys.prototype.getSpecialText = function (enemyId) {
if (enemyId == undefined) return "";
- var special = this.enemys[enemyId].special;
+ var enemy = this.enemys[enemyId];
+ var special = enemy.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, 6)) text.push((enemy.n||4)+"连击");
if (this.hasSpecial(special, 7)) text.push("破甲");
if (this.hasSpecial(special, 8)) text.push("反击");
if (this.hasSpecial(special, 9)) text.push("净化");
@@ -59,22 +60,35 @@ enemys.prototype.getSpecialText = function (enemyId) {
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("阻击");
+ if (this.hasSpecial(special, 19)) text.push("自爆");
+ if (this.hasSpecial(special, 20)) text.push("无敌");
return text.join(" ");
}
```
-如果需要双属性,则采用100x+y的写法。例如 103 则视为同时拥有1和3的属性,即先攻且坚固。同理1314为衰弱诅咒双属性。
+多属性可采用数组的写法,比如`'special': [1,3]`视为同时拥有先攻和坚固属性;`'special': [5,10,14,18]`视为拥有3连击、魔防、诅咒、阻击四个属性。
-如果需要三属性,则采用10000x+100y+z的写法。例如71116视为同时拥有7,11和16的属性,即破甲、吸血、夹击。
+本塔支持战斗动画,在`data.js`中存在三个全局选项:`canOpenBattleAnimate`, `showBattleAnimateConfirm`, `battleAnimate`。
+- `canOpenBattleAnimate`代表是否允许用户开启战斗动画。如果你添加了一些自定义属性,且不想修改战斗界面的UI,则可以将其关闭。
+- `showBattleAnimateConfirm`代表是否在游戏开始时给用户提供开启动画的选项。对于一些偏向于萌新的塔,可以开启此项。
+- `battleAnimate`代表是否默认开启战斗动画。此项会被用户存储的设置给覆盖。
+- 如果`canOpenBattleAnimate`为false,则后面两个也强制为false。
-四个乃至更多的属性以此类推。
+怪物可以负伤,在`data.js`的全局变量`enableNegativeDamage`中指定。
-怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。
+下面的`getSpecialHint`函数则给定了每个特殊属性的详细描述。这个描述将在怪物手册中看到。
+
+**打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](event#加点事件)。**
如果`data.js`中的enableExperience为false,即不启用经验的话,怪物手册里将不显示怪物的经验值,打败怪物也不获得任何经验。
拿到幸运金币后,打怪获得的金币将翻倍。
+N连击怪物的special是6,且我们可以为它定义n代表实际连击数。参见样板中剑王的写法。
+
+
+
吸血怪需要在怪物后添加value,代表吸血的比例。

@@ -89,15 +103,21 @@ enemys.prototype.getSpecialText = function (enemyId) {
领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。
-
+领域是十字伤害还是九宫格伤害由data.js中的全局变量`zoneSquare`设定。你也可以对该怪物自行进行设定。
-请注意如果吸血和领域同时存在,则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。**
+`range`选项可选,代表该领域怪的范围,不写则默认为1。
-本塔暂不支持阻击、激光、自爆、退化等属性。
+
+
+阻击怪同样需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。
+
+!> 阻击怪后退的地点不能有任何事件存在,即使是已经被禁用的自定义事件!
+
+请注意如果吸血、领域、阻击中任何两个同时存在,则value会冲突。**因此请勿将吸血、领域或阻击放置在同一个怪物身上。**
如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。
-### 路障、楼梯、传送门
+### 路障,楼梯,传送门
血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。
@@ -123,6 +143,65 @@ floorId指定的是目标楼层的唯一标识符(ID)。

+### 背景音乐
+
+本塔支持BGM和SE的播放。
+
+要播放音乐和音效,你需要将对应的文件放在sounds目录下,然后在main.js中进行定义
+
+``` js
+this.bgms = [ // 在此存放所有的bgm,和文件名一致。第一项为默认播放项
+ // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
+ '058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid'
+];
+this.sounds = [ // 在此存放所有的SE,和文件名一致
+ // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好
+ 'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg'
+]
+```
+
+!> 音频名不能使用中文,不能带空格或特殊字符。
+
+目前BGM支持主流的音乐格式,如mp3, ogg, mid格式等。SE则不支持mid格式的播放。
+
+!> mid格式是通过数学方法模拟出来的音乐效果,质量可能会和实际效果差距较大。
+
+定义完毕后,我们可以调用`playBgm`/`playSound`事件来播放对应的音乐/音效,有关事件的详细介绍请参见[事件](event)。
+
+**另外,考虑到用户的流量问题,将遵循如下规则:**
+- **如果用户当前使用的电脑,则默认开启音乐效果,并播放默认BGM**
+- **如果用户当前使用的手机,且处于Wifi状态,则默认开启音乐效果,并播放默认BGM**
+- **其他情况,将默认关闭音乐效果,只有在用户在菜单栏中点击“音乐开关”后才会播放音乐**
+
+!> iOS平台以及部分浏览器不支持获得当前网络状态,此时即使在使用Wifi也必须要用户点击“音乐开关”才能播放音乐。
+
+### 操作说明
+
+本塔主要支持鼠标(触摸屏)操作和键盘操作。
+
+鼠标(触摸屏)操作说明如下:
+- **点状态栏中图标:** 进行对应的操作
+- **点任意块:** 寻路并移动
+- **点任意块并拖动:** 指定寻路路线
+- **单击勇士:** 转向
+- **双击勇士:** 轻按(仅在轻按开关打开时有效)
+
+键盘操作快捷键如下:
+- **[CTRL]** 跳过对话
+- **[X]** 打开/关闭怪物手册
+- **[G]** 打开/关闭楼层传送器
+- **[S/D]** 打开/关闭存/读档页面
+- **[K]** 打开/关闭快捷商店选择列表
+- **[T]** 打开/关闭工具栏
+- **[ESC]** 打开/关闭系统菜单
+- **[H]** 打开帮助页面
+- **[SPACE]** 轻按(仅在轻按开关打开时有效)
+- **[1]** 快捷使用破墙镐
+- **[2]** 快捷使用炸弹/圣锤
+- **[3]** 快捷使用中心对称飞行器
+
+以上快捷键也能在游戏菜单中的操作说明中看到。
+
diff --git a/docs/event.md b/docs/event.md
index f9cab896..a1525c3c 100644
--- a/docs/event.md
+++ b/docs/event.md
@@ -397,7 +397,9 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不
如果强制战斗失败,则会立刻生命归0并死亡,调用lose函数,接下来的事件不会再被执行。
-强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。强制战斗不会触发任何afterBattle里的事件。
+打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](#加点事件)。
+
+强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。
### openDoor: 开门
@@ -416,8 +418,6 @@ loc指定门的坐标,floorId指定门所在的楼层ID。如果是当前层
如果loc所在的点既不是门也不是墙壁,则忽略本事件。
-openDoor不会触发任何afterOpenDoor里的事件。
-
### changeFloor: 楼层切换
在事件中也可以对楼层进行切换。一个比较典型的例子就是TSW中,勇士在三楼的陷阱被扔到了二楼,就是一个楼层切换事件。
@@ -553,6 +553,26 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate
不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。
+### playBgm: 播放背景音乐
+
+使用playBgm可以播放一个背景音乐。
+
+使用方法:`{"type": "playBgm", "name": "bgm.mp3"}`
+
+值得注意的是,额外添加进文件的背景音乐,需在main.js中this.bgms里加载它。
+
+目前支持mp3/ogg/wav/mid等多种格式的音乐播放。
+
+有关BGM播放的详细说明参见[背景音乐](element#背景音乐)
+
+### pauseBgm: 暂停背景音乐
+
+使用`{"type": "pauseBgm"}`可以暂停背景音乐的播放。
+
+### resumeBgm: 恢复背景音乐
+
+使用`{"type": "resumeBgm"}`可以恢复背景音乐的播放。
+
### playSound: 播放音效
使用playSound可以立刻播放一个音效。
@@ -561,10 +581,6 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate
值得注意的是,如果是额外添加进文件的音效,则需在main.js中this.sounds里加载它。
-!> 自定义添加的音效名请勿包含`-`,否则将无法正常使用!
-
-由于考虑到用户流量问题,每个额外音效最好不超过20KB。
-
### win: 获得胜利
`{"type": "win", "reason": "xxx"}` 将会直接调用events.js中的win函数,并将reason作为参数传入。
@@ -756,6 +772,43 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
// ……
```
+## 加点事件
+
+打败怪物后可以进行加点。
+
+如果要对某个怪物进行加点操作,则首先需要修改该怪物的点数值,即在怪物定义的后面添加`point`,代表怪物本身的加点数值。
+
+``` js
+... 'def': 0, 'money': 1, 'experience': 1, 'special': 0, 'point': 1}, // 在怪物后面添加point代表怪物的加点数
+```
+
+然后在`events.js`文件中找到`addPoint`函数。它将返回一个choices事件。修改此函数为我们需要的加点项即可。
+
+``` js
+////// 加点 //////
+events.prototype.addPoint = function (enemy) {
+ var point = enemy.point; // 获得该怪物的point
+ if (!core.isset(point) || point<=0) return [];
+
+ // 加点,返回一个choices事件
+ return [
+ {"type": "choices",
+ "choices": [ // 提供三个选项:对于每一点,攻击+1/防御+2/生命+200
+ {"text": "攻击+"+(1*point), "action": [
+ {"type": "setValue", "name": "status:atk", "value": "status:atk+"+(1*point)}
+ ]},
+ {"text": "防御+"+(2*point), "action": [
+ {"type": "setValue", "name": "status:def", "value": "status:def+"+(2*point)}
+ ]},
+ {"text": "生命+"+(200*point), "action": [
+ {"type": "setValue", "name": "status:hp", "value": "status:hp+"+(200*point)}
+ ]},
+ ]
+ }
+ ];
+}
+```
+
## 全局商店
我们可以采用上面的choices方式来给出一个商店。这样的商店确实可以有效地进行操作,但是却是"非全局"的,换句话说,只有在碰到NPC的时候才能触发商店事件。
@@ -776,7 +829,6 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
// 上面的例子是50层商店的计算公式。你也可以写任意其他的计算公式,只要以times作为参数即可。
// 例如: "need": "25" 就是恒定需要25金币的商店; "need": "20+2*times" 就是第一次访问要20金币,以后每次递增2金币的商店。
// 如果是对于每个选项有不同的计算公式,写 "need": "-1" 即可。可参见下面的经验商店。
-
"text": "勇敢的武士啊,给我${need}金币就可以:", // 显示的文字,需手动加换行符。可以使用${need}表示上面的need值。
"choices": [ // 商店的选项
{"text": "生命+800", "effect": "status:hp+=800"},
@@ -784,10 +836,12 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
{"text": "攻击+4", "effect": "status:atk+=4"},
{"text": "防御+4", "effect": "status:def+=4"},
{"text": "魔防+10", "effect": "status:mdef+=10"}
- // effect只能对status和item进行操作,不能修改flag值。且其中间只能用+=符号(也就是只能增加某个属性或道具)
+ // effect只能对status和item进行操作,不能修改flag值。
+ // 必须是X+=Y的形式,其中Y可以是一个表达式,以status:xxx或item:xxx为参数
// 其他effect样例:
// "item:yellowKey+=1" 黄钥匙+1
// "item:pickaxe+=3" 破墙镐+3
+ // "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍
]
},
"expShop1": { // 商店唯一ID
@@ -800,13 +854,13 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
"choices": [
// 在choices中写need,可以针对每个选项都有不同的需求。
// 这里的need同样可以以times作为参数,比如 "need": "100+20*times"
- {"text": "等级+1", "need": "100", "effect": "status:hp+=1000;status:atk+=7;status:def+=7"},
+ {"text": "等级+1", "need": "100", "effect": "status:lv+=1;status:hp+=1000;status:atk+=7;status:def+=7"},
// 多个effect直接以分号分开即可。如上面的意思是生命+1000,攻击+7,防御+7。
{"text": "攻击+5", "need": "30", "effect": "status:atk+=5"},
{"text": "防御+5", "need": "30", "effect": "status:def+=5"},
]
- }
-}
+ },
+},
```
全局商店全部定义在`data.js`中的shops一项里。
@@ -887,37 +941,70 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用
当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。
+## 经验升级(进阶/境界塔)
+
+本塔也支持经验升级,即用户杀怪获得经验后,可以到达某些数值自动进阶,全面提升属性。
+
+要经验升级,你需要先在`data.js`中的全局变量中启用。你需要将`enableExperience`启用经验,且`enableLevelUp`启用进阶。同时你也可以将`enableLv`置为true以在状态栏中显示当前等级(境界)。
+
+同时,你还需要在`data.js`中的`levelUp`来定义每一个进阶所需要的生命值,以及进阶时的效果。
+
+``` js
+"levelUp": [ // 经验升级所需要的数值,是一个数组
+ {}, // 第一项为初始等级,可以简单留空,也可以写name
+
+ // 每一个里面可以含有三个参数 need, name, effect
+ // need为所需要的经验数值,是一个正整数。请确保need所需的依次递增
+ // name为该等级的名称,也可以省略代表使用系统默认值;本项将显示在状态栏中
+ // effect为本次升级所执行的操作,可由若干项组成,由分号分开
+ // 其中每一项写法和上面的商店完全相同,同样必须是X+=Y的形式,Y是一个表达式,同样可以使用status:xxx或item:xxx代表勇士的某项数值/道具个数
+ {"need": 20, "name": "第二级", "effect": "status:hp+=2*(status:atk+status:def);status:atk+=10;status:def+=10"}, // 先将生命提升攻防和的2倍;再将攻击+10,防御+10
+
+ // effect也允许写一个function,代表本次升级将会执行的操作,比如可以显示一段提示文字,或者触发一个事件
+ {"need": 40, "effect": function () {
+ core.drawText("恭喜升级!");
+ core.status.hero.hp *= 2;
+ core.status.hero.atk += 100;
+ core.status.hero.def += 100;
+ }},
+
+ // 依次往下写需要的数值即可
+]
+```
+
+`levelUp`是一个数组,里面分别定义了每个等级的信息。里面每一项是一个object,主要有三个参数`need`, `name`, `effect`
+- `need` 该等级所需要的经验值,是一个正整数。请确保数组中的need依次递增。
+- `name` 该等级的名称,比如“佣兵下级”等。该项可以忽略,以使用系统默认的等级。该项将显示在状态栏中。
+- `effect` 为本次等级执行的操作。它有两种写法:字符串,或函数。
+ - 如果`effect`为字符串,则和上面的全局商店的写法完全相同。可由分号分开,每一项为X+=Y的形式,X为你要修改的勇士属性/道具个数,Y为一个表达式。
+ - 如果`effect`为函数,则也允许写一个`function`,来代表本次升级将会执行的操作。
+
## 开始,难度分歧,获胜与失败
-游戏开始时将调用`events.js`中的startGame函数。
+游戏开始时将调用`events.js`中的`startGame`函数。
-它将显示`data.js`中的startText内容(可以修改成自己的),并正式开始游戏。
+它将显示`data.js`中的startText内容(可以修改成自己的),提供战斗动画开启选择,设置初始福利,并正式开始游戏。
+
+我们可以修改`setInitData`函数来对于不同难度分别设置初始属性。
其参数hard为以下三个字符串之一:`"Easy"`, `"Normal"`, `"Hard"`,分别对应三个难度。针对不同的难度,我们可以设置一些难度分歧。
``` js
-////// 游戏开始事件 //////
-events.prototype.startGame = function (hard) {
-
- if (core.status.isStarting) return;
- core.status.isStarting = true;
-
- core.hideStartAnimate(function() {
- core.drawText(core.clone(core.firstData.startText), function() {
- core.startGame(hard);
- if (hard=='Easy') { // 简单难度
- core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
- // 可以在此设置一些初始福利,比如设置初始生命值可以调用:
- // core.setStatus("hp", 10000);
- }
- if (hard=='Normal') { // 普通难度
- core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
- }
- if (hard=='Hard') { // 困难难度
- core.setFlag('hard', 3); // 可以用flag:hard来获得当前难度
- }
- });
- })
+////// 不同难度分别设置初始属性 //////
+events.prototype.setInitData = function (hard) {
+ if (hard=='Easy') { // 简单难度
+ core.setFlag('hard', 1); // 可以用flag:hard来获得当前难度
+ // 可以在此设置一些初始福利,比如设置初始生命值可以调用:
+ // core.setStatus("hp", 10000);
+ // 赠送一把黄钥匙可以调用
+ // core.setItem("yellowKey", 1);
+ }
+ if (hard=='Normal') { // 普通难度
+ core.setFlag('hard', 2); // 可以用flag:hard来获得当前难度
+ }
+ if (hard=='Hard') { // 困难难度
+ core.setFlag('hard', 3); // 可以用flag:hard来获得当前难度
+ }
}
```
diff --git a/docs/img/domainenemy.png b/docs/img/domainenemy.png
deleted file mode 100644
index b74eabcb..00000000
Binary files a/docs/img/domainenemy.png and /dev/null differ
diff --git a/docs/img/nattack.png b/docs/img/nattack.png
new file mode 100644
index 00000000..011b0c55
Binary files /dev/null and b/docs/img/nattack.png differ
diff --git a/docs/img/ps.png b/docs/img/ps.png
index 9e0837e3..9e28c864 100644
Binary files a/docs/img/ps.png and b/docs/img/ps.png differ
diff --git a/docs/img/zone.png b/docs/img/zone.png
new file mode 100644
index 00000000..e5577a9f
Binary files /dev/null and b/docs/img/zone.png differ
diff --git a/docs/index.html b/docs/index.html
index e445274a..8065a5e4 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -2,7 +2,7 @@
-
+ HTML5魔塔样板
@@ -15,7 +15,7 @@
window.$docsify = {
homepage: 'index.md',
loadSidebar: true,
- name: 'H5魔塔样板',
+ name: 'HTML5魔塔样板',
repo: 'https://github.com/ckcz123/mota-js',
// basepath: '../docs/',
diff --git a/docs/personalization.md b/docs/personalization.md
index 08328a78..d63c4883 100644
--- a/docs/personalization.md
+++ b/docs/personalization.md
@@ -6,7 +6,7 @@
所有素材的图片都在`images`目录下。
- `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。
-- `autotile.png` 为Autotile块。全塔只支持一种Autotile,其ID为20。
+- `autotile.png` 为Autotile块。
- `enemys.png` 为所有怪物的图片。其对应的数字,从上至下依次是会从201开始计算(即,绿色史莱姆为201,小蝙蝠为205,依次类推)。请注意,动画效果为两帧,一般是原始四帧中的1和3。(四帧中12相同,34相同,因此只取1和3即可)
- `heros.png` 为勇士行走图。
- `items.png` 为所有道具的图标。
@@ -17,7 +17,7 @@
### 使用预定义的素材
-在images目录的“默认素材”下给定了若干预定义的自定义素材。包括野外(草地),星空,木板等等都已经被预先给定。
+在images目录的“默认素材”下给定了若干预定义的自定义素材。
如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。
@@ -29,6 +29,8 @@
我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。
+便捷PS工具同样支持图片色相的修改,和RMXP几乎完全相同。
+
用这种方式,我们能极快地替换或素材,包括需要新增的怪物。
### 添加素材到游戏
@@ -37,63 +39,74 @@
这是因为,该素材没有被定义,无法被游戏所识别。
+#### 素材的机制
+
+本塔所有的素材都拥有三个属性:**ID**,**索引**,**数字**。
+- **ID** 为该素材的唯一标识符,任何两个素材的ID都不能相同。
+- **索引** 为该素材的在对应图片上的图标索引,即该素材是图片上的第几个。
+- **数字** 为该素材的对应数字,以方便地图的生成和存储。
+
+**`ID-索引` 对应关系定义在icons.js文件中。该文件将唯一确定一个ID在图片上所在的位置。**
+
+**`ID-数字` 对应关系定义在maps.js文件的getBlock函数中。该函数将唯一确定一个ID对应的数字是多少。**
+
+如果需要添加一个素材到游戏,则必须为其分配一个唯一标识符,并同时修改`icons.js`和`maps.js`两个文件。
+
+
#### 新添加自定义地形(路面、墙壁等)
如果你在terrains.png中新增了一行:
-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
-```
+1. 指定一个唯一的英文ID,不能和现有的重复。
+2. 进入icons.js,在terrains分类下进行添加索引(对应图标在图片上的位置,即index)
+
+**如果你无须在游戏内使用本地形,而仅仅是将其作为“背景图”使用,则操作如下:**
+3. 修改对应楼层的剧本文件的`defaultGround`项,改成新的ID。
+
+**如果你要在游戏内使用本地形,则操作如下:**
+3. 指定一个数字,在maps.js的getBlock函数下类似进行添加。
+
+#### 新添加Autotile
+
+如果你需要新增一个Autotile:
+
+1. 将新的Autotile图片复制到images目录下。
+2. 进入icons.js,在autotile分类下进行添加该文件的名称,索引简单的写0。
+3. 指定一个数字,在maps.js的getBlock函数下类似进行添加。
+
+!> Autotile的ID和文件名完全相同!且其ID/文件名不能含有中文、空格或特殊字符。
#### 新添加道具
如果你需要新增一个未被定义的道具:
-1. 指定一个唯一的英文ID,不能和items中现有的重复。
-2. 进入icons.js,在items分类下进行添加(对应图标在图片上的位置,即index)
+1. 指定一个唯一的英文ID,不能和现有的重复。
+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帧**。
+你可以通过便捷PS工具的“更改色相”来将红头怪变成橙头怪等。
+
然后执行如下操作:
1. 指定一个唯一的英文ID,不能和enemys中现有的重复。
-2. 进入icons.js,在enemys分类下进行添加(对应图标在图片上的位置,即index)
+2. 进入icons.js,在enemys分类下进行添加索引(对应图标在图片上的位置,即index)
3. 在maps.js的getBlock下继续进行添加。请注意其ID为200开始的顺序,即如果新增一行为261,依次类推
4. 在enemys.js中仿照其他怪物,来添加怪物的信息。
-``` 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指定ID,在icons.js中指定ID-index关系,在maps.js中指定ID-数字的关系,即可。
+1. 指定一个唯一的英文ID,不能和现有的重复。
+2. 进入icons.js,在npcs分类下进行添加索引(对应图标在图片上的位置,即index)
+3. 指定一个数字,在maps.js的getBlock下类似进行添加。
### 地图生成器使用自定义素材
@@ -176,18 +189,24 @@ enemys.prototype.getExtraDamage = function (monster) {
}
// ... 下略
```
-3. 免疫领域、夹击效果:在`core.js`中,找到updateCheckBlock函数,并编辑成如果有神圣盾标记,则直接返回。
+3. 免疫领域、夹击、阻击效果:在`core.js`中,找到checkBlock函数,并编辑成如果有神圣盾标记,则将伤害变成0。
``` js
-// 更新领域、显伤点
-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;
+// 检查领域、夹击、阻击事件
+core.prototype.checkBlock = function () {
+ var x=core.getHeroLoc('x'), y=core.getHeroLoc('y');
+ var damage = core.status.checkBlock.damage[13*x+y];
+ if (damage>0) {
+ if (core.hasFlag("shield5")) damage = 0; // 如果存在神圣盾,则将伤害变成0
+ core.status.hero.hp -= damage;
+
+ // 检查阻击事件
+ var snipe = [];
+ var scan = {
+ 'up': {'x': 0, 'y': -1},
+ 'left': {'x': -1, 'y': 0},
+ 'down': {'x': 0, 'y': 1},
+ 'right': {'x': 1, 'y': 0}
+ }
// ... 下略
```
4. 如果有更高的需求,例如想让吸血效果变成一半(如异空间),则还是在上面这些地方进行对应的修改即可。
@@ -196,42 +215,15 @@ core.prototype.updateCheckBlock = function() {
如果你对现有的怪物不满意,想自行添加怪物属性(例如让怪物拥有双属性乃至更多属性),也是可以的。具体参见`enemys.js`文件。
-你需自己指定一个special数字,修改getSpecialText函数。
+你需自己指定一个special数字,修改getSpecialText函数(属性名)和getSpecialHint函数(属性提示文字)。
如果要修改伤害计算公式,请修改下面的calDamage函数。请注意,如果无法战斗,该函数必须返回`999999999`。
-因此无敌属性可以这样设置:
-
-先给无敌属性指定一个数字,例如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 (this.hasSpecial(mon_special, 18) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架
- return 999999999; // 返回无限大
-
- // 魔攻
- if (this.hasSpecial(mon_special, 2)) hero_def = 0;
-// ... 下略
-```
-
对于吸血怪的额外伤害计算在getExtraDamage中。
对于毒衰弱怪物的战斗后结算在`events.js`中的afterBattle函数中。
-对于领域、夹击怪物的检查在`events.js`中的checkBlock函数中。
+对于领域、夹击、阻击怪物的检查在`events.js`中的checkBlock函数中。
`getCritical`, `getCriticalDamage`和`getDefDamage`三个函数依次计算的是该怪物的临界值、临界减伤和1防减伤。也可以适当进行修改。
diff --git a/docs/start.md b/docs/start.md
index 127fd819..10300b46 100644
--- a/docs/start.md
+++ b/docs/start.md
@@ -36,7 +36,23 @@
然后将楼层名改为MT1,floorId改名为MT1;title可以改成任意内容,将在切换楼层时进行显示(比如可以改成“1层小塔”)。
-具体样板文件的每个要素都有详细的注释。我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。
+具体样板文件的每个要素如下:
+- **`floorId`** 楼层唯一标识符;必须和文件名,以及 `main.floors.xxx` 完全一致
+- **`title`** 楼层中文名,将在切换楼层时进行显示
+- **`canFlyTo`** 当前楼层可否被楼传器飞到。如果该层不能飞到,则也在该层也不允许使用楼传器。
+- **`canUseQuickShop`** 当前楼层可否使用快捷商店。
+- **`defaultGround`** 该层的背景(地面)素材。需要是在`icon.js`里`terrains`中定义的一个ID,如`ground`, `grass2`等等。
+- **`color`** 该层的画面色调。本项可选,如果不写则色调为默认值(无色调),否则是一个RGBA数组,比如`[255,0,0,0.3]`等。
+- **`bgm`** 到达该层后默认播放的BGM。本项可忽略。
+- **`map`** 本层地图,需要是13x13数组,建议使用地图生成器或者可视化地图编辑器制作。
+- **`firstArrive`** 第一次到该楼层触发的事件
+- **`events`** 该楼的所有可能事件列表
+- **`changeFloor`** 楼层转换事件;该事件不能和上面的events有冲突(同位置点),否则会被覆盖
+- **`afterBattle`** 战斗后可能触发的事件列表
+- **`afterGetItem`** 获得道具后可能触发的事件列表
+- **`afterOpenDoor`** 开完门后可能触发的事件列表
+
+我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。
换句话说,只需要简单的复制操作,我们就可以新建一个剧本了。
diff --git a/drawMapGUI.html b/drawMapGUI.html
index be032623..78373784 100644
--- a/drawMapGUI.html
+++ b/drawMapGUI.html
@@ -8,9 +8,9 @@
font-family: Roboto,"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;;
background-color: #F5F5F5;
}
- ::-webkit-scrollbar {
+ /* ::-webkit-scrollbar {
width: 5px;
- }
+ } */
.main {
max-width: 100%;
min-height: 500px;
@@ -26,7 +26,7 @@
left: 5px;
top: 10px;
width: 435px;
- height: 400px;
+ height: 630px;
}
#editArea{
@@ -108,7 +108,7 @@
border: 1px solid #ccc;
border-radius: 2px;
font-size: 15px;
- line-height: 16px;
+ line-height: 14px;
}
.files {
width: 50%;
@@ -129,6 +129,16 @@
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
}
+ #bgSelect{
+ width: 50%;
+ height: 100px;
+ margin-top: 10px;
+ }
+ #bgSelect span{
+ display: block;
+ font-size: 14px;
+ line-height: 30px;
+ }
#printOut{
margin-top: 10px;
height: 20px;
@@ -176,7 +186,7 @@
height: 620px;
left: 5px;
top: 5px;
- overflow:auto;
+ overflow: auto;
}
.gameCanvas {
position: absolute;
@@ -186,15 +196,16 @@
/* top:0;
left:320px; */
z-index:75;
- width:32px;height:32px;
-
- margin:-2px 0 0 -2px;
+ width:26px;
+ height:26px;
+ margin: 3px 0 0 3px;
padding:0;
/* display: none; */
-
+ box-sizing: border-box;
background-color:rgba(255, 255, 255, 0.0);
- border: 2px solid #30DFF3;
- box-shadow: 0px 0px 2px #30DFF3;
+ border: 1px solid #000;
+ box-shadow: 0 0 0 2px #fff,
+ 0 0 0 3px #000;
}
.warnText{
color: #D50000;
@@ -293,14 +304,22 @@
-
-
-
-
-
{{ errors[error-1] }}
+
+
+
+
+
+
{{ errors[error-1] }}
+
+
+
+
-
@@ -315,11 +334,14 @@
-
-
-
+
+
-
-
-
diff --git a/index.html b/index.html
index fa25ac90..d59b0e43 100644
--- a/index.html
+++ b/index.html
@@ -114,5 +114,6 @@
+