diff --git a/README.md b/README.md index bb72521e..db47a83d 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! │ └─ *.png # 对应的某个具体的图片素材 ├── /libs/ # JS源代码目录 │ ├─ /floors/ # 剧本文件,记录了每个地图的数据和事件 +│ ├─ /thirdparty/ # 游戏所用到的第三方库文件 │ ├─ core.js # 系统核心文件 │ ├─ data.js # 记录了勇士的初始化信息、各个全局变量和全局Flag值 │ ├─ enemys.js # 记录了怪物的信息,包括怪物的数据和特殊属性、伤害计算公式、临界值计算等。 @@ -44,7 +45,21 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! ## 更新说明 -### 2017.12.21 +### 2018.1.1 V1.3 + +* [x] 支持全键盘操作。 +* [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 +72,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! * [x] 新增:更多的默认素材,现在对于大多数地图风格无需P图,直接替换即可。 * [x] 添加部分自定义事件,部分细节优化,一些已知的Bug进行了修复。 -### 2017.12.16 +### 2017.12.16 V1.1 * [x] 新增:战斗过程显示,可以在设置中关闭 * [x] 新增:勇士支持48*32(大图)的行走图 @@ -67,7 +82,7 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! * [x] 增添Web的Markdown文档,移除原本的doc和pdf文档。 * [x] 修复若干Bug。 -### 2017.12.9 +### 2017.12.9 V1.0 * [x] 发布初版HTML5魔塔样板 diff --git a/_server/css/editor.css b/_server/css/editor.css index f202050d..4021a028 100644 --- a/_server/css/editor.css +++ b/_server/css/editor.css @@ -24,73 +24,23 @@ body{ width: 435px; height: 630px; } -.nav { - width: 100%; - height: 40px; - margin-top: 5px; - box-sizing: border-box; -} -.nav a{ - display: block; - float: left; - line-height: 30px; - font-size: 14px; - text-align: center; - color: #424242; - margin: 0 10px; - padding: 0 3px ; - list-style: none; - box-sizing: border-box; - cursor: pointer; - /* border-bottom: 3px solid #009688; */ -} -.is-selected { - border-bottom: 3px solid #009688; -} -#left .tabs{ - position: absolute; - top: 50px; - left: 0; - width: 435px; - height: 535px; -} -#left .card{ - width: 100%; - height: 535px; - position: absolute; - left: 0; - top: 10px; - border-top: 1px solid #ccc; -} -#arrEditor{ - z-index: 8; -} -.card .lineTitle { - position: absolute; - top: -12px; - left: 20px; - padding: 0 5px; - font-size: 14px; - color: #03A9F4; - background-color: #F5F5F5; - - z-index: 10; -} + #editArea{ + position: absolute; width: 100%; height: 70%; + left: 0; + top: 0; /* padding: 10px 5px; */ box-sizing: border-box; - margin-top: 15px; } #pout{ - position: absolute; - left: 22px; - top: 37px; display: block; width: 410px; - height: 300px; + height: 100%; box-sizing: border-box; + margin-left: 22px; + margin-top: 23px; line-height: 20px; font-size: 12.3px; font-family: 'Lucida Console', Monaco, monospace; @@ -118,26 +68,6 @@ body{ margin-right: 20px; margin-top: 5px; } -.files { - width: 100%; - 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; - box-shadow: inset 0 1px 1px rgba(0,0,0,.075); -} -/* ==================================== */ #mid{ position: absolute; left: 448px; @@ -175,13 +105,32 @@ body{ font-size: 15px; line-height: 14px; } +.files { + width: 50%; + height: 120px; + /* padding: 10px; */ + margin-top: 15px; +} +.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; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); +} #bgSelect{ width: 50%; height: 100px; margin-top: 10px; } #bgSelect span{ - display: block; + /* display: block; */ font-size: 14px; line-height: 30px; } @@ -198,7 +147,7 @@ body{ min-width: 50px; padding: 0 5px; display: inline-block; - + margin-top: 5px; font-size: 14px; font-weight: 400; /* text-transform: uppercase; */ @@ -218,8 +167,6 @@ body{ 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: 900px; diff --git a/_server/vm.js b/_server/vm.js index 19559607..4c1644bf 100644 --- a/_server/vm.js +++ b/_server/vm.js @@ -187,7 +187,7 @@ var tip = new Vue({ isClearBlock: false, geneMapSuccess: false, timer: null, - msgs: [ //分别编号1,2,3,4,5,6,7,8;奇数警告,偶数成功 + msgs: [ //分别编号1,2,3,4,5,6,7,8,9,10;奇数警告,偶数成功 "当前未选择任何图块,请先在右边选择要画的图块!", "生成地图成功!可点击复制按钮复制地图数组到剪切板", "生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!", @@ -195,7 +195,9 @@ var tip = new Vue({ "复制失败!", "复制成功!可直接粘贴到楼层文件的地图数组中。", "复制失败!当前还没有数据", - "修改成功!可点击复制按钮复制地图数组到剪切板" + "修改成功!可点击复制按钮复制地图数组到剪切板", + "选择背景图片失败!文件名格式错误或图片不存在!", + "更新背景图片成功!", ], mapMsg: '', whichShow: 0, @@ -255,60 +257,31 @@ var bgSelect = new Vue({ el: '#bgSelect', data: { bgs: {}, - selectedBg: 'ground' + selectedBg: 'ground', + imgname: '' }, watch:{ selectedBg: function(){ editor.bgY = this.bgs.indexOf(this.selectedBg); editor.drawMapBg(); } - } -}) - -var editFile4map = new Vue({ - el: '#editFile4map', - data: { - selected: 'untitle', - options:[], - filelist: [], - }, - watch: { - filelist: function(val){ - if(val){ - var oval = val.length ? JSON.parse(JSON.stringify(val)) : []; - // for(var i=0; i 上次更新时间:* {docsify-updated} * + 所有系统支持的API都列在了这里。所有可能被用到的API都在前面用\*标记。 可以在chrome浏览器的控制台中(`ctrl+shift+I`,找到Console)中直接进行调用,以查看效果。 +!> **`main.js`:游戏入口,所有其他JS文件都是被此文件加载。** + +``` js +main.init // 初始化 +main.loaderJs // 动态加载所有核心JS文件 +main,loaderFloors // 动态加载所有楼层(剧本) +main.loadMod // 加载某一个JS文件 +main.loadFloor // 加载某一个楼层 +main.setMainTipsText // 加载过程提示 +window.onresize // 窗口大小变化时 +main.dom.body.onkeydown // 在界面上按下某按键时 +main.dom.body.onkeydown // 在界面上放开某按键时 +main.dom.body.onselectstart // 开始选择时 +main.dom.data.onmousedown // 鼠标按下时 +main.dom.data.onmousemove // 鼠标移动时 +main.dom.data.onmouseup // 鼠标放开时 +main.dom.data.onmousewheel // 鼠标滑轮滚动时 +main.dom.data.ontouchstart // 手指在触摸屏开始触摸时 +main.dom.data.ontouchmove // 手指在触摸屏上移动时 +main.dom.data.ontouchend // 手指离开触摸屏时 +main.statusBar.image.book.onclick // 点击状态栏中的怪物手册时 +main.statusBar.image.fly.onclick // 点击状态栏中的楼层传送器时 +main.statusBar.image.toolbox.onclick // 点击状态栏中的工具箱时 +main.statusBar.image.shop.onclick // 点击状态栏中的快捷商店时 +main.statusBar.image.save.onclick // 点击状态栏中的存档按钮时 +main.statusBar.image.load.onclick // 点击状态栏中的读档按钮时 +main.statusBar.image.settings.onclick // 点击状态栏中的系统菜单时 +main.dom.playGame.onclick // 点击“开始游戏”时 +main.dom.loadGame.onclick // 点击“载入游戏”时 +main.dom.aboutGame.onclick // 点击“关于本塔”时 +main.dom.easyLevel.onclick // 点击“简单难度”时 +main.dom.normalLevel.onclick // 点击“普通难度”时 +main.dom.hardLevel.onclick // 点击“困难难度”时 +``` + !> **`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 +75,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 +92,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 +111,48 @@ 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.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 +160,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 +190,7 @@ core.saveData // 存档到本地 core.loadData // 从本地读档 * core.setStatus // 设置勇士属性 * core.getStatus // 获得勇士属性 +core.getLvName // 获得某个等级的名称 * core.setFlag // 设置某个自定义变量或flag * core.getFlag // 获得某个自定义变量或flag * core.hasFlag // 是否存在某个自定义变量或flag,且值为true @@ -151,17 +198,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,58 +217,111 @@ 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 // 初始化所有地图 core.maps.save // 将当前地图重新变成数字,以便于存档 core.maps.load // 将存档中的地图信息重新读取出来 +core.maps.getMapArray // 将当前地图重新变成二维数组形式 ``` !> `ui.js` 定义了各种界面的绘制。 @@ -232,16 +331,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..ef764af5 100644 --- a/docs/element.md +++ b/docs/element.md @@ -1,5 +1,7 @@ # 元件说明 +?> 上次更新时间:* {docsify-updated} * + 在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。 请打开样板0层 `sample0.js` 进行参照对比。 @@ -30,7 +32,7 @@ ## 怪物 -本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。 +本塔支持的怪物列表参见`enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。如不知道怪物素材长啥样的请打开`enemys.png`对比查看。 如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。 怪物可以有特殊属性,每个怪物可以有多个自定义属性。 @@ -40,14 +42,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 +62,36 @@ 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。 -怪物的伤害计算在下面的`calDamage`函数中,如有自己需求的伤害计算公式请修改该函数的代码。 +怪物可以负伤,在`data.js`的全局变量`enableNegativeDamage`中指定。 + +下面的`getSpecialHint`函数则给定了每个特殊属性的详细描述。这个描述将在怪物手册中看到。 + +**打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](event#加点事件)。** 如果`data.js`中的enableExperience为false,即不启用经验的话,怪物手册里将不显示怪物的经验值,打败怪物也不获得任何经验。 拿到幸运金币后,打怪获得的金币将翻倍。 +N连击怪物的special是6,且我们可以为它定义n代表实际连击数。参见样板中剑王的写法。 + +![N连击](./img/nattack.png) + 吸血怪需要在怪物后添加value,代表吸血的比例。 ![怪物吸血](./img/blood.png) @@ -89,15 +106,21 @@ enemys.prototype.getSpecialText = function (enemyId) { 领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 -![怪物领域](./img/domainenemy.png) +领域是十字伤害还是九宫格伤害由data.js中的全局变量`zoneSquare`设定。你也可以对该怪物自行进行设定。 -请注意如果吸血和领域同时存在,则value会冲突。**因此请勿将吸血和领域放置在同一个怪物身上。** +`range`选项可选,代表该领域怪的范围,不写则默认为1。 -本塔暂不支持阻击、激光、自爆、退化等属性。 +![怪物领域](./img/zone.png) + +阻击怪同样需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 + +!> 阻击怪后退的地点不能有任何事件存在,即使是已经被禁用的自定义事件! + +请注意如果吸血、领域、阻击中任何两个同时存在,则value会冲突。**因此请勿将吸血、领域或阻击放置在同一个怪物身上。** 如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 -### 路障、楼梯、传送门 +## 路障,楼梯,传送门 血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。 @@ -123,6 +146,68 @@ floorId指定的是目标楼层的唯一标识符(ID)。 ![楼层转换穿透](./img/floorset.png) +## 背景音乐 + +本塔支持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]** 快捷使用中心对称飞行器 + +以上快捷键也能在游戏菜单中的操作说明中看到。 +     @@ -134,4 +219,3 @@ floorId指定的是目标楼层的唯一标识符(ID)。 ========================================================================================== [继续阅读下一章:事件](event) - diff --git a/docs/event.md b/docs/event.md index 3fa2df43..58d0917e 100644 --- a/docs/event.md +++ b/docs/event.md @@ -1,10 +1,12 @@ # 事件 +?> 上次更新时间:* {docsify-updated} * + 本章内将对样板所支持的事件进行介绍。 ## 事件的机制 -本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`,包括勇士到达一个指定的`checkBlock`也可以触发一个检查领域、夹击的事件。上面说的这些事件都是系统本身自带的,即类似于RMXP中的公共事件。 +本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`,等等。上面说的这些事件都是系统本身自带的,即类似于RMXP中的公共事件。 上述这些默认的事件已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是一个自定义的事件。 @@ -29,6 +31,7 @@ "x,y": { "trigger": "action", // 触发的trigger, action代表自定义事件 "enable": true, // 该事件初始状态下是否处于启用状态 + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 "data": [ // 实际执行的事件列表 // 事件1 // 事件2 @@ -53,6 +56,7 @@ "x,y": { // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 "enable": true, // 该事件初始状态下是否处于启用状态 + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 "data": [ // 实际执行的事件列表 // 事件1 // 事件2 @@ -71,6 +75,28 @@ "x,y": { // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 + "data": [ // 实际执行的事件列表 + // 事件1 + // 事件2 + // ... + ] + } +} +``` + +`"noPass"`为该点是否可通行的标记。`true`代表该点不可通行,`false`代表该点可通行。 + +对于目前所有的素材,都存在默认的是否可通行状态。如果你在该点指定`noPass`,则原本的可通行状态会被覆盖。 + +因此,除非你想覆盖默认的可通行选项(比如将一个空地设为不可通行),否则该项可以忽略。 + +``` js +"events": { // 该楼的所有可能事件列表 + "x,y": { + // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 + // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false + // 除非你想覆盖系统默认的可通行状态,否则"noPass"项可以忽略 "data": [ // 实际执行的事件列表 // 事件1 // 事件2 @@ -86,7 +112,7 @@ ``` js "events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 + // 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号 "x,y": [ // 实际执行的事件列表 // 事件1 // 事件2 @@ -97,7 +123,7 @@ 这种简写方式可以极大方便地造塔者进行造塔。 -!> **请注意:如果该点初始的`enable`为`false`,或者该点本身有系统默认事件且需要覆盖(`trigger`),则必须采用上面那种大括号写的方式来定义。** +!> **请注意:如果该点初始的`enable`为`false`,或者该点本身有系统默认事件且需要覆盖(`trigger`),或者你想覆盖该点的默认通行状态,则必须采用上面那种大括号写的方式来定义。**   @@ -397,7 +423,9 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 如果强制战斗失败,则会立刻生命归0并死亡,调用lose函数,接下来的事件不会再被执行。 -强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。强制战斗不会触发任何afterBattle里的事件。 +打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](#加点事件)。 + +强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。 ### openDoor: 开门 @@ -416,8 +444,6 @@ loc指定门的坐标,floorId指定门所在的楼层ID。如果是当前层 如果loc所在的点既不是门也不是墙壁,则忽略本事件。 -openDoor不会触发任何afterOpenDoor里的事件。 - ### changeFloor: 楼层切换 在事件中也可以对楼层进行切换。一个比较典型的例子就是TSW中,勇士在三楼的陷阱被扔到了二楼,就是一个楼层切换事件。 @@ -553,6 +579,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 +607,6 @@ move完毕后移动的NPC/怪物一定会消失,只不过可以通过immediate 值得注意的是,如果是额外添加进文件的音效,则需在main.js中this.sounds里加载它。 -!> 自定义添加的音效名请勿包含`-`,否则将无法正常使用! - -由于考虑到用户流量问题,每个额外音效最好不超过20KB。 - ### win: 获得胜利 `{"type": "win", "reason": "xxx"}` 将会直接调用events.js中的win函数,并将reason作为参数传入。 @@ -756,6 +798,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 +855,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 +862,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 +880,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一项里。 @@ -829,7 +909,7 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用 请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。 -``` java +``` js "1,0": [ // 金币商店 // 打开商店前,你也可以添加自己的剧情 // 例如,通过if来事件来判断是不是第一次访问商店,是的则显示一段文字(类似宿命的华音那样) @@ -887,37 +967,90 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用 当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。 +## 战前剧情 + +有时候光战后事件`afterBattle`是不够的,我们可能还需要战前剧情,例如Boss战之前和Boss进行一段对话。 + +要使用战前剧情,首先你需要覆盖该店的系统默认事件,改成你自己的自定义事件,然后在战前剧情后调用`{"type": "battle"}`强制战斗。 + +值得注意的是,使用这种自定义事件来覆盖系统的默认战斗事件时,可以增加`displayDamage`代表该点是否需要显伤。此项可省略,默认为有显伤。 + +``` js +"x,y": { // (x,y)为该怪物坐标 + "trigger": "action", // 覆盖该点本身默认事件,变成自定义事件 + "displayDamage": true, // 覆盖后,该点是否有显伤;此项可忽略,默认为有。 + "data": [ // 该点的自定义事件列表 + // ... 战前剧情 + {"type": "battle", "id": "xxx"}, // 强制战斗 + // ... 战后剧情;请注意上面的强制战斗不会使怪物消失,如有需要请调动{"type": "hide"} + ] +} +``` + +## 经验升级(进阶/境界塔) + +本塔也支持经验升级,即用户杀怪获得经验后,可以到达某些数值自动进阶,全面提升属性。 + +要经验升级,你需要先在`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来获得当前难度 + } } ``` @@ -928,9 +1061,8 @@ events.prototype.startGame = function (hard) { 当获胜`{"type": "win"}`事件发生时,将调用`events.js`中的win事件。其显示一段恭喜文字,并重新开始游戏。 ``` js -////// 游戏结束事件 ////// +////// 游戏获胜事件 ////// events.prototype.win = function(reason) { - // 获胜 core.waitHeroToStop(function() { core.removeGlobalAnimate(0,0,true); core.clearMap('all'); // 清空全地图 @@ -948,8 +1080,8 @@ events.prototype.win = function(reason) { 当失败(`{"type": "lose"}`,或者被怪强制战斗打死、被领域怪扣血死、中毒导致扣血死,路障导致扣血死等等)事件发生时,将调用`events.js`中的`lose`事件。其直接显示一段文字,并重新开始游戏。 ``` js +////// 游戏失败事件 ////// events.prototype.lose = function(reason) { - // 失败 core.waitHeroToStop(function() { core.drawText([ "\t[结局1]你死了。\n如题。" 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..79028c98 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,10 +2,11 @@ - + HTML5魔塔样板 + @@ -15,11 +16,11 @@ window.$docsify = { homepage: 'index.md', loadSidebar: true, - name: 'H5魔塔样板', + name: 'HTML5魔塔样板', repo: 'https://github.com/ckcz123/mota-js', // basepath: '../docs/', - // Search Support + // Search Support // search: { // maxAge: 43200000, // 过期时间,单位毫秒,默认一天 // paths: 'auto', @@ -37,10 +38,12 @@ loadSidebar: '_sidebar.md', subMaxLevel: 2, autoHeader: true, + auto2top: true, + mergeNavbar: true, + formatUpdated: '{YYYY}-{MM}-{DD} {HH}:{mm}:{ss}', } - //离线模式 if (typeof navigator.serviceWorker !== 'undefined') { - navigator.serviceWorker.register('serviceWorker.js') + navigator.serviceWorker.register('serviceWorker.js') } diff --git a/docs/index.md b/docs/index.md index ad17349c..9f0059ef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ # HTML5 魔塔样板说明文档 +?> 上次更新时间:* {docsify-updated} * + 众所周知,魔塔的趋势是向移动端发展,贴吧中也常常能见到“求手机魔塔”的帖子。然而现有的工具中,NekoRPG有着比较大的局限性,游戏感较差,更是完全没法在iOS上运行。而一些APP的魔塔虽然可用,但是必须要下载安装,对于Android和iOS还必须开发不同的版本,非常麻烦。 但是,现在我们有了HTML5。 HTML5的画布(canvas)以及它被Android/iOS内置浏览器所支持的特性,可以让我们做出真正意义上的全平台覆盖的魔塔。 @@ -11,7 +13,7 @@ 继续查看文档的详细介绍,让你学会如何使用这一个样板来制作属于自己的HTML5魔塔。 -视频教程地址:http://www.bilibili.com/video/av17608025/ ,配合本教程观看效果更佳~ +视频教程地址:[http://www.bilibili.com/video/av17608025/](http://www.bilibili.com/video/av17608025/) ,配合本教程观看效果更佳~ ========================================================================================== diff --git a/docs/personalization.md b/docs/personalization.md index 08328a78..9b4eb0ca 100644 --- a/docs/personalization.md +++ b/docs/personalization.md @@ -1,12 +1,14 @@ # 个性化 +?> 上次更新时间:* {docsify-updated} * + 有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。 ## 自定义素材 所有素材的图片都在`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,10 +19,45 @@ ### 使用预定义的素材 -在images目录的“默认素材”下给定了若干预定义的自定义素材。包括野外(草地),星空,木板等等都已经被预先给定。 +在images目录的“默认素材”下给定了若干预定义的自定义素材。 如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。 +### 使用自己的图片作为某层楼的背景素材 + +由于HTML5功能(素材)有限,导致了对很多比较复杂的素材(比如房子内)等无法有着较好的绘图方式。 + +为了解决这个问题,我们允许用户自己放置一张图片作为某一层的背景素材。 + +要启用这个功能,我们首先需要在`main.js`中将可能的图片进行加载。 + +``` js +this.pngs = [ // 在此存放所有可能的背景图片;背景图片最好是416*416像素,其他分辨率会被强制缩放成416*416 + // 建议对于较大的图片,在网上使用在线的“图片压缩工具”来进行压缩,以节省流量 + "bg.png", // "yewai.png", +]; +``` + +!> 背景素材只支持png格式,且会被强制缩放到416*416。 + +!> 请使用网上的一些[在线图片压缩工具](http://www.asqql.com/gifzip/)对png图片进行压缩,以节省流量。一张500KB的png图片可以被压缩到20-30KB,显示效果不会有太大差异。 + +之后,我们可以在每层剧本的`"png": "xxx"`里来定义该层的默认背景图片素材。 + +``` js +"png": "bg.png", // 背景图;你可以选择一张png图片来作为背景素材。 +``` + +你的图片背景素材将会覆盖原来本身的背景层。 + +**如果你需要让某些点不可通行(比如你建了个房子,墙壁和家具等位置不让通行),则需在`events`中指定`{"noPass": false}`,参见[自定义事件](event#自定义事件)的写法。 + +``` js +"events": { + "x,y": {"noPass": true} // (x,y)点不可通行 +} +``` + ### 使用便捷PS工具生成素材 如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。 @@ -29,6 +66,8 @@ 我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。 +便捷PS工具同样支持图片色相的修改,和RMXP几乎完全相同。 + 用这种方式,我们能极快地替换或素材,包括需要新增的怪物。 ### 添加素材到游戏 @@ -37,63 +76,73 @@ 这是因为,该素材没有被定义,无法被游戏所识别。 +#### 素材的机制 + +本塔所有的素材都拥有三个属性:**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 +225,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 +251,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/serviceWorker.js b/docs/serviceWorker.js index ab031ac1..34634e1e 100644 --- a/docs/serviceWorker.js +++ b/docs/serviceWorker.js @@ -1,31 +1,31 @@ const RUNTIME = 'docsify' const HOSTNAME_WHITELIST = [ - self.location.hostname, - 'fonts.gstatic.com', - 'fonts.googleapis.com', - 'unpkg.com' + self.location.hostname, + 'fonts.gstatic.com', + 'fonts.googleapis.com', + 'cdn.bootcss.com' ] // The Util Function to hack URLs of intercepted requests const getFixedUrl = (req) => { - var now = Date.now() - var url = new URL(req.url) + var now = Date.now() + var url = new URL(req.url) - // 1. fixed http URL - // Just keep syncing with location.protocol - // fetch(httpURL) belongs to active mixed content. - // And fetch(httpRequest) is not supported yet. - url.protocol = self.location.protocol + // 1. fixed http URL + // Just keep syncing with location.protocol + // fetch(httpURL) belongs to active mixed content. + // And fetch(httpRequest) is not supported yet. + url.protocol = self.location.protocol - // 2. add query for caching-busting. - // Github Pages served with Cache-Control: max-age=600 - // max-age on mutable content is error-prone, with SW life of bugs can even extend. - // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. - // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 - if (url.hostname === self.location.hostname) { - url.search += (url.search ? '&' : '?') + 'cache-bust=' + now - } - return url.href + // 2. add query for caching-busting. + // Github Pages served with Cache-Control: max-age=600 + // max-age on mutable content is error-prone, with SW life of bugs can even extend. + // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. + // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 + if (url.hostname === self.location.hostname) { + url.search += (url.search ? '&' : '?') + 'cache-bust=' + now + } + return url.href } /** @@ -35,7 +35,7 @@ const getFixedUrl = (req) => { * waitUntil(): activating ====> activated */ self.addEventListener('activate', event => { - event.waitUntil(self.clients.claim()) + event.waitUntil(self.clients.claim()) }) /** @@ -45,8 +45,8 @@ self.addEventListener('activate', event => { * void respondWith(Promise r) */ self.addEventListener('fetch', event => { - // Skip some of cross-origin requests, like those for Google Analytics. - if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { + // Skip some of cross-origin requests, like those for Google Analytics. + if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { // Stale-while-revalidate // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 @@ -60,16 +60,16 @@ self.addEventListener('fetch', event => { // If there’s nothing in cache, wait for the fetch. // If neither yields a response, return offline pages. event.respondWith( - Promise.race([fetched.catch(_ => cached), cached]) - .then(resp => resp || fetched) - .catch(_ => { /* eat any errors */ }) - ) + Promise.race([fetched.catch(_ => cached), cached]) +.then(resp => resp || fetched) +.catch(_ => { /* eat any errors */ }) +) // Update the cache with the version we fetched (only for ok status) event.waitUntil( - Promise.all([fetchedCopy, caches.open(RUNTIME)]) - .then(([response, cache]) => response.ok && cache.put(event.request, response)) + Promise.all([fetchedCopy, caches.open(RUNTIME)]) + .then(([response, cache]) => response.ok && cache.put(event.request, response)) .catch(_ => { /* eat any errors */ }) - ) - } -}) +) +} +}) \ No newline at end of file diff --git a/docs/start.md b/docs/start.md index 127fd819..8ddde2af 100644 --- a/docs/start.md +++ b/docs/start.md @@ -1,5 +1,7 @@ # 快速上手 +?> 上次更新时间:* {docsify-updated} * + 在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔! ## 前置需求 @@ -8,8 +10,8 @@ - Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) - 任一款现代浏览器。强烈推荐Chrome。 -- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 -([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) +- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 + - ([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) 只要满足了上述条件,你就可以开始做自己的塔啦! @@ -36,7 +38,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 59c83681..952477e8 100644 --- a/drawMapGUI.html +++ b/drawMapGUI.html @@ -7,52 +7,24 @@
- -
-
-
地图数组编辑区
-
-
-
- -

{{ errors[error-1] }}

-
-
- -
-
-
文件操作区
- - 当前文件: {{ selected }} - - - - -
-
+
+
+
+
+ +

