diff --git a/_docs/_api.md b/_docs/_api.md new file mode 100644 index 00000000..64362e3b --- /dev/null +++ b/_docs/_api.md @@ -0,0 +1,689 @@ +# 附录: API列表 + +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * + +**这里只列出所有可能会被造塔者用到的常用API,更多的有关内容请在代码内进行查询。** + +如有任何疑问,请联系小艾寻求帮助。 + +可以在chrome浏览器的控制台中(`ctrl+shift+I`,找到Console)中直接进行调用,以查看效果。 + +**以下所有异步API都会加上[异步]的说明,存在此说明的请勿在事件处理的自定义脚本中使用。** + +!> 最常用的新手向命令,强烈建议每个人了解 + +``` text + +core.status.floorId +获得当前层的floorId。 + + +core.status.maps +获得所有楼层的地图信息。 + + +core.status.thisMap +获得当前楼层信息,其等价于core.status.maps[core.status.floorId]。 + + +core.floors +获得所有楼层的信息。例如core.floors[core.status.floorId].events可获得本楼层的所有自定义事件。 + + +core.status.hero +获得当前勇士状态信息。例如core.status.hero.atk就是当前勇士的攻击力数值。 + + +core.material.enemys +获得所有怪物信息。例如core.material.enemys.greenSlime就是获得绿色史莱姆的属性数据。 + + +core.material.items +获得所有道具的信息。 + + +core.debug() +开启调试模式。此模式下可以按Ctrl键进行穿墙,并忽略一切事件。 +此模式下不可回放录像和上传成绩。 + + +core.updateStatusBar() +立刻刷新状态栏和地图显伤。 + + +core.setStatus('atk', 1000) +将攻击力设置为1000;这里把atk可以改成hp, def, mdef, money, experience等等。 +本句等价于 core.status.hero.atk = 1000 + + +core.getStatus('atk') +返回当前攻击力数值。本句等价于 core.status.hero.atk。 + + +core.setHeroLoc('x', 5) +设置勇士位置。这句话的意思是将勇士当前位置的横坐标设置为5。 +同理可以设置勇士纵坐标 core.setHeroLoc('y', 3)。 +值得注意的是,这句话虽然会使勇士改变位置,但并不会使界面重新绘制;如需立刻重新绘制地图还需调用: +core.clearMap('hero'); core.drawHero(); +来对界面进行更新。 + + +core.setItem('pickaxe', 10) +将破墙镐个数设置为10个。这里可以写任何道具的ID。 + + +core.addItem('pickaxe', 2) +将破墙镐的个数增加2个,无任何特效。这里可以写任何道具的ID。 + + +core.getItem('pickaxe', 4) +令勇士获得4个破墙镐。这里可以写任何道具的ID。 +和addItem相比,使用getItem会播放获得道具的音效,也会在左上角绘制获得提示。 + + +core.removeItem('pickaxe', 3) +删除3个破墙镐。第二项可忽略,默认值为1。 + + +core.itemCount('pickaxe') +返回当前破墙镐的个数。这里可以写任何道具的ID。 + + +core.hasItem('pickaxe') +返回当前是否存在某个道具。等价于 core.itemCount('pickaxe')>0 。 + + +core.getEquip(0) +获得0号装备类型(武器)的当前装备的itemId。如果不存在则返回null。 +这里可以写任意装备类型,从0开始和全塔属性中的equipName一一对应。 + + +core.hasEquip('sword1') +获得当前某个具体的装备是否处于正在被装备状态。 + + +core.setFlag('xyz', 2) +设置某个flag/变量的值为2。这里可以写任何的flag变量名。 + + +core.getFlag('xyz', 7) +获得某个flag/变量的值;如果该变量不存在,则返回第二个参数。 +比如 core.getFlag('point', 2) 则获得变量point的值;如果该变量从未定义过则返回2。 + + +core.hasFlag('xyz') +返回是否存在某个变量且不为0。等价于 core.getFlag('xyz', 0)!=0 。 + + +core.removeFlag('xyz') +删除某个flag/变量。 + + +core.insertAction(list, x, y, callback) +插入并执行一段自定义事件。在这里你可以写任意的自定义事件列表,有关详细写法请参见文档-事件。 +x和y如果设置则覆盖"当前事件点"的坐标,callback如果设置则覆盖事件执行完毕后的回调函数。 +例如: core.insertAction(["楼层切换", {"type":"changeFloor", "floorId": "MT3"}]) +将依次显示剧情文本,并执行一个楼层切换的自定义事件。 +-------- +从V2.5.4开始提出了“公共事件”的说法,这里也可以插入一个公共事件名。 +例如:core.insertAction("毒衰咒处理") 将插入公共事件“毒衰咒处理”。 + + +core.changeFloor(floorId, stair, heroLoc, time, callback) [异步] +立刻切换到指定楼层。 +floorId为目标楼层ID,stair为到达的目标楼梯,heroLoc为到达的指定点,time为动画时间,callback为切换完毕后的回调。 +例如: +core.changeFloor('MT2', 'upFloor', null, 600) 切换到MT2层的上楼点,动画事件600ms +core.changeFloor('MT5', null, {'x': 3, 'y': 6}, 0) 无动画切换到MT5层的(3,6)位置。 + + +core.resetMap() +重置当前楼层地图和楼层属性。 +此函数参数有三种形式: + - 不加任何参数,表示重置当前层:core.resetMap() + - 加上一个floorId,表示重置某一层:core.resetMap("MT1") + - 使用一个数组,表示重置若干层:core.resetMap(["MT1", "MT2", "MT3"]) +--------------------------- +** 说明:从V2.5.5开始存档方式发生了改变,在编辑器修改了地图后现在将直接生效,无需再重置地图。 + +R +录像回放的快捷键;这不是一个控制台命令,但是也把它放在这里供使用。 +录像回放在修改地图或新增数据后会很有用。 + +``` + +!> 一些相对高级的命令,针对有一定脚本经验的人 + +``` text + +========== 可直接从core中调用的,最常被使用的函数 ========== +core.js实际上是所有API的入口(路由),核心API的实现在其他几个文件中,core.js主要进行转发操作。 + + +core.nextX(n) +获得勇士面向的第n个位置的x坐标,n可以省略默认为1(即正前方) + + +core.nextY(n) +获得勇士面向的第n个位置的y坐标,n可以省略默认为1(即正前方) + + +core.nearHero(x, y) +判断某个点是否和勇士的距离不超过1。 + + +core.openDoor(id, x, y, needKey, callback) [异步] +尝试开门操作。id为目标点的ID,x和y为坐标,needKey表示是否需要使用钥匙,callback为开门完毕后的回调函数。 +id可为null代表使用地图上的值。 +例如:core.openDoor('yellowDoor', 10, 3, false, function() {console.log("1")}) +此函数返回true代表成功开门,并将执行callback回调;返回false代表无法开门,且不会执行回调函数。 + + +core.battle(id, x, y, force, callback) [异步] +执行战斗事件。id为怪物的id,x和y为坐标,force为bool值表示是否是强制战斗,callback为战斗完毕后的回调函数。 +id可为null代表使用地图上的值。 +例如:core.battle('greenSlime', null, null, true) + + +core.trigger(x, y) [异步] +触发某个地点的事件。 + + +core.isReplaying() +当前是否正在录像播放中 + + +core.drawBlock(block) +重绘某个图块。block应为core.status.thisMap.blocks中的一项。 + + +core.drawMap(floorId, callback) +重绘某一层的地图。floorId为要绘制楼层的floorId,callback为绘制完毕后的回调函数。 + + +core.terrainExists(x, y, id, floorId) +检测某个点是否存在(指定的)地形。 +x和y为坐标;id为地形ID,可为null表示任意地形;floorId为楼层ID,可忽略表示当前楼层。 + + +core.enemyExists(x, y, id, floorId) +检测某个点是否存在(指定的)怪物。 +x和y为坐标;id为怪物ID,可为null表示任意怪物;floorId为楼层ID,可忽略表示当前楼层。 + + +core.getBlock(x, y, floorId, showDisable) +获得某个点的当前图块信息。 +x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 +showDisable如果为true,则对于禁用的点和事件也会进行返回。 +如果该点不存在图块,则返回null。 +否则,返回值如下: {"index": xxx, "block": xxx} +其中index为该点在该楼层blocks数组中的索引,block为该图块实际内容。 + + +core.getBlockId(x, y, floorId, showDisable) +获得某个点的图块ID。 +x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 +showDisable如果为true,则对于禁用的点和事件也会进行返回。 +如果该点不存在图块,则返回null,否则返回该点的图块ID。 + + +core.getBlockCls(x, y, floorId, showDisable) +获得某个点的图块cls。 +x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 +showDisable如果为true,则对于禁用的点和事件也会进行返回。 +如果该点不存在图块,则返回null,否则返回该点的图块cls。 + + +core.showBlock(x, y, floorId) +将某个点从禁用变成启用状态。 + + +core.hideBlock(x, y, floorId) +将某个点从启用变成禁用状态,但不会对其进行删除。 +此函数不会实际将该块从地图中进行删除,而是将该点设置为禁用,以供以后可能的启用事件。 + + +core.removeBlock(x, y, floorId) +将从启用变成禁用状态,并尽可能将其从地图上删除。 +和hideBlock相比,如果该点不存在自定义事件(比如门或普通的怪物),则将直接从地图中删除。 +如果存在自定义事件,则简单的禁用它,以供以后可能的启用事件。 + + +core.setBlock(number, x, y, floorId) +改变图块。number为要改变到的图块数字,x和y为坐标,floorId为楼层ID,可忽略表示当前楼层。 + + +core.useItem(itemId, noRoute, callback) +尝试使用某个道具。itemId为道具ID,noRoute如果为真则该道具的使用不计入录像。 +callback为成功或失败后的回调。 + + +core.canUseItem(itemId) +返回当前能否使用某个道具。 + + +core.loadEquip(itemId, callback) +装备上某个装备。itemId为装备的ID,callback为成功或失败后的回调。 + + +core.unloadEquip(equipType, callback) +卸下某个部位的装备。equipType为装备类型,从0开始;callback为成功或失败后的回调。 + + +core.getNextItem() +轻按。 + + +core.drawTip(text, itemIcon) +在左上角绘制一段提示信息,2秒后消失。itemIcon为道具图标的索引。 + + +core.drawText(contents, callback) [异步] +绘制一段文字。 +不推荐使用此函数,尽量使用core.insertAction(contents)来显示剧情文本。 + + +core.closePanel() +结束一切事件和绘制,关闭UI窗口,返回游戏进程。 + + +core.replaceText(text) +将一段文字中的${}进行计算并替换。 + + +core.calValue(value, prefix, need, times) +计算表达式的实际值。这个函数可以传入status:atk等这样的参数。 + + +core.getLocalStorage(key, defaultValue) +从localStorage中获得某个数据(已被parse);如果对应的key不存在则返回defaultValue。 + + +core.getLocalForage(key, defaultValue, successCallback, errorCallback) +从localForage中获得某个数据(已被parse),如果对应的key不存在则返回defaultValue。 +如果成功则通过successCallback回调,失败则通过errorCallback回调。 + + +core.hasSave(index) +判定当前某个存档位是否存在存档,返回true/false。 +index为存档编号,0代表自动存档,大于0则为正常的存档位。 + + +core.clone(data) +深拷贝某个对象。 + + +core.isset(x) +测试x是否不为null,不为undefined也不为NaN。 + + +core.rand(num) +使用伪种子生成伪随机数。该随机函数能被录像支持。 +num如果设置大于0,则生成一个[0, num-1]之间的数;否则生成一个0到1之间的浮点数。 +此函数为伪随机算法,SL大法无效。(即多次SL后调用的该函数返回的值都是相同的。) + + +core.rand2(num) +使用系统的随机数算法得到的随机数。该随机函数能被录像支持。 +num如果设置大于0,则生成一个[0, num-1]之间的数;否则生成一个0到2147483647之间的整数。 +此函数使用了系统的Math.random()函数,支持SL大法。 +但是,此函数会将生成的随机数值存入录像,因此如果调用次数太多则会导致录像文件过大。 + + +core.restart() [异步] +返回标题界面。 + + +========== core.actions.XXX 和游戏控制相关的函数 ========== +actions.js主要用来进行用户交互行为的处理。 +所有用户行为,比如按键、点击、滑动等等,都会被此文件接收并进行操作。 + + +========== core.control.XXX 和游戏控制相关的函数 ========== +control.js主要用来进行游戏控制,比如行走控制、自动寻路、存读档等等游戏核心内容。 + +core.control.setGameCanvasTranslate(canvasId, x, y) +设置大地图的偏移量 + + +core.control.updateViewport() +更新大地图的可见区域 + + +core.control.gatherFollowers() +立刻聚集所有的跟随者 + + +core.control.replay() +回放下一个操作 + + +========== core.enemys.XXX 和怪物相关的函数 ========== +enemys.js主要用来进行怪物相关的内容,比如怪物的特殊属性,伤害和临界计算等。 + + +core.enemys.hasSpecial(special, test) +测试怪物是否含有某个特殊属性。 +常见用法: core.enemys.hasSpecial(monster.special, 3) ## 测试是否拥有坚固 + + +core.enemys.getSpecialText(enemyId) +返回一个列表,包含该怪物ID对应的所有特殊属性。 + + +core.enemys.getSpecialHint(enemy, special) +获得怪物某个(或全部)特殊属性的文字说明。 + + +core.enemys.canBattle(enemyId, x, y, floorId) +返回当前能否战胜某个怪物。 +后面三个参数是怪物坐标和楼层。 + + +core.enemys.getDamage(enemyId, x, y, floorId) +返回当前对某个怪物的战斗伤害。如果无法战斗,返回null。 +后面三个参数是怪物坐标和楼层。 + + +core.enemys.getExtraDamage(enemyId) +返回某个怪物会对勇士造成的额外伤害(不可被魔防抵消),例如仇恨、固伤等等。 + + +core.enemys.nextCriticals(enemyId, number, x, y, floorId) +返回一个列表,为接下来number(可忽略,默认为1)个该怪物的临界值和临界减伤。 +列表每一项类似 [x,y] 表示临界值为x,且临界减伤为y。 +如果无临界值,则返回空列表。 + + +core.enemys.getDefDamage(enemyId, k, x, y, floorId) +获得k(可忽略,默认为1)防减伤值。 + + +core.enemys.getDamageInfo(enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) +获得实际战斗信息,比如伤害,回合数,每回合伤害等等。 +此函数是实际战斗过程的计算。 + + +core.enemys.calDamage(enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) +获得在某个勇士属性下怪物伤害;实际返回的是上面getDamageInfo中伤害的数值。 + + +core.enemys.getCurrentEnemys(floorId) +获得某一层楼剩余所有怪物的信息(供怪物手册使用) + + +========== core.events.XXX 和事件相关的函数 ========== +events.js主要用来进行事件处理,比如自定义事件,以及某些条件下可能会被触发的事件。 +大多数事件API都在脚本编辑中存在,这里只列出部分比较重要的脚本编辑中不存在的API。 + + +core.events.gameOver(ending, fromReplay) +游戏结束并上传的事件。 +该函数将提问是否上传和是否下载录像,并返回标题界面。 + + +core.events.doEvents(list, x, y, callback) [异步] +开始执行某个事件。 +请不要执行此函数,尽量使用 core.insertAction(list, x, y, callback) 来开始执行一段事件。 + + +core.events.doAction() +执行下一个事件。此函数中将对所有自定义事件类型分别处理。 + + +core.events.getCommonEvent(name) +根据名称获得一个公共事件;如果不存在对应的公共事件则返回null。 + + +core.events.openShop(shopId, needVisited) [异步] +打开一个全局商店。needVisited表示是否需要该商店已被打开过。 + + +core.events.disableQuickShop(shopId) +禁用一个全局商店 + + +core.events.canUseQuickShop(shopId) +当前能否使用某个快捷商店 + + +core.events.setHeroIcon(name) +设置勇士行走图 + + +========== core.items.XXX 和道具相关的函数 ========== +items.js将处理和道具相关的内容,比如道具的使用,获取和删除等等。 + + +core.items.compareEquipment(equipId1, equipId2) +比较两个装备的属性变化值 + + +========== core.loader.XXX 和游戏加载相关的函数 ========== +loader.js将主要用来进行资源的加载,比如加载音乐、图片、动画等等。 + + +========== core.maps.XXX 和地图处理相关的函数 ========== +maps.js主要用来进行地图相关的的操作。包括绘制地图,获取地图上的点等等。 + + +core.maps.getNumberById(id) +根据ID来获得对应的数字。如果该ID不存在对应的数字则返回0。 + + +core.maps.canMoveHero(x,y,direction,floorId) +判断能否前往某个方向。x,y为坐标,可忽略为当前点;direction为方向,可忽略为当前方向。 +floorId为楼层ID,可忽略为当前楼层。 + + +core.maps.canMoveDirectly(destX, destY) +判断当前能否瞬间移动到某个点。 +该函数如果返回0则不可瞬间移动,大于0则可以瞬间移动,且返回值是跨度(即少走的步数)。 + + +core.maps.removeBlockById(index, floorId) +根据索引删除或禁用某块。 + + +core.maps.removeBlockByIds(floorId, ids) +根据索引删除或禁用若干块。 + + +core.maps.drawAnimate(name, x, y, callback) +播放一段动画,name为动画名(需在全塔属性注册),x和y为坐标(0-12之间),callback可选,为播放完毕的回调函数。 +播放过程是异步的,如需等待播放完毕请使用insertAction插入一条type:waitAsync事件。 +此函数将随机返回一个数字id,为此异步动画的唯一标识符。 + + +core.maps.stopAnimate(id, doCallback) +立刻停止一个异步动画。 +id为该动画的唯一标识符(由drawAnimate函数返回),doCallback可选,若为true则会执行该动画所绑定的回调函数。 + + +========== core.ui.XXX 和对话框绘制相关的函数 ========== +ui.js主要用来进行UI窗口的绘制,比如对话框、怪物手册、楼传器、存读档界面等等。 + + +core.ui.getContextByName(canvas) +根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。 +也可以传画布的context自身,则返回自己。 + + +core.clearMap(name) +清空某个画布图层。 +name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。(下同) +如果name也可以是'all',若为all则为清空所有系统画布。 + + +core.ui.fillText(name, text, x, y, style, font) +在某个画布上绘制一段文字。 +text为要绘制的文本,x,y为要绘制的坐标,style可选为绘制的样式,font可选为绘制的字体。(下同) + + +core.ui.fillBoldText(name, text, x, y, style, font) +在某个画布上绘制一个描黑边的文字。 + + +core.ui.fillRect(name, x, y, width, height, style) +绘制一个矩形。style可选为绘制样式。 + + +core.ui.strokeRect(name, x, y, width, height, style) +绘制一个矩形的边框。 + + +core.ui.drawLine(name, x1, y1, x2, y2, style, lineWidth) +绘制一条线。lineWidth可选为线宽。 + + +core.ui.drawArrow(name, x1, y1, x2, y2, style, lineWidth) +绘制一个箭头。 + + +core.ui.setFont(name, font) / core.ui.setLineWidth(name, lineWidth) +设置一个画布的字体/线宽。 + + +core.ui.setAlpha(name, font) / core.ui.setOpacity(name, font) +设置一个画布的绘制不透明度和画布本身的不透明度。 +两者区别如下: + - setAlpha是设置"接下来绘制的内容的不透明度",不会对已经绘制的内容产生影响。比如setAlpha('ui', 0.5)则会在接下来的绘制中使用0.5的不透明度。 + - setOpacity是设置"画布本身的不透明度",已经绘制的内容也会产生影响。比如我已经在UI层绘制了一段文字,再setOpacity则也会看起来变得透明。 +尽量不要对系统画布使用setOpacity(因为会对已经绘制的内容产生影响),自定义创建的画布则不受此限制。 + + +core.ui.setFillStyle(name, style) / core.ui.setStrokeStyle(name, style) +设置一个画布的填充样式/描边样式。 + + +core.ui.setTextAlign(name, align) +设置一个画布的文字对齐模式。 + + +core.ui.calWidth(name, text, font) +计算一段文字在画布上的绘制宽度 +font可选,如果存在则会先设置该画布上的字体。 + + +core.ui.drawImage(name, image, x, y, w, h, x1, y1, w1, h1) +在一个画布上绘制图片。 +name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。 +image为要绘制的图片,可以是一个全塔属性中定义的图片名(会从images中去获取),图片本身,或者一个画布。 +后面的8个坐标参数与canvas的drawImage的八个参数完全相同。 +请查看 http://www.w3school.com.cn/html5/canvas_drawimage.asp 了解更多。 + + +core.ui.createCanvas(name, x, y, width, height, zIndex) +动态创建一个画布。name为要创建的画布名,如果已存在则会直接取用当前存在的。 +x,y为创建的画布相对窗口左上角的像素坐标,width,height为创建的长宽。 +zIndex为创建的纵向高度(关系到画布之间的覆盖),z值高的将覆盖z值低的;系统画布的z值可在个性化中查看。 +返回创建的画布的context,也可以通过core.dymCanvas[name]调用。 + + +core.ui.relocateCanvas(name, x, y) +重新定位一个自定义画布。 + + +core.ui.resizeCanvas(name, x, y) +重新设置一个自定义画布的大小。 + + +core.ui.deleteCanvas(name) +删除一个自定义画布。 + + +core.ui.deleteAllCanvas() +清空所有的自定义画布。 + + +core.ui.drawThumbnail(floorId, canvas, blocks, x, y, size, heroLoc, heroIcon) +绘制一个缩略图,比如楼传器界面,存读档界面等情况。 +floorId为目标楼层ID,canvas为要绘制到的图层,blocks为要绘制的所有图块。 +x,y为该图层开始绘制的起始点坐标,size为每一格的像素,heroLoc为勇士坐标,heroIcon为勇士图标。 + + +========== core.utils.XXX 工具类的辅助函数 ========== +utils.js主要用来进行一些辅助函数的计算。 + + +core.utils.splitLines(canvas, text, maxLength, font) +自动切分长文本的换行。 +canvas为图层,text为要自动换行的内容,maxLength为每行最长像素,font为文本的字体。 + + +core.utils.cropImage(image, size) +纵向对图片进行切分(裁剪)。 + + +core.utils.push(a,b) +向某个数组后插入另一个数组或元素 + + +core.utils.unshift(a, b) +向某个数组前插入另一个数组或元素 + + +core.utils.encodeBase64(str) +Base64加密字符串 + + +core.utils.decodeBase64(str) +Base64解密字符串 + + +core.utils.formatBigNumber(x, onMap) +大数据的格式化 + + +core.utils.subarray(a, b) +检查b是否是a的从头开始子串。 +如果是,则返回a删去b的一段;否则返回null。 + + +core.utils.same(a, b) +比较a和b两个对象是否相同 + + +core.utils.clamp(x, a, b) +将x限制在[a,b]之间的范围内 + + +core.utils.arrayToRGB(color) +将形如[255,0,0]之类的数组转成#FF0000这样的RGB形式。 + + +core.utils.arrayToRGBA(color) +将形如[255,0,0,1]之类的数组转成rgba(255,0,0,1)这样的RGBA形式。 + + +core.utils.encodeRoute(list) +压缩加密路线。可以使用core.encodeRoute(core.status.route)来压缩当前路线。 + + +core.utils.decodeRoute(route) +解压缩(解密)路线。 + + +core.utils.readFile(success, error, readType) [异步] +尝试请求读取一个本地文件内容。 +success和error为成功/失败后的回调,readType不设置则以文本读取,否则以DataUrl形式读取。 + + +core.utils.readFileContent(content) [异步] +文件读取完毕后的内容处理。 + + +core.utils.download(filename, content) +尝试生成并下载一个文件。 + + +core.utils.copy(data) +尝试复制一段文本到剪切板。 + + +core.utils.http(type, url, formData, success, error) [异步] +发送一个异步HTTP请求。 +type为'GET'或者'POST';url为目标地址;formData如果是POST请求则为表单数据。 +success为成功后的回调,error为失败后的回调。 + +``` diff --git a/_docs/_sidebar.md b/_docs/_sidebar.md index 039c2ea0..914ee883 100644 --- a/_docs/_sidebar.md +++ b/_docs/_sidebar.md @@ -3,4 +3,5 @@ - [元件说明](element) - [事件](event) - [个性化](personalization) +- [脚本](script) - [附录:API列表](api) diff --git a/_docs/api.md b/_docs/api.md index 88697135..346f4bd6 100644 --- a/_docs/api.md +++ b/_docs/api.md @@ -1,219 +1,1444 @@ -# 附录: API列表 +# 附录:API列表 -?> 目前版本**v2.5.5**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * -**这里只列出所有可能会被造塔者用到的常用API,更多的有关内容请在代码内进行查询。** +这里将列出所有被转发到core的API,没有被转发的函数此处不会列出,请自行在代码中查看。 + +本附录量较大,如有什么需求请自行Ctrl+F进行搜索。 如有任何疑问,请联系小艾寻求帮助。 -可以在chrome浏览器的控制台中(`ctrl+shift+I`,找到Console)中直接进行调用,以查看效果。 +## core.js -**以下所有异步API都会加上[异步]的说明,存在此说明的请勿在事件处理的自定义脚本中使用。** +core.js中只有很少的几个函数,主要是游戏开始前的初始化等。 -!> 最常用的新手向命令,强烈建议每个人了解 +但是,core中定义了很多游戏运行时的状态,这些状态很多都会被使用到。 ``` text - -core.status.floorId -获得当前层的floorId。 +core.__SIZE__, core.__PIXELS__ +游戏窗口大小;对于13x13的游戏而言这两个值分别是13和416,15x15来说分别是15和480。 -core.status.maps -获得所有楼层的地图信息。 +core.material +游戏中的所有资源列表,具体分为如下内容: +core.material.animates (动画) +core.material.bgms (背景音乐) +core.material.enemys (怪物信息,来自于 project/enemys.js) +core.material.icons (图标信息,来自于 project/icons.js) +core.material.images (图片素材,存放了各项素材图片如items.png等) + core.material.images.autotile (所有的自动元件图片) + core.material.images.tilesets (所有的额外素材图片) + core.material.images.images (用户引入的其他图片) +core.material.items (道具信息) +core.material.sounds (音效) -core.status.thisMap -获得当前楼层信息,其等价于core.status.maps[core.status.floorId]。 +core.animateFrame +主要是记录和requestAnimationFrame相关的一些数据,常用的如下: +core.animateFrame.totalTime (游戏总的运行时时间) +core.animateFrame.weather (当前的天气信息) +core.animateFrame.asyncId (当前的异步处理事件的内容) + + +core.musicStatus +主要是记录和音效相关的内容,常用的如下: +core.musicStatus.bgmStatus (音乐开启状态) +core.musicStatus.soundStatus (音效开启状态) +core.musicStatus.playingBgm (当前正在播放的BGM) +core.musicStatus.lastBgm (最近一次尝试播放的BGM) +core.musicStatus.volume (当前的音量) +core.musicStatus.cachedBgms (背景音乐的缓存内容) +core.musicStatus.cacheBgmCount (背景音乐的缓存数量,默认值是4) + + +core.platform +游戏平台相关信息,常见的几个如下: +core.platform.isPC (是否是电脑端) +core.platform.isAndroid (是否是安卓端) +core.platform.isIOS (是否是iOS端) +core.platform.useLocalForage (是否开启了新版存档) +core.platform.extendKeyBoard (是否开启了拓展键盘) + + +core.domStyle +游戏的界面信息,包含如下几个: +core.domStyle.scale (当前的放缩比) +core.domStyle.isVertical (当前是否是竖屏状态) +core.domStyle.showStatusBar (当前是否显示状态栏) +core.domStyle.toolbarBtn (当前是否显示工具栏) + + +core.bigmap +当前的地图的尺寸信息,主要包含如下几个 +core.bigmap.width (当前地图的宽度) +core.bigmap.height (当前地图的高度) +core.bigmap.offsetX (当前地图针对窗口左上角的偏移像素x) +core.bigmap.offsetX (当前地图针对窗口左上角的偏移像素y) +core.bigmap.tempCanvas (一个临时画布,可以用来临时绘制很多东西) + + +core.saves +和存档相关的信息,包含如下几个: +core.saves.saveIndex (上次保存或读取的存档编号) +core.saves.ids (当前存在存档的编号列表) +core.saves.autosave (自动存档的信息) +core.saves.favorite (收藏的存档) +core.saves.favoriteNames (自定义存档的名称) + + +core.status +游戏的状态相关,是整个游戏中最重要的东西,其核心是如下几条: +请注意,每次重新开始、存档或读档时,core.status都会重新初始化。 +core.status.played (当前是否在游戏中) +core.status.gameOver (当前是否已经游戏结束,即win或lose) +core.status.hero (勇士信息;此项和全塔属性中的hero大体是对应的) + core.status.hero.name 勇士名 + core.status.hero.lv 当前等级 + core.status.hero.hpmax 当前生命上限 + core.status.hero.hp 当前生命值 + core.status.hero.manamax 当前魔力上限 + core.status.hero.mana 当前魔力值 + core.status.hero.atk 当前攻击力 + core.status.hero.def 当前防御力 + core.status.hero.mdef 当前魔防值 + core.status.hero.money 当前金币值 + core.status.hero.experience 当前经验值 + core.status.hero.loc 当前的位置信息 + core.status.hero.equipment 当前装上的装备 + core.status.hero.items 当前拥有的道具信息 + core.status.hero.flags 当前的各项flag信息 + core.status.hero.step 当前的步数值 + core.status.hero.statistics 当前的统计信息 +core.status.floorId (当前所在的楼层) +core.status.maps (所有的地图信息) +core.status.thisMap (当前的地图信息,等价于core.status.maps[core.status.floorId]) +core.status.bgmaps (所有背景层的信息) +core.status.fgmaps (所有的前景层的信息) +core.status.checkBlock (地图上的阻激夹域信息,也作为光环的缓存) +core.status.lockControl (当前是否是控制锁定状态) +core.status.automaticRoute (当前的自动寻路信息) +core.status.route (当前记录的录像) +core.status.replay (录像回放时要用到的信息) +core.status.shops (所有全局商店信息) +core.status.textAttribute (当前的文字属性,如颜色、背景等信息,和setText事件对应) +core.status.globalAttribute (当前的全局属性,如边框色、装备栏等) +core.status.curtainColor (当前色调层的颜色) +core.status.globalAnimateObjs (当前的全局帧动画效果) +core.status.floorAnimateObjs (当前的楼层贴图帧动画效果) +core.status.boxAnimateObjs (当前的盒子帧动画效果,例如怪物手册中的怪物) +core.status.autotileAnimateObjs (当前楼层的自动元件动画效果) +core.status.globalAnimateStatus (当前的帧动画的状态) +core.status.animateObjs (当前的播放动画信息) + + +core.floorIds +一个数组,表示所有的楼层ID,和全塔属性中的floorIds一致。 core.floors -获得所有楼层的信息。例如core.floors[core.status.floorId].events可获得本楼层的所有自定义事件。 +从楼层文件中读取全部的地图数据。 +和core.status.maps不同的是,后者在每次重新开始和读档时都会重置,也允许被修改(会存入存档)。 +而core.floors全程唯一,不允许被修改。 -core.status.hero -获得当前勇士状态信息。例如core.status.hero.atk就是当前勇士的攻击力数值。 +core.statusBar +状态栏信息,例如状态栏图片,图标,以及各个内容的DOM定义等。 +core.statusBar.images (所有的系统图标,和icons.png对应) +core.statusBar.icons (状态栏中绘制的图标内容) -core.material.enemys -获得所有怪物信息。例如core.material.enemys.greenSlime就是获得绿色史莱姆的属性数据。 +core.values +所有的全局数值信息,和全塔属性中的values一致。 +此项允许被直接修改,会存入存档。 -core.material.items -获得所有道具的信息。 +core.flags +所有的全塔开关,和全塔属性中的flags一致。 +此项不允许被直接修改,如有需要请使用“设置系统开关”事件,或者调用core.setGlobalFlag这个API。 -core.debug() -开启调试模式。此模式下可以按Ctrl键进行穿墙,并忽略一切事件。 -此模式下不可回放录像和上传成绩。 +core.plugin +定义的插件函数。 -core.updateStatusBar() -立刻刷新状态栏和地图显伤。 - - -core.setStatus('atk', 1000) -将攻击力设置为1000;这里把atk可以改成hp, def, mdef, money, experience等等。 -本句等价于 core.status.hero.atk = 1000 - - -core.getStatus('atk') -返回当前攻击力数值。本句等价于 core.status.hero.atk。 - - -core.setHeroLoc('x', 5) -设置勇士位置。这句话的意思是将勇士当前位置的横坐标设置为5。 -同理可以设置勇士纵坐标 core.setHeroLoc('y', 3)。 -值得注意的是,这句话虽然会使勇士改变位置,但并不会使界面重新绘制;如需立刻重新绘制地图还需调用: -core.clearMap('hero'); core.drawHero(); -来对界面进行更新。 - - -core.setItem('pickaxe', 10) -将破墙镐个数设置为10个。这里可以写任何道具的ID。 - - -core.addItem('pickaxe', 2) -将破墙镐的个数增加2个,无任何特效。这里可以写任何道具的ID。 - - -core.getItem('pickaxe', 4) -令勇士获得4个破墙镐。这里可以写任何道具的ID。 -和addItem相比,使用getItem会播放获得道具的音效,也会在左上角绘制获得提示。 - - -core.removeItem('pickaxe', 3) -删除3个破墙镐。第二项可忽略,默认值为1。 - - -core.itemCount('pickaxe') -返回当前破墙镐的个数。这里可以写任何道具的ID。 - - -core.hasItem('pickaxe') -返回当前是否存在某个道具。等价于 core.itemCount('pickaxe')>0 。 - - -core.getEquip(0) -获得0号装备类型(武器)的当前装备的itemId。如果不存在则返回null。 -这里可以写任意装备类型,从0开始和全塔属性中的equipName一一对应。 - - -core.hasEquip('sword1') -获得当前某个具体的装备是否处于正在被装备状态。 - - -core.setFlag('xyz', 2) -设置某个flag/变量的值为2。这里可以写任何的flag变量名。 - - -core.getFlag('xyz', 7) -获得某个flag/变量的值;如果该变量不存在,则返回第二个参数。 -比如 core.getFlag('point', 2) 则获得变量point的值;如果该变量从未定义过则返回2。 - - -core.hasFlag('xyz') -返回是否存在某个变量且不为0。等价于 core.getFlag('xyz', 0)!=0 。 - - -core.removeFlag('xyz') -删除某个flag/变量。 - - -core.insertAction(list, x, y, callback) -插入并执行一段自定义事件。在这里你可以写任意的自定义事件列表,有关详细写法请参见文档-事件。 -x和y如果设置则覆盖"当前事件点"的坐标,callback如果设置则覆盖事件执行完毕后的回调函数。 -例如: core.insertAction(["楼层切换", {"type":"changeFloor", "floorId": "MT3"}]) -将依次显示剧情文本,并执行一个楼层切换的自定义事件。 --------- -从V2.5.4开始提出了“公共事件”的说法,这里也可以插入一个公共事件名。 -例如:core.insertAction("毒衰咒处理") 将插入公共事件“毒衰咒处理”。 - - -core.changeFloor(floorId, stair, heroLoc, time, callback) [异步] -立刻切换到指定楼层。 -floorId为目标楼层ID,stair为到达的目标楼梯,heroLoc为到达的指定点,time为动画时间,callback为切换完毕后的回调。 -例如: -core.changeFloor('MT2', 'upFloor', null, 600) 切换到MT2层的上楼点,动画事件600ms -core.changeFloor('MT5', null, {'x': 3, 'y': 6}, 0) 无动画切换到MT5层的(3,6)位置。 - - -core.resetMap() -重置当前楼层地图和楼层属性。 -此函数参数有三种形式: - - 不加任何参数,表示重置当前层:core.resetMap() - - 加上一个floorId,表示重置某一层:core.resetMap("MT1") - - 使用一个数组,表示重置若干层:core.resetMap(["MT1", "MT2", "MT3"]) ---------------------------- -** 说明:从V2.5.5开始存档方式发生了改变,在编辑器修改了地图后现在将直接生效,无需再重置地图。 - -R -录像回放的快捷键;这不是一个控制台命令,但是也把它放在这里供使用。 -录像回放在修改地图或新增数据后会很有用。 - +core.doFunc(func, _this) +执行一个函数,func为函数体或者插件中的函数名,_this为使用的this。 +如果func为一个字符串,则视为插件中的函数名,同时_this将被设置成core.plugin。 +此函数剩余参数将作为参数被传入func。 ``` -!> 一些相对高级的命令,针对有一定脚本经验的人 +## actions.js -``` text +actions.js主要是处理一些和用户交互相关的内容。 -========== 可直接从core中调用的,最常被使用的函数 ========== -core.js实际上是所有API的入口(路由),核心API的实现在其他几个文件中,core.js主要进行转发操作。 +```text +core.registerAction(action, name, func, priority) +注册一个用户交互行为。 +action:要注册的交互类型,如 ondown, onclick, keyDown 等等。 +name:你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +func:执行函数;可以是一个具体的函数体,或者是一个插件中的函数名。 +priority:优先级;优先级高的被注册项将会被执行。此项可不填,默认为0。 +返回:如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 -core.nextX(n) -获得勇士面向的第n个位置的x坐标,n可以省略默认为1(即正前方) +core.unregisterAction(action, name) +注销一个用户交互行为。 -core.nextY(n) -获得勇士面向的第n个位置的y坐标,n可以省略默认为1(即正前方) +core.doRegisteredAction(action) +执行一个用户交互行为。 +此函数将在该交互行为所注册的所有函数中,按照优先级从高到底依次执行。 +此函数剩余的参数将会作为参数传入该执行函数中。 +当某个执行函数返回true时将终止这一过程。 + + +core.onkeyDown(e) +当按下某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyDown"的交互函数。 + + +core.onkeyUp(e) +当放开某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyDown"的交互函数。 + + +core.pressKey(keyCode) +当按住某个键不动时的操作,目前只对方向键有效。 +如果需要添加对于其他键的长按,请复写_sys_onkeyDown和_sys_onkeyUp。 +请勿直接覆盖或调用此函数,如有需要请注册一个"pressKey"的交互函数。 + + +core.keyDown(keyCode) +当按下某个键时的操作,参数为该键的keyCode值。 +请勿直接覆盖或调用此函数,如有需要请注册一个"keyDown"的交互函数。 + + +core.keyUp(keyCode, altKey, fromReplay) +当按下某个键时的操作,参数为该键的keyCode值。 +altKey标志了Alt键是否同时被按下,fromReplay表示是否是从录像回放中调用的。 +请勿直接覆盖或调用此函数,如有需要请注册一个"keyUp"的交互函数。 + + +core.ondown(loc) +当点击屏幕时的操作。loc为点击的信息。 +请勿直接覆盖或调用此函数,如有需要请注册一个"ondown"的交互函数。 +注册的ondown交互函数需要接受x, y, px, py四个参数,代表点击的位置和像素坐标。 + + +core.onmove(loc) +当在屏幕上滑动时的操作。loc为当前的坐标信息。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onmove"的交互函数。 +注册的onmove交互函数需要接受x, y, px, py四个参数,代表当前的的位置和像素坐标。 + + +core.onup() +当从屏幕上离开时的操作。请注意此函数是没有参数的。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onup"的交互函数。 + + +core.onclick(x, y) +当点击屏幕上的某点位置时执行的操作,请注意这里的x和y是位置坐标。 +一般而言,一个完整的ondown到onup将触发一个onclick事件。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onclick"的交互函数。 + + +core.onmousewheel(direct) +当滚动鼠标滑轮时执行的操作。direct为滑轮方向,上为1,下为-1。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onmousewheel"的交互函数。 + + +core.keyDownCtrl() +当长按Ctrl键不动时执行的操作。 +请勿直接覆盖或调用此函数,如有需要请注册一个"keyDownCtrl"的交互函数。 + + +core.longClick() +当长按住屏幕时执行的操作。 +请勿直接覆盖或调用此函数,如有需要请注册一个"keyDownCtrl"的交互函数。 +注册的交互函数如果某一项返回true,则之后仍然会继续触发该长按, +如果全部返回false则将停止本次长按行为,直到手指离开屏幕并重新进行长按为止。 +``` + +## control.js + +control.js将负责整个游戏的核心控制系统,分为如下几个部分: +- requestAnimationFrame相关 +- 标题界面,开始和重新开始游戏 +- 自动寻路和人物行走相关 +- 画布、位置、阻激夹域、显伤等相关 +- 录像的回放相关 +- 存读档,自动存档,同步存档等相关 +- 人物属性和状态、位置、变量等相关 +- 天气、色调、音乐和音效的播放 +- 状态栏和工具栏相关 +- 界面resize相关 + +```text +// ------ requestAnimationFrame 相关 ------ // + +core.registerAnimationFrame(name, needPlaying, func) +注册一个animationFrame。它将在每次浏览器的帧刷新时(约16.6ms)被执行。 +name:你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +needPlaying:如果此项为true,则仅在游戏开始后才会被执行(标题界面不执行) +func:执行函数;可以是一个具体的函数体,或者是一个插件中的函数名。 +func可以接受一个timestamp作为参数,表示从整个页面加载完毕到当前时刻所经过的毫秒数。 +如果func执行报错,将在控制台打出一条信息,并自动进行注销。 + + +core.unregisterAnimationFrame(name) +注销一个animationFrame,参数是你的上面的自定义名称。 + +// ------ 开始界面相关 ------ // + +core.showStartAnimate(noAnimate, callback) +重置所有内容并显示游戏标题界面。 +noAnimate如果为true则不会有淡入动画,callback为执行完毕的回调。 + + +core.hideStartAnimate(callback) +淡出隐藏游戏标题界面,callback为执行完毕的回调。 + + +core.isPlaying() +当前是否正在游戏中。 + + +core.clearStatus() +清除所有的游戏状态和数据,包括状态栏的显示。 + +// ------ 自动寻路、人物行走 ------ // + +core.stopAutomaticRoute() +停止自动寻路的操作 + + +core.saveAndStopAutomaticRoute() +保存剩下的寻路路线并停止自动寻路操作。主要用于打怪开门后继续寻路使用。 + + +core.continueAutomaticRoute() +继续剩下的自动寻路操作。主要用于打怪开门后继续寻路使用。 + + +core.clearContinueAutomaticRoute() +清空剩下的自动寻路操作。 + + +core.setAutomaticRoute(destX, destY, stepPostfix) +尝试开始进行一个自动寻路。stepPostfix是鼠标拖动的路径。 +此函数将检测是否在寻路中(在则停止或双击瞬移),检测是否点击自己(转身或轻按), +检测是否能单击瞬移,最后找寻自动寻路路线并开始寻路。 + + +core.setAutoHeroMove(steps) +设置勇士的自动行走路线,并立刻开始行走。 + + +core.setHeroMoveInterval(callback) +设置勇士行走动画。callback是每一步行走完毕后的回调。 + + +core.moveOneStep(x, y) +每走完一步后执行的操作,被转发到了脚本编辑中。 + + +core.moveAction(callback) +尝试执行单步行走。callback是执行完毕的回调。 +如果勇士面对的方向是noPass的,将直接触发事件并执行回调。 + + +core.moveHero(direction, callback) +令勇士朝一个方向行走。如果设置了callback,则只会行走一步,并执行回调。 +否则,将一直朝该方向行走,直到core.status.heroStop为true为止。 + + +core.isMoving() +当前是否正在处于行走状态 + + +core.waitHeroToStop(callback) +停止勇士的行走,等待行动结束后,再异步执行回调。 + + +core.turnHero(direction) +转向。如果设置了direction则会转到该方向,否则会右转。该函数会自动计入录像。 + + +core.moveDirectly(destX, destY) +尝试瞬间移动到某点,被转发到了脚本编辑中。 +此函数返回非负值代表成功进行瞬移,返回值是省略的步数;如果返回-1则代表没有成功瞬移。 + + +core.tryMoveDirectly(destX, destY) +尝试单击瞬移到某点。 +如果该点可被直接瞬间移动到,则直接瞬移到该点;否则尝试瞬移到相邻的上下左右点并行走一步。 + + +core.drawHero(status, offset) +绘制勇士。 +status可选,为'stop','leftFoot'和'rightFoot'之一,不填或null默认是'stop'。 +offset可选,表示具体当前格子的偏移量。不填默认为0。 +此函数将重新计算地图的偏移量,调整窗口位置,绘制勇士和跟随者信息。 + +// ------ 画布、位置、阻激夹域、显伤 ------ // + +core.setGameCanvasTranslate(canvas, x, y) +设置某个画布的偏移量 + + +core.addGameCanvasTranslate(x, y) +加减所有系统画布(ui和data除外)的偏移量。主要是被“画面震动”所使用。 + + +core.updateViewport() +根据大地图的偏移量来更新窗口的视野范围。 + + +core.nextX(n) / core.nextY(m) +获得勇士面对的第n个位置的横纵坐标。n可不填,默认为1。 core.nearHero(x, y) -判断某个点是否和勇士的距离不超过1。 +判定某个点是否和勇士的距离不大于1。 -core.openDoor(id, x, y, needKey, callback) [异步] -尝试开门操作。id为目标点的ID,x和y为坐标,needKey表示是否需要使用钥匙,callback为开门完毕后的回调函数。 -id可为null代表使用地图上的值。 -例如:core.openDoor('yellowDoor', 10, 3, false, function() {console.log("1")}) -此函数返回true代表成功开门,并将执行callback回调;返回false代表无法开门,且不会执行回调函数。 +core.gatherFollowers() +聚集所有的跟随者到勇士的位置。 -core.battle(id, x, y, force, callback) [异步] -执行战斗事件。id为怪物的id,x和y为坐标,force为bool值表示是否是强制战斗,callback为战斗完毕后的回调函数。 -id可为null代表使用地图上的值。 -例如:core.battle('greenSlime', null, null, true) +core.updateFollowers() +更新跟随者们的坐标。 -core.trigger(x, y) [异步] -触发某个地点的事件。 +core.updateCheckBlock(floorId) +更新阻激夹域的信息,被转发到了脚本编辑中。 + + +core.checkBlock() +检查勇士坐标点的阻激夹域信息。 + + +core.updateDamage(floorId, ctx) +更新全地图的显伤。floorId可选,默认为当前楼层。 +ctx可选,为画布;如果不为空,则将会绘制到该画布上而不是damage层上。 + +// ------ 录像相关 ------ // + +core.chooseReplayFile() +弹出选择文件窗口,让用户选择录像文件。 + + +core.startReplay(list) +开始播放一段录像。list为录像的操作数组。 + + +core.triggerReplay() +播放或暂停录像,实际上是pauseReplay或resumeReplay之一。 + + +core.pauseReplay() / core.resumeReplay() +暂停和继续录像播放。 + + +core.speedUpReplay() / core.speedDownReplay() +加速和减速录像播放。 + + +core.setReplaySpeed(speed) +直接设置录像回放速度。 + + +core.stopReplay(force) +停止录像回放。如果force为true则强制停止。 + + +core.rewindReplay() +回退一个录像节点。 + + +core.saveReplay() / core.bookReplay() / core.viewMapReplay() +回放录像时的存档、查看怪物手册、浏览地图操作。 core.isReplaying() -当前是否正在录像播放中 +当前是否正在录像播放中。 -core.drawBlock(block) -重绘某个图块。block应为core.status.thisMap.blocks中的一项。 +core.registerReplayAction(name, func) +注册一个自定义的录像行为。 +name:自定义名称,可用户注销使用。 +func:具体执行录像的函数,是一个函数体或者插件中的函数名。 +func需要接受action参数,代表录像回放时的当前操作行为。 +如果func返回true,则代表成功处理了此次操作,返回false代表没有进行处理。 +自己添加的录像项只能由数字、大小写、下划线线、冒号等符号组成,否则无法正常压缩和解压缩。 +对于自定义内容(比如中文文本或数组)请使用JSON.stringify再core.encodeBase64处理。 +请注意回放录像时的二次记录问题(即回放时录像会重新记录路线)。 + + +core.unregisterReplayAction(name) +注销一个录像行为。此函数一般不应当被使用。 + +// ------ 存读档相关 ------ // + +core.autosave(remoreLast) +进行一个自动存档,实际上是加入到缓存之中。 +removeLast如果为true则会从路线中删除最后一项再存(打怪开门前的状态)。 +在事件处理中不允许调用本函数,如有需要请呼出存档页面。 + + +core.checkAutosave() +将缓存的自动存档写入存储中。平均每五秒钟,或在窗口失去焦点时被执行。 + + +core.doSL(id, type) +实际执行一个存读档事件。id为存档编号,自动存档为'autoSave'。 +type只能为'save', 'load', 'replayLoad'之一,代表存档、读档和从存档回放录像。 + + +core.syncSave(type) / core.syncLoad() +向服务器同步存档,从服务器加载存档。type如果为'all'则会向服务器同步所有存档。 + + +core.saveData() +获得要存档的内容,实际转发到了脚本编辑中。 + + +core.loadData(data, callback) +实际执行一次读档行为,data为读取到的数据,callback为执行完毕的回调。 +实际转发到了脚本编辑中。 + + +core.getSave(index, callback) +获得某个存档位的存档。index为存档编号,0代表自动存档。 + + +core.getSaves(ids, callback) +获得若干个存档位的存档。ids为一个存档编号数组,0代表自动存档。 + + +core.getAllSaves(callback) +获得全部的存档内容。目前仅被同步全部存档和下载全部存档所调用。 + + +core.getSaveIndexes(callback) +刷新全部的存档信息,将哪些档位有存档的记录到core.saves.ids中。 + + +core.hasSave(index) +判定某个存档位是否存在存档。index为存档编号,0代表自动存档。 + + +core.removeSave(index) +删除某个存档。index为存档编号,0代表自动存档。 + + +// ------ 属性、状态、位置、变量等 ------ // + +core.setStatus(name, value) +设置勇士当前的某个属性。 + + +core.addStatus(name, value) +加减勇士当前的某个属性。等价于 core.setStatus(name, core.getStatus(name) + value) + + +core.getStatus(name) +获得勇士的某个原始属性值。 + + +core.getStatusOrDefault(status, name) +尝试从status中获得某个原始属性值;如果status为null或不存在对应属性值则从勇士属性中获取。 +此项在伤害计算函数中使用较多,例如传递新的攻击和防御来计算临界和1防减伤。 + + +core.getRealStatus(name) +获得勇士的某个计算属性值。该属性值是在加成buff之后得到的。 +该函数等价于 core.getStatus(name) * core.getBuff(name) + + +core.getRealStatusOrDefault(status, name) +尝试从status中获得某个原始属性值再进行增幅,如果不存在则获取勇士本身的计算属性值。 + + +core.setBuff(name, value) +设置勇士的某个属性的增幅值。value为1代表无增幅。 + + +core.addBuff(name, value) +增减勇士的某个属性的增幅值。等价于 core.setBuff(name, core.getBuff(name) + value) + + +core.getBuff(name) +获得勇士的某个属性的增幅值。默认值是1。 + + +core.setHeroLoc(name, value, noGather) +设置勇士位置属性。name只能为'x', 'y'和'direction'之一。 +如果noGather为true,则不会聚集所有的跟随者。 + + +core.getHeroLoc(name) +获得勇士的某个位置属性。如果name为null则直接返回core.status.hero.loc。 + + +core.getLvName(lv) +获得某个等级对应的名称,其在全塔属性的levelUp中定义。如果不存在则返回原始数值。 + + +core.setFlag(name, value) +设置某个自定义变量或flag。如果value为null则会调用core.removeFlag进行删除。 + + +core.addFlag(name, value) +加减某个自定义的变量或flag。等价于 core.setFlag(name, core.getFlag(name, 0) + value) + + +core.getFlag(name, defaultValue) +获得某个自定义的变量或flag。如果该flag不存在(从未赋值过),则返回defaultValue值。 + + +core.hasFlag(name) +判定是否拥有某个自定义变量或flag。等价于 !!core.getFlag(name, 0) + + +core.removeFlag(name) +删除一个自定义变量或flag。 + + +core.lockControl() / core.unlockControl() +锁定和解锁控制。常常应用于事件处理。 + + +core.debug() +开启调试模式。此模式下可以按住Ctrl进行穿墙。 + +// ------ 天气,色调,音乐和音效 ------ // + +core.setWeather(type, level) +设置当前的天气。type只能为'rain', 'snow'或'fog',level为1-10之间代表强度信息。 + + +core.setCurtain(color, time, callback) +更改画面色调。color为更改到的色调,是个三元或四元组;time为渐变时间,0代表立刻切换。 + + +core.screenFlash(color, time, times, callback) +画面闪烁。color为色调,三元或四元组;time为单次闪烁时间,times为总闪烁次数。 + + +core.playBgm(bgm, startTime) +播放一个bgm。startTime可以控制开始时间,不填默认为0。 +如果bgm不存在、不被支持,或当前不允许播放背景音乐,则会跳过。 + + +core.pauseBgm() / core.resumeBgm() +暂停和恢复当前bgm的播放。 + + +core.triggerBgm() +更改当前bgm的播放状态。 + + +core.playSound(sound) / core.stopSound() +播放一个音效,停止全部音效。 +如果sound不存在、不被支持,或当前不允许播放音效,则会忽略。 + + +core.checkBgm() +检查bgm的状态。 +有的时候,刚打开页面时,浏览器是不允许自动播放标题界面bgm的,一定要经过一次用户操作行为。 +这时候我们可以给开始按钮增加core.checkBgm(),如果之前没有成功播放则重新播放。 + +// ------ 状态栏和工具栏相关 ------ // + +core.clearStatusBar() +清空状态栏的数据。 + + +core.updateStatusBar() +更新状态栏,被转发到了脚本编辑中。此函数还会根据是否在回放来设置工具栏的图标。 + + +core.showStatusBar() / core.hideStatusBar(showToolbox) +显示和隐藏状态栏。 +如果showToolbox为true,则在竖屏模式下不隐藏工具栏,方便手机存读档操作。 + + +core.updateHeroIcon() +更新状态栏上的勇士图标。 + + +core.updateGlobalAttribute() +更新全局属性,例如状态栏的背景图等。 + + +core.setToolbarButton(useButtom) +设置工具栏是否是拓展键盘。 + +// ------ resize 相关 ------ // + +core.registerResize(name, func) +注册一个resize函数。 +name为自定义名称,可供注销使用。 +func可以是一个函数,或插件中的函数名,可以接受一个obj作为参数。 +具体详见resize函数。 + + +core.unregisterResize(name) +注销一个resize函数。 + + +core.resize() +屏幕分辨率改变后的重新自适应。 +此函数将根据当前的屏幕分辨率信息,生成一个obj,并传入各个注册好的resize函数中执行。 +``` + +## enemys.js + +enemys.js中定义了一系列和怪物相关的API函数。 + +```text +core.hasSpecial(special, test) +判断是否含有某个特殊属性。test为要检查的特殊属性编号。 +special为要测试的内容,允许接收如下类型参数: + - 一个数字:将直接和test进行判等。 + - 一个数组:将检查test是否在该数组之中存在。 + - 一个怪物信息:将检查test是否在该怪物的特殊属性中存在 + - 一个字符串:视为怪物ID,将检查该怪物的特殊属性 + + +core.getSpecials() +获得所有特殊属性的列表。实际上被转发到了脚本编辑中。 + + +core.getSpecialText(enemy) +获得某个怪物的全部特殊属性名称。enemy可以是怪物信息或怪物ID。 +将返回一个数组,每一项是该怪物所拥有的一个特殊属性的名称。 + + +core.getSpecialHint(enemy, special) +获得怪物的某个特殊属性的描述。enemy可以是怪物信息或怪物ID,special为该特殊属性编号。 + + +core.canBattle(enemy, x, y, floorId) +判定当前能否战胜某个怪物。 +enemy可以是怪物信息或怪物ID,x,y,floorId为当前坐标和楼层。(下同) +能战胜返回true,不能战胜返回false。 + + +core.getDamage(enemy, x, y, floorId) +获得某个怪物的全部伤害值。 +如果没有破防或无法战斗则返回null,否则返回具体的伤害值。 + + +core.getExtraDamage(enemy, x, y, floorId) +获得某个怪物的额外伤害值(不可被魔防减伤)。 +目前暂时只包含了仇恨和固伤两者,如有需要可复写该函数。 + + +core.getDamageString(enemy, x, y, floorId) +获得某个怪物伤害字符串和颜色信息,以便于在地图上绘制显伤。 + + +core.nextCriticals(enemy, number, x, y, floorId) +获得接下来的N个临界值和临界减伤。enemy可以是怪物信息或怪物ID,x,y,floorId为当前坐标和楼层。 +number为要计算的临界值数量,不填默认为1。 +如果全塔属性中的useLoop开关被开启,则将使用循环法或二分法计算临界,否则使用回合法计算临界。 +返回一个二维数组 [[x1,y1],[x2,y2],...] 表示接下来的每个临界值和减伤值。 + + +core.getDefDamage(enemy, k, x, y, floorId) +获得某个怪物的k防减伤值。k可不填默认为1。 + + +core.getEnemyInfo(enemy, hero, x, y, floorId) +获得某个怪物的实际计算时的属性。该函数实际被转发到了脚本编辑中。 +hero可为null或一个对象,具体将使用core.getRealStatusOrDefault(hero, "atk")来获得攻击力数值。 +该函数应当返回一个对象,记录了怪物的实际计算时的属性。 + + +core.getDamageInfo(enemy, hero, x, y, floorId) +获得某个怪物的战斗信息。该函数实际被转发到了脚本编辑中。 +hero可为null或一个对象,具体将使用core.getRealStatusOrDefault(hero, "atk")来获得攻击力数值。 +如果该函数返回null,则代表不可战斗(如没有破防,或无敌等)。 +否则,该函数应该返回一个对象,记录了战斗伤害信息,如战斗回合数等。 +从V2.5.5开始,该函数也允许直接返回一个数字,代表战斗伤害值,此时回合数将视为0。 + + +core.updateEnemys() +更新怪物数据。该函数实际被转发到了脚本编辑中。详见文档-事件-更新怪物数据。 + + +core.getCurrentEnemys(floorId) +获得某个楼层不重复的怪物信息,floorId不填默认为当前楼层。该函数会被怪物手册所调用。 +该函数将返回一个列表,每一项都是一个不同的怪物,按照伤害值从小到大排序。 +另外值得注意的是,如果设置了某个怪物的displayIdInBook,则会返回对应的怪物。 + + +core.hasEnemyLeft(floorId) +检查某个楼层是否还有剩余的怪物。等价于 core.getCurrentEnemys(floorId).length > 0 +``` + +## events.js + +events.js将处理所有和事件相关的操作,主要分为五个部分: +- 游戏的开始和结束 +- 系统事件的处理 +- 自定义事件的处理 +- 点击状态栏图标所进行的操作 +- 一些具体事件的执行内容 + + +```text +// ------ 游戏的开始和结束 ------ // + +core.resetGame(hero, hard, floorId, maps, values) +重置整个游戏。该函数实际被转发到了脚本编辑中。 + + +core.startGame(hard, seed, route, callback) +开始新游戏。 +hard为难度字符串,会被设置为core.status.hard。 +seed为开始时要设置的的种子,route为要开始播放的录像,callback为回调函数。 +该函数将重置整个游戏,调用setInitData,执行startText事件,上传游戏人数统计信息等。 + + +core.setInitData() +根据难度分歧来初始化难度,包括设置flag:hard,设置初始属性等。 +该函数实际被转发到了脚本编辑中。 + + +core.win(reason, norank) +游戏胜利,reason为结局名,norank如果为真则该结局不计入榜单。 +该函数实际被转发到了脚本编辑中。 + + +core.lose(reason) +游戏失败,reason为结局名。该函数实际被转发到了脚本编辑中。 + + +core.gameOver(ending, fromReplay, norank) +游戏结束。ending为获胜结局名,null代表失败;fromReplay标识是否是录像触发的。 +此函数将询问是否上传成绩(如果ending不是null),是否下载录像等,并重新开始。 + + +core.restart() +重新开始游戏。本质上就是播放标题界面的BGM并调用showStartAnimate。 + + +core.confirmRestart() +确认用户是否需要重新开始。 + +// ------ 系统事件处理 ------ // + +core.registerSystemEvent(type, func) +注册一个系统事件,即通过图块的默认触发器所触发的事件。 +type为一个要注册的事件类型,func为要执行的函数体或插件中的函数名。 +func需要接受(data, callback)作为参数,分别是触发点的图块信息,和执行完毕时的回调。 +如果注册一个已经存在的系统事件,比如openDoor,则会覆盖系统的默认函数。 + + +core.unregisterSystemEvent(type) +注销一个系统事件。type是上面你注册的事件类型。 + + +core.doSystemEvent(type, data, callback) +执行一个系统事件。type为事件类型,data为该事件点的图块信息,callback为执行完毕的回调。 + + +core.battle(id, x, y, force, callback) +和怪物进行战斗。 +id为怪物的ID,x和y为怪物坐标,force如果为真将强制战斗,callback为执行完毕的回调。 +如果填写了怪物坐标,则会删除对应点的图块并执行该点战后事件。 +如果是在事件流的执行过程中调用此函数,则不会进行自动存档,且会强制战斗。 + + +core.beforeBattle(enemyId, x, y) +战前事件。实际被转发到了脚本编辑中,可以在这里加上一些战前特效。 +此函数在“检测能否战斗和自动存档”【之后】执行。 +如果需要更早的战前事件,请在插件中覆重写 core.events.doSystemEvent 函数。 +此函数返回true则将继续本次战斗,返回false将不再战斗。 + + +core.afterBattle(enemyId, x, y, callback) +战后事件,将执行扣血、加金币经验、特殊属性处理、战后事件处理等操作。 +实际被转发到了脚本编辑中。 + + +core.openDoor(x, y, needKey, callback) +尝试开一个门。x和y为门的坐标,needKey表示是否需要钥匙,callback为执行完毕的回调。 +如果不是一个有效的门,需要钥匙且未持有等,均会忽略此事件并直接执行callback。 + + +core.afterOpenDoor(doorId, x, y, callback) +开完一个门后执行的事件,实际被转发到了脚本编辑中。 + + +core.getItem(id, num, x, y, callback) +获得若干个道具。itemId为道具ID,itemNum为获得的道具个数,不填默认为1。 +x和y为道具点的坐标,如果设置则会擦除地图上的该点。 + + +core.afterGetItem(id, x, y, callback) +获得一个道具后执行的事件,实际被转发到了脚本编辑中。 + + +core.getNextItem(noRoute) +轻按,即获得面对的道具。如果noRoute为真则这个轻按行为不会计入录像。 + + +core.changeFloor(floorId, stair, heroLoc, time, callback, fromLoad) +楼层切换。floorId为目标楼层ID,stair为是什么楼梯,heroLoc为目标点坐标。 +time为切换时间,callback为切换完毕的回调,fromLoad标志是否是从读档造成的切换。 +floorId也可以填":before"和":next"表示前一层和后一层。 +heroLoc为{"x": 0, "y": 0, "direction": "up"}的形式。不存在则从勇士位置取。 +如果stair不为null,则会在该楼层中找对应的图块作为目标点的坐标并覆盖heroLoc。 +一般设置的是"upFloor"和"downFloor",但也可以用任何其他的图块ID。 + + +core.changingFloor(floorId, heroLoc, fromLoad) +正在执行楼层切换中执行的操作,实际被转发到了脚本编辑中。 + + +core.hasVisitedFloor(floorId) +是否曾经到达过某一层。 + + +core.visitFloor(floorId) +标记曾经到达了某一层。 + + +core.passNet(data) +执行一个路障处理。这里只有毒衰咒网的处理,血网被移动到了updateCheckBlock中。 + + +core.pushBox(data) +执行一个推箱子事件。 + + +core.afterPushBox() +推箱子之后触发的事件,实际被转发到了脚本编辑中。 + + +core.changeLight(id, x, y) +踩灯后的事件。 + +// ------ 自定义事件的处理 ------ // + +core.registerEvent(type, func) +注册一个自定义事件。type为事件名,func为执行事件的函数体或插件中的函数名。 +func可以接受(data, x, y, prefix)参数,其中data为事件内容,x和y为该点坐标,prefix为该点前缀。 +同名注册的事件将进行覆盖。 +请记得在自定义处理事件完毕后调用core.doAction()再继续执行下一个事件! + + +core.unregisterEvent(type) +注销一个自定义事件。 + + +core.doEvent(data, x, y, prefix) +执行一个自定义事件。data为事件内容,将根据data.type去注册的事件列表中查找对应的执行函数。 +x和y为该点坐标,prefix为该点前缀。执行事件时也会把(data, x, y, prefix)传入执行函数。 + + +core.setEvents(list, x, y, callback) +设置自定义事件的执行列表,坐标和回调函数。 + + +core.startEvents(list, x, y, callback) +开始执行一系列的自定义事件。list为事件列表,x和y为事件坐标,callback为执行完毕的回调。 +此函数将调用core.setEvents,然后停止勇士,再执行core.doAction()。 + + +core.doAction() +执行下一个自定义事件。 +此函数将检测事件列表是否全部执行完毕,如果是则执行回调函数。 +否则,将从事件列表中弹出下一个事件,并调用core.doEvent进行执行。 + + +core.insertAction(action, x, y, callback, addToLast) +向当前的事件列表中插入一个或多个事件并执行。 +如果当前并不是在事件执行流中,则会调用core.startEvents()开始执行事件,否则仅仅执行插入操作。 +action为要插入的事件,可以是一个单独的事件,或者是一个事件列表。 +x,y,callback如果设置了且不为null,则会覆盖当前的坐标和回调函数。 +addToLast如果为真,则会插入到事件执行列表的尾部,否则是插入到执行列表的头部。 + + +core.getCommonEvent(name) +根据名称获得某个公共事件内容。 + + +core.recoverEvents(data) +恢复事件现场。一般用于呼出怪物手册、呼出存读档页面等时,恢复事件执行流。 + +// ------ 点击状态栏图标时执行的一些操作 ------ // + +core.openBook(fromUserAction) +尝试打开怪物手册。fromUserAction标志是否是从用户的行为触发,如按键或点击状态栏。(下同) +不建议复写此函数,否则【呼出怪物手册】事件会出问题。 + + +core.useFly(fromUserAction) +尝试使用楼传器。可以安全的复写此函数,参见文档-个性化-覆盖楼传事件。 + + +core.flyTo(toId, callback) +尝试飞行到某个楼层,被转发到了脚本编辑中。 +如果此函数返回true代表成功进行了飞行,false代表不能进行飞行。 + + +core.openEquipbox(fromUserAction) / core.openToolbox(fromUserAction) +尝试打开道具栏和装备栏。可以安全复写这两个函数。 + + +core.openQuickShop(fromUserAction) / core.openKeyBoard(fromUserAction) +尝试打开快捷商店和虚拟键盘。可以安全复写这两个函数。 + + +core.save(fromUserAction) / core.load(fromUserAction) +尝试打开存读档页面。 +不建议复写这两个函数,否则【呼出存读档页面】事件会出问题。 + + +core.openSettings(fromUserAction) +尝试打开系统菜单。不建议复写此函数。 + + +// ------ 一些具体事件的执行内容 ------ // + +core.hasAsync() +当前是否存在未执行完毕的异步事件。请注意正在播放的动画也算异步事件。 + + +core.follow(name) / core.unfollow(name) +跟随勇士/取消跟随。name为行走图名称。 +在取消跟随时如果指定了name,则会从跟随列表中选取一个该行走图取消,否则取消所有跟随。 +跟随和取消跟随都会调用core.gatherFollowers()来聚集所有的跟随者。 + + +core.setValue(name, value, prefix) / core.addValue(name, value, prefix) +设置/增减某个数值。name可以是status:xxx,item:xxx或flag:xxx。 +value可以是一个表达式,将调用core.calValue()计算。prefix为前缀,独立开关使用。 + + +core.doEffect(effect, need, times) +执行一个effect操作。该函数目前仅被全局商店的status:xxx+=yyy所调用。 + + +core.setFloorInfo(name, values, floorId, prefix) +设置某层楼的楼层属性。 + + +core.setGlobalAttribute(name, value) +设置一个全局属性,如边框颜色等。 + + +core.setGlobalFlag(name, value) +设置一个全局开关,如enableXXX等。 +如果需要设置一个全局数值如红宝石数值,可以直接简单的修改core.values,因此没有单独列出函数。 + + +core.closeDoor(x, y, id, callback) +执行一个关门事件。如果不是一个合法的门,或者该点不为空地,则会忽略本事件。 + + +core.showImage(code, image, sloc, loc, opacityVal, time, callback) +显示一张图片。code为图片编号,image为图片内容或图片名。 +sloc为[x,y,w,h]形式,表示在原始图片上裁剪的区域,也可直接设为null表示整张图片。 +loc为[x,y,w,h]形式,表示在界面上绘制的位置和大小,w和h可忽略表示使用绘制大小。 +opacityVal为绘制的不透明度,time为淡入时间。 +此函数将创建一个画布,其z-index是100+code,即图片编号为1则是101,编号50则是150。 +请注意,curtain层的z-index是125,UI层的z-index是140;因此可以通过图片编号来调整覆盖关系。 + + +core.hideImage(code, time, callback) +隐藏一张图片。code为图片编号,time为淡出时间。 + + +core.moveImage(code, to, opacityVal, time, callback) +移动一张图片。code为图片编号,to为[x,y]表示目标位置,opacityVal目标不透明度,time为移动时间。 + + +core.showGif(name, x, y) +绘制一张gif图片或取消所有绘制内容。如果name不设置则视为取消。x和y为左上角像素坐标。 + + +core.setVolume(value, time, callback) +设置音量。value为目标音量大小,在0到1之间。time为音量渐变的时间。 + + +core.vibrate(time, callback) +画面震动。time为震动时间。 +请注意,画面震动时间必须是500的倍数,系统也会自动把time调整为上整的500倍数值。 + + +core.eventMoveHero(steps, time, callback) +使用事件移动勇士。time为每步的移动时间。 +steps为移动数组,可以接受'up','down','left','right','forward'和'backward'项。 +使用事件移动勇士将不会触发任何地图上的事件。 + + +core.jumpHero(ex, ey, time, callback) +跳跃勇士。ex和ey为目标点的坐标,可以为null表示原地跳跃。time为总跳跃时间。 + + +core.openShop(shopId, needVisited) +打开一个全局商店。needVisited表示是否需要该商店原本就是启用状态。 +如果该商店对应的实际上是一个全局事件,则会直接插入并执行。 + + +core.disableQuickShop(shopId) +禁用一个全局商店,即把一个商店从启用变成禁用状态。 + + +core.canUseQuickShop(shopId) +当前能否使用某个全局商店,实际被转发到了脚本编辑中。 +如果此函数返回null则表示可以使用,返回一个字符串表示不可以,该字符串表示不可以的原因。 + + +core.setHeroIcon(name, noDraw) +设置勇士的行走图。 +name为行走图名称,noDraw如果为真则不会调用core.drawHero()函数进行刷新。 + + +core.checkLvUp() +检查升级事件。该函数将判定当前是否升级(或连续升级),然后执行升级事件。 + + +core.tryUseItem(itemId) +尝试使用一个道具。 +对于怪物手册和楼传器,将分别调用core.openBook()和core.useFly()函数。 +对于中心对称飞行器,则会调用core.drawCenterFly()函数。 +对于其他的道具,将检查是否拥有,能否使用,并且进行使用。 + + +core.afterUseBomb() +使用炸弹或圣锤后的事件。实际被转发到了脚本编辑中。 +``` + +## icons.js + +icons.js主要是负责素材相关信息,比如某个素材在对应的图片上的位置。 + +```text +core.getClsFromId(id) +根据某个素材的ID获得该素材的cls + + +core.getTilesetOffset(id) +根据某个素材来获得对应的tileset和坐标信息。 +如果该素材不是tileset,则返回null。 +``` + +## items.js + +items.js主要负责一切和道具相关的内容。 + +```text +core.getItemEffect(itemId, itemNum) +即捡即用类的道具获得时的效果。实际对应道具图块属性中的itemEffect框。 + + +core.getItemEffectTip(itemId) +即捡即用类的道具获得时的额外提示,比如“,攻击+100”。 +实际对应道具图块属性中的itemEffectTip框。 + + +core.useItem(itemId, noRoute, callback) +尝试使用一个道具。实际对应道具图块属性中的useItemEffect框。 +此函数也会调用一遍core.canUseItem(),如果无法使用将直接返回。 +noRoute如果为真,则这次使用道具的过程不会被计入录像。 +使用道具完毕后,对于消耗道具将自动扣除,永久道具不会扣除。 + + +core.canUseItem(itemId) +当前能否使用某个道具。 +有些系统道具如破炸和上下楼器等,会在计算出目标点的坐标后存入core.status.event.ui。 +使用道具时将直接从core.status.event.ui调用,不会重新计算。 + + +core.itemCount(itemId) +获得某个道具的个数。 + + +core.hasItem(itemId) +当前是否拥有某个道具。等价于 core.itemCount(itemId) > 0 +请注意,装备上的装备不视为拥有该道具,即core.hasEquip()和core.hasItem()是完全不同的。 + + +core.hasEquip(itemId) +当前是否装备上某个装备。 +请注意,装备上的装备不视为拥有该道具,即core.hasEquip()和core.hasItem()是完全不同的。 + + +core.getEquip(equipType) +获得某个装备位的当前装备。equipType为装备类型,从0开始。 +如果该装备位没有装备则返回null,否则返回当前装备的ID。 + + +core.setItem(itemId, itemNum) +设置某个道具的个数。 + + +core.addItem(itemId, itemNum) +增减某个道具的个数,itemNum可不填默认为1。 + + +core.getEquipTypeByName(name) +根据装备位名称来找到一个空的装备孔,适用于多重装备。 +如果没有一个装备孔是该装备名称,则返回-1。 + + +core.getEquipTypeById(equipId) +获得某个装备的装备类型。 +如果其type写的是装备名(多重装备),则调用core.getEquipTypeByName()函数。 + + +core.canEquip(equipId, hint) +当前能否穿上某个装备。如果hint为真,则不可装备时会气泡提示原因。 + + +core.loadEquip(equipId, callback) +穿上某个装备。 + + +core.unloadEquip(equipType, callback) +脱下某个装备孔的装备。 + + +core.compareEquipment(compareEquipId, beComparedEquipId) +比较两个套装的差异。 +此函数将对所有的勇士属性包括生命魔力攻防魔防金币等进行比较。 +如果存在差异的,将作为一个对象返回其差异内容。 + + +core.quickSaveEquip(index) +保存当前套装。index为保存的套装编号。 + + +core.quickLoadEquip() +读取当前套装。index为读取的套装编号。 +``` + +## loader.js + +loader.js主要负责资源加载相关的内容。 + +```text +core.loadImage(imgName, callback) +从 project/images/ 中加载一张图片。imgName为图片名。 +callback为执行完毕的回调函数,接收(imgName, image)即图片名和图片内容作为参数。 +如果图片不存在或加载失败则会在控制台打出一条错误日志,不会执行回调。 + + +core.loadImages(names, toSave, callback) +从 project/images/ 中加载若干张图片。 +names为一个图片名的列表,toSave为加载并存到的对象。 +callback为全部加载完毕执行的回调。 + + +core.loadOneMusic(name) +从 project/sounds/ 或第三方中加载一个音乐,并存入core.material.bgms中。name为音乐名。 + + +core.loadOneSound(name) +从 project/sounds/ 中加载一个音效,并存入core.material.sounds中。name为音效名。 + + +core.loadBgm(name) +预加载一个bgm并加入缓存列表core.musicStatus.cachedBgms。 +此函数将会检查bgm的缓存,预加载和静音播放。 +如果缓存列表溢出(core.musicStatus.cacheBgmCount)则通过LRU算法选择一个bgm并调用core.freeBgm()。 + + +core.freeBgm(name) +释放一个bgm的内存并移出缓存列表。如果该bgm正在播放则也会立刻停止。 +``` + +## map.js + +maps.js负责一切和地图相关的处理内容,包括如下几个方面: +- 地图的初始化,保存和读取,地图数组的生成 +- 是否可移动或瞬间移动的判定 +- 地图的绘制 +- 获得某个点的图块信息 +- 启用和禁用图块,改变图块 +- 移动/跳跃图块,淡入淡出图块 +- 全局动画控制,动画的绘制 + +```text +// ------ 地图的初始化,保存和读取,地图数组的生成 ------ // + +core.loadFloor(floorId, map) +从楼层或者存档中生成core.status.maps的内容。 +map为存档信息,如果某项在map中不存在则会从core.floors中读取。 + + +core.getNumberById(id) +给定一个图块ID,找到对应的数字。 + + +core.initBlock(x, y, id, addInfo, eventFloor) +给定一个数字,初始化一个图块信息。 +x和y为坐标,id为数字或者可以:t或:f结尾表示初始是启用还是禁用状态。 +addInfo如果为true则会填充上图块的默认信息,比如给怪物添加battle触发器。 +eventFloor如果设置为某个楼层信息,则会填充上该点的自定义或楼层切换事件。 + + +core.compressMap(mapArr, floorId) +压缩地图。mapArr为要压缩的二维数组,floorId为对应的楼层。 +此函数将把mapArr和对应的楼层中的数组进行比较,并只取差异值进行存储。 +通过这种压缩地图的方式,不仅节省了存档空间,还支持了任意修改地图的接档。 + + +core.decompressMap(mapArr, floorId) +解压缩地图。mapArr为压缩后的地图,floorId为对应的楼层。 +此函数返回解压后的二维数组。 + + +core.saveMap(floorId) +将某层楼的数据生成存档所保存的内容。在core.saveData()中被调用。 + + +core.loadMap(data, floorId) +从data中读取楼层数据,并调用core.loadFloor()进行初始化。 + + +core.resizeMap(floorId) +根据某层楼的地图大小来调整大地图的画布大小。floorId可为null表示当前层。 + + +core.getMapArray(floorId, showDisable) +生成某层楼的二维数组。floorId可不填代表当前楼层。 +showDisable若为真,则对于禁用的点会加上:f表示,否则视为0。 + + +core.getMapBlocksObj(floorId, showDisable) +以x,y的形式返回每个点的图块信息。floorId可不填表示当前楼层。 +此函数将返回 {"0,0": {...}, "0,1": {...}} 这样的结构,其中内部为对应点的block信息。 + + +core.getBgMapArray(floorId, noCache) +获得某层楼的背景层的二维数组。floorId可不填表示当前楼层。 +如果noCache为真则重新从剧本中读取而不使用缓存数据。 + + +core.getFgMapArray(floorId, noCache) +获得某层楼的前景层的二维数组。floorId可不填表示当前楼层。 +如果noCache为真则重新从剧本中读取而不使用缓存数据。 + + +core.getBgNumber(x, y, floorId, noCache) +获得某层楼的背景层中某个点的数字。floorId可不填表示当前楼层。 +如果noCache为真则重新从剧本中读取而不使用缓存数据。 +本函数实际等价于 core.getBgMapArray(floorId, noCache)[y][x] + + +core.getBgNumber(x, y, floorId, noCache) +获得某层楼的前景层中某个点的数字。参数和方法同上。 + +// ------ 是否可移动或瞬间移动的判定 ------ // + +core.generateMovableArray(floorId, x, y, direction) +生成全图或某个点的可通行方向数组。floorId为楼层Id,可不填默认为当前点。 +这里的可通行方向数组,指的是["up","down","left","right"]中的一个或多个组成的数组。 + - 如果不设置x和y,则会返回一个三维数组,其中每个点都是一个该点可通行方向的数组。 + - 如果设置了x和y但没有设置direction,则只会返回该点的可通行方向数组, + - 如果设置了x和y以及direction,则会判定direction是否在该点可通行方向数组中,并返回true或false。 +可以使用core.inArray()来判定某个方向是否在可通行方向数组中。 + + +core.canMoveHero(x, y, direction, floorId) +某个点是否可朝某个方向移动。x和y可选,不填或为null则默认为勇士当前点。 +direction可选,不填或为null则默认勇士当前朝向。floorId不填则默认为当前楼层。 +此函数将直接调用 core.generateMovableArray() 进行判定。 + + +core.canMoveDirectly(destX, destY) +当前能否瞬间移动到某个点。 +如果可以瞬移则返回非负数,其值为该次瞬移所少走的步数;如果不能瞬移则返回-1。 + + +core.automaticRoute(destX, destY) +找寻到目标点的一条自动寻路路径。 + +// ------ 绘制地图相关 ------ // + +core.drawBlock(block, animate) +重新绘制一个图块,block为图块信息。 +如果animate不为null则代表是通过全局动画的绘制,其值为当前的帧数。 + + +core.generateGroundPattern(floorId) +生成某个楼层的地板信息。floorId不填默认为当前楼层。 +该函数可被怪物手册、对话框帧动画等地方使用。 core.drawMap(floorId, callback) -重绘某一层的地图。floorId为要绘制楼层的floorId,callback为绘制完毕后的回调函数。 +绘制某层楼的地图。floorId为目标楼层ID,可不填表示当前楼层。 +此函数会将core.status.floorId设置为floorId,并设置core.status.thisMap。 +将依次调用core.drawBg(), core.drawEvents()和core.drawFg()函数,最后绘制勇士和更新地图显伤。 + + +core.drawBg(floorId, ctx) +绘制背景层。floorId为目标楼层ID,可不填表示当前楼层。 +如果ctx不为null,则背景层将绘制在该画布上而不是bg层上(drawThumbnail使用)。 +可以通过复写该函数,调整_drawFloorImages和_drawBgFgMap的顺序来调整背景图块和贴图的遮挡顺序。 + + +core.drawEvents(floorId, blocks, ctx) +绘制事件层。floorId为目标楼层ID,可不填表示当前楼层。 +block表示要绘制的图块列表,可不填使用当前楼层的图块列表。 +如果ctx不为null,则背景层将绘制在该画布上而不是event层上(drawThumbnail使用)。 + + +core.drawFg(floorId, ctx) +绘制前景层。floorId为目标楼层ID,可不填表示当前楼层。 +如果ctx不为null,则背景层将绘制在该画布上而不是fg层上(drawThumbnail使用)。 +可以通过复写该函数,调整_drawFloorImages和_drawBgFgMap的顺序来调整前景图块和贴图的遮挡顺序。 + + +core.drawThumbnail(floorId, blocks, options, toDraw) +绘制一个楼层的缩略图。floorId为目标楼层ID,可不填表示当前楼层。 +block表示要绘制的图块列表,可不填使用当前楼层的图块列表。 +options为绘制选项(可为null),包括: + heroLoc: 勇士位置;heroIcon:勇士图标(默认当前勇士);damage:是否绘制显伤; + flags:当前的flags(在存读档时使用) +toDraw为要绘制到的信息(可为null,或为一个画布名),包括: + ctx:要绘制到的画布(名);x,y:起点横纵坐标(默认0);size:绘制大小(默认416/480); + all:是否绘制全图(默认false);centerX,centerY:截取中心(默认为地图正中心) + +// ------ 获得某个点的图块信息 ------ // + +core.noPass(x, y, floorId) +判定某个点是否有noPass的图块。 + + +core.npcExists(x, y, floorId) +判定某个点是否有NPC的存在。 core.terrainExists(x, y, id, floorId) -检测某个点是否存在(指定的)地形。 -x和y为坐标;id为地形ID,可为null表示任意地形;floorId为楼层ID,可忽略表示当前楼层。 +判定某个点是否有(指定的)地形存在。 +如果id为null,则只要存在terrains即为真,否则还会判定对应点的ID。 + + +core.stairExists(x, y, floorId) +判定某个点是否存在楼梯。 + + +core.nearStair() +判定当前勇士是否在楼梯上或旁边(距离不超过1)。 core.enemyExists(x, y, id, floorId) -检测某个点是否存在(指定的)怪物。 -x和y为坐标;id为怪物ID,可为null表示任意怪物;floorId为楼层ID,可忽略表示当前楼层。 +判定某个点是否有(指定的)怪物存在。 +如果id为null,则只要存在怪物即为真,否则还会判定对应点的怪物ID。 +请注意,如果需要判定某个楼层是否存在怪物请使用core.hasEnemyLeft()函数。 core.getBlock(x, y, floorId, showDisable) -获得某个点的当前图块信息。 -x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 +获得某个点的当前图块信息。x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 showDisable如果为true,则对于禁用的点和事件也会进行返回。 如果该点不存在图块,则返回null。 否则,返回值如下: {"index": xxx, "block": xxx} @@ -221,25 +1446,38 @@ showDisable如果为true,则对于禁用的点和事件也会进行返回。 core.getBlockId(x, y, floorId, showDisable) -获得某个点的图块ID。 -x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 -showDisable如果为true,则对于禁用的点和事件也会进行返回。 -如果该点不存在图块,则返回null,否则返回该点的图块ID。 +获得某个点的图块ID。如果该点不存在图块则返回null。 core.getBlockCls(x, y, floorId, showDisable) -获得某个点的图块cls。 -x和y为坐标;floorId为楼层ID,可忽略或null表示当前楼层。 -showDisable如果为true,则对于禁用的点和事件也会进行返回。 -如果该点不存在图块,则返回null,否则返回该点的图块cls。 +获得某个点的图块类型。如果该点不存在图块则返回null。 +core.getBlockInfo(block) +根据某个的图块信息获得其详细的素材信息。 +如果参数block为字符串,则视为图块ID;如果参数为数字,则视为图块的数字。 +此函数将返回一个非常详尽的素材信息,目前包括如下几项: +number:素材数字;id:素材id;cls:素材类型;image:素材所在的素材图片;animate:素材的帧数。 +posX, posY:素材在该素材图片上的位置;height:素材的高度;faceIds:NPC朝向记录。 + + +core.searchBlock(id, floorId, showDisable) +搜索一个图块出现过的所有位置。id为图块ID,也可以传入图块的数字。 +floorId为要搜索的楼层,可以是一个楼层ID,或者一个楼层数组。如果floorId不填则只搜索当前楼层。 +showDisable如果为真,则对于禁用的图块也会返回。 +此函数将返回一个数组,每一项为一个搜索到的结果: +{"floorId": ..., "index": ..., "block": {...}, "x": ..., "y": ...} +即包含该图块所在的楼层ID,在该楼层的blocks数组的索引,图块内容,和横纵坐标。 + + +// ------ 启用和禁用图块,改变图块 ------ // + core.showBlock(x, y, floorId) -将某个点从禁用变成启用状态。 +将某个点从禁用变成启用状态。floorId可不填或null表示当前楼层。 core.hideBlock(x, y, floorId) -将某个点从启用变成禁用状态,但不会对其进行删除。 +将某个点从启用变成禁用状态,但不会对其进行删除。floorId可不填或null表示当前楼层。 此函数不会实际将该块从地图中进行删除,而是将该点设置为禁用,以供以后可能的启用事件。 @@ -249,72 +1487,521 @@ core.removeBlock(x, y, floorId) 如果存在自定义事件,则简单的禁用它,以供以后可能的启用事件。 +core.removeBlockById(index, floorId) +根据索引从地图的block数组中尽可能删除一个图块。floorId可不填或null表示当前楼层。 + + +core.removeBlockByIds(floorId, ids) +根据索引数组从地图的block数组中尽可能删除一系列图块。floorId可不填或null表示当前楼层。 + + +core.canRemoveBlock(block, floorId) +判定当前能否完全删除某个图块。floorId可不填或null表示当前楼层。 +如果该点存在自定义事件,或者是重生怪,则不可进行删除。 + + +core.showBgFgMap(name, loc, floorId, callback) +显示某层楼中某个背景/前景层的图块。name只能为'bg'或'fg'表示背景或前景层。 +loc为该点坐标,floorId可不填默认为当前楼层。callback为执行完毕的回调。 + + +core.hideBgFgMap(name, loc, floorId, callback) +隐藏某层楼中某个背景/前景层的图块。name只能为'bg'或'fg'表示背景或前景层。 +loc为该点坐标,floorId可不填默认为当前楼层。callback为执行完毕的回调。 + + +core.showFloorImage(loc, floorId, callback) +显示某层楼中的某个楼层贴图。loc为该贴图的左上角坐标。floorId可省略表示当前楼层。 + + +core.hideFloorImage(loc, floorId, callback) +隐藏某层楼中的某个楼层贴图。loc为该贴图的左上角坐标。floorId可省略表示当前楼层。 + + core.setBlock(number, x, y, floorId) -改变图块。number为要改变到的图块数字,x和y为坐标,floorId为楼层ID,可忽略表示当前楼层。 +改变某个楼层的某个图块。 +number为要改变到的数字,也可以传入图块id(将调用core.getNumberById()来获得数字)。 +x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 -core.useItem(itemId, noRoute, callback) -尝试使用某个道具。itemId为道具ID,noRoute如果为真则该道具的使用不计入录像。 -callback为成功或失败后的回调。 +core.replaceBlock(fromNumber, toNumber, floorId) +将某个或某些楼层中的所有某个图块替换成另一个图块 +fromNumber和toNumber为要被替换和替换到的数字。 +floorId可为某个楼层ID,或者一个楼层数组;如果不填只视为当前楼层。 +值得注意的是,使用此函数转了的点上的自定义事件可能无法被执行。 +如有需要,再对那些存在事件的点执行core.setBlock()即可 -core.canUseItem(itemId) -返回当前能否使用某个道具。 +core.setBgFgBlock(name, number, x, y, floorId) +设置前景/背景层的某个图块。name只能为'bg'或'fg'表示前景或背景层。 +number为要设置到的图块数字,x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 -core.loadEquip(itemId, callback) -装备上某个装备。itemId为装备的ID,callback为成功或失败后的回调。 +core.resetMap(floorId) +重置某层或若干层的地图和楼层属性。 +floorId可为某个楼层ID,或者一个楼层数组(同时重置若干层);如果不填则只重置当前楼层。 + +// ------ 移动/跳跃图块,淡入淡出图块 ------ // + +core.moveBlock(x, y, steps, time, keep, callback) +移动一个图块,x和y为图块的坐标。 +steps为移动的数组,每一项只能是"up","down","left","right"之一。 +time为每一步的移动时间,不填默认为500ms。 +如果keep为真,则在移动完毕后将自动调用一个setBlock事件,改变目标点的图块(即不消失), +否则会按照time时间来淡出消失。callback会执行完毕后的回调。 -core.unloadEquip(equipType, callback) -卸下某个部位的装备。equipType为装备类型,从0开始;callback为成功或失败后的回调。 +core.jumpBlock(sx, sy, ex, ey, time, keep, callback) +跳跃一个图块,sx和sy为图块的坐标,ex和ey为目标坐标。time为整个跳跃过程中的全程用时,不填默认500。 +如果keep为真,则在移动完毕后将自动调用一个setBlock事件,改变目标点的图块(即不消失), +否则会按照time时间来淡出消失。callback会执行完毕后的回调。 -core.getNextItem() -轻按。 +core.animateBlock(loc, type, time, callback) +淡入/淡出一个或多个图块。 +loc为一个图块坐标,或者一个二维数组表示一系列图块坐标(将同时显示和隐藏)。 +type只能为'show'或'hide'表示是淡入但是淡出。time为动画时间,callback为执行完毕的回调。 + +// ------ 全局动画控制,动画的绘制 ------ // + +core.addGlobalAnimate(block) +添加一个全局帧动画。 -core.drawTip(text, itemIcon) -在左上角绘制一段提示信息,2秒后消失。itemIcon为道具图标的索引。 +core.removeGlobalAnimate(x, y, name) +删除一个或全部的全局帧动画。name可为'bg',null或'fg'表示某个图层。 +x和y如果为null,则会删除全部的全局帧动画,否则只会删除该点的该层的帧动画。 -core.drawText(contents, callback) [异步] -绘制一段文字。 -不推荐使用此函数,尽量使用core.insertAction(contents)来显示剧情文本。 +core.drawBoxAnimate() +绘制UI层的box动画,如怪物手册和对话框中的帧动画等。 +core.drawAnimate(name, x, y, callback) +绘制一个动画。name为动画名,x和y为绘制的基准坐标,callback为绘制完毕的回调函数。 +此函数将播放动画音效,并异步开始绘制该动画。 +此函数会返回一个动画id,可以通过core.stopAnimate()立刻停止该动画的播放。 + + +core.stopAnimate(id, doCallback) +立刻停止某个动画的播放。id为上面core.drawAnimate的返回值。 +如果doCallback为真,则会执行该动画所对应的回调函数。 +``` + +## ui.js + +ui.js负责一切UI界面的绘制。主要包括三个部分: +- 设置某个画布的属性的相关API +- 具体的某个UI界面的绘制 +- 动态创建画布相关的API + +```text +// ------ 设置某个画布的属性的相关API ------// +这系列函数的name一般都是画布名,可以是系统画布或动态创建的画布。 +但也同时也允许直接传画布的context本身,将返回自身。 + + +core.getContextByName(name) +根据画布名找到一个画布的context;支持系统画布和自定义画布。 +如果不存在画布此函数返回null。 +该参数也可以直接传画布的context自身,则返回自己。 + + +core.clearMap(name) +清空某个画布图层。 +该函数的name也可以是'all',若为'all'则为清空所有系统画布。 + + +core.fillText(name, text, x, y, style, font) +在某个画布上绘制一段文字。 +text为要绘制的文本,x,y为要绘制的坐标,style可选为绘制的样式,font可选为绘制的字体。(下同) +请注意textAlign和textBaseline将决定绘制的左右对齐和上下对齐方式。 +具体可详见core.setTextAlign()和core.setTextBaseline()函数。 + + +core.fillBoldText(name, text, x, y, style, font) +在某个画布上绘制一个描黑边的文字。 + + +core.fillRect(name, x, y, width, height, style) +绘制一个矩形。style可选为绘制样式。如果设置将调用core.setFillStyle()。(下同) + + +core.strokeRect(name, x, y, width, height, style, lineWidth) +绘制一个矩形的边框。style可选为绘制样式,如果设置将调用core.setStrokeStyle()。 +lineWidth如果设置将调用core.setLineWidth()。(下同) + + +core.drawLine(name, x1, y1, x2, y2, style, lineWidth) +绘制一条线。 + + +core.drawArrow(name, x1, y1, x2, y2, style, lineWidth) +绘制一个箭头。 + + +core.setFont(name, font) / core.setLineWidth(name, lineWidth) +设置一个画布的字体/线宽。 + + +core.setAlpha(name, font) / core.setOpacity(name, font) +设置一个画布的绘制不透明度和画布本身的不透明度。 +两者区别如下: + - setAlpha是设置"接下来绘制的内容的不透明度",不会对已经绘制的内容产生影响。 + > 比如setAlpha('ui', 0.5)则会在接下来的绘制中使用0.5的不透明度。 + - setOpacity是设置"画布本身的不透明度",已经绘制的内容也会产生影响。 + > 比如我已经在UI层绘制了一段文字,再setOpacity则也会让已经绘制的文字变得透明。 +尽量不要对系统画布使用setOpacity(因为会对已经绘制的内容产生影响),自定义创建的画布则不受此限制。 + + +core.setFillStyle(name, style) / core.setStrokeStyle(name, style) +设置一个画布的填充样式/描边样式。 + + +core.setTextAlign(name, align) +设置一个画布的文字横向对齐模式,这里的align只能为'left', 'right'和'center'。 +默认为'left'。 + + +core.setTextBaseline(name, baseline) +设置一个画布的纵向对齐模式。有关textBaseline的说明可参见如下资料: +http://www.runoob.com/tags/canvas-textbaseline.html +默认值是'alphabetic'。 +请注意,系统画布在绘制前都是没有设置过textBaseline的,都是按照alphabetic进行坐标绘制。 +因此,如果你修改了系统画布的textBaseline来绘图,记得再绘制完毕后修改回来。 + + +core.calWidth(name, text, font) +计算一段文字在某个画布上的绘制宽度,此函数其实是ctx.measureText()的包装。 +font可选,如果存在则会先设置该画布上的字体。 +此函数不会对文字进行换行,如需换行版本请使用core.splitLines()函数。 + + +core.splitLines(name, text, maxWidth, font) +计算一段文字在某画布上换行分割的结果。 +text为文字内容,maxWidth为最大宽度,可为null表示不自动换行。 +font可选,如果存在则会先设置该画布上的字体。 +此函数将返回一个换行完毕的文本列表。 + + +core.drawImage(name, image, x, y, w, h, x1, y1, w1, h1) +在一张画布上绘制一张图片,此函数其实是ctx.drawImage()的包装。 +可以查看下面的文档以了解各项参数的信息: +http://www.w3school.com.cn/html5/canvas_drawimage.asp +这里的image允许传一个图片,画布。也允许传递图片名,将从你导入的图片中获取图片内容。 + +// ------ 具体的某个UI界面的绘制 ------ // core.closePanel() -结束一切事件和绘制,关闭UI窗口,返回游戏进程。 +结束一切事件和UI绘制,关闭UI窗口,返回游戏。 +此函数将以此调用core.clearUI(),core.unlockControl(),并清空core.status.event里面的内容。 -core.replaceText(text) -将一段文字中的${}进行计算并替换。 +core.clearUI() +重置UI窗口。此函数将清掉所有的UI帧动画和光标,清空UI画布,并将alpha设为1。 -core.calValue(value, prefix, need, times) -计算表达式的实际值。这个函数可以传入status:atk等这样的参数。 +core.drawTip(text, id) +在左上角以气泡的形式绘制一段提示。 +text为文字内容,仅支持${}的表达式计算,不支持换行和变色。 +id可选,为同时绘制的图标ID,如果不为null则会同时绘制该图标(仅对32x32的素材有效)。 + + +core.drawText(content, callback) +绘制一段文字。contents为一个字符串或一个字符串数组,callback为全部绘制完毕的回调。 +支持所有的文字效果(如\n,${},\r,\\i等),也支持\t和\b的语法。 +如果当前在事件处理中或录像回放中,则会自动转成core.insertAction处理。 +不建议使用该函数,如有绘制文字的需求请尽量使用core.insertAction()插入剧情文本事件。 + + +core.drawWindowSelector(background, x, y, w, h) +绘制一个WindowSkin的闪烁光标。background可为winskin的图片名或图片本身。 +x,y,w,h为绘制的左上角位置和宽高,都是像素为单位。 + + +core.drawWindowSkin(background, ctx, x, y, w, h, direction, px, py) +绘制一个WindowSkin。background可为winskin的图片名或图片本身。 +ctx为画布名或画布本身,x,y,w,h为左上角位置和宽高,都是像素为单位。 +direction, px, py可选,如果设置则会绘制一个对话框箭头。 + + +core.drawBackground(left, top, right, bottom, posInfo) +绘制一个背景图。此函数将使用你在【剧情文本设置】中设置的背景,即用纯色或WindowSkin来绘制。 +left, top, right, bottom为你要绘制的左上角和右下角的坐标。 +posInfo如果不为null则是一个含position, px和py的对象,表示一个对话框箭头的绘制。 + + +core.drawTextContent(ctx, content, config) +根据配置在某个画布上绘制一段文字。此函数会被core.drawTextBox()所调用。 +ctx为画布名或画布本身,如果不设置则会忽略该函数。 +content为要绘制的文字内容,支持所有的文字效果(如\n,${},\r,\\i等),但不支持支持\t和\b的语法。 +config为绘制的配置项,目前可以包括如下几项: + - left, top:在该画布上绘制的左上角像素位置,不设置默认为(0,0)。 + > 该函数绘制时会将textBaseline设置为'top',因此只需要考虑第一个字的左上角位置。 + - maxWidth:单行最大宽度,超过此宽度将自动换行,不设置不会自动换行。 + - color:默认颜色,为#XXXXXX形式。如果不设置则使用剧情文本设置中的正文颜色。 + - bold:是否粗体。如果不设置默认为false。 + - align:文字对齐方式,仅在maxWidth设置时有效,默认为'left'。 + - fontSize:字体大小,如果不设置则使用剧情文本设置中的正文字体大小。 + - lineHeight:绘制的行距值,如果不设置则使用fontSize*1.3(即1.3被行距)。 + - time:打字机效果。如果此项不为0,则会用打字机效果逐个字进行绘制,并设置core.status.event.interval定时器。 + + +core.drawTextBox(content, showAll) +绘制一个对话框。content为一个字符串或一个字符串数组。 +支持所有的文字效果(如\n,${},\r,\\i等),也支持\t和\b的语法。 +该函数将使用用户在剧情文本设置中的配置项进行绘制。 +实际执行时,会计算文本框宽度并绘制背景,绘制标题和头像,再调用core.drawTextContent()绘制正文内容。 +showAll可选,如果为true则不会使用打字机效果而全部显示,主要用于打字机效果的点击显示全部。 + + +core.drawScrollText(content, time, lineHeight, callback) +绘制一个滚动字幕。content为绘制内容,time为总滚动时间(默认为5000),lineHeight为行距比例(默认为1.4)。 +滚动字幕将绘制在UI上,支持所有的文字效果(如\n,${},\r,\\i等),但不支持\t和\b效果。 +可以通过剧情文本设置中的align控制是否居中绘制,offset控制其距离左边的偏移量。 + + +core.textImage(content, lineHeight) +将文本图片化。content为绘制内容,lineHeight为行距比例(默认为1.4)。 +可以通过剧情文本设置中的align控制是否居中绘制。 +此函数将返回一个临时画布的canvas,供转绘到其他画布上使用。 + + +core.drawChoices(content, choices) +绘制一个选项框。 +content可选,为选项上方的提示文字,支持所有的文字效果(如\n,${},\r,\\i等),也支持\t效果。 +choices必选,为要绘制的选项内容,是一个列表。其中的每一项: + - 可以是一个字符串,表示选项文字,将使用剧情文本设置中的正文颜色来绘制,仅支持${}表达式计算。 + - 或者是一个包含text, color和icon的对象。 + > text必选,为要绘制的文字内容,仅支持${}的表达式计算, + > color为要绘制的选项颜色,可以是#XXXXXX的格式或RGBA数组。不设置则使用正文颜色。 + > icon为要绘制的图标ID,支持使用素材的ID,或者系统图标。 + + +core.drawConfirmBox(text, yesCallback, noCallback) +绘制一个确认框。text为确认文字,支持\n换行(但不支持自动换行)和${}表达式计算。 +yesCallback和noCallback分别为确定和取消的回调函数。 +可以在调用此函数前设置core.status.event.selection来控制默认的选中项(0确定1取消)。 +此函数和core.myconfirm的区别主要在于: + - 此函数会清掉UI层原有的绘制信息,core.myconfirm不会清除。 + - 此函数可以用键盘进行操作,core.myconfirm必须用鼠标点击。 +另外请注意:本函数和事件流的执行不兼容,选择也不会进录像。 +如果需要在事件流中调用请使用提供选择项。 + + +core.drawWaiting(text) +绘制一个等待界面,一般用于同步存档之类的操作。 +调用此函数后,需要再调用core.closePanel()才会恢复游戏。 + + +core.drawPagination(page, totalPage, y) +绘制一个分页。y如果不设置则绘制在最后一行。 + + +core.drawBook(index) / core.drawBookDetail(index) +绘制怪物手册,绘制怪物的详细信息。 + + +core.drawFly(page) / core.drawCenterFly() / core.drawShop(shopId) +绘制楼传页面,中心对称飞行器,绘制商店 + + +core.drawToolbox(index) / core.drawEquipbox(index) / core.drawKeyBoard() +绘制道具栏,绘制装备栏,绘制虚拟键盘。 + + +core.drawMaps(index, x, y) / core.drawSLPanel(index, refresh) +绘制浏览地图,绘制存读档界面。 + + +core.drawStatusBar() +自定义绘制状态栏,仅在状态栏canvas化开启时有效,实际被转发到了脚本编辑中。 + + +core.drawStatistics() +绘制数据统计。将从脚本编辑中获得要统计的数据列表,再遍历所有地图进行统计。 + + +core.drawAbout() / core.drawPaint() / core.drawHelp() +绘制关于界面,绘图模式,帮助界面。 + +// ------ 动态创建画布相关的API ------ // + + +core.ui.createCanvas(name, x, y, width, height, z) +动态创建一个自定义画布。name为要创建的画布名,如果已存在则会直接取用当前存在的。 +x,y为创建的画布相对窗口左上角的像素坐标,width,height为创建的长宽。 +z值为创建的纵向高度(关系到画布之间的覆盖),z值高的将覆盖z值低的;系统画布的z值可在个性化中查看。 +返回创建的画布的context,也可以通过core.dymCanvas[name]或core.getContextByName获得。 + + +core.ui.relocateCanvas(name, x, y) +重新定位一个自定义画布。x和y为画布的左上角坐标。 + + +core.ui.resizeCanvas(name, width, height) +重新设置一个自定义画布的大小。width和height为新设置的宽高。此操作会清空画布。 + + +core.ui.deleteCanvas(name) +删除一个自定义画布。 + + +core.ui.deleteAllCanvas() +删除所有的自定义画布。 +``` + +## utils.js + +utils.js是一个工具函数库,里面有各个样板中使用到的工具函数。 + +```text +core.replayText(text, need, times) +将一段文字中的${}(表达式)进行替换。need和time一般可以直接忽略。 + + +core.calValue(value, prefix, need, time) +计算一个表达式的值,支持status:xxx等的计算。 +prefix为前缀(switch:xxx的独立开关使用),need和time一般可以直接忽略。 + + +core.unshift(a, b) / core.push(a, b) +将b插入到列表a之前或之后。b可以是一个元素或者一个数组。 + + +core.decompress(value) +解压缩一个字符串并进行JSON解析。value可能会被LZString或LZW算法进行压缩。 +返回解压后的JSON格式内容,如果无法解压则返回null。 + + +core.setLocalStorage(key, value) +将一个键值对存入localStorage中,会立刻返回结果。 +此函数会自动给key加上它的name作为前缀,以便于不同塔之间的区分。 +value为要存储的内容,存储前会被JSON转成字符串形式,并LZW算法进行压缩。 +如果value为null,则实际会调用core.removeLocalStorage()函数清除该key。 +localStorage为浏览器的默认存储,和存档无关,只有5M的空间大小。 +此函数立刻返回true或false,如果为false则代表存储失败,一般指的是空间满了。 core.getLocalStorage(key, defaultValue) -从localStorage中获得某个数据(已被parse);如果对应的key不存在则返回defaultValue。 +从localStorage中获得一个键的值,会立刻返回结果。 +此函数会自动给key加上它的name作为前缀,以便于不同塔之间的区分。 +如果对应的键值不存在或为null,则会返回defaultValue。 +返回的结果会被调用core.decompress进行解压缩和JSON解析。 + + +core.removeLocalStorage(key) +从localStorage中删除一个键的值,会立刻返回。 +此函数会自动给key加上它的name作为前缀,以便于不同塔之间的区分。 + + +core.setLocalForage(key, value, successCallback, errorCallback) +将一个键值对存入localForage中,异步返回结果。 +如果用户没有开启【新版存档】开关,则仍然会使用core.setLocalStorage()。 +localForage为一个开源库,项目地址 https://github.com/localForage/localForage +一般存入的是indexedDB,这是一个浏览器的自带数据库,没有空间限制。 +successCallback和errorCallback均可选,表示该次存储成功或失败的回调, +此函数为异步的,只能通过回调函数来获得存储的成功或失败信息。 core.getLocalForage(key, defaultValue, successCallback, errorCallback) -从localForage中获得某个数据(已被parse),如果对应的key不存在则返回defaultValue。 -如果成功则通过successCallback回调,失败则通过errorCallback回调。 - - -core.hasSave(index) -判定当前某个存档位是否存在存档,返回true/false。 -index为存档编号,0代表自动存档,大于0则为正常的存档位。 +从localForage中获得一个键的值,异步返回结果。 +如果对应的键值不存在,则会使用defaultValue。 +如果获得成功,则会将core.decompress后的结果传入successCallback回调函数执行。 +errorCallback可选,如果失败,则会将错误信息传入errorCallback()。 +此函数是异步的,只能通过回调函数来获得读取的结果或错误信息。 core.clone(data) -深拷贝某个对象。 +深拷贝一个对象。有关浅拷贝,深拷贝,基本类型和引用类型等相关知识可参见: +https://zhuanlan.zhihu.com/p/26282765 -core.isset(x) -测试x是否不为null,不为undefined也不为NaN。 +core.splitImage(image, width, height) +等比例切分一张图片。width和height为每张子图片的宽高。 +请确保原始图片的宽度和高度都是是width和height的倍数。 +此函数将返回一个一维数组,每一项都是一张切分好的图片,横向再纵向排列。 +例如4x3的图片按1x1切分得到一个长度为12的数组,按如下方式进行排列: +0 1 2 3 +4 5 6 7 +8 9 10 11 +可以将很多需要的图片拼在一张大图上,然后在插件的_afterLoadResources中切分。 +切分好的图片再存入core.material.images.images中,这样可以少很多IO请求。 + + +core.formatDate(date) / core.formatDate2(date) / core.formatTime(time) +格式化日期和时间。 + + +core.setTwoDigits(x) +将x变成两位数。其实就是 parseInt(x) < 10 ? "0" + x : x + + +core.formatBigNumber(x, onMap) +大数据格式化。x为要格式化的内容,如果不合法会返回???。 +onMap标记是否在地图上调用,如果为真则尝试格式化成六位,否则五位。 + + +core.arrayToRGB(color) / core.arrayToRGBA(color) +将一个颜色数组,例如[255,0,0,1]转成#FF0000或rgba(255,0,0,1)的形式。 + + +core.encodeRoute(route) +录像压缩和解压缩。route为要压缩的路线数组。 +此函数将尽可能对录像进行压缩。对于无法识别的项目(比如自己添加的),将不压缩而原样放入。 +例如,["up","up","left","move:3:5","test:2333","getNext","item:bomb","down"] +将被压缩成"U2LM3:5(test:2333)GIbomb:D"。 +自己添加的录像项只能由数字、大小写、下划线线、冒号等符号组成,否则无法正常压缩和解压缩。 +对于自定义内容(比如中文文本或数组)请使用JSON.stringify再core.encodeBase64处理。 +压缩的结果将再次进行LZString.compressToBase64()的压缩以进一步节省空间。 + + +core.decodeRoute(route) +解压缩一个录像,返回解压完毕的路线数组。 + + +core.isset(v) +判定v是不是null, undefined或NaN。 +请尽量避免使用此函数,而是直接判定 v == null (请注意 null==undefined !) + + +core.subarray(a, b) +判定数组b是不是数组a的一个前缀子数组。 +如果是,则返回a中除去b后的剩余数组,否则返回null。 + + +core.inArray(array, element) +判定array是不是一个数组,以及element是否在该数组中。 + + +core.clamp(x, a, b) +将x限定在[a,b]区间内。 + + +core.getCookie(name) +获得一个cookie值,如果不存在该cookie则返回null。 + + +core.setStatusBarInnerHTML(name, value, css) +设置一个状态栏的innerHTML。此函数会自动设置状态栏的文字放缩和斜体等效果。 +name为状态栏的名称,如atk, def等。需要是core.statusBar中的一个合法项。 +value为要设置到的数值,如果是数字则会先core.formatBigNumber()进行格式化。 +css可选,为增添的额外css内容,比如可以设定颜色等。 + + +core.strlen(str) +计算某个字符串的实际长度。每个字符的长度,ASCII码视为1,中文等视为2。 + + +core.reverseDirection(direction) +翻转方向,即"up"转成"down", "left"转成"right"等。 + + +core.encodeBase64(str) / core.decodeBase64(str) +将字符串进行base64加密或解密。 + + +core.convertBase(str, fromBase, toBase) +任意进制转换。此函数可能执行的非常慢,慎用。 core.rand(num) @@ -328,362 +2015,70 @@ core.rand2(num) num如果设置大于0,则生成一个[0, num-1]之间的数;否则生成一个0到2147483647之间的整数。 此函数使用了系统的Math.random()函数,支持SL大法。 但是,此函数会将生成的随机数值存入录像,因此如果调用次数太多则会导致录像文件过大。 +对于需要大量生成随机数,但又想使用真随机支持SL大法的(例如随机生成地图等),可以采用如下方式: + var x = core.rand2(100); for (var i = 0; i < x; i++) core.rand() +即先生成一个真随机数,根据该数来推进伪随机的种子,这样就可以放心调用core.rand()啦。 -core.restart() [异步] -返回标题界面。 +core.readFile(success, error) +读取一个本地文件内容。success和error分别为读取成功或失败的回调函数。 +iOS平台暂不支持读取文件操作。 -========== core.actions.XXX 和游戏控制相关的函数 ========== -actions.js主要用来进行用户交互行为的处理。 -所有用户行为,比如按键、点击、滑动等等,都会被此文件接收并进行操作。 +core.readFileContent(content) +读取到的文件内容。此函数会被APP等调用,来传递文件的具体内容。 -========== core.control.XXX 和游戏控制相关的函数 ========== -control.js主要用来进行游戏控制,比如行走控制、自动寻路、存读档等等游戏核心内容。 +core.download(filename, content) +生成一个文件并下载。filename为文件名,content为具体的文件内容。 +iOS平台暂不支持下载文件操作。 -core.control.setGameCanvasTranslate(canvasId, x, y) -设置大地图的偏移量 +core.copy(data) +将一段内容拷贝到剪切板。 -core.control.updateViewport() -更新大地图的可见区域 +core.myconfirm(hint, yesCallback, noCallback) +弹窗绘制一段提示信息并让用户确认。hint为提示信息。 +yesCallback和noCallback分别为确定和取消的回调函数。 +此函数和core.drawConfirmBox的区别主要在于: + - drawConfirmBox会清掉UI层原有的绘制信息,此函数不会清除。 + - drawConfirmBox可以用键盘进行操作,此函数必须用鼠标点击。 +另外请注意:本函数的选择也不会进录像,一般用于全局的提示。 +如果需要在事件流中调用请使用显示确认框或显示选择项。 -core.control.gatherFollowers() -立刻聚集所有的跟随者 +core.myprompt(hint, value, callback) +弹窗让用户输入一段内容。hint为提示信息,value为框内的默认填写内容。 +callback为用户点击确认或取消后的回调。 +如果用户点击了确认,则会把框内的内容(可能是空串)传递给callback,否则把null传递给callback。 -core.control.replay() -回放下一个操作 +core.showWithAnimate(obj, speed, callback) / core.hideWithAnimate(obj, speed, callback) +动画淡入或淡出一个对象。 -========== core.enemys.XXX 和怪物相关的函数 ========== -enemys.js主要用来进行怪物相关的内容,比如怪物的特殊属性,伤害和临界计算等。 +core.consoleOpened() +检测当前的控制台是否处于开启状态。仅在全塔属性中的检查控制台开关开启时有效。 +此函数有可能会存在误伤行为,即没开过控制台仍认为开启过。 -core.enemys.hasSpecial(special, test) -测试怪物是否含有某个特殊属性。 -常见用法: core.enemys.hasSpecial(monster.special, 3) ## 测试是否拥有坚固 +core.hashCode(obj) +计算一个对象的哈希值。 -core.enemys.getSpecialText(enemyId) -返回一个列表,包含该怪物ID对应的所有特殊属性。 +core.same(a, b) +判定a和b是否相同,包括类型相同和值相同。 +如果a和b都是数组,则会递归依次比较数组中的值;如果都是对象亦然。 -core.enemys.getSpecialHint(enemy, special) -获得怪物某个(或全部)特殊属性的文字说明。 - -core.enemys.canBattle(enemyId, x, y, floorId) -返回当前能否战胜某个怪物。 -后面三个参数是怪物坐标和楼层。 - - -core.enemys.getDamage(enemyId, x, y, floorId) -返回当前对某个怪物的战斗伤害。如果无法战斗,返回null。 -后面三个参数是怪物坐标和楼层。 - - -core.enemys.getExtraDamage(enemyId) -返回某个怪物会对勇士造成的额外伤害(不可被魔防抵消),例如仇恨、固伤等等。 - - -core.enemys.nextCriticals(enemyId, number, x, y, floorId) -返回一个列表,为接下来number(可忽略,默认为1)个该怪物的临界值和临界减伤。 -列表每一项类似 [x,y] 表示临界值为x,且临界减伤为y。 -如果无临界值,则返回空列表。 - - -core.enemys.getDefDamage(enemyId, k, x, y, floorId) -获得k(可忽略,默认为1)防减伤值。 - - -core.enemys.getDamageInfo(enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) -获得实际战斗信息,比如伤害,回合数,每回合伤害等等。 -此函数是实际战斗过程的计算。 - - -core.enemys.calDamage(enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) -获得在某个勇士属性下怪物伤害;实际返回的是上面getDamageInfo中伤害的数值。 - - -core.enemys.getCurrentEnemys(floorId) -获得某一层楼剩余所有怪物的信息(供怪物手册使用) - - -========== core.events.XXX 和事件相关的函数 ========== -events.js主要用来进行事件处理,比如自定义事件,以及某些条件下可能会被触发的事件。 -大多数事件API都在脚本编辑中存在,这里只列出部分比较重要的脚本编辑中不存在的API。 - - -core.events.gameOver(ending, fromReplay) -游戏结束并上传的事件。 -该函数将提问是否上传和是否下载录像,并返回标题界面。 - - -core.events.doEvents(list, x, y, callback) [异步] -开始执行某个事件。 -请不要执行此函数,尽量使用 core.insertAction(list, x, y, callback) 来开始执行一段事件。 - - -core.events.doAction() -执行下一个事件。此函数中将对所有自定义事件类型分别处理。 - - -core.events.getCommonEvent(name) -根据名称获得一个公共事件;如果不存在对应的公共事件则返回null。 - - -core.events.openShop(shopId, needVisited) [异步] -打开一个全局商店。needVisited表示是否需要该商店已被打开过。 - - -core.events.disableQuickShop(shopId) -禁用一个全局商店 - - -core.events.canUseQuickShop(shopId) -当前能否使用某个快捷商店 - - -core.events.setHeroIcon(name) -设置勇士行走图 - - -========== core.items.XXX 和道具相关的函数 ========== -items.js将处理和道具相关的内容,比如道具的使用,获取和删除等等。 - - -core.items.compareEquipment(equipId1, equipId2) -比较两个装备的属性变化值 - - -========== core.loader.XXX 和游戏加载相关的函数 ========== -loader.js将主要用来进行资源的加载,比如加载音乐、图片、动画等等。 - - -========== core.maps.XXX 和地图处理相关的函数 ========== -maps.js主要用来进行地图相关的的操作。包括绘制地图,获取地图上的点等等。 - - -core.maps.getNumberById(id) -根据ID来获得对应的数字。如果该ID不存在对应的数字则返回0。 - - -core.maps.canMoveHero(x,y,direction,floorId) -判断能否前往某个方向。x,y为坐标,可忽略为当前点;direction为方向,可忽略为当前方向。 -floorId为楼层ID,可忽略为当前楼层。 - - -core.maps.canMoveDirectly(destX, destY) -判断当前能否瞬间移动到某个点。 -该函数如果返回0则不可瞬间移动,大于0则可以瞬间移动,且返回值是跨度(即少走的步数)。 - - -core.maps.removeBlockById(index, floorId) -根据索引删除或禁用某块。 - - -core.maps.removeBlockByIds(floorId, ids) -根据索引删除或禁用若干块。 - - -core.maps.drawAnimate(name, x, y, callback) -播放一段动画,name为动画名(需在全塔属性注册),x和y为坐标(0-12之间),callback可选,为播放完毕的回调函数。 -播放过程是异步的,如需等待播放完毕请使用insertAction插入一条type:waitAsync事件。 -此函数将随机返回一个数字id,为此异步动画的唯一标识符。 - - -core.maps.stopAnimate(id, doCallback) -立刻停止一个异步动画。 -id为该动画的唯一标识符(由drawAnimate函数返回),doCallback可选,若为true则会执行该动画所绑定的回调函数。 - - -========== core.ui.XXX 和对话框绘制相关的函数 ========== -ui.js主要用来进行UI窗口的绘制,比如对话框、怪物手册、楼传器、存读档界面等等。 - - -core.ui.getContextByName(canvas) -根据画布名找到一个画布的context;支持系统画布和自定义画布。如果不存在画布返回null。 -也可以传画布的context自身,则返回自己。 - - -core.clearMap(name) -清空某个画布图层。 -name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。(下同) -如果name也可以是'all',若为all则为清空所有系统画布。 - - -core.ui.fillText(name, text, x, y, style, font) -在某个画布上绘制一段文字。 -text为要绘制的文本,x,y为要绘制的坐标,style可选为绘制的样式,font可选为绘制的字体。(下同) - - -core.ui.fillBoldText(name, text, x, y, style, font) -在某个画布上绘制一个描黑边的文字。 - - -core.ui.fillRect(name, x, y, width, height, style) -绘制一个矩形。style可选为绘制样式。 - - -core.ui.strokeRect(name, x, y, width, height, style) -绘制一个矩形的边框。 - - -core.ui.drawLine(name, x1, y1, x2, y2, style, lineWidth) -绘制一条线。lineWidth可选为线宽。 - - -core.ui.drawArrow(name, x1, y1, x2, y2, style, lineWidth) -绘制一个箭头。 - - -core.ui.setFont(name, font) / core.ui.setLineWidth(name, lineWidth) -设置一个画布的字体/线宽。 - - -core.ui.setAlpha(name, font) / core.ui.setOpacity(name, font) -设置一个画布的绘制不透明度和画布本身的不透明度。 -两者区别如下: - - setAlpha是设置"接下来绘制的内容的不透明度",不会对已经绘制的内容产生影响。比如setAlpha('ui', 0.5)则会在接下来的绘制中使用0.5的不透明度。 - - setOpacity是设置"画布本身的不透明度",已经绘制的内容也会产生影响。比如我已经在UI层绘制了一段文字,再setOpacity则也会看起来变得透明。 -尽量不要对系统画布使用setOpacity(因为会对已经绘制的内容产生影响),自定义创建的画布则不受此限制。 - - -core.ui.setFillStyle(name, style) / core.ui.setStrokeStyle(name, style) -设置一个画布的填充样式/描边样式。 - - -core.ui.setTextAlign(name, align) -设置一个画布的文字对齐模式。 - - -core.ui.calWidth(name, text, font) -计算一段文字在画布上的绘制宽度 -font可选,如果存在则会先设置该画布上的字体。 - - -core.ui.drawImage(name, image, x, y, w, h, x1, y1, w1, h1) -在一个画布上绘制图片。 -name为画布名,可以是系统画布之一,也可以是任意自定义动态创建的画布名;还可以直接传画布的context本身。 -image为要绘制的图片,可以是一个全塔属性中定义的图片名(会从images中去获取),图片本身,或者一个画布。 -后面的8个坐标参数与canvas的drawImage的八个参数完全相同。 -请查看 http://www.w3school.com.cn/html5/canvas_drawimage.asp 了解更多。 - - -core.ui.createCanvas(name, x, y, width, height, zIndex) -动态创建一个画布。name为要创建的画布名,如果已存在则会直接取用当前存在的。 -x,y为创建的画布相对窗口左上角的像素坐标,width,height为创建的长宽。 -zIndex为创建的纵向高度(关系到画布之间的覆盖),z值高的将覆盖z值低的;系统画布的z值可在个性化中查看。 -返回创建的画布的context,也可以通过core.dymCanvas[name]调用。 - - -core.ui.relocateCanvas(name, x, y) -重新定位一个自定义画布。 - - -core.ui.resizeCanvas(name, x, y) -重新设置一个自定义画布的大小。 - - -core.ui.deleteCanvas(name) -删除一个自定义画布。 - - -core.ui.deleteAllCanvas() -清空所有的自定义画布。 - - -core.ui.drawThumbnail(floorId, canvas, blocks, x, y, size, heroLoc, heroIcon) -绘制一个缩略图,比如楼传器界面,存读档界面等情况。 -floorId为目标楼层ID,canvas为要绘制到的图层,blocks为要绘制的所有图块。 -x,y为该图层开始绘制的起始点坐标,size为每一格的像素,heroLoc为勇士坐标,heroIcon为勇士图标。 - - -========== core.utils.XXX 工具类的辅助函数 ========== -utils.js主要用来进行一些辅助函数的计算。 - - -core.utils.splitLines(canvas, text, maxLength, font) -自动切分长文本的换行。 -canvas为图层,text为要自动换行的内容,maxLength为每行最长像素,font为文本的字体。 - - -core.utils.cropImage(image, size) -纵向对图片进行切分(裁剪)。 - - -core.utils.push(a,b) -向某个数组后插入另一个数组或元素 - - -core.utils.unshift(a, b) -向某个数组前插入另一个数组或元素 - - -core.utils.encodeBase64(str) -Base64加密字符串 - - -core.utils.decodeBase64(str) -Base64解密字符串 - - -core.utils.formatBigNumber(x, onMap) -大数据的格式化 - - -core.utils.subarray(a, b) -检查b是否是a的从头开始子串。 -如果是,则返回a删去b的一段;否则返回null。 - - -core.utils.same(a, b) -比较a和b两个对象是否相同 - - -core.utils.clamp(x, a, b) -将x限制在[a,b]之间的范围内 - - -core.utils.arrayToRGB(color) -将形如[255,0,0]之类的数组转成#FF0000这样的RGB形式。 - - -core.utils.arrayToRGBA(color) -将形如[255,0,0,1]之类的数组转成rgba(255,0,0,1)这样的RGBA形式。 - - -core.utils.encodeRoute(list) -压缩加密路线。可以使用core.encodeRoute(core.status.route)来压缩当前路线。 - - -core.utils.decodeRoute(route) -解压缩(解密)路线。 - - -core.utils.readFile(success, error, readType) [异步] -尝试请求读取一个本地文件内容。 -success和error为成功/失败后的回调,readType不设置则以文本读取,否则以DataUrl形式读取。 - - -core.utils.readFileContent(content) [异步] -文件读取完毕后的内容处理。 - - -core.utils.download(filename, content) -尝试生成并下载一个文件。 - - -core.utils.copy(data) -尝试复制一段文本到剪切板。 - - -core.utils.http(type, url, formData, success, error) [异步] +core.utils.http(type, url, formData, success, error, mimeType, responseType) 发送一个异步HTTP请求。 type为'GET'或者'POST';url为目标地址;formData如果是POST请求则为表单数据。 success为成功后的回调,error为失败后的回调。 +mimeType和responseType如果设置将会覆盖默认值。 + +lzw_encode(s) / lzw_decode(s) +LZW压缩算法,来自https://gist.github.com/revolunet/843889 ``` diff --git a/_docs/event.md b/_docs/event.md index 3beb965a..e5a2b7b9 100644 --- a/_docs/event.md +++ b/_docs/event.md @@ -1,6 +1,6 @@ # 事件 -?> 目前版本**v2.5.5**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * 本章内将对样板所支持的事件进行介绍。 @@ -20,11 +20,13 @@ ## 关于V2.0的重要说明 -在V2.0版本中,所有事件均可以使用blockly来进行块的可视化编辑。 +在V2.0以后版本中,所有事件均可以使用blockly来进行块的可视化编辑。 它能通过拖动、复制粘贴等方式帮助你快速生成事件列表,而不用手动打大量字符。 -但是,仍然强烈建议要对每个事件的写法进行了解,因为在脚本编辑,`insertAction`等地方需要插入自定义事件时,还是很有必要的。 +下述所说的都是在事件编辑器右边所展示的,该事件的代码化写法。 + +强烈建议要对每个事件的写法进行了解,因为在脚本编辑,`insertAction`等地方需要插入自定义事件时,还是很有必要的。 ## 自定义事件 @@ -33,22 +35,18 @@ 所有自定义的事件都是如下的写法: ``` js -"events": { // 该楼的所有可能事件列表 - "x,y": { - "trigger": "action", // 触发的trigger, action代表自定义事件 - "enable": true, // 该事件初始状态下是否处于启用状态 - "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 - "data": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] - } +{ + "trigger": "action", // 触发的trigger, action代表自定义事件 + "enable": true, // 该事件初始状态下是否处于启用状态 + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 + "data": [ // 实际执行的事件列表 + // 事件1 + // 事件2 + // ... + ] } ``` -这里的`"x,y"`代表该点的横坐标为`x`,纵坐标为`y`;即从左到右第`x`列,从上到下的第`y`行(从0开始计算)。 - 我们上面提到,有很多系统已经默认的事件(例如开门、打怪等,相当于公共事件)。如果我们需要自定义一个事件,则需要`"trigger": "action"`,它表示该点是一个自定义事件。 !> **如果系统本身存在事件(如一个怪物),且你指定了`"trigger": "action"`,则原事件会被覆盖。** @@ -58,17 +56,15 @@ 如果该点本身不存在系统事件,则`"trigger":"action"`可被省略不写: ``` js -"events": { // 该楼的所有可能事件列表 - "x,y": { - // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 - "enable": true, // 该事件初始状态下是否处于启用状态 - "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 - "data": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] - } +{ + // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 + "enable": true, // 该事件初始状态下是否处于启用状态 + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 + "data": [ // 实际执行的事件列表 + // 事件1 + // 事件2 + // ... + ] } ``` @@ -77,17 +73,15 @@ 默认情况下`enable`是`true`,所以如果`enable`为`true`,该项也可以省略不写: ``` js -"events": { // 该楼的所有可能事件列表 - "x,y": { - // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 - // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false - "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 - "data": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] - } +{ + // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 + // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false + "noPass": true, // 该点是否不可通行。true代表不可通行,false代表可通行。 + "data": [ // 实际执行的事件列表 + // 事件1 + // 事件2 + // ... + ] } ``` @@ -98,17 +92,15 @@ 因此,除非你想覆盖默认的可通行选项(比如将一个空地设为不可通行),否则该项可以忽略。 ``` js -"events": { // 该楼的所有可能事件列表 - "x,y": { - // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 - // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false - // 除非你想覆盖系统默认的可通行状态,否则"noPass"项可以忽略 - "data": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] - } +{ + // 除非你要覆盖该点已存在的系统默认事件,否则"trigger": "action"可以省略 + // 该事件初始状态下是启用状态,则可以省略"enable": true;如果是禁用状态则必须加上"enable": false + // 除非你想覆盖系统默认的可通行状态,否则"noPass"项可以忽略 + "data": [ // 实际执行的事件列表 + // 事件1 + // 事件2 + // ... + ] } ``` @@ -117,14 +109,12 @@ 如果大括号里只有`"data"`,则可以省略大括号和`"data"`,直接写中括号数组,换句话说,上面和下面这种写法也是等价的,可以进行一下比较: ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] -} +// 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号 +[ + // 事件1 + // 事件2 + // ... +] ``` 这种简写方式可以极大方便地造塔者进行造塔。 @@ -136,15 +126,13 @@ `"data"`中,是由一系列的自定义事件类型组成。每个元素类似于: ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 - {"type": "xxx", ...}, // 事件1 - {"type": "xxx", ...}, // 事件2 - // ... - // 按顺序写事件,直到结束 - ] -} +// 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 +[ + {"type": "xxx", ...}, // 事件1 + {"type": "xxx", ...}, // 事件2 + // ... + // 按顺序写事件,直到结束 +] ``` `"type"`为该自定义事件的类型;而后面的`...`则为具体的一些事件参数。 @@ -158,29 +146,23 @@ 使用`{"type": "text"}`可以显示一段文字。后面`"text"`可以指定文字内容。 ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 - {"type": "text", "text": "在界面上的一段文字"}, // 显示文字事件 - {"type": "text", "text": "这是第二段文字"}, // 显示第二个文字事件 - // ... - // 按顺序写事件,直到结束 - ] -} +[ + {"type": "text", "text": "在界面上的一段文字"}, // 显示文字事件 + {"type": "text", "text": "这是第二段文字"}, // 显示第二个文字事件 + // ... + // 按顺序写事件,直到结束 +] ``` 该项可以简写成直接的字符串的形式,即下面这种方式也是可以的: ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 - "在界面上的一段文字",// 直接简写,和下面写法完全等价 - {"type": "text", "text": "这是第二段文字"}, // 显示第二个文字事件 - // ... - // 按顺序写事件,直到结束 - ] -} +[ + "在界面上的一段文字",// 直接简写,和下面写法完全等价 + {"type": "text", "text": "这是第二段文字"}, // 显示第二个文字事件 + // ... + // 按顺序写事件,直到结束 +] ``` 所有文字事件均可以进行简写,系统会自动转成`{"type": "text"}`的形式。 @@ -188,15 +170,12 @@ 值得注意的是,系统会自动对文字进行换行;不过我们也可以手动加入`\n`来换行。 ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 +[ "这一段文字特别特别长,但是系统可以对它进行自动换行,因此我们无需手动换行", "这是第一行\n这是第二行\n这是第三行", // ... // 按顺序写事件,直到结束 - ] -} +] ``` 我们可以给文字加上标题或图标,只要以`\t[...]`开头就可以。 @@ -210,7 +189,7 @@ 从V2.5.2以后,新增了绘制大头像的功能。绘制大头像图的基本写法是`\t[1.png]`或者`\t[标题,1.png]`。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "一段普通文字", "\t[勇士,hero]这是一段勇士说的话", "\t[hero]如果使用勇士默认名称也可以直接简写hero", @@ -233,7 +212,7 @@ - `\b[up,x,y]` 显示在(x,y)点的上方(下方);x和y都为整数且在0到12之间。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "\b[up]这段文字显示在当前点上方", "\b[down]这段文字显示在当前点上方", "\t[hero]\b[up,hero]这是一段勇士说的话,会显示在勇士上方", @@ -246,7 +225,7 @@ 还可以使用`\r[...]`来调整剧情文本的颜色。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "这句话是默认颜色,\r[red]将颜色变成红色,\r[blue]将颜色变成蓝色", "\r[#FF00FF]还可以使用RGB值来控制颜色,\r如果不加中括号则回到默认颜色", "\t[hero]\b[up,hero]啊啊啊,别过来,\r[red]别过来!!!\n\r你到底是什么东西!" @@ -260,7 +239,7 @@ 需要注意的是,这个图片是绘制在UI层上的,下一个事件执行时即会擦除;同时如果使用了\t的图标动画效果,重叠的地方也会被图标动画给覆盖掉。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "\t[勇士]\b[up,hero]\f[1.png,100,100]以(100,100)为左上角绘制1.png图片", "\t[hero]\f[1.png,100,100]\f[2.png,300,300]同时绘制了两张图片", "\f[1.png,100,100,300,300]也可以填写宽高,这样会把图片强制进行放缩到指定的宽高值", @@ -273,7 +252,7 @@ 这里可以使用一个合法ID(32x48图块除外),或使用一个系统图标(`core.statusBar.icons`中的内容)。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "\t[勇士]\b[up,hero]这是一个飞行器\\i[fly],这是一个破墙镐\\i[pickaxe]", "\t[hero]也可以使用系统图标,比如这是存档\\i[save],这是工具栏\\i[toolbox]", ] @@ -286,7 +265,7 @@ 另外值得一提的是,我们是可以在文字中计算一个表达式的值的。只需要将表达式用 `${ }`整个括起来就可以。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "1+2=${1+2}, 4*5+6=${4*5+6}", // 显示"1+2=3, 4*5+6=26" ] ``` @@ -294,7 +273,7 @@ 我们可以使用 `status:xxx` 代表勇士的一个属性值;`item:xxx` 代表某个道具的个数;`flag:xxx` 代表某个自定义的变量或flag值。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "你当前的攻击力是${status:atk}, 防御是${status:def},坐标是(${status:x},${status:y})", "你的攻防和的十倍是${10*(status:atk+status:def)}", "你的红黄蓝钥匙总数为${item:yellowKey+item:blueKey+item:redKey}", @@ -306,18 +285,12 @@ - `item:xxx` 中的xxx为道具ID。所有道具的ID定义在items.js中,请自行查看。例如,`item:centerFly` 代表中心对称飞行器的个数。 - `flag:xxx` 中的xxx为一个自定义的变量/Flag;如果没有对其进行赋值则默认值为false。 -另外,有个小`trick`。是否想立刻知道显示效果? - -你可以用Chrome浏览器打开游戏,按Ctrl+Shift+I打开开发者工具,找到Console(控制台),并中输入`core.drawText("...")` 即可立刻看到文字显示的效果。适当调整文字,使得显示效果满意后,再复制粘贴到你的剧情文本中。 - -![调试](./img/eventdebug.png) - ### autoText:自动剧情文本 使用`{"type": "autoText"}`可以使用剧情文本。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "autoText", "text": "一段自动显示的剧情文字", "time": 5000} ] ``` @@ -337,7 +310,7 @@ time为可选项,代表该自动文本的时间。可以不指定,不指定 使用`{"type": "scrollText"}`可以使用滚动剧情文本,即将一段文字从屏幕最下方滚动到屏幕最上方。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "scrollText", "text": "第一排\n第二牌\n\n空行后的一排", "time": 5000, "lineHeight": 1.4, "async": true}, ] ``` @@ -359,7 +332,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 使用`{"type": "setText"}`可以设置剧情文本的各项属性。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setText", "title": [255,0,0], "text": [255,255,0], "background": [0,0,255,0.3], "time": 70}, {"type": "setText", "position": "up", "offset": 15, "bold": true, "titlefont": 26, "textfont": 17}, "这段话将显示在上方(距离顶端15像素),标题为红色,正文为黄色粗体,背景为透明度0.3的蓝色,标题26px,正文17px,70毫秒速度打字机效果", @@ -392,7 +365,7 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 `{"type": "tip"}`可以在左上角显示一段提示文字。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "tip", "text": "这段话将在左上角以气泡形式显示"} ] ``` @@ -404,7 +377,7 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 使用`{"type": "comment"}`可以添加一段注释 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "comment", "text": "这是一段会被跳过的注释内容"} ] ``` @@ -418,7 +391,7 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setValue", "name": "...", "value": "..."}, // 设置一个属性、道具或自定义Flag ] ``` @@ -430,7 +403,7 @@ name为你要修改的属性/道具/Flag,每次只能修改一个值。写法 value是一个表达式,将通过这个表达式计算出的结果赋值给name。该表达式同样可以使用`status:xxx`, `item:xxx`, `flag:xxx`的写法表示勇士当前属性,道具个数和某个变量/Flag值。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setValue", "name": "status:atk", "value": "status:atk+10" } // 攻击提高10点 {"type": "setValue", "name": "status:money", "value": "1000" } // 将金币数设为1000(不是+1000) {"type": "setValue", "name": "status:hp", "value": "status:hp*2" } // 生命值翻倍 @@ -450,7 +423,7 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam 即下面的写法是等价的: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setValue", "name": "status:atk", "value": "status:atk+10" } // 攻击提高10点 {"type": "addVakue", "name": "status:atk", "value": "10" } // 和上面写法等价 {"type": "setValue", "name": "item:yellowKey", "value": "item:yellowKey-3" } // 黄钥匙个数-3 @@ -465,7 +438,7 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam 使用`{"type":"setFloor"}`可以设置某层楼的楼层属性。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setFloor", "name": "title", "value": "'主塔 0 层'" } // 设置当前楼层的中文名为主塔0层 {"type": "setFloor", "name": "canFlyTo", "floorId": "MT2", "value": "false" } // 设置MT2层不可飞行 {"type": "setFloor", "name": "cannotViewMap", "floorId": "MT0", "value": "true" } // 设置MT0层不可被浏览地图 @@ -476,8 +449,7 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam ] ``` -name为必填项,代表要修改的楼层属性。其和楼层属性中一一对应,目前只能为`"title", "name", "canFlyTo", "canUseQuickShop", "cannotViewMap", "cannotMoveDirectly", "color", "weather", -"defaultGround", "images", "item_ratio", "upFloor", "bgm", "downFloor", "underGround"`。 +name为必填项,代表要修改的楼层属性,和楼层属性中的一一对应。 floorId为可选项,代表要修改的楼层ID;可以省略代表当前楼层。 @@ -490,13 +462,12 @@ value为必填项,代表要修改到的数值。其应该和楼层属性中的 使用`{"type":"setGlobalAttribute"}`可以设置一个全局属性。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "setGlobalAttribute", "name": "font", "value": "Verdana"}, // 设置字体为Verdana +[ + {"type": "setGlobalAttribute", "name": "font", "value": "Verdana"}, // 设置字体为Verdana ] ``` -name必填项,代表要修改的全局属性。目前只能为`"font", "statusLeftBackground", "statusTopBackground", "toolsBackground", -"borderColor", "statusBarColor", "hardLabelColor", "floorChangingBackground", "floorChangingTextColor"`。 +name必填项,代表要修改的全局属性。 value为必填项,代表要修改到的结果。此项无需再手动加单引号。 @@ -505,14 +476,12 @@ value为必填项,代表要修改到的结果。此项无需再手动加单引 使用`{"type":"setGlobalValue"}`可以设置一个全局数值。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setGlobalValue", "name": "lavaDamage", "value": 200}, // 设置血网伤害为200 ] ``` -name必填项,代表要修改的全局数值,其和全塔属性中的values一一对应。目前只能为`"lavaDamage", "poisonDamage", "weakValue", "redJewel", -"blueJewel", "greenJewel", "redPotion", "bluePotion", "yellowPotion", "greenPotion", "breakArmor", "counterAttack", -"purify", "hatred", "moveSpeed", "animateSpeed"`。 +name必填项,代表要修改的全局数值,其和全塔属性中的values一一对应。 value为必填项,代表要修改到的结果。该项必须是个数值。 @@ -521,15 +490,12 @@ value为必填项,代表要修改到的结果。该项必须是个数值。 使用`{"type":"setGlobalFlag"}`可以设置一个系统开关。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setGlobalFlag", "name": "enableMDef", "value": false}, // 不在状态栏显示魔防值 ] ``` -name必填项,代表要修改的系统开关,其是全塔属性中的flags中的一部分。目前只能为`"enableFloor", "enableName", "enableLv", -"enableHPMax", "enableMana", "enableMDef", "enableMoney", "enableExperience", "enableLevelUp", "levelUpLeftMode", -"enableKeys", "enablePZF", "enableDebuff", "enableSkill", "flyNearStair", "enableAddPoint", "enableNegativeDamage", -"useLoop", "enableGentleClick", "canGoDeadZone", "enableMoveDirectly", "disableShopOnDamage"`。 +name必填项,代表要修改的系统开关,其是全塔属性中的flags中的一部分。 value为必填项,只能为true或false,代表要修改到的结果。 @@ -540,7 +506,7 @@ value为必填项,只能为true或false,代表要修改到的结果。 使用`{"type":"show"}`可以将一个本身禁用的事件启用。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "show", "loc": [3,6], "floorId": "MT1", "time": 500}, // 启用MT1层[3,6]位置事件,动画500ms {"type": "show", "loc": [3,6], "time": 500}, // 如果启用目标是当前层,则可以省略floorId项 {"type": "show", "loc": [3,6]}, // 如果不指定动画时间,则立刻显示,否则动画效果逐渐显示,time为动画时间 @@ -576,7 +542,7 @@ loc同样可以简单的写[x,y]表示单个点,或二维数组[[x1,y1],[x2,y2 NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}`,可以不写loc选项代表当前事件,可以指定time使NPC动画消失。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "hide", "loc": [3,6], "floorId": "MT1", "time": 500}, // 禁用MT1层[3,6]位置事件,动画500ms {"type": "hide", "loc": [3,6], "time": 500}, // 如果启用目标是当前层,则可以省略floorId项 {"type": "hide", "loc": [3,6]}, // 如果不指定动画时间,则立刻消失,否则动画效果逐渐消失,time为动画时间 @@ -596,7 +562,7 @@ NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}` 其基本写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "trigger", "loc": [3,6]}, // 立即触发loc位置的事件,当前剩下的事件全部不再执行 "执行trigger后,这段文字将不会再被显示" ] @@ -617,7 +583,7 @@ NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}` 其基本写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "insert", "name": "加点事件", "args": [10] }, // 插入公共事件:加点事件,传入参数10 {"type": "insert", "name": "毒衰咒处理", "args": [0]}, // 插入公共事件:毒衰咒处理,传入参数0 {"type": "insert", "loc": [3,6]}, // 插入[3,6]点的事件并执行 @@ -657,7 +623,7 @@ NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}` revisit和trigger完全相同,只不过是立刻触发的还是本地点的事件 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "revisit"}, // 立即触发本事件,等价于 {"type": "trigger", "loc": [x,y]} "执行revisit后,这段文字将不会再被显示" ] @@ -676,7 +642,7 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 例如玩家点击商人的"离开"选项,则可以调用exit返回游戏。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "exit" }, // 立即结束事件并恢复游戏,一切列表中的事件都将不再被执行 "执行exit后,这段文字将不会再被显示" ] @@ -687,7 +653,7 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 我们可以采用 `{"type": "setBlock"}` 来改变某个地图块。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setBlock", "floorId": "MT1", "loc": [3,3], "number": 233}, // 将MT1层的(3,3)点变成数字233 {"type": "setBlock", "loc": [2,1], "number": 121}, // 省略floorId则默认为本层 {"type": "setBlock", "number": 57}, // loc也可省略,默认为当前点 @@ -715,7 +681,7 @@ number为**要更改到的数字**,有关“数字”的定义详见参见[素 有关贴图说明请参见[使用自己的图片作为某层楼的背景/前景素材](personalization#使用自己的图片作为某层楼的背景前景素材)。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "hideFloorImg", "loc": [3,6], "floorId": "MT1"}, // 隐藏[3,6]的贴图 {"type": "hideFloorImg", "loc": [3,6]}, // 如果是当前层,则可以省略floorId项 {"type": "hideFloorImg", "loc": [[3,6],[2,9],[1,2]]} // 我们也可以同时隐藏多个贴图。 @@ -735,7 +701,7 @@ floorId为目标点的楼层,如果是当前楼层可以忽略不写。 其做法和参数,和隐藏贴图是完全一致的。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showFloorImg", "loc": [3,6], "floorId": "MT1"}, // 显示[3,6]的贴图 ] ``` @@ -747,7 +713,7 @@ floorId为目标点的楼层,如果是当前楼层可以忽略不写。 从V2.4.1开始,允许绘制三层图层(背景层,事件层和前景层)。使用`hideBgFgMap`可以隐藏背景或前景层中的图块。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "hideBgFgMap", "name": "bg", "loc": [3,6], "floorId": "MT1"}, // 隐藏MT1层[3,6]的背景层图块 {"type": "hideBgFgMap", "name": "bg", "loc": [3,6]}, // 如果是当前层,则可以省略floorId项 {"type": "hideBgFgMap", "name": "fg", "loc": [[3,6],[2,9],[1,2]]} // 我们也可以同时隐藏多个贴图。 @@ -767,7 +733,7 @@ floorId为目标点的楼层,如果是当前楼层可以忽略不写。 其做法和参数,和隐藏贴图是完全一致的。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showBgFgMap", "name": "bg", "loc": [3,6], "floorId": "MT1"}, // 显示MT1层[3,6]的前景层图块 ] ``` @@ -777,7 +743,7 @@ floorId为目标点的楼层,如果是当前楼层可以忽略不写。 我们可以采用 `{"type": "setBgFgBlock"}` 来改变某个背景或前景层地图块。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setBgFgBlock", "name": "bg", "floorId": "MT1", "loc": [3,3], "number": 233}, // 将MT1层背景层的(3,3)点变成数字233 {"type": "setBgFgBlock", "name": "bg", "loc": [2,1], "number": 121}, // 省略floorId则默认为本层 {"type": "setBgFgBlock", "name": "fg", "number": 57}, // loc也可省略,默认为当前点 @@ -797,7 +763,7 @@ loc为可选的,表示要更改地图块的坐标。如果忽略此项,则 使用`{"type": "setHeroIcon"}`可以更改角色行走图。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setHeroIcon", "name": "hero2.png"}, // 将勇士行走图改成hero2.png;必须在全塔属性的images中被定义过。 {"type": "setHeroIcon"}, // 如果不加name则恢复最初默认状态 {"type": "setValue", "name": "status:name", "value": "'可绒'"}, // 修改勇士名;请注意value必须加单引号。 @@ -839,7 +805,7 @@ name是可选的,代表目标行走图的文件名。 基本写法:`{"type": "sleep", "time": xxx}` ,其中xxx为指定的毫秒数。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "sleep", "time": 1000}, // 等待1000ms "等待1000ms后才开始执行这个事件", {"type": "sleep", "time": 2000, "noSkip": true}, // 等待2000毫秒,且不可被跳过 @@ -854,12 +820,10 @@ name是可选的,代表目标行走图的文件名。 调用battle可强制与某怪物进行战斗(而无需去触碰到它)。 -例如,《宿命的旋律》中,一区有个骷髅队长,当你拿了它周围三个物品时,就会立刻触发强制战斗事件。这时候就可以用`{"type": "battle"}` 实现。 - 其基本写法是: `{"type": "battle", "id": xxx}`,其中xxx为怪物ID。 ``` js -"10,4": [ // 开门后走进去的事件:强制战斗 +[ "\t[blackKing]你终于还是来了。", "\t[hero]放开我们的公主!", "\t[blackKing]如果我不愿意呢?", @@ -877,8 +841,6 @@ name是可选的,代表目标行走图的文件名。 如果强制战斗失败,则会立刻生命归0并死亡,调用lose函数,接下来的事件不会再被执行。 -打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](#加点事件)。 - 强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。 ### openDoor:开门 @@ -886,7 +848,7 @@ name是可选的,代表目标行走图的文件名。 调用`{"type":"openDoor"}`可以打开一扇门。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "openDoor", "loc": [3,6], "floorId": "MT1"}, // 打开MT1层的[3,6]位置的门 {"type": "openDoor", "loc": [3,6]}, // 如果是本层则可省略floorId {"type": "openDoor", "loc": [3,6], "needKey": true} // 打开此门需要钥匙 @@ -908,7 +870,7 @@ needKey是可选的,如果设置为true则需要钥匙才能打开此门。如 从V2.6开始提供了关门事件`{"type": "closeDoor"}`,拥有关门动画和对应的音效。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "closeDoor", "id": "yellowDoor", "loc": [3,6]}, // 给(3,6)点关上黄门 {"type": "closeDoor", "id": "specialDoor"}, // 不写loc则视为当前点 ] @@ -934,7 +896,7 @@ loc可选,为要关的位置,不填默认为当前点。 changeFloor的事件写法大致如下。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "changeFloor", "floorId": "sample0","loc": [10, 10], "direction": "left", "time": 1000 }, //后面几项依次为楼层id,楼层位置(这两项为必填);勇士方向可选,切换时间也是可选。 ] @@ -950,14 +912,14 @@ time为可选的,指定的话将作为楼层切换动画的时间。 **如果time指定为小于100,则视为没有楼层切换动画。** -### changePos:当前位置切换/勇士转向 +### changePos:当前位置切换/set勇士转向 有时候我们不想要楼层切换的动画效果,而是直接让勇士从A点到B点。 这时候可以用changePos。其参数和changeFloor类似,但少了floorId和time两个选项。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "changePos", "loc": [10,10], "direction": "left"}, // 直接切换勇士的坐标,loc为目标地点,后面勇士换位后方向 {"type": "changePos", "loc", [10,10]}, // 如无需指定方向则direction可省略 {"type": "changePos", "direction": "left"} // loc也可省略,只指定direction;此时等价于当前勇士转向到某个方向。 @@ -969,7 +931,7 @@ time为可选的,指定的话将作为楼层切换动画的时间。 调用`{"type": "useItem"}`可以使用一个道具。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "changePos", "id": "pickaxe"}, // 尝试使用破 {"type": "changePos", "id": "bomb"}, // 尝试使用炸 {"type": "changePos", "id": "centerFly"} // 尝试使用飞 @@ -995,7 +957,7 @@ time为可选的,指定的话将作为楼层切换动画的时间。 使用 `{"type": "follow"}` 可以让一个npc加入跟随。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "follow", "name": "npc.png"}, // 将 npc.png 这个行走图加入跟随 {"type": "follow", "name": "hero.png"}, // 再将另一个行走图加入跟随 ] @@ -1010,7 +972,7 @@ name所指定的图片必须存在,在全塔属性中的images中被定义过 使用 `{"type": "unfollow"}` 来取消一个跟随。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "unfollow", "name": "npc.png"}, // 将 npc.png 这个行走图取消跟随 {"type": "follow"}, // 取消所有跟随 ] @@ -1037,7 +999,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 有关动画的详细介绍可参见[动画和天气系统](element#动画和天气系统)。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "animate", "name": "yongchang", "loc": [1,3]}, // 在(1,3)显示“咏唱魔法”动画 {"type": "animate", "name": "zone", "loc": "hero"}, // 在勇士位置显示“领域”动画 {"type": "animate", "name": "hand"}, // 可以不指定loc,则默认为当前事件点 @@ -1060,7 +1022,7 @@ loc可忽略,如果忽略则显示为事件当前点。 我们可以使用 `{"type": "showImage"}` 来显示一张图片。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showImage", "code": 1, "image": "bg.jpg", "loc": [231,297], "opacity": 1, "time" : 0}, // 在(231,297)显示bg.jpg {"type": "showImage", "code": 12, "image": "1.png", "loc": [209,267], "opacity": 0.5, "time" : 1000}, // 在(209,267)渐变显示1.png,渐变时间为1000毫秒,完成时不透明度为0.5,这张图片将遮盖上一张 {"type": "showImage", "code": 8, "image": "hero.png", "loc": [349,367], "opacity": 1, "time" : 500, "async": true}, // 在(209,267)渐变显示hero.png,渐变时间为500毫秒,异步执行;这张图片将被上一张遮盖 @@ -1087,7 +1049,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 我们可以使用 `{"type": "showTextImage"}` 以图片的方式显示文本。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showTextImage", "code": 1, "text": "第一排\n第二排\n\n空行后的一排", "loc": [231,297], "opacity": 1, "time" : 0}, // 在(231,297)显示"第一排\n第二排\n\n空行后的一排" ] ``` @@ -1113,7 +1075,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 我们可以使用 `{"type": "hideImage"}` 来清除一张图片。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "hideImage", "code": 1, "time" : 0}, // 使1号图片消失 {"type": "hideImage", "code": 12, "time" : 1000}, // 使12号图片渐变消失,时间为1000毫秒 ] @@ -1130,7 +1092,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 我们可以使用 `{"type": "showGif"}` 来显示一张图片。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showGif", "name": "timg.gif", "loc": [231,297]}, // 在(231,297)显示一张动图 {"type": "showGif"} // 如果不指定name则清除所有动图。 ] @@ -1147,7 +1109,7 @@ loc为动图左上角坐标,以像素为单位进行计算。 我们可以使用 `{"type": "moveImage"}` 来造成图片移动,淡入淡出等效果。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "moveImage", "code": 1, "to": [22,333], "opacity": 1, "time": 1000}, // 将1号图片移动到(22,333),动画时间为1000ms {"type": "moveImage", "code": 12, "opacity": 0.5, "time": 500}, // 将二号图片的透明度变为0.5,动画时间500ms {"type": "moveImage", "code": 1, "to": [109,167], "opacity": 0, "time": 300, "async": true}, // 将1号图片移动到(109,167),透明度设为0(不可见),动画时间300ms,异步执行 @@ -1164,15 +1126,15 @@ time为总移动的时间。 async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 -### setFg:更改画面色调 +### setCurtain:更改画面色调 -我们可以使用 `{"type": "setFg"}` 来更改画面色调。 +我们可以使用 `{"type": "setCurtain"}` 来更改画面色调。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "setFg", "color": [255,255,255,0.6], "time": 1000}, // 更改画面色调为纯白,不透明度0.6,动画时间1000毫秒 - {"type": "setFg", "color": [0,0,0], "async": true}, // 更改画面色调为纯黑,不透明度1,不指定动画时间(使用默认时间),且异步执行 - {"type": "setFg"} // 如果不指定color则恢复原样。 +[ + {"type": "setCurtain", "color": [255,255,255,0.6], "time": 1000}, // 更改画面色调为纯白,不透明度0.6,动画时间1000毫秒 + {"type": "setCurtain", "color": [0,0,0], "async": true}, // 更改画面色调为纯黑,不透明度1,不指定动画时间(使用默认时间),且异步执行 + {"type": "setCurtain"} // 如果不指定color则恢复原样。 ] ``` @@ -1191,7 +1153,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 我们可以使用 `{"type": "screenFlash"}` 来进行画面闪烁。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "screenFlash", "color": [255,255,255,0.6], "time": 500, "times": 1}, // 闪光为白色,不透明度0.6,动画时间1000毫秒 {"type": "screenFlash", "color": [255,0,0,1], "time": 100, "times": 2, "async": true}, // 闪光为红色,强度最大,动画时间100毫秒,闪烁两次且异步执行 ] @@ -1212,7 +1174,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 我们可以使用 `{"type": "setWeather"}` 来更改天气。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setWeather", "name": "rain", "level": 6}, // 更改为雨天,强度为6级 {"type": "setWeather", "name": "snow", "level": 3}, // 更改为雪天,强度为3级 {"type": "setWeather"} // 更改回晴天 @@ -1236,10 +1198,9 @@ level为天气的强度等级,在1-10之间。1级为最弱,10级为最强 下面是该事件常见的写法: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "move", "time": 750, "loc": [x,y], "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),loc为位置可选,steps为移动数组 - {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 - "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 + "right", "right", "down" // 向右两格,向下一格 ], "keep": true, "async":true }, // keep可选,如果为true则不消失,否则渐变消失;async可选,如果为true则异步执行。 ] ``` @@ -1248,9 +1209,7 @@ time选项必须指定,为每移动一步所需要用到的时间。 loc为需要移动的事件位置。可以省略,如果省略则移动本事件。 -steps为一个数组,其每一项为一个 `{"direction" : xxx, "value": n}`,表示该步是向xxx方向移动n步。 - -如果只移动一步可以直接简单的写方向字符串(`up/left/down/right`)。 +steps为一个数组,其每一项是`up, down, left, right`之一,表示这一步应该朝哪个方向走。 keep为一个可选项,代表该事件移动完毕后是否消失。如果该项指定了并为true,则移动完毕后将不消失,否则以动画效果消失。 @@ -1265,7 +1224,7 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 ``` js "4,3": [ // [4,3]是一个NPC,比如小偷 {"type": "move", "time": 750, "steps": [ // 向上移动两格,每步750毫秒 - {"direction": "up", "value": 2}, + "up", "up" ], "keep": true}, // 移动完毕后不消失 ], "4,1": { // [4,1]为目标地点 @@ -1289,13 +1248,14 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 ``` js "x,y": [ // 实际执行的事件列表 {"type": "moveHero", "time": 750, "async": true, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 - {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 - "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 + "down", "right", "forward", "backward" // 向下、右、前、后各走一步 ]}, ] ``` -可以看到,和上面的move事件几乎完全相同,除了不能指定loc,且少了immediateHide选项。 +可以看到,和上面的move事件几乎完全相同,除了不能指定loc,且少了keep选项。 + +勇士的steps也允许`forward`和`backward`,即前进和后退。 不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。 @@ -1308,7 +1268,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 下面是该事件常见的写法: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "jump", "from": [3,6], "to": [2,1], "time": 750, "keep": true, "async": true}, ] ``` @@ -1332,7 +1292,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 下面是该事件常见的写法: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "jump", "loc": [3,6], "time": 750, "async": true}, ] ``` @@ -1351,7 +1311,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 值得注意的是,额外添加进文件的背景音乐,需在main.js中this.bgms里加载它。 -目前支持mp3/ogg/wav/mid等多种格式的音乐播放。 +目前支持mp3/ogg/wav等多种格式的音乐播放。 有关BGM播放的详细说明参见[背景音乐](element#背景音乐) @@ -1385,6 +1345,8 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 值得注意的是,如果是额外添加进文件的音效,则需在main.js中this.sounds里加载它。 +从V2.6开始,也可以加`"stop": true`来停止之前正在播放的音效。 + ### stopSound:停止所有音效 使用`{"type": "stopSound"}`可以停止所有音效。 @@ -1409,6 +1371,8 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 该事件会显示获胜页面,并重新游戏。 +可以增加`"norank": 1`来表示该结局不计入榜单。 + !> 如果`reason`不为空,则会以reason作为获胜的结局! ### lose:游戏失败 @@ -1440,7 +1404,7 @@ async可选,如果为true则会异步执行(即不等待当前事件执行 使用`{"type": "input"}`可以接受用户的输入的数字。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "input", "text": "请输入一个数"}, // 显示一个弹窗让用户输入数字 "你刚刚输入的数是${flag:input}" // 输入结果将被赋值为flag:input ] @@ -1459,7 +1423,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 类似于input事件,使用`{"type": "input2"}`可以接受用户的输入的文本。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "input2", "text": "请输入你的ID"}, // 显示一个弹窗让用户输入文本 "你好,${flag:input},欢迎来到本塔" // 输入结果将被赋值为flag:input ] @@ -1480,7 +1444,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "if", "condition": "...", // 测试某个条件 "true": [ // 条件成立则执行true里面的事件 @@ -1501,7 +1465,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 例如下面这个例子,每次将检查你的攻击力是否大于500,不是的场合将给你的攻击力加100点。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "if", "condition": "status:atk>500", // 判断攻击力是否大于500 "true": [ // 条件成立则执行true里面的事件 "你的攻击力已经大于500了!", @@ -1509,7 +1473,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 ], "false": [ // 条件不成立则执行false里的事件 "你当前攻击力为${status:atk}, 不足500!\n给你增加100点攻击力!", - {"type": "setValue", "name": "status:atk", "value": "status:atk+100"}, // 攻击力加100, 接着会执行revisit事件 + {"type": "addValue", "name": "status:atk", "value": "100"}, // 攻击力加100, 接着会执行revisit事件 ] }, {"type", "revisit"}, // 立刻重启本事件, 直到攻击力大于500后结束 @@ -1519,7 +1483,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 需要额外注意的几点: - 给定的表达式(condition)一般需要返回true或false。 -- `flag:xxx` 可取用一个自定义变量或flag。如果从未设置过该flag,则其值默认为false。而JS中,`false==0`这个判断是成立的,因此我们可以简单使用 `"flag:npc_times==0"` 来判断某个NPC是否被访问过。 +- `flag:xxx` 可取用一个自定义变量或flag。如果从未设置过该flag,则其值默认为0。 - 即使成功失败的场合不执行事件,对应的true或false数组也需要存在,不过简单的留空就好。 - if可以不断进行嵌套,一层套一层;如成立的场合再进行另一个if判断等。 - if语句内的内容执行完毕后将接着其后面的语句继续执行。 @@ -1532,13 +1496,13 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "switch", "condition": "...", // 计算某个表达式 "caseList": [ {"case": "a", "action": [// 若表达式的值等于a则执行该处事件 ], - {"case": "b", "action": [// 若表达式的值等于b则执行该处事件 + {"case": "b", "nobreak": true, "action": [// 若表达式的值等于b则执行该处事件,不跳出 ], {"case": "default", "action": [ // 没有条件成立则执行该处里的事件 @@ -1555,10 +1519,12 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 如果没有符合的值,则将执行`default`中的列表事件内容。 +nobreak是可选的,如果设置,则在当前条件满足并插入事件后,不跳出多重分歧,而是继续判定下一个条件。 + 例如下面这个例子,将检查当前游戏难度并赠送不同属性。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "switch", "condition": "flag:hard", // 判断当前游戏难度 "caseList": [ {"case": "0", "action": [// 若表达式的值等于0则执行该处事件 @@ -1581,12 +1547,32 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 需要额外注意的几点: -- 各个条件分支的判断是顺序执行的,因此若多个分支的条件都满足,将只执行最靠前的分支,同理,请不要在`default`分支后添加分支,这些分支将不可能被执行。 +- 各个条件分支的判断是顺序执行的,因此若多个分支的条件都满足,将只执行最靠前的分支。 + - 同理,请不要在`default`分支后添加分支,这些分支将不可能被执行。 - `default`分支并不是必要的,如果删除,则在没有满足条件的分支时将不执行任何事件。 - 即使某个场合不执行事件,对应的action数组也需要存在,不过简单的留空就好。 -- switch可以不断进行嵌套,一层套一层;如某条件成立的场合再进行另一个switch判断等。 - switch语句内的内容执行完毕后将接着其后面的语句继续执行。 +另外由于`case`中的内容是会被计算的,因此如下写法也是合法的 + +```js +[ + {"type": "switch", "condition": "true", // 条件:某一项为真时 + "caseList": [ + {"case": "flag:a==1", "action": [ // 如果 flag:a == 1 + "走进了 flag:a==1 分支!" + ], + {"case": "flag:b>=3", "action": [ // 如果 flag:b >= 3 + "走进了 flag:b>=3 分支!" + ], + {"case": "default", "action": [ // 上述两条均布成立 + "上述两条均不成立" + ] + ] + }, +] +``` + ### choices:给用户提供选项 @@ -1599,17 +1585,16 @@ choices是一个很麻烦的事件,它将弹出一个列表供用户进行选 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "choices", "text": "...", // 提示文字 - "color": [255,0,0,1], // 颜色 "choices": [ {"text": "选项1文字", "action": [ // 选项1执行的事件 ]}, - {"text": "选项2文字", "action": [ + {"text": "选项2文字", "color": [255,0,0,1], "action": [ // 选项2执行的事件 ]}, - {"text": "选项3文字", "action": [ + {"text": "选项3文字", "icon": "fly", "action": [ // 选项3执行的事件 ]}, ] @@ -1627,67 +1612,36 @@ action为当用户选择了该选项时将执行的事件。 color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数组([255,0,0,1])。 +icon是可选的,如果设置则会在选项前绘制图标,其可以是一个有效的ID,或者`core.statusBar.icons`中的系统图标。 + 选项可以有任意多个,但一般不要超过6个,否则屏幕可能塞不下。 -下面是一个卖钥匙的事件,是一个比较复杂却也较为典型的if和choices合并使用的样例。 +### confirm:显示确认框 -``` js -"10,11": [ // 商人事件,if语句和choices语句的写法 - // 这部分逻辑相对比较长,细心看,很容易看懂的。 - {"type": "if", "condition": "flag:woman_times==0", // 条件判断:是否从未访问过此商人。 - "true": [ // 如果从未访问过该商人,显示一段文字 - "\t[老人,woman]这是个很复杂的例子,它将教会你如何使用if 语句进行条件判断,以及 choices 提供选项来供用户进行选择。", - "\t[老人,woman]第一次访问我将显示这段文字;从第二次开始将会向你出售钥匙。\n钥匙价格将随着访问次数递增。\n当合计出售了七把钥匙后,将送你一把大黄门钥匙,并消失不再出现。", - "\t[老人,woman]这部分的逻辑比较长,请细心看样板的写法,是很容易看懂并理解的。" - // 第一次访问结束 +`{"type": "confirm"}`将提供一个确认框供用户选择,其基本写法如下: + +```js +[ + {"type": "confirm", "text": "...", // 提示文字 + "default": false, // 是否默认选中【确定】 + "yes": [ + // 点击确定时执行的事件 ], - "false": [ // 如果已经访问过该商人 - {"type": "if", "condition": "flag:woman_times==8", // 条件判断:是否已经出售七把钥匙 - "true": [ // 如果已经出售过七把钥匙,则直接结束 - "\t[老人,woman]你购买的钥匙已经够多了,再继续卖给你的话我会有危险的。", - "\t[老人,woman]看在你贡献给我这么多钱的份上,送你一把大黄门钥匙吧,希望你能好好用它。", - {"type": "setValue", "name": "item:bigKey", "value": "item:bigKey+1"}, // 获得一把大黄门钥匙 - "\t[老人,woman]我先走了,拜拜~", - {"type":"hide", "time": 500}, // 消失 - {"type":"exit"} // 立刻结束当前事件。下面的 setValue 和 revisit 都不会再执行。 - ], - "false": [ // 否则,显示选择页面 - {"type": "choices", "text": "\t[老人,woman]少年,你需要钥匙吗?\n我这里有大把的!", // 显示一个卖钥匙的选择页面 - "choices": [ // 提供四个选项:黄钥匙、蓝钥匙、红钥匙、离开。前三个选项显示需要的金额 - {"text": "黄钥匙(${9+flag:woman_times}金币)", "color": [255,255,0,1], "action": [ // 第一个选项,黄钥匙 - // 选择该选项的执行内容 - {"type": "if", "condition": "status:money>=9+flag:woman_times", // 条件判断:钱够不够 - "true": [ - {"type": "setValue", "name": "status:money", "value": "status:money-(9+flag:woman_times)"}, // 扣减金钱 - {"type": "setValue", "name": "item:yellowKey", "value": "item:yellowKey+1"}, // 增加黄钥匙 - // 然后会继续执行下面的setValue来增加商人访问次数 - ], - "false": [ - "\t[老人,woman]你的金钱不足!", - {"type": "revisit"} // 直接重新访问;不执行下面的setValue来增加访问次数 - ] - } - ]}, - {"text": "蓝钥匙(${18+2*flag:woman_times}金币)", "color": [0,0,255,1], "action": [ // 第二个选项:蓝钥匙 - // 逻辑和上面黄钥匙完全相同,略 - ]}, - {"text": "红钥匙(${36+4*flag:woman_times}金币)", "color": [255,0,0,1], "action": [ // 第三个选项:红钥匙 - // 逻辑和上面黄钥匙完全相同,略 - ]}, - {"text": "离开", "action": [ // 第四个选项:离开 - {"type": "exit"} // 立刻结束当前事件 - ]} - ] - } - ] - } + "no": [ + // 点击取消时执行的事件 ] }, - {"type": "setValue", "name": "flag:woman_times", "value": "flag:woman_times+1"}, // 增加该商人的访问次数。 - {"type": "revisit"} // 立即重新开始这个事件 -], +] ``` +text为必填项,代表提示的文字,支持${}的表达式计算和\n的手动换行。 + +text暂时不支持自动换行、变色\r、图标绘制\i等效果。如有需求请使用choices事件。 + +default可选,如果为true则显示选择项时默认选中【确定】,否则默认选中【取消】。 + +yes和no均为必填项,即用户点击确认或取消后执行的事件。 + ### while:循环处理 从2.2.1样板开始,我们提供了循环处理(while事件)。 @@ -1695,7 +1649,7 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "while", "condition": "...", // 循环测试某个条件 "data": [ // 条件成立则执行data里面的事件 @@ -1713,10 +1667,10 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 下面是一个输出1到10之间的数字,每隔1秒显示一个的例子。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type":"while", "condition": "flag:i<=10", // 循环处理;注意flag未设置则默认为0 "data":[ - {"type": "setValue", "name": "flag:i", "value": "flag:i+1"}, // 递增i + {"type": "addValue", "name": "flag:i", "value": "1"}, // 递增i "${flag:i}", // 输出i {"type": "sleep","time":1000}, // 等待1秒 ] @@ -1737,10 +1691,10 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 上面的输出例子也可以这么写: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type":"while", "condition": "true", // 循环处理;永远为真 "data":[ - {"type": "setValue", "name": "flag:i", "value": "flag:i+1"}, // 递增i + {"type": "addValue", "name": "flag:i", "value": "1"}, // 递增i "${flag:i}", // 输出i {"type": "sleep","time":1000}, // 等待1秒 {"type": "if", "condition": "flag:i<10", // 测试i是否小于10 @@ -1760,15 +1714,14 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 使用 `{"type": "wait"}` 可以等待用户进行操作(如点击、按键等)。 当用户执行操作后: -- 如果是键盘的按键操作,则会将flag:type置为0,并且把flag:keycode置为刚刚按键的keycode。 -- 如果是屏幕的点击操作,则会将flag:type置为1,并且设置flag:x和flag:y为刚刚的点击坐标(0-12之间),flag:px和flag:py置为刚刚的像素坐标(0-415之间)。 +- 如果是键盘的按键操作,则会将flag:type置为0,并且把flag:keycode置为按键的keycode。 +- 如果是屏幕的点击操作,则会将flag:type置为1,并且设置flag:x和flag:y为点击的位置坐标,flag:px和flag:py为点击的像素坐标。 下面是一个while事件和wait合并使用的例子,这个例子将不断接收用户的点击或按键行为,并输出该信息。 如果用户按下了ESC或者点击了屏幕正中心,则退出循环。 - ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "while", "condition": "true", // 永久循环 "data": [ {"type": "wait"}, // 等待用户操作 @@ -1791,7 +1744,6 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 ] } ] - ``` ### waitAsync:等待所有异步事件执行完毕 @@ -1812,7 +1764,7 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 上述给出了这么多事件,但有时候往往不能满足需求,这时候就需要执行自定义脚本了。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "function", "function": function(){ // 执行一段js脚本 // 这里写js代码 alert(core.getStatus("atk")); // 弹窗显示勇士的攻击力 @@ -1822,11 +1774,11 @@ color为可选的,可以是一个字符串(#FF0000),或者一个RGBA数 `{"type":"function"}`需要有一个`"function"`参数,它是一个JS函数,里面可以写任何自定义的JS脚本;系统将会执行它。 -系统常见可能会被造塔所用到的的API都在[附录:API列表](api)中给出,请进行参照。 +系统常见可能会被造塔所用到的的API都在[API列表](api)中给出,请进行参照。 **警告:自定义脚本中只能执行同步代码,不可执行任何异步代码,比如直接调用core.changeFloor(...)之类都是不行的。** -[附录:API列表](api)中的所有异步API都进行了标记;如果你不确定一个函数是同步的还是异步的,请向小艾咨询。 +[API列表](api)中的所有异步API都进行了标记;如果你不确定一个函数是同步的还是异步的,请向小艾咨询。 如果需要异步的代码都需要用事件(insertAction)来执行,这样事件处理过程和录像回放才不会出错。 @@ -1929,7 +1881,6 @@ core.insertAction([ } ``` - 总之,记住如下两点: - 可以使用setBlock来更改一个图块。 @@ -2013,31 +1964,13 @@ if (core.getFlag("door",0)==2) { 如果要对某个怪物进行加点操作,则首先需要修改该怪物的`point`数值,代表怪物本身的加点数值。 -然后在脚本编辑中找到加点事件,双击进行修改。它将返回一个choices事件。修改此函数为我们需要的加点项即可。 +从V2.5.5开始,加点事件移动到了[公共事件](personalization#公共事件)之中,会通过传参的形式来传递怪物的加点值。 -``` js -////// 加点事件 ////// -"addPoint" : function (enemy) { - // 加点事件 - var point = enemy.point; - if (!core.flags.enableAddPoint || !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)} - ]}, - ] - } - ]; +```js +// 如果有加点 +var point = core.material.enemys[enemyId].point; +if (core.flags.enableAddPoint && point > 0) { + core.push(todo, [{ "type": "insert", "name": "加点事件", "args": [point] }]); } ``` @@ -2060,42 +1993,14 @@ if (core.getFlag("door",0)==2) { "textInList": "1F金币商店", // 在快捷商店栏中显示的名称 "use": "money", // 商店所要使用的。只能是"money"或"experience"。 "commonTimes": true, // 是否使用全局次数 - "mustEnable": true, // 如果未开启则不显示在状态栏中 - "need": "20+10*times*(times+1)", // 商店需要的金币/经验数值;可以是一个表达式,以times作为参数计算。 - // 这里用到的times为该商店的已经的访问次数。首次访问该商店时times的值为0。 - // 上面的例子是50层商店的计算公式。你也可以写任意其他的计算公式,只要以times作为参数即可。 - // 例如: "need": "25" 就是恒定需要25金币的商店; "need": "20+2*times" 就是第一次访问要20金币,以后每次递增2金币的商店。 - // 如果是对于每个选项有不同的计算公式,写 "need": "-1" 即可。可参见下面的经验商店。 - "text": "勇敢的武士啊,给我${need}金币就可以:", // 显示的文字,需手动加换行符。可以使用${need}表示上面的need值。 + "mustEnable": false, // 如果未开启则不显示在状态栏中 + "need": "20+10*times*(times+1)", // 商店需要的金币/经验数值;可以是一个表达式,以times(访问次数)作为参数计算。 + "text": "勇敢的武士啊,给我${need}金币就可以:", // 显示的文字。可以使用${need}表示上面的need值。 "choices": [ // 商店的选项 - {"text": "生命+800", "effect": "status:hp+=800"}, - // 如果有多个effect以分号分开,参见下面的经验商店 - {"text": "攻击+4", "effect": "status:atk+=4"}, - {"text": "防御+4", "effect": "status:def+=4"}, - {"text": "魔防+10", "effect": "status:mdef+=10"} - // effect可以对status,item和flag进行操作。 - // 必须是X+=Y的形式,其中Y可以是一个表达式,以status:xxx, item:xxx或flag:xxx为参数 - // 其他effect样例: - // "item:yellowKey+=1" 黄钥匙+1 - // "item:pickaxe+=3" 破墙镐+3 - // "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍 - ] - }, - { - "id": "expShop1", // 商店唯一ID - "name": "经验之神", - "icon": "pinkShop", - "textInList": "1F经验商店", - "use": "experience", // 该商店使用的是经验进行计算 - "need": "-1", // 如果是对于每个选项所需要的数值不同,这里直接写-1,然后下面选项里给定具体数值 - "text": "勇敢的武士啊,给我若干经验就可以:", - "choices": [ - // 在choices中写need,可以针对每个选项都有不同的需求。 - // 这里的need同样可以以times作为参数,比如 "need": "100+20*times" - {"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"}, + // effect可以对status,item和flag进行操作;必须是X+=Y的形式,其中Y可以是一个表达式 + {"text": "生命+800", "effect": "status:hp+=800"}, // 生命+800 + {"text": "攻击+4", "need": 30, "effect": "status:atk+=4"}, // 规定具体的数值 + {"text": "防御+2,魔防+4", "effect": "status:def+=2;status:mdef+=4"}, // 多个效果用分号分开 ] } ], @@ -2117,14 +2022,15 @@ if (core.getFlag("door",0)==2) { - choices 为商店的各个选项,是一个list,每一项是一个选项 - text为显示文字。请注意这里不支持 ${} 的表达式计算。 - effect 为该选项的效果;effect必须是 `status:xxx+=yyy`, `item:xxx+=yyy`或`flag:xxx+=yyy`的形式。即中间必须是+=符号。 - - 如有多个effect(例如升级全属性提升),使用分号分开,参见经验商店的写法。 + - 如有多个effect(例如升级全属性提升),使用分号分开。 像这样定义了全局商店后,即可在快捷栏中看到。 请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。 ``` js -"1,0": [ // 金币商店 +// 事件列表 +[ // 打开商店前,你也可以添加自己的剧情 // 例如,通过if来事件来判断是不是第一次访问商店,是的则显示一段文字(类似宿命的华音那样) {"type": "openShop", "id": "moneyShop1"}, // 这里的id要和data.js中你定义的商店ID完全一致 @@ -2141,6 +2047,27 @@ if (core.getFlag("door",0)==2) { 另外需要注意的一点就是,每层楼都有一个 canUseQuickShop 选项。如果该选项置为false则无法在该层使用快捷商店。 +**从V2.6开始,也提出了“公共事件化的全局商店”,即打开使用全局商店实际上是执行一个公共事件。** + +```js +"shops": [ + // 定义公共事件化的全局商店 + { + "id": "keyShop1", // 商店唯一ID + "textInList": "回收钥匙商店", // 在快捷商店栏中显示的名称 + "mustEnable": false, // 如果未开启则不显示在状态栏中 + "commonEvent": "回收钥匙商店", // 公共事件名 + "args": [], // 向该公共事件传递的参数 + } +] +``` + +`id`, `textInList`, `mustEnable`和上述完全相同。 + +`commonEvent`为公共事件名,即选择此项时要执行的公共事件。 + +`args`为向该公共事件传递的参数,参见[type:insert](#insert:插入公共事件或另一个地点的事件并执行)的说明。 + ## 系统引发的自定义事件 我们知道,所有自定义事件都是需要定义在`"x,y"`处,并且得让用户经过或撞上才能触发的。 @@ -2179,17 +2106,13 @@ if (core.getFlag("door",0)==2) { !> 多个机关门请分别设置开门变量如door1, door2等等。请勿存在两个机关门用相同的变量! -同样,为了实现类似于RMXP中,到达某一层后自动触发某段事件的效果,样板中还存在`firstArrive`事件。 - -当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。 +除此以外,每层楼还提供了`firstArrive`和`eachArrive`事件,分别为首次到达该楼层和每次到达该楼层时执行的事件。 ## 使用炸弹后的事件 上面的afterBattle事件只对和怪物进行战斗后才有会被处理。 -如果我们想在使用炸弹后也能触发一些事件(如开门),则可以在`functions.js`里面的`afterUseBomb`函数进行处理: - -!> V2.0版本可以直接在“脚本编辑 - 使用炸弹后的事件”中双击进行修改! +如果我们想在使用炸弹后也能触发一些事件(如开门),则可以在脚本编辑里面的`afterUseBomb`函数进行处理: ``` js ////// 使用炸弹/圣锤后的事件 ////// @@ -2205,47 +2128,42 @@ if (core.getFlag("door",0)==2) { } ``` -## 滑冰和推箱子事件 +## 滑冰事件 -最新的样板还支持滑冰和推箱子事件。 +从V2.6开始,滑冰事件被重写。现在的滑冰由公共事件执行。 -滑冰事件的数字是167,trigger为ski。 +在新版本中,冰面应该放在背景层上,上面可以放置道具、怪物、门等图块。 -当角色走上冰面时,将触发ski事件,并会一直向前滑行,直到撞上不可通行的块会触发事件(比如撞上怪物会触发battle,撞上门会触发openDoor等等),或者离开冰面为止。 +角色走上冰面后,将一直向前滑行,直到撞上不可通行的图块,或触发事件为止。 -!> 由于H5魔塔只有事件一层,因此滑冰的冰面上将无法摆放任何东西(如怪物,门或道具等);不过可以在战后/开门后/道具后的事件写转变图块成167,从而继续滑冰。 +如果撞上怪物将自动进行战斗,此战斗是强制的,打不过将直接死亡。 -!> 撞上怪物将触发battle进行战斗,该战斗是强制战斗,打不过将直接死亡。 +默认情况下,拾取冰面上道具后将停止滑冰行为。如果要继续滑冰,请在`afterGetItem`中插入公共事件:滑冰事件。打怪和开门同理。 + +!> 滑冰图块的数字是167,请勿修改此数字! + +## 推箱子事件 关于推箱子,存在三种状态:花(168),箱子(169)和已经推到花的箱子(170)。 !> 推箱子的前方不允许存在任何事件(花除外),包括已经禁用的自定义事件。 -推完箱子后将触发functions.js中的afterPushBox事件,你可以在这里进行开门判断。 +推完箱子后将触发脚本编辑中的afterPushBox函数,你可以在这里进行开门判断。 ``` js ////// 推箱子后的事件 ////// "afterPushBox" = function () { - - var noBoxLeft = function () { - // 地图上是否还存在未推到的箱子,如果不存在则返回true,存在则返回false - for (var i=0;i 如果reason不为空,则将会作为结局名! - -当失败(`{"type": "lose"}`,或者被怪强制战斗打死、被领域怪扣血死、中毒导致扣血死,路障导致扣血死等等)事件发生时,将调用`events.js`中的`lose`事件。其直接显示一段文字,并重新开始游戏。 - -``` js -////// 游戏失败事件 ////// -"lose": function(reason) { - core.ui.closePanel(); - var replaying = core.isReplaying(); - core.stopReplay(); - core.waitHeroToStop(function() { - core.drawText([ - "\t[结局1]你死了。\n如题。" - ], function () { - core.events.gameOver(null, replaying); - }); - }) -} -``` - -其参数reason为失败原因。你可以在这里修改失败界面时显示的文字。 - ========================================================================================== [继续阅读下一章:个性化](personalization) diff --git a/_docs/img/console.jpg b/_docs/img/console.jpg new file mode 100644 index 00000000..76d59378 Binary files /dev/null and b/_docs/img/console.jpg differ diff --git a/_docs/img/console1.jpg b/_docs/img/console1.jpg new file mode 100644 index 00000000..46d1f5ba Binary files /dev/null and b/_docs/img/console1.jpg differ diff --git a/_docs/img/elements.jpg b/_docs/img/elements.jpg new file mode 100644 index 00000000..d32ebaf2 Binary files /dev/null and b/_docs/img/elements.jpg differ diff --git a/_docs/img/plugin.jpg b/_docs/img/plugin.jpg new file mode 100644 index 00000000..d0d69bef Binary files /dev/null and b/_docs/img/plugin.jpg differ diff --git a/_docs/img/sources.jpg b/_docs/img/sources.jpg new file mode 100644 index 00000000..7fce53b3 Binary files /dev/null and b/_docs/img/sources.jpg differ diff --git a/_docs/personalization.md b/_docs/personalization.md index f960eed9..c8f517e6 100644 --- a/_docs/personalization.md +++ b/_docs/personalization.md @@ -1,6 +1,6 @@ # 个性化 -?> 目前版本**v2.5.5**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * 有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。 @@ -21,10 +21,14 @@ HTML5魔塔是使用画布(canvas)来绘制,存在若干个图层,它们 - route**[D]**:路线层;主要用来绘制勇士的行走路线图。 (z-index: 95) - paint**[D]**:绘图层;主要用来进行绘图模式。(z-index: 95) - curtain:色调层;用来控制当前楼层的画面色调 (z-index: 125) -- image1\~50**[D]**:图片层;用来绘制图片等操作。(z-index: 100+code, 101~150;也就是图片编号在1~25的在色调层之下,26~50的在色调层之上) -- ui:UI层;用来绘制一切UI窗口,如剧情文本、怪物手册、楼传器、系统菜单等等 (z-index: 160) +- image1\~50**[D]**:图片层;用来绘制图片等操作。(z-index: 100+code, 101~150) +- ui:UI层;用来绘制一切UI窗口,如剧情文本、怪物手册、楼传器、系统菜单等等 (z-index: 140) - data:数据层;用来绘制一些顶层的或更新比较快的数据,如左上角的提示,战斗界面中数据的变化等等。 (z-index: 170) +请注意:显示图片事件将自动创建一个图片层,z-index是100+图片编号。 + +而,色调层的z-index是25,ui层的z-index是140;因此,图片编号在1~24的将被色调层遮挡,25~40的将被ui层遮挡,41~50的将遮挡UI层。 + ### 动态创建canvas 从V2.5.3开始,可以在H5样板中任意动态创建canvas并进行使用。 @@ -104,11 +108,11 @@ core.fillText('test', '这是一段文字', 10, 30, '#FF0000', '16px Verdana'); 从V2.5.4开始,贴图也允许进行帧动画,只要设置第五项的数值。 ``` js -"images": [[96,120,"bg.jpg",0]], // 背景图;你可以选择一张或多张图片来作为背景/前景素材。 -"images": [], // 无任何背景图 -"images": [[32,32,"house.png",0], [160,170,"bed.png",1]] // 在(32,32)放一个house.png在背景层,且(160,170)放bed.png在前景层 -"images": [[96,120,"tree.png",2]] // 如果写2,则会自动调节遮挡效果 -"images": [[64,0,"x.png",1,4]] // 这是一个前景层的4帧动画贴图 +[[96,120,"bg.jpg",0]] // 背景图;你可以选择一张或多张图片来作为背景/前景素材。 +[] // 无任何背景图 +[[32,32,"house.png",0], [160,170,"bed.png",1]] // 在(32,32)放一个house.png在背景层,且(160,170)放bed.png在前景层 +[[96,120,"tree.png",2]] // 如果写2,则会自动调节遮挡效果 +[[64,0,"x.png",1,4]] // 这是一个前景层的4帧动画贴图 ``` images为一个数组,代表当前层所有作为背景素材的图片信息。每一项为一个五元组,分别为该背景素材的x,y,图片名,遮挡方式和帧数。 @@ -129,18 +133,21 @@ images为一个数组,代表当前层所有作为背景素材的图片信息 关于楼层贴图和前景、背景层的层叠覆盖关系,默认是:**地板 - 背景贴图 - 背景图块 - 事件 - 勇士 - 前景贴图 - 前景图块**。 -可以通过修改`libs/maps.js`的`drawMap`函数中下面三行的顺序来改变其覆盖关系。 +可以通过修改`libs/maps.js`的`drawBg`和`drawFg`函数来改变其覆盖关系。 ``` js -// ----- 可以调整这三行的顺序来修改覆盖关系;同层画布上,后绘制的覆盖先绘制的 -// ----- ui.js的drawThumbnail函数也需要对应进行修改。 - -// 绘制楼层贴图 -core.maps.drawFloorImages(floorId, images); -// 绘制背景层图块 -core.maps.drawBgFgMap(floorId, core.canvas.bg, "bg", true); -// 绘制前景层图块 -core.maps.drawBgFgMap(floorId, core.canvas.fg, "fg", true); +////// 绘制背景层 ////// +maps.prototype.drawBg = function (floorId, ctx) { + var onMap = ctx == null; + if (onMap) { + ctx = core.canvas.bg; + core.clearMap(ctx); + } + this._drawBg_drawBackground(floorId, ctx); + // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。 + this._drawFloorImages(floorId, ctx, 'bg'); + this._drawBgFgMap(floorId, ctx, 'bg', onMap); +} ``` 楼层贴图可以被事件隐藏和显示,详见[隐藏贴图](event#hideFloorImg:隐藏贴图)的写法。 @@ -192,7 +199,7 @@ ID必须由数字字母下划线组成,数字在1000以内,且均不能和 之后刷新编辑器即可。 -对于怪物和道具,我们也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 +我们也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 素材注册完毕后,即可在游戏中正常使用,也可以被地图生成器所识别(需要重开地图生成器)。 @@ -206,85 +213,6 @@ ID必须由数字字母下划线组成,数字在1000以内,且均不能和 2. 下拉框选择autotile,然后点“追加” 3. 看到成功的提示后刷新编辑器即可。 - - - -### 地图生成器使用自定义素材 - -地图生成器是直接从js文件中读取数字-图标对应关系的。 - -因此,在你修改了icons.js和maps.js两个文件,也就是将素材添加到游戏后,地图生成器的对应关系也将同步更新。 - ### 额外素材 从V2.4.2开始,HTML5魔塔样板开始支持额外素材。 @@ -300,6 +228,7 @@ ID必须由数字字母下划线组成,数字在1000以内,且均不能和 **该素材的宽高必须都是32的倍数,且图片上的总图块数不超过1000(即最多有1000个32*32的图块在该图片上)。** ```js +// 在全塔属性中的tilesets导入素材 "tilesets": ["1.png", "2.png"] // 导入两个额外素材,文件名分别是1.png和2.png ``` @@ -350,19 +279,6 @@ core.status.hero.atk += core.values.redJewel + 2*ratio 具体过程比较复杂,需要一定的JS能力,在这里就不多说了,有需求可以找`艾之葵`进行了解。 -但值得一提的是,我们可以使用`core.hasItem(name)` 来判断是否某个道具是否存在。例如下面是passNet(通过路障处理)的一部分: - -``` js -/****** 经过路障 ******/ -events.prototype.passNet = function (data) { - // 有鞋子 - if (core.hasItem('shoes')) return; - if (data.event.id=='lavaNet') { // 血网 -// ... 下略 -``` - -我们进行了一个简单的判断,如果拥有绿鞋,则不进行任何路障的处理。 - ### 实战!拿到神圣盾后免疫吸血、领域、夹击效果 1. 在itemEffect中修改拿到神圣盾时的效果,标记一个自定义Flag。 @@ -421,109 +337,20 @@ function (enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) { 对于特殊的塔,我们可以考虑修改楼传事件来完成一些特殊的要求,比如镜子可以按楼传来切换表里。 -要修改楼传事件,需要进行如下几步: +要修改楼传事件,需要进行如下两步: -1. 截获楼传的点击事件。在control.js中找到useFly函数,并将其替换成如下内容: -``` js -////// 点击楼层传送器时的打开操作 ////// -control.prototype.useFly = function (need) { - if (core.isMoving()) { - core.drawTip("请先停止勇士行动"); - return; - } - if (core.status.lockControl || core.status.event.id != null) return; - - if (core.canUseItem('fly')) core.useItem('fly'); - else core.drawTip("当前无法使用"+core.material.items.fly.name); -} -``` +1. 重写楼传的点击事件。在插件中对`core.control.useFly进行重写`。详细代码参见[重写点击楼传事件](script#重写点击楼传事件)。 2. 修改楼传的使用事件。和其他永久道具一样,在地图编辑器的图块属性中修改楼传的useItemEffect和canUseItemEffect两个内容。例如: ``` js "useItemEffect": "core.insertAction([...])" // 执行某段自定义事件,或者其他脚本 "canUseItemEffect": "true" // 任何时候可用 ``` -修改时,请先把`null`改成空字符串`""`,然后再双击进行编辑。 - +除了覆盖楼传事件外,对于快捷商店、虚拟键盘等等也可以进行覆盖,只不过是仿照上述代码重写对应的函数(`openQuickShop`,`openKeyBoard`)即可。 ## 自定义怪物属性 -如果你对现有的怪物不满意,想自行添加怪物属性也是可以的。具体参见脚本编辑-getSpecials。 +如果你对现有的怪物不满意,想自行添加怪物属性也是可以的。具体参见脚本编辑的getSpecials。 你需自己指定一个special数字,修改属性名和属性提示文字。后两者可以直接写字符串,或写个函数传入怪物。 @@ -552,7 +379,8 @@ case 89: // 使用该按键的keyCode,比如Y键就是89 // ... 在这里写你要执行脚本 // **强烈建议所有新增的自定义快捷键均能给个对应的道具可点击,以方便手机端的行为** if (core.hasItem('...')) { - core.useItem('...'); + core.status.route.push("key:0"); // 记录按键到录像中 + core.useItem('...', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像 } break; @@ -560,6 +388,10 @@ case 89: // 使用该按键的keyCode,比如Y键就是89 强烈建议所有新增的自定义快捷键均给个对应的永久道具可点击,以方便手机端的行为。 +使用`core.status.route.push("key:"+keyCode)`可以将这次按键记录在录像中。 + +!> 如果记录了按键,且使用道具的话,需要将useItem的第二个参数设为true,避免重复记录! + 可以使用altKey来判断Alt键是否被同时按下。 ## 公共事件 @@ -574,13 +406,9 @@ case 89: // 使用该按键的keyCode,比如Y键就是89 ## 插件系统 -在H5中,提供了“插件”系统。具体参见“脚本编辑 - 插件编写”。 +在H5中,提供了“插件”系统。在V2.6中提供了一个插件下拉框,用户可以自行创建和写插件。 -![插件编写](./img/plugin.png) - -当我们在这上面定义了自己需要的函数(插件后),就可以通过任何方式进行调用。 - -在这个插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。 +在插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。 下面是一个很简单的例子,我编写一个插件函数,其效果是让勇士生命值变成原来的x倍,并令面前的图块消失。 @@ -599,11 +427,13 @@ this.myfunc = function(x) { 网站上也提供了一个[插件库](https://h5mota.com/plugins/),欢迎大家把自己写的插件进行共享。 +从V2.6开始,在插件中用`this.xxx`定义的函数将会被转发到core中。例如上述的`myfunc`除了`core.plugin.myfunc`外也可以直接`core.myfunc`调用。 + +详见[函数的转发](script#函数的转发)。 + ## 标题界面事件化 -从V2.5.3开始,我们可以将标题界面的绘制和游戏开始用事件来完成。可以通过绘制画布、 - -全塔属性,flags中的startUsingCanvas可以决定是否开启标题界面事件化。 +从V2.5.3开始,我们可以将标题界面的绘制和游戏开始用事件来完成。可以通过绘制画布、全塔属性,flags中的startUsingCanvas可以决定是否开启标题界面事件化。 然后就可以使用“事件流”的形式来绘制标题界面、提供选项等等。 @@ -619,36 +449,70 @@ this.myfunc = function(x) { 从V2.5.3以后,我们可以给手机端增加按键了,这样将非常有利于技能的释放。 -当用户在竖屏模式下点击工具栏,就会在工具栏按钮和快捷键模式之间进行切换。 +用户在菜单栏打开“拓展键盘”后,在竖屏模式下点击工具栏,就会在工具栏按钮和快捷键模式之间进行切换。 -切换到快捷键模式后,可以点1-7,分别等价于在电脑端按键1-7。 +切换到快捷键模式后,可以点1-8,分别等价于在电脑端按键1-8。 可以在脚本编辑的onKeyUp中定义每个快捷键的使用效果,比如使用道具或释放技能等。 -默认值下,1使用破,2使用炸,3使用飞,4使用其他存在的道具,5-7未定义。可以相应修改成自己的效果。 +默认值下,1使用破,2使用炸,3使用飞,4使用其他存在的道具,5-8未定义。可以相应修改成自己的效果。 -也可以替换icons.png中的对应图标,以及修改main.js中`main.statusBar.image.btn1~7`中的onclick事件来自定义按钮和对应按键。 +也可以替换icons.png中的对应图标,以及修改main.js中`main.statusBar.image.btn1~8`中的onclick事件来自定义按钮和对应按键。 非竖屏模式下、回放录像中、隐藏状态栏中,将不允许进行切换。 -## 自定义状态栏(新增显示项) +## 自绘状态栏 + +从V2.5.3开始允许自绘状态栏。要自绘状态栏,则应该打开全塔属性中的`statusCanvas`开关。 + +自绘模式下,全塔属性中的`statusCanvasRowsOnMobile`将控制竖屏模式下的状态栏行数。 + +开启自绘模式后,可以在脚本编辑的`drawStatusBar`中自行进行绘制。 + +横屏模式下的状态栏为`129x416`(15x15则是`149x480`);竖屏模式下的状态栏为`416*(32*rows+9)`(15x15是480)。 + +具体可详见脚本编辑的`drawStatusBar`函数。 + +## 自定义状态栏的显示项 在V2.2以后,我们可以自定义状态栏背景图(全塔属性 - statusLeftBackground)等等。 但是,如果我们还想新增其他项目的显示,比如攻速或者暴击,该怎么办? -需要进行如下几个操作: +我们可以[自绘状态栏](#自绘状态栏),或者采用下面两个方式之一来新增。 + +### 利用已有项目 + +一个最为简单的方式是,直接利用已有项目。 + +例如,如果本塔中没有技能栏,则可以使用技能栏所对应的显示项。 + +1. 覆盖project/icons.png中技能的图标 +2. 打开全塔属性的enableSkill开关 +3. 在脚本编辑-updateStatusBar中可以直接替换技能栏的显示内容 + +``` + // 设置技能栏 + if (core.flags.enableSkill) { + // 替换成你想显示的内容,比如你定义的一个flag:abc。 + core.setStatusBarInnerHTML('skill', core.getFlag("abc", 0)); + } +``` + +### 额外新增新项目 + +如果是在需要给状态栏新定义项目,则需要进行如下几个操作: 1. 定义ID;比如攻速我就定义speed,暴击可以简单的定义baoji;你也可以定义其他的ID,但是不能和已有的重复。这里以speed为例。 -2. 在index.html的statusBar中(44行起),进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。 +2. 在index.html的statusBar中(46行起),进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。 ``` html

``` -3. 在editor.html中的statusBar(323行起),仿照第二点同样添加;这一项如果不进行则会地图编辑器报错。editor-mobile.html同理。 -4. 使用便捷PS工具,打开icons.png,新增一行并将魔力的图标P上去;记下其索引比如37(从0开始数)。 +3. 在editor.html中的statusBar(383行起),仿照第二点同样添加;这一项如果不进行则会地图编辑器报错。editor-mobile.html同理。 +4. 使用便捷PS工具,打开project/icons.png,新增一行并将魔力的图标P上去;记下其索引比如37(从0开始数)。 5. 在main.js的this.statusBar中增加图片、图标和内容的定义。 ``` js this.statusBar = { @@ -674,17 +538,15 @@ core.statusBar.speed.innerHTML = core.getFlag('speed', 0); ## 技能塔的支持 -其实,在HTML5上制作技能塔是完全可行的。 - 要支持技能塔,可能需要如下几个方面: +从V2.5开始,内置了"二倍斩"技能,可以仿照其制作自己的技能。 + - 魔力(和上限)的添加;技能的定义 - 状态栏的显示 - 技能的触发(按键与录像问题) - 技能的效果 -从V2.5开始,内置了"二倍斩"技能,可以仿照其制作自己的技能。 - ### 魔力的定义添加;技能的定义 从V2.5开始,提供了status:mana选项,可以直接代表当前魔力值。 @@ -697,6 +559,8 @@ core.statusBar.speed.innerHTML = core.getFlag('speed', 0); 如果flag:skill不为0,则代表当前处于某个技能开启状态,且状态栏显示flag:skillName值。伤害计算函数中只需要对flag:skill进行处理即可。 +!> 关于魔力上限:样板中默认没有提供status:manamax + ### 状态栏的显示 从V2.5开始,魔力值和技能名的状态栏项目已经被添加,可以直接使用。 @@ -706,9 +570,14 @@ core.statusBar.speed.innerHTML = core.getFlag('speed', 0); ``` js // 设置魔力值 if (core.flags.enableMana) { - // 也可以使用flag:manaMax来表示最大魔力值 - // core.status.hero.mana = Math.max(core.status.hero.mana, core.getFlag('manaMax', 10)); - // core.statusBar.mana.innerHTML = core.status.hero.mana + "/" + core.getFlag('manaMax', 10); + // status:manamax 只有在非负时才生效。 + if (core.status.hero.manamax != null && core.status.hero.manamax >= 0) { + core.status.hero.mana = Math.min(core.status.hero.mana, core.status.hero.manamax); + core.setStatusBarInnerHTML('mana', core.status.hero.mana + "/" + core.status.hero.manamax); + } + else { + core.setStatusBarInnerHTML("mana", core.status.hero.mana); + } } // 设置技能栏 if (core.flags.enableSkill) { @@ -761,14 +630,15 @@ else { // 关闭技能 case 87: // W:开启技能“二倍斩” // 检测技能栏是否开启,是否拥有“二倍斩”这个技能道具 if (core.flags.enableSkill && core.hasItem('skill1')) { - core.useItem('skill1'); + core.status.route.push("key:87"); + core.useItem('skill1', true); } break; ``` 在勇士处于停止的条件下,按下W键时,判断技能的道具是否存在,如果存在再使用它。 -!> 1,2,3这三个键被默认绑定到了破炸飞;如果想用的话也是一样,只不过是把已有的实现进行替换。 +!> 由于现在手机端存在拓展键盘,也强烈建议直接覆盖1-8的使用效果,这样手机端使用也非常方便。 ### 技能的效果 @@ -816,141 +686,6 @@ if (core.flags.enableSkill) { 通过上述这几种方式,我们就能成功的让H5支持技能啦! -## 成就系统 - -我们还可以给HTML5魔塔增加成就系统。注意到成就是和游戏相关,因此需要使用getLocalStorage而不是getFlag判定。 - -可将下面的代码粘贴到脚本编辑 - 插件编写中。 - -``` js -// 所有成就项的定义 -this.achievements = [ - // 每行一个,分别定义flag、名称、描述、是否存在提示、成就点数 - {"flag": "a1", "name": "成就1", "text": "成就1的达成描述", "hint": false, "point": 1}, - // 可以继续往后新增其他的。 -]; - -// 达成成就;如 core.plugin.achieve("a1") 即达成a1对应的成就 -this.achieve = function (flag) { - // 获得已达成的成就;如果跟存档而不是跟游戏则改成getFlag - var achieved = core.getLocalStorage("achievements", []); - var point = core.getLocalStorage("achievePoint", 0); - // 已经获得该成就 - if (achieved.indexOf(flag)>=0) return; - // 尝试达成成就;找到对应的成就项 - this.achievements.forEach(function (one) { - if (one.flag == flag) { - // 执行达成成就的操作;也可以自行在上面加上达成成就后的事件 - core.insertAction("\t[达成成就:"+one.name+"]"+one.text); - point += one.point || 0; - } - }); - achieved.push(flag); - // 存入localStorage中;如果跟存档走则使用setFlag - core.setLocalStorage("achievements", achieved); - core.setLocalStorage("achievePoint", point); -} - -// 获得所有成就说明;这里简单使用两个insertAction,你也可以修改成自己的实现 -// 简单一点的可以使用insertAction+剧情文本;稍微复杂一点的可以使用图片化文本等;更复杂的可以自绘UI。 -this.getAchievements = function () { - var achieved = core.getLocalStorage("achievements", []); - var yes = [], no = []; - // 对所有成就进行遍历 - this.achievements.forEach(function (one) { - // 检测是否达成 - if (achieved.indexOf(one.flag)>=0) { - yes.push(one.name+":"+one.text); - } - else { - no.push(one.name+":"+(one.hint?one.text:"达成条件请自行探索")); - } - }); - core.insertAction([ - "\t[已达成的成就]"+(yes.length==0?"暂无":yes.join("\n")), - "\t[尚未达成的成就]"+(no.length==0?"暂无":no.join("\n")) - ]); -} -``` - - - -## 多角色的支持 - -其实,我们的样板还能支持多角色的制作。比如《黑·白·间》之类的塔也是完全可以刻的。 - -你只需要如下几步来达到多角色的效果。 - -1. 每个角色弄一张行走图。相关信息参见[自定义事件:setHeroIcon](event#setHeroIcon:更改角色行走图)。 -2. [覆盖楼传事件](#覆盖楼传事件),这样可以通过点工具栏的楼层传送按钮来切换角色。当然你也完全可以自己写一个道具,或[自定义快捷键](#自定义快捷键)来进行绑定。 -3. 将下述代码直接贴入脚本编辑 - 插件编写中。 - ``` js - // 所有需要保存的内容;这些保存的内容不会多角色共用,在切换时会进行恢复。 - // 你也可以自行新增或删除,比如不共用金币则可以加上"money"的初始化,不共用道具则可以加上"items"的初始化, - // 多角色共用hp的话则删除hp,等等。总之,不共用的属性都在这里进行定义就好。 - var hero1 = { // 1号勇士(默认的是0号) - "floorId": "MT0", // 该角色楼层ID - "icon": "hero1.png", // 角色的行走图名称 - "name": "1号角色", - "lv": 1, - "hp": 1000, - "atk": 10, - "def": 10, - "mdef": 0, - "loc": {"x": 0, "y": 0, "direction": "up"}, - // 如果道具不共用就将下面这句话取消注释 - // "items": {"keys":{"yellowKey":0,"blueKey":0,"redKey":0},"tools":{},"constants":{}} - } - // 也可以类似新增其他勇士 - // var hero2 = { ... - - var heroCount = 2; // 包含默认的在内总共多少个勇士,该值需手动修改。 - - // 初始化该勇士 - this.initHeros = function () { - core.status.hero.icon = "hero.png"; - core.setFlag("hero1", core.clone(hero1)); // 将属性值存到变量中 - // core.setFlag("hero2", core.clone(hero2)); // 更多的勇士... - } - - // 切换勇士 - this.changeHero = function (toHeroId) { - var currHeroId = core.getFlag("heroId", 0); // 获得当前角色ID - if (!core.isset(toHeroId)) { - toHeroId = (currHeroId+1)%heroCount; - } - if (currHeroId == toHeroId) return; - - var saveList = Object.keys(hero1); - - // 保存当前内容 - var toSave = {}; - saveList.forEach(function(name) { - if (name=='floorId') toSave[name] = core.status.floorId; // 楼层单独设置 - else toSave[name] = core.clone(core.status.hero[name]); // 使用core.clone()来创建新对象 - }) - - core.setFlag("hero"+currHeroId, toSave); // 将当前角色信息进行保存 - var data = core.getFlag("hero"+toHeroId); // 获得要切换的角色保存内容 - - // 设置角色的属性值 - saveList.forEach(function(name) { - if (name != 'floorId') - core.status.hero[name] = core.clone(data[name]); - }) - - // 插入事件:改变角色行走图并进行楼层切换 - core.insertAction([ - {"type": "setHeroIcon", "name": data.icon||"hero.png"}, // 改变行走图 - {"type": "changeFloor", "floorId": data.floorId, "loc": [data.loc.x, data.loc.y], - "direction": data.loc.direction, "time": 0} // 楼层切换事件 - ]) - core.setFlag("heroId", toHeroId); // 保存切换到的角色ID - } - ``` -3. 在脚本编辑 - setInitData中加上`core.plugin.initHeros()`来初始化新勇士。(写在`core.events.afterLoadData()`后,反大括号之前。) -4. 如果需要切换角色(包括事件、道具或者快捷键等),可以直接调用自定义JS脚本:`core.plugin.changeHero();`进行切换。也可以指定参数调用`core.plugin.changeHero(1)`来切换到某个具体的勇士上。 - ## 系统使用的flag变量 众所周知,自定义flag变量都可以任意定义并取用(未定义直接取用的flag默认值为0)。 @@ -979,4 +714,4 @@ this.getAchievements = function () { ========================================================================================== -[继续阅读附录:所有API列表](api) +[继续阅读脚本](script) diff --git a/_docs/script.md b/_docs/script.md new file mode 100644 index 00000000..96a48838 --- /dev/null +++ b/_docs/script.md @@ -0,0 +1,324 @@ +# 脚本 + +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * + +在V2.6版本中,基本对整个项目代码进行了重写,更加方便造塔者的使用和复写函数。 + +## 控制台的使用 + +在Chrome浏览器中,按(Ctrl+Shift+I)可打开控制台。 + +![](img/console.jpg) + +控制台中有很多的标签,最常用的是`Console`, `Sources`和`Elements`。 + +有关更详尽的控制台使用可自行搜索[Chrome开发者工具](https://www.baidu.com/s?wd=chrome%20%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7)了解更多。 + +### Console:命令行 + +Console页为命令行。可以在这里输入一些命令进行调试。 + +比如,进入游戏后,输入`core.status.hero.atk`即可获得勇士的当前攻击力数值。`core.status.hero.atk=100`可以设置攻击力为100。 + +更多的API可参见[附录:API列表](#附录:API列表)。 + +除此以外,游戏中的报错等信息也是可以在Console中进行查看的。 + +![](img/console1.jpg) + +### Sources:断点调试 + +Sources页可以查看JS源代码,并进行断点调试等。 + +例如,如果相对脚本编辑中的伤害计算函数进行断点调试: +1. 在左边找到`project/functions.js`,单击打开文件 +2. 并找到对应的行(可以Ctrl+F搜索),比如搜索`getDamageInfo` +3. 在行号上点一下打断点,会出现一个蓝色标签 + +之后,当代码运行到你的断点处时,将自动停止运行。 + +![](img/sources.jpg) + +可以将鼠标移动到变量上,将弹窗形式显示这个变量的各项数值,从而查看变量值是否符合预期。 + +图中红色框内有几个按钮,从左到右分别是:**继续执行**,**执行到下一行**,**进入当前函数**,**跳出当前函数**,**单步执行**。 + +通过这几个按钮,可以一行一行的对代码进行执行,执行过程中能不断查看各个变量的数值变化,从而定位问题所在。 + +红圈下方是Call Stack,即当前的函数调用链(从哪些地方调用过来的)。 + +Sources还有更多有趣的功能,在此不做介绍,有兴趣的可自行网上搜索了解。 + +### Elements:网页元素查看 + +Elements页可以查看网页的源代码,调整css布局等。 + +![](img/elements.jpg) + +不过对魔塔样板来说,最重要的是红圈中的按钮。点击此按钮可以进入**手机模式**。 + +手机模式下,左边可以对屏幕分辨率进行调整和模拟。 + +这可以很有效的帮我们进行测试样板在手机端的表现。 + +## 整体项目架构 + +``` text +├── /_server/ # 为可视化地图编辑器提供一些支持的目录 +├── /libs/ # ---- 系统库目录 ---- +│ ├─ /thirdparty/ # 游戏所用到的第三方库文件 +│ ├─ actions.js # 用户交互处理 +│ ├─ core.js # 系统核心文件(游戏入口,接口&转发) +│ ├─ control.js # 游戏逻辑控制 +│ ├─ data.js # 全塔属性等 +│ ├─ enemys.js # 怪物相关处理 +│ ├─ events.js # 各个事件的执行 +│ ├─ icons.js # 图标和素材 +│ ├─ items.js # 道具效果 +│ ├─ loader.js # 各个资源加载 +│ ├─ maps.js # 地图数据和绘制 +│ ├─ ui.js # UI窗口绘制 +│ └─ utils.js # 工具类函数 +├── /project/ # ---- 项目目录 ---- +│ ├─ /animates/ # 动画目录 +│ ├─ /floors/ # 楼层文件 +│ ├─ /images/ # 图片素材 +│ ├─ /sounds/ # bgm和音效 +│ ├─ data.js # 全塔属性 +│ ├─ enemys.js # 怪物属性 +│ ├─ events.js # 公共事件 +│ ├─ functions.js # 脚本编辑 +│ ├─ icons.js # 素材和ID的对应关系定义 +│ ├─ items.js # 道具的定义和效果 +│ ├─ maps.js # 地图和数字的对应关系 +│ └─ plugins.js # 自定义插件 +├── /常用工具/ # 辅助造塔的小工具 +├── editor.html # 地图编辑器 +├── editor-mobile.html # 手机版的地图编辑器 +├── index.html # 主程序,游戏的入口 +├── main.js # JS程序的入口,将动态对所需JS进行加载 +├── style.css # 游戏所需要用到的样式表 +└── 启动服务.exe # 一个本地的HTTP服务器,通过它来运行游戏 +``` + +`_server`为**地图编辑器目录**,里面存放了地图编辑器相关的各项内容。 + +`libs`为**系统库目录**,里面存放了各个系统核心函数。 + +从V2.6开始,请勿直接修改libs下的代码,如有需要修改系统库函数请尝试在插件中[复写函数](#复写函数)。 + +`project`为**项目目录**,你所造的塔的数据全部存放在project下。在不同样板之间接档也是直接迁移project目录即可。 + +## 函数的转发 + +在本样板中,`core.js`里面基本是没有定义什么函数的,所有的游戏内函数都在其他几个文件中实现。 + +例如,常见的获得某个变量值`getFlag`是定义在`control.js`中的: + +```js +////// 获得某个自定义变量或flag ////// +control.prototype.getFlag = function(name, defaultValue) { + if (!core.status.hero) return defaultValue; + var value = core.status.hero.flags[name]; + return value != null ? value : defaultValue; +} +``` + +也就是,我们可以通过`core.control.getFlag(name, value)`来调用此函数。 + +但是这样会十分不便,我们希望能直接调用`core.getFlag(name, value)`,而不需要中间的control。 + +为了达到这个目的,样板设置了**函数转发**,即**将其他文件中定义的函数,转发到core中执行**。 + +上述`getFlag`代码的转发实际上是增加了如下函数: + +```js +////// getFlag函数的转发 ////// +core.getFlag = function (name, defaultValue) { + return core.control.getFlag(name, defaultValue); +} +// 转发后,即可通过 core.getFlag() 来实际调用 core.control.getFlag() +``` + +转发是自动完成的,其满足如下两条规则: +- **在libs中其他文件定义的函数,如果不以下划线`_`开头,就会进行转发。** +- **如果core中已经存在同名函数,则会在控制台中打出一条报错信息,并不转发该函数。** + +具体函数的转发实现代码可参见`core.js`的`_forwardFunc`函数。 + +!> 除此以外,插件中以`this.xxx`来定义的函数也会被转发! + +例如,你可以直接调用`core.drawLight()`来实际调用插件中的`core.plugin.drawLight`。 + +## 插件编写 + +插件编写是H5魔塔的一个重大特点,从V2.0.1引入,并逐渐发扬光大。 + +对于有一定脚本经验的人来说,可以编写插件来实现各种各样的功能,包括且不仅限于拓展功能的实现,系统代码的复写等等。 + +在V2.5.5以前,插件位置都在脚本编辑中;从V2.6开始则迁移到了新的下拉框中,并进行了切分。 + +你也可以创建自己的插件。 + +![](img/plugin.jpg) + +新的插件切分和原来的单插件使用方法完全一致,单纯进行了切分而已。可参见已有的`init`和`drawLight`的样例。 + +拆分的意义主要是将各个可能的功能独立出来,避免单个框内内容太长,过大和混杂等。 + +在V2.6中,应当每个独立的额外功能实现都新建一个自己的插件,这样也方便进行拓展,例如打包迁移到别的塔上,或发布在网页插件库中。 + +另外一点需要注意的是,所有插件的初始化都会在系统资源加载之前,此时图片等资源尚未进行加载。 + +在所有资源加载完毕时,将会执行init插件中的_afterLoadResources函数,可以在这里对资源进行一些操作,比如切分图片等。 + +```js +function () { + console.log("插件编写测试"); + + // 可以写一些直接执行的代码 + // 在这里写的代码将会在【资源加载前】被执行,此时图片等资源尚未被加载。 + // 请勿在这里对包括bgm,图片等资源进行操作。 + + + this._afterLoadResources = function () { + // 本函数将在所有资源加载完毕后,游戏开启前被执行 + // 可以在这个函数里面对资源进行一些操作,比如切分图片等。 + + // 这是一个将assets.png拆分成若干个32x32像素的小图片并保存的样例。 + // var arr = core.splitImage("assets.png", 32, 32); + // for (var i = 0; i < arr.length; i++) { + // core.material.images.images["asset"+i+".png"] = arr[i]; + // } + + } + + // 可以在任何地方(如afterXXX或自定义脚本事件)调用函数,方法为 core.plugin.xxx(); + // 从V2.6开始,插件中用this.XXX方式定义的函数也会被转发到core中,详见文档-脚本-函数的转发。 +} +``` + +网站上提供了一个插件库,[https://h5mota.com/plugins/](https://h5mota.com/plugins/),上面有一些大家分享的插件,可供使用。 + +可以查看附录中的[API列表](api)来查看所有的系统API内容。 + +## 复写函数 + +样板的功能毕竟是写死的,有时候我们也需要修改样板的一些行为。 + +在V2.6以前,需要直接打开libs目录下的对应文件并进行修改。但是开libs下的文件就会出现各种问题: + +- 不容易记得自己修改过什么,而且如果改错了很麻烦 + - 例如,直接修改了某函数加了新功能,结果过段时间发现不需要,想删掉,但是这时候已经很难找到自己改过了什么了。 + - 或者,如果代码改错了,不断往上面打补丁,也只会使得libs越来越乱,最后连自己做过什么都不记得。 +- 不容易随着新样板接档进行迁移 +- 不方便能整理成新的插件在别的塔使用(总不能让别的塔也去修改libs吧) +- …… + +好消息的是,从V2.6开始,我们再也不需要开文件了,而是可以直接在插件中对原始函数进行复写。 + +函数复写的好处如下: + +- 不会影响系统原有代码。 + - 即使写错了或不需要了,也只用把插件中的函数注释或删除即可,不会对原来的系统代码产生任何影响。 +- 清晰明了。很容易方便知道自己修改过什么,尤其是可以和系统原有代码进行对比。 +- 方便整理成新的插件,给其他的塔使用。 + +如果我想对xxx文件中的yyy函数进行重写,其模式一般是:`core.xxx.yyy = function (参数列表) { ... }` + +下面是几个例子,从简单到复杂。 + +### 重写怪物手册的背景图绘制,使用winskin而不是默认的黑色 + +直接重写怪物手册的背景图绘制,使用`core.drawBackground`来用winskin绘制一个背景图。 + +```js +// 重写ui.js中的_drawBook_drawBackground函数 +core.ui._drawBook_drawBackground = function () { + // core.__PIXELS__为定义的一个宏,对于13x13的值是416,对于15x15的值是480 + core.drawBackground(0, 0, core.__PIXELS__, core.__PIXELS__); +} +``` + +### 重写点击楼传事件 + +重写点击楼传事件,使得点击楼传按钮时能使用一个道具(比如item:fly)。 + +```js +// 重写events.js的useFly函数,即点击楼传按钮时的事件 +core.events.useFly = function (fromUserAction) { + if (core.isMoving()) { + core.drawTip("请先停止勇士行动"); + return; + } + if (core.status.lockControl || core.status.event.id != null) return; + + if (core.canUseItem('fly')) core.useItem('fly'); + else core.drawTip("当前无法使用"+core.material.items.fly.name); +} +``` + +其他的几个按钮,如快捷商店`openQuickShop`,虚拟键盘`openKeyBoard`的重写也几乎完全一样。 + +### 楼层切换时根据flag来播放不同的音效 + +整体复制并重写整个楼传切换前的函数,将`core.playSound('floor.mp3')`替换成根据flag来判定。 + +```js +// 复制重写events.js中的_changeFloor_beforeChange,修改音效 +core.events._changeFloor_beforeChange = function (info, callback) { + // 直接替换原始函数中的 core.playSound('floor.mp3'); + if (core.getFlag("floorSound") == 0) core.playSound('floor0.mp3'); + if (core.getFlag("floorSound") == 1) core.playSound('floor1.mp3'); + if (core.getFlag("floorSound") == 2) core.playSound('floor2.mp3'); + // ... + + // 下面是原始函数中的剩余代码,保持不变 + window.setTimeout(function () { + if (info.time == 0) + core.events._changeFloor_changing(info, callback); + else + core.showWithAnimate(core.dom.floorMsgGroup, info.time / 2, function () { + core.events._changeFloor_changing(info, callback); + }); + }, 25) +} +``` + +### 每次打开全局商店时播放一个音效 + +打开全局商店是在`events.js`中的`openShop`函数,因此需要对其进行重写。 + +然而,我们只需要在这个函数执行之前插一句音效播放,所以并不需要重写整个函数,而是直接插入一行就行。 + +```js +var openShop = core.events.openShop; // 先把原始函数用一个变量记录下来 +core.events.openShop = function (shopId, needVisited) { + core.playSound("shop.mp3"); // 播放一个音效 + return openShop(shopId, needVisited); // 直接调用原始函数 +} +``` + +### 每次绘制地图前在控制台打出一条信息 + +绘制地图在`maps.js`的`drawMap`函数,因此需要对其进行重写。 + +由于只需要额外在函数执行前增加一句控制台输出,所以直接插入一行即可。 + +但是需要注意的是,`drawMap`中使用了`this._drawMap_drawAll()`,因此使用函数时需要用`call`或者`apply`来告知this是什么。 + +```js +var drawMap = core.maps.drawMap; // 先把原始函数用一个变量记录下来 +core.maps.drawMap = function (floorId, callback) { + console.log("drawMap..."); // 控制台打出一条信息 + return drawMap.call(core.maps, floorId, callback); // 需要使用`call`来告知this是core.maps +} +``` + +详见[call和apply的用法](https://www.jianshu.com/p/80ea0d1c04f8)。 + +========================================================================================== + +[继续阅读下一章:API列表](api) + + diff --git a/_server/MotaAction.g4 b/_server/MotaAction.g4 index 05014715..a8741045 100644 --- a/_server/MotaAction.g4 +++ b/_server/MotaAction.g4 @@ -305,8 +305,8 @@ action | moveImage_s | showGif_0_s | showGif_1_s - | setFg_0_s - | setFg_1_s + | setCurtain_0_s + | setCurtain_1_s | screenFlash_s | setWeather_s | move_s @@ -324,6 +324,7 @@ action | win_s | lose_s | if_s + | if_1_s | switch_s | while_s | break_s @@ -1278,35 +1279,35 @@ var code = '{"type": "moveImage", "code": '+Int_0+toloc+EvalString_0+',"time": ' return code; */; -setFg_0_s +setCurtain_0_s : '更改画面色调' EvalString Colour '动画时间' Int? '不等待执行完毕' Bool Newline -/* setFg_0_s -tooltip : setFg: 更改画面色调,动画时间可不填 -helpUrl : https://h5mota.com/games/template/docs/#/event?id=setfg%EF%BC%9A%E6%9B%B4%E6%94%B9%E7%94%BB%E9%9D%A2%E8%89%B2%E8%B0%83 +/* setCurtain_0_s +tooltip : setCurtain: 更改画面色调,动画时间可不填 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setcurtain%EF%BC%9A%E6%9B%B4%E6%94%B9%E7%94%BB%E9%9D%A2%E8%89%B2%E8%B0%83 default : ["255,255,255,1",'rgba(255,255,255,1)',500,false] colour : this.soundColor var colorRe = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d),(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d),(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(,0(\.\d+)?|,1)?$/; if (!colorRe.test(EvalString_0))throw new Error('颜色格式错误,形如:0~255,0~255,0~255,0~1'); Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; var async = Bool_0?', "async": true':''; -var code = '{"type": "setFg", "color": ['+EvalString_0+']'+Int_0 +async+'},\n'; +var code = '{"type": "setCurtain", "color": ['+EvalString_0+']'+Int_0 +async+'},\n'; return code; */; -setFg_1_s +setCurtain_1_s : '恢复画面色调' '动画时间' Int? '不等待执行完毕' Bool Newline -/* setFg_1_s -tooltip : setFg: 恢复画面色调,动画时间可不填 -helpUrl : https://h5mota.com/games/template/docs/#/event?id=setfg%EF%BC%9A%E6%9B%B4%E6%94%B9%E7%94%BB%E9%9D%A2%E8%89%B2%E8%B0%83 +/* setCurtain_1_s +tooltip : setCurtain: 恢复画面色调,动画时间可不填 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setcurtain%EF%BC%9A%E6%9B%B4%E6%94%B9%E7%94%BB%E9%9D%A2%E8%89%B2%E8%B0%83 default : [500,false] colour : this.soundColor Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; var async = Bool_0?', "async": true':''; -var code = '{"type": "setFg"'+Int_0 +async+'},\n'; +var code = '{"type": "setCurtain"'+Int_0 +async+'},\n'; return code; */; @@ -1482,15 +1483,16 @@ return code; */; playSound_s - : '播放音效' EvalString Newline + : '播放音效' EvalString '停止之前音效' Bool? Newline /* playSound_s tooltip : playSound: 播放音效 helpUrl : https://h5mota.com/games/template/docs/#/event?id=playsound%EF%BC%9A%E6%92%AD%E6%94%BE%E9%9F%B3%E6%95%88 -default : ["item.mp3"] +default : ["item.mp3",false] colour : this.soundColor -var code = '{"type": "playSound", "name": "'+EvalString_0+'"},\n'; +Bool_0 = Bool_0 ? ', "stop": true' : ''; +var code = '{"type": "playSound", "name": "'+EvalString_0+'"'+Bool_0+'},\n'; return code; */; @@ -1587,6 +1589,20 @@ var code = ['{"type": "if", "condition": "',expression_0,'",\n', return code; */; +if_1_s + : '如果' ':' expression BGNL? Newline action+ BEND Newline + + +/* if_1_s +tooltip : if: 条件判断 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=if%EF%BC%9A%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD +colour : this.eventColor +var code = ['{"type": "if", "condition": "',expression_0,'",\n', + '"true": [\n',action_0,'],\n', +'},\n'].join(''); +return code; +*/; + switch_s : '多重分歧 条件判定' ':' expression BGNL? Newline switchCase+ BEND Newline @@ -1603,14 +1619,16 @@ return code; */; switchCase - : '如果是' expression '的场合' BGNL? Newline action+ + : '如果是' expression '的场合' '不跳出' Bool BGNL? Newline action+ /* switchCase tooltip : 选项的选择 helpUrl : https://h5mota.com/games/template/docs/#/event?id=switch%EF%BC%9A%E5%A4%9A%E9%87%8D%E6%9D%A1%E4%BB%B6%E5%88%86%E6%AD%A7 +default : ["", false] colour : this.subColor -var code = '{"case": "'+expression_0+'", "action": [\n'+action_0+']},\n'; +Bool_0 = Bool_0?', "nobreak": true':''; +var code = '{"case": "'+expression_0+'"'+Bool_0+', "action": [\n'+action_0+']},\n'; return code; */; @@ -1639,13 +1657,13 @@ return code; */; choicesContext - : '子选项' EvalString '图标' IdString? '颜色' EvalString? Colour '不跳出' Bool BGNL? Newline action+ + : '子选项' EvalString '图标' IdString? '颜色' EvalString? Colour BGNL? Newline action+ /* choicesContext tooltip : 选项的选择 helpUrl : https://h5mota.com/games/template/docs/#/event?id=choices%EF%BC%9A%E7%BB%99%E7%94%A8%E6%88%B7%E6%8F%90%E4%BE%9B%E9%80%89%E9%A1%B9 -default : ["提示文字:红钥匙","","",null,false] +default : ["提示文字:红钥匙","",""] colour : this.subColor if (EvalString_1) { var colorRe = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d),(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d),(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(,0(\.\d+)?|,1)?$/; @@ -1655,19 +1673,19 @@ if (EvalString_1) { EvalString_1 = ', "color": "'+EvalString_1+'"'; } IdString_0 = IdString_0?(', "icon": "'+IdString_0+'"'):''; -var nobreak = Bool_0?', "nobreak": true':''; -var code = '{"text": "'+EvalString_0+'"'+IdString_0+EvalString_1+', "action": [\n'+action_0+']'+nobreak+'},\n'; +var code = '{"text": "'+EvalString_0+'"'+IdString_0+EvalString_1+', "action": [\n'+action_0+']},\n'; return code; */; confirm_s - : '显示确认框' ':' EvalString BGNL? '确定' ':' BGNL? Newline action+ '取消' ':' BGNL? Newline action+ BEND Newline + : '显示确认框' ':' EvalString BGNL? '确定的场合' ':' '(默认选中' Bool ')' BGNL? Newline action+ '取消的场合' ':' BGNL? Newline action+ BEND Newline /* confirm_s tooltip : 弹出确认框 helpUrl : https://h5mota.com/games/template/docs/#/ -default : ["确认要???吗?"] -var code = ['{"type": "confirm", "text": "',EvalString_0,'",\n', +default : ["确认要xxx吗?",false] +Bool_0 = Bool_0?', "default": true':'' +var code = ['{"type": "confirm"'+Bool_0+', "text": "',EvalString_0,'",\n', '"yes": [\n',action_0,'],\n', '"no": [\n',action_1,']\n', '},\n'].join(''); @@ -2448,11 +2466,12 @@ ActionParser.prototype.parseAction = function() { } break; case "setFg": // 颜色渐变 + case "setCurtain": if(this.isset(data.color)){ - this.next = MotaActionBlocks['setFg_0_s'].xmlText([ + this.next = MotaActionBlocks['setCurtain_0_s'].xmlText([ data.color,'rgba('+data.color+')',data.time||0,data.async||false,this.next]); } else { - this.next = MotaActionBlocks['setFg_1_s'].xmlText([ + this.next = MotaActionBlocks['setCurtain_1_s'].xmlText([ data.time||0,data.async||false,this.next]); } break; @@ -2511,7 +2530,7 @@ ActionParser.prototype.parseAction = function() { break; case "playSound": this.next = MotaActionBlocks['playSound_s'].xmlText([ - data.name,this.next]); + data.name,data.stop,this.next]); break; case "playBgm": this.next = MotaActionBlocks['playBgm_s'].xmlText([ @@ -2579,16 +2598,23 @@ ActionParser.prototype.parseAction = function() { data.text,this.next]); break; case "if": // 条件判断 - this.next = MotaActionBlocks['if_s'].xmlText([ - // MotaActionBlocks['evalString_e'].xmlText([data.condition]), - this.tryToUseEvFlag_e('evalString_e', [data.condition]), - this.insertActionList(data["true"]), - this.insertActionList(data["false"]), - this.next]); + if (data["false"]) { + this.next = MotaActionBlocks['if_s'].xmlText([ + this.tryToUseEvFlag_e('evalString_e', [data.condition]), + this.insertActionList(data["true"]), + this.insertActionList(data["false"]), + this.next]); + } + else { + this.next = MotaActionBlocks['if_1_s'].xmlText([ + this.tryToUseEvFlag_e('evalString_e', [data.condition]), + this.insertActionList(data["true"]), + this.next]); + } break; case "confirm": // 显示确认框 this.next = MotaActionBlocks['confirm_s'].xmlText([ - this.EvalString(data.text), + this.EvalString(data.text), data["default"], this.insertActionList(data["yes"]), this.insertActionList(data["no"]), this.next]); @@ -2597,7 +2623,7 @@ ActionParser.prototype.parseAction = function() { var case_caseList = null; for(var ii=data.caseList.length-1,caseNow;caseNow=data.caseList[ii];ii--) { case_caseList=MotaActionBlocks['switchCase'].xmlText([ - this.isset(caseNow.case)?MotaActionBlocks['evalString_e'].xmlText([caseNow.case]):"值",this.insertActionList(caseNow.action),case_caseList]); + this.isset(caseNow.case)?MotaActionBlocks['evalString_e'].xmlText([caseNow.case]):"值",caseNow.nobreak,this.insertActionList(caseNow.action),case_caseList]); } this.next = MotaActionBlocks['switch_s'].xmlText([ // MotaActionBlocks['evalString_e'].xmlText([data.condition]), @@ -2608,7 +2634,7 @@ ActionParser.prototype.parseAction = function() { var text_choices = null; for(var ii=data.choices.length-1,choice;choice=data.choices[ii];ii--) { text_choices=MotaActionBlocks['choicesContext'].xmlText([ - choice.text,choice.icon,choice.color,'rgba('+choice.color+')',choice.nobreak,this.insertActionList(choice.action),text_choices]); + choice.text,choice.icon,choice.color,'rgba('+choice.color+')',this.insertActionList(choice.action),text_choices]); } this.next = MotaActionBlocks['choices_s'].xmlText([ this.isset(data.text)?this.EvalString(data.text):null,'','',text_choices,this.next]); diff --git a/_server/data.comment.js b/_server/data.comment.js index c4ad3175..c4b8b951 100644 --- a/_server/data.comment.js +++ b/_server/data.comment.js @@ -187,6 +187,11 @@ var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = "_type": "textarea", "_data": "初始生命值" }, + "manamax": { + "_leaf": true, + "_type": "textarea", + "_data": "魔力上限;此项非负才会生效(null或小于0都不会生效)" + }, "mana": { "_leaf": true, "_type": "textarea", diff --git a/_server/editor_blockly.js b/_server/editor_blockly.js index 6aaa3df7..2a641312 100644 --- a/_server/editor_blockly.js +++ b/_server/editor_blockly.js @@ -70,9 +70,9 @@ editor_blockly = function () { MotaActionBlocks['lose_s'].xmlText(), MotaActionBlocks['choices_s'].xmlText([ '选择剑或者盾','流浪者','man',MotaActionBlocks['choicesContext'].xmlText([ - '剑','','',null,null,MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [3,3]}]), + '剑','','',null,MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [3,3]}]), MotaActionBlocks['choicesContext'].xmlText([ - '盾','','',null,null,MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [9,3]}]), + '盾','','',null,MotaActionFunctions.actionParser.parseList([{"type": "openDoor", "loc": [9,3]}]), ]) ]) ]), @@ -111,9 +111,10 @@ editor_blockly = function () { ], '事件控制':[ MotaActionBlocks['if_s'].xmlText(), + MotaActionBlocks['if_1_s'].xmlText(), MotaActionFunctions.actionParser.parseList({"type": "switch", "condition": "判别值", "caseList": [ {"action": [{"type": "comment", "text": "当判别值是值的场合执行此事件"}]}, - {"action": []}, + {"action": [], "nobreak": true}, {"case": "default", "action": [{"type": "comment", "text": "当没有符合的值的场合执行default事件"}]}, ]}), MotaActionBlocks['while_s'].xmlText(), @@ -142,8 +143,8 @@ editor_blockly = function () { MotaActionBlocks['animate_s'].xmlText(), MotaActionBlocks['showStatusBar_s'].xmlText(), MotaActionBlocks['hideStatusBar_s'].xmlText(), - MotaActionBlocks['setFg_0_s'].xmlText(), - MotaActionBlocks['setFg_1_s'].xmlText(), + MotaActionBlocks['setCurtain_0_s'].xmlText(), + MotaActionBlocks['setCurtain_1_s'].xmlText(), MotaActionBlocks['screenFlash_s'].xmlText(), MotaActionBlocks['setWeather_s'].xmlText(), MotaActionBlocks['playBgm_s'].xmlText(), diff --git a/libs/actions.js b/libs/actions.js index 917af40f..c4388106 100644 --- a/libs/actions.js +++ b/libs/actions.js @@ -105,7 +105,7 @@ actions.prototype.doRegisteredAction = function (action) { return false; } -actions.prototype.checkReplaying = function () { +actions.prototype._checkReplaying = function () { if (core.isReplaying() && core.status.event.id != 'save' && (core.status.event.id || "").indexOf('book') != 0 && core.status.event.id != 'viewMaps') return true; @@ -114,7 +114,7 @@ actions.prototype.checkReplaying = function () { ////// 检查是否在录像播放中,如果是,则停止交互 actions.prototype._sys_checkReplay = function () { - if (this.checkReplaying()) return true; + if (this._checkReplaying()) return true; } ////// 按下某个键时 ////// @@ -145,7 +145,7 @@ actions.prototype.onkeyUp = function (e) { } actions.prototype._sys_onkeyUp_replay = function (e) { - if (this.checkReplaying()) { + if (this._checkReplaying()) { if (e.keyCode == 27) // ESCAPE core.stopReplay(); else if (e.keyCode == 90) // Z @@ -286,7 +286,7 @@ actions.prototype.keyUp = function (keyCode, altKey, fromReplay) { } actions.prototype._sys_keyUp_replay = function (keyCode, altKey, fromReplay) { - if (!fromReplay && this.checkReplaying()) return true; + if (!fromReplay && this._checkReplaying()) return true; } actions.prototype._sys_keyUp_lockControl = function (keyCode, altKey) { @@ -389,7 +389,7 @@ actions.prototype._sys_keyUp = function (keyCode, altKey) { if (core.status.automaticRoute && core.status.automaticRoute.autoHeroMove) { core.stopAutomaticRoute(); } - core.stopHero(); + core.status.heroStop = true; return true; } @@ -539,8 +539,8 @@ actions.prototype._sys_onup = function () { return true; } -////// 获得点击事件相对左上角的坐标(0到12之间) ////// -actions.prototype.getClickLoc = function (x, y) { +////// 获得点击事件相对左上角的坐标 ////// +actions.prototype._getClickLoc = function (x, y) { var statusBar = {'x': 0, 'y': 0}; var size = 32; @@ -663,7 +663,7 @@ actions.prototype.onmousewheel = function (direct) { actions.prototype._sys_onmousewheel = function (direct) { // 向下滚动是 -1 ,向上是 1 - if (this.checkReplaying()) { + if (this._checkReplaying()) { // 滚轮控制速度 if (direct == 1) core.speedUpReplay(); if (direct == -1) core.speedDownReplay(); @@ -891,6 +891,21 @@ actions.prototype._clickAction = function (x, y) { core.doAction(); } } + return; + } + + if (core.status.event.data.type == 'confirm') { + if ((x == this.HSIZE-2 || x == this.HSIZE-1) && y == this.HSIZE+1) { + core.status.route.push("choices:0"); + core.insertAction(core.status.event.ui.yes); + core.doAction(); + } + if ((x == this.HSIZE+2 || x == this.HSIZE+1) && y == this.HSIZE+1) { + core.status.route.push("choices:1"); + core.insertAction(core.status.event.ui.no); + core.doAction(); + } + return; } } @@ -898,6 +913,12 @@ actions.prototype._clickAction = function (x, y) { actions.prototype._keyDownAction = function (keycode) { if (core.status.event.data.type == 'choices') { this._keyDownChoices(keycode); + return; + } + if (core.status.event.data.type == 'confirm' && (keycode == 37 || keycode == 39)) { + core.status.event.selection = 1 - core.status.event.selection; + core.drawConfirmBox(core.status.event.ui.text); + return; } } @@ -924,6 +945,15 @@ actions.prototype._keyUpAction = function (keycode) { if (choices.length > 0) { this._selectChoices(choices.length, keycode, this._clickAction); } + return; + } + if (core.status.event.data.type == 'confirm'&& (keycode == 13 || keycode == 32 || keycode == 67)) { + core.status.route.push("choices:" + core.status.event.selection); + if (core.status.event.selection == 0) + core.insertAction(core.status.event.ui.yes); + else core.insertAction(core.status.event.ui.no); + core.doAction(); + return; } } @@ -958,6 +988,7 @@ actions.prototype._clickBook = function (x, y) { var index = this.HSIZE * page + parseInt(y / 2); core.ui.drawBook(index); core.ui.drawBookDetail(index); + return; } return; } @@ -1101,17 +1132,20 @@ actions.prototype._clickViewMaps = function (x, y) { index++; if (index < core.floorIds.length) core.ui.drawMaps(index); + return; } - else if (y >= this.HSIZE + 2 && (mh == this.SIZE || (x >= per && x <= this.LAST - per))) { + if (y >= this.HSIZE + 2 && (mh == this.SIZE || (x >= per && x <= this.LAST - per))) { index--; while (index >= 0 && index != now && core.status.maps[core.floorIds[index]].cannotViewMap) index--; if (index >= 0) core.ui.drawMaps(index); + return; } - else if (x >= per && x <= this.LAST - per && y >= this.HSIZE - 1 && y <= this.HSIZE + 1) { + if (x >= per && x <= this.LAST - per && y >= this.HSIZE - 1 && y <= this.HSIZE + 1) { core.clearMap('data'); core.ui.closePanel(); + return; } } @@ -1217,7 +1251,9 @@ actions.prototype._clickQuickShop = function (x, y) { // 离开 else if (y == topIndex + keys.length) core.ui.closePanel(); + return; } + return; } ////// 快捷商店界面时,放开某个键的操作 ////// @@ -1255,7 +1291,7 @@ actions.prototype._clickToolbox = function (x, y) { core.ui.drawToolbox(core.status.event.selection); } if (y == this.LAST && constantsPage > 1) { - core.status.event.data.toolsPage--; + core.status.event.data.constantsPage--; core.ui.drawToolbox(core.status.event.selection); } } @@ -1904,11 +1940,11 @@ actions.prototype._clickSettings = function (x, y) { core.ui.drawKeyBoard(); break; case 2: - core.clearSelector(); + core.clearUI(); core.ui.drawMaps(); break; case 3: - core.clearSelector(); + core.clearUI(); core.ui.drawPaint(); break; case 4: @@ -1920,7 +1956,7 @@ actions.prototype._clickSettings = function (x, y) { core.ui.drawGameInfo(); break; case 6: - return core.confirmRestart(true); + return core.confirmRestart(); case 7: core.ui.closePanel(); break; @@ -2065,7 +2101,7 @@ actions.prototype._clickLocalSaveSelect = function (x, y) { var selection = y - topIndex; core.status.event.selection = selection; if (selection < 2) { - core.getAllSaves(selection == 0 ? null : core.saves.saveIndex, function (saves) { + var callback = function (saves) { if (saves) { var content = { "name": core.firstData.name, @@ -2074,7 +2110,9 @@ actions.prototype._clickLocalSaveSelect = function (x, y) { } core.download(core.firstData.name + "_" + core.formatDate2(new Date()) + ".h5save", JSON.stringify(content)); } - }) + }; + if (selection == 0) core.getAllSaves(callback); + else core.getSave(core.saves.saveIndex, callback); } core.status.event.selection = 2; @@ -2209,7 +2247,7 @@ actions.prototype._clickReplay_fromBeginning = function () { actions.prototype._clickReplay_fromLoad = function () { core.status.event.id = 'replayLoad'; core.status.event.selection = null; - core.ui.clearSelector(); + core.clearUI(); var saveIndex = core.saves.saveIndex; var page = parseInt((saveIndex - 1) / 5), offset = saveIndex - 5 * page; core.ui.drawSLPanel(10 * page + offset); @@ -2448,7 +2486,7 @@ actions.prototype._onupPaint = function () { core.status.event.data.x = null; core.status.event.data.y = null; // 保存 - core.paint[core.status.floorId] = lzw_encode(core.utils.encodeCanvas(core.dymCanvas.paint).join(",")); + core.paint[core.status.floorId] = lzw_encode(core.utils._encodeCanvas(core.dymCanvas.paint).join(",")); } actions.prototype.setPaintMode = function (mode) { @@ -2496,7 +2534,7 @@ actions.prototype.loadPaint = function () { core.clearMap('paint'); var value = core.paint[core.status.floorId]; if (value) value = lzw_decode(value).split(","); - core.utils.decodeCanvas(value, 32 * core.bigmap.width, 32 * core.bigmap.height); + core.utils._decodeCanvas(value, 32 * core.bigmap.width, 32 * core.bigmap.height); core.drawImage('paint', core.bigmap.tempCanvas.canvas, 0, 0); core.drawTip("读取绘图文件成功"); diff --git a/libs/control.js b/libs/control.js index 7a1de28b..5f38acfd 100644 --- a/libs/control.js +++ b/libs/control.js @@ -308,7 +308,7 @@ control.prototype.showStartAnimate = function (noAnimate, callback) { this._showStartAnimate_resetDom(); if (core.flags.startUsingCanvas || noAnimate) return this._showStartAnimate_finished(core.flags.startUsingCanvas, callback); - core.hide(core.dom.startTop, 20, function () { + core.hideWithAnimate(core.dom.startTop, 20, function () { core.control._showStartAnimate_finished(false, callback); }); } @@ -324,8 +324,8 @@ control.prototype._showStartAnimate_resetDom = function () { core.status.played = false; core.clearStatus(); core.clearMap('all'); - core.deleteAllCanvas(); core.dom.musicBtn.style.display = 'block'; + core.setMusicBtn(); // 重置音量 core.events.setVolume(1, 0); core.updateStatusBar(); @@ -340,7 +340,7 @@ control.prototype._showStartAnimate_finished = function (start, callback) { ////// 隐藏游戏开始界面 ////// control.prototype.hideStartAnimate = function (callback) { - core.hide(core.dom.startPanel, 20, callback); + core.hideWithAnimate(core.dom.startPanel, 20, callback); } ////// 游戏是否已经开始 ////// @@ -348,30 +348,6 @@ control.prototype.isPlaying = function() { return core.status.played; } -////// 重新开始游戏;此函数将回到标题页面 ////// -control.prototype.restart = function() { - this.showStartAnimate(); - core.playBgm(main.startBgm); -} - -////// 询问是否需要重新开始 ////// -control.prototype.confirmRestart = function (fromSettings) { - core.status.event.selection = 1; - core.ui.drawConfirmBox("你确定要返回标题页面吗?", function () { - core.ui.closePanel(); - core.restart(); - }, function () { - if (fromSettings) { - core.status.event.selection = 3; - core.ui.drawSettings(); - } - else { - core.ui.closePanel(); - } - }); -} - - ////// 清除游戏状态和数据 ////// control.prototype.clearStatus = function() { // 停止各个Timeout和Interval @@ -424,7 +400,7 @@ control.prototype.stopAutomaticRoute = function () { core.status.automaticRoute.destX=null; core.status.automaticRoute.destY=null; core.status.automaticRoute.lastDirection = null; - core.stopHero(); + core.status.heroStop = true; if (core.status.automaticRoute.moveStepBeforeStop.length==0) core.deleteCanvas('route'); } @@ -472,8 +448,10 @@ control.prototype.setAutomaticRoute = function (destX, destY, stepPostfix) { if (this._setAutomaticRoute_isTurning(destX, destY, stepPostfix)) return; if (this._setAutomaticRoute_clickMoveDirectly(destX, destY, stepPostfix)) return; // 找寻自动寻路路线 - var moveStep = core.automaticRoute(destX, destY).concat(stepPostfix); - if (moveStep.length == 0) return core.deleteCanvas('route'); + var moveStep = core.automaticRoute(destX, destY); + if (moveStep.length == 0 && (destX != core.status.hero.loc.x || destY != core.status.hero.loc.y || stepPostfix.length == 0)) + return; + moveStep = moveStep.concat(stepPostfix); core.status.automaticRoute.destX=destX; core.status.automaticRoute.destY=destY; this._setAutomaticRoute_drawRoute(moveStep); @@ -659,7 +637,7 @@ control.prototype._moveAction_moving = function (callback) { core.updateStatusBar(); // 检查该点是否是滑冰 - if (core.getBgFgNumber('bg') == 167) { + if (core.getBgNumber() == 167) { core.insertAction("滑冰事件", null, null, null, true); } @@ -749,11 +727,6 @@ control.prototype.waitHeroToStop = function(callback) { } } -////// 停止勇士的移动状态 ////// -control.prototype.stopHero = function () { - core.status.heroStop = true; -} - ////// 转向 ////// control.prototype.turnHero = function(direction) { if (direction) { @@ -1272,7 +1245,12 @@ control.prototype._doReplayAction = function (action) { control.prototype._replay_finished = function () { core.status.replay.replaying = false; core.status.event.selection = 0; - core.ui.drawConfirmBox("录像播放完毕,你想退出播放吗?", function () { + var str = "录像播放完毕,你想退出播放吗?"; + if (core.status.route.length != core.status.replay.totalList.length + || core.subarray(core.status.route, core.status.replay.totalList) == null) { + str = "录像播放完毕,但记录不一致。\n请检查录像播放时的二次记录问题。\n你想退出播放吗?"; + } + core.ui.drawConfirmBox(str, function () { core.ui.closePanel(); core.stopReplay(true); }, function () { @@ -1460,6 +1438,7 @@ control.prototype._replayAction_moveDirectly = function (action) { // 忽略连续的瞬移事件 while (core.status.replay.toReplay.length>0 && core.status.replay.toReplay[0].indexOf('move:')==0) { + core.status.route.push(action); action = core.status.replay.toReplay.shift(); } @@ -1615,13 +1594,15 @@ control.prototype._doSL_replayLoad_afterGet = function (id, data) { ////// 同步存档到服务器 ////// control.prototype.syncSave = function (type) { core.ui.drawWaiting("正在同步,请稍后..."); - core.getAllSaves(type=='all'?null:core.saves.saveIndex, function (saves) { - if (!saves) return core.drawText("没有要同步的存档"); + var callback = function (saves) { core.control._syncSave_http(type, saves); - }) + } + if (type == 'all') core.getAllSaves(callback); + else core.getSave(core.saves.saveIndex, callback); } control.prototype._syncSave_http = function (type, saves) { + if (!saves) return core.drawText("没有要同步的存档"); var formData = new FormData(); formData.append('type', 'save'); formData.append('name', core.firstData.name); @@ -1635,7 +1616,7 @@ control.prototype._syncSave_http = function (type, saves) { else { core.drawText((type=='all'?"所有存档":"存档"+core.saves.saveIndex)+"同步成功!\n\n您的存档编号: " +response.code+"\n您的存档密码: "+response.msg - +"\n\n请牢记以上两个信息(如截图等),在从服务器\n同步存档时使用。") + +"\n\n请牢记以上两个信息(如截图等),在从服务器\n同步存档时使用。\n\r[yellow]另外请注意,存档同步只会保存一个月的时间。\r") } }, function (e) { core.drawText("出错啦!\n无法同步存档到服务器。\n错误原因:"+e); @@ -1746,8 +1727,7 @@ control.prototype.getSaves = function (ids, callback) { } } -control.prototype.getAllSaves = function (id, callback) { - if (id != null) return this.getSave(id, callback); +control.prototype.getAllSaves = function (callback) { var ids = Object.keys(core.saves.ids).filter(function(x){return x!=0;}) .sort(function(a,b) {return a-b;}), saves = []; this.getSaves(ids, function (data) { @@ -1790,7 +1770,7 @@ control.prototype.hasSave = function (index) { return core.saves.ids[index] || false; } -////// 删除一个或多个存档 +////// 删除某个存档 control.prototype.removeSave = function (index, callback) { if (index == 0 || index == "autoSave") { index = "autoSave"; @@ -1825,8 +1805,6 @@ control.prototype._updateFavoriteSaves = function () { core.setLocalStorage("favoriteName", core.saves.favoriteName); } -////// 加载某个存档 - // ------ 属性,状态,位置,buff,变量,锁定控制等 ------ // ////// 设置勇士属性 ////// @@ -2017,7 +1995,7 @@ control.prototype._setWeather_createNodes = function (type, level) { } ////// 更改画面色调 ////// -control.prototype.setFg = function(color, time, callback) { +control.prototype.setCurtain = function(color, time, callback) { if (time == null) time=750; if (time<=0) time=0; if (!core.status.curtainColor) @@ -2035,10 +2013,10 @@ control.prototype.setFg = function(color, time, callback) { return; } - this._setFg_animate(core.status.curtainColor, color, time, callback); + this._setCurtain_animate(core.status.curtainColor, color, time, callback); } -control.prototype._setFg_animate = function (nowColor, color, time, callback) { +control.prototype._setCurtain_animate = function (nowColor, color, time, callback) { var per_time = 10, step = parseInt(time / per_time); var animate = setInterval(function() { nowColor = [ @@ -2066,8 +2044,8 @@ control.prototype.screenFlash = function (color, time, times, callback) { times = times || 1; time = time / 3; var nowColor = core.clone(core.status.curtainColor); - core.setFg(color, time, function() { - core.setFg(nowColor, time * 2, function() { + core.setCurtain(color, time, function() { + core.setCurtain(nowColor, time * 2, function() { if (times > 1) core.screenFlash(color, time * 3, times - 1, callback); else { @@ -2203,8 +2181,8 @@ control.prototype.stopSound = function () { for (var i in core.musicStatus.playingSounds) { var source = core.musicStatus.playingSounds[i]; try { - if (source[i].stop) source[i].stop(); - else if (source[i].noteOff) source[i].noteOff(); + if (source.stop) source.stop(); + else if (source.noteOff) source.noteOff(); } catch (e) { main.log(e); @@ -2353,10 +2331,11 @@ control.prototype.updateGlobalAttribute = function (name) { core.dom.statusBar.style.borderTop = border; core.dom.statusBar.style.borderLeft = border; core.dom.statusBar.style.borderRight = core.domStyle.isVertical?border:''; + core.dom.statusBar.style.borderBottom = core.domStyle.isVertical?'':border; core.dom.gameDraw.style.border = border; - core.dom.toolBar.style.borderBottom = border; core.dom.toolBar.style.borderLeft = border; core.dom.toolBar.style.borderRight = core.domStyle.isVertical?border:''; + core.dom.toolBar.style.borderBottom = core.domStyle.isVertical?border:''; break; } case 'statusBarColor': @@ -2378,7 +2357,7 @@ control.prototype.updateGlobalAttribute = function (name) { } } -////// 改变工具栏为按钮1-7 ////// +////// 改变工具栏为按钮1-8 ////// control.prototype.setToolbarButton = function (useButton) { if (!core.domStyle.showStatusBar) { // 隐藏状态栏时检查竖屏 @@ -2544,7 +2523,7 @@ control.prototype._resize_gameGroup = function (obj) { floorMsgGroup.style.color = obj.globalAttribute.floorChangingTextColor; // musicBtn if (core.domStyle.isVertical || core.domStyle.scale < 1) { - core.dom.musicBtn.style.right = core.dom.musicBtn.style.height = "3px"; + core.dom.musicBtn.style.right = core.dom.musicBtn.style.bottom = "3px"; } else { core.dom.musicBtn.style.right = (obj.clientWidth - totalWidth) / 2 + "px"; @@ -2588,7 +2567,7 @@ control.prototype._resize_statusBar = function (obj) { } else { statusBar.style.width = obj.BAR_WIDTH * core.domStyle.scale + "px"; - statusBar.style.height = obj.outerSize - 3 + "px"; + statusBar.style.height = obj.outerSize + "px"; statusBar.style.background = obj.globalAttribute.statusLeftBackground; // --- 计算文字大小 statusBar.style.fontSize = 16 * Math.min(1, (core.__HALF_SIZE__ + 3) / obj.count) * core.domStyle.scale + "px"; @@ -2596,6 +2575,7 @@ control.prototype._resize_statusBar = function (obj) { statusBar.style.display = 'block'; statusBar.style.borderTop = statusBar.style.borderLeft = obj.border; statusBar.style.borderRight = core.domStyle.isVertical ? obj.border : ''; + statusBar.style.borderBottom = core.domStyle.isVertical ? '' : obj.border; // 自绘状态栏 if (core.domStyle.isVertical) { core.dom.statusCanvas.style.width = obj.outerSize - 6 + "px"; @@ -2651,8 +2631,8 @@ control.prototype._resize_toolBar = function (obj) { toolBar.style.background = 'transparent'; } toolBar.style.display = 'block'; - toolBar.style.borderLeft = toolBar.style.borderBottom = obj.border; - toolBar.style.borderRight = core.domStyle.isVertical ? obj.border : ''; + toolBar.style.borderLeft = obj.border; + toolBar.style.borderRight = toolBar.style.borderBottom = core.domStyle.isVertical ? obj.border : ''; toolBar.style.fontSize = 16 * core.domStyle.scale + "px"; } diff --git a/libs/core.js b/libs/core.js index 966aa4d0..3f1165b6 100644 --- a/libs/core.js +++ b/libs/core.js @@ -16,8 +16,7 @@ function core() { 'ground': null, 'items': {}, 'enemys': {}, - 'icons': {}, - 'events': {} + 'icons': {} } this.timeout = { 'tipTimeout': null, @@ -84,8 +83,8 @@ function core() { this.domStyle = { scale: 1.0, isVertical: false, - toolbarBtn: false, showStatusBar: true, + toolbarBtn: false, } this.bigmap = { canvas: ["bg", "event", "event2", "fg", "damage"], @@ -220,6 +219,7 @@ core.prototype.init = function (coreData, callback) { this._init_flags(); this._init_platform(); this._init_others(); + this._initPlugins(); core.loader._load(function () { core._afterLoadResources(callback); @@ -342,9 +342,10 @@ core.prototype._init_others = function () { core.prototype._afterLoadResources = function (callback) { // 初始化地图 - core.initStatus.maps = core.maps.initMaps(core.floorIds); + core.initStatus.maps = core.maps._initMaps(); core.control._setRequestAnimationFrame(); - core._initPlugins(); + if (core.plugin._afterLoadResources) + core.plugin._afterLoadResources(); core.showStartAnimate(); if (callback) callback(); } diff --git a/libs/enemys.js b/libs/enemys.js index 75f64ede..2b7e551d 100644 --- a/libs/enemys.js +++ b/libs/enemys.js @@ -107,13 +107,13 @@ enemys.prototype.canBattle = function (enemy, x, y, floorId) { ////// 获得某个怪物的伤害 ////// enemys.prototype.getDamage = function (enemy, x, y, floorId) { if (typeof enemy == 'string') enemy = core.material.enemys[enemy]; - var damage = this.calDamage(enemy, null, x, y, floorId); + var damage = this._calDamage(enemy, null, x, y, floorId); if (damage == null) return null; - return damage + this.getExtraDamage(enemy); + return damage + this.getExtraDamage(enemy, x, y, floorId); } ////// 获得某个怪物的额外伤害 ////// -enemys.prototype.getExtraDamage = function (enemy) { +enemys.prototype.getExtraDamage = function (enemy, x, y, floorId) { if (typeof enemy == 'string') enemy = core.material.enemys[enemy]; var extra_damage = 0; if (this.hasSpecial(enemy.special, 17)) { // 仇恨 @@ -265,8 +265,8 @@ enemys.prototype._nextCriticals_useTurn = function (enemy, info, number, x, y, f enemys.prototype.getDefDamage = function (enemy, k, x, y, floorId) { if (typeof enemy == 'string') enemy = core.material.enemys[enemy]; k = k || 1; - var nowDamage = this.calDamage(enemy, null, x, y, floorId); - var nextDamage = this.calDamage(enemy, {"def": core.status.hero.def + k}, x, y, floorId); + var nowDamage = this._calDamage(enemy, null, x, y, floorId); + var nextDamage = this._calDamage(enemy, {"def": core.status.hero.def + k}, x, y, floorId); if (nowDamage == null || nextDamage == null) return "???"; return nowDamage - nextDamage; } @@ -284,7 +284,7 @@ enemys.prototype.getDamageInfo = function (enemy, hero, x, y, floorId) { } ////// 获得在某个勇士属性下怪物伤害 ////// -enemys.prototype.calDamage = function (enemy, hero, x, y, floorId) { +enemys.prototype._calDamage = function (enemy, hero, x, y, floorId) { if (typeof enemy == 'string') enemy = core.material.enemys[enemy]; var info = this.getDamageInfo(enemy, hero, x, y, floorId); @@ -357,4 +357,8 @@ enemys.prototype._getCurrentEnemys_sort = function (enemys) { } return a.damage - b.damage; }); +} + +enemys.prototype.hasEnemyLeft = function (floorId) { + return core.getCurrentEnemys(floorId).length > 0; } \ No newline at end of file diff --git a/libs/events.js b/libs/events.js index 768cc35d..814afc2b 100644 --- a/libs/events.js +++ b/libs/events.js @@ -52,9 +52,6 @@ events.prototype._startGame_start = function (hard, seed, route, callback) { } else core.utils.__init_seed(); this.setInitData(); - - core.clearMap('all'); - core.deleteAllCanvas(); core.clearStatusBar(); var todo = []; @@ -183,7 +180,7 @@ events.prototype._gameOver_doUpload = function (username, ending, norank) { formData.append('money', core.status.hero.money); formData.append('experience', core.status.hero.experience); formData.append('steps', core.status.hero.steps); - formData.append('norank', norank || 0); + formData.append('norank', norank ? 1 : 0); formData.append('seed', core.getFlag('__seed__')); formData.append('totalTime', Math.floor(core.status.hero.statistics.totalTime / 1000)); formData.append('route', core.encodeRoute(core.status.route)); @@ -234,6 +231,23 @@ events.prototype._gameOver_askRate = function (ending) { }); } +////// 重新开始游戏;此函数将回到标题页面 ////// +events.prototype.restart = function() { + core.showStartAnimate(); + core.playBgm(main.startBgm); +} + +////// 询问是否需要重新开始 ////// +events.prototype.confirmRestart = function () { + core.status.event.selection = 1; + core.ui.drawConfirmBox("你确定要返回标题页面吗?", function () { + core.ui.closePanel(); + core.restart(); + }, function () { + core.ui.closePanel(); + }); +} + // ------ 系统事件的处理 ------ // ////// 注册一个系统事件 ////// @@ -279,10 +293,7 @@ events.prototype._trigger = function (x, y) { if (trigger == 'changeFloor' && !noPass && this._trigger_ignoreChangeFloor(block)) return; core.status.automaticRoute.moveDirectly = false; - this.doSystemEvent(trigger, block, function () { - if (trigger == 'openDoor' || trigger == 'changeFloor') - core.replay(); - }) + this.doSystemEvent(trigger, block); } } @@ -341,16 +352,20 @@ events.prototype.afterBattle = function (enemyId, x, y, callback) { } events.prototype._sys_openDoor = function (data, callback) { - this.openDoor(data.event.id, data.x, data.y, true, callback); + this.openDoor(data.x, data.y, true, function () { + core.replay(); + if (callback) callback(); + }); } ////// 开门 ////// -events.prototype.openDoor = function (id, x, y, needKey, callback) { - id = id || core.getBlockId(x, y); +events.prototype.openDoor = function (x, y, needKey, callback) { + var id = core.getBlockId(x, y); core.saveAndStopAutomaticRoute(); if (!this._openDoor_check(id, x, y, needKey)) { + var locked = core.status.lockControl; core.waitHeroToStop(function () { - core.unLockControl(); + if (!locked) core.unLockControl(); if (callback) callback(); }); return; @@ -362,7 +377,7 @@ events.prototype.openDoor = function (id, x, y, needKey, callback) { events.prototype._openDoor_check = function (id, x, y, needKey) { // 是否存在门或暗墙 if (!core.terrainExists(x, y, id) || !(id.endsWith("Door") || id.endsWith("Wall")) - || !core.material.icons.animates[id]) { + || core.material.icons.animates[id] == null) { core.clearContinueAutomaticRoute(); return false; } @@ -376,7 +391,7 @@ events.prototype._openDoor_check = function (id, x, y, needKey) { core.clearContinueAutomaticRoute(); return false; } - core.autosave(true); + if (!core.status.event.id) core.autosave(true); core.removeItem(key); } return true; @@ -386,6 +401,7 @@ events.prototype._openDoor_animate = function (id, x, y, callback) { var door = core.material.icons.animates[id]; var speed = id.endsWith("Door") ? 30 : 70; + var locked = core.status.lockControl; core.lockControl(); core.status.replay.animate = true; var state = 0; @@ -394,7 +410,7 @@ events.prototype._openDoor_animate = function (id, x, y, callback) { if (state == 4) { clearInterval(animate); core.removeBlock(x, y); - core.unLockControl(); + if (!locked) core.unLockControl(); core.status.replay.animate = false; core.events.afterOpenDoor(id, x, y, callback); return; @@ -414,29 +430,33 @@ events.prototype._sys_getItem = function (data, callback) { } ////// 获得某个物品 ////// -events.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { - itemNum = itemNum || 1; - var itemCls = core.material.items[itemId].cls; - core.items.getItemEffect(itemId, itemNum); - core.removeBlock(itemX, itemY); - var text = '获得 ' + core.material.items[itemId].name; - if (itemNum > 1) text += "x" + itemNum; - if (itemCls === 'items') text += core.items.getItemEffectTip(itemId); - core.drawTip(text, core.material.icons.items[itemId]); +events.prototype.getItem = function (id, num, x, y, callback) { + num = num || 1; + var itemCls = core.material.items[id].cls; + core.items.getItemEffect(id, num); + core.removeBlock(x, y); + var text = '获得 ' + core.material.items[id].name; + if (num > 1) text += "x" + num; + if (itemCls === 'items') text += core.items.getItemEffectTip(id); + core.drawTip(text, id); core.updateStatusBar(); - this.eventdata.afterGetItem(itemId, itemX, itemY, callback); + this.afterGetItem(id, x, y, callback); +} + +events.prototype.afterGetItem = function (id, x, y, callback) { + this.eventdata.afterGetItem(id, x, y, callback); } ////// 获得面前的物品(轻按) ////// -events.prototype.getNextItem = function () { +events.prototype.getNextItem = function (noRoute) { if (core.isMoving() || !core.canMoveHero() || !core.flags.enableGentleClick) return false; var nextX = core.nextX(), nextY = core.nextY(); var block = core.getBlock(nextX, nextY); if (block == null) return false; if (block.block.event.trigger == 'getItem') { - core.status.route.push("getNext"); + if (!noRoute) core.status.route.push("getNext"); this.getItem(block.block.event.id, 1, nextX, nextY); return true; } @@ -449,7 +469,10 @@ events.prototype._sys_changeFloor = function (data, callback) { if (data.loc) heroLoc = {'x': data.loc[0], 'y': data.loc[1]}; if (data.direction) heroLoc.direction = data.direction; if (core.status.event.id != 'action') core.status.event.id = null; - core.changeFloor(data.floorId, data.stair, heroLoc, data.time, callback); + core.changeFloor(data.floorId, data.stair, heroLoc, data.time, function () { + core.replay(); + if (callback) callback(); + }); } ////// 楼层切换 ////// @@ -461,6 +484,7 @@ events.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback } info.fromLoad = fromLoad; floorId = info.floorId; + info.locked = core.status.lockControl; core.dom.floorNameLabel.innerHTML = core.status.maps[floorId].title; core.lockControl(); @@ -535,7 +559,7 @@ events.prototype._changeFloor_beforeChange = function (info, callback) { if (info.time == 0) core.events._changeFloor_changing(info, callback); else - core.show(core.dom.floorMsgGroup, info.time / 2, function () { + core.showWithAnimate(core.dom.floorMsgGroup, info.time / 2, function () { core.events._changeFloor_changing(info, callback); }); }, 25) @@ -547,13 +571,13 @@ events.prototype._changeFloor_changing = function (info, callback) { if (info.time == 0) this._changeFloor_afterChange(info, callback); else - core.hide(core.dom.floorMsgGroup, info.time / 4, function () { + core.hideWithAnimate(core.dom.floorMsgGroup, info.time / 4, function () { core.events._changeFloor_afterChange(info, callback); }); } events.prototype._changeFloor_afterChange = function (info, callback) { - core.unLockControl(); + if (!info.locked) core.unLockControl(); core.status.replay.animate = false; core.events.afterChangeFloor(info.floorId, info.fromLoad); @@ -661,11 +685,6 @@ events.prototype._sys_changeLight = function (data, callback) { events.prototype.changeLight = function (id, x, y) { if (id != null && id != 'light') return; core.setBlock(core.getNumberById('darkLight'), x, y); - this.afterChangeLight(x, y); -} - -////// 改变亮灯之后,可以触发的事件 ////// -events.prototype.afterChangeLight = function (x, y) { return this.eventdata.afterChangeLight(x, y); } @@ -724,8 +743,19 @@ events.prototype.doEvent = function (data, x, y, prefix) { core.doAction(); } +events.prototype.setEvents = function (list, x, y, callback) { + var data = core.status.event.data || {}; + if (list) + data.list = [{todo: core.clone(list), total: core.clone(list), condition: "false"}]; + if (x != null) data.x = x; + if (y != null) data.y = y; + if (callback) data.callback = callback; + core.status.event.id = 'action'; + core.status.event.data = data; +} + ////// 开始执行一系列自定义事件 ////// -events.prototype.doEvents = function (list, x, y, callback) { +events.prototype.startEvents = function (list, x, y, callback) { if (!list) return; if (!(list instanceof Array)) { list = [list]; @@ -738,24 +768,10 @@ events.prototype.doEvents = function (list, x, y, callback) { }); } -events.prototype.setEvents = function (list, x, y, callback) { - var data = core.status.event.data || {}; - if (list) - data.list = [{todo: core.clone(list), total: core.clone(list), condition: "false"}]; - if (x != null) data.x = x; - if (y != null) data.y = y; - if (callback) data.callback = callback; - core.status.event.id = 'action'; - core.status.event.data = data; -} - ////// 执行当前自定义事件列表中的下一个事件 ////// events.prototype.doAction = function () { // 清空boxAnimate和UI层 - core.status.boxAnimateObjs = []; - clearInterval(core.status.event.interval); - core.status.event.interval = null; - core.clearSelector(); + core.clearUI(); // 判定是否执行完毕 if (this._doAction_finishEvents()) return; // 当前点坐标和前缀 @@ -799,7 +815,7 @@ events.prototype._popEvents = function (current, prefix) { return false; } -////// 往当前事件列表之前添加一个或多个事件 ////// +////// 往当前事件列表之前或之后添加一个或多个事件 ////// events.prototype.insertAction = function (action, x, y, callback, addToLast) { if (core.hasFlag("__statistics__")) return; if (core.status.gameOver) return; @@ -810,7 +826,7 @@ events.prototype.insertAction = function (action, x, y, callback, addToLast) { if (!action) return; if (core.status.event.id != 'action') { - this.doEvents(action, x, y, callback); + this.startEvents(action, x, y, callback); } else { if (addToLast) @@ -1022,10 +1038,7 @@ events.prototype._action_jumpHero = function (data, x, y, prefix) { events.prototype._action_changeFloor = function (data, x, y, prefix) { var loc = this.__action_getHeroLoc(data.loc, prefix); var heroLoc = {x: loc[0], y: loc[1], direction: data.direction}; - core.changeFloor(data.floorId || core.status.floorId, null, heroLoc, data.time, function () { - core.lockControl(); - core.doAction(); - }); + core.changeFloor(data.floorId || core.status.floorId, null, heroLoc, data.time, core.doAction); } events.prototype._action_changePos = function (data, x, y, prefix) { @@ -1068,13 +1081,17 @@ events.prototype._action_moveImage = function (data, x, y, prefix) { } events.prototype._action_setFg = function (data, x, y, prefix) { + return this._action_setCurtain(data, x, y, prefix); +} + +events.prototype._action_setCurtain = function (data, x, y, prefix) { if (data.async) { - core.setFg(data.color, data.time); + core.setCurtain(data.color, data.time); core.setFlag('__color__', data.color || null); core.doAction(); } else { - core.setFg(data.color, data.time, function () { + core.setCurtain(data.color, data.time, function () { core.setFlag('__color__', data.color || null); core.doAction(); }); @@ -1097,10 +1114,7 @@ events.prototype._action_openDoor = function (data, x, y, prefix) { var loc = this.__action_getLoc(data.loc, x, y, prefix); var floorId = data.floorId || core.status.floorId; if (floorId == core.status.floorId) { - core.openDoor(null, loc[0], loc[1], data.needKey, function () { - core.lockControl(); - core.doAction(); - }); + core.openDoor(loc[0], loc[1], data.needKey, core.doAction); } else { core.removeBlock(loc[0], loc[1], floorId); @@ -1148,14 +1162,10 @@ events.prototype._action_trigger = function (data, x, y, prefix) { if (block != null && block.block.event.trigger) { block = block.block; this.setEvents([], block.x, block.y); - var _callback = function () { - core.lockControl(); - core.doAction(); - } if (block.event.trigger == 'action') this.setEvents(block.event.data); else { - core.doSystemEvent(block.event.trigger, block, _callback); + core.doSystemEvent(block.event.trigger, block, core.doAction); return; } } @@ -1213,6 +1223,7 @@ events.prototype._action_freeBgm = function (data, x, y, prefix) { } events.prototype._action_playSound = function (data, x, y, prefix) { + if (data.stop) core.stopSound(); core.playSound(data.name); core.doAction(); } @@ -1318,13 +1329,16 @@ events.prototype._action_if = function (data, x, y, prefix) { events.prototype._action_switch = function (data, x, y, prefix) { var key = core.calValue(data.condition, prefix) + var list = []; for (var i = 0; i < data.caseList.length; i++) { var condition = data.caseList[i]["case"]; if (condition == "default" || core.calValue(condition, prefix) == key) { - this.insertAction(data.caseList[i].action); - break; + core.push(list, data.caseList[i].action); + if (!data.caseList[i].nobreak) + break; } } + core.insertAction(list); core.doAction(); } @@ -1352,6 +1366,35 @@ events.prototype._action_choices = function (data, x, y, prefix) { core.ui.drawChoices(data.text, data.choices); } +events.prototype._action_confirm = function (data, x, y, prefix) { + core.status.event.ui = {"text": data.text, "yes": data.yes, "no": data.no}; + if (core.isReplaying()) { + var action = core.status.replay.toReplay.shift(), index; + // --- 忽略可能的turn事件 + if (action == 'turn') action = core.status.replay.toReplay.shift(); + if (action.indexOf("choices:") == 0 && ((index = parseInt(action.substring(8))) >= 0) && index < 2) { + core.status.event.selection = index; + setTimeout(function () { + core.status.route.push("choices:" + index); + if (index == 0) core.insertAction(data.yes); + else core.insertAction(data.no); + core.doAction(); + }, 750 / Math.max(1, core.status.replay.speed)) + } + else { + main.log("录像文件出错!当前需要一个 choices: 项,实际为 " + action); + core.stopReplay(); + core.insertAction(["录像文件出错,请在控制台查看报错信息。", {"type": "exit"}]); + core.doAction(); + return; + } + } + else { + core.status.event.selection = data["default"] ? 0 : 1; + } + core.ui.drawConfirmBox(data.text); +} + events.prototype._action_while = function (data, x, y, prefix) { if (core.calValue(data.condition, prefix)) { core.unshift(core.status.event.data.list, @@ -1641,7 +1684,6 @@ events.prototype.load = function (fromUserAction) { core.dom.startPanel.style.display = 'none'; core.clearStatus(); core.clearMap('all'); - core.deleteAllCanvas(); core.status.event = {'id': 'load', 'data': null}; core.status.lockControl = true; core.ui.drawSLPanel(10*page+offset); @@ -1698,24 +1740,6 @@ events.prototype.unfollow = function (name) { core.drawHero(); } -////// 绘制或取消一张gif图片 ////// -events.prototype.showGif = function (name, x, y) { - var image = core.material.images.images[name]; - if (image) { - var gif = new Image(); - gif.src = image.src; - gif.style.position = 'absolute'; - gif.style.left = x * core.domStyle.scale + "px"; - gif.style.top = y * core.domStyle.scale + "px"; - gif.style.width = image.width * core.domStyle.scale + "px"; - gif.style.height = image.height * core.domStyle.scale + "px"; - core.dom.gif2.appendChild(gif); - } - else { - core.dom.gif2.innerHTML = ""; - } -} - ////// 数值操作 ////// events.prototype.setValue = function (name, value, prefix, add) { var value = core.calValue(value, prefix); @@ -1919,6 +1943,24 @@ events.prototype._moveImage_moving = function (name, moveInfo, callback) { core.animateFrame.asyncId[animate] = true; } +////// 绘制或取消一张gif图片 ////// +events.prototype.showGif = function (name, x, y) { + var image = core.material.images.images[name]; + if (image) { + var gif = new Image(); + gif.src = image.src; + gif.style.position = 'absolute'; + gif.style.left = x * core.domStyle.scale + "px"; + gif.style.top = y * core.domStyle.scale + "px"; + gif.style.width = image.width * core.domStyle.scale + "px"; + gif.style.height = image.height * core.domStyle.scale + "px"; + core.dom.gif2.appendChild(gif); + } + else { + core.dom.gif2.innerHTML = ""; + } +} + ////// 淡入淡出音乐 ////// events.prototype.setVolume = function (value, time, callback) { var set = function (value) { @@ -2040,7 +2082,7 @@ events.prototype.jumpHero = function (ex, ey, time, callback) { var sx=core.status.hero.loc.x, sy=core.status.hero.loc.y; if (!core.isset(ex)) ex=sx; if (!core.isset(ey)) ey=sy; - core.maps.__playJumpSound(); + core.playSound('jump.mp3'); var jumpInfo = core.maps.__generateJumpInfo(sx, sy, ex, ey, time || 500); jumpInfo.icon = core.material.icons.hero[core.getHeroLoc('direction')]; jumpInfo.height = core.material.icons.hero.height; @@ -2209,7 +2251,7 @@ events.prototype.afterUseBomb = function () { } ////// 上传当前数据 ////// -events.prototype.uploadCurrent = function (username) { +events.prototype._uploadCurrent = function (username) { var formData = new FormData(); formData.append('type', 'score'); diff --git a/libs/items.js b/libs/items.js index 23056d8d..6c13e824 100644 --- a/libs/items.js +++ b/libs/items.js @@ -196,20 +196,6 @@ items.prototype.setItem = function (itemId, itemNum) { core.updateStatusBar(); } -////// 删除某个物品 ////// -items.prototype.removeItem = function (itemId, itemNum) { - if (itemNum == null) itemNum = 1; - if (!core.hasItem(itemId)) return false; - var itemCls = core.material.items[itemId].cls; - core.status.hero.items[itemCls][itemId] -= itemNum; - if (core.status.hero.items[itemCls][itemId] <= 0) { - if (itemCls != 'keys') delete core.status.hero.items[itemCls][itemId]; - else core.status.hero.items[itemCls][itemId] = 0; - } - core.updateStatusBar(); - return true; -} - ////// 增加某个物品的个数 ////// items.prototype.addItem = function (itemId, itemNum) { if (itemNum == null) itemNum = 1; @@ -230,15 +216,22 @@ items.prototype.addItem = function (itemId, itemNum) { core.updateStatusBar(); } -// ---------- 装备相关 ------------ // - -items.prototype.getEquipTypeById = function (equipId) { - var type = core.material.items[equipId].equip.type; - if (typeof type == 'string') - type = this.getEquipTypeByName(type); - return type; +////// 删除某个物品 ////// +items.prototype.removeItem = function (itemId, itemNum) { + if (itemNum == null) itemNum = 1; + if (!core.hasItem(itemId)) return false; + var itemCls = core.material.items[itemId].cls; + core.status.hero.items[itemCls][itemId] -= itemNum; + if (core.status.hero.items[itemCls][itemId] <= 0) { + if (itemCls != 'keys') delete core.status.hero.items[itemCls][itemId]; + else core.status.hero.items[itemCls][itemId] = 0; + } + core.updateStatusBar(); + return true; } +// ---------- 装备相关 ------------ // + items.prototype.getEquipTypeByName = function (name) { var names = core.status.globalAttribute.equipName; for (var i = 0; i < names.length; ++i) { @@ -249,6 +242,13 @@ items.prototype.getEquipTypeByName = function (name) { return -1; } +items.prototype.getEquipTypeById = function (equipId) { + var type = core.material.items[equipId].equip.type; + if (typeof type == 'string') + type = this.getEquipTypeByName(type); + return type; +} + // 当前能否撞上某装备 items.prototype.canEquip = function (equipId, hint) { // 装备是否合法 @@ -365,8 +365,8 @@ items.prototype._realLoadEquip = function (type, loadId, unloadId, callback) { core.status.hero.equipment[type] = loadId || null; // --- 提示 - if (loadId) core.drawTip("已装备上" + loadEquip.name, core.material.icons.items[loadId]); - else if (unloadId) core.drawTip("已卸下" + unloadEquip.name, core.material.icons.items[unloadId]); + if (loadId) core.drawTip("已装备上" + loadEquip.name, loadId); + else if (unloadId) core.drawTip("已卸下" + unloadEquip.name, unloadId); if (callback) callback(); } diff --git a/libs/maps.js b/libs/maps.js index 445fc113..f03bfe60 100644 --- a/libs/maps.js +++ b/libs/maps.js @@ -155,7 +155,8 @@ maps.prototype._addEvent = function (block, x, y, event) { } ////// 初始化所有地图 ////// -maps.prototype.initMaps = function (floorIds) { +maps.prototype._initMaps = function () { + var floorIds = core.floorIds; var maps = {}; for (var i = 0; i < floorIds.length; i++) { var floorId = floorIds[i]; @@ -242,7 +243,7 @@ maps.prototype.saveMap = function (floorId) { return map; } var map = maps[floorId], floor = core.floors[floorId]; - var blocks = this.getMapArray(map.blocks, floor.width, floor.height, true); + var blocks = this._getMapArrayFromBlocks(map.blocks, floor.width, floor.height, true); if (main.mode == 'editor') return blocks; var thisFloor = this._compressFloorData(map, floor); @@ -297,7 +298,13 @@ maps.prototype.resizeMap = function (floorId) { } ////// 将当前地图重新变成二维数组形式 ////// -maps.prototype.getMapArray = function (blockArray, width, height, checkDisable) { +maps.prototype.getMapArray = function (floorId, showDisable) { + floorId = floorId || core.status.floorId; + return this._getMapArrayFromBlocks(core.status.maps[floorId].blocks, + core.floors[floorId].width, core.floors[floorId].height, showDisable); +} + +maps.prototype._getMapArrayFromBlocks = function (blockArray, width, height, showDisable) { if (typeof blockArray == 'string') { var floorId = blockArray; blockArray = core.status.maps[floorId].blocks; @@ -313,11 +320,11 @@ maps.prototype.getMapArray = function (blockArray, width, height, checkDisable) blockArray.forEach(function (block) { var x = block.x, y = block.y; if (block.disable) { - if (checkDisable) blocks[y][x] = block.id + ":f"; + if (showDisable) blocks[y][x] = block.id + ":f"; } else { blocks[y][x] = block.id; - if (checkDisable && block.disable === false) + if (showDisable && block.disable === false) blocks[y][x] = block.id + ":t"; } }); @@ -336,7 +343,7 @@ maps.prototype.getMapBlocksObj = function (floorId, showDisable) { } ////// 将背景前景层变成二维数组的形式 ////// -maps.prototype.getBgFgMapArray = function (name, floorId, noCache) { +maps.prototype._getBgFgMapArray = function (name, floorId, noCache) { floorId = floorId || core.status.floorId; if (!floorId) return []; var width = core.floors[floorId].width; @@ -362,10 +369,26 @@ maps.prototype.getBgFgMapArray = function (name, floorId, noCache) { return arr; } -maps.prototype.getBgFgNumber = function (name, x, y, floorId, noCache) { +maps.prototype.getBgMapArray = function (floorId, noCache) { + return this._getBgFgMapArray('bg', floorId, noCache); +} + +maps.prototype.getFgMapArray = function (floorId, noCache) { + return this._getBgFgMapArray('fg', floorId, noCache); +} + +maps.prototype._getBgFgNumber = function (name, x, y, floorId, noCache) { if (x == null) x = core.getHeroLoc('x'); if (y == null) y = core.getHeroLoc('y'); - return this.getBgFgMapArray(name, floorId, noCache)[y][x]; + return this._getBgFgMapArray(name, floorId, noCache)[y][x]; +} + +maps.prototype.getBgNumber = function (x, y, floorId, noCache) { + return this._getBgFgNumber('bg', x, y, floorId, noCache); +} + +maps.prototype.getFgNumber = function (x, y, floorId, noCache) { + return this._getBgFgNumber('fg', x, y, floorId, noCache); } // ------ 当前能否朝某方向移动,能否瞬间移动 ------ // @@ -375,8 +398,8 @@ maps.prototype.generateMovableArray = function (floorId, x, y, direction) { floorId = floorId || core.status.floorId; if (!floorId) return null; var width = core.floors[floorId].width, height = core.floors[floorId].height; - var bgArray = this.getBgFgMapArray('bg', floorId), - fgArray = this.getBgFgMapArray('fg', floorId), + var bgArray = this.getBgMapArray(floorId), + fgArray = this.getFgMapArray(floorId), eventArray = this.getMapArray(floorId); var generate = function (x, y, direction) { @@ -489,7 +512,7 @@ maps.prototype._canMoveDirectly_bfs = function (sx, sy, ex, ey) { var canMoveArray = this.generateMovableArray(); var blocksObj = this.getMapBlocksObj(core.status.floorId); // 滑冰 - var bgMap = this.getBgFgMapArray('bg'); + var bgMap = this.getBgMapArray(); var visited = [], queue = []; visited[sx + "," + sy] = 0; @@ -685,7 +708,7 @@ maps.prototype._drawMap_drawAll = function (floorId) { maps.prototype._drawMap_drawBlockInfo = function (ctx, block, blockInfo, arr, onMap) { if (blockInfo == null) return; if (blockInfo.cls == 'autotile') { // Autotile单独处理 - this.drawAutotile(ctx, arr, block, 32, 0, 0); + this._drawAutotile(ctx, arr, block, 32, 0, 0); if (onMap) this.addGlobalAnimate(block); return; } @@ -700,15 +723,16 @@ maps.prototype._drawMap_drawBlockInfo = function (ctx, block, blockInfo, arr, on ////// 绘制背景层 ////// maps.prototype.drawBg = function (floorId, ctx) { + floorId = floorId || core.status.floorId; var onMap = ctx == null; if (onMap) { ctx = core.canvas.bg; core.clearMap(ctx); } - this._drawBg_drawBackground(floorId, ctx); + core.maps._drawBg_drawBackground(floorId, ctx); // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。 - this._drawFloorImages(floorId, ctx, 'bg'); - this._drawBgFgMap(floorId, ctx, 'bg', onMap); + core.maps._drawFloorImages(floorId, ctx, 'bg'); + core.maps._drawBgFgMap(floorId, ctx, 'bg', onMap); } maps.prototype._drawBg_drawBackground = function (floorId, ctx) { @@ -728,7 +752,7 @@ maps.prototype._drawBg_drawBackground = function (floorId, ctx) { maps.prototype.drawEvents = function (floorId, blocks, ctx) { floorId = floorId || core.status.floorId; if (!blocks) blocks = core.status.maps[floorId].blocks; - var arr = this.getMapArray(blocks, core.floors[floorId].width, core.floors[floorId].height); + var arr = this._getMapArrayFromBlocks(blocks, core.floors[floorId].width, core.floors[floorId].height); var onMap = ctx == null; if (onMap) ctx = core.canvas.event; blocks.filter(function (block) { @@ -741,6 +765,7 @@ maps.prototype.drawEvents = function (floorId, blocks, ctx) { ////// 绘制前景层 ////// maps.prototype.drawFg = function (floorId, ctx) { + floorId = floorId || core.status.floorId; var onMap = ctx == null; if (onMap) ctx = core.canvas.fg; // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。 @@ -758,7 +783,7 @@ maps.prototype._drawBgFgMap = function (floorId, ctx, name, onMap) { if (!core.status[name + "maps"]) core.status[name + "maps"] = {}; - var arr = this.getBgFgMapArray(name, floorId, true); + var arr = this._getBgFgMapArray(name, floorId, true); for (var x = 0; x < width; x++) { for (var y = 0; y < height; y++) { var block = this.initBlock(x, y, arr[y][x], true); @@ -850,7 +875,7 @@ maps.prototype._drawFloorImage = function (ctx, name, type, image, offsetX, widt } ////// 绘制Autotile ////// -maps.prototype.drawAutotile = function (ctx, mapArr, block, size, left, top, status) { +maps.prototype._drawAutotile = function (ctx, mapArr, block, size, left, top, status) { var indexArrs = [ //16种组合的图块索引数组; // 将autotile分割成48块16*16的小块; 数组索引即对应各个小块 // +----+----+----+----+----+----+ [10, 9, 4, 3], //0 bin:0000 | 1 | 2 | 3 | 4 | 5 | 6 | @@ -1215,7 +1240,7 @@ maps.prototype.searchBlock = function (id, floorId, showDisable) { for (var i = 0; i < core.status.maps[floorId].blocks.length; ++i) { var block = core.status.maps[floorId].blocks[i]; if (block.event.id == id && (showDisable || !block.disable)) - result.push({floorId: floorId, index: i, block: block}); + result.push({floorId: floorId, index: i, block: block, x: block.x, y: block.y}); } return result; } @@ -1301,19 +1326,6 @@ maps.prototype.removeBlockById = function (index, floorId) { } } -////// 能否彻底从地图中删除一个图块 ////// -maps.prototype.canRemoveBlock = function (block, floorId) { - var x = block.x, y = block.y; - // 检查该点是否存在事件 - if (core.floors[floorId].events[x + "," + y] || core.floors[floorId].changeFloor[x + "," + y]) - return false; - // 检查是否存在重生 - if (block.event && block.event.cls.indexOf('enemy') == 0 && core.hasSpecial(block.event.id, 23)) - return false; - - return true; -} - ////// 一次性删除多个block ////// maps.prototype.removeBlockByIds = function (floorId, ids) { floorId = floorId || core.status.floorId; @@ -1325,24 +1337,17 @@ maps.prototype.removeBlockByIds = function (floorId, ids) { }); } -////// 将地图中所有某个图块替换成另一个图块 ////// -maps.prototype.replaceBlock = function (fromNumber, toNumber, floorId) { - floorId = floorId || core.status.floorId; - if (floorId instanceof Array) { - floorId.forEach(function (floorId) { - core.replaceBlock(fromNumber, toNumber, floorId); - }); - return; - } - var toBlock = this.initBlock(0, 0, toNumber, true); - core.status.maps[floorId].blocks.forEach(function (block) { - if (block.id == fromNumber) { - block.id = toNumber; - for (var one in toBlock.event) { - block.event[one] = core.clone(toBlock.event[one]); - } - } - }); +////// 能否彻底从地图中删除一个图块 ////// +maps.prototype.canRemoveBlock = function (block, floorId) { + var x = block.x, y = block.y; + // 检查该点是否存在事件 + if (core.floors[floorId].events[x + "," + y] || core.floors[floorId].changeFloor[x + "," + y]) + return false; + // 检查是否存在重生 + if (block.event && block.event.cls.indexOf('enemy') == 0 && core.hasSpecial(block.event.id, 23)) + return false; + + return true; } ////// 显示前景/背景地图 ////// @@ -1420,6 +1425,7 @@ maps.prototype.setBlock = function (number, x, y, floorId) { floorId = floorId || core.status.floorId; if (!floorId || number == null || x == null || y == null) return; if (x < 0 || x >= core.floors[floorId].width || y < 0 || y >= core.floors[floorId].height) return; + if (typeof number == 'string') number = core.getNumberById(number); var originBlock = core.getBlock(x, y, floorId, true); var block = this.initBlock(x, y, number, true, core.floors[floorId]); @@ -1447,6 +1453,26 @@ maps.prototype.setBlock = function (number, x, y, floorId) { } } +////// 将地图中所有某个图块替换成另一个图块 ////// +maps.prototype.replaceBlock = function (fromNumber, toNumber, floorId) { + floorId = floorId || core.status.floorId; + if (floorId instanceof Array) { + floorId.forEach(function (floorId) { + core.replaceBlock(fromNumber, toNumber, floorId); + }); + return; + } + var toBlock = this.initBlock(0, 0, toNumber, true); + core.status.maps[floorId].blocks.forEach(function (block) { + if (block.id == fromNumber) { + block.id = toNumber; + for (var one in toBlock.event) { + block.event[one] = core.clone(toBlock.event[one]); + } + } + }); +} + ////// 改变前景背景的图块 ////// maps.prototype.setBgFgBlock = function (name, number, x, y, floorId) { floorId = floorId || core.status.floorId; @@ -1629,10 +1655,8 @@ maps.prototype.jumpBlock = function (sx, sy, ex, ey, time, keep, callback) { var block = blockArr[0], blockInfo = blockArr[1]; var canvases = this._initDetachedBlock(blockInfo, sx, sy, block.event.animate !== false); this._moveDetachedBlock(blockInfo, 32 * sx, 32 * sy, 1, canvases); - - this.__playJumpSound(); - - var jumpInfo = ths.__generateJumpInfo(sx, sy, ex, ey, time); + core.playSound('jump.mp3'); + var jumpInfo = this.__generateJumpInfo(sx, sy, ex, ey, time); jumpInfo.keep = keep; this._jumpBlock_doJump(blockInfo, canvases, jumpInfo, callback); @@ -1644,14 +1668,10 @@ maps.prototype.__generateJumpInfo = function (sx, sy, ex, ey, time) { return { x: sx, y: sy, ex: ex, ey: ey, px: 32 * sx, py: 32 * sy, opacity: 1, jump_peak: jump_peak, jump_count: jump_count, - step: 0, per_time: time / 16 / core.status.replay.speed + step: 0, per_time: time / jump_count }; } -maps.prototype.__playJumpSound = function () { - core.playSound('jump.mp3'); -} - maps.prototype._jumpBlock_doJump = function (blockInfo, canvases, jumpInfo, callback) { var animate = window.setInterval(function () { if (jumpInfo.jump_count > 0) @@ -1766,16 +1786,16 @@ maps.prototype._animateBlock_drawList = function (list, opacity) { // ------ 全局动画控制,动画绘制 ------ // ////// 添加一个全局动画 ////// -maps.prototype.addGlobalAnimate = function (b) { - if (!b.event || b.event.animate == null) return; - if (b.event.cls == 'autotile') { - var id = b.event.id, img = core.material.images.autotile[id]; +maps.prototype.addGlobalAnimate = function (block) { + if (!block.event || block.event.animate == null) return; + if (block.event.cls == 'autotile') { + var id = block.event.id, img = core.material.images.autotile[id]; if (!img || img.width == 96) return; - core.status.autotileAnimateObjs.blocks.push(b); + core.status.autotileAnimateObjs.blocks.push(block); } else { - if (!b.event.animate || b.event.animate == 1) return; - core.status.globalAnimateObjs.push(b); + if (!block.event.animate || block.event.animate == 1) return; + core.status.globalAnimateObjs.push(block); } } diff --git a/libs/ui.js b/libs/ui.js index 8a569922..9d63d8d0 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -23,12 +23,13 @@ ui.prototype._init = function () { ////////////////// 地图设置 -ui.prototype.getContextByName = function (canvas) { - if (typeof canvas == 'string') { - if (core.canvas[canvas]) - canvas = core.canvas[canvas]; - else if (core.dymCanvas[canvas]) - canvas = core.dymCanvas[canvas]; +ui.prototype.getContextByName = function (name) { + var canvas = name; + if (typeof name == 'string') { + if (core.canvas[name]) + canvas = core.canvas[name]; + else if (core.dymCanvas[name]) + canvas = core.dymCanvas[name]; } if (canvas && canvas.canvas) { return canvas; @@ -190,6 +191,36 @@ ui.prototype.calWidth = function (name, text, font) { return 0; } +////// 字符串自动换行的分割 ////// +ui.prototype.splitLines = function (name, text, maxWidth, font) { + var ctx = this.getContextByName(name); + if (!ctx) return; + if (font) core.setFont(name, font); + + var contents = []; + var last = 0; + for (var i = 0; i < text.length; i++) { + if (text.charAt(i) == '\n') { + contents.push(text.substring(last, i)); + last = i + 1; + } + else if (text.charAt(i) == '\\' && text.charAt(i + 1) == 'n') { + contents.push(text.substring(last, i)); + last = i + 2; + } + else { + var toAdd = text.substring(last, i + 1); + var width = core.calWidth(name, toAdd); + if (maxWidth && width > maxWidth) { + contents.push(text.substring(last, i)); + last = i; + } + } + } + contents.push(text.substring(last)); + return contents; +} + ////// 绘制一张图片 ////// ui.prototype.drawImage = function (name, image, x, y, w, h, x1, y1, w1, h1) { var ctx = this.getContextByName(name); @@ -233,25 +264,23 @@ ui.prototype.clearUI = function () { clearInterval(core.status.event.interval); core.status.event.interval = null; core.status.boxAnimateObjs = []; - core.clearSelector(); - core.clearMap('ui'); - core.setAlpha('ui', 1); -} - -////// 清除光标 ////// -ui.prototype.clearSelector = function () { if (core.dymCanvas._selector) core.deleteCanvas("_selector"); core.clearMap('ui'); core.setAlpha('ui', 1); } ////// 左上角绘制一段提示 ////// -ui.prototype.drawTip = function (text, itemIcon) { +ui.prototype.drawTip = function (text, id) { var textX, textY, width, height; clearInterval(core.interval.tipAnimate); core.setFont('data', "16px Arial"); core.setTextAlign('data', 'left'); - if (!itemIcon) { + if (id != null) { + var info = core.getBlockInfo(id); + if (info == null || !info.image || info.height != 32) id = null; + else id = info; + } + if (!id) { textX = 16; textY = 18; width = textX + core.calWidth('data', text) + 16; @@ -263,10 +292,10 @@ ui.prototype.drawTip = function (text, itemIcon) { width = textX + core.calWidth('data', text) + 8; height = 42; } - this._drawTip_animate(text, itemIcon, textX, textY, width, height); + this._drawTip_animate(text, id, textX, textY, width, height); } -ui.prototype._drawTip_animate = function (text, itemIcon, textX, textY, width, height) { +ui.prototype._drawTip_animate = function (text, info, textX, textY, width, height) { var alpha = 0, hide = false; core.interval.tipAnimate = window.setInterval(function () { if (hide) alpha -= 0.1; @@ -274,8 +303,8 @@ ui.prototype._drawTip_animate = function (text, itemIcon, textX, textY, width, h core.clearMap('data', 5, 5, core.ui.PIXEL, height); core.setAlpha('data', alpha); core.fillRect('data', 5, 5, width, height, '#000'); - if (itemIcon) - core.drawImage('data', core.material.images.items, 0, itemIcon * 32, 32, 32, 10, 8, 32, 32); + if (info) + core.drawImage('data', info.image, info.posX * 32, info.posY * 32, 32, 32, 10, 8, 32, 32); core.fillText('data', text, textX + 5, textY + 15, '#fff'); core.setAlpha('data', 1); if (alpha > 0.6 || alpha < 0) { @@ -507,13 +536,13 @@ ui.prototype.drawBackground = function (left, top, right, bottom, posInfo) { } ////// 计算有效文本框的宽度 -ui.prototype.calTextBoxWidth = function (ctx, content, min_width, max_width, font) { +ui.prototype._calTextBoxWidth = function (ctx, content, min_width, max_width, font) { // 无限长度自动换行 var allLines = core.splitLines(ctx, content, null, font); // 如果不存在手动换行,尽量调成半行形式 if (allLines.length == 1) { - var w = core.calWidth(ctx, allLines[0]); + var w = core.calWidth(ctx, allLines[0]) + 10; if (w 1) - core.fillText('ui', '上一页', this.HPIXEL - 80, top*32+19); + core.fillText('ui', '上一页', this.HPIXEL - 80, y*32+19); if (page < totalPage) - core.fillText('ui', '下一页', this.HPIXEL + 80, top*32+19); + core.fillText('ui', '下一页', this.HPIXEL + 80, y*32+19); } ////// 绘制键盘光标 ////// @@ -1254,6 +1287,7 @@ ui.prototype.drawBook = function (index) { core.drawBoxAnimate(); this.drawPagination(page, totalPage); + core.setTextAlign('ui', 'center'); core.fillText('ui', '返回游戏', this.PIXEL - 46, this.PIXEL - 13,'#DDDDDD', this._buildFont(15, true)); } @@ -1632,7 +1666,7 @@ ui.prototype.drawMaps = function (index, x, y) { var offsetX = 32 * (data.x - this.HSIZE), offsetY = 32 * (data.y - this.HSIZE); var value = core.paint[data.floorId]; if (value) value = lzw_decode(value).split(","); - core.utils.decodeCanvas(value, 32 * data.mw, 32 * data.mh); + core.utils._decodeCanvas(value, 32 * data.mw, 32 * data.mh); core.drawImage('ui', core.bigmap.tempCanvas.canvas, offsetX * 32, offsetY * 32, this.PIXEL, this.PIXEL, 0, 0, this.PIXEL, this.PIXEL); } @@ -2358,13 +2392,13 @@ ui.prototype._drawPaint_draw = function () { core.status.event.id = 'paint'; core.status.event.data = {"x": null, "y": null, "erase": false}; - core.clearSelector(); + core.clearUI(); core.createCanvas('paint', -core.bigmap.offsetX, -core.bigmap.offsetY, 32*core.bigmap.width, 32*core.bigmap.height, 95); // 将已有的内容绘制到route上 var value = core.paint[core.status.floorId]; if (core.isset(value)) value = lzw_decode(value).split(","); - core.utils.decodeCanvas(value, 32*core.bigmap.width, 32*core.bigmap.height); + core.utils._decodeCanvas(value, 32*core.bigmap.width, 32*core.bigmap.height); core.drawImage('paint', core.bigmap.tempCanvas.canvas, 0, 0); core.setLineWidth('paint', 3); @@ -2383,7 +2417,7 @@ ui.prototype._drawPaint_draw = function () { ////// 绘制帮助页面 ////// ui.prototype.drawHelp = function () { - core.clearSelector(); + core.clearUI(); if (core.material.images.keyboard) { core.status.event.id = 'help'; core.lockControl(); diff --git a/libs/utils.js b/libs/utils.js index d3bac9d3..d491197e 100644 --- a/libs/utils.js +++ b/libs/utils.js @@ -75,35 +75,6 @@ utils.prototype.calValue = function (value, prefix, need, times) { return value; } -////// 字符串自动换行的分割 ////// -utils.prototype.splitLines = function (canvas, text, maxLength, font) { - if (font) core.setFont(canvas, font); - - var contents = []; - var last = 0; - for (var i = 0; i < text.length; i++) { - - if (text.charAt(i) == '\n') { - contents.push(text.substring(last, i)); - last = i + 1; - } - else if (text.charAt(i) == '\\' && text.charAt(i + 1) == 'n') { - contents.push(text.substring(last, i)); - last = i + 2; - } - else { - var toAdd = text.substring(last, i + 1); - var width = core.calWidth(canvas, toAdd); - if (maxLength && width > maxLength) { - contents.push(text.substring(last, i)); - last = i; - } - } - } - contents.push(text.substring(last)); - return contents; -} - ////// 向某个数组前插入另一个数组或元素 ////// utils.prototype.unshift = function (a, b) { if (!(a instanceof Array) || b == null) return; @@ -128,6 +99,28 @@ utils.prototype.push = function (a, b) { return a; } +utils.prototype.decompress = function (value) { + try { + var output = lzw_decode(value); + if (output) return JSON.parse(output); + } + catch (e) { + } + try { + var output = LZString.decompress(value); + if (output) return JSON.parse(output); + } + catch (e) { + } + try { + return JSON.parse(value); + } + catch (e) { + main.log(e); + } + return null; +} + ////// 设置本地存储 ////// utils.prototype.setLocalStorage = function (key, value) { try { @@ -163,28 +156,6 @@ utils.prototype.setLocalStorage = function (key, value) { } } -utils.prototype.decompress = function (value) { - try { - var output = lzw_decode(value); - if (output) return JSON.parse(output); - } - catch (e) { - } - try { - var output = LZString.decompress(value); - if (output) return JSON.parse(output); - } - catch (e) { - } - try { - return JSON.parse(value); - } - catch (e) { - main.log(e); - } - return null; -} - ////// 获得本地存储 ////// utils.prototype.getLocalStorage = function (key, defaultValue) { var res = this.decompress(localStorage.getItem(core.firstData.name + "_" + key)); @@ -475,7 +446,7 @@ utils.prototype._encodeRoute_encodeOne = function (t) { return 'K' + t.substring(4); else if (t.indexOf('random:') == 0) return 'X' + t.substring(7); - return ''; + return '('+t+')'; } ////// 解密路线 ////// @@ -485,8 +456,9 @@ utils.prototype.decodeRoute = function (route) { // 解压缩 try { var v = LZString.decompressFromBase64(route); - if (/^[a-zA-Z0-9+\/=:]*$/.test(v)) { - route = v; + if (v != null && /^[-_a-zA-Z0-9+\/=:()]*$/.test(v)) { + if (v != "" || route.length < 8) + route = v; } } catch (e) { } @@ -525,6 +497,15 @@ utils.prototype._decodeRoute_number2id = function (number) { } utils.prototype._decodeRoute_decodeOne = function (decodeObj, c) { + // --- 特殊处理自定义项 + if (c == '(') { + var idx = decodeObj.route.indexOf(')', decodeObj.index); + if (idx >= 0) { + decodeObj.ans.push(decodeObj.route.substring(decodeObj.index, idx)); + decodeObj.index = idx + 1; + return; + } + } var nxt = (c == 'I' || c == 'e' || c == 'F' || c == 'S' || c == 'Q' || c == 't') ? this._decodeRoute_getString(decodeObj) : this._decodeRoute_getNumber(decodeObj); @@ -618,6 +599,7 @@ utils.prototype.getCookie = function (name) { ////// 设置statusBar的innerHTML,会自动斜体和放缩,也可以增加自定义css ////// utils.prototype.setStatusBarInnerHTML = function (name, value, css) { + if (!core.statusBar[name]) return; if (typeof value == 'number') value = this.formatBigNumber(value); // 判定是否斜体 var italic = /^[-a-zA-Z0-9`~!@#$%^&*()_=+\[{\]}\\|;:'",<.>\/?]*$/.test(value); @@ -921,7 +903,7 @@ utils.prototype.myprompt = function (hint, value, callback) { } ////// 动画显示某对象 ////// -utils.prototype.show = function (obj, speed, callback) { +utils.prototype.showWithAnimate = function (obj, speed, callback) { obj.style.display = 'block'; if (!speed && main.mode != 'play') { obj.style.opacity = 1; @@ -941,7 +923,7 @@ utils.prototype.show = function (obj, speed, callback) { } ////// 动画使某对象消失 ////// -utils.prototype.hide = function (obj, speed, callback) { +utils.prototype.hideWithAnimate = function (obj, speed, callback) { if (!speed || main.mode != 'play') { obj.style.display = 'none'; if (callback) callback(); @@ -960,7 +942,7 @@ utils.prototype.hide = function (obj, speed, callback) { }, speed); } -utils.prototype.encodeCanvas = function (ctx) { +utils.prototype._encodeCanvas = function (ctx) { var list = []; var width = ctx.canvas.width, height = ctx.canvas.height; ctx.mozImageSmoothingEnabled = false; @@ -987,7 +969,7 @@ utils.prototype.encodeCanvas = function (ctx) { } ////// 解析arr数组,并绘制到tempCanvas上 ////// -utils.prototype.decodeCanvas = function (arr, width, height) { +utils.prototype._decodeCanvas = function (arr, width, height) { // 清空tempCanvas var tempCanvas = core.bigmap.tempCanvas; tempCanvas.canvas.width = width; @@ -1070,7 +1052,7 @@ utils.prototype._export = function (floorIds) { // map var content = floorIds.length + "\n" + core.__SIZE__ + " " + core.__SIZE__ + "\n\n"; floorIds.forEach(function (floorId) { - var arr = core.maps.getMapArray(core.status.maps[floorId].blocks); + var arr = core.maps._getMapArrayFromBlocks(core.status.maps[floorId].blocks); content += arr.map(function (x) { // check monster x.forEach(function (t) { diff --git a/main.js b/main.js index 9d26b266..6091c580 100644 --- a/main.js +++ b/main.js @@ -364,7 +364,7 @@ main.dom.body.onselectstart = function () { main.dom.data.onmousedown = function (e) { try { e.stopPropagation(); - var loc = main.core.getClickLoc(e.clientX, e.clientY); + var loc = main.core.actions._getClickLoc(e.clientX, e.clientY); if (loc == null) return; main.core.ondown(loc); } catch (ee) { main.log(ee); } @@ -374,7 +374,7 @@ main.dom.data.onmousedown = function (e) { main.dom.data.onmousemove = function (e) { try { e.stopPropagation(); - var loc = main.core.getClickLoc(e.clientX, e.clientY); + var loc = main.core.actions._getClickLoc(e.clientX, e.clientY); if (loc == null) return; main.core.onmove(loc); }catch (ee) { main.log(ee); } @@ -401,7 +401,7 @@ main.dom.data.onmousewheel = function(e) { main.dom.data.ontouchstart = function (e) { try { e.preventDefault(); - var loc = main.core.getClickLoc(e.targetTouches[0].clientX, e.targetTouches[0].clientY); + var loc = main.core.actions._getClickLoc(e.targetTouches[0].clientX, e.targetTouches[0].clientY); if (loc == null) return; main.core.ondown(loc); }catch (ee) { main.log(ee); } @@ -411,7 +411,7 @@ main.dom.data.ontouchstart = function (e) { main.dom.data.ontouchmove = function (e) { try { e.preventDefault(); - var loc = main.core.getClickLoc(e.targetTouches[0].clientX, e.targetTouches[0].clientY); + var loc = main.core.actions._getClickLoc(e.targetTouches[0].clientX, e.targetTouches[0].clientY); if (loc == null) return; main.core.onmove(loc); }catch (ee) { main.log(ee); } diff --git a/project/data.js b/project/data.js index 096e2dc6..ba2c9d99 100644 --- a/project/data.js +++ b/project/data.js @@ -78,6 +78,7 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = "lv": 1, "hpmax": 9999, "hp": 1000, + "manamax": -1, "mana": 0, "atk": 100, "def": 100, diff --git a/project/events.js b/project/events.js index cfc58540..d8559846 100644 --- a/project/events.js +++ b/project/events.js @@ -223,7 +223,7 @@ var events_c12a15a8_c380_4b28_8144_256cba95f760 = }, { "type": "if", - "condition": "core.getBgFgNumber('bg') == 167", + "condition": "core.getBgNumber() == 167", "true": [ { "type": "function", diff --git a/project/floors/sample1.js b/project/floors/sample1.js index a7d256df..92b3a0e8 100644 --- a/project/floors/sample1.js +++ b/project/floors/sample1.js @@ -151,7 +151,7 @@ main.floors.sample1= "type": "hide" }, { - "type": "setFg", + "type": "setCurtain", "color": [ 0, 0, @@ -184,7 +184,7 @@ main.floors.sample1= "2,11": [ "\t[杰克,thief]喂!醒醒!快醒醒!", { - "type": "setFg", + "type": "setCurtain", "time": 1500 }, "\t[hero]额,我这是在什么地方?", diff --git a/project/functions.js b/project/functions.js index 2044fe73..4e4c0762 100644 --- a/project/functions.js +++ b/project/functions.js @@ -6,6 +6,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = // hero:勇士信息;hard:难度;floorId:当前楼层ID;maps:地图信息;values:全局数值信息 // 清除游戏数据 + // 这一步会清空状态栏和全部画布内容,并删除所有动态创建的画布 core.clearStatus(); // 初始化status core.status = core.clone(core.initStatus); @@ -753,152 +754,156 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = // keyCode:当前按键的keyCode(每个键的keyCode自行百度) // altKey:Alt键是否被按下,为true代表同时按下了Alt键 // 可以在这里任意增加或编辑每个按键的行为 - + // 如果处于正在行走状态,则不处理 - if (!core.status.heroStop || core.status.heroMoving > 0) + if (core.isMoving()) return; // Alt+0~9,快捷换上套装 - if (altKey && keyCode>=48 && keyCode<=57) { - core.items.quickLoadEquip(keyCode-48); + if (altKey && keyCode >= 48 && keyCode <= 57) { + core.items.quickLoadEquip(keyCode - 48); return; } // 根据keyCode值来执行对应操作 switch (keyCode) { - case 27: // ESC:打开菜单栏 - core.openSettings(true); - break; - case 88: // X:使用怪物手册 - core.openBook(true); - break; - case 71: // G:使用楼传器 - core.useFly(true); - break; - case 65: // A:读取自动存档(回退) - core.doSL("autoSave", "load"); - break; - case 83: // S:存档 - core.save(true); - break; - case 68: // D:读档 - core.load(true); - break; - case 69: // E:打开光标 - core.ui.drawCursor(); - break; - case 84: // T:打开道具栏 - core.openToolbox(true); - break; - case 81: // Q:打开装备栏 - core.openEquipbox(true); - break; - case 90: // Z:转向 - core.turnHero(); - break; - case 75: case 86: // V:打开快捷商店列表 - core.openQuickShop(true); - break; - case 32: // SPACE:轻按 - core.getNextItem(); - break; - case 82: // R:回放录像 - core.actions._clickSyncSave_replay(); - break; - case 33: case 34: // PgUp/PgDn:浏览地图 - core.ui.drawMaps(); - break; - case 77: // M:绘图模式 - core.ui.drawPaint(); - break; - case 66: // B:打开数据统计 - core.ui.drawStatistics(); - break; - case 72: // H:打开帮助页面 - core.ui.drawHelp(); - break; - case 78: // N:重新开始 - core.confirmRestart(); - break; - case 79: // O:查看工程 - core.actions._clickGameInfo_openProject(); - break; - case 80: // P:游戏主页 - core.actions._clickGameInfo_openComments(); - break; - case 49: // 快捷键1: 破 - if (core.hasItem('pickaxe')) { - if (core.canUseItem('pickaxe')) { - core.useItem('pickaxe'); - } - else { - core.drawTip('当前不能使用破墙镐'); - } + case 27: // ESC:打开菜单栏 + core.openSettings(true); + break; + case 88: // X:使用怪物手册 + core.openBook(true); + break; + case 71: // G:使用楼传器 + core.useFly(true); + break; + case 65: // A:读取自动存档(回退) + core.doSL("autoSave", "load"); + break; + case 83: // S:存档 + core.save(true); + break; + case 68: // D:读档 + core.load(true); + break; + case 69: // E:打开光标 + core.ui.drawCursor(); + break; + case 84: // T:打开道具栏 + core.openToolbox(true); + break; + case 81: // Q:打开装备栏 + core.openEquipbox(true); + break; + case 90: // Z:转向 + core.turnHero(); + break; + case 75: + case 86: // V:打开快捷商店列表 + core.openQuickShop(true); + break; + case 32: // SPACE:轻按 + core.getNextItem(); + break; + case 82: // R:回放录像 + core.actions._clickSyncSave_replay(); + break; + case 33: + case 34: // PgUp/PgDn:浏览地图 + core.ui.drawMaps(); + break; + case 77: // M:绘图模式 + core.ui.drawPaint(); + break; + case 66: // B:打开数据统计 + core.ui.drawStatistics(); + break; + case 72: // H:打开帮助页面 + core.ui.drawHelp(); + break; + case 78: // N:重新开始 + core.confirmRestart(); + break; + case 79: // O:查看工程 + core.actions._clickGameInfo_openProject(); + break; + case 80: // P:游戏主页 + core.actions._clickGameInfo_openComments(); + break; + case 49: // 快捷键1: 破 + if (core.hasItem('pickaxe')) { + if (core.canUseItem('pickaxe')) { + core.status.route.push("key:49"); // 将按键记在录像中 + core.useItem('pickaxe', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像 + } else { + core.drawTip('当前不能使用破墙镐'); } - break; - case 50: // 快捷键2: 炸 - if (core.hasItem('bomb')) { - if (core.canUseItem('bomb')) { - core.useItem('bomb'); - } - else { - core.drawTip('当前不能使用炸弹'); - } + } + break; + case 50: // 快捷键2: 炸 + if (core.hasItem('bomb')) { + if (core.canUseItem('bomb')) { + core.status.route.push("key:50"); // 将按键记在录像中 + core.useItem('bomb', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像 + } else { + core.drawTip('当前不能使用炸弹'); } - else if (core.hasItem('hammer')) { - if (core.canUseItem('hammer')) { - core.useItem('hammer'); - } - else { - core.drawTip('当前不能使用圣锤'); - } - - } - break; - case 51: // 快捷键3: 飞 - if (core.hasItem('centerFly')) { - core.ui.drawCenterFly(); - } - break; - case 52: // 快捷键4:破冰/冰冻/地震/上下楼器/... 其他道具依次判断 - { - var list = ["icePickaxe", "snow", "earthquake", "upFly", "downFly", "jumpShoes", "lifeWand", "poisonWine", "weakWine", "curseWine", "superWine"]; - for (var i=0;i= 0) { + core.status.hero.mana = Math.min(core.status.hero.mana, core.status.hero.manamax); + core.setStatusBarInnerHTML('mana', core.status.hero.mana + "/" + core.status.hero.manamax); + } + else { + core.setStatusBarInnerHTML("mana", core.status.hero.mana); + } } // 设置技能栏 if (core.flags.enableSkill) { diff --git a/project/plugins.js b/project/plugins.js index 3f0afa16..d09e9944 100644 --- a/project/plugins.js +++ b/project/plugins.js @@ -1,18 +1,28 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { "init": function () { - // 在这里写的代码,在所有模块加载完毕后,游戏开始前会被执行 + console.log("插件编写测试"); - // 可以写一些其他的被直接执行的代码 + // 可以写一些直接执行的代码 + // 在这里写的代码将会在【资源加载前】被执行,此时图片等资源尚未被加载。 + // 请勿在这里对包括bgm,图片等资源进行操作。 - this.test = function () { - console.log("插件函数执行测试"); - console.log(this); + this._afterLoadResources = function () { + // 本函数将在所有资源加载完毕后,游戏开启前被执行 + // 可以在这个函数里面对资源进行一些操作,比如切分图片等。 + + // 这是一个将assets.png拆分成若干个32x32像素的小图片并保存的样例。 + // var arr = core.splitImage("assets.png", 32, 32); + // for (var i = 0; i < arr.length; i++) { + // core.material.images.images["asset"+i+".png"] = arr[i]; + // } + } - // 可以在任何地方(如afterXXX或自定义脚本事件)调用函数,方法为 core.plugin.xxx(); + // 可以在任何地方(如afterXXX或自定义脚本事件)调用函数,方法为 core.plugin.xxx(); + // 从V2.6开始,插件中用this.XXX方式定义的函数也会被转发到core中,详见文档-脚本-函数的转发。 }, "drawLight": function () {