{{ errors[error-1] }}

-
-
对象属性编辑区
-
-
-
地图选点事件编辑区
-
-
-
楼层事件编辑区
+
+
+
+ +
+
+ +
+
@@ -80,14 +52,19 @@
+
+ 当前地板: - 当前地板: {{ selectedBg }} +
+ + +
@@ -129,14 +106,9 @@ + \ No newline at end of file diff --git a/libs/core.js b/libs/core.js index f0a0a18c..145c6b9b 100644 --- a/libs/core.js +++ b/libs/core.js @@ -7,12 +7,15 @@ function core() { this.statusBar = {}; this.canvas = {}; this.images = []; - this.sounds = {}; + this.pngs = []; + this.bgms = []; + this.sounds = []; this.floorIds = []; this.floors = {}; this.firstData = {}; this.material = { 'images': {}, + 'bgms': {}, 'sounds': {}, 'ground': null, 'items': {}, @@ -35,12 +38,12 @@ function core() { 'openDoorAnimate': null } this.musicStatus = { - 'isIOS': false, - 'loaded': false, - 'bgmStatus': false, - 'soundStatus': true, - 'playedSound': null, - 'playedBgm': null, + 'audioContext': null, // WebAudioContext + 'startDirectly': false, // 是否直接播放(加载)音乐 + 'bgmStatus': false, // 是否播放BGM + 'soundStatus': true, // 是否播放SE + 'playingBgm': null, // 正在播放的BGM + 'isPlaying': false, } // 样式 this.domStyle = { @@ -57,7 +60,7 @@ function core() { 'floorId': null, 'thisMap': null, 'maps': null, - 'checkBlock': [], // 显伤伤害 + 'checkBlock': {}, // 显伤伤害 // 勇士状态;自动寻路相关 'heroMoving': false, @@ -87,6 +90,7 @@ function core() { 'selection': null, 'ui': null, }, + 'curtainColor': null, 'usingCenterFly':false, 'openingDoor': null, @@ -101,11 +105,14 @@ function core() { /////////// 系统事件相关 /////////// -core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds, floors, coreData) { +////// 初始化 ////// +core.prototype.init = function (dom, statusBar, canvas, images, pngs, bgms, sounds, floorIds, floors, coreData) { core.dom = dom; core.statusBar = statusBar; core.canvas = canvas; core.images = images; + core.pngs = pngs; + core.bgms = bgms; core.sounds = sounds; core.floorIds = floorIds; core.floors = floors; @@ -113,6 +120,13 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds core[key] = coreData[key]; } core.flags = core.clone(core.data.flags); + if (!core.flags.enableExperience) + core.flags.enableLevelUp = false; + if (!core.flags.canOpenBattleAnimate) { + core.flags.showBattleAnimateConfirm = false; + core.flags.battleAnimate = false; + core.setLocalStorage('battleAnimate', false); + } core.values = core.clone(core.data.values); core.firstData = core.data.getFirstData(); core.initStatus.shops = core.firstData.shops; @@ -126,16 +140,41 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds core.material.icons = core.icons.getIcons(); core.material.events = core.events.getEvents(); - // test if iOS - core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); - var userAgent = navigator.userAgent; - - if (userAgent.indexOf('iPhone') > -1 || userAgent.indexOf('iPad') > -1) { - console.log("你的设备为iphone,不自动播放音乐!"); - core.musicStatus.isIOS = true; - core.musicStatus.soundStatus = false; + if (location.protocol.indexOf("http")==0) { + window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext; + try { + core.musicStatus.audioContext = new window.AudioContext(); + } catch (e) { + console.log("该浏览器不支持AudioContext"); + core.musicStatus.audioContext = null; + } } + // 音效设置部分 + var isPC = true; + ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"].forEach(function (t) { + if (navigator.userAgent.indexOf(t)>=0) isPC=false; + }); + if (isPC) { + // 如果是PC端直接加载 + core.musicStatus.startDirectly = true; + } + else { + var connection = navigator.connection; + if (core.isset(connection) && connection.type=='wifi') + core.musicStatus.startDirectly = true; + } + + // 先从存储中读取BGM状态 + core.musicStatus.bgmStatus = core.getLocalStorage('bgmStatus', true); + if (!core.musicStatus.startDirectly) // 如果当前网络环境不允许 + core.musicStatus.bgmStatus = false; + core.setLocalStorage('bgmStatus', core.musicStatus.bgmStatus); + + core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); + core.setLocalStorage('soundStatus', core.musicStatus.soundStatus); + + // switchs core.flags.battleAnimate = core.getLocalStorage('battleAnimate', core.flags.battleAnimate); core.flags.displayEnemyDamage = core.getLocalStorage('enemyDamage', core.flags.displayEnemyDamage); @@ -154,6 +193,7 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds }); } +////// 显示游戏开始界面 ////// core.prototype.showStartAnimate = function (callback) { core.dom.startPanel.style.opacity=1; core.dom.startPanel.style.display="block"; @@ -162,6 +202,8 @@ core.prototype.showStartAnimate = function (callback) { core.dom.startButtonGroup.style.display = 'none'; core.dom.startButtons.style.display = 'block'; core.dom.levelChooseButtons.style.display = 'none'; + core.dom.curtain.style.background = "#000000"; + core.dom.curtain.style.opacity = 0; core.status.played = false; core.clearStatus(); core.clearMap('all'); @@ -180,6 +222,7 @@ core.prototype.showStartAnimate = function (callback) { }, 20); } +////// 隐藏游戏开始界面 ////// core.prototype.hideStartAnimate = function (callback) { var opacityVal = 1; var startAnimate = window.setInterval(function () { @@ -193,14 +236,17 @@ core.prototype.hideStartAnimate = function (callback) { }, 20); } +////// 设置加载进度条进度 ////// core.prototype.setStartProgressVal = function (val) { core.dom.startTopProgress.style.width = val + '%'; } +////// 设置加载进度条提示文字 ////// core.prototype.setStartLoadTipText = function (text) { core.dom.startTopLoadTips.innerHTML = text; } +////// 加载图片和音频 ////// core.prototype.loader = function (callback) { var loadedImageNum = 0, allImageNum = 0, allSoundNum = 0; allImageNum = core.images.length; @@ -210,41 +256,60 @@ core.prototype.loader = function (callback) { for (var i = 0; i < core.images.length; i++) { core.loadImage(core.images[i], function (imgName, image) { core.setStartLoadTipText('正在加载图片 ' + imgName + "..."); - imgName = imgName.split('-'); - imgName = imgName[0]; core.material.images[imgName] = image; loadedImageNum++; core.setStartLoadTipText(imgName + ' 加载完毕...'); core.setStartProgressVal(loadedImageNum * (100 / allImageNum)); if (loadedImageNum == allImageNum) { - // 加载音频 - for (var key in core.sounds) { - for (var i = 0; i < core.sounds[key].length; i++) { - var soundName=core.sounds[key][i]; - soundName = soundName.split('-'); - var sound = new Audio(); - sound.preload = 'none'; - sound.src = 'sounds/' + soundName[0] + '.' + key; - if (soundName[1] == 'loop') { - sound.loop = 'loop'; - } - if (!core.isset(core.material.sounds[key])) - core.material.sounds[key] = {}; - core.material.sounds[key][soundName[0]] = sound; - } + // 加载pngs + core.material.images.pngs = {}; + if (core.pngs.length==0) { + core.loadAutotile(callback); + return; + } + for (var x=0;x0) return; - core.musicStatus.bgmStatus=1; - if (core.musicStatus.soundStatus) - core.playBgm('bgm', 'mp3'); - return; - } - var item = toLoadList.shift(); - item.oncanplay = function() { - core.loadSoundItem(toLoadList); - } - item.load(); + else { + var music = new Audio(); + music.preload = core.musicStatus.startDirectly?'auto':'none'; + music.src = 'sounds/'+t; + music.loop = 'loop'; + core.material.bgms[t] = music; + } + }); + + core.sounds.forEach(function (t) { + + if (core.musicStatus.audioContext != null) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'sounds/'+t, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function(e) { //下载完成 + try { + core.musicStatus.audioContext.decodeAudioData(this.response, function (buffer) { + core.material.sounds[t] = buffer; + }, function (e) { + console.log(e); + core.material.sounds[t] = null; + }) + } + catch (ee) { + console.log(ee); + core.material.sounds[t] = null; + } + }; + + xhr.ontimeout = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.onerror = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.send(); + } + else { + var music = new Audio(); + music.src = 'sounds/'+t; + core.material.sounds[t] = music; + } + + }); + + // 直接开始播放 + if (core.musicStatus.startDirectly && core.bgms.length>0) + core.playBgm(core.bgms[0]); + + callback(); } +////// 游戏是否已经开始 ////// core.prototype.isPlaying = function() { if (core.isset(core.status.played) && core.status.played) return true; return false; } - +////// 清除游戏状态和数据 ////// core.prototype.clearStatus = function() { // 停止各个Timeout和Interval for (var i in core.interval) { @@ -307,6 +441,7 @@ core.prototype.clearStatus = function() { core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); } +////// 重置游戏状态和初始数据 ////// core.prototype.resetStatus = function(hero, hard, floorId, maps) { // 停止各个Timeout和Interval @@ -331,6 +466,7 @@ core.prototype.resetStatus = function(hero, hard, floorId, maps) { core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); } +////// 开始游戏 ////// core.prototype.startGame = function (hard, callback) { console.log('开始游戏'); @@ -342,7 +478,7 @@ core.prototype.startGame = function (hard, callback) { }); } - +////// 重新开始游戏;此函数将回到标题页面 ////// core.prototype.restart = function() { core.showStartAnimate(); } @@ -354,6 +490,7 @@ core.prototype.restart = function() { /////////// 键盘、鼠标事件相关 /////////// +////// 按下某个键时 ////// core.prototype.onkeyDown = function(e) { if (!core.isset(core.status.holdingKeys))core.status.holdingKeys=[]; var isArrow={37:true,38:true,39:true,40:true}[e.keyCode] @@ -370,6 +507,7 @@ core.prototype.onkeyDown = function(e) { } } +////// 放开某个键时 ////// core.prototype.onkeyUp = function(e) { var isArrow={37:true,38:true,39:true,40:true}[e.keyCode] if(isArrow && !core.status.lockControl){ @@ -387,6 +525,7 @@ core.prototype.onkeyUp = function(e) { } } +////// 按住某个键时 ////// core.prototype.pressKey = function (keyCode) { if (keyCode === core.status.holdingKeys.slice(-1)[0]) { core.keyDown(keyCode); @@ -394,6 +533,7 @@ core.prototype.pressKey = function (keyCode) { } } +////// 根据按下键的code来执行一系列操作 ////// core.prototype.keyDown = function(keyCode) { if(core.status.automaticRouting || core.status.automaticRouted) { core.stopAutomaticRoute(); @@ -444,14 +584,6 @@ core.prototype.keyDown = function(keyCode) { core.events.keyDownSyncSave(keyCode); return; } - - /* - if (core.status.event.id == 'save' || core.status.event.id == 'load') { - if (keyCode==37) core.ui.drawSLPanel(core.status.event.data-1); - else if (keyCode==39) core.ui.drawSLPanel(core.status.event.data+1); - return; - } - */ return; } if(!core.status.played) { @@ -470,7 +602,7 @@ core.prototype.keyDown = function(keyCode) { case 40: core.moveHero('down'); break; - case 13: case 32: case 51: // 快捷键3:飞 + case 13: case 32: case 67: case 51: // 快捷键3:飞 // 因为加入了两次的检测机制,从keydown转移到keyup,同时保证位置信息正确,但以下情况会触发作图的bug: // 在鼠标的路线移动中使用飞,绿块会滞后一格,显示的位置不对,同时也不会倍以下的代码清除 if (core.status.heroStop && core.hasItem('centerFly')) { @@ -486,9 +618,9 @@ core.prototype.keyDown = function(keyCode) { core.status.usingCenterFly = false; } else if (keyCode==51) { core.status.usingCenterFly = true; - var fillstyle = 'rgba(255,0,0,0.5)'; - if (core.canUseItem('centerFly')) fillstyle = 'rgba(0,255,0,0.5)'; - core.fillRect('ui',(12-core.getHeroLoc('x'))*32,(12-core.getHeroLoc('y'))*32,32,32,fillstyle); + core.setAlpha('ui', 0.5); + core.fillRect('ui',(12-core.getHeroLoc('x'))*32,(12-core.getHeroLoc('y'))*32,32,32,core.canUseItem('centerFly')?'#00FF00':'#FF0000'); + core.setAlpha('ui', 1); core.drawTip("请确认当前中心对称飞行器的位置"); } } @@ -500,6 +632,7 @@ core.prototype.keyDown = function(keyCode) { } } +////// 根据放开键的code来执行一系列操作 ////// core.prototype.keyUp = function(keyCode) { if (core.status.lockControl) { @@ -525,6 +658,10 @@ core.prototype.keyUp = function(keyCode) { core.events.keyUpBook(keyCode); return; } + if (core.status.event.id=='book-detail' && (keyCode==13 || keyCode==32 || keyCode==67)) { + core.events.clickBookDetail(); + return; + } if (core.status.event.id=='fly') { core.events.keyUpFly(keyCode); return; @@ -649,6 +786,7 @@ core.prototype.keyUp = function(keyCode) { core.stopHero(); } +////// 点击(触摸)事件按下时 ////// core.prototype.ondown = function (x ,y) { if (!core.status.played || core.status.lockControl) { core.onclick(x, y, []); @@ -665,6 +803,7 @@ core.prototype.ondown = function (x ,y) { core.fillPosWithPoint(pos); } +////// 当在触摸屏上滑动时 ////// core.prototype.onmove = function (x ,y) { if (core.status.holdingPath==0){return;} core.status.mouseOutCheck =1; @@ -687,6 +826,7 @@ core.prototype.onmove = function (x ,y) { } } +////// 当点击(触摸)事件放开时 ////// core.prototype.onup = function () { core.status.holdingPath=0; if(core.status.stepPostfix.length>0){ @@ -707,6 +847,7 @@ core.prototype.onup = function () { } } +////// 获得点击事件相对左上角的坐标(0到12之间) ////// core.prototype.getClickLoc = function (x, y) { var statusBar = {'x': 0, 'y': 0}; @@ -731,6 +872,7 @@ core.prototype.getClickLoc = function (x, y) { return loc; } +////// 具体点击屏幕上(x,y)点时,执行的操作 ////// core.prototype.onclick = function (x, y, stepPostfix) { // console.log("Click: (" + x + "," + y + ")"); @@ -767,6 +909,12 @@ core.prototype.onclick = function (x, y, stepPostfix) { return; } + // 怪物详细信息 + if (core.status.event.id == 'book-detail') { + core.events.clickBookDetail(x,y); + return; + } + // 楼层飞行器 if (core.status.event.id == 'fly') { core.events.clickFly(x,y); @@ -840,6 +988,7 @@ core.prototype.onclick = function (x, y, stepPostfix) { } +////// 滑动鼠标滚轮时的操作 ////// core.prototype.onmousewheel = function (direct) { // 向下滚动是 -1 ,向上是 1 @@ -852,8 +1001,8 @@ core.prototype.onmousewheel = function (direct) { // 怪物手册 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); + if (direct==1) core.ui.drawBook(core.status.event.data - 1); + if (direct==-1) core.ui.drawBook(core.status.event.data + 1); return; } @@ -872,10 +1021,13 @@ core.prototype.onmousewheel = function (direct) { /////////// 寻路代码相关 /////////// +////// 清除自动寻路路线 ////// core.prototype.clearAutomaticRouteNode = function (x, y) { - core.canvas.ui.clearRect(x * 32 + 5, y * 32 + 5, 27, 27); + if (core.status.event.id==null) + core.canvas.ui.clearRect(x * 32 + 5, y * 32 + 5, 27, 27); } +////// 停止自动寻路操作 ////// core.prototype.stopAutomaticRoute = function () { if (!core.status.played) { return; @@ -889,6 +1041,7 @@ core.prototype.stopAutomaticRoute = function () { core.canvas.ui.clearRect(0, 0, 416, 416); } +////// 继续剩下的自动寻路操作 ////// core.prototype.continueAutomaticRoute = function () { // 此函数只应由events.afterOpenDoor和events.afterBattle调用 var moveStep = core.status.moveStepBeforeStop; @@ -899,11 +1052,13 @@ core.prototype.continueAutomaticRoute = function () { core.setAutoHeroMove(moveStep); } +////// 清空剩下的自动寻路列表 ////// core.prototype.clearContinueAutomaticRoute = function () { core.canvas.ui.clearRect(0, 0, 416, 416); core.status.moveStepBeforeStop=[]; } +////// 设置自动寻路路线 ////// core.prototype.setAutomaticRoute = function (destX, destY, stepPostfix) { if (!core.status.played || core.status.lockControl) { return; @@ -1018,7 +1173,8 @@ core.prototype.setAutomaticRoute = function (destX, destY, stepPostfix) { core.status.automaticRoutingTemp = {'destX': 0, 'destY': 0, 'moveStep': []}; } -// BFS + +////// 自动寻路算法,找寻最优路径 ////// core.prototype.automaticRoute = function (destX, destY) { var startX = core.getHeroLoc('x'); var startY = core.getHeroLoc('y'); @@ -1085,8 +1241,8 @@ core.prototype.automaticRoute = function (destX, destY) { // 绕过可能的夹击点 // if (nextBlock.block.event.trigger == 'checkBlock') deepAdd=200; } - if (core.status.checkBlock[nid]>0) - deepAdd = core.status.checkBlock[nid]; + if (core.status.checkBlock.damage[nid]>0) + deepAdd = core.status.checkBlock.damage[nid]; if (nx == destX && ny == destY) { route[nid] = direction; @@ -1117,10 +1273,12 @@ core.prototype.automaticRoute = function (destX, destY) { return ans; } +////// 显示离散的寻路点 ////// core.prototype.fillPosWithPoint = function (pos) { core.fillRect('ui', pos.x*32+12,pos.y*32+12,8,8, '#bfbfbf'); } +////// 清除已经寻路过的部分 ////// core.prototype.clearStepPostfix = function () { if(core.status.mouseOutCheck >0){ core.status.mouseOutCheck--; @@ -1141,6 +1299,7 @@ core.prototype.clearStepPostfix = function () { /////////// 自动行走 & 行走控制 /////////// +////// 停止勇士的自动行走 ////// core.prototype.stopAutoHeroMove = function () { core.status.autoHeroMove = false; core.status.automaticRouting = false; @@ -1153,6 +1312,7 @@ core.prototype.stopAutoHeroMove = function () { clearInterval(core.interval.heroAutoMoveScan); } +////// 设置勇士的自动行走路线 ////// core.prototype.setAutoHeroMove = function (steps, start) { if (steps.length == 0) { return; @@ -1172,12 +1332,14 @@ core.prototype.setAutoHeroMove = function (steps, start) { }, 80); } +////// 让勇士开始自动行走 ////// core.prototype.autoHeroMove = function (direction, step) { core.status.autoHeroMove = true; core.status.destStep = step; core.moveHero(direction); } +////// 设置行走的效果动画 ////// core.prototype.setHeroMoveInterval = function (direction, x, y, callback) { if (core.status.heroMoving) { return; @@ -1209,6 +1371,7 @@ core.prototype.setHeroMoveInterval = function (direction, x, y, callback) { }, 12.5); } +////// 设置勇士行走过程中对事件的触发检测 ////// core.prototype.setHeroMoveTriggerInterval = function () { var direction, x, y; var scan = { @@ -1222,7 +1385,7 @@ core.prototype.setHeroMoveTriggerInterval = function () { direction = core.getHeroLoc('direction'); x = core.getHeroLoc('x'); y = core.getHeroLoc('y'); - var noPass = core.noPass(x + scan[direction].x, y + scan[direction].y), canMove = core.canMove(); + var noPass = core.noPass(x + scan[direction].x, y + scan[direction].y), canMove = core.canMoveHero(); if (noPass || !canMove) { if (canMove) // 非箭头:触发 core.trigger(x + scan[direction].x, y + scan[direction].y); @@ -1257,14 +1420,15 @@ 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; + core.checkBlock(); }); } }, 50); } +////// 设置勇士的方向(转向) ////// core.prototype.turnHero = function(direction) { if (core.isset(direction)) { core.status.hero.loc.direction = direction; @@ -1278,7 +1442,8 @@ core.prototype.turnHero = function(direction) { core.canvas.ui.clearRect(0, 0, 416, 416); } -core.prototype.canMove = function() { +////// 勇士能否前往某方向 ////// +core.prototype.canMoveHero = function() { var direction = core.getHeroLoc('direction'); var nowBlock = core.getBlock(core.getHeroLoc('x'),core.getHeroLoc('y')); if (nowBlock!=null){ @@ -1316,11 +1481,13 @@ core.prototype.canMove = function() { return true; } +////// 让勇士开始移动 ////// core.prototype.moveHero = function (direction) { core.setHeroLoc('direction', direction); core.status.heroStop = false; } +/////// 使用事件让勇士移动。这个函数将不会触发任何事件 ////// core.prototype.eventMoveHero = function(steps, time, callback) { time = time || 100; @@ -1381,6 +1548,7 @@ core.prototype.eventMoveHero = function(steps, time, callback) { }, time/8); } +////// 每移动一格后执行的事件 ////// core.prototype.moveOneStep = function() { // 中毒状态 if (core.hasFlag('poison')) { @@ -1395,6 +1563,7 @@ core.prototype.moveOneStep = function() { } } +////// 停止勇士的一切行动,等待勇士行动结束后,再执行callback ////// core.prototype.waitHeroToStop = function(callback) { core.stopAutomaticRoute(); core.clearContinueAutomaticRoute(); @@ -1407,10 +1576,12 @@ core.prototype.waitHeroToStop = function(callback) { } } +////// 停止勇士的移动状态 ////// core.prototype.stopHero = function () { core.status.heroStop = true; } +////// 绘制勇士 ////// core.prototype.drawHero = function (direction, x, y, status, offsetX, offsetY) { offsetX = offsetX || 0; offsetY = offsetY || 0; @@ -1423,13 +1594,54 @@ core.prototype.drawHero = function (direction, x, y, status, offsetX, offsetY) { core.canvas.hero.drawImage(core.material.images.hero, heroIcon[status] * 32, heroIcon.loc * height, 32, height, x + offsetX, y + offsetY + 32-height, 32, height); } +////// 设置勇士的位置 ////// +core.prototype.setHeroLoc = function (itemName, itemVal) { + if (itemVal == '++') { + core.status.hero.loc[itemName]++; + return; + } + else if (itemVal == '--') { + core.status.hero.loc[itemName]--; + return; + } + core.status.hero.loc[itemName] = itemVal; +} + +////// 获得勇士的位置 ////// +core.prototype.getHeroLoc = function (itemName) { + if (!core.isset(itemName)) return core.status.hero.loc; + return core.status.hero.loc[itemName]; +} + +////// 获得勇士面对位置的x坐标 ////// +core.prototype.nextX = function() { + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; + return core.getHeroLoc('x')+scan[core.getHeroLoc('direction')].x; +} + +////// 获得勇士面对位置的y坐标 ////// +core.prototype.nextY = function () { + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; + return core.getHeroLoc('y')+scan[core.getHeroLoc('direction')].y; +} + /////////// 自动行走 & 行走控制 END /////////// /////////// 地图处理 /////////// -// 开门 +////// 开门 ////// core.prototype.openDoor = function (id, x, y, needKey, callback) { if (core.interval.openDoorAnimate!=null) return; @@ -1458,7 +1670,7 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { } } // open - core.playSound("door", "ogg"); + core.playSound("door.ogg"); var state = 0; var doorId = id; if (!(doorId.substring(doorId.length-4)=="Door")) { @@ -1480,7 +1692,7 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { }, speed) } -// 战斗 +////// 战斗 ////// core.prototype.battle = function (id, x, y, force, callback) { if (core.status.moveStepBeforeStop.length==0) { core.status.moveStepBeforeStop=core.status.autoStepRoutes.slice(core.status.autoStep-1,core.status.autoStepRoutes.length); @@ -1504,11 +1716,12 @@ core.prototype.battle = function (id, x, y, force, callback) { }); } else { - core.playSound('attack', 'ogg'); + core.playSound('attack.ogg'); core.afterBattle(id, x, y, callback); } } +////// 战斗完毕 ////// core.prototype.afterBattle = function(id, x, y, callback) { core.status.hero.hp -= core.enemys.getDamage(id); if (core.status.hero.hp<=0) { @@ -1524,23 +1737,24 @@ core.prototype.afterBattle = function(id, x, y, callback) { var experience = core.material.enemys[id].experience; if (core.hasFlag('curse')) experience=0; core.status.hero.experience += experience; - core.updateStatusBar(); if (core.isset(x) && core.isset(y)) { core.removeBlock(x, y); core.canvas.event.clearRect(32 * x, 32 * y, 32, 32); } - core.updateFg(); - var hint = "打败 " + core.material.enemys[id].name + ",金币+" + money; + // core.updateStatusBar(); + var hint = "打败 " + core.material.enemys[id].name; + if (core.flags.enableMoney) + hint += ",金币+" + money; if (core.flags.enableExperience) hint += ",经验+" + core.material.enemys[id].experience; core.drawTip(hint); - core.updateCheckBlockMap(); // 打完怪物,触发事件 core.events.afterBattle(id,x,y,callback); } +////// 触发(x,y)点的事件 ////// core.prototype.trigger = function (x, y) { var mapBlocks = core.status.thisMap.blocks; var noPass; @@ -1567,7 +1781,7 @@ core.prototype.trigger = function (x, y) { } } -// 楼层切换 +////// 楼层切换 ////// core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) { time = time || 800; time /= 20; @@ -1576,16 +1790,22 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) core.stopAutomaticRoute(); core.clearContinueAutomaticRoute(); core.dom.floorNameLabel.innerHTML = core.status.maps[floorId].title; - if (core.isset(stair)) { - // find heroLoc + if (!core.isset(stair) && !core.isset(heroLoc)) heroLoc = core.status.hero.loc; + if (core.isset(stair)) { + if (!core.isset(heroLoc)) heroLoc={}; var blocks = core.status.maps[floorId].blocks; for (var i in blocks) { if (core.isset(blocks[i].event) && !(core.isset(blocks[i].enable) && !blocks[i].enable) && blocks[i].event.id === stair) { heroLoc.x = blocks[i].x; heroLoc.y = blocks[i].y; + break; } } + if (!core.isset(heroLoc.x)) { + heroLoc.x=core.status.hero.loc.x; + heroLoc.y=core.status.hero.loc.y; + } } if (core.status.maps[floorId].canFlyTo && core.status.hero.flyRange.indexOf(floorId)<0) { if (core.floorIds.indexOf(floorId)>core.floorIds.indexOf(core.status.floorId)) @@ -1595,11 +1815,43 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) } window.setTimeout(function () { - // console.log('地图切换到' + floorId); - core.playSound('floor', 'mp3'); + core.playSound('floor.mp3'); core.mapChangeAnimate('show', time/2, function () { - core.statusBar.floor.innerHTML = core.status.maps[floorId].name; - core.updateStatusBar(); + + // 根据文字判断是否斜体 + var floorName = core.status.maps[floorId].name; + if (!core.isset(floorName) || floorName=="") floorName=" " + core.statusBar.floor.innerHTML = floorName; + if (/^[+-]?\d+$/.test(floorName)) + core.statusBar.floor.style.fontStyle = 'italic'; + else core.statusBar.floor.style.fontStyle = 'normal'; + + // 更改BGM + if (core.isset(core.floors[floorId].bgm)) { + core.playBgm(core.floors[floorId].bgm); + } + + // 不存在事件时,更改画面色调 + if (core.status.event.id == null) { + // 默认画面色调 + if (core.isset(core.floors[floorId].color)) { + var color = core.floors[floorId].color; + + // 直接变色 + var nowR = parseInt(color[0]), nowG = parseInt(color[1]), nowB = parseInt(color[2]); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + if (core.isset(color[3])) + core.dom.curtain.style.opacity = color[3]; + else core.dom.curtain.style.opacity=1; + core.status.curtainColor = color; + } + else { + core.dom.curtain.style.background = "#000000"; + core.dom.curtain.style.opacity = 0; + } + } + core.drawMap(floorId, function () { setTimeout(function() { core.mapChangeAnimate('hide', time/4, function () { @@ -1612,16 +1864,14 @@ 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(); + core.updateStatusBar(); }, 15) }); }); }, 50); } -// 地图切换 +////// 地图切换动画效果 ////// core.prototype.mapChangeAnimate = function (mode, time, callback) { if (mode == 'show') { core.show(core.dom.floorMsgGroup, time, function () { @@ -1635,6 +1885,7 @@ core.prototype.mapChangeAnimate = function (mode, time, callback) { } } +////// 清除地图 ////// core.prototype.clearMap = function (map, x, y, width, height) { if (map == 'all') { for (var m in core.canvas) { @@ -1646,6 +1897,7 @@ core.prototype.clearMap = function (map, x, y, width, height) { } } +////// 在某个canvas上绘制一段文字 ////// core.prototype.fillText = function (map, text, x, y, style, font) { if (core.isset(style)) { core.setFillStyle(map, style); @@ -1656,6 +1908,7 @@ core.prototype.fillText = function (map, text, x, y, style, font) { core.canvas[map].fillText(text, x, y); } +////// 在某个canvas上绘制一个矩形 ////// core.prototype.fillRect = function (map, x, y, width, height, style) { if (core.isset(style)) { core.setFillStyle(map, style); @@ -1663,6 +1916,7 @@ core.prototype.fillRect = function (map, x, y, width, height, style) { core.canvas[map].fillRect(x, y, width, height); } +////// 在某个canvas上绘制一个矩形的边框 ////// core.prototype.strokeRect = function (map, x, y, width, height, style, lineWidth) { if (core.isset(style)) { core.setStrokeStyle(map, style); @@ -1673,6 +1927,7 @@ core.prototype.strokeRect = function (map, x, y, width, height, style, lineWidth core.canvas[map].strokeRect(x, y, width, height); } +////// 在某个canvas上绘制一条线 ////// core.prototype.drawLine = function (map, x1, y1, x2, y2, style, lineWidth) { if (core.isset(style)) { core.setStrokeStyle(map, style); @@ -1686,10 +1941,12 @@ core.prototype.drawLine = function (map, x1, y1, x2, y2, style, lineWidth) { core.canvas[map].stroke(); } +////// 设置某个canvas的文字字体 ////// core.prototype.setFont = function (map, font) { core.canvas[map].font = font; } +////// 设置某个canvas的线宽度 ////// core.prototype.setLineWidth = function (map, lineWidth) { if (map == 'all') { for (var m in core.canvas) { @@ -1699,14 +1956,17 @@ core.prototype.setLineWidth = function (map, lineWidth) { core.canvas[map].lineWidth = lineWidth; } +////// 保存某个canvas状态 ////// core.prototype.saveCanvas = function (map) { core.canvas[map].save(); } +////// 加载某个canvas状态 ////// core.prototype.loadCanvas = function (map) { core.canvas[map].restore(); } +////// 设置某个canvas边框属性 ////// core.prototype.setStrokeStyle = function (map, style) { if (map == 'all') { for (var m in core.canvas) { @@ -1718,6 +1978,7 @@ core.prototype.setStrokeStyle = function (map, style) { } } +////// 设置某个canvas的alpha值 ////// core.prototype.setAlpha = function (map, alpha) { if (map == 'all') { for (var m in core.canvas) { @@ -1727,6 +1988,7 @@ core.prototype.setAlpha = function (map, alpha) { else core.canvas[map].globalAlpha = alpha; } +////// 设置某个canvas的透明度 ////// core.prototype.setOpacity = function (map, opacity) { if (map == 'all') { for (var m in core.canvas) { @@ -1736,6 +1998,7 @@ core.prototype.setOpacity = function (map, opacity) { else core.canvas[map].canvas.style.opacity = opacity; } +////// 设置某个canvas的绘制属性(如颜色等) ////// core.prototype.setFillStyle = function (map, style) { if (map == 'all') { for (var m in core.canvas) { @@ -1747,11 +2010,7 @@ core.prototype.setFillStyle = function (map, style) { } } -/** - * 地图绘制 - * @param mapName 地图ID - * @param callback 绘制完毕后的回调函数 - */ +////// 绘制某张地图 ////// core.prototype.drawMap = function (mapName, callback) { var mapData = core.status.maps[mapName]; var mapBlocks = mapData.blocks; @@ -1767,192 +2026,167 @@ core.prototype.drawMap = function (mapName, callback) { core.canvas.bg.drawImage(blockImage, 0, blockIcon * 32, 32, 32, x * 32, y * 32, 32, 32); } } - var autotileMaps = []; + + // 如果存在png + if (core.isset(core.floors[mapName].png)) { + var png = core.floors[mapName].png; + if (core.isset(core.material.images.pngs[png])) { + core.canvas.bg.drawImage(core.material.images.pngs[png], 0, 0, 416, 416); + } + } + + var mapArray = core.maps.getMapArray(core.status.maps, mapName); for (var b = 0; b < mapBlocks.length; b++) { // 事件启用 var block = mapBlocks[b]; if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) { if (block.event.cls == 'autotile') { - // core.drawAutotile(); - autotileMaps[13*block.x + block.y] = true; - continue; + core.drawAutotile(core.canvas.event, mapArray, block, 32, 0, 0); } else { - blockIcon = core.material.icons[block.event.cls][block.event.id]; - blockImage = core.material.images[block.event.cls]; - core.canvas.event.drawImage(core.material.images[block.event.cls], 0, blockIcon * 32, 32, 32, block.x * 32, block.y * 32, 32, 32); - core.addGlobalAnimate(block.event.animate, block.x * 32, block.y * 32, blockIcon, blockImage); + if (block.event.id!='none') { + blockIcon = core.material.icons[block.event.cls][block.event.id]; + blockImage = core.material.images[block.event.cls]; + core.canvas.event.drawImage(core.material.images[block.event.cls], 0, blockIcon * 32, 32, 32, block.x * 32, block.y * 32, 32, 32); + core.addGlobalAnimate(block.event.animate, block.x * 32, block.y * 32, blockIcon, blockImage); + } } } } - core.drawAutotile(mapName, 'event', autotileMaps, 0, 0, 32); core.setGlobalAnimate(core.values.animateSpeed); - if (core.isset(callback)) callback(); } -core.prototype.drawAutotile = function (floorId, canvas, autotileMaps, left, top, size) { - var isAutotile = function(x, y) { - if (x<0 || x>12 || y<0 || y>12) return 0; - return autotileMaps[13*x+y]?1:0; +////// 绘制Autotile ////// +core.prototype.drawAutotile = function(ctx, mapArr, block, size, left, top){ + var indexArrs = [ //16种组合的图块索引数组; // 将autotile分割成48块16*16的小块; 数组索引即对应各个小块 + // +----+----+----+----+----+----+ + [10, 9, 4, 3 ], //0 bin:0000 | 1 | 2 | 3 | 4 | 5 | 6 | + [10, 9, 4, 13], //1 bin:0001 +----+----+----+----+----+----+ + [10, 9, 18, 3 ], //2 bin:0010 | 7 | 8 | 9 | 10 | 11 | 12 | + [10, 9, 16, 15], //3 bin:0011 +----+----+----+----+----+----+ + [10, 43, 4, 3 ], //4 bin:0100 | 13 | 14 | 15 | 16 | 17 | 18 | + [10, 31, 4, 25], //5 bin:0101 +----+----+----+----+----+----+ + [10, 7, 2, 3 ], //6 bin:0110 | 19 | 20 | 21 | 22 | 23 | 24 | + [10, 31, 16, 5 ], //7 bin:0111 +----+----+----+----+----+----+ + [48, 9, 4, 3 ], //8 bin:1000 | 25 | 26 | 27 | 28 | 29 | 30 | + [ 8, 9, 4, 1 ], //9 bin:1001 +----+----+----+----+----+----+ + [36, 9, 30, 3 ], //10 bin:1010 | 31 | 32 | 33 | 34 | 35 | 36 | + [36, 9, 6, 15], //11 bin:1011 +----+----+----+----+----+----+ + [46, 45, 4, 3 ], //12 bin:1100 | 37 | 38 | 39 | 40 | 41 | 42 | + [46, 11, 4, 25], //13 bin:1101 +----+----+----+----+----+----+ + [12, 45, 30, 3 ], //14 bin:1110 | 43 | 44 | 45 | 46 | 47 | 48 | + [34, 33, 28, 27] //15 bin:1111 +----+----+----+----+----+----+ + ]; + + var drawBlockByIndex = function(ctx, dx, dy, autotileImg, index, size){ //index为autotile的图块索引1-48 + var sx = 16*((index-1)%6), sy = 16*(~~((index-1)/6)); + ctx.drawImage(autotileImg, sx, sy, 16, 16, dx, dy, size/2, size/2); } - for (var xx=0;xx<13;xx++) { - for (var yy=0;yy<13;yy++) { - if (isAutotile(xx, yy)) { - // 绘制autotile - var id=isAutotile(xx, yy - 1) + 2 * isAutotile(xx - 1, yy) + 4 * isAutotile(xx, yy + 1) + 8 * isAutotile(xx + 1, yy); - core.drawAutotileBlock(floorId, canvas, left + xx * size, top + yy * size, size, core.material.images.autotile, id); + var getAutotileAroundId = function(currId, x, y){ + if(x<0 || y<0 || x>12 || y>12) return 1; + else return mapArr[y][x]==currId ? 1:0; + } + var checkAround = function(x, y){ // 得到周围四个32*32块(周围每块都包含当前块的1/4,不清楚的话画下图你就明白)的数组索引 + var currId = mapArr[y][x]; + var pointBlock = []; + for(var i=0; i<4; i++){ + var bsum = 0; + var offsetx = i%2, offsety = ~~(i/2); + for(var j=0; j<4; j++){ + var mx = j%2, my = ~~(j/2); + var b = getAutotileAroundId(currId, x+offsetx+mx-1, y+offsety+my-1); + bsum += b*(Math.pow(2, 3-j)); } + pointBlock.push(bsum); } + return pointBlock; } - for (var xx=0;xx<13;xx++) { - for (var yy=0;yy<13;yy++) { - if (isAutotile(xx, yy) + isAutotile(xx + 1, yy) + isAutotile(xx + 1, yy + 1) + isAutotile(xx, yy + 1) != 3) continue; - if (!isAutotile(xx, yy)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size, size, core.material.images.autotile, 16); - } - if (!isAutotile(xx + 1, yy)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size, size, core.material.images.autotile, 17); - } - if (!isAutotile(xx + 1, yy + 1)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size / 2, size, core.material.images.autotile, 18); - } - if (!isAutotile(xx, yy + 1)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size / 2, size, core.material.images.autotile, 19); - } + var getAutotileIndexs = function(x, y){ + var indexArr = []; + var pointBlocks = checkAround(x, y); + for(var i=0; i<4; i++){ + var arr = indexArrs[pointBlocks[i]] + indexArr.push(arr[3-i]); } + return indexArr; + } + // 开始绘制autotile + var x = block.x, y = block.y; + var pieceIndexs = getAutotileIndexs(x, y); + + //修正四个边角的固定搭配 + if(pieceIndexs[0] == 13){ + if(pieceIndexs[1] == 16) pieceIndexs[1] = 14; + if(pieceIndexs[2] == 31) pieceIndexs[2] = 19; + } + if(pieceIndexs[1] == 18){ + if(pieceIndexs[0] == 15) pieceIndexs[0] = 17; + if(pieceIndexs[3] == 36) pieceIndexs[3] = 24; + } + if(pieceIndexs[2] == 43){ + if(pieceIndexs[0] == 25) pieceIndexs[0] = 37; + if(pieceIndexs[3] == 46) pieceIndexs[3] = 44; + } + if(pieceIndexs[3] == 48){ + if(pieceIndexs[1] == 30) pieceIndexs[1] = 42; + if(pieceIndexs[2] == 45) pieceIndexs[2] = 47; + } + for(var i=0; i<4; i++){ + var index = pieceIndexs[i]; + var dx = x*size + size/2*(i%2), dy = y*size + size/2*(~~(i/2)); + drawBlockByIndex(ctx, dx+left, dy+top, core.material.images['autotile'][block.event.id], index, size); } } -core.prototype.drawAutotileBlock = function (floorId, map, x, y, size, autotile, index) { - var canvas = core.canvas[map]; - var groundId = core.floors[floorId].defaultGround || "ground"; - var blockIcon = core.material.icons.terrains[groundId]; - var blockImage = core.material.images.terrains; - switch (index) { - case 0: - canvas.drawImage(autotile, 0, 0, 32, 32, x, y, size, size); - break; - case 1: - canvas.drawImage(autotile, 0, 3 * 32, 16, 32, x, y, size / 2, size); - canvas.drawImage(autotile, 2 * 32 + 16, 3 * 32, 16, 32, x + size / 2, y, size / 2, size); - break; - case 2: - canvas.drawImage(autotile, 2 * 32, 32, 32, 16, x, y, size, size / 2); - canvas.drawImage(autotile, 2 * 32, 3 * 32 + 16, 32, 16, x, y + size / 2, size, size / 2); - break; - case 3: - canvas.drawImage(autotile, 2 * 32, 3 * 32, 32, 32, x, y, size, size); - break; - case 4: - canvas.drawImage(autotile, 0, 1 * 32, 16, 32, x, y, size / 2, size); - canvas.drawImage(autotile, 2 * 32 + 16, 1 * 32, 16, 32, x + size / 2, y, size / 2, size); - break; - case 5: - canvas.drawImage(autotile, 0, 2 * 32, 16, 32, x, y, size / 2, size); - canvas.drawImage(autotile, 2 * 32 + 16, 2 * 32, 16, 32, x + size / 2, y, size / 2, size); - break; - case 6: - canvas.drawImage(autotile, 2 * 32, 1 * 32, 32, 32, x, y, size, size); - break; - case 7: - canvas.drawImage(autotile, 2 * 32, 2 * 32, 32, 32, x, y, size, size); - break; - case 8: - canvas.drawImage(autotile, 0, 32, 32, 16, x, y, size, size / 2); - canvas.drawImage(autotile, 0, 3 * 32 + 16, 32, 16, x, y + size / 2, size, size / 2); - break; - case 9: - canvas.drawImage(autotile, 0, 3 * 32, 32, 32, x, y, size, size); - break; - case 10: - canvas.drawImage(autotile, 32, 32, 32, 16, x, y, size, size / 2); - canvas.drawImage(autotile, 32, 3 * 32 + 16, 32, 16, x, y + size / 2, size, size / 2); - break; - case 11: - canvas.drawImage(autotile, 32, 3 * 32, 32, 32, x, y, size, size); - break; - case 12: - canvas.drawImage(autotile, 0, 32, 32, 32, x, y, size, size); - break; - case 13: - canvas.drawImage(autotile, 0, 2 * 32, 32, 32, x, y, size, size); - break; - case 14: - canvas.drawImage(autotile, 32, 32, 32, 32, x, y, size, size); - break; - case 15: - canvas.drawImage(autotile, 32, 2 * 32, 32, 32, x, y, size, size); - break; - case 16: - canvas.clearRect(x, y, size / 2, size / 2); - canvas.drawImage(blockImage, 0, blockIcon * 32, 16, 16, x, y, size / 2, size / 2); - canvas.drawImage(autotile, 2 * 32, 0, 16, 16, x, y, size / 2, size / 2); - break; - case 17: - canvas.clearRect(x, y, size / 2, size / 2); - canvas.drawImage(blockImage, 0, blockIcon * 32, 16, 16, x, y, size / 2, size / 2); - canvas.drawImage(autotile, 2 * 32 + 16, 0, 16, 16, x, y, size / 2, size / 2); - break; - case 18: - canvas.clearRect(x, y, size / 2, size / 2); - canvas.drawImage(blockImage, 0, blockIcon * 32, 16, 16, x, y, size / 2, size / 2); - canvas.drawImage(autotile, 2 * 32 + 16, 16, 16, 16, x, y, size / 2, size / 2); - break; - case 19: - canvas.clearRect(x, y, size / 2, size / 2); - canvas.drawImage(blockImage, 0, blockIcon * 32, 16, 16, x, y, size / 2, size / 2); - canvas.drawImage(autotile, 2 * 32, 16, 16, 16, x, y, size / 2, size / 2); - break; - } -} - -core.prototype.autotileExists = function (x, y, floorId) { - var block = core.getBlock(x,y,floorId); - if (block==null) return false; - return block.block.event.cls == 'autotile'; -} - +////// 某个点是否不可通行 ////// core.prototype.noPassExists = function (x, y, floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; return core.isset(block.block.event.noPass) && block.block.event.noPass; } +////// 某个点是否在区域内且不可通行 ////// core.prototype.noPass = function (x, y) { return x<0 || x>12 || y<0 || y>12 || core.noPassExists(x,y); } +////// 某个点是否存在NPC ////// core.prototype.npcExists = function (x, y, floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; return block.block.event.cls == 'npcs'; } +////// 某个点是否存在(指定的)地形 ////// core.prototype.terrainExists = function (x, y, id, floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; - return block.block.event.cls=='terrains' && block.block.event.id==id; + return block.block.event.cls=='terrains' && (core.isset(id)?block.block.event.id==id:true); } +////// 某个点是否存在楼梯 ////// core.prototype.stairExists = function (x, y, floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; return block.block.event.cls=='terrains' && (block.block.event.id=='upFloor' || block.block.event.id=='downFloor'); } +////// 当前位置是否在楼梯边 ////// core.prototype.nearStair = function() { var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); return core.stairExists(x,y) || core.stairExists(x-1,y) || core.stairExists(x,y-1) || core.stairExists(x+1,y) || core.stairExists(x,y+1); } +////// 某个点是否存在(指定的)怪物 ////// core.prototype.enemyExists = function (x, y, id,floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; - return block.block.event.cls=='enemys' && block.block.event.id==id; + return block.block.event.cls=='enemys' && (core.isset(id)?block.block.event.id==id:true); } +////// 获得某个点的block ////// core.prototype.getBlock = function (x, y, floorId, needEnable) { if (!core.isset(floorId)) floorId=core.status.floorId; if (!core.isset(needEnable)) needEnable=true; @@ -1966,6 +2200,7 @@ core.prototype.getBlock = function (x, y, floorId, needEnable) { return null; } +////// 显示移动某块的动画,达到{“type”:”move”}的效果 ////// core.prototype.moveBlock = function(x,y,steps,time,immediateHide,callback) { time = time || 500; @@ -2065,6 +2300,7 @@ core.prototype.moveBlock = function(x,y,steps,time,immediateHide,callback) { }, time/16); } +////// 显示/隐藏某个块时的动画效果 ////// core.prototype.animateBlock = function (x,y,type,time,callback) { if (type!='hide') type='show'; @@ -2107,8 +2343,8 @@ core.prototype.animateBlock = function (x,y,type,time,callback) { }, time/10); } -// 显示一个事件 -core.prototype.addBlock = function(x,y,floodId) { +////// 将某个块从禁用变成启用状态 ////// +core.prototype.showBlock = function(x, y, floodId) { floodId = floodId || core.status.floorId; var block = core.getBlock(x,y,floodId,false); if (block==null) return; // 不存在 @@ -2116,7 +2352,6 @@ 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]; @@ -2128,6 +2363,7 @@ core.prototype.addBlock = function(x,y,floodId) { } } +////// 将某个块从启用变成禁用状态 ////// core.prototype.removeBlock = function (x, y, floorId) { floorId = floorId || core.status.floorId; @@ -2147,7 +2383,7 @@ core.prototype.removeBlock = function (x, y, floorId) { core.updateFg(); } - +////// 根据block的索引删除该块 ////// core.prototype.removeBlockById = function (index, floorId) { var blocks = core.status.maps[floorId].blocks; @@ -2158,25 +2394,22 @@ core.prototype.removeBlockById = function (index, floorId) { 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(); } +////// 一次性删除多个block ////// core.prototype.removeBlockByIds = function (floorId, ids) { ids.sort(function (a,b) {return b-a}).forEach(function (id) { core.removeBlockById(id, floorId); }); } +////// 添加一个全局动画 ////// core.prototype.addGlobalAnimate = function (animateMore, x, y, loc, image) { if (animateMore == 2) { core.status.twoAnimateObjs.push({ @@ -2198,6 +2431,7 @@ core.prototype.addGlobalAnimate = function (animateMore, x, y, loc, image) { } } +////// 删除一个或所有全局动画 ////// core.prototype.removeGlobalAnimate = function (x, y, all) { if (all == true) { core.status.twoAnimateObjs = []; @@ -2217,6 +2451,7 @@ core.prototype.removeGlobalAnimate = function (x, y, all) { } } +////// 设置全局动画的显示效果 ////// core.prototype.setGlobalAnimate = function (speed) { clearInterval(core.interval.twoAnimate); clearInterval(core.interval.fourAnimate); @@ -2245,6 +2480,17 @@ core.prototype.setGlobalAnimate = function (speed) { }, speed / 2); } +////// 同步所有的全局动画效果 ////// +core.prototype.syncGlobalAnimate = function () { + core.status.twoAnimateObjs.forEach(function (t) { + t.status=0; + }) + core.status.fourAnimateObjs.forEach(function (t) { + t.status=0; + }) +} + +////// 显示UI层某个box的动画 ////// core.prototype.setBoxAnimate = function () { clearInterval(core.interval.boxAnimate); if (core.status.boxAnimateObjs.length > 0) { @@ -2256,6 +2502,7 @@ core.prototype.setBoxAnimate = function () { } } +////// 绘制UI层的box动画 ////// core.prototype.drawBoxAnimate = function (background) { for (var a = 0; a < core.status.boxAnimateObjs.length; a++) { var obj = core.status.boxAnimateObjs[a]; @@ -2267,84 +2514,136 @@ core.prototype.drawBoxAnimate = function (background) { } } -core.prototype.updateCheckBlockMap = function() { - // 更新领域、夹击地图 - core.status.checkBlockMap = []; +////// 更新领域、夹击、阻击的伤害地图 ////// +core.prototype.updateCheckBlock = function() { + core.status.checkBlock = {}; + if (!core.isset(core.status.thisMap)) return; var blocks = core.status.thisMap.blocks; + + // Step1: 更新怪物地图 + core.status.checkBlock.map = []; // 记录怪物地图 for (var n=0;n12 || ny<0 || ny>12) return; - if (core.status.checkBlockMap[13*nx+ny]%1000000>0) { - damage += core.status.checkBlockMap[13*nx+ny] % 1000000; + var id = core.status.checkBlock.map[13*x+y]; + if (core.isset(id)) { + var enemy = core.enemys.getEnemys(id); + // 存在领域 + if (core.enemys.hasSpecial(enemy.special, 15)) { + var range = enemy.range || 1; + var zoneSquare = core.flags.zoneSquare; + if (core.isset(enemy.zoneSquare)) zoneSquare=enemy.zoneSquare; + for (var dx=-range;dx<=range;dx++) { + for (var dy=-range;dy<=range;dy++) { + if (dx==0 && dy==0) continue; + var nx=x+dx, ny=y+dy; + if (nx<0 || nx>12 || ny<0 || ny>12) continue; + if (!zoneSquare && Math.abs(dx)+Math.abs(dy)>range) continue; + core.status.checkBlock.damage[13*nx+ny]+=enemy.value; + } } - }) - - 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); + } + // 存在阻击 + if (core.enemys.hasSpecial(enemy.special, 18)) { + for (var dx=-1;dx<=1;dx++) { + for (var dy=-1;dy<=1;dy++) { + if (dx==0 && dy==0) continue; + var nx=x+dx, ny=y+dy; + if (nx<0 || nx>12 || ny<0 || ny>12 || Math.abs(dx)+Math.abs(dy)>1) continue; + core.status.checkBlock.damage[13*nx+ny]+=enemy.value; + } } } } - core.status.checkBlock[13*x+y] = damage; + } + } + + + // Step3: 更新夹击点坐标,并将夹击伤害加入到damage中 + core.status.checkBlock.betweenAttack = []; // 记录(x,y)点是否有夹击 + for (var x=0;x<13;x++) { + for (var y=0;y<13;y++) { + var has=false; + if (x>0 && x<12) { + var id1=core.status.checkBlock.map[13*(x-1)+y], + id2=core.status.checkBlock.map[13*(x+1)+y]; + if (core.isset(id1) && core.isset(id2) && id1==id2) { + var enemy = core.enemys.getEnemys(id1); + if (core.enemys.hasSpecial(enemy.special, 16)) { + has = true; + } + } + } + if (y>0 && y<12) { + var id1=core.status.checkBlock.map[13*x+y-1], + id2=core.status.checkBlock.map[13*x+y+1]; + if (core.isset(id1) && core.isset(id2) && id1==id2) { + var enemy = core.enemys.getEnemys(id1); + if (core.enemys.hasSpecial(enemy.special, 16)) { + has = true; + } + } + } + // 存在夹击 + if (has) { + core.status.checkBlock.betweenAttack[13*x+y]=true; + var leftHp = core.status.hero.hp - core.status.checkBlock.damage[13*x+y]; + if (leftHp>1) + core.status.checkBlock.damage[13*x+y] += parseInt((leftHp+1)/2); + } } } } +////// 检查并执行领域、夹击、阻击事件 ////// 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)) { + var damage = core.status.checkBlock.damage[13*x+y]; + if (damage>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} + } + for (var direction in scan) { + var nx = x+scan[direction].x, ny=y+scan[direction].y; + if (nx<0 || nx>12 || ny<0 || ny>12) continue; + var id=core.status.checkBlock.map[13*nx+ny]; + if (core.isset(id)) { + var enemy = core.enemys.getEnemys(id); + if (core.isset(enemy) && core.enemys.hasSpecial(enemy.special, 18)) { + snipe.push({'direction': direction, 'x': nx, 'y': ny}); + } + } + } + + if (core.status.checkBlock.betweenAttack[13*x+y] && damage>0) { core.drawTip('受到夹击,生命变成一半'); } - else if (core.hasZone(x,y)) { - core.drawTip('受到领域伤害'+core.status.checkBlock[13*x+y]+'点'); + // 阻击 + else if (snipe.length>0 && damage>0) { + core.drawTip('受到阻击伤害'+damage+'点'); + } + else if (damage>0) { + core.drawTip('受到领域伤害'+damage+'点'); } if (core.status.hero.hp<=0) { core.status.hero.hp=0; @@ -2352,67 +2651,156 @@ core.prototype.checkBlock = function () { core.events.lose('zone'); return; } + snipe = snipe.filter(function (t) { + var x=t.x, y=t.y, direction = t.direction; + var nx = x+scan[direction].x, ny=y+scan[direction].y; + + return nx>=0 && nx<=12 && ny>=0 && ny<=12 && core.getBlock(nx, ny, core.status.floorId, false)==null; + }); core.updateStatusBar(); + if (snipe.length>0) + core.snipe(snipe); } } -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.snipe = function (snipes) { + core.waitHeroToStop(function() { + core.lockControl(); + + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; + + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; + snipe.nx = x+scan[snipe.direction].x; + snipe.ny = y+scan[snipe.direction].y; + + core.removeGlobalAnimate(x, y); + + var block = core.getBlock(x,y).block; + + snipe.blockIcon = core.material.icons[block.event.cls][block.event.id]; + snipe.blockImage = core.material.images[block.event.cls]; + var damage = core.enemys.getDamage(block.event.id); + + var color = "#000000"; + if (damage <= 0) color = '#00FF00'; + else if (damage < core.status.hero.hp / 3) color = '#FFFFFF'; + else if (damage < core.status.hero.hp * 2 / 3) color = '#FFFF00'; + else if (damage < core.status.hero.hp) color = '#FF7F00'; + else color = '#FF0000'; + + if (damage >= 999999999) damage = "???"; + else if (damage > 100000) damage = (damage / 10000).toFixed(1) + "w"; + + snipe.damage = damage; + snipe.color = color; + snipe.block = core.clone(block); + }) + + var time = 500, step = 0; + + var animateValue = 2; + var animateCurrent = 0; + var animateTime = 0; + + core.canvas.fg.textAlign = 'left'; + + var animate=window.setInterval(function() { + + step++; + animateTime += time / 16; + if (animateTime >= core.values.animateSpeed * 2 / animateValue) { + animateCurrent++; + animateTime = 0; + if (animateCurrent>=animateValue) animateCurrent=0; + } + + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; + + var nowX=32*x+scan[direction].x*2*step, nowY=32*y+scan[direction].y*2*step; + + // 清空上一次 + core.clearMap('event', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + core.clearMap('fg', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + + core.canvas.event.drawImage(snipe.blockImage, animateCurrent*32, snipe.blockIcon*32, 32, 32, nowX, nowY, 32, 32); + + if (core.hasItem('book')) { + // drawFG + core.setFillStyle('fg', '#000000'); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 32); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 32); + + core.setFillStyle('fg', snipe.color); + core.canvas.fg.fillText(snipe.damage, nowX + 1, nowY + 31); + } + + }) + + if (step==16) { // 移动完毕 + clearInterval(animate); + snipes.forEach(function (t) { + core.removeBlock(t.x, t.y); + var nBlock = core.clone(t.block); + nBlock.x = t.nx; nBlock.y = t.ny; + core.status.thisMap.blocks.push(nBlock); + core.addGlobalAnimate(animateValue, 32*t.nx, 32*t.ny, t.blockIcon, t.blockImage); + }); + core.syncGlobalAnimate(); + core.updateStatusBar(); + // 不存在自定义事件 + if (core.status.event.id==null) + core.unLockControl(); + } + }, time/16); + + + + }); } +////// 更改画面色调 ////// core.prototype.setFg = function(color, time, callback) { - time = time || 750; - core.setOpacity('fg', 1); + if (!core.isset(time)) time=750; + if (time<=0) time=0; - var reset = false; - - if (!core.isset(core.status.event.data.currentColor)) { - core.status.event.data.currentColor = [0,0,0,0]; + if (!core.isset(core.status.curtainColor)) { + core.status.curtainColor = [0,0,0,0]; } - var fromColor = core.status.event.data.currentColor; + var fromColor = core.status.curtainColor; - if (!core.isset(color)) { + if (!core.isset(color)) color = [0,0,0,0]; - reset = true; - } - if (color.length==3) { + if (color.length==3) color.push(1); - } if (color[3]<0) color[3]=0; if (color[3]>1) color[3]=1; + + if (time==0) { + // 直接变色 + var nowR = parseInt(color[0]), nowG = parseInt(color[1]), nowB = parseInt(color[2]); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + core.dom.curtain.style.opacity = color[3]; + core.status.curtainColor = color; + if (core.isset(callback)) callback(); + return; + } + var step=0; var changeAnimate = setInterval(function() { step++; - core.clearMap('fg', 0, 0, 416, 416); var nowAlpha = fromColor[3]+(color[3]-fromColor[3])*step/25; var nowR = parseInt(fromColor[0]+(color[0]-fromColor[0])*step/25); @@ -2422,76 +2810,22 @@ core.prototype.setFg = function(color, time, callback) { if (nowG<0) nowG=0; if (nowG>255) nowG=255; if (nowB<0) nowB=0; if (nowB>255) nowB=255; - core.setAlpha('fg', nowAlpha); - var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1) - core.fillRect('fg', 0, 0, 416, 416, toRGB); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + core.dom.curtain.style.opacity = nowAlpha; if (step>=25) { clearInterval(changeAnimate); - if (reset) { - core.clearMap('fg', 0, 0, 416, 416); - delete core.status.event.data.currentColor; - core.setAlpha('fg', 1); - core.updateFg(); - } - else core.status.event.data.currentColor = color; + core.status.curtainColor = color; if (core.isset(callback)) callback(); } }, time/25); } - -core.prototype.setHeroLoc = function (itemName, itemVal) { - if (itemVal == '++') { - core.status.hero.loc[itemName]++; - return; - } - else if (itemVal == '--') { - core.status.hero.loc[itemName]--; - return; - } - core.status.hero.loc[itemName] = itemVal; -} - -core.prototype.getHeroLoc = function (itemName) { - if (!core.isset(itemName)) return core.status.hero.loc; - return core.status.hero.loc[itemName]; -} - -core.prototype.nextX = function() { - var scan = { - 'up': {'x': 0, 'y': -1}, - 'left': {'x': -1, 'y': 0}, - 'down': {'x': 0, 'y': 1}, - 'right': {'x': 1, 'y': 0} - }; - return core.getHeroLoc('x')+scan[core.getHeroLoc('direction')].x; -} - -core.prototype.nextY = function () { - var scan = { - 'up': {'x': 0, 'y': -1}, - 'left': {'x': -1, 'y': 0}, - 'down': {'x': 0, 'y': 1}, - 'right': {'x': 1, 'y': 0} - }; - return core.getHeroLoc('y')+scan[core.getHeroLoc('direction')].y; -} - -/** - * 更新显伤 - */ +////// 更新全地图显伤 ////// 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.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; - } - if (!core.isset(core.status.thisMap) || !core.isset(core.status.thisMap.blocks)) return; // 更新显伤 var mapBlocks = core.status.thisMap.blocks; @@ -2504,8 +2838,19 @@ core.prototype.updateFg = function () { 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' + if (core.isset(mapBlocks[b].event) && mapBlocks[b].event.cls == 'enemys' && !(core.isset(mapBlocks[b].enable) && !mapBlocks[b].enable)) { + + // 非系统默认的战斗事件(被覆盖) + if (mapBlocks[b].event.trigger != 'battle') { + // 判断显伤 + var event = core.floors[core.status.floorId].events[x+","+y]; + if (core.isset(event) && !(event instanceof Array)) { + if (core.isset(event.displayDamage) && !event.displayDamage) + continue; + } + } + var id = mapBlocks[b].event.id; var damage = core.enemys.getDamage(id); @@ -2536,7 +2881,7 @@ core.prototype.updateFg = function () { 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]; + var damage = core.status.checkBlock.damage[13*x+y]; if (damage>0) { core.setFillStyle('fg', '#000000'); core.canvas.fg.fillText(damage, 32 * x + 17, 32 * (y + 1) - 13); @@ -2552,9 +2897,7 @@ core.prototype.updateFg = function () { } } -/** - * 物品处理 start - */ +////// 获得某个物品的个数 ////// core.prototype.itemCount = function (itemId) { if (!core.isset(itemId) || !core.isset(core.material.items[itemId])) return 0; var itemCls = core.material.items[itemId].cls; @@ -2562,10 +2905,12 @@ core.prototype.itemCount = function (itemId) { return core.isset(core.status.hero.items[itemCls][itemId]) ? core.status.hero.items[itemCls][itemId] : 0; } +////// 是否存在某个物品 ////// core.prototype.hasItem = function (itemId) { return core.itemCount(itemId) > 0; } +////// 设置某个物品的个数 ////// core.prototype.setItem = function (itemId, itemNum) { var itemCls = core.material.items[itemId].cls; if (itemCls == 'items') return; @@ -2573,28 +2918,35 @@ core.prototype.setItem = function (itemId, itemNum) { core.status.hero.items[itemCls] = {}; } core.status.hero.items[itemCls][itemId] = itemNum; + if (itemCls!='keys' && itemNum==0) { + delete core.status.hero.items[itemCls][itemId]; + } } +////// 删除某个物品 ////// core.prototype.removeItem = function (itemId) { if (!core.hasItem(itemId)) return false; var itemCls = core.material.items[itemId].cls; core.status.hero.items[itemCls][itemId]--; - core.updateStatusBar(); - if (itemCls=='tools' && core.status.hero.items[itemCls][itemId]==0) { + if (itemCls!='keys' && core.status.hero.items[itemCls][itemId]==0) { delete core.status.hero.items[itemCls][itemId]; } + core.updateStatusBar(); return true; } +////// 使用某个物品 ////// core.prototype.useItem = function (itemId) { core.items.useItem(itemId); return; } +////// 能否使用某个物品 ////// core.prototype.canUseItem = function (itemId) { return core.items.canUseItem(itemId); } +////// 增加某个物品的个数 ////// core.prototype.addItem = function (itemId, itemNum) { var itemData = core.material.items[itemId]; var itemCls = itemData.cls; @@ -2609,6 +2961,7 @@ 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(); @@ -2619,9 +2972,10 @@ core.prototype.getNextItem = function() { } } +////// 获得某个物品 ////// core.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { // core.getItemAnimate(itemId, itemNum, itemX, itemY); - core.playSound('item', 'ogg'); + core.playSound('item.ogg'); var itemCls = core.material.items[itemId].cls; core.items.getItemEffect(itemId, itemNum); core.removeBlock(itemX, itemY); @@ -2640,6 +2994,7 @@ core.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { else if (core.isset(callback)) callback(); } +////// 左上角绘制一段提示 ////// core.prototype.drawTip = function (text, itemIcon) { var textX, textY, width, height, hide = false, opacityVal = 0; clearInterval(core.interval.tipAnimate); @@ -2682,7 +3037,7 @@ core.prototype.drawTip = function (text, itemIcon) { return; } else { - if (!core.timeout.getItemTipTimeout) { + if (!core.isset(core.timeout.getItemTipTimeout)) { core.timeout.getItemTipTimeout = window.setTimeout(function () { hide = true; core.timeout.getItemTipTimeout = null; @@ -2695,6 +3050,7 @@ core.prototype.drawTip = function (text, itemIcon) { }, 30); } +////// 地图中间绘制一段文字 ////// core.prototype.drawText = function (contents, callback) { if (core.isset(contents)) { if (typeof contents == 'string') { @@ -2737,18 +3093,19 @@ core.prototype.drawText = function (contents, callback) { /////////// 地图相关 END /////////// -/** - * 系统机制 start - */ -// 替换文本中的数为实际值 + + +/////////// 系统机制 /////////// + +////// 将文字中的${和}(表达式)进行替换 ////// core.prototype.replaceText = function (text) { return text.replace(/\${([^}]+)}/g, function (word, value) { return core.calValue(value); }); } - +////// 计算表达式的值 ////// core.prototype.calValue = function (value) { value=value.replace(/status:([\w\d_]+)/g, "core.getStatus('$1')"); value=value.replace(/item:([\w\d_]+)/g, "core.itemCount('$1')"); @@ -2756,6 +3113,23 @@ core.prototype.calValue = function (value) { return eval(value); } +////// 执行一个表达式的effect操作 ////// +core.prototype.doEffect = function (expression) { + // 必须使用"+=" + var arr = expression.split("+="); + if (arr.length!=2) return; + var name=arr[0], value=core.calValue(arr[1]); + if (name.indexOf("status:")==0) { + var status=name.substring(7); + core.setStatus(status, core.getStatus(status)+value); + } + else if (name.indexOf("item:")==0) { + var itemId=name.substring(5); + core.setItem(itemId, core.itemCount(itemId)+value); + } +} + +////// 字符串自动换行的分割 ////// core.prototype.splitLines = function(canvas, text, maxLength, font) { if (core.isset(font)) core.setFont(canvas, font); @@ -2780,6 +3154,7 @@ core.prototype.splitLines = function(canvas, text, maxLength, font) { return contents; } +////// 向某个数组前插入另一个数组或元素 ////// core.prototype.unshift = function (a,b) { if (!(a instanceof Array) || !core.isset(b)) return; if (b instanceof Array) { @@ -2791,6 +3166,7 @@ core.prototype.unshift = function (a,b) { return a; } +////// 设置本地存储 ////// core.prototype.setLocalStorage = function(key, value) { try { localStorage.setItem(core.firstData.name + "_" + key, JSON.stringify(value)); @@ -2801,16 +3177,20 @@ core.prototype.setLocalStorage = function(key, value) { return false; } } + +////// 获得本地存储 ////// core.prototype.getLocalStorage = function(key, defaultValue) { var value = localStorage.getItem(core.firstData.name+"_"+key); if (core.isset(value)) return JSON.parse(value); return defaultValue; } +////// 移除本地存储 ////// core.prototype.removeLocalStorage = function (key) { localStorage.removeItem(core.firstData.name+"_"+key); } +////// 深拷贝一个对象 ////// core.prototype.clone = function (data) { if (!core.isset(data)) return data; // date @@ -2845,25 +3225,19 @@ core.prototype.clone = function (data) { return data; } +////// 格式化时间为字符串 ////// core.prototype.formatDate = function(date) { if (!core.isset(date)) return ""; return date.getFullYear()+"-"+core.setTwoDigits(date.getMonth()+1)+"-"+core.setTwoDigits(date.getDate())+" " +core.setTwoDigits(date.getHours())+":"+core.setTwoDigits(date.getMinutes())+":"+core.setTwoDigits(date.getSeconds()); } +////// 两位数显示 ////// core.prototype.setTwoDigits = function (x) { return parseInt(x)<10?"0"+x:x; } -core.prototype.win = function(reason) { - core.events.win(reason); -} - -core.prototype.lose = function(reason) { - core.events.lose(reason); -} - -// 作弊 +////// 作弊 ////// core.prototype.debug = function() { core.setStatus('hp', 999999); core.setStatus('atk', 10000); @@ -2883,9 +3257,10 @@ core.prototype.debug = function() { core.drawTip("作弊成功"); } -core.prototype.checkStatus = function (name, need, item, clearData) { +////// 判断当前能否进入某个事件 ////// +core.prototype.checkStatus = function (name, need, item) { if (need && core.status.event.id == name) { - core.ui.closePanel(clearData); + core.ui.closePanel(); return false; } @@ -2905,12 +3280,14 @@ core.prototype.checkStatus = function (name, need, item, clearData) { return true; } +////// 点击怪物手册时的打开操作 ////// core.prototype.openBook = function (need) { - if (!core.checkStatus('book', need, true, true)) + if (!core.checkStatus('book', need, true)) return; core.useItem('book'); } +////// 点击楼层传送器时的打开操作 ////// core.prototype.useFly = function (need) { if (!core.checkStatus('fly', need, true)) return; @@ -2932,18 +3309,21 @@ core.prototype.useFly = function (need) { return; } +////// 点击工具栏时的打开操作 ////// core.prototype.openToolbox = function (need) { if (!core.checkStatus('toolbox', need)) return; core.ui.drawToolbox(); } +////// 点击保存按钮时的打开操作 ////// core.prototype.save = function(need) { if (!core.checkStatus('save', need)) return; core.ui.drawSLPanel(core.status.saveIndex); } +////// 点击读取按钮时的打开操作 ////// core.prototype.load = function (need) { // 游戏开始前读档 @@ -2960,6 +3340,7 @@ core.prototype.load = function (need) { core.ui.drawSLPanel(core.status.saveIndex); } +////// 实际进行存读档事件 ////// core.prototype.doSL = function (id, type) { core.status.saveIndex=id; if (type=='save') { @@ -2993,6 +3374,7 @@ core.prototype.doSL = function (id, type) { } } +////// 存档同步操作 ////// core.prototype.syncSave = function(type) { if (type=='save') { core.status.event.selection=1; @@ -3015,30 +3397,29 @@ core.prototype.syncSave = function(type) { // send var xhr = new XMLHttpRequest(); - xhr.open("POST", "../sync.php"); - xhr.timeout = 1000; + xhr.open("POST", "/games/sync.php"); xhr.onload = function(e) { if (xhr.status==200) { // console.log("同步成功。"); var response = JSON.parse(xhr.response); if (response.code<0) { - core.drawText("出错啦!\n无法同步存档到服务器。"); + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+response.msg); } else { core.drawText("同步成功!\n\n您的存档编号: "+response.code+"\n您的存档密码: "+response.msg+"\n\n请牢记以上两个信息(如截图等),在从服务器\n同步存档时使用。") } } else { - core.drawText("出错啦!\n无法同步存档到服务器。"); + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:HTTP "+xhr.status); } }; xhr.ontimeout = function(e) { console.log(e); - core.drawText("出错啦!\n无法同步存档到服务器。"); + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+e); } xhr.onerror = function(e) { console.log(e); - core.drawText("出错啦!\n无法同步存档到服务器。"); + core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+e); } xhr.send(formData); }, function() { @@ -3067,8 +3448,7 @@ core.prototype.syncSave = function(type) { // send var xhr = new XMLHttpRequest(); - xhr.open("POST", "../sync.php"); - xhr.timeout = 1000; + xhr.open("POST", "/games/sync.php"); xhr.onload = function(e) { if (xhr.status==200) { // console.log("同步成功。"); @@ -3095,22 +3475,19 @@ core.prototype.syncSave = function(type) { core.drawText("出错啦!\n存档密码错误!"); break; default: - core.drawText("出错啦!\n无法从服务器同步存档。"); + core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:"+response.msg); break; } - } else { - core.drawText("出错啦!\n无法从服务器同步存档。"); + core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:HTTP "+xhr.status); } }; xhr.ontimeout = function(e) { - console.log(e); - core.drawText("出错啦!\n无法从服务器同步存档。"); + core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:"+e); } xhr.onerror = function(e) { - console.log(e); - core.drawText("出错啦!\n无法从服务器同步存档。"); + core.drawText("出错啦!\n无法从服务器同步存档。\n错误原因:"+e); } xhr.send(formData); }, function() { @@ -3121,6 +3498,7 @@ core.prototype.syncSave = function(type) { } +////// 存档到本地 ////// core.prototype.saveData = function(dataId) { var data = { 'floorId': core.status.floorId, @@ -3143,6 +3521,7 @@ core.prototype.saveData = function(dataId) { return core.setLocalStorage(dataId, data); } +////// 从本地读档 ////// core.prototype.loadData = function (data, callback) { core.resetStatus(data.hero, data.hard, data.floorId, core.maps.load(data.maps)); @@ -3161,19 +3540,29 @@ core.prototype.loadData = function (data, callback) { }); } +////// 设置勇士属性 ////// core.prototype.setStatus = function (statusName, statusVal) { core.status.hero[statusName] = statusVal; } +////// 获得勇士属性 ////// core.prototype.getStatus = function (statusName) { return core.status.hero[statusName]; } +////// 获得某个等级的名称 ////// +core.prototype.getLvName = function () { + if (core.status.hero.lv>core.firstData.levelUp.length) return core.status.hero.lv; + return core.firstData.levelUp[core.status.hero.lv-1].name || core.status.hero.lv; +} + +////// 设置某个自定义变量或flag ////// core.prototype.setFlag = function(flag, value) { if (!core.isset(core.status.hero)) return; core.status.hero.flags[flag]=value; } +////// 获得某个自定义变量或flag ////// core.prototype.getFlag = function(flag, defaultValue) { if (!core.isset(core.status.hero)) return defaultValue; var value = core.status.hero.flags[flag]; @@ -3181,24 +3570,28 @@ core.prototype.getFlag = function(flag, defaultValue) { return defaultValue; } -// 只有不为0或false时才会返回true +////// 是否存在某个自定义变量或flag,且值为true ////// core.prototype.hasFlag = function(flag) { if (core.getFlag(flag)) return true; return false; } +////// 往当前事件列表之前插入一系列事件 ////// core.prototype.insertAction = function (list) { core.events.insertAction(list); } +////// 锁定状态栏,常常用于事件处理 ////// core.prototype.lockControl = function () { core.status.lockControl = true; } +////// 解锁状态栏 ////// core.prototype.unLockControl = function () { core.status.lockControl = false; } +////// 判断某对象是否不为undefined也不会null ////// core.prototype.isset = function (val) { if (val == undefined || val == null) { return false; @@ -3206,46 +3599,117 @@ core.prototype.isset = function (val) { return true } -core.prototype.playSound = function (soundName, soundType) { - if (!core.musicStatus.soundStatus || !core.musicStatus.loaded) { +////// 播放背景音乐 ////// +core.prototype.playBgm = function (bgm) { + + // 如果不允许播放 + if (!core.musicStatus.bgmStatus) return; + // 音频不存在 + if (!core.isset(core.material.bgms[bgm])) return; + + // 延迟播放 + if (core.material.bgms[bgm] == 'loading') { + core.material.bgms[bgm] = 'starting'; return; } - if (!core.isset(core.material.sounds[soundType][soundName])) return; - core.musicStatus.playedSound = core.material.sounds[soundType][soundName]; - core.musicStatus.playedSound.play(); -} -core.prototype.playBgm = function (bgmName, bgmType) { - if (core.musicStatus.isIOS || !core.musicStatus.loaded) return; - if (core.isset(core.musicStatus.playedBgm)) { - core.musicStatus.playedBgm.pause(); - } - core.musicStatus.playedBgm = core.material.sounds[bgmType][bgmName]; - if (core.musicStatus.soundStatus) - core.musicStatus.playedBgm.play(); -} + try { + // 如果当前正在播放,且和本BGM相同,直接忽略 + if (core.musicStatus.playingBgm == bgm && core.musicStatus.isPlaying) { + return; + } + // 如果正在播放中,暂停 + if (core.isset(core.musicStatus.playingBgm) && core.musicStatus.isPlaying) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + // 播放当前BGM + core.musicStatus.playingBgm = bgm; + core.material.bgms[bgm].play(); + core.musicStatus.isPlaying = true; -core.prototype.changeSoundStatus = function () { - if (core.musicStatus.soundStatus) { - main.core.disabledSound(); } - else { - main.core.enabledSound(); + catch (e) { + console.log("无法播放BGM "+bgm); + console.log(e); + core.musicStatus.playingBgm = null; } } -core.prototype.enabledSound = function () { - core.musicStatus.soundStatus = true; - core.playBgm('bgm', 'mp3'); - core.setLocalStorage('soundStatus', true); +////// 暂停背景音乐的播放 ////// +core.prototype.pauseBgm = function () { + // 直接暂停播放 + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + core.musicStatus.isPlaying = false; + } + catch (e) { + console.log("无法暂停BGM "+bgm); + console.log(e); + } } -core.prototype.disabledSound = function () { - core.musicStatus.playedBgm.pause(); - core.musicStatus.soundStatus = false; - core.setLocalStorage('soundStatus', false); +////// 恢复背景音乐的播放 ////// +core.prototype.resumeBgm = function () { + + // 如果不允许播放 + if (!core.musicStatus.bgmStatus) return; + + // 恢复BGM + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].play(); + core.musicStatus.isPlaying = true; + } + else { + if (core.bgms.length>0) { + core.playBgm(core.bgms[0]); + core.musicStatus.isPlaying = true; + } + } + } + catch (e) { + console.log("无法恢复BGM "+bgm); + console.log(e); + } } +////// 播放音频 ////// +core.prototype.playSound = function (sound) { + + // 如果不允许播放 + if (!core.musicStatus.soundStatus) return; + // 音频不存在 + if (!core.isset(core.material.sounds[sound])) return; + + try { + if (core.musicStatus.audioContext != null) { + var source = core.musicStatus.audioContext.createBufferSource(); + source.buffer = core.material.sounds[sound]; + source.connect(core.musicStatus.audioContext.destination); + try { + source.start(0); + } + catch (e) { + try { + source.noteOn(0); + } + catch (ee) { + } + } + } + else { + core.material.sounds[sound].play(); + } + } + catch (eee) { + console.log("无法播放SE "+bgm); + console.log(eee); + } +} + +////// 动画显示某对象 ////// core.prototype.show = function (obj, speed, callback) { if (!core.isset(speed)) { obj.style.display = 'block'; @@ -3266,6 +3730,7 @@ core.prototype.show = function (obj, speed, callback) { }, speed); } +////// 动画使某对象消失 ////// core.prototype.hide = function (obj, speed, callback) { if (!core.isset(speed)) { obj.style.display = 'none'; @@ -3286,32 +3751,46 @@ core.prototype.hide = function (obj, speed, callback) { } -////// 状态栏相关 ////// - +////// 清空状态栏 ////// core.prototype.clearStatusBar = function() { - var statusList = ['floor', 'hp', 'atk', 'def', 'mdef', 'money', 'experience', 'yellowKey', 'blueKey', 'redKey', 'poison', 'weak', 'curse', 'hard']; + var statusList = ['floor', 'lv', 'hp', 'atk', 'def', 'mdef', 'money', 'experience', 'up', 'yellowKey', 'blueKey', 'redKey', 'poison', 'weak', 'curse', 'hard']; statusList.forEach(function (e) { - core.statusBar[e].innerHTML = ""; + core.statusBar[e].innerHTML = " "; }); core.statusBar.image.book.style.opacity = 0.3; core.statusBar.image.fly.style.opacity = 0.3; } -/** - * 更新状态栏 - */ +////// 更新状态栏 ////// core.prototype.updateStatusBar = function () { - // 上限999999 + // 检查等级 + core.events.checkLvUp(); + + // 检查HP上限 if (core.values.HPMAX>0) { core.setStatus('hp', Math.min(core.values.HPMAX, core.getStatus('hp'))); } - // core.statusBar.floor.innerHTML = core.maps.maps[core.status.floorId].name; + // 更新领域、阻击、显伤 + core.updateCheckBlock(); + + var lvName = core.getLvName(); + core.statusBar.lv.innerHTML = lvName; + if (/^[+-]?\d+$/.test(lvName)) + core.statusBar.lv.style.fontStyle = 'italic'; + else core.statusBar.lv.style.fontStyle = 'normal'; + var statusList = ['hp', 'atk', 'def', 'mdef', 'money', 'experience']; statusList.forEach(function (item) { core.statusBar[item].innerHTML = core.getStatus(item); }); + // 进阶 + if (core.flags.enableLevelUp && core.status.hero.lv9) statusLineFontSize = statusLineFontSize * 9 / count; - var shopDisplay, mdefDisplay, expDisplay; - mdefDisplay = core.flags.enableMDef ? 'block' : 'none'; - expDisplay = core.flags.enableExperience ? 'block' : 'none'; + var shopDisplay; statusBarBorder = '3px #fff solid'; toolBarBorder = '3px #fff solid'; @@ -3395,13 +3878,13 @@ core.prototype.resize = function(clientWidth, clientHeight) { var scale = core.domStyle.scale var tempWidth = DEFAULT_CANVAS_WIDTH * scale; - fontSize = DEFAULT_FONT_SIZE * scale; if(!isHorizontal){ //竖屏 core.domStyle.screenMode = 'vertical'; //显示快捷商店图标 shopDisplay = 'block'; //判断应该显示几行 - var col = core.flags.enableMDef || core.flags.enableExperience || core.flags.enableDebuff ? 3 : 2; + // var col = core.flags.enableMDef || core.flags.enableExperience || core.flags.enableDebuff ? 3 : 2; + var col = parseInt((count-1)/3)+1; var tempTopBarH = scale * (BASE_LINEHEIGHT * col + SPACE * 2) + 6; var tempBotBarH = scale * (BASE_LINEHEIGHT + SPACE * 4) + 6; @@ -3428,6 +3911,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { margin = scale * SPACE * 2; toolsMargin = scale * SPACE * 4; + fontSize = DEFAULT_FONT_SIZE * scale; + toolbarFontSize = DEFAULT_FONT_SIZE * scale; }else { //横屏 core.domStyle.screenMode = 'horizontal'; shopDisplay = 'none'; @@ -3445,6 +3930,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { toolBarTop = scale*statusLineHeight * count + SPACE * 2; toolBarBorder = '3px #fff solid'; toolsHeight = scale * BASE_LINEHEIGHT; + fontSize = statusLineFontSize * scale; + toolbarFontSize = DEFAULT_FONT_SIZE * scale; borderRight = ''; statusMaxWidth = scale * DEFAULT_BAR_WIDTH; toolsPMaxwidth = scale * DEFAULT_BAR_WIDTH; @@ -3474,7 +3961,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { toolsHeight = BASE_LINEHEIGHT; borderRight = ''; - fontSize = DEFAULT_FONT_SIZE; + fontSize = statusLineFontSize; + toolbarFontSize = DEFAULT_FONT_SIZE; statusMaxWidth = DEFAULT_BAR_WIDTH; toolsPMaxwidth = DEFAULT_BAR_WIDTH * .9; margin = SPACE * 2; @@ -3499,17 +3987,24 @@ core.prototype.resize = function(clientWidth, clientHeight) { height: canvasWidth + unit, top: canvasTop + unit, right: 0, - // left: canvasLeft + unit, - border: '3px #fff solid', } }, + { + id: 'curtain', + rules: { + width: (canvasWidth - SPACE*2) + unit, + height:(canvasWidth - SPACE*2) + unit, + top: (canvasTop + SPACE) + unit, + right: SPACE + unit, + } + }, { id: 'floorMsgGroup', rules:{ width: (canvasWidth - SPACE*2) + unit, - height:(canvasWidth - SPACE*2) + unit, - top: (canvasTop + SPACE) + unit, + height: (gameGroupHeight - SPACE*2) + unit, + top: SPACE + unit, right: SPACE + unit, } }, @@ -3555,7 +4050,7 @@ core.prototype.resize = function(clientWidth, clientHeight) { borderBottom: toolBarBorder, borderLeft: toolBarBorder, borderRight: borderRight, - fontSize: fontSize + unit + fontSize: toolbarFontSize + unit } }, { @@ -3574,21 +4069,58 @@ core.prototype.resize = function(clientWidth, clientHeight) { } }, { - id: 'expCol', + id: 'floorCol', rules: { - display: expDisplay + display: core.flags.enableFloor ? 'block': 'none' + } + }, + { + id: 'lvCol', + rules: { + display: core.flags.enableLv ? 'block': 'none' } }, { id: 'mdefCol', rules: { - display: mdefDisplay + display: core.flags.enableMDef ? 'block': 'none' } }, + { + id: 'moneyCol', + rules: { + display: core.flags.enableMoney ? 'block': 'none' + } + }, + { + id: 'expCol', + rules: { + display: core.flags.enableExperience ? 'block': 'none' + } + }, + { + id: 'upCol', + rules: { + display: core.flags.enableLevelUp ? 'block': 'none' + } + }, + { + 'id': 'debuffCol', + rules: { + display: core.flags.enableDebuff ? 'block': 'none' + } + }, + { + id: 'hard', + rules: { + lineHeight: toolsHeight + unit + } + } ] core.domRenderer(); } +////// 渲染DOM ////// core.prototype.domRenderer = function(){ core.dom.statusBar.style.display = 'block'; diff --git a/libs/data.js b/libs/data.js index fab64b02..e82b3603 100644 --- a/libs/data.js +++ b/libs/data.js @@ -10,12 +10,13 @@ data.prototype.init = function() { "floorId": "sample0", // 初始楼层ID "hero": { // 勇士初始数据 "name": "阳光", // 勇士名;可以改成喜欢的 + 'lv': 1, // 初始等级,该项必须为正整数 "hp": 1000, // 初始生命值 "atk": 100, // 初始攻击 "def": 100, // 初始防御 "mdef": 100, // 初始魔防 "money": 100, // 初始金币 - "experience": 1000, // 初始经验 + "experience": 0, // 初始经验 "items": { // 初始道具个数 "keys": { "yellowKey": 0, @@ -57,10 +58,11 @@ data.prototype.init = function() { {"text": "防御+4", "effect": "status:def+=4"}, {"text": "魔防+10", "effect": "status:mdef+=10"} // 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 @@ -73,13 +75,33 @@ data.prototype.init = function() { "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"}, ] }, }, + "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; + }}, + + // 依次往下写需要的数值即可 + ] } // 各种数值;一些数值可以在这里设置 this.values = { @@ -117,17 +139,26 @@ data.prototype.init = function() { } // 系统FLAG,在游戏运行中中请不要修改它。 this.flags = { - /****** 角色状态相关 ******/ - "enableMDef": true, // 是否涉及勇士的魔防值;如果此项为false,则状态栏不会显示勇士的魔防值 - "enableExperience": true, // 是否涉及经验值;如果此项为false,则状态栏和怪物手册均将不会显示经验值 + /****** 状态栏相关 ******/ + "enableFloor": true, // 是否在状态栏显示当前楼层 + "enableLv": true, // 是否在状态栏显示当前等级 + "enableMDef": true, // 是否在状态栏及战斗界面显示魔防(护盾) + "enableMoney": true, // 是否在状态栏、怪物手册及战斗界面显示金币 + "enableExperience": true, // 是否在状态栏、怪物手册及战斗界面显示经验 + "enableLevelUp": true, // 是否允许等级提升(进阶);如果上面enableExperience为false,则此项恒视为false "enableDebuff": true, // 是否涉及毒衰咒;如果此项为false则不会在状态栏中显示毒衰咒的debuff + ////// 上述的几个开关将直接影响状态栏的显示效果 ////// /****** 道具相关 ******/ "flyNearStair": true, // 是否需要在楼梯边使用传送器 "pickaxeFourDirections": true, // 使用破墙镐是否四个方向都破坏;如果false则只破坏面前的墙壁 "bombFourDirections": true, // 使用炸弹是否四个方向都会炸;如果false则只炸面前的怪物(即和圣锤等价) "bigKeyIsBox": false, // 如果此项为true,则视为钥匙盒,红黄蓝钥匙+1;若为false,则视为大黄门钥匙 + /****** 怪物相关 ******/ + "enableNegativeDamage": true, // 是否支持负伤害(回血) + "zoneSquare": false, // 领域类型。如果此项为true则为九宫格伤害,为false则为十字伤害 /****** 系统相关 ******/ "startDirectly": false, // 点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面 + "canOpenBattleAnimate": true, // 是否允许用户开启战斗过程;如果此项为false,则下面两项均强制视为false "showBattleAnimateConfirm": true, // 是否在游戏开始时提供“是否开启战斗动画”的选项 "battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中开关 "displayEnemyDamage": true, // 是否地图怪物显伤;用户可以手动在菜单栏中开关 diff --git a/libs/enemys.js b/libs/enemys.js index a5e8b71f..43bc5cf9 100644 --- a/libs/enemys.js +++ b/libs/enemys.js @@ -2,13 +2,14 @@ function enemys() { } +////// 初始化 ////// enemys.prototype.init = function () { // 怪物属性初始化定义: this.enemys = { - 'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 0, 'special': 0}, + 'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 1, 'special': [1,5,7,8]}, 'redSlime': {'name': '红头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'blackSlime': {'name': '青头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 9}, + 'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': [1,9]}, 'bat': {'name': '小蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 2, 'experience': 0, 'special': 1}, 'bigBat': {'name': '大蝙蝠', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redBat': {'name': '红蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 5, 'experience': 0, 'special': 4}, @@ -21,10 +22,10 @@ enemys.prototype.init = function () { 'zombieKnight': {'name': '兽人武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'rock': {'name': '石头人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 4, 'experience': 0, 'special': 3}, 'slimeMan': {'name': '影子战士', 'hp': 100, 'atk': 0, 'def': 0, 'money': 11, 'experience': 0, 'special': 10}, // 模仿怪的攻防设为0就好 - 'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2}, + 'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2, 'point': 1}, // 'point'可以在打败怪物后进行加点,详见文档说明。 'redPriest': {'name': '高级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100}, // 领域怪需要加value表示领域伤害的数值 - 'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200}, + 'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100, 'zoneSquare': true}, // 领域怪需要加value表示领域伤害的数值;zoneSquare代表是否九宫格伤害 + 'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200, 'range': 2}, // range可选,代表领域伤害的范围;不加默认为1 'yellowGuard': {'name': '初级卫兵', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 0}, 'blueGuard': {'name': '中级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redGuard': {'name': '高级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, @@ -41,7 +42,7 @@ enemys.prototype.init = function () { 'poisonSkeleton': {'name': '紫骷髅', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'poisonBat': {'name': '紫蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 14, 'experience': 0, 'special': 13}, 'steelRock': {'name': '铁面人', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'skeletonPriest': {'name': '骷髅法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, + 'skeletonPriest': {'name': '骷髅法师', 'hp': 100, 'atk': 100, 'def': 0, 'money': 0, 'experience': 0, 'special': 18, 'value': 20}, 'skeletonKing': {'name': '骷髅王', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'skeletonWizard': {'name': '骷髅巫师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redSkeletonCaption': {'name': '骷髅武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'special': 0}, @@ -58,7 +59,7 @@ enemys.prototype.init = function () { 'badPrincess': {'name': '痛苦魔女', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'badFairy': {'name': '黑暗仙子', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'grayPriest': {'name': '中级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6}, + 'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6, 'n': 8}, // 多连击需要在后面指定n代表是几连击 'whiteGhost': {'name': '水银战士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, '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}, @@ -68,27 +69,31 @@ enemys.prototype.init = function () { } } +////// 获得一个或所有怪物数据 ////// enemys.prototype.getEnemys = function (enemyId) { - if (enemyId == undefined) { + if (!core.isset(enemyId)) { return this.enemys; } return this.enemys[enemyId]; } +////// 判断是否含有某特殊属性 ////// enemys.prototype.hasSpecial = function (special, test) { - return special!=0 && (special%100 == test || this.hasSpecial(parseInt(special/100), test)); + return (special instanceof Array)?special.indexOf(test)>=0:(special!=0&&(special%100==test||this.hasSpecial(parseInt(special/100), test))); } +////// 获得所有特殊属性的名称 ////// 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("净化"); @@ -100,18 +105,63 @@ 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("仇恨"); - return text.join(" "); + if (this.hasSpecial(special, 18)) text.push("阻击"); + if (this.hasSpecial(special, 19)) text.push("自爆"); + if (this.hasSpecial(special, 20)) text.push("无敌"); + return text; } +////// 获得每个特殊属性的说明 ////// +enemys.prototype.getSpecialHint = function (enemy, special) { + if (!core.isset(special)) { + var hints = []; + for (var i=1;i<100;i++) { + if (this.hasSpecial(enemy.special, i)) { + var hint=this.getSpecialHint(enemy, i); + if (hint!='') + hints.push(hint); + } + } + return hints; + } + + switch (special) { + case 1: return "先攻:怪物首先攻击"; + case 2: return "魔攻:怪物无视勇士的防御"; + case 3: return "坚固:勇士每回合最多只能对怪物造成1点伤害"; + case 4: return "2连击:怪物每回合攻击2次"; + case 5: return "3连击:怪物每回合攻击3次"; + case 6: return (enemy.n||4)+"连击: 怪物每回合攻击"+(enemy.n||4)+"次"; + case 7: return "破甲:战斗前,怪物附加角色防御的"+parseInt(100*core.values.breakArmor)+"%作为伤害"; + case 8: return "反击:战斗时,怪物每回合附加角色攻击的"+parseInt(100*core.values.counterAttack)+"%作为伤害,无视角色防御"; + case 9: return "净化:战斗前,怪物附加勇士魔防的"+core.values.purify+"倍作为伤害"; + case 10: return "模仿:怪物的攻防和勇士攻防相等"; + case 11: return "吸血:战斗前,怪物首先吸取角色的"+parseInt(100*enemy.value)+"%生命作为伤害"; + case 12: return "中毒:战斗后,勇士陷入中毒状态,每一步损失生命"+core.values.poisonDamage+"点"; + case 13: return "衰弱:战斗后,勇士陷入衰弱状态,攻防暂时下降"+core.values.weakValue+"点"; + case 14: return "诅咒:战斗后,勇士陷入诅咒状态,战斗无法获得金币和经验"; + case 15: return "领域:经过怪物周围"+(enemy.range||1)+"格时自动减生命"+enemy.value+"点"; + case 16: return "夹击:经过两只相同的怪物中间,勇士生命值变成一半"; + case 17: return "仇恨:战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得"+core.values.hatred+"点仇恨值)"; + case 18: return "阻击:经过怪物的十字领域时自动减生命"+enemy.value+"点,同时怪物后退一格"; + case 19: return "自爆:战斗后勇士的生命值变成1"; + case 20: return "无敌:勇士无法打败怪物,除非拥有十字架"; + default: break; + } + return "" +} + +////// 获得某个怪物的伤害 ////// enemys.prototype.getDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; var hero_atk = core.status.hero.atk, hero_def = core.status.hero.def, hero_mdef = core.status.hero.mdef; var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def, mon_special = monster.special; - var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special); + var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, monster.n); if (damage == 999999999) return damage; return damage + this.getExtraDamage(monster); } +////// 获得某个怪物的额外伤害 ////// enemys.prototype.getExtraDamage = function (monster) { var extra_damage = 0; if (this.hasSpecial(monster.special, 11)) { // 吸血 @@ -125,17 +175,17 @@ enemys.prototype.getExtraDamage = function (monster) { return extra_damage; } -// 临界值计算 +////// 临界值计算 ////// enemys.prototype.getCritical = function (monsterId) { var monster = core.material.enemys[monsterId]; if (this.hasSpecial(monster.special, 3) || this.hasSpecial(monster.special, 10)) return "???"; var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); - if (last == 0) return 0; + monster.hp, monster.atk, monster.def, monster.special, monster.n); + if (last <= 0) return 0; for (var i = core.status.hero.atk + 1; i <= monster.hp + monster.def; i++) { var damage = this.calDamage(i, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); if (damage < last) return i - core.status.hero.atk; last = damage; @@ -143,40 +193,46 @@ enemys.prototype.getCritical = function (monsterId) { return 0; } -// 临界减伤计算 +////// 临界减伤计算 ////// enemys.prototype.getCriticalDamage = function (monsterId) { var c = this.getCritical(monsterId); if (c == '???') return '???'; - if (c == 0) return 0; + if (c <= 0) return 0; var monster = core.material.enemys[monsterId]; - // if (c<=0) return 0; var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); if (last == 999999999) return '???'; return last - this.calDamage(core.status.hero.atk + c, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); } -// 1防减伤计算 +////// 1防减伤计算 ////// enemys.prototype.getDefDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; - return this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special) - - this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special) + var nowDamage = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, + monster.hp, monster.atk, monster.def, monster.special, monster.n); + var nextDamage = this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef, + monster.hp, monster.atk, monster.def, monster.special, monster.n); + if (nowDamage == 999999999 || nextDamage == 999999999) return "???"; + return nowDamage - nextDamage; } -enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) { - // 魔攻 - if (this.hasSpecial(mon_special,2)) hero_def = 0; - // 坚固 - if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1; +////// 具体的伤害计算公式 ////// +enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, n) { + + if (this.hasSpecial(mon_special, 20) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 + return 999999999; // 返回无限大 + // 模仿 if (this.hasSpecial(mon_special,10)) { mon_atk = hero_atk; mon_def = hero_def; } + // 魔攻 + if (this.hasSpecial(mon_special,2)) hero_def = 0; + // 坚固 + if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1; if (hero_atk <= mon_def) return 999999999; // 不可战斗时请直接返回999999999 var per_damage = mon_atk - hero_def; @@ -185,7 +241,7 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo if (this.hasSpecial(mon_special, 4)) per_damage *= 2; if (this.hasSpecial(mon_special, 5)) per_damage *= 3; - if (this.hasSpecial(mon_special, 6)) per_damage *= 4; + if (this.hasSpecial(mon_special, 6)) per_damage *= (n||4); var counterDamage = 0; // 反击 @@ -202,14 +258,10 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo var ans = damage + turn * per_damage + (turn + 1) * counterDamage; ans -= hero_mdef; - // 魔防回血 - // return ans; - - // 魔防不回血 - return ans <= 0 ? 0 : ans; + return core.flags.enableNegativeDamage?ans:Math.max(0, ans); } -// 获得当前楼层的怪物列表 +////// 获得当前楼层的怪物列表 ////// enemys.prototype.getCurrentEnemys = function () { var enemys = []; var used = {}; @@ -229,6 +281,10 @@ enemys.prototype.getCurrentEnemys = function () { mon_def=core.status.hero.def; } + var specialText = core.enemys.getSpecialText(monsterId); + if (specialText.length>=3) specialText = "多属性..."; + else specialText = specialText.join(" "); + enemys.push({ 'id': monsterId, 'name': monster.name, @@ -237,7 +293,7 @@ enemys.prototype.getCurrentEnemys = function () { 'def': mon_def, 'money': monster.money, 'experience': monster.experience, - 'special': core.enemys.getSpecialText(monsterId), + 'special': specialText, 'damage': this.getDamage(monsterId), 'critical': this.getCritical(monsterId), 'criticalDamage': this.getCriticalDamage(monsterId), diff --git a/libs/events.js b/libs/events.js index 91091425..cf66d51e 100644 --- a/libs/events.js +++ b/libs/events.js @@ -2,6 +2,7 @@ function events() { } +////// 初始化 ////// events.prototype.init = function () { this.events = { 'battle': function (data, core, callback) { @@ -20,12 +21,11 @@ events.prototype.init = function () { callback(); }, 'changeFloor': function (data, core, callback) { - var heroLoc = null; - if (core.isset(data.event.data.loc)) { + var heroLoc = {}; + if (core.isset(data.event.data.loc)) heroLoc = {'x': data.event.data.loc[0], 'y': data.event.data.loc[1]}; - if (core.isset(data.event.data.direction)) - heroLoc.direction = data.event.data.direction; - } + if (core.isset(data.event.data.direction)) + heroLoc.direction = data.event.data.direction; core.changeFloor(data.event.data.floorId, data.event.data.stair, heroLoc, data.event.data.time, callback); }, @@ -46,9 +46,9 @@ events.prototype.init = function () { } } -// 初始化 +////// 获得一个或所有系统事件类型 ////// events.prototype.getEvents = function (eventName) { - if (eventName == undefined) { + if (!core.isset(eventName)) { return this.events; } return this.events[eventName]; @@ -88,12 +88,14 @@ events.prototype.startGame = function (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来获得当前难度 @@ -103,9 +105,8 @@ events.prototype.setInitData = function (hard) { } } -////// 游戏结束事件 ////// +////// 游戏获胜事件 ////// events.prototype.win = function(reason) { - // 获胜 core.waitHeroToStop(function() { core.removeGlobalAnimate(0,0,true); core.clearMap('all'); // 清空全地图 @@ -117,8 +118,8 @@ events.prototype.win = function(reason) { }); } +////// 游戏失败事件 ////// events.prototype.lose = function(reason) { - // 失败 core.waitHeroToStop(function() { core.drawText([ "\t[结局1]你死了。\n如题。" @@ -136,7 +137,7 @@ events.prototype.afterChangeFloor = function (floorId) { } } -////// 实际事件的处理 ////// +////// 开始执行一系列自定义事件 ////// events.prototype.doEvents = function (list, x, y, callback) { // 停止勇士 core.waitHeroToStop(function() { @@ -152,6 +153,7 @@ events.prototype.doEvents = function (list, x, y, callback) { }); } +////// 执行当前自定义事件列表中的下一个事件 ////// events.prototype.doAction = function() { // 清空boxAnimate和UI层 clearInterval(core.interval.boxAnimate); @@ -162,7 +164,7 @@ events.prototype.doAction = function() { if (core.status.event.data.list.length==0) { if (core.isset(core.status.event.data.callback)) core.status.event.data.callback(); - core.ui.closePanel(false); + core.ui.closePanel(); return; } @@ -191,12 +193,12 @@ events.prototype.doAction = function() { 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 () { - core.addBlock(data.loc[0],data.loc[1],data.floorId); + core.showBlock(data.loc[0],data.loc[1],data.floorId); core.events.doAction(); }); } else { - core.addBlock(data.loc[0],data.loc[1],data.floorId) + core.showBlock(data.loc[0],data.loc[1],data.floorId) this.doAction(); } break; @@ -286,11 +288,6 @@ events.prototype.doAction = function() { block = block.block; if (core.isset(block.event) && block.event.trigger=='action') { // 触发 - /* - core.status.event = {'id': 'action', 'data': { - 'list': core.clone(block.event.data), 'x': block.x, 'y': block.y, 'callback': core.status.event.data.callback - }} - */ core.status.event.data.list = core.clone(block.event.data); core.status.event.data.x=block.x; core.status.event.data.y=block.y; @@ -299,11 +296,21 @@ events.prototype.doAction = function() { this.doAction(); break; case "playSound": - var name=data.name.split("."); - if (name.length==2) - core.playSound(name[0],name[1]); + core.playSound(data.name); this.doAction(); break; + case "playBgm": + core.playBgm(data.name); + this.doAction(); + break + case "pauseBgm": + core.pauseBgm(); + this.doAction(); + break + case "resumeBgm": + core.resumeBgm(); + this.doAction(); + break case "setValue": try { var value=core.calValue(data.value); @@ -330,7 +337,6 @@ events.prototype.doAction = function() { core.status.hero.hp=0; core.updateStatusBar(); core.events.lose('damage'); - } else { core.updateStatusBar(); @@ -354,8 +360,14 @@ events.prototype.doAction = function() { core.events.lose(data.reason); break; case "function": - if (core.isset(data["function"])) - data["function"](); + var func = data["function"]; + if (core.isset(func)) { + if ((typeof func == "string") && func.indexOf("function")==0) { + eval('('+func+')()'); + } + else if (func instanceof Function) + func(); + } this.doAction(); break; case "update": @@ -390,10 +402,15 @@ events.prototype.doAction = function() { ////// 往当前事件列表之前添加一个或多个事件 ////// events.prototype.insertAction = function (action) { - core.unshift(core.status.event.data.list, action) + if (core.status.event.id == null) { + this.doEvents(action); + } + else { + core.unshift(core.status.event.data.list, action) + } } -////// 打开商店 ////// +////// 打开一个全局商店 ////// events.prototype.openShop = function(shopId, needVisited) { var shop = core.status.shops[shopId]; shop.times = shop.times || 0; @@ -435,35 +452,11 @@ events.prototype.openShop = function(shopId, needVisited) { core.ui.drawChoices(content, choices); } +////// 禁用一个全局商店 ////// events.prototype.disableQuickShop = function (shopId) { core.status.shops[shopId].visited = false; } -////// 降低难度 ////// - -events.prototype.decreaseHard = function() { - core.drawTip("本塔不支持降低难度!"); - /* - if (core.status.hard == 0) { - core.drawTip("当前已是难度0,不能再降低难度了"); - return; - } - var add = 100, x=core.status.hard; - while (x<10) { - x++; add*=2; - } - core.ui.drawConfirmBox("本次操作可生命+" + add + ",确定吗?", function () { - core.status.hero.hp += add; - core.status.hard--; - core.updateStatusBar(); - core.ui.closePanel(); - core.drawTip("降低难度成功,生命+" + add); - }, function () { - core.ui.drawSettings(false); - }); - */ -} - ////// 能否使用快捷商店 ////// events.prototype.canUseQuickShop = function(shopIndex) { if (core.isset(core.floors[core.status.floorId].canUseQuickShop) && !core.isset(core.floors[core.status.floorId].canUseQuickShop)) @@ -472,9 +465,36 @@ events.prototype.canUseQuickShop = function(shopIndex) { return null; } +////// 检查升级事件 ////// +events.prototype.checkLvUp = function () { + if (!core.flags.enableLevelUp || core.status.hero.lv>=core.firstData.levelUp.length) return; + // 计算下一个所需要的数值 + var need=core.firstData.levelUp[core.status.hero.lv].need; + if (!core.isset(need)) return; + if (core.status.hero.experience>=need) { + // 升级 + core.status.hero.lv++; + var effect = core.firstData.levelUp[core.status.hero.lv-1].effect; + if (typeof effect == "string") { + if (effect.indexOf("function")==0) { + eval("("+effect+")()"); + } + else { + effect.split(";").forEach(function (t) { + core.doEffect(t); + }); + } + } + else if (effect instanceof Function) { + effect(); + } + this.checkLvUp(); + } +} + ////// 尝试使用道具 ////// events.prototype.useItem = function(itemId) { - core.ui.closePanel(false); + core.ui.closePanel(); if (itemId=='book') { core.openBook(false); @@ -497,7 +517,30 @@ events.prototype.useItem = function(itemId) { else core.drawTip("当前无法使用"+core.material.items[itemId].name); } -/****** 打完怪物 ******/ +////// 加点事件 ////// +events.prototype.addPoint = function (enemy) { + var point = enemy.point; + if (!core.isset(point) || point<=0) return []; + + // 加点,返回一个choices事件 + return [ + {"type": "choices", + "choices": [ + {"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)} + ]}, + ] + } + ]; +} + +////// 战斗结束后触发的事件 ////// events.prototype.afterBattle = function(enemyId,x,y,callback) { // 毒衰咒的处理 @@ -520,50 +563,66 @@ events.prototype.afterBattle = function(enemyId,x,y,callback) { if (core.enemys.hasSpecial(special, 17)) { core.setFlag('hatred', parseInt(core.getFlag('hatred', 0)/2)); } + // 自爆 + if (core.enemys.hasSpecial(special, 19)) { + core.status.hero.hp = 1; + } // 增加仇恨值 core.setFlag('hatred', core.getFlag('hatred',0)+core.values.hatred); core.updateStatusBar(); - // 如果已有事件正在处理中 - if (core.status.lockControl) { - if (core.isset(callback)) callback(); - return; + + // 事件的处理 + var todo = []; + // 如果不为阻击,且该点存在,且有事件 + if (!core.enemys.hasSpecial(special, 18) && core.isset(x) && core.isset(y)) { + var event = core.floors[core.status.floorId].afterBattle[x+","+y]; + if (core.isset(event)) { + // 插入事件 + core.unshift(todo, event); + } + } + // 如果有加点 + var point = core.material.enemys[enemyId].point; + if (core.isset(point) && point>0) { + core.unshift(todo, core.events.addPoint(core.material.enemys[enemyId])); } - // 检查处理后的事件。 - var event = core.floors[core.status.floorId].afterBattle[x+","+y]; - if (core.isset(event)) { - core.events.doEvents(event, x, y, callback); + // 如果事件不为空,将其插入 + if (todo.length>0) { + this.insertAction(todo); } - //继续行走 - else { + + // 如果已有事件正在处理中 + if (core.status.event.id == null) { core.continueAutomaticRoute(); - if (core.isset(callback)) callback(); } + if (core.isset(callback)) callback(); + } -/****** 开完门 ******/ +////// 开一个门后触发的事件 ////// events.prototype.afterOpenDoor = function(doorId,x,y,callback) { - // 如果已有事件正在处理中 - if (core.status.lockControl) { - if (core.isset(callback)) callback(); - return; + var todo = []; + if (core.isset(x) && core.isset(y)) { + var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y]; + if (core.isset(event)) { + core.unshift(todo, event); + } } - // 检查处理后的事件。 - var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y]; - if (core.isset(event)) { - core.events.doEvents(event, x, y, callback); + if (todo.length>0) { + this.insertAction(todo); } - //继续行走 - else { + + if (core.status.event.id == null) { core.continueAutomaticRoute(); - if (core.isset(callback)) callback(); } + if (core.isset(callback)) callback(); } -/****** 经过路障 ******/ +////// 经过一个路障 ////// events.prototype.passNet = function (data) { // 有鞋子 if (core.hasItem('shoes')) return; @@ -594,6 +653,7 @@ events.prototype.passNet = function (data) { core.updateStatusBar(); } +////// 改变亮灯(感叹号)的事件 ////// events.prototype.changeLight = function(x, y) { var block = core.getBlock(x, y); if (block==null) return; @@ -610,26 +670,33 @@ events.prototype.changeLight = function(x, y) { this.afterChangeLight(x,y); } -// 改变灯后的事件 +////// 改变亮灯之后,可以触发的事件 ////// events.prototype.afterChangeLight = function(x,y) { } -// 存档事件前一刻的处理 +////// 使用炸弹/圣锤后的事件 ////// +events.prototype.afterUseBomb = function () { + + +} + +////// 即将存档前可以执行的操作 ////// events.prototype.beforeSaveData = function(data) { } -// 读档事件后,载入事件前,对数据的处理 +////// 读档事件后,载入事件前,可以执行的操作 ////// events.prototype.afterLoadData = function(data) { } -/******************************************/ -/*********** 界面上的点击事件 ***************/ -/******************************************/ +/****************************************/ +/********** 点击事件、键盘事件 ************/ +/****************************************/ +////// 按下Ctrl键时(快捷跳过对话) ////// events.prototype.keyDownCtrl = function () { if (core.status.event.id=='text') { core.drawText(); @@ -641,6 +708,15 @@ events.prototype.keyDownCtrl = function () { } } +////// 点击确认框时 ////// +events.prototype.clickConfirmBox = function (x,y) { + if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) + core.status.event.data.yes(); + if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) + core.status.event.data.no(); +} + +////// 键盘操作确认框时 ////// events.prototype.keyUpConfirmBox = function (keycode) { if (keycode==37) { core.status.event.selection=0; @@ -663,14 +739,8 @@ events.prototype.keyUpConfirmBox = function (keycode) { } } } -events.prototype.clickConfirmBox = function (x,y) { - if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) - core.status.event.data.yes(); - if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) - core.status.event.data.no(); -} -// 正在处理事件时的点击操作... +////// 自定义事件时的点击操作 ////// events.prototype.clickAction = function (x,y) { if (core.status.event.data.type=='text') { @@ -693,6 +763,7 @@ events.prototype.clickAction = function (x,y) { } } +////// 自定义事件时,按下某个键的操作 ////// events.prototype.keyDownAction = function (keycode) { if (core.status.event.data.type=='choices') { var data = core.status.event.data.current; @@ -712,6 +783,7 @@ events.prototype.keyDownAction = function (keycode) { } } +////// 自定义事件时,放开某个键的操作 ////// events.prototype.keyUpAction = function (keycode) { if (core.status.event.data.type=='text' && (keycode==13 || keycode==32 || keycode==67)) { this.doAction(); @@ -729,37 +801,67 @@ events.prototype.keyUpAction = function (keycode) { } } -// 怪物手册 +////// 怪物手册界面的点击操作 ////// events.prototype.clickBook = function(x,y) { // 上一页 if ((x == 3 || x == 4) && y == 12) { - core.ui.drawEnemyBook(core.status.event.data - 1); + core.ui.drawBook(core.status.event.data - 6); + return; } // 下一页 if ((x == 8 || x == 9) && y == 12) { - core.ui.drawEnemyBook(core.status.event.data + 1); + core.ui.drawBook(core.status.event.data + 6); + return; } // 返回 if (x>=10 && x<=12 && y==12) { - core.ui.closePanel(true); + core.ui.closePanel(); + return; + } + // 怪物信息 + var data = core.status.event.data; + if (core.isset(data) && y<12) { + var page=parseInt(data/6); + var index=6*page+parseInt(y/2); + core.ui.drawBook(index); + core.ui.drawBookDetail(index); } return; } +////// 怪物手册界面时,按下某个键的操作 ////// events.prototype.keyDownBook = function (keycode) { - if (keycode==37 || keycode==38) core.ui.drawEnemyBook(core.status.event.data - 1); - else if (keycode==39 || keycode==40) core.ui.drawEnemyBook(core.status.event.data + 1); + if (keycode==37) core.ui.drawBook(core.status.event.data-6); + if (keycode==38) core.ui.drawBook(core.status.event.data-1); + if (keycode==39) core.ui.drawBook(core.status.event.data+6); + if (keycode==40) core.ui.drawBook(core.status.event.data+1); + if (keycode==33) core.ui.drawBook(core.status.event.data-6); + if (keycode==34) core.ui.drawBook(core.status.event.data+6); return; } +////// 怪物手册界面时,放开某个键的操作 ////// events.prototype.keyUpBook = function (keycode) { if (keycode==27 || keycode==88) { - core.ui.closePanel(true); + core.ui.closePanel(); + return; + } + if (keycode==13 || keycode==32 || keycode==67) { + var data=core.status.event.data; + if (core.isset(data)) { + this.clickBook(6, 2*(data%6)); + } return; } } -// 飞行器 +////// 怪物手册属性显示界面时的点击操作 ////// +events.prototype.clickBookDetail = function () { + core.clearMap('data', 0, 0, 416, 416); + core.status.event.id = 'book'; +} + +////// 楼层传送器界面时的点击操作 ////// events.prototype.clickFly = function(x,y) { if ((x==10 || x==11) && y==9) core.ui.drawFly(core.status.event.data-1); if ((x==10 || x==11) && y==5) core.ui.drawFly(core.status.event.data+1); @@ -774,12 +876,14 @@ events.prototype.clickFly = function(x,y) { return; } +////// 楼层传送器界面时,按下某个键的操作 ////// events.prototype.keyDownFly = function (keycode) { if (keycode==37 || keycode==38) core.ui.drawFly(core.status.event.data+1); else if (keycode==39 || keycode==40) core.ui.drawFly(core.status.event.data-1); return; } +////// 楼层传送器界面时,放开某个键的操作 ////// events.prototype.keyUpFly = function (keycode) { if (keycode==71 || keycode==27 || keycode==88) core.ui.closePanel(); @@ -788,7 +892,7 @@ events.prototype.keyUpFly = function (keycode) { return; } -// 商店 +////// 商店界面时的点击操作 ////// events.prototype.clickShop = function(x,y) { var shop = core.status.event.data.shop; var choices = shop.choices; @@ -818,12 +922,7 @@ events.prototype.clickShop = function(x,y) { // 更新属性 choice.effect.split(";").forEach(function (t) { - if (t.indexOf("status:")==0) { - eval(t.replace("status:", "core.status.hero.")); - } - else if (t.indexOf("item:")==0) { - eval(t.replace("item:", "core.getItem('").replace("+=", "', ")+")"); - } + core.doEffect(t); }); core.updateStatusBar(); shop.times++; @@ -840,6 +939,7 @@ events.prototype.clickShop = function(x,y) { } } +////// 商店界面时,按下某个键的操作 ////// events.prototype.keyDownShop = function (keycode) { var shop = core.status.event.data.shop; var choices = shop.choices; @@ -855,6 +955,7 @@ events.prototype.keyDownShop = function (keycode) { } } +////// 商店界面时,放开某个键的操作 ////// events.prototype.keyUpShop = function (keycode) { if (keycode==27 || keycode==88) { if (core.status.event.data.fromList) { @@ -875,7 +976,7 @@ events.prototype.keyUpShop = function (keycode) { return; } -// 快捷商店 +////// 快捷商店界面时的点击操作 ////// events.prototype.clickQuickShop = function(x, y) { var shopList = core.status.shops, keys = Object.keys(shopList); if (x >= 5 && x <= 7) { @@ -896,6 +997,7 @@ events.prototype.clickQuickShop = function(x, y) { } } +////// 快捷商店界面时,按下某个键的操作 ////// events.prototype.keyDownQuickShop = function (keycode) { var shopList = core.status.shops, keys = Object.keys(shopList); if (keycode==38) { @@ -910,6 +1012,7 @@ events.prototype.keyDownQuickShop = function (keycode) { } } +////// 快捷商店界面时,放开某个键的操作 ////// events.prototype.keyUpQuickShop = function (keycode) { if (keycode==27 || keycode==75 || keycode==88) { core.ui.closePanel(); @@ -923,11 +1026,11 @@ events.prototype.keyUpQuickShop = function (keycode) { return; } -// 工具栏 +////// 工具栏界面时的点击操作 ////// events.prototype.clickToolbox = function(x,y) { // 返回 if (x>=10 && x<=12 && y==12) { - core.ui.closePanel(false); + core.ui.closePanel(); return; } var index=0; @@ -937,6 +1040,7 @@ events.prototype.clickToolbox = function(x,y) { this.clickToolboxIndex(index); } +////// 选择工具栏界面中某个Index后的操作 ////// events.prototype.clickToolboxIndex = function(index) { var items = null; var ii=index; @@ -957,6 +1061,7 @@ events.prototype.clickToolboxIndex = function(index) { } } +////// 工具栏界面时,按下某个键的操作 ////// events.prototype.keyDownToolbox = function (keycode) { if (!core.isset(core.status.event.data)) return; @@ -1020,6 +1125,7 @@ events.prototype.keyDownToolbox = function (keycode) { } } +////// 工具栏界面时,放开某个键的操作 ////// events.prototype.keyUpToolbox = function (keycode) { if (keycode==84 || keycode==27 || keycode==88) { core.ui.closePanel(); @@ -1033,7 +1139,7 @@ events.prototype.keyUpToolbox = function (keycode) { } } -// 存读档 +////// 存读档界面时的点击操作 ////// events.prototype.clickSL = function(x,y) { // 上一页 if ((x == 3 || x == 4) && y == 12) { @@ -1045,7 +1151,7 @@ events.prototype.clickSL = function(x,y) { } // 返回 if (x>=10 && x<=12 && y==12) { - core.ui.closePanel(false); + core.ui.closePanel(); if (!core.isPlaying()) { core.showStartAnimate(); } @@ -1066,6 +1172,7 @@ events.prototype.clickSL = function(x,y) { } } +////// 存读档界面时,按下某个键的操作 ////// events.prototype.keyDownSL = function(keycode) { if (keycode==37) { // left core.ui.drawSLPanel(core.status.event.data - 1); @@ -1093,6 +1200,7 @@ events.prototype.keyDownSL = function(keycode) { } } +////// 存读档界面时,放开某个键的操作 ////// events.prototype.keyUpSL = function (keycode) { if (keycode==27 || keycode==88 || (core.status.event.id == 'save' && keycode==83) || (core.status.event.id == 'load' && keycode==68)) { core.ui.closePanel(); @@ -1107,41 +1215,53 @@ events.prototype.keyUpSL = function (keycode) { } } +////// 系统设置界面时的点击操作 ////// events.prototype.clickSwitchs = function (x,y) { if (x<5 || x>7) return; var choices = [ - "背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" + "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y7) return; var choices = [ @@ -1228,6 +1350,7 @@ events.prototype.clickSettings = function (x,y) { return; } +////// 系统菜单栏界面时,按下某个键的操作 ////// events.prototype.keyDownSettings = function (keycode) { var choices = [ "系统设置", "快捷商店", "同步存档", "重新开始", "操作帮助", "关于本塔", "返回游戏" @@ -1244,6 +1367,7 @@ events.prototype.keyDownSettings = function (keycode) { } } +////// 系统菜单栏界面时,放开某个键的操作 ////// events.prototype.keyUpSettings = function (keycode) { if (keycode==27 || keycode==88) { core.ui.closePanel(); @@ -1258,6 +1382,7 @@ events.prototype.keyUpSettings = function (keycode) { } } +////// 同步存档界面时的点击操作 ////// events.prototype.clickSyncSave = function (x,y) { if (x<5 || x>7) return; var choices = [ @@ -1293,6 +1418,7 @@ events.prototype.clickSyncSave = function (x,y) { return; } +////// 同步存档界面时,按下某个键的操作 ////// events.prototype.keyDownSyncSave = function (keycode) { var choices = [ "同步存档到服务器", "从服务器加载存档", "清空本地存档", "返回主菜单" @@ -1309,6 +1435,7 @@ events.prototype.keyDownSyncSave = function (keycode) { } } +////// 同步存档界面时,放开某个键的操作 ////// events.prototype.keyUpSyncSave = function (keycode) { if (keycode==27 || keycode==88) { core.status.event.selection=2; @@ -1324,11 +1451,11 @@ events.prototype.keyUpSyncSave = function (keycode) { } } +////// “关于”界面时的点击操作 ////// events.prototype.clickAbout = function () { if (core.isPlaying()) - core.ui.closePanel(false); + core.ui.closePanel(); else core.showStartAnimate(); } -/*********** 点击事件 END ***************/ diff --git a/libs/floors/MT0.js b/libs/floors/MT0.js index a267cd1e..624b8e91 100644 --- a/libs/floors/MT0.js +++ b/libs/floors/MT0.js @@ -4,10 +4,13 @@ main.floors.MT0 = { "floorId": "MT0", // 楼层唯一标识符,需要和名字完全一致 "title": "主塔 0 层", // 楼层中文名 - "name": 0, // 显示在状态栏中的层数 + "name": "0", // 显示在状态栏中的层数 "canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器) "canUseQuickShop": true, // 该层是否允许使用快捷商店 "defaultGround": "ground", // 默认地面的图块ID(terrains中) + // "png": "bg.png", // 背景图;你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。 + // "color": [0,0,0,0.3], // 该层的默认画面色调。本项可不写(代表无色调),如果写需要是一个RGBA数组。 + // "bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。 "map": [ // 地图数据,需要是13x13,建议使用地图生成器来生成 ], diff --git a/libs/floors/sample0.js b/libs/floors/sample0.js index 88f49e47..bbc54cd8 100644 --- a/libs/floors/sample0.js +++ b/libs/floors/sample0.js @@ -4,10 +4,13 @@ main.floors.sample0 = { "floorId": "sample0", // 楼层唯一标识符,需要和名字完全一致 "title": "样板 0 层", // 楼层中文名 - "name": 0, // 显示在状态栏中的层数 + "name": "0", // 显示在状态栏中的层数 "canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器) "canUseQuickShop": true, // 该层是否允许使用快捷商店 "defaultGround": "ground", // 默认地面的图块ID(terrains中) + // "png": "bg.png", // 背景图;你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。 + // "color": [0,0,0,0.3] // 该层的默认画面色调。本项可不写(代表无色调),如果写需要是一个RGBA数组。 + "bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。 "map": [ // 地图数据,需要是13x13,建议使用地图生成器来生成 [0, 0, 220, 0, 0, 20, 87, 3, 65, 64, 44, 43, 42], [0, 246, 0, 246, 0, 20, 0, 3, 58, 59, 60, 61, 41], @@ -16,9 +19,9 @@ main.floors.sample0 = { [216, 247, 256, 235, 248, 6, 0, 3, 49, 50, 51, 52, 38], [6, 6, 125, 6, 6, 6, 0, 1, 45, 46, 47, 48, 37], [224, 254, 212, 232, 204, 5, 0, 1, 31, 32, 34, 33, 36], - [201, 205, 217, 215, 207, 5, 50, 1, 27, 28, 29, 30, 35], - [5, 5, 125, 5, 5, 5, 50, 1, 21, 22, 23, 24, 25], - [0, 0, 0, 0, 0, 0, 45, 1, 1, 1, 121, 1, 1], + [201, 205, 217, 215, 207, 5, 0, 1, 27, 28, 29, 30, 35], + [5, 5, 125, 5, 5, 5, 0, 1, 21, 22, 23, 24, 25], + [0, 0, 237, 0, 0, 0, 45, 1, 1, 1, 121, 1, 1], [4, 4, 126, 4, 4, 4, 0, 0, 0, 0, 0, 85, 124], [87, 11, 12, 13, 14, 4, 4, 2, 2, 2, 122, 2, 2], [88, 89, 90, 91, 92, 93, 94, 2, 81, 82, 83, 84, 86], @@ -51,7 +54,7 @@ main.floors.sample0 = { {"type": "hide", "time": 500} ], "2,8": [ // 守着第一批怪物的老人 - "\t[老人,magician]这些都是各种各样的怪物,所有怪物的数据都在enemys.js中设置。\n\n每个怪物最多只能有一个特殊属性。", + "\t[老人,magician]这些都是各种各样的怪物,所有怪物的数据都在enemys.js中设置。", "\t[老人,magician]这批怪物分别为:普通、先攻、魔攻、坚固、2连击、3连击、4连击、破甲、反击、净化。", "\t[老人,magician]打败怪物后可触发 afterBattle 事件。\n\n有关事件的各种信息在下一层会有更为详细的说明。", {"type": "hide", "time": 500} @@ -102,7 +105,6 @@ main.floors.sample0 = { "炸弹是只能炸面前的怪物还是四个方向的怪物,由data.js中的系统Flag所决定。\n如只能炸前方怪物则和上面的圣锤等价。\n不能被炸的怪物在enemys中可以定义,可参见样板里黑衣魔王和黑暗大法师的写法。", ], "10,4": ["“上楼”和“下楼”的目标层由 main.js 的 floorIds顺序所决定。"], - "10,3": ["十字架目前未被定义,可能需要自行实现功能。\n有关如何实现一个道具功能参见doc文档。"], "9,2": ["该道具默认是大黄门钥匙,如需改为钥匙盒直接修改 data.js 中的系统Flag即可。"], "10,2": ["屠龙匕首目前未被定义,可能需要自行实现功能。\n有关如何实现一个道具功能参见doc文档。"], }, diff --git a/libs/floors/sample1.js b/libs/floors/sample1.js index 4aaa4a92..ee2d9b4e 100644 --- a/libs/floors/sample1.js +++ b/libs/floors/sample1.js @@ -4,24 +4,27 @@ main.floors.sample1 = { "floorId": "sample1", // 楼层唯一标识符,需要和名字完全一致 "title": "样板 1 层", // 楼层中文名 - "name": 1, // 显示在状态栏中的层数 + "name": "1", // 显示在状态栏中的层数 "canFlyTo": true, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器) "canUseQuickShop": true, // 该层是否允许使用快捷商店 "defaultGround": "grass", // 默认地面的图块ID(terrains中) + "png": "bg.png", // 背景图;你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。 + // "color": [0,0,0,0.3] // 该层的默认画面色调。本项可不写(代表无色调),如果写需要是一个RGBA数组。 + // "bgm": "bgm.mp3", // 到达该层后默认播放的BGM。本项可忽略。 "map": [ // 地图数据,需要是13x13,建议使用地图生成器来生成 - [7, 131, 8, 2, 9, 130, 10, 2, 166, 165, 132, 165, 166], - [0, 0, 0, 0, 0, 0, 0, 2, 165, 164, 0, 162, 165], - [2, 2, 2, 2, 121, 2, 2, 2, 0, 0, 229, 0, 0], - [43, 33, 44, 1, 0, 0, 0, 2, 165, 161, 0, 163, 165], - [21, 22, 21, 1, 0, 0, 0, 2, 166, 165, 0, 165, 166], - [1, 245, 1, 1, 0, 87, 0, 2, 2, 2, 85, 2, 2], - [0, 246, 0, 1, 0, 0, 0, 2, 2, 221, 0, 221, 2], - [246, 0, 246, 1, 0, 0, 0, 121, 85, 0, 0, 0, 2], - [1, 246, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2], + [7, 131, 8, 152, 9, 130, 10, 152, 166, 165, 132, 165, 166], + [0, 0, 0, 0, 0, 0, 0, 152, 165, 164, 0, 162, 165], + [152, 152, 152, 152, 121, 152, 152, 152, 0, 0, 229, 0, 0], + [43, 33, 44, 151, 0, 0, 0, 152, 165, 161, 0, 163, 165], + [21, 22, 21, 151, 0, 0, 0, 152, 166, 165, 0, 165, 166], + [151, 245, 151, 151, 0, 87, 0, 152, 152, 152, 85, 153, 153], + [0, 246, 0, 151, 0, 0, 0, 152, 152, 221, 0, 221, 153], + [246, 0, 246, 151, 0, 0, 0, 121, 85, 0, 0, 0, 153], + [151, 246, 151, 151, 0, 153, 153, 153, 153, 153, 153, 153, 153], [0, 0, 0, 0, 0, 0, 0, 164, 0, 0, 163, 0, 0], - [1, 1, 1, 1, 0, 3, 0, 0, 0, 162, 0, 161, 0], - [1, 0, 123, 1, 0, 3, 124, 0, 121, 0, 122, 0, 126], - [1, 0, 0, 1, 88, 3, 86, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 20, 0, 0, 0, 162, 0, 161, 0], + [1, 0, 123, 1, 0, 20, 124, 0, 121, 0, 122, 0, 126], + [1, 0, 0, 1, 88, 20, 86, 0, 0, 0, 0, 0, 0], ], "firstArrive": [ // 第一次到该楼层触发的事件 @@ -31,8 +34,9 @@ main.floors.sample1 = { "4,10": [ // 走到中间时的提示 "\t[样板提示]本层楼将会对各类事件进行介绍。", "左边是一个仿50层的陷阱做法,上方是商店、快捷商店的使用方法,右上是一个典型的杀怪开门的例子,右下是各类可能的NPC事件。", - "本样板目前支持的事件列表大致有:\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更多支持的事件还在编写中,欢迎您宝贵的意见。", + "本样板目前支持的事件列表大致有:\ntext: 显示一段文字(比如你现在正在看到的)\ntip: 左上角显示提示\nshow: 使一个事件有效(可见、可被交互)\nhide: 使一个事件失效(不可见、不可被交互)\ntrigger: 触发另一个地点的事件\nbattle: 强制和某怪物战斗\nopenDoor: 无需钥匙开门(例如机关门、暗墙)", + "openShop: 打开一个全局商店\ndisableShop: 禁用一个全局商店\nchangeFloor: 传送勇士到某层某位置\nchangePos: 传送勇士到当层某位置;转向\nsetFg: 更改画面色调\nmove: 移动事件效果\nmoveHero: 移动勇士效果\nplayBgm: 播放某个背景音乐\npauseBgm: 暂停背景音乐\nresumeBgm: 恢复背景音乐的播放\nplaySound: 播放某个音频", + "if: 条件判断\nchoices: 提供选项\nsetValue: 设置勇士属性道具,或某个变量/flag\nupdate: 更新状态栏和地图显伤\nwin: 获得胜利(游戏通关)\nlose: 游戏失败\nsleep: 等待多少毫秒\nexit: 立刻结束当前事件\nrevisit: 立刻结束事件并重新触发\nfunction: 自定义JS脚本\n\n更多支持的事件还在编写中,欢迎您宝贵的意见。", "有关各事件的样例,可参见本层一些NPC的写法。\n所有事件样例本层都有介绍。\n\n一个自定义事件处理完后,需要调用{\"type\": \"hide\"}该事件才不会再次出现。", {"type": "hide"} ], @@ -273,7 +277,7 @@ main.floors.sample1 = { }, "changeFloor": { // 楼层转换事件;该事件不能和上面的events有冲突(同位置点),否则会被覆盖 "4,12": {"floorId": "sample0", "loc": [6,0]}, // 由于楼下有多个上楼梯,所以需指定位置而不是简单地写"stair": "upFloor" - "5,5": {"floorId": "sample2", "stair": "downFloor"} + "5,5": {"floorId": "sample2", "stair": "downFloor", "direction": "up"} }, "afterBattle": { // 战斗后可能触发的事件列表 "9,6": [ // 初级卫兵1 diff --git a/libs/floors/sample2.js b/libs/floors/sample2.js index 10be1230..5e440aa4 100644 --- a/libs/floors/sample2.js +++ b/libs/floors/sample2.js @@ -4,10 +4,13 @@ main.floors.sample2 = { "floorId": "sample2", // 楼层唯一标识符,需要和名字完全一致 "title": "主塔 40 层", // 楼层中文名 - "name": 40, // 显示在状态栏中的层数 + "name": "40", // 显示在状态栏中的层数 "canFlyTo": false, // 该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器) "canUseQuickShop": true, // 该层是否允许使用快捷商店 - "defaultGround": "soil", // 默认地面的图块ID(terrains中) + "defaultGround": "snowGround", // 默认地面的图块ID(terrains中) + // "png": "bg.png", // 背景图;你可以选择一张png图片来作为背景素材。详细用法请参见文档“自定义素材”中的说明。 + "color": [255,0,0,0.3], // 该层的默认画面色调。本项可不写(代表无色调),如果写需要是一个RGBA数组。 + "bgm": "qianjin.mid", // 到达该层后默认播放的BGM。本项可忽略。 "map": [ // 地图数据,需要是13x13,建议使用地图生成器来生成 [5, 5, 5, 5, 5, 5, 87, 5, 5, 5, 5, 5, 5], [5, 4, 4, 4, 4, 1, 0, 1, 4, 4, 4, 4, 5], @@ -199,10 +202,26 @@ main.floors.sample2 = { {"type": "trigger", "loc": [6,6]} // 立刻触发妖精事件 ] }, - "4,3": {"trigger":"action","enable":false}, // 四个角的大法师,添加trigger:action可避免该点出现显伤 - "8,3": {"trigger":"action","enable":false}, // 四个角的大法师 - "4,6": {"trigger":"action","enable":false}, // 四个角的大法师 - "8,6": {"trigger":"action","enable":false}, // 四个角的大法师 + "4,3": { // 四个角的大法师, + "trigger": "action", + "displayDamage": false, + "enable":false + }, + "8,3": { // 四个角的大法师, + "trigger": "action", + "displayDamage": false, + "enable":false + }, + "4,6": { // 四个角的大法师, + "trigger": "action", + "displayDamage": false, + "enable":false + }, + "8,6": { // 四个角的大法师, + "trigger": "action", + "displayDamage": false, + "enable":false + }, "6,6": { // 妖精 "enable":false, // 初始时禁用状态 diff --git a/libs/items.js b/libs/items.js index e2a36c8a..149fb395 100644 --- a/libs/items.js +++ b/libs/items.js @@ -2,6 +2,7 @@ function items() { } +////// 初始化 ////// items.prototype.init = function () { this.items = { // 钥匙 @@ -36,7 +37,7 @@ items.prototype.init = function () { 'fly': {'cls': 'constants', 'name': '楼层传送器', 'text': '可以自由往来去过的楼层'}, 'coin': {'cls': 'constants', 'name': '幸运金币', 'text': '持有时打败怪物可得双倍金币'}, 'snow': {'cls': 'constants', 'name': '冰冻徽章', 'text': '可以将四周的熔岩变成平地'}, - 'cross': {'cls': 'constants', 'name': '十字架', 'text': '该道具尚未被定义'}, + 'cross': {'cls': 'constants', 'name': '十字架', 'text': '持有后无视怪物的无敌属性'}, 'knife': {'cls': 'constants', 'name': '屠龙匕首', 'text': '该道具尚未被定义'}, 'shoes': {'cls': 'constants', 'name': '绿鞋', 'text': '持有时无视负面地形'}, @@ -59,7 +60,7 @@ items.prototype.init = function () { } } -// 初始化道具 +////// 获得所有道具 ////// items.prototype.getItems = function () { // 大黄门钥匙?钥匙盒? if (core.flags.bigKeyIsBox) @@ -74,7 +75,7 @@ items.prototype.getItems = function () { main.instance.items = new items(); - +////// “即捡即用类”道具的使用效果 ////// items.prototype.getItemEffect = function(itemId, itemNum) { var itemCls = core.material.items[itemId].cls; // 消耗品 @@ -115,6 +116,7 @@ items.prototype.getItemEffect = function(itemId, itemNum) { } } +////// “即捡即用类”道具的文字提示 ////// items.prototype.getItemEffectTip = function(itemId) { if (itemId === 'redJewel') return ",攻击+"+core.values.redJewel; if (itemId === 'blueJewel') return ",防御+"+core.values.blueJewel; @@ -140,14 +142,12 @@ items.prototype.getItemEffectTip = function(itemId) { return ""; } - - +////// 使用道具 ////// items.prototype.useItem = function (itemId) { - // 使用道具 if (!this.canUseItem(itemId)) return; var itemCls = core.material.items[itemId].cls; - if (itemId=='book') core.ui.drawEnemyBook(1); + if (itemId=='book') core.ui.drawBook(0); if (itemId=='fly') core.ui.drawFly(core.status.hero.flyRange.indexOf(core.status.floorId)); if (itemId == 'earthquake' || itemId == 'bomb' || itemId == 'pickaxe' || itemId=='icePickaxe' || itemId == 'snow' || itemId == 'hammer' || itemId=='bigKey') { @@ -157,6 +157,9 @@ items.prototype.useItem = function (itemId) { core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); core.updateFg(); core.drawTip(core.material.items[itemId].name + "使用成功"); + + if (itemId == 'bomb' || itemId == 'hammer') + core.events.afterUseBomb(); }); } if (itemId == 'centerFly') { @@ -182,9 +185,11 @@ items.prototype.useItem = function (itemId) { if (itemId == 'curseWine') core.setFlag('curse', false); if (itemId == 'superWine') { core.setFlag('poison', false); - core.setFlag('weak', false); - core.status.hero.atk += core.values.weakValue; - core.status.hero.def += core.values.weakValue; + if (core.hasFlag('weak')) { + core.setFlag('weak', false); + core.status.hero.atk += core.values.weakValue; + core.status.hero.def += core.values.weakValue; + } core.setFlag('curse', false); } core.updateStatusBar(); @@ -195,12 +200,11 @@ items.prototype.useItem = function (itemId) { delete core.status.hero.items[itemCls][itemId]; } +////// 当前能否使用道具 ////// items.prototype.canUseItem = function (itemId) { // 没有道具 if (!core.hasItem(itemId)) return false; - var itemCls = core.material.items[itemId].cls; - if (itemId == 'book') return true; if (itemId == 'fly') return core.status.hero.flyRange.indexOf(core.status.floorId)>=0; if (itemId == 'pickaxe') { diff --git a/libs/maps.js b/libs/maps.js index b9a26395..1e9c72dc 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -1,6 +1,7 @@ function maps() {} maps.prototype.init = function() {} +////// 加载某个楼层(从剧本或存档中) ////// maps.prototype.loadFloor = function (floorId, map) { var floor = core.floors[floorId]; var content = {}; @@ -13,7 +14,7 @@ maps.prototype.loadFloor = function (floorId, map) { for (var i = 0; i < 13; i++) { for (var j = 0; j < 13; j++) { var block = this.getBlock(j, i, map[i][j]); - if (block.event != undefined) { + if (core.isset(block.event)) { if (block.event.cls == 'enemys' && block.event.trigger==undefined) { block.event.trigger = 'battle'; } @@ -34,7 +35,7 @@ maps.prototype.loadFloor = function (floorId, map) { } } } - this.addEvent(block,j,i,floor.events[j+","+i],floor.defaultGround || "ground") + this.addEvent(block,j,i,floor.events[j+","+i]) this.addChangeFloor(block,j,i,floor.changeFloor[j+","+i]); if (core.isset(block.event)) blocks.push(block); } @@ -44,6 +45,7 @@ maps.prototype.loadFloor = function (floorId, map) { return content; } +////// 数字和ID的对应关系 ////// maps.prototype.getBlock = function (x, y, id) { var enable=null; id = ""+id; @@ -61,6 +63,8 @@ maps.prototype.getBlock = function (x, y, id) { var tmp = {'x': x, 'y': y, 'id': id}; if (enable!=null) tmp.enable = enable; + ////////////////////////// 地形部分 ////////////////////////// + // 0-20 地形 if (id == 1) tmp.event = {'cls': 'terrains', 'id': 'yellowWall'}; // 黄墙 if (id == 2) tmp.event = {'cls': 'terrains', 'id': 'whiteWall'}; // 白墙 @@ -77,15 +81,17 @@ maps.prototype.getBlock = function (x, y, id) { 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}; // 水 - + // 在这里添加更多地形 + // 如果空位不足,可以从180以后开始继续放,只要不和现有的数字冲突即可 // Autotile if (id == 20) tmp.event = {'cls': 'autotile', 'id': 'autotile', 'noPass': true}; // autotile - // 更多的autotile从151到160,只要不和现有的数字冲突即可 + // 更多的autotile从151到160等,只要不和现有的数字冲突即可 if (id == 151) tmp.event = {'cls': 'autotile', 'id': 'autotile1', 'noPass': true}; if (id == 152) tmp.event = {'cls': 'autotile', 'id': 'autotile2', 'noPass': true}; if (id == 153) tmp.event = {'cls': 'autotile', 'id': 'autotile3', 'noPass': true}; + ////////////////////////// 物品部分 ////////////////////////// // 21-80 物品 if (id == 21) tmp.event = {'cls': 'items', 'id': 'yellowKey'}; // 黄钥匙 @@ -134,6 +140,9 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 64) tmp.event = {'cls': 'items', 'id': 'shoes'} // 绿鞋 if (id == 65) tmp.event = {'cls': 'items', 'id': 'hammer'} // 圣锤 + + ////////////////////////// 门、楼梯、传送点部分 ////////////////////////// + // 81-100 门 if (id == 81) tmp.event = {'cls': 'terrains', 'id': 'yellowDoor', 'trigger': 'openDoor'}; // 黄门 if (id == 82) tmp.event = {'cls': 'terrains', 'id': 'blueDoor', 'trigger': 'openDoor'}; // 蓝门 @@ -151,6 +160,8 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 94) tmp.event = {'cls': 'animates', 'id': 'rightPortal', 'noPass': false}; // 右箭头 + ////////////////////////// NPC部分 ////////////////////////// + // 121-150 NPC if (id == 121) tmp.event = {'cls': 'npcs', 'id': 'man'}; if (id == 122) tmp.event = {'cls': 'npcs', 'id': 'woman'}; @@ -165,6 +176,8 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 131) tmp.event = {'cls': 'npcs', 'id': 'blueShop'}; if (id == 132) tmp.event = {'cls': 'npcs', 'id': 'princess'}; + ////////////////////////// 其他部分 ////////////////////////// + // 161-200 其他(单向箭头、灯、箱子等等) if (id == 161) tmp.event = {'cls': 'terrains', 'id': 'arrowUp', 'noPass': false}; // 单向上箭头 if (id == 162) tmp.event = {'cls': 'terrains', 'id': 'arrowDown', 'noPass': false}; // 单向下箭头 @@ -173,6 +186,9 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 165) tmp.event = {'cls': 'terrains', 'id': 'light', 'trigger': 'changeLight', 'noPass': false}; // 灯 if (id == 166) tmp.event = {'cls': 'terrains', 'id': 'darkLight', 'noPass': true}; // 暗灯 + + ////////////////////////// 怪物部分 ////////////////////////// + // 201-300 怪物 if (id == 201) tmp.event = {'cls': 'enemys', 'id': 'greenSlime'}; if (id == 202) tmp.event = {'cls': 'enemys', 'id': 'redSlime'}; @@ -235,13 +251,17 @@ maps.prototype.getBlock = function (x, y, id) { if (id == 259) tmp.event = {'cls': 'enemys', 'id': 'darkFairy'}; if (id == 260) tmp.event = {'cls': 'enemys', 'id': 'greenKnight'}; + ////////////////////////// 待定... ////////////////////////// + // 目前ID暂时不要超过400 + return tmp; } -maps.prototype.addEvent = function (block, x, y, event, ground) { +////// 向该楼层添加剧本的自定义事件 ////// +maps.prototype.addEvent = function (block, x, y, event) { if (!core.isset(event)) return; if (!core.isset(block.event)) { // 本身是空地? - block.event = {'cls': 'terrains', 'id': ground, 'noPass': false}; + block.event = {'cls': 'terrains', 'id': 'none', 'noPass': false}; } // event是字符串或数组? if (typeof event == "string") { @@ -253,6 +273,10 @@ maps.prototype.addEvent = function (block, x, y, event, ground) { if (!core.isset(event.data)) event.data = []; + // 覆盖noPass + if (core.isset(event.noPass)) + block.event.noPass = event.noPass; + // 覆盖enable if (!core.isset(block.enable) && core.isset(event.enable)) { block.enable=event.enable; @@ -273,11 +297,13 @@ maps.prototype.addEvent = function (block, x, y, event, ground) { } } +////// 向该楼层添加剧本的楼层转换事件 ////// maps.prototype.addChangeFloor = function (block, x, y, event, ground) { if (!core.isset(event)) return; this.addEvent(block, x, y, {"trigger": "changeFloor", "data": event}, ground); } +////// 初始化所有地图 ////// maps.prototype.initMaps = function (floorIds) { var maps = {}; for (var i=0;i> 4; + event.channel = eventTypeByte & 0x0f; + event.type = 'channel'; + switch (eventType) { + case 0x08: + event.subtype = 'noteOff'; + event.noteNumber = param1; + event.velocity = stream.readInt8(); + return event; + case 0x09: + event.noteNumber = param1; + event.velocity = stream.readInt8(); + if (event.velocity == 0) { + event.subtype = 'noteOff'; + } else { + event.subtype = 'noteOn'; + } + return event; + case 0x0a: + event.subtype = 'noteAftertouch'; + event.noteNumber = param1; + event.amount = stream.readInt8(); + return event; + case 0x0b: + event.subtype = 'controller'; + event.controllerType = param1; + event.value = stream.readInt8(); + return event; + case 0x0c: + event.subtype = 'programChange'; + event.programNumber = param1; + return event; + case 0x0d: + event.subtype = 'channelAftertouch'; + event.amount = param1; + return event; + case 0x0e: + event.subtype = 'pitchBend'; + event.value = param1 + (stream.readInt8() << 7); + return event; + default: + throw "Unrecognised MIDI event type: " + eventType + } + } + } + + stream = Stream(data); + var headerChunk = readChunk(stream); + if (headerChunk.id != 'MThd' || headerChunk.length != 6) { + throw "Bad .mid file - header not found"; + } + var headerStream = Stream(headerChunk.data); + var formatType = headerStream.readInt16(); + var trackCount = headerStream.readInt16(); + var timeDivision = headerStream.readInt16(); + + if (timeDivision & 0x8000) { + throw "Expressing time division in SMTPE frames is not supported yet" + } else { + ticksPerBeat = timeDivision; + } + + var header = { + 'formatType': formatType, + 'trackCount': trackCount, + 'ticksPerBeat': ticksPerBeat + } + var tracks = []; + for (var i = 0; i < header.trackCount; i++) { + tracks[i] = []; + var trackChunk = readChunk(stream); + if (trackChunk.id != 'MTrk') { + throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id; + } + var trackStream = Stream(trackChunk.data); + while (!trackStream.eof()) { + var event = readEvent(trackStream); + tracks[i].push(event); + //console.log(event); + } + } + + return { + 'header': header, + 'tracks': tracks + } +} +function Replayer(midiFile, synth) { + var trackStates = []; + var beatsPerMinute = 120; + var ticksPerBeat = midiFile.header.ticksPerBeat; + var channelCount = 16; + var channels = []; + var nextEventInfo; + var samplesToNextEvent; + + function Channel() { + + var generatorsByNote = {}; + var currentProgram = PianoProgram; + + function noteOn(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + /* playing same note before releasing the last one. BOO */ + generatorsByNote[note].noteOff(); /* TODO: check whether we ought to be passing a velocity in */ + } + generator = currentProgram.createNote(note, velocity); + synth.addGenerator(generator); + generatorsByNote[note] = generator; + } + function noteOff(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + generatorsByNote[note].noteOff(velocity); + } + } + function setProgram(programNumber) { + currentProgram = PROGRAMS[programNumber] || PianoProgram; + } + + return { + 'noteOn': noteOn, + 'noteOff': noteOff, + 'setProgram': setProgram + } + } + + function getNextEvent() { + var ticksToNextEvent = null; + var nextEventTrack = null; + var nextEventIndex = null; + + for (var i = 0; i < trackStates.length; i++) { + if ( + trackStates[i].ticksToNextEvent != null + && (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent) + ) { + ticksToNextEvent = trackStates[i].ticksToNextEvent; + nextEventTrack = i; + nextEventIndex = trackStates[i].nextEventIndex; + } + } + if (nextEventTrack != null) { + /* consume event from that track */ + var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex]; + if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) { + trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime; + } else { + trackStates[nextEventTrack].ticksToNextEvent = null; + } + trackStates[nextEventTrack].nextEventIndex += 1; + /* advance timings on all tracks by ticksToNextEvent */ + for (var i = 0; i < trackStates.length; i++) { + if (trackStates[i].ticksToNextEvent != null) { + trackStates[i].ticksToNextEvent -= ticksToNextEvent + } + } + nextEventInfo = { + 'ticksToEvent': ticksToNextEvent, + 'event': nextEvent, + 'track': nextEventTrack + } + var beatsToNextEvent = ticksToNextEvent / ticksPerBeat; + var secondsToNextEvent = beatsToNextEvent / (beatsPerMinute / 60); + samplesToNextEvent += secondsToNextEvent * synth.sampleRate; + } else { + nextEventInfo = null; + samplesToNextEvent = null; + self.finished = true; + } + } + + function generate(samples) { + var data = new Array(samples*2); + var samplesRemaining = samples; + var dataOffset = 0; + + while (true) { + if (samplesToNextEvent != null && samplesToNextEvent <= samplesRemaining) { + /* generate samplesToNextEvent samples, process event and repeat */ + var samplesToGenerate = Math.ceil(samplesToNextEvent); + if (samplesToGenerate > 0) { + synth.generateIntoBuffer(samplesToGenerate, data, dataOffset); + dataOffset += samplesToGenerate * 2; + samplesRemaining -= samplesToGenerate; + samplesToNextEvent -= samplesToGenerate; + } + + handleEvent(); + getNextEvent(); + } else { + /* generate samples to end of buffer */ + if (samplesRemaining > 0) { + synth.generateIntoBuffer(samplesRemaining, data, dataOffset); + samplesToNextEvent -= samplesRemaining; + } + break; + } + } + return data; + } + + function handleEvent() { + var event = nextEventInfo.event; + switch (event.type) { + case 'meta': + switch (event.subtype) { + case 'setTempo': + beatsPerMinute = 60000000 / event.microsecondsPerBeat + } + break; + case 'channel': + switch (event.subtype) { + case 'noteOn': + channels[event.channel].noteOn(event.noteNumber, event.velocity); + break; + case 'noteOff': + channels[event.channel].noteOff(event.noteNumber, event.velocity); + break; + case 'programChange': + //console.log('program change to ' + event.programNumber); + channels[event.channel].setProgram(event.programNumber); + break; + } + break; + } + } + + function reset() { + for (var i = 0; i < midiFile.tracks.length; i++) { + trackStates[i] = { + 'nextEventIndex': 0, + 'ticksToNextEvent': ( + midiFile.tracks[i].length ? + midiFile.tracks[i][0].deltaTime : + null + ) + }; + } + for (var i = 0; i < channelCount; i++) { + channels[i] = Channel(); + } + samplesToNextEvent = 0; + getNextEvent(); + } + + reset(); + + var self = { + 'reset': reset, + 'generate': generate, + 'finished': false + } + return self; +} +/* Wrapper for accessing strings through sequential reads */ +function Stream(str) { + var position = 0; + + function read(length) { + var result = str.substr(position, length); + position += length; + return result; + } + + /* read a big-endian 32-bit integer */ + function readInt32() { + var result = ( + (str.charCodeAt(position) << 24) + + (str.charCodeAt(position + 1) << 16) + + (str.charCodeAt(position + 2) << 8) + + str.charCodeAt(position + 3)); + position += 4; + return result; + } + + /* read a big-endian 16-bit integer */ + function readInt16() { + var result = ( + (str.charCodeAt(position) << 8) + + str.charCodeAt(position + 1)); + position += 2; + return result; + } + + /* read an 8-bit integer */ + function readInt8(signed) { + var result = str.charCodeAt(position); + if (signed && result > 127) result -= 256; + position += 1; + return result; + } + + function eof() { + return position >= str.length; + } + + /* read a MIDI-style variable-length integer + (big-endian value in groups of 7 bits, + with top bit set to signify that another byte follows) + */ + function readVarInt() { + var result = 0; + while (true) { + var b = readInt8(); + if (b & 0x80) { + result += (b & 0x7f); + result <<= 7; + } else { + /* b is the last byte */ + return result + b; + } + } + } + + return { + 'eof': eof, + 'read': read, + 'readInt32': readInt32, + 'readInt16': readInt16, + 'readInt8': readInt8, + 'readVarInt': readVarInt + } +} +function SineGenerator(freq) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var phase = t / period; + var result = Math.sin(phase * 2 * Math.PI); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function SquareGenerator(freq, phase) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var result = ( (t / period) % 1 > phase ? 1 : -1); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) { + var self = {'alive': true} + var attackTime = sampleRate * attackTimeS; + var decayTime = sampleRate * (attackTimeS + decayTimeS); + var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime); + var releaseTime = null; /* not known yet */ + var endTime = null; /* not known yet */ + var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS); + var t = 0; + + self.noteOff = function() { + if (self.released) return; + releaseTime = t; + self.released = true; + endTime = releaseTime + sampleRate * releaseTimeS; + } + + self.generate = function(buf, offset, count) { + if (!self.alive) return; + var input = new Array(count * 2); + for (var i = 0; i < count*2; i++) { + input[i] = 0; + } + child.generate(input, 0, count); + + childOffset = 0; + while(count) { + if (releaseTime != null) { + if (t < endTime) { + /* release */ + while(count && t < endTime) { + var ampl = sustainAmplitude - releaseRate * (t - releaseTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* dead */ + self.alive = false; + return; + } + } else if (t < attackTime) { + /* attack */ + while(count && t < attackTime) { + var ampl = attackAmplitude * t / attackTime; + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else if (t < decayTime) { + /* decay */ + while(count && t < decayTime) { + var ampl = attackAmplitude - decayRate * (t - attackTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* sustain */ + while(count) { + buf[offset++] += input[childOffset++] * sustainAmplitude; + buf[offset++] += input[childOffset++] * sustainAmplitude; + t++; + count--; + } + } + } + } + + return self; +} + +function midiToFrequency(note) { + return 440 * Math.pow(2, (note-69)/12); +} + +PianoProgram = { + 'attackAmplitude': 0.2, + 'sustainAmplitude': 0.1, + 'attackTime': 0.02, + 'decayTime': 0.3, + 'releaseTime': 0.02, + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128), + this.attackTime, this.decayTime, this.releaseTime + ); + } +} + +StringProgram = { + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + 0.5 * (velocity / 128), 0.2 * (velocity / 128), + 0.4, 0.8, 0.4 + ); + } +} + +PROGRAMS = { + 41: StringProgram, + 42: StringProgram, + 43: StringProgram, + 44: StringProgram, + 45: StringProgram, + 46: StringProgram, + 47: StringProgram, + 49: StringProgram, + 50: StringProgram +}; + +function Synth(sampleRate) { + + var generators = []; + + function addGenerator(generator) { + generators.push(generator); + } + + function generate(samples) { + var data = new Array(samples*2); + generateIntoBuffer(samples, data, 0); + return data; + } + + function generateIntoBuffer(samplesToGenerate, buffer, offset) { + for (var i = offset; i < offset + samplesToGenerate * 2; i++) { + buffer[i] = 0; + } + for (var i = generators.length - 1; i >= 0; i--) { + generators[i].generate(buffer, offset, samplesToGenerate); + if (!generators[i].alive) generators.splice(i, 1); + } + } + + return { + 'sampleRate': sampleRate, + 'addGenerator': addGenerator, + 'generate': generate, + 'generateIntoBuffer': generateIntoBuffer + } +} diff --git a/libs/ui.js b/libs/ui.js index 6ac8143a..f3635f61 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -12,17 +12,12 @@ ui.prototype.init = function () { main.instance.ui = new ui(); -/** - * 关闭一切UI窗口 - * @param clearData 是否同时清掉data层 - */ -ui.prototype.closePanel = function (clearData) { +////// 结束一切事件和绘制,关闭UI窗口,返回游戏进程 ////// +ui.prototype.closePanel = function () { core.status.boxAnimateObjs = []; core.setBoxAnimate(); core.clearMap('ui', 0, 0, 416, 416); core.setAlpha('ui', 1.0); - if (core.isset(clearData) && clearData) - core.clearMap('data', 0, 0, 416, 416); core.unLockControl(); core.status.event.data = null; core.status.event.id = null; @@ -30,12 +25,7 @@ ui.prototype.closePanel = function (clearData) { core.status.event.ui = null; } - -/** - * 绘制对话框 - * @param content - * @param id - */ +////// 绘制一个对话框 ////// ui.prototype.drawTextBox = function(content) { // 获得name, image, icon @@ -137,7 +127,7 @@ ui.prototype.drawTextBox = function(content) { core.fillText('ui', '<点击任意位置继续>', 270, top+height-13, '#CCCCCC', '13px Verdana'); } -// 绘制选项事件 +////// 绘制一个选项界面 ////// ui.prototype.drawChoices = function(content, choices) { var background = core.canvas.ui.createPattern(core.material.ground, "repeat"); @@ -273,12 +263,7 @@ ui.prototype.drawChoices = function(content, choices) { return; } -/** - * 绘制确认/取消警告 - * @param text - * @param yesCallback - * @param noCallback - */ +////// 绘制一个确认/取消的警告页面 ////// ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) { core.lockControl(); @@ -327,14 +312,13 @@ ui.prototype.drawConfirmBox = function (text, yesCallback, noCallback) { } -////// 绘制开关界面 ////// +////// 绘制系统设置界面 ////// ui.prototype.drawSwitchs = function() { - // 背景音乐、背景音效、战斗动画、怪物显伤、领域显伤、返回 - core.status.event.id = 'switchs'; var choices = [ - "背景音乐:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), + "背景音乐:"+(core.musicStatus.bgmStatus ? "[ON]" : "[OFF]"), + "背景音效:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), "战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"), "怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), "领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), @@ -344,10 +328,7 @@ ui.prototype.drawSwitchs = function() { } -/** - * 绘制菜单栏 - * @param need - */ +////// 绘制系统菜单栏 ////// ui.prototype.drawSettings = function (need) { if (!core.checkStatus('settings', need)) return; @@ -357,6 +338,7 @@ ui.prototype.drawSettings = function (need) { ]); } +////// 绘制快捷商店选择栏 ////// ui.prototype.drawQuickShop = function (need) { if (core.isset(need) && !core.checkStatus('selectShop', need)) return; @@ -373,7 +355,7 @@ ui.prototype.drawQuickShop = function (need) { this.drawChoices(null, choices); } - +////// 绘制战斗动画 ////// ui.prototype.drawBattleAnimate = function(monsterId, callback) { // UI层 @@ -389,12 +371,12 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) { hero_hp -= core.enemys.getExtraDamage(monster); - if (core.enemys.hasSpecial(mon_special, 2)) hero_def=0; // 魔攻 - if (core.enemys.hasSpecial(mon_special, 3) && mon_def=enemys.length) index=enemys.length-1; var perpage = 6; + var page=parseInt(index/perpage)+1; var totalPage = parseInt((enemys.length - 1) / perpage) + 1; - if (page < 1) page = 1; - if (page > totalPage) page = totalPage; - core.status.event.data = page; + core.status.event.data = index; var start = (page - 1) * perpage, end = Math.min(page * perpage, enemys.length); enemys = enemys.slice(start, end); @@ -796,25 +766,34 @@ ui.prototype.drawEnemyBook = function (page) { core.fillText('ui', enemy.atk, 285, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana'); core.fillText('ui', '防御', 335, 62 * i + 32, '#DDDDDD', '13px Verdana'); core.fillText('ui', enemy.def, 365, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana'); - core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana'); - core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); - var damage_offset = 326; + var expOffset = 165; + if (core.flags.enableMoney) { + core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana'); + core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); + expOffset = 255; + } + if (core.flags.enableExperience) { core.canvas.ui.textAlign = "left"; - core.fillText('ui', '经验', 255, 62 * i + 50, '#DDDDDD', '13px Verdana'); - core.fillText('ui', enemy.experience, 285, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); - damage_offset = 361; + core.fillText('ui', '经验', expOffset, 62 * i + 50, '#DDDDDD', '13px Verdana'); + core.fillText('ui', enemy.experience, expOffset + 30, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); } + var damageOffet = 281; + if (core.flags.enableMoney && core.flags.enableExperience) + damageOffet = 361; + else if (core.flags.enableMoney || core.flags.enableExperience) + damageOffet = 326; + + core.canvas.ui.textAlign = "center"; var damage = enemy.damage; var color = '#FFFF00'; if (damage >= core.status.hero.hp) color = '#FF0000'; - if (damage == 0) color = '#00FF00'; + if (damage <= 0) color = '#00FF00'; if (damage >= 999999999) damage = '无法战斗'; - var length = core.canvas.ui.measureText(damage).width; - core.fillText('ui', damage, damage_offset, 62 * i + 50, color, 'bold 13px Verdana'); + core.fillText('ui', damage, damageOffet, 62 * i + 50, color, 'bold 13px Verdana'); core.canvas.ui.textAlign = "left"; @@ -825,15 +804,79 @@ ui.prototype.drawEnemyBook = function (page) { core.fillText('ui', '1防', 335, 62 * i + 68, '#DDDDDD', '13px Verdana'); core.fillText('ui', enemy.defDamage, 365, 62 * i + 68, '#DDDDDD', 'bold 13px Verdana'); + if (index == start+i) { + core.strokeRect('ui', 10, 62 * i + 13, 416-10*2, 62, '#FFD700'); + } + } core.setBoxAnimate(); this.drawPagination(page, totalPage); } -/** - * 绘制楼传器 - * @param page - */ +////// 绘制怪物属性的详细信息 ////// +ui.prototype.drawBookDetail = function (index) { + var enemys = core.enemys.getCurrentEnemys(); + if (enemys.length==0) return; + if (index<0) index=0; + if (index>=enemys.length) index=enemys.length-1; + + var enemy = enemys[index]; + var enemyId=enemy.id; + var hints=core.enemys.getSpecialHint(core.enemys.getEnemys(enemyId)); + + if (hints.length==0) { + core.drawTip("该怪物无特殊属性!"); + return; + } + var content=hints.join("\n"); + + core.status.event.id = 'book-detail'; + clearInterval(core.interval.tipAnimate); + + core.clearMap('data', 0, 0, 416, 416); + core.setOpacity('data', 1); + + var left=10, right=416-2*left; + var content_left = left + 25; + + var validWidth = right-(content_left-left)-13; + var contents = core.splitLines("data", content, validWidth, '16px Verdana'); + + var height = 416 - 10 - Math.min(416-24*(contents.length+1)-65, 250); + var top = (416-height)/2, bottom = height; + + // var left = 97, top = 64, right = 416 - 2 * left, bottom = 416 - 2 * top; + core.setAlpha('data', 0.9); + core.fillRect('data', left, top, right, bottom, '#000000'); + core.setAlpha('data', 1); + core.strokeRect('data', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); + + // 名称 + core.canvas.data.textAlign = "left"; + + core.fillText('data', enemy.name, content_left, top + 30, '#FFD700', 'bold 22px Verdana'); + var content_top = top + 57; + + for (var i=0;i=0) { + var x1 = text.substring(0, index+1); + core.fillText('data', x1, content_left, content_top, '#FF6A6A', 'bold 16px Verdana'); + var len=core.canvas.data.measureText(x1).width; + core.fillText('data', text.substring(index+1), content_left+len, content_top, '#FFFFFF', '16px Verdana'); + } + else { + core.fillText('data', contents[i], content_left, content_top, '#FFFFFF', '16px Verdana'); + } + content_top+=24; + } + + core.fillText('data', '<点击任意位置继续>', 270, top+height-13, '#CCCCCC', '13px Verdana'); +} + +////// 绘制楼层传送器 ////// ui.prototype.drawFly = function(page) { if (page<0) page=0; @@ -859,6 +902,7 @@ ui.prototype.drawFly = function(page) { this.drawThumbnail(floorId, 'ui', core.status.maps[floorId].blocks, 20, 100, 273); } +////// 绘制道具栏 ////// ui.prototype.drawToolbox = function(index) { var tools = Object.keys(core.status.hero.items.tools).sort(); @@ -973,7 +1017,7 @@ ui.prototype.drawToolbox = function(index) { core.fillText('ui', '返回游戏', 370, 403,'#DDDDDD', 'bold 15px Verdana'); } - +////// 绘制存档/读档界面 ////// ui.prototype.drawSLPanel = function(index) { if (!core.isset(index)) index=1; if (index<=0) index=1; @@ -1027,6 +1071,7 @@ ui.prototype.drawSLPanel = function(index) { this.drawPagination(page+1, 30); } +////// 绘制一个缩略图 ////// ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroLoc) { core.clearMap(canvas, x, y, size, size); var groundId = core.floors[floorId].defaultGround || "ground"; @@ -1038,23 +1083,30 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + i * persize, y + j * persize, persize, persize); } } - var autotileMaps = []; + + if (core.isset(core.floors[floorId].png)) { + var png = core.floors[floorId].png; + if (core.isset(core.material.images.pngs[png])) { + core.canvas.ui.drawImage(core.material.images.pngs[png], x, y, size, size); + } + } + + var mapArray = core.maps.getMapArray(core.status.maps, floorId); for (var b in blocks) { var block = blocks[b]; if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) { if (block.event.cls == 'autotile') { - // core.drawAutotile(); - autotileMaps[13*block.x + block.y] = true; - continue; + core.drawAutotile(core.canvas.ui, mapArray, block, persize, x, y); } else { - var blockIcon = core.material.icons[block.event.cls][block.event.id]; - var blockImage = core.material.images[block.event.cls]; - core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + block.x * persize, y + block.y * persize, persize, persize); + if (block.event.id!='none') { + var blockIcon = core.material.icons[block.event.cls][block.event.id]; + var blockImage = core.material.images[block.event.cls]; + core.canvas[canvas].drawImage(blockImage, 0, blockIcon * 32, 32, 32, x + block.x * persize, y + block.y * persize, persize, persize); + } } } } - core.drawAutotile(floorId, 'ui', autotileMaps, x, y, persize); if (core.isset(heroLoc)) { var heroIcon = core.material.icons.hero[heroLoc.direction]; @@ -1064,9 +1116,7 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL } } -/** - * 绘制"关于" - */ +////// 绘制“关于”界面 ////// ui.prototype.drawAbout = function() { if (!core.isPlaying()) { @@ -1092,18 +1142,9 @@ ui.prototype.drawAbout = function() { core.fillText('ui', "作者: 艾之葵", text_start, top + 80, "#FFFFFF", "bold 17px Verdana"); core.fillText('ui', 'HTML5魔塔交流群:539113091', text_start, top+112); // TODO: 写自己的“关于”页面 - /* - core.fillText('ui', "原作: ss433_2", text_start, top + 112, "#FFFFFF", "bold 17px Verdana"); - core.fillText('ui', "制作工具: WebStorm", text_start, top + 144, "#FFFFFF", "bold 17px Verdana"); - core.fillText('ui', "测试平台: Chrome/微信/iOS", text_start, top + 176, "#FFFFFF", "bold 17px Verdana"); - core.fillText('ui', '特别鸣谢: ss433_2', text_start, top+208); - var len = core.canvas.ui.measureText('特别鸣谢: ').width; - core.fillText('ui', 'iEcho', text_start+len, top+240); - core.fillText('ui', '打Dota的喵', text_start+len, top+272); - core.fillText('ui', 'HTML5魔塔交流群:539113091', text_start, top+304); - */ } +////// 绘制帮助页面 ////// ui.prototype.drawHelp = function () { core.drawText([ "\t[键盘快捷键列表]"+ diff --git a/main.js b/main.js index f173fa80..fd19a8a0 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,33 @@ function main() { + + //------------------------ 用户修改内容 ------------------------// + + this.version = "0.1"; // 游戏版本号;如果更改了游戏内容建议修改此version以免造成缓存问题。 + + this.useCompress = false; // 是否使用压缩文件 + // 当你即将发布你的塔时,请使用“JS代码压缩工具”将所有js代码进行压缩,然后将这里的useCompress改为true。 + // 请注意,只有useCompress是false时才会读取floors目录下的文件,为true时会直接读取libs目录下的floors.min.js文件。 + // 如果要进行剧本的修改请务必将其改成false。 + + this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序 + "sample0", "sample1", "sample2" + ]; + this.pngs = [ // 在此存放所有可能的背景图片;背景图片最好是416*416像素,其他分辨率会被强制缩放成416*416 + // 建议对于较大的图片,在网上使用在线的“图片压缩工具”来进行压缩,以节省流量 + // 有关使用自定义背景图,请参见文档的“自定义素材”说明 + "bg.png", "yewai.png", // 依次向后添加 + ]; + this.bgms = [ // 在此存放所有的bgm,和文件名一致。第一项为默认播放项 + // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 + '058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid', + ]; + this.sounds = [ // 在此存放所有的SE,和文件名一致 + // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 + 'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg', + ]; + + //------------------------ 用户修改内容 END ------------------------// + this.dom = { 'body': document.body, 'gameGroup': document.getElementById('gameGroup'), @@ -20,6 +49,7 @@ function main() { 'toolBar': document.getElementById('toolBar'), 'tools': document.getElementsByClassName('tools'), 'gameCanvas': document.getElementsByClassName('gameCanvas'), + 'curtain': document.getElementById('curtain'), 'startButtons': document.getElementById('startButtons'), 'playGame': document.getElementById('playGame'), 'loadGame': document.getElementById('loadGame'), @@ -30,31 +60,33 @@ function main() { 'hardLevel': document.getElementById('hardLevel'), 'data': document.getElementById('data'), 'statusLabels': document.getElementsByClassName('statusLabel'), + 'floorCol': document.getElementById('floorCol'), + 'lvCol': document.getElementById('lvCol'), 'mdefCol': document.getElementById('mdefCol'), + 'moneyCol': document.getElementById('moneyCol'), 'expCol': document.getElementById('expCol'), + 'upCol': document.getElementById('upCol'), + 'debuffCol': document.getElementById('debuffCol'), + 'hard': document.getElementById('hard'), }; - // console.log('加载游戏容器和开始界面dom对象完成 如下'); - // console.log(this.dom); this.loadList = [ 'items', 'icons', 'maps', 'enemys', 'events', 'data', 'ui', 'core' ]; - // console.log('加载js文件列表加载完成' + this.loadList); this.images = [ - 'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains', "autotile" + 'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains' ]; - this.sounds = { - 'mp3': ['bgm-loop', 'floor'], - 'ogg': ['attack', 'door', 'item'] - } + this.statusBar = { 'image': { 'floor': document.getElementById('img-floor'), + 'lv': document.getElementById('img-lv'), 'hp': document.getElementById("img-hp"), 'atk': document.getElementById("img-atk"), 'def': document.getElementById("img-def"), 'mdef': document.getElementById("img-mdef"), 'money': document.getElementById("img-money"), 'experience': document.getElementById("img-experience"), + 'up': document.getElementById("img-up"), 'book': document.getElementById("img-book"), 'fly': document.getElementById("img-fly"), 'toolbox': document.getElementById("img-toolbox"), @@ -64,12 +96,14 @@ function main() { 'settings': document.getElementById("img-settings") }, 'floor': document.getElementById('floor'), + 'lv': document.getElementById('lv'), 'hp': document.getElementById('hp'), 'atk': document.getElementById('atk'), 'def': document.getElementById("def"), 'mdef': document.getElementById('mdef'), 'money': document.getElementById("money"), 'experience': document.getElementById("experience"), + 'up': document.getElementById('up'), 'yellowKey': document.getElementById("yellowKey"), 'blueKey': document.getElementById("blueKey"), 'redKey': document.getElementById("redKey"), @@ -78,21 +112,12 @@ function main() { 'curse': document.getElementById('curse'), 'hard': document.getElementById("hard") } - this.version = "0.1"; // 游戏版本号;如果更改了游戏内容建议修改此version以免造成缓存问题。 - - this.useCompress = false; // 是否使用压缩文件 - // 当你即将发布你的塔时,请使用“JS代码压缩工具”将所有js代码进行压缩,然后将这里的useCompress改为true。 - // 请注意,只有useCompress是false时才会读取floors目录下的文件,为true时会直接读取libs目录下的floors.min.js文件。 - // 如果要进行剧本的修改请务必将其改成false。 - - this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序 - "sample0", "sample1", "sample2" - ] this.floors = {} this.instance = {}; this.canvas = {}; } +////// 初始化 ////// main.prototype.init = function () { for (var i = 0; i < main.dom.gameCanvas.length; i++) { main.canvas[main.dom.gameCanvas[i].id] = main.dom.gameCanvas[i].getContext('2d'); @@ -106,12 +131,13 @@ main.prototype.init = function () { coreData[name] = main[name]; } main.loaderFloors(function() { - main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.sounds, main.floorIds, main.floors, coreData); + main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.pngs, main.bgms, main.sounds, main.floorIds, main.floors, coreData); main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); }) }); } +////// 动态加载所有核心JS文件 ////// main.prototype.loaderJs = function (callback) { var instanceNum = 0; // 加载js @@ -132,13 +158,14 @@ main.prototype.loaderJs = function (callback) { } } +////// 动态加载所有楼层(剧本) ////// main.prototype.loaderFloors = function (callback) { // 加载js main.setMainTipsText('正在加载楼层文件...') if (this.useCompress) { // 读取压缩文件 var script = document.createElement('script'); - script.src = 'libs/floors.min.js?' + this.version; + script.src = 'libs/floors.min.js?v=' + this.version; main.dom.body.appendChild(script); script.onload = function () { main.dom.mainTips.style.display = 'none'; @@ -158,10 +185,11 @@ main.prototype.loaderFloors = function (callback) { } } +////// 加载某一个JS文件 ////// main.prototype.loadMod = function (modName, callback) { var script = document.createElement('script'); var name = modName; - script.src = 'libs/' + modName + (this.useCompress?".min":"") + '.js?' + this.version; + script.src = 'libs/' + modName + (this.useCompress?".min":"") + '.js?v=' + this.version; main.dom.body.appendChild(script); script.onload = function () { main[name] = main.instance[name]; @@ -169,15 +197,17 @@ main.prototype.loadMod = function (modName, callback) { } } +////// 加载某一个楼层 ////// main.prototype.loadFloor = function(floorId, callback) { var script = document.createElement('script'); - script.src = 'libs/floors/' + floorId +'.js?' + this.version; + script.src = 'libs/floors/' + floorId +'.js?v=' + this.version; main.dom.body.appendChild(script); script.onload = function () { callback(floorId); } } +////// 加载过程提示 ////// main.prototype.setMainTipsText = function (text) { main.dom.mainTips.innerHTML = text; } @@ -185,12 +215,14 @@ main.prototype.setMainTipsText = function (text) { var main = new main(); main.init(); +////// 窗口大小变化时 ////// window.onresize = function () { try { main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); }catch (e) {} } +////// 在界面上按下某按键时 ////// main.dom.body.onkeydown = function(e) { try { if (main.core.isPlaying() || main.core.status.lockControl) @@ -198,6 +230,7 @@ main.dom.body.onkeydown = function(e) { } catch (ee) {} } +////// 在界面上放开某按键时 ////// main.dom.body.onkeyup = function(e) { try { if (main.core.isPlaying() || main.core.status.lockControl) @@ -205,22 +238,12 @@ main.dom.body.onkeyup = function(e) { } catch (ee) {} } +////// 开始选择时 ////// main.dom.body.onselectstart = function () { return false; } -document.onmousemove = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - -document.ontouchstart = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - +////// 鼠标按下时 ////// main.dom.data.onmousedown = function (e) { try { e.stopPropagation(); @@ -235,6 +258,7 @@ main.dom.data.onmousedown = function (e) { } catch (ee) {} } +////// 鼠标移动时 ////// main.dom.data.onmousemove = function (e) { try { e.stopPropagation(); @@ -245,12 +269,14 @@ main.dom.data.onmousemove = function (e) { }catch (ee) {} } +////// 鼠标放开时 ////// main.dom.data.onmouseup = function () { try { main.core.onup(); }catch (e) {} } +////// 鼠标滑轮滚动时 ////// main.dom.data.onmousewheel = function(e) { try { if (e.wheelDelta) @@ -260,6 +286,7 @@ main.dom.data.onmousewheel = function(e) { } catch (ee) {} } +////// 手指在触摸屏开始触摸时 ////// main.dom.data.ontouchstart = function (e) { try { e.preventDefault(); @@ -271,6 +298,7 @@ main.dom.data.ontouchstart = function (e) { }catch (ee) {} } +////// 手指在触摸屏上移动时 ////// main.dom.data.ontouchmove = function (e) { try { e.preventDefault(); @@ -281,6 +309,7 @@ main.dom.data.ontouchmove = function (e) { }catch (ee) {} } +////// 手指离开触摸屏时 ////// main.dom.data.ontouchend = function () { try { main.core.onup(); @@ -288,41 +317,49 @@ main.dom.data.ontouchend = function () { } } +////// 点击状态栏中的怪物手册时 ////// main.statusBar.image.book.onclick = function () { if (main.core.isPlaying()) main.core.openBook(true); } +////// 点击状态栏中的楼层传送器时 ////// main.statusBar.image.fly.onclick = function () { if (main.core.isPlaying()) main.core.useFly(true); } +////// 点击状态栏中的工具箱时 ////// main.statusBar.image.toolbox.onclick = function () { if (main.core.isPlaying()) main.core.openToolbox(true); } +////// 点击状态栏中的快捷商店时 ////// main.statusBar.image.shop.onclick = function () { if (main.core.isPlaying()) main.core.ui.drawQuickShop(true); } +////// 点击状态栏中的存档按钮时 ////// main.statusBar.image.save.onclick = function () { if (main.core.isPlaying()) main.core.save(true); } +////// 点击状态栏中的读档按钮时 ////// main.statusBar.image.load.onclick = function () { if (main.core.isPlaying()) main.core.load(true); } +////// 点击状态栏中的系统菜单时 ////// main.statusBar.image.settings.onclick = function () { if (main.core.isPlaying()) main.core.ui.drawSettings(true); } +////// 点击“开始游戏”时 ////// main.dom.playGame.onclick = function () { main.dom.startButtons.style.display='none'; @@ -334,22 +371,27 @@ main.dom.playGame.onclick = function () { } } +////// 点击“载入游戏”时 ////// main.dom.loadGame.onclick = function() { main.core.load(); } +////// 点击“关于本塔”时 ////// main.dom.aboutGame.onclick = function () { main.core.ui.drawAbout(); } +////// 点击“简单难度”时 ////// main.dom.easyLevel.onclick = function() { core.events.startGame('Easy'); } +////// 点击“普通难度”时 ////// main.dom.normalLevel.onclick = function () { core.events.startGame('Normal'); } +////// 点击“困难难度”时 ////// main.dom.hardLevel.onclick = function () { core.events.startGame('Hard'); } diff --git a/sounds/058-Slow01.mid b/sounds/058-Slow01.mid new file mode 100644 index 00000000..05ce9228 Binary files /dev/null and b/sounds/058-Slow01.mid differ diff --git a/sounds/qianjin.mid b/sounds/qianjin.mid new file mode 100644 index 00000000..b4236a96 Binary files /dev/null and b/sounds/qianjin.mid differ diff --git a/sounds/star.mid b/sounds/star.mid new file mode 100644 index 00000000..cc78e590 Binary files /dev/null and b/sounds/star.mid differ diff --git a/styles.css b/styles.css index c63e5e92..6523b3b8 100644 --- a/styles.css +++ b/styles.css @@ -21,7 +21,7 @@ position: fixed; top: 10px; left: 10px; - z-index: 12; + z-index: 13; } #startPanel { @@ -32,7 +32,7 @@ left: 0; background-color: #fff; overflow: hidden; - z-index: 8; + z-index: 9; } #startTop { @@ -42,7 +42,7 @@ top: 0; left: 0; background-color: #000; - z-index: 11; + z-index: 12; } #startTopProgressBar { @@ -52,7 +52,7 @@ position: absolute; top: 5%; background-color: #fff; - z-index: 12; + z-index: 13; } #startTopProgress { @@ -67,7 +67,7 @@ position: absolute; top: 8%; left: 5%; - z-index: 12; + z-index: 13; } #startBackground { @@ -77,12 +77,12 @@ height: 100%; width: auto; transform:translate(-50%,-50%); - z-index: 9; + z-index: 10; } #startLogo { position: absolute; - z-index: 9; + z-index: 10; left: 0; right: 0; margin-left: auto; @@ -95,7 +95,7 @@ #startTitle { position: absolute; - z-index: 10; + z-index: 11; } #startButtonGroup { @@ -106,7 +106,7 @@ background-color: #000; opacity: 0.85; display: none; - z-index: 9; + z-index: 10; bottom: 0; margin-bottom: 7%; } @@ -142,7 +142,7 @@ display: none; color: #fff; background-color: #000; - z-index: 7; + z-index: 8; } #logoLabel { @@ -170,7 +170,7 @@ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; background: url(images/ground.png) round; - z-index: 6; + z-index: 7; display: none; } #statusBar .status{ @@ -180,8 +180,9 @@ } .status img{ vertical-align: middle; - width: 1.6em; - height: 1.6em; + width: auto; + height: 100%; + max-height: 1.6em; } #statusBar span{ color: white; @@ -198,7 +199,7 @@ #toolBar { position: absolute; background: url(images/ground.png) round; - z-index: 5; + z-index: 6; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -232,6 +233,13 @@ span#poison, span#weak, span#curse { -webkit-box-sizing: border-box; } +#curtain { + z-index: 5; + position: absolute; + opacity: 0; + background: #000000; +} + #bg { z-index: 1; } @@ -240,20 +248,20 @@ span#poison, span#weak, span#curse { z-index: 2; } -#hero { +#fg { z-index: 3; } -#fg { +#hero { z-index: 4; } #ui { - z-index: 5; + z-index: 6; } #data { - z-index: 6; + z-index: 7; } .clearfix:before, diff --git a/常用工具/便捷PS工具.exe b/常用工具/便捷PS工具.exe index 7ec2fc98..a85e29fd 100644 Binary files a/常用工具/便捷PS工具.exe and b/常用工具/便捷PS工具.exe differ diff --git a/常用工具/地图生成器.exe b/常用工具/地图生成器.exe index 541b3626..286aa26b 100644 Binary files a/常用工具/地图生成器.exe and b/常用工具/地图生成器.exe differ diff --git a/更新说明.txt b/更新说明.txt index b0e69a89..6ee14efa 100644 --- a/更新说明.txt +++ b/更新说明.txt @@ -1,6 +1,40 @@ -全键盘操作 √ -Ctrl快速跳过对话 √ -支持不同层使用不同的地面素材 √ -直接内嵌了诸多默认的terrains素材 -自动定位到上次存/读档位置 √ -设置储存 √ +HTML5魔塔样板V1.3: + +支持全键盘操作。 +支持将某个图片作为某层的背景素材。 +便捷PS工具支持更改图片色相。 +支持经验升级(进阶/境界塔)。 +打败怪物可以进行加点(加点塔)。 +增加阻击、N连击等属性;在怪物手册有属性显示。 +支持九宫格领域和大范围领域。 +增加负伤。 +支持各种BGM的播放。 +支持不同层使用不同的地面素材;支持多个Autotile同时存在。 +许多细节进行了优化,一些已知的Bug进行了修复。 + +----------------------------------------------------------------------- + +HTML5魔塔样板V1.2: + +新增:本地HTTP服务器。 +新增:可视化地图编辑工具。 +新增:便捷PS工具。 +移除了meaning.txt,现在“地图生成器”将直接从js文件中读取数字和图块对应关系。 +新增:对Autotile图块的支持。 +新增:怪物支持多种属性;添加仇恨属性。 +移除了不再支持的checkBlock,现在对于领域和夹击无需再手动指定可能的点了。 +新增:单向箭头、感叹号(单次通行)的支持。 +新增:更多的默认素材,现在对于大多数地图风格无需P图,直接替换即可。 +添加部分自定义事件,部分细节优化,一些已知的Bug进行了修复。 + +----------------------------------------------------------------------- + +HTML5魔塔样板V1.1: + +新增:战斗过程显示,可以在设置中关闭 +新增:勇士支持48*32(大图)的行走图 +新增:更改画面色调 +新增:文字显示支持自动换行 +部分修改状态栏UI +增添Web的Markdown文档,移除原本的doc和pdf文档。 +修复若干Bug。