diff --git a/.gitignore b/.gitignore index 928ab591..cd2cdffd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .vscode -*ce5eec52_2fa1_447b_8dad_764e267a7fab* .DS_Store +MTBuilder.app # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 diff --git a/API列表.txt b/API列表.txt new file mode 100644 index 00000000..8233a735 --- /dev/null +++ b/API列表.txt @@ -0,0 +1,2144 @@ +附录:API列表(V2.6版) + +这里将列出所有被转发到core的API,没有被转发的函数此处不会列出,请自行在代码中查看。 + +本文档较大,如有什么需求请自行Ctrl+F进行搜索。 + +如有任何疑问,请联系小艾寻求帮助。 + +------------------------------------ [core.js] ------------------------------------ + +core.js中只有很少的几个函数,主要是游戏开始前的初始化等。 +但是,core中定义了很多游戏运行时的状态,这些状态很多都会被使用到。 + + + + +core.__SIZE__, core.__PIXELS__ +游戏窗口大小;对于13x13的游戏而言这两个值分别是13和416,15x15来说分别是15和480。 + + +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.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.status.maps不同的是,后者在每次重新开始和读档时都会重置,也允许被修改(会存入存档)。 +而core.floors全程唯一,不允许被修改。 + + +core.statusBar +状态栏信息,例如状态栏图片,图标,以及各个内容的DOM定义等。 +core.statusBar.images (所有的系统图标,和icons.png对应) +core.statusBar.icons (状态栏中绘制的图标内容) + + +core.values +所有的全局数值信息,和全塔属性中的values一致。 +此项允许被直接修改,会存入存档。 + + +core.flags +所有的全塔开关,和全塔属性中的flags一致。 +此项不允许被直接修改,如有需要请使用“设置系统开关”事件,或者调用core.setGlobalFlag这个API。 + + +core.plugin +定义的插件函数。 + + +core.doFunc(func, _this) +执行一个函数,func为函数体或者插件中的函数名,_this为使用的this。 +如果func为一个字符串,则视为插件中的函数名,同时_this将被设置成core.plugin。 +此函数剩余参数将作为参数被传入func。 + + + + +------------------------------------ [actions.js] ------------------------------------ + +actions.js主要是处理一些和用户交互相关的内容。 + + + + +core.registerAction(action, name, func, priority) +注册一个用户交互行为。 +action:要注册的交互类型,如 ondown, onclick, keyDown 等等。 +name:你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +func:执行函数;可以是一个具体的函数体,或者是一个插件中的函数名。 +priority:优先级;优先级高的被注册项将会被执行。此项可不填,默认为0。 +返回:如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 + + +core.unregisterAction(action, name) +注销一个用户交互行为。 + + +core.doRegisteredAction(action) +执行一个用户交互行为。 +此函数将在该交互行为所注册的所有函数中,按照优先级从高到底依次执行。 +此函数剩余的参数将会作为参数传入该执行函数中。 +当某个执行函数返回true时将终止这一过程。 + + +core.onkeyDown(e) +当按下某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyDown"的交互函数。 + + +core.onkeyUp(e) +当放开某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyUp"的交互函数。 + + +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相关 + + + + +// ------ 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(n) +获得勇士面对的第n个位置的横纵坐标。n可不填,默认为1。 + + +core.nearHero(x, y, n) +判定某个点是否和勇士的距离不大于n。n可不填,默认为1。 + + +core.gatherFollowers() +聚集所有的跟随者到勇士的位置。 + + +core.updateFollowers() +更新跟随者们的坐标。 + + +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.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函数。 + + + + +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(enemyId, floorId) +检查某个楼层是否还有剩余的(指定)怪物。 +floorId为楼层ID,可忽略表示当前楼层。也可以传数组如["MT0","MT1"]同时检测多个楼层。 +enemyId如果不填或null则检查是否剩余任何怪物,否则只检查是否剩余指定的某类怪物。 + + + + +------------------------------------ [events.js] ------------------------------------ + +events.js将处理所有和事件相关的操作,主要分为五个部分: +- 游戏的开始和结束 +- 系统事件的处理 +- 自定义事件的处理 +- 点击状态栏图标所进行的操作 +- 一些具体事件的执行内容 + + + + +// ------ 游戏的开始和结束 ------ // + +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主要是负责素材相关信息,比如某个素材在对应的图片上的位置。 + + + + +core.getClsFromId(id) +根据某个素材的ID获得该素材的cls + + +core.getTilesetOffset(id) +根据某个素材来获得对应的tileset和坐标信息。 +如果该素材不是tileset,则返回null。 + + + + +------------------------------------ [items.js] ------------------------------------ + +items.js主要负责一切和道具相关的内容。 + + + + +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正在播放则也会立刻停止。 + + + + +------------------------------------ [maps.js] ------------------------------------ + +maps.js负责一切和地图相关的处理内容,包括如下几个方面: +- 地图的初始化,保存和读取,地图数组的生成 +- 是否可移动或瞬间移动的判定 +- 地图的绘制 +- 获得某个点的图块信息 +- 启用和禁用图块,改变图块 +- 移动/跳跃图块,淡入淡出图块 +- 全局动画控制,动画的绘制 + + + + +// ------ 地图的初始化,保存和读取,地图数组的生成 ------ // + +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为目标楼层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) +判定某个点是否有(指定的)地形存在。 +如果id为null,则只要存在terrains即为真,否则还会判定对应点的ID。 + + +core.stairExists(x, y, floorId) +判定某个点是否存在楼梯。 + + +core.nearStair() +判定当前勇士是否在楼梯上或旁边(距离不超过1)。 + + +core.enemyExists(x, y, id, floorId) +判定某个点是否有(指定的)怪物存在。 +如果id为null,则只要存在怪物即为真,否则还会判定对应点的怪物ID。 +请注意,如果需要判定某个楼层是否存在怪物请使用core.hasEnemyLeft()函数。 + + +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。如果该点不存在图块则返回null。 + + +core.getBlockCls(x, y, floorId, showDisable) +获得某个点的图块类型。如果该点不存在图块则返回null。 + + +core.getBlockInfo(block) +根据某个的图块信息获得其详细的素材信息。 +如果参数block为字符串,则视为图块ID;如果参数为数字,则视为图块的数字。 +此函数将返回一个非常详尽的素材信息,目前包括如下几项: +number:素材数字;id:素材id;cls:素材类型;image:素材所在的素材图片;animate:素材的帧数。 +posX, posY:素材在该素材图片上的位置;height:素材的高度;faceIds:NPC朝向记录。 + + +core.searchBlock(id, floorId, showDisable) +搜索一个图块出现过的所有位置。id为图块ID,也可以传入图块的数字。 +id支持通配符搜索,比如"*Door"可以搜索所有的门,"unknownEvent*"可以所有所有的unknownEvent。 +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表示当前楼层。 +此函数不会实际将该块从地图中进行删除,而是将该点设置为禁用,以供以后可能的启用事件。 + + +core.removeBlock(x, y, floorId) +将从启用变成禁用状态,并尽可能将其从地图上删除。 +和hideBlock相比,如果该点不存在自定义事件(比如门或普通的怪物),则将直接从地图中删除。 +如果存在自定义事件,则简单的禁用它,以供以后可能的启用事件。 + + +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为要改变到的数字,也可以传入图块id(将调用core.getNumberById()来获得数字)。 +x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 + + +core.replaceBlock(fromNumber, toNumber, floorId) +将某个或某些楼层中的所有某个图块替换成另一个图块 +fromNumber和toNumber为要被替换和替换到的数字。 +floorId可为某个楼层ID,或者一个楼层数组;如果不填只视为当前楼层。 +值得注意的是,使用此函数转了的点上的自定义事件可能无法被执行。 +如有需要,再对那些存在事件的点执行core.setBlock()即可 + + +core.setBgFgBlock(name, number, x, y, floorId) +设置前景/背景层的某个图块。name只能为'bg'或'fg'表示前景或背景层。 +number为要设置到的图块数字,x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 + + +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.jumpBlock(sx, sy, ex, ey, time, keep, callback) +跳跃一个图块,sx和sy为图块的坐标,ex和ey为目标坐标。time为整个跳跃过程中的全程用时,不填默认500。 +如果keep为真,则在移动完毕后将自动调用一个setBlock事件,改变目标点的图块(即不消失), +否则会按照time时间来淡出消失。callback会执行完毕后的回调。 + + +core.animateBlock(loc, type, time, callback) +淡入/淡出一个或多个图块。 +loc为一个图块坐标,或者一个二维数组表示一系列图块坐标(将同时显示和隐藏)。 +type只能为'show'或'hide'表示是淡入但是淡出。time为动画时间,callback为执行完毕的回调。 + +// ------ 全局动画控制,动画的绘制 ------ // + +core.addGlobalAnimate(block) +添加一个全局帧动画。 + + +core.removeGlobalAnimate(x, y, name) +删除一个或全部的全局帧动画。name可为'bg',null或'fg'表示某个图层。 +x和y如果为null,则会删除全部的全局帧动画,否则只会删除该点的该层的帧动画。 + + +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 + + + + +// ------ 设置某个画布的属性的相关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窗口,返回游戏。 +此函数将以此调用core.clearUI(),core.unlockControl(),并清空core.status.event里面的内容。 + + +core.clearUI() +重置UI窗口。此函数将清掉所有的UI帧动画和光标,清空UI画布,并将alpha设为1。 + + +core.drawTip(text, id) +在左上角以气泡的形式绘制一段提示。 +text为文字内容,仅支持${}的表达式计算,不支持换行和变色。 +id可选,为同时绘制的图标ID,如果不为null则会同时绘制该图标(仅对32x32的素材有效)。 +也可以使用状态栏的图标ID,例如lv, hp, up, save, settings等。 + + +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是一个工具函数库,里面有各个样板中使用到的工具函数。 + + + + +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中获得一个键的值,会立刻返回结果。 +此函数会自动给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中获得一个键的值,异步返回结果。 +如果对应的键值不存在,则会使用defaultValue。 +如果获得成功,则会将core.decompress后的结果传入successCallback回调函数执行。 +errorCallback可选,如果失败,则会将错误信息传入errorCallback()。 +此函数是异步的,只能通过回调函数来获得读取的结果或错误信息。 + + +core.setGlobal(key, value) +设置一个全局存储,适用于global:xxx。 +录像播放时将忽略此函数,否则直接调用core.setLocalStorage。 + + +core.getGlobal(key, value) +获得一个全局存储,适用于global:xxx,支持录像。 +正常游戏时将使用core.getLocalStorage获得具体的数据,并将结果存放到录像中。 +录像播放时会直接从录像中获得对应的数据。 + + +core.clone(data, filter, recursion) +深拷贝一个对象。有关浅拷贝,深拷贝,基本类型和引用类型等相关知识可参见: +https://zhuanlan.zhihu.com/p/26282765 +filter为过滤函数,如果设置且不为null则需传递一个可接受(name, value)的函数, +并返回true或false,表示该项是否应该被深拷贝。 +recursion表示该filter是否应递归向下传递,如果为true则递归函数也将传该filter。 +例如: +core.clone(core.status.hero, function(name, value) { + return name == 'items' || typeof value == 'number'; +}, false); +这个例子将会深拷贝勇士的属性和道具。 + + +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.matchWildcard(pattern, string) +进行通配符的匹配判定,目前仅支持*(可匹配0或任意个字符)。比如"a*b*c"可以匹配"aa012bc"。 + + +core.encodeBase64(str) / core.decodeBase64(str) +将字符串进行base64加密或解密。 + + +core.convertBase(str, fromBase, toBase) +任意进制转换。此函数可能执行的非常慢,慎用。 + + +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大法。 +但是,此函数会将生成的随机数值存入录像,因此如果调用次数太多则会导致录像文件过大。 +对于需要大量生成随机数,但又想使用真随机支持SL大法的(例如随机生成地图等),可以采用如下方式: + var x = core.rand2(100); for (var i = 0; i < x; i++) core.rand() +即先生成一个真随机数,根据该数来推进伪随机的种子,这样就可以放心调用core.rand()啦。 + + +core.readFile(success, error) +读取一个本地文件内容。success和error分别为读取成功或失败的回调函数。 +iOS平台暂不支持读取文件操作。 + + +core.readFileContent(content) +读取到的文件内容。此函数会被APP等调用,来传递文件的具体内容。 + + +core.download(filename, content) +生成一个文件并下载。filename为文件名,content为具体的文件内容。 +iOS平台暂不支持下载文件操作。 + + +core.copy(data) +将一段内容拷贝到剪切板。 + + +core.myconfirm(hint, yesCallback, noCallback) +弹窗绘制一段提示信息并让用户确认。hint为提示信息。 +yesCallback和noCallback分别为确定和取消的回调函数。 +此函数和core.drawConfirmBox的区别主要在于: + - drawConfirmBox会清掉UI层原有的绘制信息,此函数不会清除。 + - drawConfirmBox可以用键盘进行操作,此函数必须用鼠标点击。 +另外请注意:本函数的选择也不会进录像,一般用于全局的提示。 +如果需要在事件流中调用请使用显示确认框或显示选择项。 + + +core.myprompt(hint, value, callback) +弹窗让用户输入一段内容。hint为提示信息,value为框内的默认填写内容。 +callback为用户点击确认或取消后的回调。 +如果用户点击了确认,则会把框内的内容(可能是空串)传递给callback,否则把null传递给callback。 + + +core.showWithAnimate(obj, speed, callback) / core.hideWithAnimate(obj, speed, callback) +动画淡入或淡出一个对象。 + + +core.consoleOpened() +检测当前的控制台是否处于开启状态。仅在全塔属性中的检查控制台开关开启时有效。 +此函数有可能会存在误伤行为,即没开过控制台仍认为开启过。 + + +core.hashCode(obj) +计算一个对象的哈希值。 + + +core.same(a, b) +判定a和b是否相同,包括类型相同和值相同。 +如果a和b都是数组,则会递归依次比较数组中的值;如果都是对象亦然。 + + +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/B站视频教程.url b/B站视频教程.url new file mode 100644 index 00000000..036a8a64 --- /dev/null +++ b/B站视频教程.url @@ -0,0 +1,5 @@ +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,2 +[InternetShortcut] +IDList= +URL=https://www.bilibili.com/video/av32781473/ \ No newline at end of file diff --git a/HTML5魔塔样板使用指南.url b/HTML5魔塔样板使用说明文档.url similarity index 64% rename from HTML5魔塔样板使用指南.url rename to HTML5魔塔样板使用说明文档.url index 823d8f8d..021c516b 100644 --- a/HTML5魔塔样板使用指南.url +++ b/HTML5魔塔样板使用说明文档.url @@ -2,4 +2,4 @@ Prop3=19,2 [InternetShortcut] IDList= -URL=https://ckcz123.github.io/mota-js/ +URL=https://h5mota.com/games/template/docs/ \ No newline at end of file diff --git a/README.md b/README.md index 055ec27a..7e331545 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! * [List / HTML5魔塔游戏列表](https://h5mota.com/) * [Demo / 样板效果](https://ckcz123.com/games/template/) * [Docs / 使用文档说明](https://ckcz123.github.io/mota-js/) -* [Video / 视频教程](http://www.bilibili.com/video/av17608025/) +* [Video / 视频教程](https://www.bilibili.com/video/av32781473/) -![样板](./docs/img/sample0.png) +![样板](./_docs/img/sample0.png) ## 目录结构 @@ -25,8 +25,8 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! │ ├─ data.js # 记录了一些初始化信息 │ ├─ enemys.js # 记录了怪物的信息,包括特殊属性、伤害计算公式、临界值计算等。 │ ├─ events.js # 处理事件的文件,所有自定义事件都会在此文件中进行处理 -│ ├─ icons.js # 记录了图标信息 -│ ├─ items.js # 道具的使用 +│ ├─ icons.js # 图标信息,会被转发到project下 +│ ├─ items.js # 道具信息,会被转发到project下 │ ├─ loader.js # 动态加载JS代码、图片、音效等 │ ├─ maps.js # 记录了地图信息,和地图绘制等操作 │ ├─ ui.js # UI绘制信息,主要负责绘制各个UI窗口。 @@ -38,27 +38,240 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! │ ├─ /sounds/ # 音效目录 │ ├─ data.js # 全局变量信息 │ ├─ enemys.js # 怪物属性数据 +│ ├─ events.js # 公共事件 │ ├─ functions.js # 可能会被修改的脚本代码 │ ├─ icons.js # 素材和ID的对应关系定义 │ ├─ items.js # 道具的定义,获得道具的效果 -│ └─ maps.js # 地图和数字的对应关系 -├── /常用工具/ # 一些常用工具,可以辅助造塔 -│ ├─ RM动画导出器.exe # 能从RMXP中导出动画,以供H5使用。 http://github.com/ckcz123/animate_export/ -│ ├─ JS代码压缩工具.exe # 能对Javascript代码进行压缩和整合,从而减少IO请求量。 http://github.com/ckcz123/JSCompressor/ -│ ├─ 便捷PS工具.exe # 能只用复制和粘贴来快速对素材进行PS操作。 http://github.com/ckcz123/ps/ -│ ├─ 地图生成器.exe # 能从一张截图识别出来具体的数字数组,方便复刻已有的塔。 http://github.com/ckcz123/map_generator/ -│ └─ 伤害和临界值计算器.exe # 一个能帮助计算怪物的伤害和临界值的小工具。 http://github.com/ckcz123/magic-tower-calculator/ -├── /启动服务(mac版).app/ # 启动服务的mac版本。 +│ ├─ maps.js # 地图和数字的对应关系 +│ └─ plugins.js # 自定义插件 +├── /常用工具/ # 一些常用工具,可以辅助造塔;具体可参见下面的【相关工具】 ├── editor.html # 可视化地图编辑工具 ├── editor-mobile.html # 可视化地图编辑工具(手机版) ├── index.html # 主程序,游戏的入口 ├── main.js # JS程序的入口,将动态对所需JS进行加载 ├── style.css # 游戏所需要用到的样式表 -└── 启动服务.exe # 一个本地的HTTP服务器,也能支撑前端的一些POST请求从而能拓展JS的IO功能。 http://github.com/ckcz123/mota-js-server/ +└── 启动服务.exe # 一个本地的HTTP服务器,也能支撑前端的一些POST请求从而能拓展JS的IO功能。 ``` ## 更新说明 +### 2019.5.2 V2.6.1 + +* [x] 区域优化的录像播放功能,R键使用 +* [x] 强制战斗可以指定怪物坐标,将自动隐藏并执行该点战后事件 +* [x] flag:xxx也支持中文,例如 flag:2楼机关门 +* [x] 增加文件名映射,可以用中文映射到某个图片或bgm文件并使用 +* [x] 勇士宽度可以超过32(例如48x48的勇士行走图) +* [x] 现在允许修改floorId和图块ID了(在表格下方) +* [x] 增加事件:自动存档,返回标题界面;部分事件优化 +* [x] 商店长按空格可以连续加点 +* [x] 增设global:xxx使用全局存储,可被录像支持 +* [x] 支持\b[hero]和\b[null,x,y]来自动调整上下方向 +* [x] 支持\t[yellowKey]等只显示图标而没有标题 +* [x] 编辑器中前景层对于有事件的点半透明显示 +* [x] 存档改成1000页,长按上下页可快速翻页 +* [x] 录像播放初始默认暂停,N键可以单步执行 +* [x] 增设本地API文档,部分API和事件的优化 +* [x] 所有已知的bug修复,大量细节优化 + +### 2019.4.13 V2.6 + +* [x] 拆分整个项目,大幅重构代码,新增大量API +* [x] 重写文档,尤其是脚本和API列表 +* [x] 现在可以对编辑器的表格的结构进行配置 +* [x] 可以收藏和高亮存档 +* [x] 独立出来的插件编写 +* [x] 新增事件:关门、显示确认框、后置循环处理 +* [x] 剧情文本的绘制可以设置居中选项 +* [x] 选项框的绘制可以增加图标 +* [x] 增加公共事件版的全局商店 +* [x] 公共事件现在可以传入参数 +* [x] 重写滑冰事件,现在滑冰在背景层了 +* [x] 将输入框改成自定义实现,避免部分设备不支持 +* [x] 状态栏文字可以自动放缩 +* [x] 显示图片和对话框立绘可以裁剪图片 +* [x] 修复所有已知bug,大量细节优化 + +### 2019.2.19 V2.5.5 + +* [x] 现在编辑器修改地图后可以直接读档生效,无需再重置地图或回放录像 +* [x] 存档方式优化,大幅降低单个存档的占用空间 +* [x] 脚本编辑器增加代码格式化的选项 +* [x] 事件和脚本编辑器中Ctrl+S可以进行保存 +* [x] 显示选择项提供颜色控制 +* [x] 事件的移动勇士增加前进和后退两个操作 +* [x] 事件编辑器的下拉框增加滚动条 +* [x] 通关后将询问是否进行评分 +* [x] 录像播放失败后可以回退到上个节点 +* [x] 修复已知的所有Bug,大量细节优化 + +### 2019.2.4 V2.5.4 + +* [x] 发布15x15的版本 +* [x] 独立出来的公共事件 +* [x] 支持多重装备(一个装备可以装到多个孔上) +* [x] 工具栏按钮增添至8个,快捷商店和虚拟键盘同时显示 +* [x] 点击状态栏的金币图标也可以打开快捷商店 +* [x] 等待事件提供flag:px和flag:py在0~415之间 +* [x] 事件:呼出存读档界面;呼出怪物手册 +* [x] 事件:使用道具,暂停背景音乐,暂停所有音效 +* [x] type:trigger可以触发系统事件 +* [x] 独立开关 +* [x] 贴图也可以支持帧动画了 +* [x] 图块内置颜色选择器 +* [x] 标题界面增加音乐按钮 +* [x] 等待事件可被Ctrl(长按)跳过 +* [x] 部分Bug修复,大量细节优化,性能进一步得到提升 + +### 2018.12.22 V2.5.3 + +* [x] 标题界面事件化;现在可以用事件流来处理标题界面了 +* [x] 动态canvas管理;无限图层,可以任意创建图层并使用 +* [x] 状态栏canvas化,可以自行对状态栏进行绘制 +* [x] 手机端新增1-7按钮,可点工具栏进行切换 +* [x] 事件编辑器可以查看最近使用图块和搜索图块 +* [x] 事件编辑器中增加增加颜色选择器 +* [x] 对话框里`\f`可以自带立绘效果 +* [x] 图片相关事件全部修改为动态canvas实现 +* [x] 新增事件:滚动字幕 +* [x] 新增事件:等待所有异步事件执行完毕 +* [x] 新增事件:画面闪烁 +* [x] BGM缓存管理;新增事件:预加载BGM +* [x] 新增天气:雾 +* [x] 每次到达楼层执行的事件`eachArrive` +* [x] 可以控制某些图块无全局动画效果 +* [x] 背景/前景层的素材可以全局动画/动态Autotile效果 +* [x] 可以为每个装备单独设置是否按比例增加 +* [x] 地图编辑器中选中点高亮;双击选中素材;WASD平移大地图 +* [x] 修复所有Bug,部分代码重构,大量细节优化 + +### 2018.11.30 V2.5.2 + +* [x] 怪物和NPC的行走图和朝向问题 +* [x] 可以引入WindowSkin作为对话框的背景素材 +* [x] 允许使用\t[标题,1.png]来绘制大头像图 +* [x] 对话框的宽度可以根据文本长度自动调整 +* [x] \r[red]可以动态调整剧情文本的颜色 +* [x] 升级事件改用事件编辑器完成 +* [x] 每层楼都增添该层的并行事件处理 +* [x] 新增快捷键:N返回标题;P游戏主页;O打开工程 +* [x] 新增事件:设置全局属性或全局数值 +* [x] 新增事件:隐藏/显示状态栏 +* [x] 道具可以设置是否在回放时绘制道具栏或直接使用 +* [x] 可以同时异步移动/跳跃勇士和多个NPC +* [x] 可以同时异步移动两张或以上的图片了 +* [x] 追加素材一次可以追加多个 +* [x] 菜单栏中新增虚拟键盘的弹出 +* [x] 修复所有已知Bug;部分细节优化 + +### 2018.11.21 V2.5.1 + +* [x] 新增事件type:insert,可以插入另一个地点的事件执行(公共事件) +* [x] 可以使用\r来控制剧情文本部分文字的颜色 +* [x] 新增事件type:switch,多重分歧 +* [x] 绘制前景/背景层时淡化其他图层 +* [x] 追加素材的自动调整(如白底、不规范的素材) +* [x] 浏览地图时:左上角/V开启显伤;右上角/Z查看当前层大地图 +* [x] 允许在受到领域夹击等伤害后禁用快捷商店 +* [x] 升级的扣除模式,即不显示经验值,只显示升级的所需剩余值 +* [x] 装备增加可装备条件判定 +* [x] 选项界面可以使用1-9快速选择 +* [x] 未开启状态的快捷商店用灰色显示 +* [x] 修复不能在背景/前景层绘图的Bug +* [x] 手机端的地图编辑器也能有报错信息了 +* [x] 部分其他细节优化 + +### 2018.10.31 V2.5 + +* [x] 添加绘图模式支持;可以用户手动绘图和保存 +* [x] 内置主动技能:二倍斩的支持,可以仿照制作其他主动技能 +* [x] 将按键处理移动到脚本编辑中 +* [x] Alt+0\~9保存和读取当前套装 +* [x] 图块属性的cannotOut和cannotIn控制可通行方向(来造成悬崖效果) +* [x] 支持动态Autotile自动元件(仅在事件层有效) +* [x] 允许快捷商店使用共用的times +* [x] 未启用的快捷商店可以隐藏或预览 +* [x] 开始剧情startText可以执行任意事件 +* [x] 对话窗口可以任意调节位置(上中下、距离顶部/底部的像素值) +* [x] 楼层转换界面可以设置背景图片文字颜色等 +* [x] 数据统计进行分段描写,剑盾显示数值 +* [x] 现在可以在事件编辑器中注释内容了 +* [x] 存读档界面显示该存档的属性 +* [x] F7键可以开启debug模式 +* [x] R键可以从本地选取录像文件从头播放 +* [x] 吸血属性的显伤增加^;仇恨怪显示仇恨伤害 +* [x] 4键默认使用破冰稿或冰冻徽章或地震卷轴或上下楼器(依次判断是否存在) +* [x] 血瓶的道具化选项;黄宝石增加加点选项 +* [x] 破炸飞增加默认音效 +* [x] 修复单击瞬移的拖动打怪问题 +* [x] 其他细节优化 + +### 2018.10.27 V2.4.4 + +* [x] tilesets可以设置图块属性(如可通行状态) +* [x] 追加素材时可以更改图片色调 +* [x] 防作弊手段的进一步加强:打开控制台则禁止上传成绩 +* [x] 图块属性增加“是否可被破震”的选项 +* [x] 模仿怪物内置模仿临界计算器 +* [x] 部分其他细节优化 + +### 2018.10.14 V2.4.3 + +* [x] 并行事件处理 +* [x] 事件:设置楼层属性 +* [x] 增加光环属性,还可以制作区域光环效果 +* [x] 将部分代码移动到脚本编辑中 +* [x] 事件改变天气或画面色调,读档后仍有效 +* [x] Autotile自动元件的新增和注册 +* [x] 状态栏可以显示角色名字 +* [x] 浏览地图可以显伤 +* [x] 图片淡入淡出和移动图片可以将其保留 +* [x] 双击道具栏图标直接进入装备栏 +* [x] 可以设置剧情文本的字体大小 +* [x] 录像播放可以最高24倍速 +* [x] 1-6键快速设置录像播放速度;滚轮加减速 +* [x] 修复大地图的夹击Bug +* [x] 部分其他细节优化 + +### 2018.9.28 V2.4.2 + +* [x] 允许导入tilesets直接使用,无需PS和注册 +* [x] tilesets的素材允许以矩形方式整体绘制 +* [x] Alt+0\~9保存素材,Ctrl+0\~9快速选中 +* [x] 增加了透明块的支持 +* [x] 装备允许按照百分比增加属性 +* [x] 多动画的同时播放 +* [x] 修复了打开存读档页面时闪屏的问题 +* [x] 修复了cannotMove仍然能轻按和瞬移的问题 +* [x] 所有已知Bug修复,部分代码重构和细节优化 + +### 2018.9.18 V2.4.1 + +* [x] 增加背景层和前景层的图块绘制,多层图块可叠加 +* [x] 背景层/前景层图块的显示、隐藏、修改等事件 +* [x] 专门的装备页面(Q键开启);装备系统大改造 +* [x] 灯光和漆黑层效果,通过插件函数方式给出 +* [x] 将状态栏更新和阻激夹域的计算移动到脚本编辑中 +* [x] 增加控制免疫阻激夹域的flag:no_zone等 +* [x] 打字机效果时点击显示全部文字 +* [x] 修复更改画面色调的Bug +* [x] 修复更改剧情文本属性后读档恢复原样的问题 +* [x] 部分细节优化 + +### 2018.8.28 V2.4 + +* [x] 大地图的支持 +* [x] 突破了5M的存档空间大小限制 +* [x] 事件:隐藏/显示贴图 +* [x] 事件:接收用户文本输入 +* [x] 同点多事件的颜色块绘制 +* [x] 录像播放时可以按PgUp/PgDn浏览地图 +* [x] 录像播放时对于瞬间移动绘制箭头 +* [x] 增加激光属性 +* [x] 可以在读档时E键直接指定编号 +* [x] 破炸飞可以在状态栏显示个数 +* [x] 部分细节优化,所有已知Bug修复 + ### 2018.7.21 V2.3.3 * [x] 将怪物特殊属性定义和伤害计算函数移动到脚本编辑中 @@ -279,6 +492,16 @@ HTML5 canvas制作的魔塔样板,支持全平台游戏! * [x] 发布初版HTML5魔塔样板 +## 相关工具 + +- [启动服务](http://github.com/ckcz123/mota-js-server/): 一个本地的HTTP服务器,也能支撑前端的一些POST请求从而能拓展JS的IO功能。 +- [RM动画导出器](http://github.com/ckcz123/animate_export/):能从RMXP中导出动画,以供H5使用。 +- [JS代码压缩工具](http://github.com/ckcz123/JSCompressor/):能对Javascript代码进行压缩和整合,从而减少IO请求量。 +- [便捷PS工具](http://github.com/ckcz123/ps/):能只用复制和粘贴来快速对素材进行PS操作。 +- [地图生成器](http://github.com/ckcz123/map_generator/):能从一张截图识别出来具体的数字数组,方便复刻已有的塔。 +- [怪物数据导出器](http://github.com/ckcz123/enemy_export/):能从RMXP中导出怪物数据,以供H5使用。 +- [伤害和临界值计算器](http://github.com/ckcz123/magic-tower-calculator/):一个能帮助计算怪物的伤害和临界值的小工具。 + ## 联系我们 本样板主要由 [`ckcz123`](https://github.com/ckcz123) (百度ID `艾之葵`)编写。 @@ -302,6 +525,8 @@ HTML5魔塔交流群群号: `539113091` [@wadxm](https://github.com/wadxm) iOS平台的APP(因为苹果政策无法上架)和启动服务mac版的开发者。我们现在能在mac上制作魔塔得归功于他。 -[@fux4](https://github.com/fux4) 打通了RM和H5之间的障壁(从而使RM动画导出器和怪物数据导出器成为可能),同时也是部分新功能(如跳跃、跟随、画面震动)等的编写者。 +[@fux4](https://github.com/fux4) 打通了RM和H5之间的障壁(从而使RM动画导出器和怪物数据导出器成为可能),同时也是大地图和部分新功能(如跳跃、跟随、画面震动)等的编写者。 + +[@tocque](https://github.com/tocque) 装备栏、动态创建图层等的编写者。 以及[百度贴吧魔塔吧](https://tieba.baidu.com/f?kw=%E9%AD%94%E5%A1%94)和H5魔塔交流群`539113091`内的诸位魔塔爱好者们对本样板的大力支持! diff --git a/V2.0版本简易造塔流程(For新人).txt b/V2.0版本简易造塔流程(For新人).txt deleted file mode 100644 index 042e6517..00000000 --- a/V2.0版本简易造塔流程(For新人).txt +++ /dev/null @@ -1,78 +0,0 @@ -这只是一个最简单的造塔流程,更为详细的还是请参见视频或教程文档。 - -1. 打开启动服务,地图编辑器。 - -2. 切换到MT0层,然后开始绘制地图。可以在上面放置墙、门、道具或怪物等。 -如果提示“该素材未被定义”,请参见第7点。 -如果要从RMXP中复刻已有的塔的地图,请参见第11点。 - -3. 切换到楼层属性,一项一项仔细进行楼层属性的编辑,如楼层名,firstArrive等等。 -将鼠标移动到表格的中间可以查看详细信息。 -firstArrive为初次到达楼层的事件,可以双击进行事件的编辑。有关事件请详见文档。 - -3. 输入怪物数据:点击右边怪物,然后左边写怪物的攻防血等数值。 -将鼠标移动到表格的中间可以查看详细信息。 -如果是特殊属性怪物直接写特殊编号,多个特殊属性可以用 [1,2] 来表示。 - -4. 在中间的下拉框切换全塔属性,一项一项仔细进行根据需求编辑。 -请注意"name"一项必须修改为“字母、数字、下划线组成的字符串”,否则会出现串档问题。 -startText(初始剧情)、shops(全局商店)、levelUp(升级)等都是可以双击方框进行编辑的。 -有关全局商店和升级等信息详见 教程文档 - 事件 - -5. 给地图添加事件;可以给地图上的NPC增加事件,或者战斗/开门事件等。 -点击地图上的某个点,在左边进行编辑。 -event -- 该点的自定义事件(例如NPC,商店,等等) -changeFloor -- 该点的楼层传送事件(楼梯/传送门) -afterBattle -- 该点战斗后触发的战后事件 -afterOpenDoor -- 该点开门后触发的事件 -afterGetItem -- 该点获得道具后触发的事件 -等等。 -有关事件详细内容请参见 文档 - 事件。 - -6. 新建楼层:切换到地图编辑,然后在框内输入新楼层的floorId,点击新建地图即可保存。 -创建的floorId必须是字母、数字和下划线组成,且不能以数字开头。 -不能为空白,不能和任何已有楼层的floorId重复。 -保存成功后刷新页面。 - -删除楼层同理,不过请注意删除的会是当前的楼层而不是框中的内容。 - -7. 关于素材未被定义的问题:如果点击某个怪物或NPC提示该素材未被定义,请在左边进行素材的注册。 -输入该素材的唯一ID(不能和其他素材的ID重复),和素材的唯一数字(1000以内,不能和其他的数字重复), -保存并刷新,即注册成功。 - -8. 添加新素材:请打开启动服务的便捷PS工具,然后左边读取你要添加到的图片(比如怪物是enemys.png,道具是items.png), -右边读取你要导入的怪物素材,通过复制粘贴进行导入,再保存,刷新页面后按照第7点来进行素材的注册。 - -9. 道具的自定义效果:如果需要自定义道具效果,请仿照其他的几个道具来写,更多信息详见文档。 - -10. 报错处理:有时候刷新后可能页面变成空白,即无法正确加载。 -出现这种问题的原因往往是手动错误编辑了文件、新建楼层使用了不合法的floorId(比如中文或数字)、楼层floorId定义重复,等等。 - -出现这种问题,(在Chrome浏览器中)请按Ctrl+Shift+I打开控制台,找到Console查看报错。 -一般都会具体到哪个楼层文件出错。 - -解决方式:哪个楼层文件出错,请使用VSCode等打开project目录下的data.js文件,并将出错的那个楼层定义删除。 - -举例,比如我在新建地图中写了 “水潭边” 这样一个楼层名(中文),然后新建并保存,刷新会出错。 -此时,打开控制台(Ctrl+Shift+I的Console),并查看报错,发现是该楼层错误。 -那么打开data.js文件,并将 "floorIds": [..., "水潭边"] 这里对它的楼层定义删除,再刷新即可。 - -11. 从RMXP中导入已有的塔的地图。 -如果你想复刻老塔,则需使用启动服务的地图生成器。 -请确保老地图中的所有使用素材(地面/墙壁/门/道具/怪物等等)都已经被注册过(参见第7点)。 - -打开windows自带的截图工具,并对地图进行截图。 -截图时请注意:必须截刚好13x13范围大小的地图,尽量对其边缘进行截取。 -如果不是13x13的范围大小,可能会导致地图生成器无响应。 -截图完毕后,请复制到剪切板,然后在地图生成器中点加载图片。等1-2秒,就可以看到截图被识别。 -点“复制地图”,然后在地图编辑器中切换到“地图编辑”,并粘贴到左边的框内,即可。 -如果存在个别识别问题,可以对这个别素材再进行重新绘制。 - -如果出现大量识别问题,比如基本全是错的,则代表你的截图方式有问题。 -由于地图生成器的识别以左上角的图块为基准来找寻截图偏移量,请确保左上角一定要是一个能被很好识别的图块。 -建议:在RM的图层第三层,左上角放一个岩浆,再进行截图(保证截图的13x13的左上角是岩浆,从而可以确保定位), -识别后再复制到地图编辑器中进行绘制。 - --------------------------------- - -HTML5魔塔交流群:539113091,如果有问题请加群提问。 diff --git a/docs/V2.0.md b/_docs/V2.0.md similarity index 98% rename from docs/V2.0.md rename to _docs/V2.0.md index 558eb1d1..0b8bd92a 100644 --- a/docs/V2.0.md +++ b/_docs/V2.0.md @@ -1,6 +1,6 @@ # V2.0版本介绍 -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * 目前样板已经更新到V2.0版本以上,本章将对V2.0的一些内容进行介绍。 diff --git a/docs/api.md b/_docs/_api.md similarity index 62% rename from docs/api.md rename to _docs/_api.md index f28da96b..64362e3b 100644 --- a/docs/api.md +++ b/_docs/_api.md @@ -1,6 +1,6 @@ # 附录: API列表 -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6**,上次更新时间:* {docsify-updated} * **这里只列出所有可能会被造塔者用到的常用API,更多的有关内容请在代码内进行查询。** @@ -72,8 +72,17 @@ core.setItem('pickaxe', 10) 将破墙镐个数设置为10个。这里可以写任何道具的ID。 +core.addItem('pickaxe', 2) +将破墙镐的个数增加2个,无任何特效。这里可以写任何道具的ID。 + + core.getItem('pickaxe', 4) -另勇士获得四个破墙镐。这里可以写任何道具的ID。 +令勇士获得4个破墙镐。这里可以写任何道具的ID。 +和addItem相比,使用getItem会播放获得道具的音效,也会在左上角绘制获得提示。 + + +core.removeItem('pickaxe', 3) +删除3个破墙镐。第二项可忽略,默认值为1。 core.itemCount('pickaxe') @@ -84,6 +93,15 @@ 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变量名。 @@ -97,11 +115,18 @@ 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) [异步] @@ -113,21 +138,18 @@ core.changeFloor('MT5', null, {'x': 3, 'y': 6}, 0) 无动画切换到MT5层的(3 core.resetMap() -重置当前楼层地图。 -当我们修改某一层地图后,进游戏读档,会发现修改的内容并没有被更新上去。 -这是因为,H5的存档是会存下来每一个楼层的地图的,读档会从档里面获得地图信息。 -此时,如果我们在某一层地图执行 core.resetMap() ,则可以立刻从剧本中读取并重置当前楼层地图。 -已经被修改过的内容也会相应出现。 - +重置当前楼层地图和楼层属性。 +此函数参数有三种形式: + - 不加任何参数,表示重置当前层:core.resetMap() + - 加上一个floorId,表示重置某一层:core.resetMap("MT1") + - 使用一个数组,表示重置若干层:core.resetMap(["MT1", "MT2", "MT3"]) +--------------------------- +** 说明:从V2.5.5开始存档方式发生了改变,在编辑器修改了地图后现在将直接生效,无需再重置地图。 R 录像回放的快捷键;这不是一个控制台命令,但是也把它放在这里供使用。 录像回放在修改地图或新增数据后会很有用。 - -localStorage -获得所有的存档数据。可以用 core.getLocalStorage('save1') 来具体获得某个存档。 - ``` !> 一些相对高级的命令,针对有一定脚本经验的人 @@ -146,13 +168,20 @@ 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) @@ -160,10 +189,8 @@ core.trigger(x, y) [异步] 触发某个地点的事件。 -core.clearMap(mapName) -清空某个画布图层。 -mapName可为'bg', 'event', 'fg', 'event2', 'hero', 'animate', 'weather', 'ui', 'data', 'all'之一。 -如果mapName为'all',则为清空所有画布;否则只清空对应的画布。 +core.isReplaying() +当前是否正在录像播放中 core.drawBlock(block) @@ -211,30 +238,36 @@ 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, callback) -尝试使用某个道具。itemId为道具ID,callback为成功或失败后的回调。 +core.useItem(itemId, noRoute, callback) +尝试使用某个道具。itemId为道具ID,noRoute如果为真则该道具的使用不计入录像。 +callback为成功或失败后的回调。 core.canUseItem(itemId) 返回当前能否使用某个道具。 -core.addItem(itemId, number) -将某个道具增加number个。 +core.loadEquip(itemId, callback) +装备上某个装备。itemId为装备的ID,callback为成功或失败后的回调。 -core.removeItem(itemId) -将某个道具个数-1;如果道具个数归0则从道具列表删除。 +core.unloadEquip(equipType, callback) +卸下某个部位的装备。equipType为装备类型,从0开始;callback为成功或失败后的回调。 core.getNextItem() @@ -258,7 +291,7 @@ core.replaceText(text) 将一段文字中的${}进行计算并替换。 -core.calValue(value) +core.calValue(value, prefix, need, times) 计算表达式的实际值。这个函数可以传入status:atk等这样的参数。 @@ -266,6 +299,16 @@ 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) 深拷贝某个对象。 @@ -299,6 +342,21 @@ 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主要用来进行怪物相关的内容,比如怪物的特殊属性,伤害和临界计算等。 @@ -317,34 +375,36 @@ core.enemys.getSpecialHint(enemy, special) 获得怪物某个(或全部)特殊属性的文字说明。 -core.enemys.canBattle(enemyId) +core.enemys.canBattle(enemyId, x, y, floorId) 返回当前能否战胜某个怪物。 +后面三个参数是怪物坐标和楼层。 -core.enemys.getDamage(enemyId) +core.enemys.getDamage(enemyId, x, y, floorId) 返回当前对某个怪物的战斗伤害。如果无法战斗,返回null。 +后面三个参数是怪物坐标和楼层。 core.enemys.getExtraDamage(enemyId) 返回某个怪物会对勇士造成的额外伤害(不可被魔防抵消),例如仇恨、固伤等等。 -core.enemys.nextCriticals(enemyId, number) +core.enemys.nextCriticals(enemyId, number, x, y, floorId) 返回一个列表,为接下来number(可忽略,默认为1)个该怪物的临界值和临界减伤。 列表每一项类似 [x,y] 表示临界值为x,且临界减伤为y。 如果无临界值,则返回空列表。 -core.enemys.getDefDamage(enemyId, k) +core.enemys.getDefDamage(enemyId, k, x, y, floorId) 获得k(可忽略,默认为1)防减伤值。 -core.enemys.getDamageInfo(enemy, hero_hp, hero_atk, hero_def, hero_mdef) +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) +core.enemys.calDamage(enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) 获得在某个勇士属性下怪物伤害;实际返回的是上面getDamageInfo中伤害的数值。 @@ -371,6 +431,10 @@ core.events.doAction() 执行下一个事件。此函数中将对所有自定义事件类型分别处理。 +core.events.getCommonEvent(name) +根据名称获得一个公共事件;如果不存在对应的公共事件则返回null。 + + core.events.openShop(shopId, needVisited) [异步] 打开一个全局商店。needVisited表示是否需要该商店已被打开过。 @@ -391,6 +455,10 @@ core.events.setHeroIcon(name) items.js将处理和道具相关的内容,比如道具的使用,获取和删除等等。 +core.items.compareEquipment(equipId1, equipId2) +比较两个装备的属性变化值 + + ========== core.loader.XXX 和游戏加载相关的函数 ========== loader.js将主要用来进行资源的加载,比如加载音乐、图片、动画等等。 @@ -399,6 +467,10 @@ loader.js将主要用来进行资源的加载,比如加载音乐、图片、 maps.js主要用来进行地图相关的的操作。包括绘制地图,获取地图上的点等等。 +core.maps.getNumberById(id) +根据ID来获得对应的数字。如果该ID不存在对应的数字则返回0。 + + core.maps.canMoveHero(x,y,direction,floorId) 判断能否前往某个方向。x,y为坐标,可忽略为当前点;direction为方向,可忽略为当前方向。 floorId为楼层ID,可忽略为当前楼层。 @@ -417,10 +489,113 @@ 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为要绘制的所有图块。 @@ -440,6 +615,10 @@ core.utils.cropImage(image, size) 纵向对图片进行切分(裁剪)。 +core.utils.push(a,b) +向某个数组后插入另一个数组或元素 + + core.utils.unshift(a, b) 向某个数组前插入另一个数组或元素 @@ -452,14 +631,31 @@ core.utils.decodeBase64(str) Base64解密字符串 -core.utils.formatBigNumber(x) +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)来压缩当前路线。 diff --git a/docs/_sidebar.md b/_docs/_sidebar.md similarity index 82% rename from docs/_sidebar.md rename to _docs/_sidebar.md index a7960592..914ee883 100644 --- a/docs/_sidebar.md +++ b/_docs/_sidebar.md @@ -3,5 +3,5 @@ - [元件说明](element) - [事件](event) - [个性化](personalization) -- [V2.0版本介绍](V2.0) +- [脚本](script) - [附录:API列表](api) diff --git a/_docs/api.md b/_docs/api.md new file mode 100644 index 00000000..c1c65a3d --- /dev/null +++ b/_docs/api.md @@ -0,0 +1,2111 @@ +# 附录:API列表 + +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * + +这里将列出所有被转发到core的API,没有被转发的函数此处不会列出,请自行在代码中查看。 + +本附录量较大,如有什么需求请自行Ctrl+F进行搜索。 + +如有任何疑问,请联系小艾寻求帮助。 + +## core.js + +core.js中只有很少的几个函数,主要是游戏开始前的初始化等。 + +但是,core中定义了很多游戏运行时的状态,这些状态很多都会被使用到。 + +``` text +core.__SIZE__, core.__PIXELS__ +游戏窗口大小;对于13x13的游戏而言这两个值分别是13和416,15x15来说分别是15和480。 + + +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.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.status.maps不同的是,后者在每次重新开始和读档时都会重置,也允许被修改(会存入存档)。 +而core.floors全程唯一,不允许被修改。 + + +core.statusBar +状态栏信息,例如状态栏图片,图标,以及各个内容的DOM定义等。 +core.statusBar.images (所有的系统图标,和icons.png对应) +core.statusBar.icons (状态栏中绘制的图标内容) + + +core.values +所有的全局数值信息,和全塔属性中的values一致。 +此项允许被直接修改,会存入存档。 + + +core.flags +所有的全塔开关,和全塔属性中的flags一致。 +此项不允许被直接修改,如有需要请使用“设置系统开关”事件,或者调用core.setGlobalFlag这个API。 + + +core.plugin +定义的插件函数。 + + +core.doFunc(func, _this) +执行一个函数,func为函数体或者插件中的函数名,_this为使用的this。 +如果func为一个字符串,则视为插件中的函数名,同时_this将被设置成core.plugin。 +此函数剩余参数将作为参数被传入func。 +``` + +## actions.js + +actions.js主要是处理一些和用户交互相关的内容。 + +```text +core.registerAction(action, name, func, priority) +注册一个用户交互行为。 +action:要注册的交互类型,如 ondown, onclick, keyDown 等等。 +name:你的自定义名称,可被注销使用;同名重复注册将后者覆盖前者。 +func:执行函数;可以是一个具体的函数体,或者是一个插件中的函数名。 +priority:优先级;优先级高的被注册项将会被执行。此项可不填,默认为0。 +返回:如果func返回true,则不会再继续执行其他的交互函数;否则会继续执行其他的交互函数。 + + +core.unregisterAction(action, name) +注销一个用户交互行为。 + + +core.doRegisteredAction(action) +执行一个用户交互行为。 +此函数将在该交互行为所注册的所有函数中,按照优先级从高到底依次执行。 +此函数剩余的参数将会作为参数传入该执行函数中。 +当某个执行函数返回true时将终止这一过程。 + + +core.onkeyDown(e) +当按下某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyDown"的交互函数。 + + +core.onkeyUp(e) +当放开某个键时的操作,e为KeyboardEvent。 +请勿直接覆盖或调用此函数,如有需要请注册一个"onkeyUp"的交互函数。 + + +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(n) +获得勇士面对的第n个位置的横纵坐标。n可不填,默认为1。 + + +core.nearHero(x, y, n) +判定某个点是否和勇士的距离不大于n。n可不填,默认为1。 + + +core.gatherFollowers() +聚集所有的跟随者到勇士的位置。 + + +core.updateFollowers() +更新跟随者们的坐标。 + + +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.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(enemyId, floorId) +检查某个楼层是否还有剩余的(指定)怪物。 +floorId为楼层ID,可忽略表示当前楼层。也可以传数组如["MT0","MT1"]同时检测多个楼层。 +enemyId如果不填或null则检查是否剩余任何怪物,否则只检查是否剩余指定的某类怪物。 +``` + +## 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为目标楼层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) +判定某个点是否有(指定的)地形存在。 +如果id为null,则只要存在terrains即为真,否则还会判定对应点的ID。 + + +core.stairExists(x, y, floorId) +判定某个点是否存在楼梯。 + + +core.nearStair() +判定当前勇士是否在楼梯上或旁边(距离不超过1)。 + + +core.enemyExists(x, y, id, floorId) +判定某个点是否有(指定的)怪物存在。 +如果id为null,则只要存在怪物即为真,否则还会判定对应点的怪物ID。 +请注意,如果需要判定某个楼层是否存在怪物请使用core.hasEnemyLeft()函数。 + + +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。如果该点不存在图块则返回null。 + + +core.getBlockCls(x, y, floorId, showDisable) +获得某个点的图块类型。如果该点不存在图块则返回null。 + + +core.getBlockInfo(block) +根据某个的图块信息获得其详细的素材信息。 +如果参数block为字符串,则视为图块ID;如果参数为数字,则视为图块的数字。 +此函数将返回一个非常详尽的素材信息,目前包括如下几项: +number:素材数字;id:素材id;cls:素材类型;image:素材所在的素材图片;animate:素材的帧数。 +posX, posY:素材在该素材图片上的位置;height:素材的高度;faceIds:NPC朝向记录。 + + +core.searchBlock(id, floorId, showDisable) +搜索一个图块出现过的所有位置。id为图块ID,也可以传入图块的数字。 +id支持通配符搜索,比如"*Door"可以搜索所有的门,"unknownEvent*"可以所有所有的unknownEvent。 +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表示当前楼层。 +此函数不会实际将该块从地图中进行删除,而是将该点设置为禁用,以供以后可能的启用事件。 + + +core.removeBlock(x, y, floorId) +将从启用变成禁用状态,并尽可能将其从地图上删除。 +和hideBlock相比,如果该点不存在自定义事件(比如门或普通的怪物),则将直接从地图中删除。 +如果存在自定义事件,则简单的禁用它,以供以后可能的启用事件。 + + +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为要改变到的数字,也可以传入图块id(将调用core.getNumberById()来获得数字)。 +x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 + + +core.replaceBlock(fromNumber, toNumber, floorId) +将某个或某些楼层中的所有某个图块替换成另一个图块 +fromNumber和toNumber为要被替换和替换到的数字。 +floorId可为某个楼层ID,或者一个楼层数组;如果不填只视为当前楼层。 +值得注意的是,使用此函数转了的点上的自定义事件可能无法被执行。 +如有需要,再对那些存在事件的点执行core.setBlock()即可 + + +core.setBgFgBlock(name, number, x, y, floorId) +设置前景/背景层的某个图块。name只能为'bg'或'fg'表示前景或背景层。 +number为要设置到的图块数字,x,y和floorId为目标点坐标和楼层,可忽略为当前点和当前楼层。 + + +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.jumpBlock(sx, sy, ex, ey, time, keep, callback) +跳跃一个图块,sx和sy为图块的坐标,ex和ey为目标坐标。time为整个跳跃过程中的全程用时,不填默认500。 +如果keep为真,则在移动完毕后将自动调用一个setBlock事件,改变目标点的图块(即不消失), +否则会按照time时间来淡出消失。callback会执行完毕后的回调。 + + +core.animateBlock(loc, type, time, callback) +淡入/淡出一个或多个图块。 +loc为一个图块坐标,或者一个二维数组表示一系列图块坐标(将同时显示和隐藏)。 +type只能为'show'或'hide'表示是淡入但是淡出。time为动画时间,callback为执行完毕的回调。 + +// ------ 全局动画控制,动画的绘制 ------ // + +core.addGlobalAnimate(block) +添加一个全局帧动画。 + + +core.removeGlobalAnimate(x, y, name) +删除一个或全部的全局帧动画。name可为'bg',null或'fg'表示某个图层。 +x和y如果为null,则会删除全部的全局帧动画,否则只会删除该点的该层的帧动画。 + + +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窗口,返回游戏。 +此函数将以此调用core.clearUI(),core.unlockControl(),并清空core.status.event里面的内容。 + + +core.clearUI() +重置UI窗口。此函数将清掉所有的UI帧动画和光标,清空UI画布,并将alpha设为1。 + + +core.drawTip(text, id) +在左上角以气泡的形式绘制一段提示。 +text为文字内容,仅支持${}的表达式计算,不支持换行和变色。 +id可选,为同时绘制的图标ID,如果不为null则会同时绘制该图标(仅对32x32的素材有效)。 +也可以使用状态栏的图标ID,例如lv, hp, up, save, settings等。 + + +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中获得一个键的值,会立刻返回结果。 +此函数会自动给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中获得一个键的值,异步返回结果。 +如果对应的键值不存在,则会使用defaultValue。 +如果获得成功,则会将core.decompress后的结果传入successCallback回调函数执行。 +errorCallback可选,如果失败,则会将错误信息传入errorCallback()。 +此函数是异步的,只能通过回调函数来获得读取的结果或错误信息。 + + +core.setGlobal(key, value) +设置一个全局存储,适用于global:xxx。 +录像播放时将忽略此函数,否则直接调用core.setLocalStorage。 + + +core.getGlobal(key, value) +获得一个全局存储,适用于global:xxx,支持录像。 +正常游戏时将使用core.getLocalStorage获得具体的数据,并将结果存放到录像中。 +录像播放时会直接从录像中获得对应的数据。 + + +core.clone(data, filter, recursion) +深拷贝一个对象。有关浅拷贝,深拷贝,基本类型和引用类型等相关知识可参见: +https://zhuanlan.zhihu.com/p/26282765 +filter为过滤函数,如果设置且不为null则需传递一个可接受(name, value)的函数, +并返回true或false,表示该项是否应该被深拷贝。 +recursion表示该filter是否应递归向下传递,如果为true则递归函数也将传该filter。 +例如: +core.clone(core.status.hero, function(name, value) { + return name == 'items' || typeof value == 'number'; +}, false); +这个例子将会深拷贝勇士的属性和道具。 + + +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.matchWildcard(pattern, string) +进行通配符的匹配判定,目前仅支持*(可匹配0或任意个字符)。比如"a*b*c"可以匹配"aa012bc"。 + + +core.encodeBase64(str) / core.decodeBase64(str) +将字符串进行base64加密或解密。 + + +core.convertBase(str, fromBase, toBase) +任意进制转换。此函数可能执行的非常慢,慎用。 + + +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大法。 +但是,此函数会将生成的随机数值存入录像,因此如果调用次数太多则会导致录像文件过大。 +对于需要大量生成随机数,但又想使用真随机支持SL大法的(例如随机生成地图等),可以采用如下方式: + var x = core.rand2(100); for (var i = 0; i < x; i++) core.rand() +即先生成一个真随机数,根据该数来推进伪随机的种子,这样就可以放心调用core.rand()啦。 + + +core.readFile(success, error) +读取一个本地文件内容。success和error分别为读取成功或失败的回调函数。 +iOS平台暂不支持读取文件操作。 + + +core.readFileContent(content) +读取到的文件内容。此函数会被APP等调用,来传递文件的具体内容。 + + +core.download(filename, content) +生成一个文件并下载。filename为文件名,content为具体的文件内容。 +iOS平台暂不支持下载文件操作。 + + +core.copy(data) +将一段内容拷贝到剪切板。 + + +core.myconfirm(hint, yesCallback, noCallback) +弹窗绘制一段提示信息并让用户确认。hint为提示信息。 +yesCallback和noCallback分别为确定和取消的回调函数。 +此函数和core.drawConfirmBox的区别主要在于: + - drawConfirmBox会清掉UI层原有的绘制信息,此函数不会清除。 + - drawConfirmBox可以用键盘进行操作,此函数必须用鼠标点击。 +另外请注意:本函数的选择也不会进录像,一般用于全局的提示。 +如果需要在事件流中调用请使用显示确认框或显示选择项。 + + +core.myprompt(hint, value, callback) +弹窗让用户输入一段内容。hint为提示信息,value为框内的默认填写内容。 +callback为用户点击确认或取消后的回调。 +如果用户点击了确认,则会把框内的内容(可能是空串)传递给callback,否则把null传递给callback。 + + +core.showWithAnimate(obj, speed, callback) / core.hideWithAnimate(obj, speed, callback) +动画淡入或淡出一个对象。 + + +core.consoleOpened() +检测当前的控制台是否处于开启状态。仅在全塔属性中的检查控制台开关开启时有效。 +此函数有可能会存在误伤行为,即没开过控制台仍认为开启过。 + + +core.hashCode(obj) +计算一个对象的哈希值。 + + +core.same(a, b) +判定a和b是否相同,包括类型相同和值相同。 +如果a和b都是数组,则会递归依次比较数组中的值;如果都是对象亦然。 + + +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/element.md b/_docs/element.md new file mode 100644 index 00000000..a6fbcc9f --- /dev/null +++ b/_docs/element.md @@ -0,0 +1,484 @@ +# 元件说明 + +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * + +在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。 + +请打开样板0层 `sample0.js` 进行参照对比。 + +![生成地图](./img/sample0.png) + +## 道具 + +本塔目前支持的所有道具列表在样板0层中已全部给出。当你在样板0层中拿到某个宝物时会有提示,这里不再赘述,详见拿到该道具的说明。 + +大多数宝物都有默认的效果,屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](personalization#自定义道具效果)。 + +拿到道具后将触发`afterGetItem`事件,有关事件的详细介绍请参见[事件](event)。 + +如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](personalization#自定义道具效果)的说明。 + +**有关轻按,在data.js的系统变量中有定义。如果`enableGentleClick`为true,则鼠标(触摸屏)通过双击勇士,键盘通过空格可达到轻按效果,即不向前移动而获得前方物品。** + +## 装备 + +如果需要让剑盾等变成装备,可以直接在`data.js`中设置`'equipment': true`即可。 + +从V2.4.1开始,HTML5魔塔样板终于拥有了属于自己的装备页面。 + +### 装备栏的设置,装备类型 + +在全塔属性中,有一个`equipName`项,其定义了本塔的所有可装备的装备栏。 + +其需要是一个不小于1且不大于6的数组,其中每一项为装备栏的名称,**建议是两个汉字**。 + +例如下面这种写法就是定义了四个装备孔,名称分别为武器、防御、首饰和魔杖。 + +``` js +"equipName": ["武器","防具","首饰","魔杖"] +``` + +这么定义好后,装备类型即为每个装备孔的索引(从0开始)。 + +即,武器的装备类型是0,防御的装备类型是1,首饰的装备类型是2,魔杖的装备类型是3。 + +### 设置每个装备的属性 + +如果要将一个道具设置为装备,首先需要将其`cls`设为`equips`。 + +然后在图块属性的`equip`一项中设置装备的具体属性。该项写法如下: + +``` js +{"type": 0, "atk": 0, "def": 0, "mdef":0, "animate": "hand", "percentage": true} +``` + +type为该装备的类型,必填,和上面装备栏一一对应。例如,0就是武器,2就是首饰等等。 + +atk/def/mdef为该装备分别增加的攻防魔防数值(支持负数);如果不加也可省略不写。 + +从V2.6开始,可以拓展到任何勇士的属性,如hpmax, atk, def, mdef, experience等等;自行添加的属性包括攻速speed也能使用。 + +animate为该装备的攻击动画,仅对type为0时有效。具体可参见[动画和天气系统](#动画和天气系统)。 + +percentage为该装备是否按比例增加属性。 + +下面是几个写法例子。 + +``` js +{"type": 0, "atk": 10} // 装备类型是武器,效果是攻击+10,使用默认的攻击动画 +{"type": 0, "atk": 40, "animate": "sword"} // 装备类型为武器,效果是攻击+10,攻击动画是sword +{"type": 1, "def": 40, "percentage": true} // 装备类型是防具,效果是防御提升40% +{"type": 1, "def": 100, "mdef": 100} // 装备类型是防具,效果是防御和魔防各+100 +{"type": 3, "atk": -20, "def": 50, "mdef": 50} // 装备类型是魔杖,效果是攻击-20,防御和魔防各+50 +{"type": 2, "atk": -20, "def": 50, "mdef": 50, "percentage": true} // 装备类型是魔杖,效果是攻击下降20%,防御和魔防各提升50% +``` + +所有取值全部向下取整。 + +值得注意的是,如果多个装备同时按比例增加属性,使用加法计算。比如武器增加30%攻击,防具增加10%攻击,最终合起来增加的是40%而不是43%的属性。 + +### 检测是否存在装备 + +可以使用`core.hasEquip(itemId)`来检测是否装上某个装备。 + +使用`core.hasItem(itemId)`来检测是否存在一个未装上的装备。 + +使用`core.getEquip(equipType)`来获得某个装备类型的当前装备。 + +更多相关API详见[附录:API列表](api)。 + +### 多重装备 + +从V2.5.4开始,允许支持多重装备,即有若干的装备可共用若干的格子(例如永不复还那样)。 + +要实现这一点,上面的写法有所改变。 + +在全塔属性中的`equipName`项写法不变,不过可以写重复的装备孔名称。(但仍然最多只能写6个) + +``` js +"equipName": ["武器", "武器", "武器", "防具", "防具", "首饰"] +``` + +然后对于某个装备,将其`type`(装备类型)写对应的装备孔名称即可。 + +``` js +{"type": "武器", "atk": 20, "def": 0, ...} +``` + +这样写的话,则所有该名称的装备孔均可装上此装备。 + +当尝试装上此装备时,会取最小的一个空的装备孔进行装备。如果没有空闲的装备孔,则会提示“请先卸下装备”。 + +装备动画仍然会取第一个(装备类型为0)的装备的`animate`项,即使装备了多个有动画的武器。 + +## 门 + +本塔支持6种门,黄蓝红绿铁花。前五种门需要有对应的钥匙打开,花门只能通过调用`openDoor`事件进行打开。 + +开门后可触发该层的`afterOpenDoor`事件,有关事件的详细介绍请参见[事件](event)。 + +如果要新增自己的门,请参见[新增门和对应的钥匙](personalization#新增门和对应的钥匙)。 + +## 暗墙 + +本塔支持暗墙。 + +要制作一个暗墙非常简单:在该点直接放一个普通墙壁,然后事件写“开门”,坐标为该点就行。 + +``` js +// 该点画一个普通的墙壁,比如`yellowWall` + +// 在该点的事件events中: +"x,y": [ + {"type": "openDoor"} // 直接使用开门事件,坐标可忽略表示当前点 +] +``` + +系统会自动调用animates中的开暗墙动画。 + +目前只有如下ID支持以这种方式开门: + +``` text +yellowDoor, blueDoor, redDoor, greenDoor, specialDoor, steelDoor, +yellowWall, blueWall, whiteWall +``` + +## 怪物 + +本塔支持的怪物列表参见`project/enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。 + +如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。 + +怪物可以有特殊属性,每个怪物可以有多个自定义属性。 + +怪物的特殊属性所对应的数字(special)在脚本编辑中的`getSpecials`中定义,请勿对已有的属性进行修改。 + +多属性可采用数组的写法,比如`'special': [1,3]`视为同时拥有先攻和坚固属性;`'special': [5,10,14,18]`视为拥有3连击、魔防、诅咒、阻击四个属性。 + +怪物可以负伤,在全塔属性的全局变量`enableNegativeDamage`中指定。 + +打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](event#加点事件)。 + +如果全塔属性中的enableExperience为false,即不启用经验的话,怪物手册里将不显示怪物的经验值,打败怪物也不获得任何经验。 + +拿到幸运金币后,打怪获得的金币将翻倍。 + +如果怪物有`"notBomb": true`,则该系列诖怪物均不可被炸。 + +N连击怪物的special是6,且我们可以为它定义n代表实际连击数。参见样板中剑王的写法。 + +吸血怪需要给怪物设置value,代表吸血的比例。 + +可以给吸血怪添加`'add': true`来将吸血的数值加到自身上。 + +中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 + +衰弱怪让勇士衰弱后,攻防会下降一定比例或固定数值(直到衰弱状态解除恢复);其在`data.js`中的values定义。 + +诅咒怪将让勇士陷入诅咒状态,诅咒状态下杀怪不获得金币和经验值。 + +领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 + +领域是十字伤害还是九宫格伤害由`zoneSquare`设定,如设置为true则为九宫格伤害,不指定或为false则为十字伤害。 + +领域怪还可以设置`range`选项代表该领域怪的范围,不写则默认为1。 + +**将`flag:no_zone`设置为true可以取消领域效果。** + +阻击怪同样需要设置value,代表阻击伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 + +**将`flag:no_snipe`设置为true可以取消阻击效果。** + +!> 阻击怪后退的地点不能有任何事件存在,即使是已经被禁用的自定义事件! + +激光怪同样需要设置value,代表激光伤害的数值。 + +请注意如果吸血、领域、阻击中任何两个同时存在,则value会冲突。**因此请勿将吸血、领域、阻击或激光放置在同一个怪物身上。** + +**将`flag:no_laser`设置为true可以免疫激光效果。** + +退化怪需要设置'atkValue'和'defValue'表示退化的数值;也可以不设置默认为0。 + +夹击可以通过全塔属性中的`betweenAttackCeil`设为true可以将伤害向上取整。 + +**将`flag:no_betweenAttack`设置为true可以免疫夹击效果。** + +固伤怪则需要设置`damage`选项,代表战前扣血数值。 + +如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 + +## 怪物和NPC的朝向问题 + +从V2.5.2开始,对于人形怪物和NPC的朝向问题已经有着比较好的解决方式。 + +首先明确一点的是,和RM不同,H5中即使是对同一个怪物/NPC的不同朝向,也需要分别将其各个朝向素材追加对应的图片上,并进行注册。 + +### 怪物的朝向问题 + +对于同一个怪物的不同朝向,需要对每个朝向创建一个怪物(属性完全相同),这样就可以在地图上绘制不同朝向的怪物。 + +但是这样会存在一个问题,就是怪物手册中怪物的每个朝向都会显示成一个单独的怪物。 + +为了避免这种情况的发生,在怪物的属性中存在一个`displayIdInBook`项,**我们可以指定该项来控制每个怪物在怪物手册中中显示成的怪物。** + +举个例子,假设我现在有个怪物,其向下的行走图ID是`E300`,其向左的行走图ID是`E301`,向右的行走图ID是`E302`。分别对这几个怪物填写完全相同的怪物属性。 + +如果我在地图上同时绘制向下、向左和向右的该怪物,则确实能在地图上显示出来不同的朝向,但是在怪物手册中会同时显示这三种类型的怪物,观感较差。 + +我们可以给`E301`和`E302`怪物属性中的`displayIdInBook`项填写为`"E300"`。 + +这样的话,在怪物手册中,所有的E301和E302均会被视为E300并进行合并。即使只有一只朝向左的怪物(E301),怪物手册仍然会按E300进行显示。 + +从而完美解决了同种怪物不同朝向在怪物手册的显示问题。 + +### NPC的朝向问题 + +和怪物不同的是,NPC朝向问题更复杂一点。 + +在NPC的图块属性中,存在一个`faceIds`的项目,可以用其来绑定一个图块所对应的其他朝向的图块ID。 + +举个例子,假设我存在一个NPC,其向上的图块ID是N333,向下的图块ID是N334,向左的图块ID是N335,不存在向右的图块ID。 + +则可以在这几个图块属性中的`faceIds`中写:`{"up": "N333", "down": "N334", "left": "N335"}`。 + +当勇士从左边撞上此怪物后,将从该图块的图块属性中的faceIds中寻求`left`所对应的ID。 +如果存在定义(如N335),则会在触发对话事件前改变当前图块为N335,看起来就是在对话前进行了转向,面向勇士。 + +!> 请注意,在对话结束后朝向不会切换回来,因此如果有必要切换朝向请在事件结束前调用转变图块事件。 + +同理,使用移动事件让NPC在行走时,不同朝向的行走会自动调用`faceIds`中不同朝向的ID所对应的行走图,看起来就是在行走时也可以不断转向了。 + +从而,完美解决了NPC的朝向问题(碰触时面向勇士、行走时改变朝向)。 + +## 路障,楼梯,传送门 + +血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在全塔属性的values内定义。 + +路障同样会尽量被自动寻路绕过。 + +有关楼梯和传送门,必须在该层样板的changeFloor里指定传送点的目标。 + +floorId指定的是目标楼层的唯一标识符(ID)。 + +也可以写`"floorId": ":before"`和`"floorId": ":next"`表示上一楼和下一楼。 + +后面可以写stair到upFloor或downFloor,表示将前往目标楼层的上楼梯/下楼梯位置。你也可以写loc然后指定目标点的坐标。 + +请注意的是,如果目标楼层有多个楼梯,写stair可能会导致到达的楼梯不确定,这时候请使用loc方式来指定具体的点位置。 + +可以指定direction为up/left/right/down,指定后勇士将面向该方向。 + +可以指定time,指定后切换动画时长为指定的数值。 + +**从2.1.1开始,楼层属性中提供了`upFloor`和`downFloor`两项。如果设置此项(比如`"upFloor": [2,3]`),则写stair:upFloor或者楼传器的落点将用此点来替换楼梯位置(即类似于RM中的上箭头)。** + +## 剧情文本控制与对话框效果 + +在写剧情文本时,可以: + +- 使用`\t[...]`来给文字加上标题和图标。如`\t[老人,man]`。 +- 使用`\b[...]`来制作对话框效果,如`\b[up,3,2]`。 +- 使用`\r[...]`来动态修改局部文本的颜色,如`\r[red]`。 +- 使用`${}`来计算一个表达式的值,如`${status:atk+status:def}`。 +- 使用`\f[...]`来同时插入一张立绘图,如`\f[1.png,100,200]`。 +- 使用`\\i[...]`来在对话框中绘制一个图标,如`\\i[fly]`。 + +从V2.5.2开始,也允许绘制一张头像图在对话框中,只要通过`\t[1.png]`或`\t[标题,1.png]`的写法。 + +**使用`\\i[...]`绘制图标请注意:在事件块中,允许只写一个反斜杠`\i`,系统会自动转义成`\\i`;但是在脚本中必须两个反斜杠都写上!** + +详细信息请参见[剧情文本控制](event#text:显示一段文字(剧情))中的说明。 + +从V2.5.2开始,可以用一张WindowSkin图片作为对话框的背景皮肤。 + +使用时,需要将图片放在images目录下,并在全塔属性中予以注册。 + +可以使用[设置剧情文本的属性](setText:设置剧情文本的属性)事件将对话框背景设置为需要的皮肤。 + +!> 关于对话框效果请注意,现在是采用WindowSkin的右下角两个32x32的图片作为对话框尖角进行绘制。因此请尽量使用群文件或网盘的常用素材中给出的WindowSkin素材(均已进行对话框适配)。如需使用来自第三方的WindowSkin素材,请自行注意对话框的尖角问题,或弃用`\b`效果。 + +另外一点是,V2.5.2以后,对话框`\b`可以根据文字长度来自动控制文本框宽度,其基本控制原理如下: + +- 如果用户存在手动换行`\n`,则选取**最长的一段话**作为文本框宽度。 +- 如果用户不存在手动换行,则会将文本框宽度调整为X行半的最佳宽度。 +- 文本框宽度存在上下界,最终宽度一定会控制在该范围内。 + +该自动调整仅对`\b`的对话框效果有效。非对话框仍然会绘制整个界面的宽度。 + +## 大地图 + +从V2.4开始,H5魔塔开始支持大地图。 + +大地图在创建时可以指定宽高,要求**宽和高都不得小于13(15x15版本则是不小于15),且宽高之积不超过1000**。 + +大地图一旦创建成功则不得修改宽高数值。 + +## 动画和天气系统 + +现在我们的H5魔塔支持播放动画,也支持天气系统了。 + +要播放动画,你需要先使用“RM动画导出器”将动画导出,放在animates目录下,然后在全塔属性的animates中定义。 + +``` js +// 在此存放所有可能使用的动画,必须是animate格式,在这里不写后缀名 +// 动画必须放在animates目录下;文件名不能使用中文,不能带空格或特殊字符 +"animates": ["hand", "sword", "zone", "yongchang", "thunder"] +``` + +!> 动画必须是animate格式,名称不能使用中文,不能带空格或特殊字符。 + +导出动画时可能会进行一些压缩以节省流量,因此清晰度可能不如原版。 + +从2.3.2开始,动画可以同时导出所用的音效。**如果导出音效,请确保将所用到的音效复制到了`sounds`目录下,并且在全塔属性中注册过。** + +动画播放时,是按照每秒20帧的速度(即50ms/帧)。 + +定义完毕后,我们可以调用`animate`事件来播放该动画,有关事件的详细介绍请参见[事件](event)。 + +!> 播放录像时,将默认忽略所有动画。 + +目前天气系统支持雨和雪和雾两种天气。 + +在每层楼的楼层属性中存在一个weather选项,表示该层楼的默认天气。 + +``` js +// 该层的默认天气。本项可忽略表示晴天,如果写则第一项为"rain","snow"或"fog"代表雨雪雾,第二项为1-10之间的数代表强度。 +"weather": ["snow",5] +``` + +我们也可以使用`setWeather`事件来设置当前天气,有关事件的详细介绍请参见[事件](event)。 + +## 背景音乐 + +本塔支持BGM和SE的播放。 + +要播放音乐和音效,你需要将对应的文件放在sounds目录下,然后在全塔属性中进行定义 + +``` js +// 在此存放所有的bgm,和文件名一致。 +// 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 +"bgms": ["bgm.mp3"] + +// 在此存放所有的SE,和文件名一致 +"sounds": ["floor.mp3", "attack.mp3", "door.mp3", "item.mp3", "zone.mp3"] +``` + +!> 音频名不能使用中文,不能带空格或特殊字符。 + +目前BGM支持主流的音乐格式,如mp3, ogg等。不支持mid格式的播放。 + +定义完毕后,我们可以调用`playBgm`/`playSound`事件来播放对应的音乐/音效,有关事件的详细介绍请参见[事件](event)。 + +**另外,考虑到用户的流量问题,将遵循如下规则:** + +- **如果用户当前使用的电脑,则默认开启音乐效果,并播放默认BGM** +- **如果用户当前使用的手机,且处于Wifi状态,则默认开启音乐效果,并播放默认BGM** +- **其他情况,将默认关闭音乐效果,只有在用户在菜单栏中点击“音乐开关”后才会播放音乐** + +!> iOS平台以及部分浏览器不支持获得当前网络状态,此时即使在使用Wifi也必须要用户点击“音乐开关”才能播放音乐。 + +从V2.5.3开始,可以使用`loadBgm`事件来预加载一个bgm,这样到播放时无需等待,直接播放。 + +同时BGM将使用LRU算法增加缓存机制。默认最多缓存4个BGM(在core.js的musicStatus.cachedBgmCount控制)。 + +系统会自动释放最久未使用的BGM。 + +也可以使用`freeBgm`事件来手动释放一个无需再用的bgm。 + +## 录像 + +HTML5魔塔一大亮点就是存在录像系统,可以很方便进行录像回放。 + +当你在游戏的过程中,随着你的操作,录像也会被依次记录。游戏结束后将提示是否下载录像,上传成绩时也会上传你的录像信息。 + +在菜单栏-同步存档中,可以直接对当前录像进行下载。 + +!> 录像记录的是你当前的路线(本质上是模拟键盘操作),是一个纯文本文件,占用空间很小! + +录像的回放主要有两种方式: + +1. 保存成的录像文件(.h5route文件):在标题界面点录像回放,再选择文件即可。 +2. 游戏过程中时的当前录像:随时按R可以进行回放;手机端则可调出虚拟键盘,再按R。 + +录像播放过程中,可以进行如下操作: + +- **暂停/播放:** 按空格可以随时暂停或播放录像。 +- **加速:** 按X可以加速录像播放,最高可达6倍速。 +- **减速:** 按Z可以减速录像播放,最低可达0.3倍速。 +- **停止:** 按ESC可以立刻停止录像播放,并返回正常游戏。 +- **回退:** 按A可以回退到上一个录像节点(录像播放过程中每50步存一个录像节点)。 +- **存档:** 按S可以在录像播放过程中进行存档。 +- **查看手册:** 按C可以在录像播放过程中查看怪物手册。 +- **浏览地图:** 按PgUp/PgDn可以在录像播放过程中浏览地图。 + +如果录像出现问题,请加群539113091找小艾反馈Bug。 + +## 绘图模式 + +从V2.5开始,样板提供了绘图模式,可以让玩家在画布上任意进行绘制,标记等。 + +使用M键,或在菜单栏中可以进入绘图模式。 + +**绘图的内容会自动保存,且以页面为生命周期,和存读档无关,返回标题并重新开始游戏后绘制的内容仍有效,但刷新页面就会消失。** + +你可以将绘制内容保存到文件,也可以从文件读取保存的绘制内容。 + +绘图模式下,状态栏的图标也会相应改变,铅笔为绘制模式,橡皮为擦除模式,存读档为保存和读取绘图文件,退出为返回默认值。 + +在浏览地图页面中也可以按楼传按钮或M键来开启/关闭该层的绘图显示。 + +## 操作说明 + +![](img/keyboard.png) + + + +  + +  + +上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。 + +尝试着做一个两到三层的塔吧! + +========================================================================================== + +[继续阅读下一章:事件](event) diff --git a/docs/event.md b/_docs/event.md similarity index 50% rename from docs/event.md rename to _docs/event.md index 04e68bc7..34fff45b 100644 --- a/docs/event.md +++ b/_docs/event.md @@ -1,6 +1,6 @@ # 事件 -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * 本章内将对样板所支持的事件进行介绍。 @@ -10,8 +10,6 @@ 上述这些默认的事件已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是一个自定义的事件。 -**本塔中的所有自定义事件能且只能被其他事件触发。不存在RMXP里面那种,设置了某个变量为true后,一个事件被自动执行的问题。这点和RMXP的区别非常大,请务必注意。** - 所有事件都存在两种状态:启用和禁用。 - 启用状态下,该事件才处于可见状态,可被触发、交互与处理。 - 禁用状态下该事件相当于不存在,不可见、不可被触发、不可交互。 @@ -22,11 +20,13 @@ ## 关于V2.0的重要说明 -在V2.0版本中,所有事件均可以使用blockly来进行块的可视化编辑。 +在V2.0以后版本中,所有事件均可以使用blockly来进行块的可视化编辑。 它能通过拖动、复制粘贴等方式帮助你快速生成事件列表,而不用手动打大量字符。 -但是,仍然强烈建议要对每个事件的写法进行了解,因为在脚本编辑,`insertAction`等地方需要插入自定义事件时,还是很有必要的。 +下述所说的都是在事件编辑器右边所展示的,该事件的代码化写法。 + +强烈建议要对每个事件的写法进行了解,因为在脚本编辑,`insertAction`等地方需要插入自定义事件时,还是很有必要的。 ## 自定义事件 @@ -35,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"`,则原事件会被覆盖。** @@ -60,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 + // ... + ] } ``` @@ -79,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 + // ... + ] } ``` @@ -100,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 + // ... + ] } ``` @@ -119,14 +109,12 @@ 如果大括号里只有`"data"`,则可以省略大括号和`"data"`,直接写中括号数组,换句话说,上面和下面这种写法也是等价的,可以进行一下比较: ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 - // 事件1 - // 事件2 - // ... - ] -} +// 如果大括号里只有"data"项(没有"action", "enable"或"noPass"),则可以省略到只剩下中括号 +[ + // 事件1 + // 事件2 + // ... +] ``` 这种简写方式可以极大方便地造塔者进行造塔。 @@ -138,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"`为该自定义事件的类型;而后面的`...`则为具体的一些事件参数。 @@ -160,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"}`的形式。 @@ -190,15 +170,12 @@ 值得注意的是,系统会自动对文字进行换行;不过我们也可以手动加入`\n`来换行。 ``` js -"events": { // 该楼的所有可能事件列表 - // 如果大括号里只有"data"项(没有"action"或"enable"),则可以省略到只剩下中括号 - "x,y": [ // 实际执行的事件列表 +[ "这一段文字特别特别长,但是系统可以对它进行自动换行,因此我们无需手动换行", "这是第一行\n这是第二行\n这是第三行", // ... // 按顺序写事件,直到结束 - ] -} +] ``` 我们可以给文字加上标题或图标,只要以`\t[...]`开头就可以。 @@ -209,8 +186,12 @@ 对于hero和怪物,也可以不写名字代表使用默认值。 +从V2.5.2以后,新增了绘制大头像的功能。绘制大头像图的基本写法是`\t[1.png]`或者`\t[标题,1.png]`。 + +从V2.6开始,所有图块都允许只写ID,对于非怪物则没有标题。 + ``` js -"x,y": [ // 实际执行的事件列表 +[ "一段普通文字", "\t[勇士,hero]这是一段勇士说的话", "\t[hero]如果使用勇士默认名称也可以直接简写hero", @@ -218,63 +199,117 @@ "\t[blackMagician]如果使用怪物的默认名称也可以简写怪物id", "\t[小妖精,fairy]这是一段小妖精说的话,使用仙子(fairy)的图标", "\t[你赢了]直接显示标题为【你赢了】", + "\t[1.png]绘制1.png这个头像图", + "\t[标题,1.png]同时绘制标题和1.png这个头像图", + "\t[sword1]获得铁剑,没有标题", + "\t[man]没有标题的npc动画" ] ``` +!> 大头像的头像图需要在全塔属性中注册,且必须是png格式,不可以用jpg或者其他格式,请自行转换。 + 除此以外,我们还能实现“对话框效果”,只要有`\b[...]`就可以。 - `\b[up]` 直接显示在当前点上方。同样把这里的up换成down则为下方。 - - 如果不存在当前点(如在firstArrive中调用),则显示在屏幕最上方(最下方) + - 如果不存在当前点(如在firstArrive或eachArrive中调用),则显示在屏幕最上方(最下方) - `\b[up,hero]` 显示在勇士上方。同样把这里的up换成down则为下方。 -- `\b[up,x,y]` 显示在(x,y)点的上方(下方);x和y都为整数且在0到12之间。 + - 从V2.6开始,也允许写`\b[hero]`来根据勇士位置自动决定上方还是下方 +- `\b[up,x,y]` 显示在(x,y)点的上方(下方);x和y都为整数且在0到12之间 + - 从V2.6开始,也允许写`\b[null,x,y]`来根据(x,y)位置自动决定上方还是下方 +- `\b[up,x]` 显示在勇士的第x个跟随的行走图的上方(下方);也允许把up换成null来自动适配 ``` js -"x,y": [ // 实际执行的事件列表 +[ "\b[up]这段文字显示在当前点上方", "\b[down]这段文字显示在当前点上方", "\t[hero]\b[up,hero]这是一段勇士说的话,会显示在勇士上方", + "\t[hero]\b[hero]这是一段勇士说的话,根据勇士位置自动适配上下", "\t[小妖精,fairy]\b[down,2,2]这是一段小妖精说的话,会显示在(2,2)点下方", + "\t[null,1,3]根据坐标位置自动适配上下", + "\t[up,1]"显示在勇士第一个跟随的行走图上方", + "\t[null,2]显示在勇士第二个跟随的行走图,自动适配上下", ] ``` +从V2.6开始,`\b`提供了更多功能,包括: + +- `\b[hero]` + !> `\t[...]`必须在`\b[...]`前面!不然两者都无法正常显示。 +还可以使用`\r[...]`来调整剧情文本的颜色。 + +``` js +[ + "这句话是默认颜色,\r[red]将颜色变成红色,\r[blue]将颜色变成蓝色", + "\r[#FF00FF]还可以使用RGB值来控制颜色,\r如果不加中括号则回到默认颜色", + "\t[hero]\b[up,hero]啊啊啊,别过来,\r[red]别过来!!!\n\r你到底是什么东西!" +] +``` + +从V2.5.3以后,也可以使用`\f[...]`来同时绘制一张图片。 + +其基本写法是`\f[img,x,y]`,或者`\f[img,x,y,w,h]`,或者`\f[img,sx,sy,sw,sh,x,y,w,h]`。 + +需要注意的是,这个图片是绘制在UI层上的,下一个事件执行时即会擦除;同时如果使用了\t的图标动画效果,重叠的地方也会被图标动画给覆盖掉。 + +``` js +[ + "\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]也可以填写宽高,这样会把图片强制进行放缩到指定的宽高值", + "\f[1.png,64,64,128,128,100,100,128,128]裁剪1.png上以(64,64)开始的128x128图片,并绘制到画布的(100,100)处" +] +``` + +从V2.5.5以后,也可以使用`\\i[...]`来在对话框中绘制一个图标。 + +这里可以使用一个合法ID(32x48图块除外),或使用一个系统图标(`core.statusBar.icons`中的内容)。 + +``` js +[ + "\t[勇士]\b[up,hero]这是一个飞行器\\i[fly],这是一个破墙镐\\i[pickaxe]", + "\t[hero]也可以使用系统图标,比如这是存档\\i[save],这是工具栏\\i[toolbox]", +] +``` + +**可以在控制台中输入`core.statusBar.icons`以查看所有的系统图标定义。** + +!> 注意,在事件块中,允许只写一个反斜杠`\i`,系统会自动转义成`\\i`;但是在脚本中必须两个反斜杠都写上! 另外值得一提的是,我们是可以在文字中计算一个表达式的值的。只需要将表达式用 `${ }`整个括起来就可以。 ``` js -"x,y": [ // 实际执行的事件列表 +[ "1+2=${1+2}, 4*5+6=${4*5+6}", // 显示"1+2=3, 4*5+6=26" ] ``` 我们可以使用 `status:xxx` 代表勇士的一个属性值;`item:xxx` 代表某个道具的个数;`flag:xxx` 代表某个自定义的变量或flag值。 +从V2.6开始,也可以使用`global:xxx`代表全局存储(和存档无关的存储)。 + ``` js -"x,y": [ // 实际执行的事件列表 +[ "你当前的攻击力是${status:atk}, 防御是${status:def},坐标是(${status:x},${status:y})", "你的攻防和的十倍是${10*(status:atk+status:def)}", "你的红黄蓝钥匙总数为${item:yellowKey+item:blueKey+item:redKey}", "你访问某个老人的次数为${flag:man_times}", + "当前的存档编号是${global:saveIndex}", ] ``` - `status:xxx` 获取勇士属性时只能使用如下几个:hp(生命值),atk(攻击力),def(防御力),mdef(魔防值),money(金币),experience(经验),x(勇士的横坐标),y(勇士的纵坐标),direction(勇士的方向)。 - `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) +- `flag:xxx` 中的xxx为一个自定义的变量/Flag(支持中文);如果没有对其进行赋值则默认值为0。 +- `global:xxx` 中的xxx为一个全局存储的名称(支持中文);如果没有对其进行赋值则默认值为0。 ### autoText:自动剧情文本 使用`{"type": "autoText"}`可以使用剧情文本。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "autoText", "text": "一段自动显示的剧情文字", "time": 5000} ] ``` @@ -289,15 +324,38 @@ time为可选项,代表该自动文本的时间。可以不指定,不指定 !> 由于用户无法跳过自动剧情文本,因此对于大段剧情文本请自行添加“是否跳过剧情”的提示,否则可能会非常不友好。 +### scrollText:滚动剧情文本 + +使用`{"type": "scrollText"}`可以使用滚动剧情文本,即将一段文字从屏幕最下方滚动到屏幕最上方。 + +``` js +[ + {"type": "scrollText", "text": "第一排\n第二牌\n\n空行后的一排", "time": 5000, "lineHeight": 1.4, "async": true}, +] +``` + +text为正文文本内容。可以使用`${ }`来计算表达式的值,且使用`\n`手动换行。系统不会对滚动剧情文本进行自动换行。 + +time为可选项,代表总的滚动时间。默认为5000毫秒。 + +lineHeight为可选项,代表行距。默认为1.4。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +可以使用下面的[设置剧情文本的属性](event#setText:设置剧情文本的属性)来对文字颜色、文字大小、粗体、距离左边的偏移量进行设置。 + +!> 滚动剧情文本会绘制在UI层(和对话框冲突)!如果是异步处理请注意不要和对话框混用。 + ### setText:设置剧情文本的属性 使用`{"type": "setText"}`可以设置剧情文本的各项属性。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "setText", "title": [255,0,0], "text": [255,255,0], "background": [0,0,255,0.3]}, - {"type": "setText", "position": "up", "bold": true, "time": 70}, - "这段话将显示在上方,标题为红色,正文为黄色粗体,背景为透明度0.3的蓝色,70毫秒速度打字机效果" +[ + {"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毫秒速度打字机效果", + {"type": "setText", "background": "winskin.png"} // 还可以一张使用WindowSkin作为皮肤。 ] ``` @@ -305,12 +363,20 @@ title为可选项,如果设置则为一个RGB三元组或RGBA四元组,表 text为可选项,如果设置则为一个RGB三元组或RGBA四元组,表示正文颜色。 默认值:`[255,255,255,1]` -background为可选项,如果设置则为一个RGB三元组或RGBA四元组,表示背景色。 默认值:`[0,0,0,0.85]` +background为可选项,如果设置可为一个RGB三元组或RGBA四元组,表示背景色。 默认值:`[0,0,0,0.85]` + +V2.5.2以后,background也可以为一个WindowSkin的文件名。详见[剧情文本控制与界面皮肤](element#剧情文本控制与界面皮肤)。 position为可选项,表示设置文字显示位置。只能为up(上),center(中)和down(下)三者。 默认值: `center` +offset为可选项,如果设置则为代表距离如果显示位置是上/下的话,距离顶端/底端的像素值。也作为滚动剧情文本时距离左边的像素值。 + bold为可选项,如果设置则为true或false,表示正文是否使用粗体。 默认值:`false` +titlefont为可选项,表示标题字体大小(px为单位)。默认值:`22` + +textfont为可选项,表示正文字体大小(px为单位)。默认值:`16` + time为可选项,表示文字添加的速度。若此项设置为0将直接全部显示,若大于0则会设置为相邻字符依次显示的时间间隔。 默认值:`0` ### tip:显示一段提示文字 @@ -318,12 +384,26 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 `{"type": "tip"}`可以在左上角显示一段提示文字。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "tip", "text": "这段话将在左上角以气泡形式显示"} +[ + {"type": "tip", "text": "这段话将在左上角以气泡形式显示", "icon": "book"} ] ``` -值得注意的是,提示的text内容也是可以使用`${ }`来计算表达式的值的。 +text必填,为显示的内容,支持`${}`的表达式计算。 + +icon是可选的,如果设置则会绘制图标,其可以是一个有效的ID,或者`core.statusBar.icons`中的系统图标。 + +### comment:添加注释 + +使用`{"type": "comment"}`可以添加一段注释 + +``` js +[ + {"type": "comment", "text": "这是一段会被跳过的注释内容"} +] +``` + +这个事件将在运行时被游戏跳过。 ### setValue:设置勇士的某个属性、道具个数,或某个变量/Flag的值 @@ -332,7 +412,7 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setValue", "name": "...", "value": "..."}, // 设置一个属性、道具或自定义Flag ] ``` @@ -341,10 +421,10 @@ time为可选项,表示文字添加的速度。若此项设置为0将直接全 name为你要修改的属性/道具/Flag,每次只能修改一个值。写法和上面完全相同,`status:xxx` 表示勇士一个属性,`item:xxx` 表示某个道具个数,`flag:xxx` 表示某个变量或flag值。参见上面的介绍。 -value是一个表达式,将通过这个表达式计算出的结果赋值给name。该表达式同样可以使用`status:xxx`, `item:xxx`, `flag:xxx`的写法表示勇士当前属性,道具个数和某个变量/Flag值。 +value是一个表达式,将通过这个表达式计算出的结果赋值给name。该表达式同样可以使用`status:xxx`, `item:xxx`, `flag:xxx`, `global: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" } // 生命值翻倍 @@ -352,11 +432,95 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam {"type": "setValue", "name": "item:bomb", "value": "item:bomb+10" } // 炸弹个数+10 {"type": "setValue", "name": "flag:man_times", "value": "0" } // 将变量man_times设为0 {"type": "setValue", "name": "flag:man_times", "value": "flag:man_times+2*status:atk" } // 将变量man_times的值加上勇士的攻击数值的两倍 + {"type": "setValue", "name": "global:123", "value": "4"} // 设置全局存储233为4(任何存档都可以读取它) ] ``` 另外注意一点的是,如果hp被设置成了0或以下,将触发lose事件,直接死亡。 +### addValue:增减勇士的某个属性、道具个数,或某个变量/Flag的值 + +和`{"type": "setValue"}`的写法完全相同,不过此项是可以直接将值加减到原始数值上。 + +即下面的写法是等价的: + +``` js +[ + {"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 + {"type": "addValue", "name": "item:yellowKey", "value": "-3" } // 和上面写法等价 + {"type": "setValue", "name": "flag:door2", "value": "flag:door2+1" } // 将变量door值+1 + {"type": "addValue", "name": "flag:door2", "value": "01" } // 和上面写法等价 +] +``` + +### setFloor:设置楼层属性 + +使用`{"type":"setFloor"}`可以设置某层楼的楼层属性。 + +``` js +[ + {"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层不可被浏览地图 + {"type": "setFloor", "name": "item_ratio", "value": "5" } // 设置当前楼层的宝石血瓶属性加成为5 + {"type": "setFloor", "name": "images", "value": "[[0,0,'tree.png',2]]" } // 设置当前楼层的楼层贴图 + {"type": "setFloor", "name": "upFloor", "value": "[2,3]" } // 设置当前楼层的上楼梯 + {"type": "setFloor", "name": "bgm", "floorId": "MT10", "value": "'233.mp3'" } // 设置当前楼层的背景音乐 +] +``` + +name为必填项,代表要修改的楼层属性,和楼层属性中的一一对应。 + +floorId为可选项,代表要修改的楼层ID;可以省略代表当前楼层。 + +value为必填项,代表要修改到的数值。其应该和楼层属性中的对应数值类型完全一致,对于字符串需要加单引号,其他类型(数字、true/false、数组)等则不需要引号。 + +!> 如果修改到的是字符串类型,比如楼层中文名、状态栏名称、地面素材ID、背景音乐等,必须加引号,否则会报错。 + +### setGlobalAttribute:设置一个全局属性 + +使用`{"type":"setGlobalAttribute"}`可以设置一个全局属性。 + +``` js +[ + {"type": "setGlobalAttribute", "name": "font", "value": "Verdana"}, // 设置字体为Verdana +] +``` + +name必填项,代表要修改的全局属性。 + +value为必填项,代表要修改到的结果。此项无需再手动加单引号。 + +### setGlobalValue:设置一个全局数值 + +使用`{"type":"setGlobalValue"}`可以设置一个全局数值。 + +``` js +[ + {"type": "setGlobalValue", "name": "lavaDamage", "value": 200}, // 设置血网伤害为200 +] +``` + +name必填项,代表要修改的全局数值,其和全塔属性中的values一一对应。 + +value为必填项,代表要修改到的结果。该项必须是个数值。 + +### setGlobalFlag:设置一个系统开关 + +使用`{"type":"setGlobalFlag"}`可以设置一个系统开关。 + +``` js +[ + {"type": "setGlobalFlag", "name": "enableMDef", "value": false}, // 不在状态栏显示魔防值 +] +``` + +name必填项,代表要修改的系统开关,其是全塔属性中的flags中的一部分。 + +value为必填项,只能为true或false,代表要修改到的结果。 + ### show:将一个禁用事件启用 我们上面提到了,所有事件都必须靠其他事件驱动来完成,不存在当某个flag为true时自动执行的说法。那么,我们自然要有启用事件的写法。 @@ -364,11 +528,12 @@ value是一个表达式,将通过这个表达式计算出的结果赋值给nam 使用`{"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为动画时间 - {"type": "show", "loc": [[3,6],[2,9],[1,2]], "time": 500} // 我们也可以同时动画显示多个点。 + {"type": "show", "loc": [[3,6],[2,9],[1,2]], "time": 500}, // 我们也可以同时动画显示多个点。 + {"type": "show", "loc": [3,6], "time": 500, "async": true} // 可以使用异步动画效果 ] ``` @@ -380,13 +545,15 @@ floorId为目标点的楼层,如果不是该楼层的事件(比如4楼小偷 time为动画效果时间,如果指定了某个大于0的数,则会以动画效果慢慢从无到有显示,动画时间为该数值;如果不指定该选项则无动画直接立刻显示。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + !> **要注意的是,调用show事件后只是让该事件从禁用状态变成启用,从不可见不可交互变成可见可交互,但本身不会去执行该点的事件。** ### hide:将一个启用事件禁用 `{"type":"hide"}`和show刚好相反,它会让一个已经启用的事件被禁用。 -其参数和show也完全相同,loc指定事件的位置,floorId为楼层(同层可忽略),time指定的话事件会以动画效果从有到无慢慢消失。 +其参数和show也完全相同,loc指定事件的位置,floorId为楼层(同层可忽略),time指定的话事件会以动画效果从有到无慢慢消失,async代表是否是异步效果。 loc同样可以简单的写[x,y]表示单个点,或二维数组[[x1,y1],[x2,y2],...]表示多个点。 @@ -397,14 +564,14 @@ 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为动画时间 {"type": "hide", "loc": [[3,6],[2,9],[1,2]], "time": 500}, // 也可以同时指定多个点消失 {"type": "hide", "time": 500}, // 如果不指定loc选项则默认为当前点, 例如这个就是500ms消失当前对话的NPC {"type": "hide"}, // 无动画将当前事件禁用,常常适用于某个空地点(触发陷阱事件、触发机关门这种) - + {"type": "hide", "loc": [3,6], "time": 500, "async": true} // 可以使用异步动画效果 ] ``` @@ -412,30 +579,73 @@ NPC对话事件结束后如果需要NPC消失也需要调用 `{"type": "hide"}` `{"type":"trigger"}` 会立刻触发当层另一个地点的自定义事件。 -上面我们说到,show事件会让一个禁用事件启用且可被交互;但是如果我想立刻让它执行应该怎么办呢?使用trigger就行。 - 其基本写法如下: ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "trigger", "loc": [3, 6]}, // 立即触发loc位置的事件,当前剩下的事件全部不再执行 - "执行trigger后,这段文字将不会再被显示" +[ + {"type": "trigger", "loc": [3,6]}, // 立即触发loc位置的事件,当前剩下的事件全部不再执行 + {"type": "trigger", "loc": [3,6], "keep": true}, // 触发loc位置的事件,不结束当前事件 ] ``` 其后面带有loc选项,代表另一个地点的坐标。 -执行trigger事件后,当前事件将立刻被结束,剩下所有内容被忽略;然后重新启动另一个地点的action事件。 +keep可选,如果此项为true则不会结束当前的事件列表,否则会中断当前的事件流。 -例如上面这个例子,下面的文字将不会再被显示,而是直接跳转到`"3,6"`对应的事件列表从头执行。 +从V2.5.4开始允许执行另一个点的系统事件,如打怪开门拾取道具等等,对应点的战后等事件也会被插入执行。 + +值得注意的是,此事件会**改变当前点坐标**为目标点。 + +### insert:插入公共事件或另一个地点的事件并执行 + +`{"type":"insert"}` 会插入公共事件或另一个地点的事件并执行。 + +其基本写法如下: + +``` js +[ + {"type": "insert", "name": "加点事件", "args": [10] }, // 插入公共事件:加点事件,传入参数10 + {"type": "insert", "name": "毒衰咒处理", "args": [0]}, // 插入公共事件:毒衰咒处理,传入参数0 + {"type": "insert", "loc": [3,6]}, // 插入[3,6]点的事件并执行 + {"type": "insert", "loc": [10,10], "floorId": "MT1"}, // 插入MT1层[10,10]点的事件并执行 + {"type": "insert", "loc": [2,2], "args": [1,"flag:abc","status:atk+status:def"]}, // 传入三个参数 + "上面的插入事件执行完毕后会接着继续执行后面的事件" +] +``` + +`insert`的写法有两种,可以写`name`,或者`loc`。 + +- 如果写了`"name": "xxx"`,则会去公共事件列表中找寻对应的事件,并执行。 + - name为公共事件的名称,如果对应公共事件不存在则跳过。 +- 否则,如果写了`"loc": [x,y]`,则会插入另一个地点的事件 + - loc为另一个地点的坐标 + - floorId可选,代表另一个地点所在的楼层;如果不写则默认为当前层。 + - 从V2.6开始,还可以传可选的which,可以为`afterBattle`/`afterGetItem`/`afterOpenDoor`,代表插入该点的战后/获得道具后/开门后事件。 + +和`type:trigger`不同点如下: +- `type:trigger`只能指定当前楼层且会改变当前点坐标,`type:insert`可以跨楼层且不会改变当前点坐标。 +- `type:trigger`如果不设置`keep:true`则还会结束当前事件,`type:insert`而是将另一个点的事件插入到当前事件列表中执行。 +- `type:trigger`可以触发系统事件,`type:insert`只能触发该点的自定义事件或战后事件等。 + +插入的事件执行完毕后,会继续执行接下来的内容。 + +从V2.6开始,插入事件允许传参。如果需要传参,则需要增加一个`args`数组。 + +例如: `"args": [1,"flag:abc","status:atk+status:def"]` 传入了三个参数。 + +系统会自动把`flag:arg1`设置为第一个参数数值,`flag:arg2`设置为第二个参数数值,等等。 + +(`flag:arg0`则会被置为公共事件名称,或者插入的点的坐标) + +即可在事件中直接取用`flag:arg1`等等来获得各项参数值!。 ### revisit:立即重启当前事件 revisit和trigger完全相同,只不过是立刻触发的还是本地点的事件 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "revisit"}, // 立即触发本事件,等价于{"type": "trigger", "loc": [x,y]} +[ + {"type": "revisit"}, // 立即触发本事件,等价于 {"type": "trigger", "loc": [x,y]} "执行revisit后,这段文字将不会再被显示" ] ``` @@ -453,7 +663,7 @@ revisit常常使用在一些商人之类的地方,当用户购买物品后不 例如玩家点击商人的"离开"选项,则可以调用exit返回游戏。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "exit" }, // 立即结束事件并恢复游戏,一切列表中的事件都将不再被执行 "执行exit后,这段文字将不会再被显示" ] @@ -464,10 +674,11 @@ 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", "loc": [2,1],setVa "number": 121}, // 省略floorId则默认为本层 {"type": "setBlock", "number": 57}, // loc也可省略,默认为当前点 + {"type": "setBlock", "number": "yellowDoor"}, // 从V2.6开始也允许写图块ID ] ``` @@ -477,6 +688,8 @@ loc为可选的,表示要更改地图块的坐标。如果忽略此项,则 number为**要更改到的数字**,有关“数字”的定义详见参见[素材的机制](personalization#素材的机制)。 +从V2.6开始,number也允许写图块的ID,将自动转成对应的数字。 + 图块更改后: - 其启用/禁用状态不会发生任何改变。原来是启用还是启用,原来是禁用还是禁用。 @@ -485,12 +698,96 @@ number为**要更改到的数字**,有关“数字”的定义详见参见[素 图块更改往往与[同一个点的多事件处理](#同一个点的多事件处理)相关。 +### hideFloorImg:隐藏楼层贴图 + +使用`{"type":"hideFloorImg"}`可以隐藏某个楼层的贴图。 + +有关贴图说明请参见[使用自己的图片作为某层楼的背景/前景素材](personalization#使用自己的图片作为某层楼的背景前景素材)。 + +``` js +[ + {"type": "hideFloorImg", "loc": [3,6], "floorId": "MT1"}, // 隐藏[3,6]的贴图 + {"type": "hideFloorImg", "loc": [3,6]}, // 如果是当前层,则可以省略floorId项 + {"type": "hideFloorImg", "loc": [[3,6],[2,9],[1,2]]} // 我们也可以同时隐藏多个贴图。 +] +``` + +loc为要隐藏的贴图的左上角坐标,可以简单的写[x,y]代表一个点,也可以写个二维数组[[x1,y1],[x2,y2],...]来同时显示多个点。 + +如果同时存在若干个贴图都是是该坐标为左上角,则这些贴图全部会被隐藏。 + +floorId为目标点的楼层,如果是当前楼层可以忽略不写。 + +### showFloorImg:显示楼层贴图 + +使用`{"type":"showFloorImg"}`可以显示某个楼层的贴图。 + +其做法和参数,和隐藏贴图是完全一致的。 + +``` js +[ + {"type": "showFloorImg", "loc": [3,6], "floorId": "MT1"}, // 显示[3,6]的贴图 +] +``` + +### hideBgFgMap:隐藏楼层的某些背景或前景图块 + +使用`{"type":"hideBgFgMap"}`可以隐藏某个楼层的背景或前景图块。 + +从V2.4.1开始,允许绘制三层图层(背景层,事件层和前景层)。使用`hideBgFgMap`可以隐藏背景或前景层中的图块。 + +``` js +[ + {"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]]} // 我们也可以同时隐藏多个贴图。 +] +``` + +name为必选的,且只能是`bg`和`fg`之一,分别代表背景图层和前景图层。 + +loc为要隐藏的贴图的左上角坐标,可以简单的写[x,y]代表一个点,也可以写个二维数组[[x1,y1],[x2,y2],...]来同时显示多个点。 + +floorId为目标点的楼层,如果是当前楼层可以忽略不写。 + +### showBgFgMap:显示楼层贴图 + +使用`{"type":"showFloorImg"}`可以显示某个楼层的贴图。 + +其做法和参数,和隐藏贴图是完全一致的。 + +``` js +[ + {"type": "showBgFgMap", "name": "bg", "loc": [3,6], "floorId": "MT1"}, // 显示MT1层[3,6]的前景层图块 +] +``` + +### setBgFgBlock:设置某个背景或前景层图块 + +我们可以采用 `{"type": "setBgFgBlock"}` 来改变某个背景或前景层地图块。 + +``` js +[ + {"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也可省略,默认为当前点 +] +``` + +name为必选的,且只能是`bg`和`fg`之一,分别代表背景层和前景层。 + +floorId为可选的,表示要更改的目标楼层。如果忽略此项,则默认为当前楼层。 + +loc为可选的,表示要更改地图块的坐标。如果忽略此项,则默认为当前事件点。 + +图块更改后,其隐藏/显示状态不会发生任何改变,即原来是隐藏还是会不显示。可以使用`showBgFgMap`事件将其显示出来。 + ### setHeroIcon:更改角色行走图 使用`{"type": "setHeroIcon"}`可以更改角色行走图。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setHeroIcon", "name": "hero2.png"}, // 将勇士行走图改成hero2.png;必须在全塔属性的images中被定义过。 {"type": "setHeroIcon"}, // 如果不加name则恢复最初默认状态 {"type": "setValue", "name": "status:name", "value": "'可绒'"}, // 修改勇士名;请注意value必须加单引号。 @@ -509,6 +806,16 @@ name是可选的,代表目标行走图的文件名。 如果你需要刷新状态栏和地图显伤,只需要简单地调用 `{"type": "update"}` 即可。 +### hideStatusBar:隐藏状态栏 + +使用`{"type": "hideStatusBar"}`可以隐藏状态栏。读档或重新开始游戏时,状态栏会重新显示。 + +可以添加`"toolbox": true`来不隐藏竖屏模式下的工具栏。 + +### showStatusBar:显示状态栏 + +使用`{"type": "showStatusBar"}`会重新显示状态栏。 + ### updateEnemys:更新怪物数据 使用 `{"type": "updateEnemys"}` 可以动态修改怪物数据。 @@ -522,51 +829,45 @@ name是可选的,代表目标行走图的文件名。 基本写法:`{"type": "sleep", "time": xxx}` ,其中xxx为指定的毫秒数。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "sleep", "time": 1000}, // 等待1000ms - "等待1000ms后才开始执行这个事件" + "等待1000ms后才开始执行这个事件", + {"type": "sleep", "time": 2000, "noSkip": true}, // 等待2000毫秒,且不可被跳过 ] ``` +默认的等待事件可以被Ctrl跳过,下面两种情况下不可呗跳过: + - 加上`"noSkip": true`后 + - 当前存在尚未执行完毕的异步事件。 + ### battle:强制战斗 调用battle可强制与某怪物进行战斗(而无需去触碰到它)。 -例如,《宿命的旋律》中,一区有个骷髅队长,当你拿了它周围三个物品时,就会立刻触发强制战斗事件。这时候就可以用`{"type": "battle"}` 实现。 - -其基本写法是: `{"type": "battle", "id": xxx}`,其中xxx为怪物ID。 - ``` js -"10,4": [ // 开门后走进去的事件:强制战斗 - "\t[blackKing]你终于还是来了。", - "\t[hero]放开我们的公主!", - "\t[blackKing]如果我不愿意呢?", - "\t[hero]无需多说,拔剑吧!", - {"type": "battle", "id": "blackKing"}, // 强制战斗 - // 如果战斗失败直接死亡,不会继续触发接下来的剧情。 - {"type": "hide", "loc": [10,2]}, // 战斗后需要手动使怪物消失;战斗后不会引发afterBattle事件。 - {"type": "openDoor", "loc": [8,7]}, // 开门口的机关门 - "\t[blackKing]没想到你已经变得这么强大了... 算你厉害。\n公主就交给你了,请好好对她。", - {"type": "hide"} // 隐藏本事件 +[ + {"type": "battle", "id": "blackKing"}, // 强制战斗黑衣魔王 + {"type": "battle", "loc": [2,3]}, // 强制战斗(2,3)点的怪物 ], ``` -上面就是样板层中右上角的强制战斗例子。 +从V2.6开始,有两种写法: +- 写id,则视为和空降怪物进行强制战斗,不会执行一些战后事件,也不会隐藏任何点。 +- 写loc(可选,不填默认当前点),则视为和某点怪物进行强制战斗,会隐藏该点并且插入该点战后事件执行。 -如果强制战斗失败,则会立刻生命归0并死亡,调用lose函数,接下来的事件不会再被执行。 +强制战斗不会自动存档,如果战斗失败则立刻死亡。 -打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](#加点事件)。 - -强制战斗没有指定loc的选项,因此战斗后需要调用hide使怪物消失(如果有必要)。 +值得注意的是,如果是和某个点的怪物强制战斗,并且该点存在战后事件,执行时会**修改当前点坐标**为目标点。 ### openDoor:开门 调用`{"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, "async": true} // 打开此门需要钥匙,异步执行 ] ``` @@ -576,6 +877,40 @@ loc指定门的坐标,floorId指定门所在的楼层ID。如果是当前层 如果loc所在的点既不是门也不是墙壁,则忽略本事件。 +needKey是可选的,如果设置为true则需要钥匙才能打开此门。如果没有钥匙则跳过此事件。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +!> needKey仅对当前楼层开门有效!跨楼层的门仍然不需要钥匙即可打开,如有需求请自行判定。 + +值得注意的是,如果该点存在开门事件,执行时会**修改当前点坐标**为目标点。 + +### closeDoor:关门 + +从V2.6开始提供了关门事件`{"type": "closeDoor"}`,拥有关门动画和对应的音效。 + +``` js +[ + {"type": "closeDoor", "id": "yellowDoor", "loc": [3,6]}, // 给(3,6)点关上黄门 + {"type": "closeDoor", "id": "specialDoor"}, // 不写loc则视为当前点 +] +``` + +id为你要关门的ID,需要是一个合法的门,系统默认只支持如下几种: + +``` +yellowDoor, blueDoor, redDoor, greenDoor, specialDoor, steelDoor, +yellowWall, blueWall, whiteWall +``` + +如果需要自己添加门,请参考[新增门和对应的钥匙](personalization#新增门和对应的钥匙)。 + +loc可选,为要关的位置,不填默认为当前点。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +关门事件需要保证该点是空地,否则将无视此事件。 + ### changeFloor:楼层切换 在事件中也可以对楼层进行切换。一个比较典型的例子就是TSW中,勇士在三楼的陷阱被扔到了二楼,就是一个楼层切换事件。 @@ -583,7 +918,7 @@ loc指定门的坐标,floorId指定门所在的楼层ID。如果是当前层 changeFloor的事件写法大致如下。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "changeFloor", "floorId": "sample0","loc": [10, 10], "direction": "left", "time": 1000 }, //后面几项依次为楼层id,楼层位置(这两项为必填);勇士方向可选,切换时间也是可选。 ] @@ -599,22 +934,38 @@ time为可选的,指定的话将作为楼层切换动画的时间。 **如果time指定为小于100,则视为没有楼层切换动画。** -!> **changeFloor到达一个新的楼层,将不会执行firstArrive事件!如有需求请在到达点设置自定义事件,然后使用type: trigger立刻调用之。** - -### 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;此时等价于当前勇士转向到某个方向。 ] ``` +### useItem:使用道具 + +调用`{"type": "useItem"}`可以使用一个道具。 + +``` js +[ + {"type": "changePos", "id": "pickaxe"}, // 尝试使用破 + {"type": "changePos", "id": "bomb"}, // 尝试使用炸 + {"type": "changePos", "id": "centerFly"} // 尝试使用飞 +] +``` + +使用道具事件会消耗对应的道具。 + +如果当前不可使用该道具(如没有,或者达不到使用条件),则会进行提示并跳过本事件。 + +不可使用“怪物手册”(请使用【呼出怪物手册】事件)或楼层传送器(如果[覆盖楼传事件](personalization#覆盖楼传事件)则可忽视本项)。 + ### openShop:打开一个全局商店 使用openShop可以打开一个全局商店。有关全局商店的说明可参见[全局商店](#全局商店)。 @@ -628,7 +979,7 @@ time为可选的,指定的话将作为楼层切换动画的时间。 使用 `{"type": "follow"}` 可以让一个npc加入跟随。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "follow", "name": "npc.png"}, // 将 npc.png 这个行走图加入跟随 {"type": "follow", "name": "hero.png"}, // 再将另一个行走图加入跟随 ] @@ -643,7 +994,7 @@ name所指定的图片必须存在,在全塔属性中的images中被定义过 使用 `{"type": "unfollow"}` 来取消一个跟随。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "unfollow", "name": "npc.png"}, // 将 npc.png 这个行走图取消跟随 {"type": "follow"}, // 取消所有跟随 ] @@ -655,9 +1006,13 @@ name为可选的,是要取消跟随的行走图文件名。 如果name省略,则会取消所有的跟随效果。 -### viberate:画面震动 +### vibrate:画面震动 -使用 `{"type": "viberate", "time": 2000}` 可以造成画面震动效果,后面time可以指定震动时间。 +使用 `{"type": "vibrate", "time": 2000, "async": true}` 可以造成画面震动效果。 + +time可以指定震动时间,默认是2000毫秒。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 ### animate:显示动画 @@ -666,80 +1021,106 @@ name为可选的,是要取消跟随的行走图文件名。 有关动画的详细介绍可参见[动画和天气系统](element#动画和天气系统)。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "animate", "name": "yongchang", "loc": [1,3]}, // 在(1,3)显示“咏唱魔法”动画 {"type": "animate", "name": "zone", "loc": "hero"}, // 在勇士位置显示“领域”动画 - {"type": "animate", "name": "hand"} // 可以不指定loc,则默认为当前事件点 + {"type": "animate", "name": "hand"}, // 可以不指定loc,则默认为当前事件点 + {"type": "animate", "async": true}, // 异步,不等待动画绘制完毕 ] ``` -name为动画名,**请确保动画在main.js中的this.animates中被定义过。** +name为动画名,**请确保动画在全塔属性中的animates中被定义过。** loc为动画的位置,可以是`[x,y]`表示在(x,y)点显示,也可以是字符串`"hero"`表示在勇士点显示。 loc可忽略,如果忽略则显示为事件当前点。 -在动画播放结束后才会继续执行下一个事件。 +如果async指定为true,则不会等待动画绘制完毕,立刻执行下个事件。 + +否则,在动画播放结束后才会继续执行下一个事件。 ### showImage:显示图片 我们可以使用 `{"type": "showImage"}` 来显示一张图片。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "showImage", "name": "bg.jpg", "loc": [231,297]}, // 在(231,297)显示bg.jpg - {"type": "showImage", "name": "1.png", "loc": [109,167]}, // 在(109,167)显示1.png - {"type": "showImage"} // 如果不指定name则清除所有图片。 +[ + {"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毫秒,异步执行;这张图片将被上一张遮盖 + {"type": "showImage", "code": 10, "image": "hero.png", "sloc": [100,100,100,100], "loc": [0,0,100,100], "opacity": 1, "time": 0} // 截取原图的一部分绘制到画布上的一部分。 ] ``` -name为图片名。**请确保图片在data.js中的images中被定义过。** +code为图片编号,如果两张图片重叠,编号较大会覆盖编号较小的。该值需要在1~50之间。 -loc为图片左上角坐标,以像素为单位进行计算。 +image为图片名。**请确保图片在全塔属性中的images中被定义过。** -如果不指定name则清除所有显示的图片。 +sloc为可选项;如果设置了则是个2或4元组,代表裁剪原始图片的左上角像素位置和宽高。 -调用show/hide/move/animate等几个事件同样会清除所有显示的图片。 +loc为2或4元组,代表要绘制的画布上的左上角像素位置和宽高。 -### animateImage:图片淡入淡出 +opacity为图片不透明度,在0~1之间,默认值为1,即不透明。 -我们还可以使用 `{"type": "animateImage"}` 来造成显示图片的淡入淡出效果。 +time为渐变时间,默认值为0,即不渐变直接显示。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +### showTextImage:显示文本化图片 + +我们可以使用 `{"type": "showTextImage"}` 以图片的方式显示文本。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "animateImage", "action": "show", "name": "bg.jpg", "loc": [231,297], "time": 500}, // 在(231,297)淡入bg.jpg,动画时间500ms - {"type": "animateImage", "action": "hide", "name": "1.png", "loc": [109,167], "time": 300}, // 在(109,167)淡出1.png,动画时间300ms +[ + {"type": "showTextImage", "code": 1, "text": "第一排\n第二排\n\n空行后的一排", "loc": [231,297], "opacity": 1, "time" : 0}, // 在(231,297)显示"第一排\n第二排\n\n空行后的一排" ] ``` -action为淡入还是淡出,`show`为淡入,`hide`会淡出。 +code为图片编号,如果两张图片重叠,编号较大会覆盖编号较小的。该值需要在1~50之间。 -name为图片名。**请确保图片在data.js中的images中被定义过。** +text为要显示的文本。默认行宽为416。 loc为图片左上角坐标,以像素为单位进行计算。 -time为淡入淡出的时间,如果是0则忽略此项。 +opacity为图片不透明度,在0~1之间,默认值为1,即不透明。 -!> 淡入淡出图片只是会在顶层绘制“淡入”和“淡出”效果,动画结束即消失,并不会实际对图片的显示造成影响。请与showImage事件合用。 +lineHeight为可选项,代表行距。默认为1.4。 -如果多张图片的淡入淡出可以采用以下方式(仅供参考): +time为渐变时间,默认值为0,即不渐变直接显示。 -假设我现在已经有了`1.jpg`显示在屏幕上: -- 淡入显示`2.png`:调用`animateImage`淡入图片,然后立刻调用`showImage`显示图片。 -- 淡出`1.png`:清除所有图片,`showImage`显示`2.png`,然后调用`animateImage`淡出`1.jpg` +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +文本通过图片的方式显示后,即视为一张正常图片,可以被清除或者移动。 + +### hideImage:清除图片 + +我们可以使用 `{"type": "hideImage"}` 来清除一张图片。 + +``` js +[ + {"type": "hideImage", "code": 1, "time" : 0}, // 使1号图片消失 + {"type": "hideImage", "code": 12, "time" : 1000}, // 使12号图片渐变消失,时间为1000毫秒 +] +``` + +time为渐变时间,默认值为0,即不渐变直接消除。 + +code为显示图片时输入的图片编号。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 ### showGif:显示动图 我们可以使用 `{"type": "showGif"}` 来显示一张图片。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "showGif", "name": "timg.gif", "loc": [231,297]}, // 在(231,297)显示一张动图 {"type": "showGif"} // 如果不指定name则清除所有动图。 ] ``` -name为图片名。**请确保图片在data.js中的images中被定义过。** +name为图片名。**请确保图片在全塔属性中的images中被定义过。** loc为动图左上角坐标,以像素为单位进行计算。 @@ -747,33 +1128,35 @@ loc为动图左上角坐标,以像素为单位进行计算。 ### moveImage:图片移动 -我们可以使用 `{"type": "moveImage"}` 来造成图片移动效果。 +我们可以使用 `{"type": "moveImage"}` 来造成图片移动,淡入淡出等效果。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "moveImage", "name": "bg.jpg", "from": [231,297], "to": [22,333], "time": 500}, +[ + {"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,异步执行 ] ``` -name为图片名。**请确保图片在data.js中的images中被定义过。** +code为图片编号。该值需要在1~50之间。 -from为起点图片左上角坐标,以像素为单位进行计算。 +to为终点图片左上角坐标,以像素为单位进行计算,不填写则视为当前图片位置。 -to为终点图片左上角坐标,以像素为单位进行计算。 +opacity为完成时图片不透明度,移动过程中逐渐变化。在0~1之间。 time为总移动的时间。 -!> 移动图片只是会在顶层绘制“移动”效果,动画结束即消失,并不会实际对图片的显示造成影响。请与showImage事件合用。 +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]}, // 更改画面色调为纯黑,不透明度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则恢复原样。 ] ``` @@ -785,12 +1168,35 @@ color为需要更改画面色调的颜色。它是一个数组,分别指定目 time为可选的,如果指定,则会作为更改画面色调的时间。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + +### screenFlash:画面闪烁 + +我们可以使用 `{"type": "screenFlash"}` 来进行画面闪烁。 + +``` js +[ + {"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毫秒,闪烁两次且异步执行 +] +``` + +color为闪光的颜色。它是一个数组,分别指定目标颜色的R,G,B,A值。 +- 常见RGB颜色: 纯黑[0,0,0],纯白[255,255,255],纯红[255,0,0],等等。 +A即闪光的不透明度,为一个0到1之间的数,值越高闪烁效果越强。默认为1 + +time为闪烁时间,默认值为500 + +times为闪烁次数,两次闪烁会连续进行,默认值为1 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### setWeather:更改天气 我们可以使用 `{"type": "setWeather"}` 来更改天气。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "setWeather", "name": "rain", "level": 6}, // 更改为雨天,强度为6级 {"type": "setWeather", "name": "snow", "level": 3}, // 更改为雪天,强度为3级 {"type": "setWeather"} // 更改回晴天 @@ -799,6 +1205,8 @@ time为可选的,如果指定,则会作为更改画面色调的时间。 name为天气选项。目前只支持`rain`和`snow`,即雨天和雪天。 +从V2.5.3开始,也支持雾天`fog`。 + level为天气的强度等级,在1-10之间。1级为最弱,10级为最强。 如果想改回晴天则直接不加任何参数。 @@ -812,11 +1220,10 @@ 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 - ], "keep": true }, // keep可选,如果为true则不消失,否则渐变消失 + "right", "right", "down" // 向右两格,向下一格 + ], "keep": true, "async":true }, // keep可选,如果为true则不消失,否则渐变消失;async可选,如果为true则异步执行。 ] ``` @@ -824,9 +1231,7 @@ time选项必须指定,为每移动一步所需要用到的时间。 loc为需要移动的事件位置。可以省略,如果省略则移动本事件。 -steps为一个数组,其每一项为一个 `{"direction" : xxx, "value": n}`,表示该步是向xxx方向移动n步。 - -如果只移动一步可以直接简单的写方向字符串(`up/left/down/right`)。 +steps为一个数组,其每一项是`up, down, left, right`之一,表示这一步应该朝哪个方向走。 keep为一个可选项,代表该事件移动完毕后是否消失。如果该项指定了并为true,则移动完毕后将不消失,否则以动画效果消失。 @@ -841,7 +1246,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]为目标地点 @@ -852,7 +1257,9 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 } ``` -即,在移动的到达点指定一个初始禁用的相同NPC,然后move事件中指定immediateHide使立刻消失,并show该到达点坐标使其立刻显示(看起来就像没有消失),然后就可以触发目标点的事件了。 +即,在移动的到达点指定一个事件,然后move事件中指定"keep":true,然后就可以触发目标点的事件了。 + +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 ### moveHero:移动勇士 @@ -862,17 +1269,20 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 ``` js "x,y": [ // 实际执行的事件列表 - {"type": "moveHero", "time": 750, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 - {"direction": "right", "value": 2},// 这里steps 的效果为向右移动2步,在向下移动一步并消失 - "down" // 如果该方向上只移动一步则可以这样简写,效果等价于上面value为1 + {"type": "moveHero", "time": 750, "async": true, "steps": [// 动画效果,time为移动速度(比如这里每750ms一步),steps为移动数组 + "down", "right", "forward", "backward" // 向下、右、前、后各走一步 ]}, ] ``` -可以看到,和上面的move事件几乎完全相同,除了不能指定loc,且少了immediateHide选项。 +可以看到,和上面的move事件几乎完全相同,除了不能指定loc,且少了keep选项。 + +勇士的steps也允许`forward`和`backward`,即前进和后退。 不过值得注意的是,用这种方式移动勇士的过程中将无视一切地形,无视一切事件,中毒状态也不会扣血。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### jump:让某个NPC/怪物跳跃 如果我们需要移动某个NPC或怪物,可以使用`{"type": "jump"}`。 @@ -880,8 +1290,8 @@ keep为一个可选项,代表该事件移动完毕后是否消失。如果该 下面是该事件常见的写法: ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "jump", "from": [3,6], "to": [2,1], "time": 750, "keep": true}, +[ + {"type": "jump", "from": [3,6], "to": [2,1], "time": 750, "keep": true, "async": true}, ] ``` @@ -895,6 +1305,8 @@ keep为一个可选项,同上代表该跳跃完毕后是否不消失。如果 如果指定了`"keep": true`,则相当于会在目标地点触发一个`setBlock`事件;如需能继续对话交互请在目标地点再写事件。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### jumpHero:跳跃勇士 如果我们需要跳跃勇士,可以使用`{"type": "jumpHero"}`。 @@ -902,8 +1314,8 @@ keep为一个可选项,同上代表该跳跃完毕后是否不消失。如果 下面是该事件常见的写法: ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "jump", "loc": [3,6], "time": 750}, +[ + {"type": "jump", "loc": [3,6], "time": 750, "async": true}, ] ``` @@ -911,6 +1323,8 @@ loc为目标坐标,可以忽略表示原地跳跃(请注意是原地跳跃 time选项为该跳跃所需要用到的时间。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### playBgm:播放背景音乐 使用playBgm可以播放一个背景音乐。 @@ -919,7 +1333,7 @@ time选项为该跳跃所需要用到的时间。 值得注意的是,额外添加进文件的背景音乐,需在main.js中this.bgms里加载它。 -目前支持mp3/ogg/wav/mid等多种格式的音乐播放。 +目前支持mp3/ogg/wav等多种格式的音乐播放。 有关BGM播放的详细说明参见[背景音乐](element#背景音乐) @@ -931,6 +1345,20 @@ time选项为该跳跃所需要用到的时间。 使用`{"type": "resumeBgm"}`可以恢复背景音乐的播放。 +### loadBgm:预加载一个背景音乐 + +使用loadBgm可以预加载一个背景音乐。 + +使用方法:`{"type": "loadBgm", "name": "bgm.mp3"}` + +有关BGM播放的详细说明参见[背景音乐](element#背景音乐) + +### freeBgm:释放一个背景音乐的缓存 + +使用freeBgm可以预加载一个背景音乐。 + +使用方法:`{"type": "freeBgm", "name": "bgm.mp3"}` + ### playSound:播放音效 使用playSound可以立刻播放一个音效。 @@ -939,22 +1367,34 @@ time选项为该跳跃所需要用到的时间。 值得注意的是,如果是额外添加进文件的音效,则需在main.js中this.sounds里加载它。 +从V2.6开始,也可以加`"stop": true`来停止之前正在播放的音效。 + +### stopSound:停止所有音效 + +使用`{"type": "stopSound"}`可以停止所有音效。 + +这在人物对话音效时很有用。 + ### setVolume:设置音量 使用setVolume可以设置音量大小。 -使用方法: `{"type": "setVolume", "value": 90, "time": 500}` +使用方法: `{"type": "setVolume", "value": 90, "time": 500, "async": true}` -value为音量大小,在0到100之间,默认为100。设置后,BGM和SE都将使用该音量进行播放。 +value为音量大小,在0到100之间,默认为100。设置后,BGM将使用该音量进行播放。SE的音量大小不会发生改变。 可以设置time为音量渐变时间。 +async可选,如果为true则会异步执行(即不等待当前事件执行完毕,立刻执行下一个事件)。 + ### win:获得胜利 `{"type": "win", "reason": "xxx"}` 将会直接调用events.js中的win函数,并将reason作为结局传入。 该事件会显示获胜页面,并重新游戏。 +可以增加`"norank": 1`来表示该结局不计入榜单。 + !> 如果`reason`不为空,则会以reason作为获胜的结局! ### lose:游戏失败 @@ -963,13 +1403,39 @@ value为音量大小,在0到100之间,默认为100。设置后,BGM和SE都 该事件会显示失败页面,并重新开始游戏。 -### input:接受用户输入 +### restart:直接回到标题界面 -使用`{"type": "input"}`可以接受用户的输入。 +`{"type": "restart"}` 会中断一切执行的事件,并直接直接返回标题界面。 + +### callBook:呼出怪物手册 + +`{"type": "callBook"}` 可以呼出怪物手册,玩家可以自由查看当前楼层怪物数据和详细信息。 + +返回游戏后将继续执行后面的事件。没有怪物手册或在录像播放中,则会跳过本事件。 + +### callSave:呼出存档界面 + +`{"type": "callSave"}` 可以呼出存档页面并允许玩家存一次档。 + +在玩家进行一次存档,或者直接点返回游戏后,将接着执行后面的事件。录像播放将会跳过本事件。 + +### autoSave:自动存档 + +`{"type": "autoSave"}` 可以立刻进行一次自动存档。录像播放不会跳过本事件。 + +### callLoad:呼出读档界面 + +`{"type": "callLoad"}` 可以呼出读档页面并允许玩家进行读档。 + +如果玩家没有进行读档而是直接返回游戏,则会继续执行后面的事件。录像播放将会跳过本事件。 + +### input:接受用户输入数字 + +使用`{"type": "input"}`可以接受用户的输入的数字。 ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "input", "text": "请输入一个数"}, // 显示一个弹窗让用户输入内容 +[ + {"type": "input", "text": "请输入一个数"}, // 显示一个弹窗让用户输入数字 "你刚刚输入的数是${flag:input}" // 输入结果将被赋值为flag:input ] ``` @@ -982,6 +1448,25 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 输入得到的结果将被赋值给flag:input,可以供后续if来进行判断。 +### input2:接受用户输入文本 + +类似于input事件,使用`{"type": "input2"}`可以接受用户的输入的文本。 + +``` js +[ + {"type": "input2", "text": "请输入你的ID"}, // 显示一个弹窗让用户输入文本 + "你好,${flag:input},欢迎来到本塔" // 输入结果将被赋值为flag:input +] +``` + +text为提示文字,可以在这里给输入提示文字。这里同样可以使用${ }来计算表达式的值。 + +当执行input2事件时,将显示一个弹窗,并提示用户输入一个内容。 + +该事件可以接收任何形式的文本输入,包括中文,空格,标点符号等等。如果用户点击取消按钮,则视为空字符串。 + +输入得到的结果也将被赋值给flag:input,可以供后续使用。 + ### if:条件判断 使用`{"type": "if"}`可以对条件进行判断,根据判断结果将会选择不同的分支执行。 @@ -989,7 +1474,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "if", "condition": "...", // 测试某个条件 "true": [ // 条件成立则执行true里面的事件 @@ -1010,7 +1495,7 @@ text为提示文字,可以在这里给输入提示文字。这里同样可以 例如下面这个例子,每次将检查你的攻击力是否大于500,不是的场合将给你的攻击力加100点。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "if", "condition": "status:atk>500", // 判断攻击力是否大于500 "true": [ // 条件成立则执行true里面的事件 "你的攻击力已经大于500了!", @@ -1018,7 +1503,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后结束 @@ -1028,11 +1513,97 @@ 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语句内的内容执行完毕后将接着其后面的语句继续执行。 + +### switch:多重条件分歧 + +使用`{"type": "switch"}`可以比较判别值和不同分支的条件,根据判断结果选择不同的分支执行。 + +其大致写法如下: + +``` js +[ + {"type": "switch", "condition": "...", // 计算某个表达式 + "caseList": [ + {"case": "a", "action": [// 若表达式的值等于a则执行该处事件 + + ], + {"case": "b", "nobreak": true, "action": [// 若表达式的值等于b则执行该处事件,不跳出 + + ], + {"case": "default", "action": [ // 没有条件成立则执行该处里的事件 + + ] + ] + }, +] +``` + +我们可以在condition中给出一个表达式(能将`status:xxx`, `item:xxx`, `flag:xxx`来作为参数),并计算它的值 + +如果某条件中的值与其相等,则将执行其对应的列表事件内容。 + +如果没有符合的值,则将执行`default`中的列表事件内容。 + +nobreak是可选的,如果设置,则在当前条件满足并插入事件后,不跳出多重分歧,而是继续判定下一个条件。 + +例如下面这个例子,将检查当前游戏难度并赠送不同属性。 + +``` js +[ + {"type": "switch", "condition": "flag:hard", // 判断当前游戏难度 + "caseList": [ + {"case": "0", "action": [// 若表达式的值等于0则执行该处事件 + "当前为简单难度,赠送100点攻防!", + {"type": "setValue", "name": "status:atk", "value": "status:atk+100"}, + {"type": "setValue", "name": "status:def", "value": "status:def+100"}, + ], + {"case": "1", "action": [// 若表达式的值等于1则执行该处事件 + "当前为普通难度,赠送50点攻防!", + {"type": "setValue", "name": "status:atk", "value": "status:atk+50"}, + {"type": "setValue", "name": "status:def", "value": "status:def+50"}, + ], + {"case": "default", "action": [ // 其他难度下不赠送属性 + + ] + ] + }, +] +``` + +需要额外注意的几点: + +- 各个条件分支的判断是顺序执行的,因此若多个分支的条件都满足,将只执行最靠前的分支。 + - 同理,请不要在`default`分支后添加分支,这些分支将不可能被执行。 +- `default`分支并不是必要的,如果删除,则在没有满足条件的分支时将不执行任何事件。 +- 即使某个场合不执行事件,对应的action数组也需要存在,不过简单的留空就好。 +- 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:给用户提供选项 choices是一个很麻烦的事件,它将弹出一个列表供用户进行选择。 @@ -1044,16 +1615,16 @@ choices是一个很麻烦的事件,它将弹出一个列表供用户进行选 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 - {"type": "choices", "text": "...", // 提示文字 +[ + {"type": "choices", "text": "...", // 提示文字 "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执行的事件 ]}, ] @@ -1061,81 +1632,54 @@ choices是一个很麻烦的事件,它将弹出一个列表供用户进行选 ] ``` -其中最外面的"text"为提示文本。同上面的`"type":"text"`一样,支持`${}`表达式的计算,和\t显示名称、图标。text可省略,如果省略将不显示任何提示文字。 +其中最外面的"text"为提示文本。同上面的`"type":"text"`一样,支持`${}`表达式的计算,和\t显示名称、图标,\r更改颜色。text可省略,如果省略将不显示任何提示文字。 choices为一个数组,其中每一项都是一个选项列表。 -每一项的text为显示在屏幕上的选项名,也支持${}的表达式计算,但不支持`\t[]`的显示。action为当用户选择了该选项时将执行的事件。 +每一项的text为显示在屏幕上的选项名,也支持${}的表达式计算,但不支持`\t[]`的显示。 + +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}金币)", "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}金币)", "action": [ // 第二个选项:蓝钥匙 - // 逻辑和上面黄钥匙完全相同,略 - ]}, - {"text": "红钥匙(${36+4*flag:woman_times}金币)", "action": [ // 第三个选项:红钥匙 - // 逻辑和上面黄钥匙完全相同,略 - ]}, - {"text": "离开", "action": [ // 第四个选项:离开 - {"type": "exit"} // 立刻结束当前事件 - ]} - ] - } - ] - } + "no": [ + // 点击取消时执行的事件 ] }, - {"type": "setValue", "name": "flag:woman_times", "value": "flag:woman_times+1"}, // 增加该商人的访问次数。 - {"type": "revisit"} // 立即重新开始这个事件 -], +] ``` -### while:循环处理 +text为必填项,代表提示的文字,支持${}的表达式计算和\n的手动换行。 + +text暂时不支持自动换行、变色\r、图标绘制\i等效果。如有需求请使用choices事件。 + +default可选,如果为true则显示选择项时默认选中【确定】,否则默认选中【取消】。 + +yes和no均为必填项,即用户点击确认或取消后执行的事件。 + +### while:前置条件循环 从2.2.1样板开始,我们提供了循环处理(while事件)。 其大致写法如下: ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "while", "condition": "...", // 循环测试某个条件 "data": [ // 条件成立则执行data里面的事件 @@ -1153,10 +1697,10 @@ choices为一个数组,其中每一项都是一个选项列表。 下面是一个输出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秒 ] @@ -1164,6 +1708,12 @@ choices为一个数组,其中每一项都是一个选项列表。 ] ``` +### dowhile:后置条件循环 + +`type:dowhile`可以制作一个后置条件循环。 + +其写法与参数和`type:while`完全一致,不过与其不同的是,会先执行一次事件列表,再对条件进行判定,就和C/C++中的 `do {...} while (...);` 语法一样。 + ### break:跳出循环 使用 `{"type": "break"}` 可以跳出当前循环。 @@ -1177,10 +1727,10 @@ choices为一个数组,其中每一项都是一个选项列表。 上面的输出例子也可以这么写: ``` 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 @@ -1200,15 +1750,14 @@ choices为一个数组,其中每一项都是一个选项列表。 使用 `{"type": "wait"}` 可以等待用户进行操作(如点击、按键等)。 当用户执行操作后: -- 如果是键盘的按键操作,则会将flag:type置为0,并且把flag:keycode置为刚刚按键的keycode。 -- 如果是屏幕的点击操作,则会将flag:type置为1,并且设置flag:x和flag:y为刚刚的点击坐标。 +- 如果是键盘的按键操作,则会将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"}, // 等待用户操作 @@ -1221,7 +1770,7 @@ choices为一个数组,其中每一项都是一个选项列表。 } ], "false": [ // flag:type==1,鼠标点击 - "你当前点击屏幕了,坐标是[${flag:x},${flag:y}]", + "你当前点击屏幕了,位置坐标是[${flag:x},${flag:y}],像素坐标是[${flag:px},${flag:py}]", {"type": "if", "condition": "flag:x==6 && flag:y==6", // 点击(6,6) "true": [{"type": "break"}], // 跳出循环 "false": [] @@ -1231,15 +1780,27 @@ choices为一个数组,其中每一项都是一个选项列表。 ] } ] - ``` +### waitAsync:等待所有异步事件执行完毕 + +上面有很多很多的异步事件(也就是执行时不等待执行完毕)。 + +由于录像是加速播放,且会跳过`{"type":"sleep"}`(等待X毫秒)事件;因此异步行为很有可能导致录像播放出错。 + +例如,异步移动一个NPC去某格,然后等待X毫秒,再勇士走过去对话; +但是录像播放中,等待X毫秒的行为会被跳过,因此勇士可能走过去时异步还未执行完成,导致录像出错。 + +我们可以使用`{"type":"waitAsync"}`来等待所有异步事件执行完毕。 + +该事件会进行等待,直到所有可能的异步事件(异步动画除外)执行完毕。 + ### function: 自定义JS脚本 上述给出了这么多事件,但有时候往往不能满足需求,这时候就需要执行自定义脚本了。 ``` js -"x,y": [ // 实际执行的事件列表 +[ {"type": "function", "function": function(){ // 执行一段js脚本 // 这里写js代码 alert(core.getStatus("atk")); // 弹窗显示勇士的攻击力 @@ -1249,11 +1810,11 @@ choices为一个数组,其中每一项都是一个选项列表。 `{"type":"function"}`需要有一个`"function"`参数,它是一个JS函数,里面可以写任何自定义的JS脚本;系统将会执行它。 -系统常见可能会被造塔所用到的的API都在[附录:API列表](api)中给出,请进行参照。 +系统常见可能会被造塔所用到的的API都在[API列表](api)中给出,请进行参照。 **警告:自定义脚本中只能执行同步代码,不可执行任何异步代码,比如直接调用core.changeFloor(...)之类都是不行的。** -[附录:API列表](api)中的所有异步API都进行了标记;如果你不确定一个函数是同步的还是异步的,请向小艾咨询。 +[API列表](api)中的所有异步API都进行了标记;如果你不确定一个函数是同步的还是异步的,请向小艾咨询。 如果需要异步的代码都需要用事件(insertAction)来执行,这样事件处理过程和录像回放才不会出错。 @@ -1268,6 +1829,23 @@ core.insertAction([ // 请勿直接调用 core.changeFloor(toFloor, ...),这个代码是异步的,会导致事件处理和录像出问题! ``` +!> 从V2.5.3开始,提供了一个"不自动执行下一个事件"的选项(`"async": true`)。如果设置了此项,那么在该部分代码执行完毕后,不会立刻执行下一个事件。你需要在脚本中手动调用`core.events.doAction()`来执行下一个事件。可以通过此项来实现一些异步的代码,即在异步函数的回调中再执行下一个事件。使用此选项请谨慎,最好向开发者寻求咨询。 + +## 独立开关 + +从V2.5.3开始,针对每个事件都提供了独立开关。 + +独立开关的写法是`switch:A`, `switch:A`直到`switch:Z`,共计26个;不过样板中的值块默认只提供前6个。 + +独立开关算是特殊的flag,它在事件中使用时会和事件的楼层及坐标进行绑定;换句话说每个事件对应的`switch:A`都是不同的。 + +事实上,在某个楼层某个点的事件的独立开关A对应的系统flag为`floorId@x@y@A`, +比如在`MT0`层的`[2,5]`点事件,对应的`switch:B`独立开关,实际会被映射到`flag:MT0@2@5@B`。 + +如果在事件外想访问某个事件的独立开关也需要通过上面这个方式。 + +通过独立开关的方式,我们无需对某些NPC的对话都设立单独的互不重复flag,只需要关注该事件自身的逻辑即可。 + ## 同一个点的多事件处理 我们可以发现,就目前而且,每个点的事件是和该点进行绑定,并以该点坐标作为唯一索引来查询。 @@ -1339,7 +1917,6 @@ core.insertAction([ } ``` - 总之,记住如下两点: - 可以使用setBlock来更改一个图块。 @@ -1349,6 +1926,72 @@ core.insertAction([ - 如果弄不清楚系统trigger和自定义事件等的区别,也可以全部覆盖为自定义事件,然后通过type:battle,type:openDoor等来具体进行控制。 - 多事件处理时请不要使用`changeFloor`那一项,而是使用`events`或者`afterXXX`来处理。 +## 并行事件处理 + +从V2.4.3后,H5样板开始支持并行事件处理。 + +在脚本编辑里面提供了一个parallelDo函数,这个函数可以用来做并行处理内容。 + +从V2.5.2开始,每层楼的楼层属性中也增加了一个parallelDo选项,可以在里面写任何脚本代码。该部分代码仅在人物在该楼层时才会被反复执行。 + +``` js +"parallelDo": function (timestamp) { + // 并行事件处理,可以在这里写任何需要并行处理的脚本或事件 + // 该函数将被系统反复执行,每次执行间隔视浏览器或设备性能而定,一般约为16.6ms一次 + // 参数timestamp为“从游戏资源加载完毕到当前函数执行时”的时间差,以毫秒为单位 + + // 检查当前是否处于游戏开始状态 + if (!core.isPlaying()) return; + + // 执行当前楼层的并行事件处理 + if (core.isset(core.status.floorId)) { + try { + eval(core.floors[core.status.floorId].parallelDo); + } catch (e) { + main.log(e); + } + } + + // 下面是一个并行事件开门的样例 + /* + // 如果某个flag为真 + if (core.hasFlag("xxx")) { + // 千万别忘了将该flag清空!否则下次仍然会执行这段代码。 + core.removeFlag("xxx"); + // 使用insertAction来插入若干自定义事件执行 + core.insertAction([ + {"type":"openDoor", "loc":[0,0], "floorId": "MT0"} + ]) + // 也可以写任意其他的脚本代码 + } + */ +} +``` + +该函数将被系统反复执行,执行间隔试浏览器或设备性能而定,一般约为16.6ms一次。 + +此函数有个参数timestamp,为**从游戏资源加载完毕到当前函数执行时**的时间差,以毫秒为单位。可以使用此参数来制作一些时间相关内容或者特效等。 + +如果要执行并行的自定义事件,请使用if+flag判断的形式,然后insertAction将自定义事件插入到事件列表中。 + +!> 判定flag后千万别忘了将该flag清空!否则下次仍然会执行这段代码。 + +每层楼的并行事件处理类似,只有角色在当前楼层时才会反复执行当前楼层中parallelDo部分的代码。 + +下面是一个打怪开门的样例:(假设每打一个怪的战后事件把`flag:door`+1) + +``` js +// 每层楼的并行事件处理代码样例 +if (core.getFlag("door",0)==2) { + // 将该flag清空 + core.removeFlag("door"); + // 开门,如果是当前层则无需写floorId + core.insertAction([ + {"type":"openDoor", "loc":[0,0]} + ]); +} +``` + ## 加点事件 打败怪物后可以进行加点。 @@ -1357,31 +2000,13 @@ core.insertAction([ 如果要对某个怪物进行加点操作,则首先需要修改该怪物的`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] }]); } ``` @@ -1403,41 +2028,15 @@ core.insertAction([ "icon": "blueShop", // 商店图标,blueShop为蓝色商店,pinkShop为粉色商店 "textInList": "1F金币商店", // 在快捷商店栏中显示的名称 "use": "money", // 商店所要使用的。只能是"money"或"experience"。 - "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值。 + "commonTimes": true, // 是否使用全局次数 + "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为参数 - // 其他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"}, // 多个效果用分号分开 ] } ], @@ -1450,21 +2049,24 @@ core.insertAction([ - icon 为商店的图标,在icons.js的npcs中定义。如woman可代表一个商人。 - textInList 为其在快捷商店栏中显示的名称,如"3楼金币商店"等 - use 为消耗的类型,是金币(money)还是经验(experience)。 +- commonTimes 是否使用全局次数;如果为true则可以多个快捷商店共享相同的次数 +- mustEnable 是否必须是只在开启状态才在列表显示;如果此项为true则未开启的快捷商店不予显示 - need 是一个表达式,计算商店所需要用到的数值。 - 可以将times作为参数,times为该商店已经访问过的次数,第一次访问时times是0。 - 如果对于每个选项都需要不同的数值,这里设为"-1";可参见下面经验商店的例子。 - text 为商店所说的话。可以用${need}表示需要的数值。 - choices 为商店的各个选项,是一个list,每一项是一个选项 - text为显示文字。请注意这里不支持 ${} 的表达式计算。 - - effect 为该选项的效果;effect只能对status或items进行操作,且必须是 `status:xxx+=yyy` 或 `item:xxx+=yyy`的形式。即中间必须是+=符号。 - - 如有多个effect(例如升级全属性提升),使用分号分开,参见经验商店的写法。 + - effect 为该选项的效果;effect必须是 `status:xxx+=yyy`, `item:xxx+=yyy`或`flag:xxx+=yyy`的形式。即中间必须是+=符号。 + - 如有多个effect(例如升级全属性提升),使用分号分开。 像这样定义了全局商店后,即可在快捷栏中看到。 请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。 ``` js -"1,0": [ // 金币商店 +// 事件列表 +[ // 打开商店前,你也可以添加自己的剧情 // 例如,通过if来事件来判断是不是第一次访问商店,是的则显示一段文字(类似宿命的华音那样) {"type": "openShop", "id": "moneyShop1"}, // 这里的id要和data.js中你定义的商店ID完全一致 @@ -1481,6 +2083,27 @@ core.insertAction([ 另外需要注意的一点就是,每层楼都有一个 canUseQuickShop 选项。如果该选项置为false则无法在该层使用快捷商店。 +**从V2.6开始,也提出了“公共事件化的全局商店”,即打开使用全局商店实际上是执行一个公共事件。** + +```js +"shops": [ + // 定义公共事件化的全局商店 + { + "id": "keyShop1", // 商店唯一ID + "textInList": "回收钥匙商店", // 在快捷商店栏中显示的名称 + "mustEnable": false, // 如果未开启则不显示在状态栏中 + "commonEvent": "回收钥匙商店", // 公共事件名 + "args": [], // 向该公共事件传递的参数 + } +] +``` + +`id`, `textInList`, `mustEnable`和上述完全相同。 + +`commonEvent`为公共事件名,即选择此项时要执行的公共事件。 + +`args`为向该公共事件传递的参数,参见[type:insert](#insert:插入公共事件或另一个地点的事件并执行)的说明。 + ## 系统引发的自定义事件 我们知道,所有自定义事件都是需要定义在`"x,y"`处,并且得让用户经过或撞上才能触发的。 @@ -1519,17 +2142,13 @@ core.insertAction([ !> 多个机关门请分别设置开门变量如door1, door2等等。请勿存在两个机关门用相同的变量! -同样,为了实现类似于RMXP中,到达某一层后自动触发某段事件的效果,样板中还存在`firstArrive`事件。 - -当且仅当勇士第一次到达某层时,将会触发此事件。可以利用此事件来显示一些剧情,或再让它调用 `{"type": "trigger"}` 来继续调用其他的事件。 +除此以外,每层楼还提供了`firstArrive`和`eachArrive`事件,分别为首次到达该楼层和每次到达该楼层时执行的事件。 ## 使用炸弹后的事件 上面的afterBattle事件只对和怪物进行战斗后才有会被处理。 -如果我们想在使用炸弹后也能触发一些事件(如开门),则可以在`functions.js`里面的`afterUseBomb`函数进行处理: - -!> V2.0版本可以直接在“脚本编辑 - 使用炸弹后的事件”中双击进行修改! +如果我们想在使用炸弹后也能触发一些事件(如开门),则可以在脚本编辑里面的`afterUseBomb`函数进行处理: ``` js ////// 使用炸弹/圣锤后的事件 ////// @@ -1545,47 +2164,42 @@ core.insertAction([ } ``` -## 滑冰和推箱子事件 +## 滑冰事件 -最新的样板还支持滑冰和推箱子事件。 +从V2.6开始,滑冰事件被重写。现在的滑冰由公共事件执行。 -滑冰事件的数字是167,trigger为ski。 +在新版本中,冰面应该放在背景层上,上面可以放置道具、怪物、门等图块。 -当角色走上冰面时,将触发ski事件,并会一直向前滑行,直到撞上不可通行的块会触发事件(比如撞上怪物会触发battle,撞上门会触发openDoor等等),或者离开冰面为止。 +角色走上冰面后,将一直向前滑行,直到撞上不可通行的图块,或触发事件为止。 -!> 由于H5魔塔只有事件一层,因此滑冰的冰面上将无法摆放任何东西(如道具)。 +如果撞上怪物将自动进行战斗,此战斗是强制的,打不过将直接死亡。 -!> 撞上怪物将触发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.status.replay.replaying; - core.stopReplay(); - core.waitHeroToStop(function() { - core.drawText([ - "\t[结局1]你死了。\n如题。" - ], function () { - core.events.gameOver(null, replaying); - }); - }) -} -``` - -其参数reason为失败原因。你可以在这里修改失败界面时显示的文字。 - ========================================================================================== [继续阅读下一章:个性化](personalization) diff --git a/docs/img/blood.png b/_docs/img/blood.png similarity index 100% rename from docs/img/blood.png rename to _docs/img/blood.png diff --git a/docs/img/changefloor.png b/_docs/img/changefloor.png similarity index 100% rename from docs/img/changefloor.png rename to _docs/img/changefloor.png diff --git a/docs/img/checkblock.png b/_docs/img/checkblock.png similarity index 100% rename from docs/img/checkblock.png rename to _docs/img/checkblock.png diff --git a/docs/img/chrome.png b/_docs/img/chrome.png similarity index 100% rename from docs/img/chrome.png rename to _docs/img/chrome.png diff --git a/_docs/img/commonEvent.png b/_docs/img/commonEvent.png new file mode 100644 index 00000000..d6358b89 Binary files /dev/null and b/_docs/img/commonEvent.png differ 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/console.png b/_docs/img/console.png similarity index 100% rename from docs/img/console.png rename to _docs/img/console.png 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/debuff.png b/_docs/img/debuff.png similarity index 100% rename from docs/img/debuff.png rename to _docs/img/debuff.png diff --git a/docs/img/drawmap.jpg b/_docs/img/drawmap.jpg similarity index 100% rename from docs/img/drawmap.jpg rename to _docs/img/drawmap.jpg 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/enemy.png b/_docs/img/enemy.png similarity index 100% rename from docs/img/enemy.png rename to _docs/img/enemy.png diff --git a/docs/img/enemyarray.png b/_docs/img/enemyarray.png similarity index 100% rename from docs/img/enemyarray.png rename to _docs/img/enemyarray.png diff --git a/docs/img/eventdebug.png b/_docs/img/eventdebug.png similarity index 100% rename from docs/img/eventdebug.png rename to _docs/img/eventdebug.png diff --git a/docs/img/flag.png b/_docs/img/flag.png similarity index 100% rename from docs/img/flag.png rename to _docs/img/flag.png diff --git a/docs/img/floor.png b/_docs/img/floor.png similarity index 100% rename from docs/img/floor.png rename to _docs/img/floor.png diff --git a/docs/img/floordata.png b/_docs/img/floordata.png similarity index 100% rename from docs/img/floordata.png rename to _docs/img/floordata.png diff --git a/docs/img/floorid.png b/_docs/img/floorid.png similarity index 100% rename from docs/img/floorid.png rename to _docs/img/floorid.png diff --git a/docs/img/floorset.png b/_docs/img/floorset.png similarity index 100% rename from docs/img/floorset.png rename to _docs/img/floorset.png diff --git a/docs/img/getspecialtext.png b/_docs/img/getspecialtext.png similarity index 100% rename from docs/img/getspecialtext.png rename to _docs/img/getspecialtext.png diff --git a/docs/img/imginfo.png b/_docs/img/imginfo.png similarity index 100% rename from docs/img/imginfo.png rename to _docs/img/imginfo.png diff --git a/docs/img/init.png b/_docs/img/init.png similarity index 100% rename from docs/img/init.png rename to _docs/img/init.png diff --git a/_docs/img/keyboard.png b/_docs/img/keyboard.png new file mode 100644 index 00000000..c2b9d241 Binary files /dev/null and b/_docs/img/keyboard.png differ diff --git a/docs/img/logo.png b/_docs/img/logo.png similarity index 100% rename from docs/img/logo.png rename to _docs/img/logo.png diff --git a/docs/img/map1.png b/_docs/img/map1.png similarity index 100% rename from docs/img/map1.png rename to _docs/img/map1.png diff --git a/docs/img/maparray.png b/_docs/img/maparray.png similarity index 100% rename from docs/img/maparray.png rename to _docs/img/maparray.png diff --git a/docs/img/mapgui.png b/_docs/img/mapgui.png similarity index 100% rename from docs/img/mapgui.png rename to _docs/img/mapgui.png diff --git a/docs/img/mapmean.png b/_docs/img/mapmean.png similarity index 100% rename from docs/img/mapmean.png rename to _docs/img/mapmean.png diff --git a/docs/img/moddata.png b/_docs/img/moddata.png similarity index 100% rename from docs/img/moddata.png rename to _docs/img/moddata.png diff --git a/docs/img/nattack.png b/_docs/img/nattack.png similarity index 100% rename from docs/img/nattack.png rename to _docs/img/nattack.png 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/plugin.png b/_docs/img/plugin.png new file mode 100644 index 00000000..5c65cd4d Binary files /dev/null and b/_docs/img/plugin.png differ diff --git a/docs/img/point.png b/_docs/img/point.png similarity index 100% rename from docs/img/point.png rename to _docs/img/point.png diff --git a/docs/img/ps.png b/_docs/img/ps.png similarity index 100% rename from docs/img/ps.png rename to _docs/img/ps.png diff --git a/docs/img/register.png b/_docs/img/register.png similarity index 100% rename from docs/img/register.png rename to _docs/img/register.png diff --git a/docs/img/rmxp1.png b/_docs/img/rmxp1.png similarity index 100% rename from docs/img/rmxp1.png rename to _docs/img/rmxp1.png diff --git a/docs/img/rmxp2.png b/_docs/img/rmxp2.png similarity index 100% rename from docs/img/rmxp2.png rename to _docs/img/rmxp2.png diff --git a/docs/img/rmxp3.png b/_docs/img/rmxp3.png similarity index 100% rename from docs/img/rmxp3.png rename to _docs/img/rmxp3.png diff --git a/docs/img/rmxp4.jpg b/_docs/img/rmxp4.jpg similarity index 100% rename from docs/img/rmxp4.jpg rename to _docs/img/rmxp4.jpg diff --git a/docs/img/sample0.png b/_docs/img/sample0.png similarity index 100% rename from docs/img/sample0.png rename to _docs/img/sample0.png diff --git a/docs/img/save.png b/_docs/img/save.png similarity index 100% rename from docs/img/save.png rename to _docs/img/save.png diff --git a/docs/img/script.png b/_docs/img/script.png similarity index 100% rename from docs/img/script.png rename to _docs/img/script.png diff --git a/docs/img/server.png b/_docs/img/server.png similarity index 100% rename from docs/img/server.png rename to _docs/img/server.png 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/img/tuihua.png b/_docs/img/tuihua.png similarity index 100% rename from docs/img/tuihua.png rename to _docs/img/tuihua.png diff --git a/docs/img/zone.png b/_docs/img/zone.png similarity index 100% rename from docs/img/zone.png rename to _docs/img/zone.png diff --git a/_docs/index.html b/_docs/index.html new file mode 100644 index 00000000..10679f3f --- /dev/null +++ b/_docs/index.html @@ -0,0 +1,50 @@ + + + + + HTML5魔塔样板 + + + + + + + + + + +
+ + + + diff --git a/docs/index.md b/_docs/index.md similarity index 74% rename from docs/index.md rename to _docs/index.md index 0e744fb2..510bd14c 100644 --- a/docs/index.md +++ b/_docs/index.md @@ -1,11 +1,10 @@ # HTML5 魔塔样板说明文档 -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * 众所周知,魔塔的趋势是向移动端发展,贴吧中也常常能见到“求手机魔塔”的帖子。然而现有的工具中,NekoRPG有着比较大的局限性,游戏感较差,更是完全没法在iOS上运行。而一些APP的魔塔虽然可用,但是必须要下载安装,对于Android和iOS还必须开发不同的版本,非常麻烦。 但是,现在我们有了HTML5。 HTML5的画布(canvas)以及它被Android/iOS内置浏览器所支持的特性,可以让我们做出真正意义上的全平台覆盖的魔塔。 -事实上,在贴吧的试水发布也证明了,H5魔塔确实是可以成功的。两部即使是复刻的魔塔也受到了不少人的追捧,其流畅的手感和全平台支持的特性,也让很多没办法打开电脑的人爱不释手。 然而,一般而言使用非RMXP制作魔塔往往需要一定的编程技术,HTML5魔塔自然也不例外。但是,为了能让大家更加注重于“做塔”本身,而不用考虑做塔以外的各种脚本问题,我特意制作了这样一部HTML5的魔塔样板。 @@ -13,7 +12,7 @@ 继续查看文档的详细介绍,让你学会如何使用这一个样板来制作属于自己的HTML5魔塔。 -视频教程地址:[http://www.bilibili.com/video/av17608025/](http://www.bilibili.com/video/av17608025/) ,配合本教程观看效果更佳~ +本说明文档配有B站视频教程,对照查看效果更佳哦:[https://www.bilibili.com/video/av32781473/](https://www.bilibili.com/video/av32781473/)。 ========================================================================================== diff --git a/_docs/personalization.md b/_docs/personalization.md new file mode 100644 index 00000000..72de08cd --- /dev/null +++ b/_docs/personalization.md @@ -0,0 +1,717 @@ +# 个性化 + +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * + +有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。 + +## 图层的说明 + +HTML5魔塔是使用画布(canvas)来绘制,存在若干个图层,它们之间有一个覆盖关系,后面的图层将覆盖前面的图层。 + +所有图层从低往高依次如下:(加[B]的代表该层是大地图,[D]代表由系统按需动态创建,z-index代表该层的纵向高度) + +- bg**[B]**:背景层;绘制背景图层素材bgmap,和背景贴图 (z-index: 10) +- event**[B]**:事件层;所有事件(道具、墙壁、NPC、怪物等)都绘制在这一层进行处理 (z-index: 30) +- hero:勇士层;主要用来绘制勇士 (z-index: 40) +- event2**[B]**:事件2层;本层主要用来绘制48x32的图片素材的上半部分(避免和勇士错位) (z-index: 50) +- fg**[B]**:前景层;绘制前景图层素材fgmap,和前景贴图 (z-index: 60) +- damage**[B]**:显伤层;主要用来绘制怪物显伤和领域显伤 (z-index: 65) +- animate:动画层;主要用来绘制动画。 (z-index: 70) +- weather**[D]**:天气层;主要用来绘制天气(雨/雪/雾) (z-index: 80) +- route**[D]**:路线层;主要用来绘制勇士的行走路线图。 (z-index: 95) +- paint**[D]**:绘图层;主要用来进行绘图模式。(z-index: 95) +- curtain:色调层;用来控制当前楼层的画面色调 (z-index: 125) +- 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并进行使用。 + +使用`core.createCanvas(name, x, y, w, h, z)`来动态创建一个画布。 + +其中name为动态canvas名称,x,y,w,h为创建的画布相对窗口左上角的像素坐标和长宽,z为画布的纵向高度。 + +例如:`core.createCanvas('test', 10, 20, 100, 200, 74)` 创建了一个名为test的画布,其左上角相对窗口的像素坐标为(10,20),宽100高200,纵向高度74(在动画层和天气层之间)。 + +该函数会返回画布的context,也可以通过 `core.dymCanvas[name]` 来获得;例如 `core.dymCanvas.test` 就是我们上面创建的画布的context,然后进行操作。 + +也可以简单的使用`core.fillText()`, `core.fillRect()`, `core.strokeRect()`等等对画布进行任意绘制。 + +``` js +core.fillText('test', '这是一段文字', 10, 30, '#FF0000', '16px Verdana'); // 绘制一段文本 +``` + +使用 `core.deleteCanvas(name)` 删除一个动态创建的画布,例如 `core.deleteCanvas('test')`。 + +`core.deleteAllCanvas()`可以删除所有动态创建的画布,`core.relocateCanvas(name, x, y)`和`core.resizeCanvas(name, x, y)`可以对画布的位置和大小进行改变。 + +更多详细API请参见[API列表](api)。 + +## 自定义素材 + +所有素材的图片都在`images`目录下。 +- `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。 +- `autotile*.png` 为Autotile块。 +- `enemys.png` 为所有怪物的图片。 +- `enemy48.png` 为所有48x32怪物的图片。 +- `heros.png` 为勇士行走图。 +- `items.png` 为所有道具的图标。 +- `npcs.png` 为所有NPC的图标。 +- `npc48.png` 为所有48x32的NPC图标。 +- `terrains.png` 为所有地形的图标。 + +系统会读取`icon.js`文件,并获取每个ID对应的图标所在的位置。 + +### 使用预定义的素材 + +在images目录的“默认素材”下给定了若干预定义的自定义素材。 + +如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。 + +### 背景和前景图层 + +从V2.4.1开始,样板允许多个图层叠加,最多支持背景层、事件层和前景层三个图层。 + +在地图编辑器中绘图时,下拉框选中“背景层”或“前景层”即可在对应的图层上绘图。 + +其中背景层和前景层可以使用任何素材,以及使用自动元件(autotile)。 + +可以使用`showBgFgMap`, `hideBgFgMap`, `setBgFgBlock`等事件对背景和前景图层进行操作。 + +### 使用自己的图片作为某层楼的背景/前景素材 + +由于HTML5功能(素材)有限,导致了对很多比较复杂的素材(比如房子内)等无法有着较好的绘图方式。 + +为了解决这个问题,我们允许用户自己放置一张或多张图片作为某一层的背景/前景素材。 + +要启用这个功能,我们首先需要在`data.js`中将可能的图片进行加载。 + +``` js +"images": [ // 在此存放所有可能使用的图片 + // 图片可以被作为背景/前景图,也可以直接用自定义事件进行显示。 + // 图片名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 + // 建议对于较大的图片,在网上使用在线的“图片压缩工具(http://compresspng.com/zh/)”来进行压缩,以节省流量 + "bg.jpg", "house.png", "bed.png"// 依次向后添加 +]; +``` + +!> 请使用网上的一些[在线图片压缩工具](http://compresspng.com/zh/)对图片进行压缩,以节省流量。 + +之后,我们可以在每层剧本的`"images"`里来定义该层的默认背景/前景层的图片素材。 + +从V2.5.4开始,贴图也允许进行帧动画,只要设置第五项的数值。 + +``` js +[[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,图片名,遮挡方式和帧数。 + +其中x和y分别为左上角的像素坐标;图片名则必须在全塔属性的images中定义过。 + +第四项为遮挡方式,定义如下: + +- 0:该图片将全部画在背景层,被勇士所遮挡。举例:某些特殊地形等。 +- 1:该图片将全部画在前景层,可以遮挡勇士。举例:云彩等效果。 +- 2:该图片将上部分画在前景层,下部分画在背景层。从而可以达到一个“自动调节遮挡的效果”。举例:树、房子等等。 + +!> 如果写2的话,最好让x,y和图片高度都是32的倍数! + +第五项为图片的帧数,可选。如果进行了设置,则会将该贴图视为帧动画,并切分成对应的帧数。 + +例如,假设图片是100x100的,且帧数设为4,则视为四帧帧动画,每次绘制的图片大小实际上是25x100。 + +关于楼层贴图和前景、背景层的层叠覆盖关系,默认是:**地板 - 背景贴图 - 背景图块 - 事件 - 勇士 - 前景贴图 - 前景图块**。 + +可以通过修改`libs/maps.js`的`drawBg`和`drawFg`函数来改变其覆盖关系。 + +``` js +////// 绘制背景层 ////// +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:隐藏贴图)的写法。 + +**如果要让贴图的某些点不可通行,则可以使用noPass或者空气墙。** + +!> 小技巧:可以使用帧动画贴图来贴一些大型怪物,比如魔龙、章鱼,或者《永不复还》中的恐怖利刃等等。如果使用帧贴图来贴怪物,则可以使用一个“透明的怪物”放置在对应位置并写上具体属性数值(这样就可以进行战斗和显伤了);然后可以使用[displayIdInBook](element#怪物的朝向问题)在怪物手册中将该透明怪物映射到另一个有素材的怪物ID上。战斗完毕后使用[隐藏贴图](event#hideFloorImg:隐藏贴图)事件将贴图隐藏即可。 + +### 使用便捷PS工具生成素材 + +如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。 + +![便捷PS工具](img/ps.png) + +我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。 + +便捷PS工具同样支持图片色相的修改,和RMXP几乎完全相同。 + +用这种方式,我们能极快地替换或素材,包括需要新增的怪物。 + +### 添加素材到游戏 + +在使用地图编辑器编辑的过程中,我们有可能会出现“该数字和ID未被定义”的错误提示。 + +这是因为,该素材没有被定义,无法被游戏所识别。 + +!> 在V2.0中,我们可以简单的在地图编辑器中新增素材,以及定义新增素材的ID和数字,但是仍然**强烈建议**对素材的机制进行了解。 + +#### 素材的机制 + +本塔所有的素材都拥有三个属性:**ID**,**索引**,**数字**。 +- **ID** 为该素材的唯一标识符,任何两个素材的ID都不能相同。 +- **索引** 为该素材的在对应图片上的图标索引,即该素材是图片上的第几个。 +- **数字** 为该素材的对应数字,以方便地图的生成和存储。 + +**`ID-索引` 对应关系定义在icons.js文件中。该文件将唯一确定一个ID在图片上所在的位置。** + +**`ID-数字` 对应关系定义在maps.js文件中。该文件将唯一确定一个ID对应的数字是多少。** + +在V2.0中,我们可以在地图编辑器中很方便查看每个图块的三个属性信息。 + +#### 注册素材 + +在V2.0的地图编辑器中,要注册新素材,我们只需要在图块属性一栏输入新素材的ID和数字。 + +![素材注册](./img/register.png) + +ID必须由数字字母下划线组成,数字在1000以内,且均不能和已有的进行重复。 + +之后刷新编辑器即可。 + +我们也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 + +素材注册完毕后,即可在游戏中正常使用,也可以被地图生成器所识别(需要重开地图生成器)。 + +#### Autotile自动元件的注册 + +但是,通过上面这种方式,我们是没办法新增并注册Autotile的。 + +除了替换样板现有的几个外,如果我们还需要新添加Autotile,则: + +1. 下拉框切到“追加素材”,导入文件到画板,然后导入一张Autotile自动元件图片。 +2. 下拉框选择autotile,然后点“追加” +3. 看到成功的提示后刷新编辑器即可。 + +### 额外素材 + +从V2.4.2开始,HTML5魔塔样板开始支持额外素材。 + +具体而言,通过上面的“素材导入”的方式,确实可以有效地添加素材到游戏。但是,如果想增加大量自定义素材,需要通过便捷PS工具将这些素材全部导入到`terrains.png`中,并且全部是单列,极度不友好。这也导致了野外风的制作相对变得很困难,增加了大量素材处理的工作量。 + +额外素材就是为了解决这个问题而被提出。 + +所谓`额外素材`,即用户可以自定导入任意张素材图片,无需PS,无需注册,即可直接在游戏中使用。这一点已经十分向RM靠拢了。 + +要使用额外素材,请将你需要的素材图片放在`images`目录下,并在`全塔属性`的`tilesets`中定义图片名。 + +**该素材的宽高必须都是32的倍数,且图片上的总图块数不超过1000(即最多有1000个32*32的图块在该图片上)。** + +```js +// 在全塔属性中的tilesets导入素材 +"tilesets": ["1.png", "2.png"] // 导入两个额外素材,文件名分别是1.png和2.png +``` + +刷新后,系统会自动加载该素材并添加到素材区。 + +额外素材无需导入,无需注册。在`tilesets`中定义了图片后,即可直接使用绘图,无需再注册其数字和ID。其ID、索引和数字均为系统自动分配,且不允许修改。 + +请注意,额外素材的ID、索引和数字,与该图片在tilesets数组中的index及该素材在图片上的位置都有关系。 + +!> **因此如果对`tilesets`数组随意删除或修改顺序,可能会导致所有额外素材全部发生变化!这点请务必注意!!!** + +除此之外,额外素材在游戏中的使用和正式素材都是一致的,也能在前景或背景图层绘制。 + +额外素材可以使用“tileset贴图”的方式进行绘制,一次绘制一个矩形区域。 + +## 自定义道具效果 + +本节中将继续介绍如何自己编辑一个道具的效果。 + +道具效果的具体实现都在`items.js`中。 + +### 即捡即用类道具(cls: items) + +对于即捡即用类道具,如宝石、血瓶、剑盾等,我们可以简单地修改`data.js`中的value一栏即可。 + +如果你想要同种宝石在不同层效果不同的话,可以进行如下操作: + +1. 在楼层的item_ratio中定义宝石的比率(比如1-10的写1,11-20层写2等) +2. 修改获得道具的itemEffect函数(编辑器中双击进行编辑) + +``` js +// ratio为楼层的item_ratio值,可以进行翻倍宝石属性 +core.status.hero.atk += core.values.redJewel * ratio +``` + +这里我们可以直接写ratio来取用该楼层中定义的`item_ratio`的值。 + +如果不是倍数增加(比如线性增加)也可以类似来写 + +``` js +// 一个二倍线性增加的例子 +core.status.hero.atk += core.values.redJewel + 2*ratio +``` + +### 消耗类道具(cls: tools);永久类道具(cls: constants) + +如果要自己实现消耗类道具或永久类道具的使用效果,则需修改`items.js`中的canUseItem和useItem两个函数。 + +具体过程比较复杂,需要一定的JS能力,在这里就不多说了,有需求可以找`艾之葵`进行了解。 + +### 实战!拿到神圣盾后免疫吸血、领域、夹击效果 + +1. 在itemEffect中修改拿到神圣盾时的效果,标记一个自定义Flag。 +``` js +core.status.hero.def += core.values.shield5 * ratio; +core.setFlag("shield5", true); // 增加一个自定义Flag:已经拿到神圣盾 +``` +2. 免疫吸血效果:在脚本编辑的getDamageInfo中,编辑成如果存在神圣盾标记,吸血伤害为0。 +``` js +function (enemy, hero_hp, hero_atk, hero_def, hero_mdef, x, y, floorId) { +// ... 上略 + // 吸血 + if (this.hasSpecial(mon_special, 11)) { + var vampireDamage = hero_hp * enemy.value; + + // 如果有神圣盾免疫吸血等可以在这里写 + // 也可以用hasItem或hasEquip来判断装备 + if (core.hasFlag("shield5")) vampireDamage = 0; // 存在神圣盾,吸血伤害为0 + + vampireDamage = Math.floor(vampireDamage) || 0; + // 加到自身 + if (enemy.add) // 如果加到自身 + mon_hp += vampireDamage; + + initDamage += vampireDamage; + } +// ... 下略 +``` +3. 免疫领域、夹击、阻击效果:在2.4.1之后,可以直接将flag:no_zone设为true来免疫领域效果,其他几个同理。 +``` js +// 写在获得道具后事件 +[ + // 设置不同的flag可以分别无视对应的阻激夹域效果 + {"type": "setValue", "name": "flag:no_zone", "value": "true"}, // 免疫领域 + {"type": "setValue", "name": "flag:no_snipe", "value": "true"}, // 免疫阻击 + {"type": "setValue", "name": "flag:no_laser", "value": "true"}, // 免疫激光 + {"type": "setValue", "name": "flag:no_betweenAttack", "value": "true"}, // 免疫夹击 +] +``` +4. 如果有更高的需求,例如想让吸血效果变成一半,则还是在上面这些地方进行对应的修改即可。 + +## 新增门和对应的钥匙 + +如果要新增一个门和对应的钥匙,需要进行如下几步: + +1. 在terrains.png中添加新的门的素材,并在地图编辑器中注册门的ID。该ID必须是以`Door`结尾,例如`abcDoor`。 +2. 在animates.png中添加开门的四格动画,然后直接打开icons.js文件,在animates下直接添加ID和索引信息,例如`'abcDoor': 34`。 +3. 在items.png中添加钥匙的素材,并在地图编辑器中注册钥匙的ID。该ID必须是和门对应且以`Key`结尾,例如`abcKey`。 +4. 该道具的cls应为`tools`,可以自行写道具描述,最下面几项均留`null`即可。 + +!> **请勿在animates中对门的动画素材进行注册!而是请直接打开icons.js文件并添加ID和索引信息!!!** + +!> terrains和animates的门ID必须完全一致,且以`Door`结尾;所对应的钥匙ID应当是把`Door`换成`Key`,这样才能对应的上! + +## 覆盖楼传事件 + +对于特殊的塔,我们可以考虑修改楼传事件来完成一些特殊的要求,比如镜子可以按楼传来切换表里。 + +要修改楼传事件,需要进行如下两步: + +1. 重写楼传的点击事件。在插件中对`core.control.useFly进行重写`。详细代码参见[重写点击楼传事件](script#重写点击楼传事件)。 +2. 修改楼传的使用事件。和其他永久道具一样,在地图编辑器的图块属性中修改楼传的useItemEffect和canUseItemEffect两个内容。例如: +``` js +"useItemEffect": "core.insertAction([...])" // 执行某段自定义事件,或者其他脚本 +"canUseItemEffect": "true" // 任何时候可用 +``` + +除了覆盖楼传事件外,对于快捷商店、虚拟键盘等等也可以进行覆盖,只不过是仿照上述代码重写对应的函数(`openQuickShop`,`openKeyBoard`)即可。 + +## 自定义怪物属性 + +如果你对现有的怪物不满意,想自行添加怪物属性也是可以的。具体参见脚本编辑的getSpecials。 + +你需自己指定一个special数字,修改属性名和属性提示文字。后两者可以直接写字符串,或写个函数传入怪物。 + +如果要修改伤害计算公式,请修改下面的getDamageInfo函数。请注意,如果无法战斗,该函数必须返回`null`。 + +!> 如果改动了伤害计算公式,可能导致临界计算崩掉,因此建议将全塔属性中的`useLoop`置为true。 + +对于毒衰弱怪物的战斗后结算在脚本编辑中的afterBattle函数中。 + +对于领域、夹击、阻击怪物的检查在`control.js`中的checkBlock函数中。 + +## 自定义快捷键 + +如果需要绑定某个快捷键为处理一段事件,也是可行的。 + +要修改按键,我们可以在脚本编辑的`onKeyUp`进行处理: + +比如,我们设置一个快捷键进行绑定,比如`Y`,其keycode是89。(有关每个键的keycode搜一下就能得到) + +然后在脚本编辑的`onKeyUp`函数的`switch`中进行处理。 + +``` js +case 89: // 使用该按键的keyCode,比如Y键就是89 + // 还可以再判定altKey是否被按下,即 if (altKey) { ... + + // ... 在这里写你要执行脚本 + // **强烈建议所有新增的自定义快捷键均能给个对应的道具可点击,以方便手机端的行为** + if (core.hasItem('...')) { + core.status.route.push("key:0"); // 记录按键到录像中 + core.useItem('...', true); // 第二个参数true代表该次使用道具是被按键触发的,使用过程不计入录像 + } + + break; +``` + +强烈建议所有新增的自定义快捷键均给个对应的永久道具可点击,以方便手机端的行为。 + +使用`core.status.route.push("key:"+keyCode)`可以将这次按键记录在录像中。 + +!> 如果记录了按键,且使用道具的话,需要将useItem的第二个参数设为true,避免重复记录! + +可以使用altKey来判断Alt键是否被同时按下。 + +## 公共事件 + +从V2.5.4开始,样板提供了“公共事件”下拉框,我们可以在里面用事件编辑器进行编辑,并通过`{"type":"insert"}`进行调用。 + +![公共事件](./img/commonEvent.png) + +具体详见[插入公共事件或另一个地点的事件并执行](event#insert:插入公共事件或另一个地点的事件并执行)。 + +当然,继续使用**插件**的写法也是可以的。 + +## 插件系统 + +在H5中,提供了“插件”系统。在V2.6中提供了一个插件下拉框,用户可以自行创建和写插件。 + +在插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。 + +下面是一个很简单的例子,我编写一个插件函数,其效果是让勇士生命值变成原来的x倍,并令面前的图块消失。 + +``` js +this.myfunc = function(x) { + core.status.hero.hp *= x; // 勇士生命翻若干倍 + core.insertAction([ // 自定义事件:令面前的图块消失。 + {"type": "setValue", "name": "flag:x", "value": "core.nextX()"}, + {"type": "setValue", "name": "flag:y", "value": "core.nextY()"}, + {"type": "hide", "loc": ["flag:x", "flag:y"]} + ]); +} +``` + +然后比如我们在某个道具的使用效果 `useItemEffect` 中写 `core.plugin.myfunc(2)` 即可调用此插件函数。也可以在战后事件或自定义脚本等位置来写。 + +网站上也提供了一个[插件库](https://h5mota.com/plugins/),欢迎大家把自己写的插件进行共享。 + +从V2.6开始,在插件中用`this.xxx`定义的函数将会被转发到core中。例如上述的`myfunc`除了`core.plugin.myfunc`外也可以直接`core.myfunc`调用。 + +详见[函数的转发](script#函数的转发)。 + +## 标题界面事件化 + +从V2.5.3开始,我们可以将标题界面的绘制和游戏开始用事件来完成。可以通过绘制画布、全塔属性,flags中的startUsingCanvas可以决定是否开启标题界面事件化。 + +然后就可以使用“事件流”的形式来绘制标题界面、提供选项等等。 + +在这里可以调用任意事件。例如,可以贴若干个图,可以事件切换楼层到某个剧情层再执行若干事件,等等。 + +关于选项,样板默认给出的是最简单的choices事件;你也可以使用贴按钮图,循环处理+等待操作来定制自己的按钮点击效果。 + +!> 开始游戏、读取存档、录像回放的效果已经默认给出,请不要修改或删减这些内容,以免出现问题。 + +标题界面事件全部处理完后,将再继续执行startText事件。 + +## 手机端按键模式 + +从V2.5.3以后,我们可以给手机端增加按键了,这样将非常有利于技能的释放。 + +用户在菜单栏打开“拓展键盘”后,在竖屏模式下点击工具栏,就会在工具栏按钮和快捷键模式之间进行切换。 + +切换到快捷键模式后,可以点1-8,分别等价于在电脑端按键1-8。 + +可以在脚本编辑的onKeyUp中定义每个快捷键的使用效果,比如使用道具或释放技能等。 + +默认值下,1使用破,2使用炸,3使用飞,4使用其他存在的道具,5-8未定义。可以相应修改成自己的效果。 + +也可以替换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中(46行起),进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。 +``` html +
+ +

+
+``` +3. 在editor.html中的statusBar(383行起),仿照第二点同样添加;这一项如果不进行则会地图编辑器报错。editor-mobile.html同理。 +4. 使用便捷PS工具,打开project/icons.png,新增一行并将魔力的图标P上去;记下其索引比如37(从0开始数)。 +5. 在main.js的this.statusBar中增加图片、图标和内容的定义。 +``` js +this.statusBar = { + 'images': { + // ...其他略 + 'speed': document.getElementById("img-speed"), // 图片的定义 + }, + 'icons': { + // ...其他略 + 'speed': 37, // 图标的定义,这里对应的是icons.png中的索引 + }, + // ...其他略 + 'speed': document.getElementById('speed'), // 显示内容(数据)的定义 +} +``` +6. 显示内容的设置。在脚本编辑的updateStatusBar函数,可以对该状态栏显示内容进行设置,下面是几个例子。 +``` js +// 设置其显示内容为status:speed值;需要在project/data.js中firstData的hero那里新增初始值`"speed": 0`。 +core.statusBar.speed.innerHTML = core.getStatus('speed'); +// 设置其显示内容为flag:speed值,无需额外进行定义。 +core.statusBar.speed.innerHTML = core.getFlag('speed', 0); +``` + +## 技能塔的支持 + +要支持技能塔,可能需要如下几个方面: + +从V2.5开始,内置了"二倍斩"技能,可以仿照其制作自己的技能。 + +- 魔力(和上限)的添加;技能的定义 +- 状态栏的显示 +- 技能的触发(按键与录像问题) +- 技能的效果 + +### 魔力的定义添加;技能的定义 + +从V2.5开始,提供了status:mana选项,可以直接代表当前魔力值。 + +如果要启用,需要开启全塔属性的enableMana选项。 + +如果需要魔力上限,则可以使用flag:manaMax来表示当前的魔力最大值。 + +同时,我们可以使用flag:skill表示当前开启的技能编号,flag:skillName表示当前开启的技能名称。 + +如果flag:skill不为0,则代表当前处于某个技能开启状态,且状态栏显示flag:skillName值。伤害计算函数中只需要对flag:skill进行处理即可。 + +!> 关于魔力上限:样板中默认没有提供status:manamax + +### 状态栏的显示 + +从V2.5开始,魔力值和技能名的状态栏项目已经被添加,可以直接使用。 + +在脚本编辑-updateStatusBar中,可以对状态栏显示内容进行修改。 + +``` js +// 设置魔力值 +if (core.flags.enableMana) { + // 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) { + // 可以用flag:skill表示当前开启的技能类型,flag:skillName显示技能名 + core.statusBar.skill.innerHTML = core.getFlag('skillName', '无'); +} +``` + +### 技能的触发 + +#### 使用道具作为技能 + +由于手机端按键十分不方便,虚拟键盘不好用,因此强烈推荐**给每个技能设置一个道具图标,在道具栏点击使用!** + +下面是个很简单的例子,要制作一个技能"二倍斩"。 + +我们可以设置一个道具,其cls是`constants`(永久道具),ID比如是`skill1`。 + +该道具的使用判定`canUseItemEffect`是`true`(表示任意时候都可使用),使用效果`useItemEffect`是: + +``` js +if (core.getFlag('skill', 0)==0) { // 判断当前是否已经开了技能 + if (core.getStatus('mana')>=5) { // 这里要写当前能否开技能的条件判断,比如魔力值至少要多少 + core.setFlag('skill', 1); // 开技能1 + core.setFlag('skillName', '二倍斩'); // 设置技能名 + } + else { + core.drawTip("魔力不足,无法开技能"); + } +} +else { // 关闭技能 + core.setFlag('skill', 0); // 关闭技能状态 + core.setFlag('skillName', '无'); +} +``` + +简单的说,用flag:skill判断当前开启的技能,flag:skillName表示该技能名。(可在状态栏显示) + +该(技能)道具任何时候都可被使用;使用时,判断当前是否开启了技能,如果开启则关闭,没开则再判断是否允许开启(魔力值够不够等)。 + +#### 快捷键触发技能 + +在PC端,我们还可以按键触发技能。 + +在技能的道具定义完毕后,再将该道具绑定到一个快捷键上。有关绑定按键请参见[自定义快捷键](#自定义快捷键)。 + +下面是一个很简单的例子,当勇士按下W后,触发我们上面定义的二倍斩技能。 + +``` js +case 87: // W:开启技能“二倍斩” + // 检测技能栏是否开启,是否拥有“二倍斩”这个技能道具 + if (core.flags.enableSkill && core.hasItem('skill1')) { + core.status.route.push("key:87"); + core.useItem('skill1', true); + } + break; +``` + +在勇士处于停止的条件下,按下W键时,判断技能的道具是否存在,如果存在再使用它。 + +!> 由于现在手机端存在拓展键盘,也强烈建议直接覆盖1-8的使用效果,这样手机端使用也非常方便。 + +### 技能的效果 + +最后一点就是技能的效果;其实到了这里就和RM差不多了。 + +技能的效果要分的话有地图类技能,战斗效果类技能,后续影响类技能什么的,这里只介绍最简单的战斗效果类技能。 + +其他的几类技能根据需求可能更为麻烦,有兴趣可自行进行研究。 + +战斗效果内技能要改两个地方:战斗伤害计算,战后扣除魔力值。 + +战斗伤害计算在脚本编辑的`getDamageInfo`函数,有需求直接修改这个函数即可。 + +战后扣除魔力值则在脚本编辑的`afterBattle`中进行编辑即可。 + +举个例子,我设置一个勇士的技能:二倍斩,开启技能消耗5点魔力,下一场战斗攻击力翻倍。 + +那么,直接在脚本编辑的`getDamageInfo`中进行判断: + +``` js +if (core.getFlag('skill', 0)==1) { // 开启了技能1 + hero_atk *= 2; // 计算时攻击力翻倍 +} +``` + +然后在脚本编辑的`afterBattle`中进行魔力值的扣除: + +``` js +// 战后的技能处理,比如扣除魔力值 +if (core.flags.enableSkill) { + // 检测当前开启的技能类型 + var skill = core.getFlag('skill', 0); + if (skill==1) { // 技能1:二倍斩 + core.status.hero.mana-=5; // 扣除5点魔力值 + } + // 关闭技能 + core.setFlag('skill', 0); + core.setFlag('skillName', '无'); +} +``` + +!> 开启技能后,建议将全塔属性的useLoop置为true,即改用循环计算临界值,这样临界计算才不会出问题! + +  + +通过上述这几种方式,我们就能成功的让H5支持技能啦! + +## 系统使用的flag变量 + +众所周知,自定义flag变量都可以任意定义并取用(未定义直接取用的flag默认值为0)。 + +下面是一些可能会被系统设置或取用的flag变量: + +- **`flag:hard`**: 当前的难度标志;此flag变量在setInitData中被定义,可以直接取用来判定当前难度分歧。上传成绩时将根据此flag来对不同难度进行排序。 +- **`flag:posion`**, **`flag:weak`**, **`flag:curse`**: 中毒、衰弱、诅咒状态。 +- **`flag:no_zone`**, **`flag:no_snipe`**, **`flag:no_laser`**, **`flag:no_betweenAttack`**: 是否分别免疫领域、阻击、激光、夹击效果。 +- **`flag:hatred`**: 当前的仇恨数值。 +- **`flag:commonTimes`**: 全局商店共用次数时的访问次数。 +- **`flag:input`**: 接受用户输入的事件后,存放用户输入的结果。 +- **`flag:type`**, **`flag:keycode`**, **`flag:x`**, **`flag:y`**, **`flag:px`**, **`flag:py`**: 等待用户操作后,用户的操作类型,按键keycode或点击/像素坐标。 +- **`flag:skill`**, **`flag:skillName`**: 开启的技能编号和技能名。 +- **`flag:heroIcon`**: 当前的勇士行走图名称。 +- **`flag:saveEquips`**: 快速换装时保存的套装。 +- **`flag:__visited__`**: 当前访问过的楼层。 +- **`flag:__atk_buff__`**, **`flag:__def_buff__`**, **`flag:__mdef_buff__`**: 当前攻防魔防的实际计算比例加成。 +- **`flag:__color__`**, **`flag:__weather__`**, **`flag:__volume__`**: 当前的画面色调、天气和音量。 +- **`flag:__events__`**: 当前保存的事件列表,读档时会恢复(适用于在事件中存档) +- **`flag:textAttribute`**, **`flag:globalAttribute`**, **`flag:globalFlags`**: 当前的剧情文本属性,当前的全局属性,当前的全局开关。 +- **`flag:cannotMoveDirectly`**, **`flag:__noClickMove__`**: 当前是否不允许瞬间移动,当前用户是否开启了单击瞬移。 +- **`flag:hideStatusBar`**, **`flag:showToolbox`**: 是否隐藏状态栏,是否显示工具栏。 +- **`flag:debug`**, **`flag:__consoleOpened__`**: 当前是否开启了调试模式,是否开启了控制台。 +- **`flag:__seed__`**, **`flag:__rand__`**: 伪随机数生成种子和当前的状态 + +========================================================================================== + +[继续阅读脚本](script) diff --git a/_docs/script.md b/_docs/script.md new file mode 100644 index 00000000..c6d0aeff --- /dev/null +++ b/_docs/script.md @@ -0,0 +1,354 @@ +# 脚本 + +?> 目前版本**v2.6.1**,上次更新时间:* {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 (参数列表) { ... }`。** + +但是,对于`registerXXX`所注册的函数是无效的,例如直接复写`core.control._animationFrame_globalAnimate`函数是没有效果的。对于这种情况引入的函数,需要注册同名函数,可参见最下面的样例。 + +下面是几个例子,从简单到复杂。 + +### 重写怪物手册的背景图绘制,使用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)。 + +### 复写全局动画绘制函数 + +全局动画绘制在`control.js`的`_animationFrame_globalAnimate`函数。 + +注意到此函数是由`registerAnimationFrame`注册的,因此直接复写是无效的。 + +其在control.js的注册的定义如下: + +```js +// 注册全局动画函数 +this.registerAnimationFrame("globalAnimate", true, this._animationFrame_globalAnimate); +``` + +因此,可以在插件中自行注册一个**同名**的函数来覆盖原始的内容。 + +```js +// 插件中复写全局动画绘制函数 +this.myGlobalAnimate = function (timestamp) { + // ...... 实际复写的函数内容 +} + +// 注册同名(globalAnimate)函数来覆盖系统原始内容 +core.registerAnimationFrame("globalAnimate", true, "myGlobalAnimate"); +``` + + +========================================================================================== + +[继续阅读下一章:API列表](api) + + diff --git a/docs/start.md b/_docs/start.md similarity index 82% rename from docs/start.md rename to _docs/start.md index 6ab94659..87319f4c 100644 --- a/docs/start.md +++ b/_docs/start.md @@ -1,6 +1,6 @@ # 快速上手 -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * +?> 目前版本**v2.6.1**,上次更新时间:* {docsify-updated} * 在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔! @@ -10,6 +10,7 @@ - Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) - Mac系统则需OS X 10.7及以上,能正确打开启动服务(Mac版).app即可。 + - 安卓手机端需要下载最新版的HTML5安卓造塔器。 - Chrome浏览器。其他浏览器可能会导致本地服务器产生闪退等现象。 - 强烈推荐安装一个很好的文本编辑器:VSCode。在某些需要直接修改文件的场合,可能会非常重要。 @@ -19,7 +20,7 @@ ## 启动HTTP服务 -在根目录下有一个“启动服务.exe”,运行之。(Mac版本则双击运行“启动服务(Mac版).app”) +在根目录下有一个“启动服务.exe”,运行之。(Mac版本则双击运行“启动服务(Mac版).app”,手机端则使用造塔APP。) ![启动服务](img/server.png) @@ -27,6 +28,7 @@ * “地图编辑器”允许你以可视化的方式进行编辑地图。 * “便捷PS工具”能让你很方便的对自定义素材进行添加。参见[自定义素材](personalization#自定义素材)。 * “地图生成器”能让你从已有的截图(如RMXP项目)中立刻生成可被本样板识别的地图数据。 +* “怪物数据导出”能让你从RMXP中导出怪物数据而被H5魔塔使用。 * “RM动画导出器”能让你从RMXP中导出动画而被H5魔塔使用。 * “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。 * “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。 @@ -49,19 +51,25 @@ 绘制地图时可以右键弹出菜单,移动图块和事件。 +从V2.4.2开始,可以使用`Alt+0~9`对一个图块素材快速保存,`Ctrl+0~9`来快速选用。 + ### 从RMXP导入已有的地图 +!> 注:现在已经不推荐此方法,如需从RM刻塔请使用 [RM转H5刻塔器使用教程](https://www.bilibili.com/video/av43125840) 进行操作。 + 如果我们想复刻一个现有的,已经被RMXP所制作的塔,也有很便捷的方式,那就是用到我们的“地图生成器”。 首先,我们打开RMXP和对应的项目,可以看到它的地图。 ![绘制地图](./img/rmxp2.png) -我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 +我们打开地图编辑器,创建一个地图,宽高需要和RM中的地图一致。 + +之后,我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 ![绘制地图](./img/rmxp3.png) -截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是13x13。(如果地图小于13*13,请用星空或墙壁填充到13x13)。** +截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是创建的地图的大小(至少为13x13)。** 确认地图的图片文件已经复制到剪切板后,我们打开“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 @@ -71,7 +79,9 @@ !> **地图生成器默认只支持已被定义的素材。如果有自定义素材需求(例如原版的1层小塔那种素材),请先[导入并注册素材](#素材注册)后再进行操作。** -!> **请确保截图范围刚好为13x13,并且保证每个位置的像素都是32x32。** +!> **请确保截图范围为你创建的地图大小,并且保证每个位置的像素都是32x32。** + +!> **如果宽度超过13,地图生成器将无法显示完全,但是仍然可以粘贴到地图编辑器中进行修改。** !> **地图生成器靠左上角来确定偏移量,如果左上角是全黑或者星空之类的素材可能导致识别不准,此时请在左上角放置一个岩浆后再进行截图识别。** @@ -169,10 +179,20 @@ 之后刷新编辑器即可。 -对于怪物和道具,我们也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 +也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 素材注册完毕后,即可在游戏中正常使用,也可以被地图生成器所识别(需要重开地图生成器)。 +### 额外素材 + +从2.4.2开始,H5魔塔样板支持额外素材,你可以导入任意个类似RM中的tilesets文件,且无需注册即可使用。 + +要使用额外素材,请在`全塔属性`中的`tilesets`项,添加额外素材的图片名称,刷新后即可在地图编辑器中使用。 + +额外素材不可注册,其数字、ID和索引都是和该图块在图片上的位置相关,不可编辑。 + +有关额外素材的更多说明参见[额外素材](personalization#额外素材) + ## 控制台调试 HTML5的塔都是可以进行控制台调试的。 @@ -197,20 +217,34 @@ HTML5的塔都是可以进行控制台调试的。 - `core.getItem('pickaxe', 2)` 令勇士获得两个破墙镐。 - `core.itemCount('pickaxe')` 返回勇士某个道具的个数。 - `core.hasItem('pickaxe')` 返回勇士是否拥有某个道具。等价于`core.itemCount('pickaxe')!=0`。 +- `core.getEquip(0)` 返回0号装备类型(武器)的当前装备的itemId,不存在则返回null +- `core.hasEquip('sword1')` 返回某个装备当前是否处于被装备状态 - `core.setFlag('xxx', 1)` 设置某个flag/自定义变量的值。 - `core.getFlag('xxx', 10)` 获得某个flag/自定义变量的值;如果该项不存在(未被定义),则返回第二个参数的值。 - `core.hasFlag('xxx')` 返回是否存在某个变量且不为0。等价于`core.getFlag('xxx', 0)!=0`。 +- `core.removeFlag('xxx')` 删除某个flag/自定义变量 - `core.insertAction(list)` 执行一段自定义事件。比如 `core.insertAction(["剧情文本"])` 将执行一个剧情文本显示事件。 - `core.changeFloor('MT2', 'downFloor')` 立刻执行楼层切换到MT2层的下楼点位置。 - `core.changeFloor('MT5', null, {'x': 4, 'y': 7})` 立刻切换楼层到MT5层的(4,7)点。 - `core.getBlock(3, 5, 'MT1')` 获得当前地图上某一个块的信息。第三个参数为floorId,可省略表示当前楼层。 - `core.getBlockId(3, 5, 'MT1')` 获得当前地图上某一个点的图块ID。第三个参数为floorId,可省略表示当前楼层。 -- `core.resetMap()` 重置当前层地图。**当修改地图后再读档,修改的地图不会立刻生效,此时可以使用resetMap来重置当前楼层的地图。** -- `localStorage` 获得所有的存档数据。可以用 `core.getLocalStorage('save1')` 来具体获得某个存档。 +- `core.resetMap()` 重置当前层地图。 - …… 更多API和详细参数介绍可参见[API列表](api)。 +## 编辑器的基本操作 + +- **Alt+0~9, Ctrl+0~9** 保存和读取当前选中图块 +- **W/A/S/D** 移动大地图 +- **Ctrl+Z** 撤销上次绘图 +- **Ctrl+Y** 重做上次绘图 +- **PgUp/PgDn** 切换楼层 +- **Ctrl+S** 保存事件编辑器/脚本编辑器 +- **地图上单击** 选中该点 +- **地图上双击** 选中该点图块 +- **地图上右键** 弹出菜单栏,包括选中、复制、清除等操作 +- **事件编辑器中Ctrl+C, Ctrl+X, 右键等** 执行相应操作 ## 报错处理 diff --git a/_server/CodeMirror/LICENSE b/_server/CodeMirror/LICENSE index ff7db4b9..a139012a 100644 --- a/_server/CodeMirror/LICENSE +++ b/_server/CodeMirror/LICENSE @@ -19,3 +19,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------------------------------- + +The MIT License (MIT) + +Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/_server/CodeMirror/beautify.min.js b/_server/CodeMirror/beautify.min.js new file mode 100644 index 00000000..d57943a9 --- /dev/null +++ b/_server/CodeMirror/beautify.min.js @@ -0,0 +1 @@ +!function(){var t=function(u){var i={};function n(t){if(i[t])return i[t].exports;var e=i[t]={i:t,l:!1,exports:{}};return u[t].call(e.exports,e,e.exports,n),e.l=!0,e.exports}return n.m=u,n.c=i,n.d=function(t,e,u){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:u})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var u=Object.create(null);if(n.r(u),Object.defineProperty(u,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(u,i,function(t){return e[t]}.bind(null,i));return u},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,u){"use strict";var i=u(1).Beautifier,n=u(5).Options;t.exports=function(t,e){return new i(t,e).beautify()},t.exports.defaultOptions=function(){return new n}},function(t,e,u){"use strict";var i=u(2).Output,n=u(3).Token,o=u(4),_=u(5).Options,s=u(7).Tokenizer,p=u(7).line_starters,l=u(7).positionable_operators,f=u(7).TOKEN;function c(t,e){return-1!==e.indexOf(t)}function a(t,e){return t&&t.type===f.RESERVED&&t.text===e}function d(t,e){return t&&t.type===f.RESERVED&&c(t.text,e)}var b=["case","return","do","if","throw","else","await","break","continue","async"],g=function(t){for(var e={},u=0;uu&&(u=t.line_indent_level)),{mode:e,parent:t,last_token:t?t.last_token:new n(f.START_BLOCK,""),last_word:t?t.last_word:"",declaration_statement:!1,declaration_assignment:!1,multiline_frame:!1,inline_frame:!1,if_block:!1,else_block:!1,do_block:!1,do_while:!1,import_block:!1,in_case_statement:!1,in_case:!1,case_body:!1,indentation_level:u,alignment:0,line_indent_level:t?t.line_indent_level:u,start_line_index:this._output.get_line_number(),ternary_depth:0}},T.prototype._reset=function(t){var e=t.match(/^[\t ]*/)[0];this._last_last_text="",this._output=new i(this._options,e),this._output.raw=this._options.test_output_raw,this._flag_store=[],this.set_mode(m);var u=new s(t,this._options);return this._tokens=u.tokenize(),t},T.prototype.beautify=function(){if(this._options.disabled)return this._source_text;var t=this._reset(this._source_text),e=this._options.eol;"auto"===this._options.eol&&(e="\n",t&&o.lineBreak.test(t||"")&&(e=t.match(o.lineBreak)[0]));for(var u=this._tokens.next();u;)this.handle_token(u),this._last_last_text=this._flags.last_token.text,this._flags.last_token=u,u=this._tokens.next();return this._output.get_code(e)},T.prototype.handle_token=function(t,e){t.type===f.START_EXPR?this.handle_start_expr(t):t.type===f.END_EXPR?this.handle_end_expr(t):t.type===f.START_BLOCK?this.handle_start_block(t):t.type===f.END_BLOCK?this.handle_end_block(t):t.type===f.WORD?this.handle_word(t):t.type===f.RESERVED?this.handle_word(t):t.type===f.SEMICOLON?this.handle_semicolon(t):t.type===f.STRING?this.handle_string(t):t.type===f.EQUALS?this.handle_equals(t):t.type===f.OPERATOR?this.handle_operator(t):t.type===f.COMMA?this.handle_comma(t):t.type===f.BLOCK_COMMENT?this.handle_block_comment(t,e):t.type===f.COMMENT?this.handle_comment(t,e):t.type===f.DOT?this.handle_dot(t):t.type===f.EOF?this.handle_eof(t):(t.type,f.UNKNOWN,this.handle_unknown(t,e))},T.prototype.handle_whitespace_and_comments=function(t,e){var u=t.newlines,i=this._options.keep_array_indentation&&R(this._flags.mode);if(t.comments_before)for(var n=t.comments_before.next();n;)this.handle_whitespace_and_comments(n,e),this.handle_token(n,e),n=t.comments_before.next();if(i)for(var _=0;_this._options.max_preserve_newlines&&(u=this._options.max_preserve_newlines),this._options.preserve_newlines&&1this._flags.parent.indentation_level)&&(this._flags.indentation_level-=1,this._output.set_indent(this._flags.indentation_level,this._flags.alignment))},T.prototype.set_mode=function(t){this._flags?(this._flag_store.push(this._flags),this._previous_flags=this._flags):this._previous_flags=this.create_flags(null,t),this._flags=this.create_flags(this._previous_flags,t),this._output.set_indent(this._flags.indentation_level,this._flags.alignment)},T.prototype.restore_mode=function(){0"===this._flags.last_token.text?this.set_mode(m):c(this._flags.last_token.type,[f.EQUALS,f.START_EXPR,f.COMMA,f.OPERATOR])||d(this._flags.last_token,["return","throw","import","default"])?this.set_mode(r):this.set_mode(m);var i=!e.comments_before&&"}"===e.text,n=i&&"function"===this._flags.last_word&&this._flags.last_token.type===f.END_EXPR;if(this._options.brace_preserve_inline){var _=0,s=null;this._flags.inline_frame=!0;do{if(_+=1,(s=this._tokens.peek(_-1)).newlines){this._flags.inline_frame=!1;break}}while(s.type!==f.EOF&&(s.type!==f.END_BLOCK||s.opened!==t))}("expand"===this._options.brace_style||"none"===this._options.brace_style&&t.newlines)&&!this._flags.inline_frame?this._flags.last_token.type!==f.OPERATOR&&(n||this._flags.last_token.type===f.EQUALS||d(this._flags.last_token,b)&&"else"!==this._flags.last_token.text)?this._output.space_before_token=!0:this.print_newline(!1,!0):(!R(this._previous_flags.mode)||this._flags.last_token.type!==f.START_EXPR&&this._flags.last_token.type!==f.COMMA||((this._flags.last_token.type===f.COMMA||this._options.space_in_paren)&&(this._output.space_before_token=!0),(this._flags.last_token.type===f.COMMA||this._flags.last_token.type===f.START_EXPR&&this._flags.inline_frame)&&(this.allow_wrap_or_preserved_newline(t),this._previous_flags.multiline_frame=this._previous_flags.multiline_frame||this._flags.multiline_frame,this._flags.multiline_frame=!1)),this._flags.last_token.type!==f.OPERATOR&&this._flags.last_token.type!==f.START_EXPR&&(this._flags.last_token.type!==f.START_BLOCK||this._flags.inline_frame?this._output.space_before_token=!0:this.print_newline())),this.print_token(t),this.indent(),i||this._options.brace_preserve_inline&&this._flags.inline_frame||this.print_newline()},T.prototype.handle_end_block=function(t){for(this.handle_whitespace_and_comments(t);this._flags.mode===w;)this.restore_mode();var e=this._flags.last_token.type===f.START_BLOCK;this._flags.inline_frame&&!e?this._output.space_before_token=!0:"expand"===this._options.brace_style?e||this.print_newline():e||(R(this._flags.mode)&&this._options.keep_array_indentation?(this._options.keep_array_indentation=!1,this.print_newline(),this._options.keep_array_indentation=!0):this.print_newline()),this.restore_mode(),this.print_token(t)},T.prototype.handle_word=function(t){if(t.type===f.RESERVED)if(c(t.text,["set","get"])&&this._flags.mode!==r)t.type=f.WORD;else if("import"===t.text&&"("===this._tokens.peek().text)t.type=f.WORD;else if(c(t.text,["as","from"])&&!this._flags.import_block)t.type=f.WORD;else if(this._flags.mode===r){":"===this._tokens.peek().text&&(t.type=f.WORD)}if(this.start_of_statement(t)?d(this._flags.last_token,["var","let","const"])&&t.type===f.WORD&&(this._flags.declaration_statement=!0):!t.newlines||O(this._flags.mode)||this._flags.last_token.type===f.OPERATOR&&"--"!==this._flags.last_token.text&&"++"!==this._flags.last_token.text||this._flags.last_token.type===f.EQUALS||!this._options.preserve_newlines&&d(this._flags.last_token,["var","let","const","set","get"])?this.handle_whitespace_and_comments(t):(this.handle_whitespace_and_comments(t),this.print_newline()),this._flags.do_block&&!this._flags.do_while){if(a(t,"while"))return this._output.space_before_token=!0,this.print_token(t),this._output.space_before_token=!0,void(this._flags.do_while=!0);this.print_newline(),this._flags.do_block=!1}if(this._flags.if_block)if(!this._flags.else_block&&a(t,"else"))this._flags.else_block=!0;else{for(;this._flags.mode===w;)this.restore_mode();this._flags.if_block=!1,this._flags.else_block=!1}if(this._flags.in_case_statement&&d(t,["case","default"]))return this.print_newline(),(this._flags.case_body||this._options.jslint_happy)&&(this.deindent(),this._flags.case_body=!1),this.print_token(t),void(this._flags.in_case=!0);if(this._flags.last_token.type!==f.COMMA&&this._flags.last_token.type!==f.START_EXPR&&this._flags.last_token.type!==f.EQUALS&&this._flags.last_token.type!==f.OPERATOR||this.start_of_object_property()||this.allow_wrap_or_preserved_newline(t),a(t,"function"))return(c(this._flags.last_token.text,["}",";"])||this._output.just_added_newline()&&!c(this._flags.last_token.text,["(","[","{",":","=",","])&&this._flags.last_token.type!==f.OPERATOR)&&(this._output.just_added_blankline()||t.comments_before||(this.print_newline(),this.print_newline(!0))),this._flags.last_token.type===f.RESERVED||this._flags.last_token.type===f.WORD?d(this._flags.last_token,["get","set","new","export"])||d(this._flags.last_token,A)?this._output.space_before_token=!0:a(this._flags.last_token,"default")&&"export"===this._last_last_text?this._output.space_before_token=!0:"declare"===this._flags.last_token.text?this._output.space_before_token=!0:this.print_newline():this._flags.last_token.type===f.OPERATOR||"="===this._flags.last_token.text?this._output.space_before_token=!0:(this._flags.multiline_frame||!O(this._flags.mode)&&!R(this._flags.mode))&&this.print_newline(),this.print_token(t),void(this._flags.last_word=t.text);var e="NONE";(this._flags.last_token.type===f.END_BLOCK?this._previous_flags.inline_frame?e="SPACE":d(t,["else","catch","finally","from"])?"expand"===this._options.brace_style||"end-expand"===this._options.brace_style||"none"===this._options.brace_style&&t.newlines?e="NEWLINE":(e="SPACE",this._output.space_before_token=!0):e="NEWLINE":this._flags.last_token.type===f.SEMICOLON&&this._flags.mode===m?e="NEWLINE":this._flags.last_token.type===f.SEMICOLON&&O(this._flags.mode)?e="SPACE":this._flags.last_token.type===f.STRING?e="NEWLINE":this._flags.last_token.type===f.RESERVED||this._flags.last_token.type===f.WORD||"*"===this._flags.last_token.text&&(c(this._last_last_text,["function","yield"])||this._flags.mode===r&&c(this._last_last_text,["{",","]))?e="SPACE":this._flags.last_token.type===f.START_BLOCK?e=this._flags.inline_frame?"SPACE":"NEWLINE":this._flags.last_token.type===f.END_EXPR&&(this._output.space_before_token=!0,e="NEWLINE"),d(t,p)&&")"!==this._flags.last_token.text&&(e=this._flags.inline_frame||"else"===this._flags.last_token.text||"export"===this._flags.last_token.text?"SPACE":"NEWLINE"),d(t,["else","catch","finally"]))?(this._flags.last_token.type!==f.END_BLOCK||this._previous_flags.mode!==m||"expand"===this._options.brace_style||"end-expand"===this._options.brace_style||"none"===this._options.brace_style&&t.newlines)&&!this._flags.inline_frame?this.print_newline():(this._output.trim(!0),"}"!==this._output.current_line.last()&&this.print_newline(),this._output.space_before_token=!0):"NEWLINE"===e?d(this._flags.last_token,b)?this._output.space_before_token=!0:"declare"===this._flags.last_token.text&&d(t,["var","let","const"])?this._output.space_before_token=!0:this._flags.last_token.type!==f.END_EXPR?this._flags.last_token.type===f.START_EXPR&&d(t,["var","let","const"])||":"===this._flags.last_token.text||(a(t,"if")&&a(t.previous,"else")?this._output.space_before_token=!0:this.print_newline()):d(t,p)&&")"!==this._flags.last_token.text&&this.print_newline():this._flags.multiline_frame&&R(this._flags.mode)&&","===this._flags.last_token.text&&"}"===this._last_last_text?this.print_newline():"SPACE"===e&&(this._output.space_before_token=!0);!t.previous||t.previous.type!==f.WORD&&t.previous.type!==f.RESERVED||(this._output.space_before_token=!0),this.print_token(t),this._flags.last_word=t.text,t.type===f.RESERVED&&("do"===t.text?this._flags.do_block=!0:"if"===t.text?this._flags.if_block=!0:"import"===t.text?this._flags.import_block=!0:this._flags.import_block&&a(t,"from")&&(this._flags.import_block=!1))},T.prototype.handle_semicolon=function(t){this.start_of_statement(t)?this._output.space_before_token=!1:this.handle_whitespace_and_comments(t);for(var e=this._tokens.peek();!(this._flags.mode!==w||this._flags.if_block&&a(e,"else")||this._flags.do_block);)this.restore_mode();this._flags.import_block&&(this._flags.import_block=!1),this.print_token(t)},T.prototype.handle_string=function(t){this.start_of_statement(t)?this._output.space_before_token=!0:(this.handle_whitespace_and_comments(t),this._flags.last_token.type===f.RESERVED||this._flags.last_token.type===f.WORD||this._flags.inline_frame?this._output.space_before_token=!0:this._flags.last_token.type===f.COMMA||this._flags.last_token.type===f.START_EXPR||this._flags.last_token.type===f.EQUALS||this._flags.last_token.type===f.OPERATOR?this.start_of_object_property()||this.allow_wrap_or_preserved_newline(t):this.print_newline()),this.print_token(t)},T.prototype.handle_equals=function(t){this.start_of_statement(t)||this.handle_whitespace_and_comments(t),this._flags.declaration_statement&&(this._flags.declaration_assignment=!0),this._output.space_before_token=!0,this.print_token(t),this._output.space_before_token=!0},T.prototype.handle_comma=function(t){this.handle_whitespace_and_comments(t,!0),this.print_token(t),this._output.space_before_token=!0,this._flags.declaration_statement?(O(this._flags.parent.mode)&&(this._flags.declaration_assignment=!1),this._flags.declaration_assignment?(this._flags.declaration_assignment=!1,this.print_newline(!1,!0)):this._options.comma_first&&this.allow_wrap_or_preserved_newline(t)):this._flags.mode===r||this._flags.mode===w&&this._flags.parent.mode===r?(this._flags.mode===w&&this.restore_mode(),this._flags.inline_frame||this.print_newline()):this._options.comma_first&&this.allow_wrap_or_preserved_newline(t)},T.prototype.handle_operator=function(t){var e="*"===t.text&&(d(this._flags.last_token,["function","yield"])||c(this._flags.last_token.type,[f.START_BLOCK,f.COMMA,f.END_BLOCK,f.SEMICOLON])),u=c(t.text,["-","+"])&&(c(this._flags.last_token.type,[f.START_BLOCK,f.START_EXPR,f.EQUALS,f.OPERATOR])||c(this._flags.last_token.text,p)||","===this._flags.last_token.text);if(this.start_of_statement(t));else{var i=!e;this.handle_whitespace_and_comments(t,i)}if(d(this._flags.last_token,b))return this._output.space_before_token=!0,void this.print_token(t);if("*"!==t.text||this._flags.last_token.type!==f.DOT)if("::"!==t.text){if(this._flags.last_token.type===f.OPERATOR&&c(this._options.operator_position,k)&&this.allow_wrap_or_preserved_newline(t),":"===t.text&&this._flags.in_case)return this._flags.case_body=!0,this.indent(),this.print_token(t),this.print_newline(),void(this._flags.in_case=!1);var n=!0,_=!0,s=!1;if(":"===t.text?0===this._flags.ternary_depth?n=!1:(this._flags.ternary_depth-=1,s=!0):"?"===t.text&&(this._flags.ternary_depth+=1),!u&&!e&&this._options.preserve_newlines&&c(t.text,l)){var a=":"===t.text,o=a&&s,r=a&&!s;switch(this._options.operator_position){case g.before_newline:return this._output.space_before_token=!r,this.print_token(t),a&&!o||this.allow_wrap_or_preserved_newline(t),void(this._output.space_before_token=!0);case g.after_newline:return this._output.space_before_token=!0,!a||o?this._tokens.peek().newlines?this.print_newline(!1,!0):this.allow_wrap_or_preserved_newline(t):this._output.space_before_token=!1,this.print_token(t),void(this._output.space_before_token=!0);case g.preserve_newline:return r||this.allow_wrap_or_preserved_newline(t),n=!(this._output.just_added_newline()||r),this._output.space_before_token=n,this.print_token(t),void(this._output.space_before_token=!0)}}if(e){this.allow_wrap_or_preserved_newline(t),n=!1;var h=this._tokens.peek();_=h&&c(h.type,[f.WORD,f.RESERVED])}else"..."===t.text?(this.allow_wrap_or_preserved_newline(t),n=this._flags.last_token.type===f.START_BLOCK,_=!1):(c(t.text,["--","++","!","~"])||u)&&(this._flags.last_token.type!==f.COMMA&&this._flags.last_token.type!==f.START_EXPR||this.allow_wrap_or_preserved_newline(t),_=n=!1,!t.newlines||"--"!==t.text&&"++"!==t.text||this.print_newline(!1,!0),";"===this._flags.last_token.text&&O(this._flags.mode)&&(n=!0),this._flags.last_token.type===f.RESERVED?n=!0:this._flags.last_token.type===f.END_EXPR?n=!("]"===this._flags.last_token.text&&("--"===t.text||"++"===t.text)):this._flags.last_token.type===f.OPERATOR&&(n=c(t.text,["--","-","++","+"])&&c(this._flags.last_token.text,["--","-","++","+"]),c(t.text,["+","-"])&&c(this._flags.last_token.text,["--","++"])&&(_=!0)),(this._flags.mode!==m||this._flags.inline_frame)&&this._flags.mode!==w||"{"!==this._flags.last_token.text&&";"!==this._flags.last_token.text||this.print_newline());this._output.space_before_token=this._output.space_before_token||n,this.print_token(t),this._output.space_before_token=_}else this.print_token(t);else this.print_token(t)},T.prototype.handle_block_comment=function(t,e){if(this._output.raw)return this._output.add_raw_token(t),void(t.directives&&"end"===t.directives.preserve&&(this._output.raw=this._options.test_output_raw));if(t.directives)return this.print_newline(!1,e),this.print_token(t),"start"===t.directives.preserve&&(this._output.raw=!0),void this.print_newline(!1,!0);if(!o.newline.test(t.text)&&!t.newlines)return this._output.space_before_token=!0,this.print_token(t),void(this._output.space_before_token=!0);var u,i=function(t){for(var e=[],u=(t=t.replace(o.allLineBreaks,"\n")).indexOf("\n");-1!==u;)e.push(t.substring(0,u)),u=(t=t.substring(u+1)).indexOf("\n");return t.length&&e.push(t),e}(t.text),n=!1,_=!1,s=t.whitespace_before,a=s.length;if(this.print_newline(!1,e),this.print_token(t,i[0]),this.print_newline(!1,e),1this.__parent.wrap_line_length&&this.__wrap_point_character_count>this.__parent.next_line.__character_count},n.prototype._allow_wrap=function(){if(this._should_wrap()){this.__parent.add_new_line();var t=this.__parent.current_line;return t.set_indent(this.__wrap_point_indent_count,this.__wrap_point_alignment_count),t.__items=this.__items.slice(this.__wrap_point_index),this.__items=this.__items.slice(0,this.__wrap_point_index),t.__character_count+=this.__character_count-this.__wrap_point_character_count,this.__character_count=this.__wrap_point_character_count," "===t.__items[0]&&(t.__items.splice(0,1),t.__character_count-=1),!0}return!1},n.prototype.is_empty=function(){return 0===this.__items.length},n.prototype.last=function(){return this.is_empty()?null:this.__items[this.__items.length-1]},n.prototype.push=function(t){this.__items.push(t);var e=t.lastIndexOf("\n");-1!==e?this.__character_count=t.length-e:this.__character_count+=t.length},n.prototype.pop=function(){var t=null;return this.is_empty()||(t=this.__items.pop(),this.__character_count-=t.length),t},n.prototype._remove_indent=function(){0=this.__cache.length;)this.__add_column()},i.prototype.__add_column=function(){var t=this.__cache.length,e=0,u="";this.__indent_size&&t>=this.__indent_size&&(t-=(e=Math.floor(t/this.__indent_size))*this.__indent_size,u=new Array(e+1).join(this.__indent_string)),t&&(u+=new Array(t+1).join(" ")),this.__cache.push(u)},_.prototype.__add_outputline=function(){this.previous_line=this.current_line,this.current_line=this.next_line.clone_empty(),this.__lines.push(this.current_line)},_.prototype.get_line_number=function(){return this.__lines.length},_.prototype.get_indent_string=function(t,e){return this.__indent_cache.get_indent_string(t,e)},_.prototype.get_indent_size=function(t,e){return this.__indent_cache.get_indent_size(t,e)},_.prototype.is_empty=function(){return!this.previous_line&&this.current_line.is_empty()},_.prototype.add_new_line=function(t){return!(this.is_empty()||!t&&this.just_added_newline())&&(this.raw||this.__add_outputline(),!0)},_.prototype.get_code=function(t){this.trim(!0);var e=this.current_line.pop();e&&("\n"===e[e.length-1]&&(e=e.replace(/\n+$/g,"")),this.current_line.push(e)),this._end_with_newline&&this.__add_outputline();var u=this.__lines.join("\n");return"\n"!==t&&(u=u.replace(/[\n]/g,t)),u},_.prototype.set_wrap_point=function(){this.current_line._set_wrap_point()},_.prototype.set_indent=function(t,e){return t=t||0,e=e||0,this.next_line.set_indent(t,e),1>> === !== << && >= ** != == <= >> || < / - + > : & % ? ^ | *".split(" "),g=">>>= ... >>= <<= === >>> !== **= => ^= :: /= << <= == && -= >= >> != -- += ** || ++ %= &= *= |= = ! ? > < : / ^ - + * & % ~ |";g=(g=g.replace(/[-[\]{}()*+?.,\\^$|#]/g,"\\$&")).replace(/ /g,"|");var k,m=new RegExp(g),w="continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export".split(","),y=w.concat(["do","in","of","else","get","set","new","catch","finally","typeof","yield","async","await","from","as"]),x=new RegExp("^(?:"+y.join("|")+")$"),v=function(t,e){n.call(this,t,e),this._patterns.whitespace=this._patterns.whitespace.matching(/\u00A0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff/.source,/\u2028\u2029/.source);var u=new a(this._input),i=new o(this._input);i=(i=i.disable("handlebars")).disable("django"),this.__patterns={template:i,identifier:i.starting_with(r.identifier).matching(r.identifierMatch),number:u.matching(f),punct:u.matching(m),comment:u.starting_with(/\/\//).until(/[\n\r\u2028\u2029]/),block_comment:u.starting_with(/\/\*/).until_after(/\*\//),html_comment_start:u.matching(//),include:u.starting_with(/#include/).until_after(r.lineBreak),shebang:u.starting_with(/#!/).until_after(r.lineBreak),xml:u.matching(/[\s\S]*?<(\/?)([-a-zA-Z:0-9_.]+|{[\s\S]+?}|!\[CDATA\[[\s\S]*?\]\])(\s+{[\s\S]+?}|\s+[-a-zA-Z:0-9_.]+|\s+[-a-zA-Z:0-9_.]+\s*=\s*('[^']*'|"[^"]*"|{[\s\S]+?}))*\s*(\/?)\s*>/),single_quote:i.until(/['\\\n\r\u2028\u2029]/),double_quote:i.until(/["\\\n\r\u2028\u2029]/),template_text:i.until(/[`\\$]/),template_expression:i.until(/[`}\\]/)}};(v.prototype=new n)._is_comment=function(t){return t.type===p.COMMENT||t.type===p.BLOCK_COMMENT||t.type===p.UNKNOWN},v.prototype._is_opening=function(t){return t.type===p.START_BLOCK||t.type===p.START_EXPR},v.prototype._is_closing=function(t,e){return(t.type===p.END_BLOCK||t.type===p.END_EXPR)&&e&&("]"===t.text&&"["===e.text||")"===t.text&&"("===e.text||"}"===t.text&&"{"===e.text)},v.prototype._reset=function(){k=!1},v.prototype._get_next_token=function(t,e){var u=null;this._readWhitespace();var i=this._input.peek();return null===i?this._create_token(p.EOF,""):u=(u=(u=(u=(u=(u=(u=(u=(u=u||this._read_string(i))||this._read_word(t))||this._read_singles(i))||this._read_comment(i))||this._read_regexp(i,t))||this._read_xml(i,t))||this._read_non_javascript(i))||this._read_punctuation())||this._create_token(p.UNKNOWN,this._input.next())},v.prototype._read_word=function(t){var e;return""!==(e=this.__patterns.identifier.read())?(e=e.replace(r.allLineBreaks,"\n"),t.type!==p.DOT&&(t.type!==p.RESERVED||"set"!==t.text&&"get"!==t.text)&&x.test(e)?"in"===e||"of"===e?this._create_token(p.OPERATOR,e):this._create_token(p.RESERVED,e):this._create_token(p.WORD,e)):""!==(e=this.__patterns.number.read())?this._create_token(p.WORD,e):void 0},v.prototype._read_singles=function(t){var e=null;return"("===t||"["===t?e=this._create_token(p.START_EXPR,t):")"===t||"]"===t?e=this._create_token(p.END_EXPR,t):"{"===t?e=this._create_token(p.START_BLOCK,t):"}"===t?e=this._create_token(p.END_BLOCK,t):";"===t?e=this._create_token(p.SEMICOLON,t):"."===t&&d.test(this._input.peek(1))?e=this._create_token(p.DOT,t):","===t&&(e=this._create_token(p.COMMA,t)),e&&this._input.next(),e},v.prototype._read_punctuation=function(){var t=this.__patterns.punct.read();if(""!==t)return"="===t?this._create_token(p.EQUALS,t):this._create_token(p.OPERATOR,t)},v.prototype._read_non_javascript=function(t){var e="";if("#"===t){if(this._is_first_token()&&(e=this.__patterns.shebang.read()))return this._create_token(p.UNKNOWN,e.trim()+"\n");if(e=this.__patterns.include.read())return this._create_token(p.UNKNOWN,e.trim()+"\n");t=this._input.next();var u="#";if(this._input.hasNext()&&this._input.testChar(c)){for(;u+=t=this._input.next(),this._input.hasNext()&&"#"!==t&&"="!==t;);return"#"===t||("["===this._input.peek()&&"]"===this._input.peek(1)?(u+="[]",this._input.next(),this._input.next()):"{"===this._input.peek()&&"}"===this._input.peek(1)&&(u+="{}",this._input.next(),this._input.next())),this._create_token(p.WORD,u)}this._input.back()}else if("<"===t){if(e=this.__patterns.html_comment_start.read()){for(;this._input.hasNext()&&!this._input.testChar(r.newline);)e+=this._input.next();return k=!0,this._create_token(p.COMMENT,e)}}else if(k&&"-"===t&&(e=this.__patterns.html_comment_end.read()))return k=!1,this._create_token(p.COMMENT,e);return null},v.prototype._read_comment=function(t){var e=null;if("/"===t){var u="";if("*"===this._input.peek(1)){u=this.__patterns.block_comment.read();var i=l.get_directives(u);i&&"start"===i.ignore&&(u+=l.readIgnored(this._input)),u=u.replace(r.allLineBreaks,"\n"),(e=this._create_token(p.BLOCK_COMMENT,u)).directives=i}else"/"===this._input.peek(1)&&(u=this.__patterns.comment.read(),e=this._create_token(p.COMMENT,u))}return e},v.prototype._read_string=function(t){if("`"!==t&&"'"!==t&&'"'!==t)return null;var e=this._input.next();return this.has_char_escapes=!1,e+="`"===t?this._read_string_recursive("`",!0,"${"):this._read_string_recursive(t),this.has_char_escapes&&this._options.unescape_strings&&(e=function(t){var e="",u=0,i=new _(t),n=null;for(;i.hasNext();)if((n=i.match(/([\s]|[^\\]|\\\\)+/g))&&(e+=n[0]),"\\"===i.peek()){if(i.next(),"x"===i.peek())n=i.match(/x([0-9A-Fa-f]{2})/g);else{if("u"!==i.peek()){e+="\\",i.hasNext()&&(e+=i.next());continue}n=i.match(/u([0-9A-Fa-f]{4})/g)}if(!n)return t;if(126<(u=parseInt(n[1],16))&&u<=255&&0===n[0].indexOf("x"))return t;if(0<=u&&u<32){e+="\\"+n[0];continue}e+=34===u||39===u||92===u?"\\"+String.fromCharCode(u):String.fromCharCode(u)}return e}(e)),this._input.peek()===t&&(e+=this._input.next()),e=e.replace(r.allLineBreaks,"\n"),this._create_token(p.STRING,e)},v.prototype._allow_regexp_or_xml=function(t){return t.type===p.RESERVED&&h(t.text,["return","case","throw","else","do","typeof","yield"])||t.type===p.END_EXPR&&")"===t.text&&t.opened.previous.type===p.RESERVED&&h(t.opened.previous.text,["if","while","for"])||h(t.type,[p.COMMENT,p.START_EXPR,p.START_BLOCK,p.START,p.END_BLOCK,p.OPERATOR,p.EQUALS,p.EOF,p.SEMICOLON,p.COMMA])},v.prototype._read_regexp=function(t,e){if("/"===t&&this._allow_regexp_or_xml(e)){for(var u=this._input.next(),i=!1,n=!1;this._input.hasNext()&&(i||n||this._input.peek()!==t)&&!this._input.testChar(r.newline);)u+=this._input.peek(),i?i=!1:(i="\\"===this._input.peek(),"["===this._input.peek()?n=!0:"]"===this._input.peek()&&(n=!1)),this._input.next();return this._input.peek()===t&&(u+=this._input.next(),u+=this._input.read(r.identifier)),this._create_token(p.STRING,u)}return null},v.prototype._read_xml=function(t,e){if(this._options.e4x&&"<"===t&&this._allow_regexp_or_xml(e)){var u="",i=this.__patterns.xml.read_match();if(i){for(var n=i[2].replace(/^{\s+/,"{").replace(/\s+}$/,"}"),_=0===n.indexOf("{"),s=0;i;){var a=!!i[1],o=i[2];if(!(!!i[i.length-1]||"![CDATA["===o.slice(0,8))&&(o===n||_&&o.replace(/^{\s+/,"{").replace(/\s+}$/,"}"))&&(a?--s:++s),u+=i[0],s<=0)break;i=this.__patterns.xml.read_match()}return i||(u+=this._input.match(/[\s\S]*/g)[0]),u=u.replace(r.allLineBreaks,"\n"),this._create_token(p.STRING,u)}}return null},v.prototype._read_string_recursive=function(t,e,u){var i,n;"'"===t?n=this.__patterns.single_quote:'"'===t?n=this.__patterns.double_quote:"`"===t?n=this.__patterns.template_text:"}"===t&&(n=this.__patterns.template_expression);for(var _=n.read(),s="";this._input.hasNext();){if((s=this._input.next())===t||!e&&r.newline.test(s)){this._input.back();break}"\\"===s&&this._input.hasNext()?("x"===(i=this._input.peek())||"u"===i?this.has_char_escapes=!0:"\r"===i&&"\n"===this._input.peek(1)&&this._input.next(),s+=this._input.next()):u&&("${"===u&&"$"===s&&"{"===this._input.peek()&&(s+=this._input.next()),u===s&&(s+="`"===t?this._read_string_recursive("}",e,"`"):this._read_string_recursive("`",e,"${"),this._input.hasNext()&&(s+=this._input.next()))),_+=s+=n.read()}return _},t.exports.Tokenizer=v,t.exports.TOKEN=p,t.exports.positionable_operators=b.slice(),t.exports.line_starters=w.slice()},function(t,e,u){"use strict";var n=RegExp.prototype.hasOwnProperty("sticky");function i(t){this.__input=t||"",this.__input_length=this.__input.length,this.__position=0}i.prototype.restart=function(){this.__position=0},i.prototype.back=function(){0=t.length&&this.__input.substring(e-t.length,e).toLowerCase()===t},t.exports.InputScanner=i},function(t,e,u){"use strict";var i=u(8).InputScanner,_=u(3).Token,s=u(10).TokenStream,n=u(11).WhitespacePattern,a={START:"TK_START",RAW:"TK_RAW",EOF:"TK_EOF"},o=function(t,e){this._input=new i(t),this._options=e||{},this.__tokens=null,this._patterns={},this._patterns.whitespace=new n(this._input)};o.prototype.tokenize=function(){var t;this._input.restart(),this.__tokens=new s,this._reset();for(var e=new _(a.START,""),u=null,i=[],n=new s;e.type!==a.EOF;){for(t=this._get_next_token(e,u);this._is_comment(t);)n.add(t),t=this._get_next_token(e,u);n.isEmpty()||(t.comments_before=n,n=new s),t.parent=u,this._is_opening(t)?(i.push(u),u=t):u&&this._is_closing(t,u)&&((t.opened=u).closed=t,u=i.pop(),t.parent=u),(t.previous=e).next=t,this.__tokens.add(t),e=t}return this.__tokens},o.prototype._is_first_token=function(){return this.__tokens.isEmpty()},o.prototype._reset=function(){},o.prototype._get_next_token=function(t,e){this._readWhitespace();var u=this._input.read(/.+/g);return u?this._create_token(a.RAW,u):this._create_token(a.EOF,"")},o.prototype._is_comment=function(t){return!1},o.prototype._is_opening=function(t){return!1},o.prototype._is_closing=function(t,e){return!1},o.prototype._create_token=function(t,e){return new _(t,e,this._patterns.whitespace.newline_count,this._patterns.whitespace.whitespace_before_token)},o.prototype._readWhitespace=function(){return this._patterns.whitespace.read()},t.exports.Tokenizer=o,t.exports.TOKEN=a},function(t,e,u){"use strict";function i(t){this.__tokens=[],this.__tokens_length=this.__tokens.length,this.__position=0,this.__parent_token=t}i.prototype.restart=function(){this.__position=0},i.prototype.isEmpty=function(){return 0===this.__tokens_length},i.prototype.hasNext=function(){return this.__position/),erb:u.starting_with(/<%[^%]/).until_after(/[^%]%>/),django:u.starting_with(/{%/).until_after(/%}/),django_value:u.starting_with(/{{/).until_after(/}}/),django_comment:u.starting_with(/{#/).until_after(/#}/)}}(_.prototype=new i)._create=function(){return new _(this._input,this)},_.prototype._update=function(){this.__set_templated_pattern()},_.prototype.disable=function(t){var e=this._create();return e._disabled[t]=!0,e._update(),e},_.prototype.exclude=function(t){var e=this._create();return e._excluded[t]=!0,e._update(),e},_.prototype.read=function(){var t="";t=this._match_pattern?this._input.read(this._starting_pattern):this._input.read(this._starting_pattern,this.__template_pattern);for(var e=this._read_template();e;)this._match_pattern?e+=this._input.read(this._match_pattern):e+=this._input.readUntil(this.__template_pattern),t+=e,e=this._read_template();return this._until_after&&(t+=this._input.readUntilAfter(this._until_pattern)),t},_.prototype.__set_templated_pattern=function(){var t=[];this._disabled.php||t.push(this.__patterns.php._starting_pattern.source),this._disabled.handlebars||t.push(this.__patterns.handlebars._starting_pattern.source),this._disabled.erb||t.push(this.__patterns.erb._starting_pattern.source),this._disabled.django||(t.push(this.__patterns.django._starting_pattern.source),t.push(this.__patterns.django_value._starting_pattern.source),t.push(this.__patterns.django_comment._starting_pattern.source)),this._until_pattern&&t.push(this._until_pattern.source),this.__template_pattern=this._input.get_regexp("(?:"+t.join("|")+")")},_.prototype._read_template=function(){var t="",e=this._input.peek();if("<"===e){var u=this._input.peek(1);this._disabled.php||this._excluded.php||"?"!==u||(t=t||this.__patterns.php.read()),this._disabled.erb||this._excluded.erb||"%"!==u||(t=t||this.__patterns.erb.read())}else"{"===e&&(this._disabled.handlebars||this._excluded.handlebars||(t=(t=t||this.__patterns.handlebars_comment.read())||this.__patterns.handlebars.read()),this._disabled.django||(this._excluded.django||this._excluded.handlebars||(t=t||this.__patterns.django_value.read()),this._excluded.django||(t=(t=t||this.__patterns.django_comment.read())||this.__patterns.django.read())));return t},t.exports.TemplatablePattern=_}]);"function"==typeof define&&define.amd?define([],function(){return{js_beautify:t}}):"undefined"!=typeof exports?exports.js_beautify=t:"undefined"!=typeof window?window.js_beautify=t:"undefined"!=typeof global&&(global.js_beautify=t)}(); \ No newline at end of file diff --git a/_server/MotaAction.g4 b/_server/MotaAction.g4 new file mode 100644 index 00000000..d8593e39 --- /dev/null +++ b/_server/MotaAction.g4 @@ -0,0 +1,2944 @@ +grammar MotaAction; + +//===============parser=============== +//===blockly语句=== + +//事件 事件编辑器入口之一 +event_m + : '事件' BGNL? Newline '覆盖触发器' Bool '启用' Bool '通行状态' B_0_List '显伤' Bool BGNL? Newline action+ BEND + + +/* event_m +tooltip : 编辑魔塔的事件 +helpUrl : https://h5mota.com/games/template/docs/#/event +default : [false,null,null,null,null] +B_0_List_0=eval(B_0_List_0); +var code = { + 'trigger': Bool_0?'action':null, + 'enable': Bool_1, + 'noPass': B_0_List_0, + 'displayDamage': Bool_2, + 'data': 'data_asdfefw' +} +if (!Bool_0 && Bool_1 && (B_0_List_0===null) && Bool_2) code = 'data_asdfefw'; +code=JSON.stringify(code,null,2).split('"data_asdfefw"').join('[\n'+action_0+']\n'); +return code; +*/; + + +//升级 事件编辑器入口之一 +level_m + : '等级提升' BGNL? Newline levelCase+ BEND + + +/* level_m +tooltip : 升级事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%bb%8f%e9%aa%8c%e5%8d%87%e7%ba%a7%ef%bc%88%e8%bf%9b%e9%98%b6%2f%e5%a2%83%e7%95%8c%e5%a1%94%ef%bc%89 +var code = '[\n'+levelCase_0+']\n'; +return code; +*/; + +levelCase + : '需求' expression '称号' EvalString? '是否扣除经验' Bool BGNL? Newline action+ + + +/* levelCase +tooltip : 升级设定 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%bb%8f%e9%aa%8c%e5%8d%87%e7%ba%a7%ef%bc%88%e8%bf%9b%e9%98%b6%2f%e5%a2%83%e7%95%8c%e5%a1%94%ef%bc%89 +default : [0,"",false,null] +colour : this.subColor +Bool_0 = Bool_0?', "clear": true':''; +var code = '{"need": "'+expression_0+'", "title": "'+EvalString_0+'"'+Bool_0+', "action": [\n'+action_0+']},\n'; +return code; +*/; + +//商店 事件编辑器入口之一 +shop_m + : '全局商店列表' BGNL? Newline shoplist+ + +/* shop_m +tooltip : 全局商店列表 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e5%85%a8%e5%b1%80%e5%95%86%e5%ba%97 +var code = '['+shoplist_0+']\n'; +return code; +*/; + +shoplist + : shopsub + | shopcommonevent + | emptyshop + ; + +emptyshop + : Newline + + +/* emptyshop +var code = ' \n'; +return code; +*/; + +shopcommonevent + : '商店 id' IdString '快捷商店栏中名称' EvalString BGNL? '未开启状态则不显示在列表中' Bool BGNL? '执行的公共事件 id' EvalString '参数列表' EvalString? + +/* shopcommonevent +tooltip : 全局商店, 执行一个公共事件 +helpUrl : https://h5mota.com/games/template/docs/#/ +default : ["shop1","回收钥匙商店",false,"回收钥匙商店",""] +if (EvalString_2) { + if (EvalString_2.indexOf('"')>=0) + throw new Error('请勿在此处使用双引号!尝试使用单引号吧~'); + // 检查是不是数组 + try { + EvalString_2 = JSON.parse(EvalString_2.replace(/'/g, '"')); + if (!(EvalString_2 instanceof Array)) throw new Error(); + } + catch (e) { + throw new Error('参数列表必须是个有效的数组!'); + } +} +var code = { + 'id': IdString_0, + 'textInList': EvalString_0, + 'mustEnable': Bool_0, + 'commonEvent': EvalString_1 +} +if (EvalString_2) code.args = EvalString_2; +code=JSON.stringify(code,null,2)+',\n'; +return code; +*/; + +shopsub + : '商店 id' IdString '标题' EvalString '图标' IdString BGNL? Newline '快捷商店栏中名称' EvalString '共用times' Bool BGNL? Newline '未开启状态则不显示在列表中' Bool BGNL? NewLine '使用' ShopUse_List '消耗' EvalString BGNL? Newline '显示文字' EvalString BGNL? Newline shopChoices+ BEND + + +/* shopsub +tooltip : 全局商店,消耗填-1表示每个选项的消耗不同,正数表示消耗数值 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e5%85%a8%e5%b1%80%e5%95%86%e5%ba%97 +default : ["shop1","贪婪之神","blueShop","1F金币商店",false,false,null,"20+10*times*(times+1)","勇敢的武士啊, 给我${need}金币就可以:"] +var code = { + 'id': IdString_0, + 'name': EvalString_0, + 'icon': IdString_1, + 'textInList': EvalString_1, + 'commonTimes': Bool_0, + 'mustEnable': Bool_1, + 'use': ShopUse_List_0, + 'need': EvalString_2, + 'text': EvalString_3, + 'choices': 'choices_asdfefw' +} +code=JSON.stringify(code,null,2).split('"choices_asdfefw"').join('[\n'+shopChoices_0+']\n')+',\n'; +return code; +*/; + +shopChoices + : '商店选项' EvalString '消耗' EvalString? BGNL? Newline shopEffect+ + + +/* shopChoices +tooltip : 商店选项,商店消耗是-1时,这里的消耗对应各自选项的消耗,商店消耗不是-1时这里的消耗不填 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e5%85%a8%e5%b1%80%e5%95%86%e5%ba%97 +default : ["攻击+1",""] +colour : this.subColor +EvalString_1 = EvalString_1 && (', "need": "'+EvalString_1+'"'); +var code = '{"text": "'+EvalString_0+'"'+EvalString_1+', "effect": "'+shopEffect_0.slice(2,-1)+'"},\n'; +return code; +*/; + +shopEffect + : idString_e '+=' expression + + +/* shopEffect +colour : this.subColor +var code = idString_e_0+'+='+expression_0+';' +return code; +*/; + +//afterBattle 事件编辑器入口之一 +afterBattle_m + : '战斗结束后' BGNL? Newline action+ BEND + + +/* afterBattle_m +tooltip : 系统引发的自定义事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%b3%bb%e7%bb%9f%e5%bc%95%e5%8f%91%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6 +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//afterGetItem 事件编辑器入口之一 +afterGetItem_m + : '获取道具后' BGNL? Newline action+ BEND + + +/* afterGetItem_m +tooltip : 系统引发的自定义事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%b3%bb%e7%bb%9f%e5%bc%95%e5%8f%91%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6 +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//afterOpenDoor 事件编辑器入口之一 +afterOpenDoor_m + : '打开门后' BGNL? Newline action+ BEND + + +/* afterOpenDoor_m +tooltip : 系统引发的自定义事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%b3%bb%e7%bb%9f%e5%bc%95%e5%8f%91%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6 +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//firstArrive 事件编辑器入口之一 +firstArrive_m + : '首次到达楼层' BGNL? Newline action+ BEND + + +/* firstArrive_m +tooltip : 首次到达楼层 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%b3%bb%e7%bb%9f%e5%bc%95%e5%8f%91%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6 +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//eachArrive 事件编辑器入口之一 +eachArrive_m + : '每次到达楼层' BGNL? Newline action+ BEND + + +/* eachArrive_m +tooltip : 每次到达楼层 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=%e7%b3%bb%e7%bb%9f%e5%bc%95%e5%8f%91%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6 +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//changeFloor 事件编辑器入口之一 +changeFloor_m + : '楼梯, 传送门' BGNL? Newline Floor_List IdString? Stair_List 'x' Number ',' 'y' Number '朝向' DirectionEx_List '动画时间' Int? '允许穿透' Bool BEND + + +/* changeFloor_m +tooltip : 楼梯, 传送门, 如果目标楼层有多个楼梯, 写upFloor或downFloor可能会导致到达的楼梯不确定, 这时候请使用loc方式来指定具体的点位置 +helpUrl : https://h5mota.com/games/template/docs/#/element?id=%e8%b7%af%e9%9a%9c%ef%bc%8c%e6%a5%bc%e6%a2%af%ef%bc%8c%e4%bc%a0%e9%80%81%e9%97%a8 +default : [null,"MT1",null,0,0,null,500,null] +var toFloorId = IdString_0; +if (Floor_List_0!='floorId') toFloorId = Floor_List_0; +var loc = ', "loc": ['+Number_0+', '+Number_1+']'; +if (Stair_List_0==='now')loc = ''; +else if (Stair_List_0!=='loc')loc = ', "stair": "'+Stair_List_0+'"'; +DirectionEx_List_0 = DirectionEx_List_0 && (', "direction": "'+DirectionEx_List_0+'"'); +Int_0 = (Int_0!=='') ?(', "time": '+Int_0):''; +Bool_0 = Bool_0 ?'':(', "ignoreChangeFloor": false'); +var code = '{"floorId": "'+toFloorId+'"'+loc+DirectionEx_List_0+Int_0+Bool_0+' }\n'; +return code; +*/; + +//commonEvent 事件编辑器入口之一 +commonEvent_m + : '公共事件' BGNL? Newline action+ BEND + + +/* commonEvent_m +tooltip : 公共事件 +helpUrl : https://h5mota.com/games/template/docs/#/event +var code = '[\n'+action_0+']\n'; +return code; +*/; + +//为了避免关键字冲突,全部加了_s +//动作 +action + : text_0_s + | text_1_s + | comment_s + | autoText_s + | scrollText_s + | setText_s + | tip_s + | setValue_s + | addValue_s + | setFloor_s + | setGlobalAttribute_s + | setGlobalValue_s + | setGlobalFlag_s + | show_s + | hide_s + | trigger_s + | insert_1_s + | insert_2_s + | revisit_s + | exit_s + | setBlock_s + | showFloorImg_s + | hideFloorImg_s + | showBgFgMap_s + | hideBgFgMap_s + | setBgFgBlock_s + | setHeroIcon_s + | update_s + | showStatusBar_s + | hideStatusBar_s + | updateEnemys_s + | sleep_s + | wait_s + | waitAsync_s + | battle_s + | battle_1_s + | openDoor_s + | closeDoor_s + | changeFloor_s + | changePos_0_s + | changePos_1_s + | useItem_s + | openShop_s + | disableShop_s + | follow_s + | unfollow_s + | animate_s + | vibrate_s + | showImage_s + | showImage_1_s + | hideImage_s + | showTextImage_s + | moveImage_s + | showGif_0_s + | showGif_1_s + | setCurtain_0_s + | setCurtain_1_s + | screenFlash_s + | setWeather_s + | move_s + | moveHero_s + | jump_s + | jumpHero_s + | playBgm_s + | pauseBgm_s + | resumeBgm_s + | loadBgm_s + | freeBgm_s + | playSound_s + | stopSound_s + | setVolume_s + | win_s + | lose_s + | restart_s + | if_s + | if_1_s + | switch_s + | while_s + | dowhile_s + | break_s + | continue_s + | input_s + | input2_s + | choices_s + | confirm_s + | callBook_s + | callSave_s + | autoSave_s + | callLoad_s + | unknown_s + | function_s + | pass_s + ; + +text_0_s + : '显示文章' ':' EvalString Newline + + +/* text_0_s +tooltip : text:显示一段文字(剧情) +helpUrl : https://h5mota.com/games/template/docs/#/event?id=text%EF%BC%9A%E6%98%BE%E7%A4%BA%E4%B8%80%E6%AE%B5%E6%96%87%E5%AD%97%EF%BC%88%E5%89%A7%E6%83%85%EF%BC%89 +default : ["欢迎使用事件编辑器(双击方块进入多行编辑)"] +var code = '"'+EvalString_0+'",\n'; +return code; +*/; + +text_1_s + : '标题' EvalString? '图像' IdString? '对话框效果' EvalString? ':' EvalString Newline + + +/* text_1_s +tooltip : text:显示一段文字(剧情),选项较多请右键点击帮助 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=text%EF%BC%9A%E6%98%BE%E7%A4%BA%E4%B8%80%E6%AE%B5%E6%96%87%E5%AD%97%EF%BC%88%E5%89%A7%E6%83%85%EF%BC%89 +default : ["小妖精","fairy","","欢迎使用事件编辑器(双击方块进入多行编辑)"] +var title=''; +if (EvalString_0==''){ + if (IdString_0=='')title=''; + else title='\\t['+IdString_0+']'; +} else { + if (IdString_0=='')title='\\t['+EvalString_0+']'; + else title='\\t['+EvalString_0+','+IdString_0+']'; +} +if(EvalString_1 && !(/^(up|down)(,hero)?(,([+-]?\d+),([+-]?\d+))?$/.test(EvalString_1))) { + throw new Error('对话框效果的用法请右键点击帮助'); +} +EvalString_1 = EvalString_1 && ('\\b['+EvalString_1+']'); +var code = '"'+title+EvalString_1+EvalString_2+'",\n'; +return code; +*/; + +comment_s + : '添加注释' ':' EvalString Newline + + +/* comment_s +tooltip : comment:添加一段会被游戏跳过的注释内容 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=comment%ef%bc%9a%e6%b7%bb%e5%8a%a0%e6%b3%a8%e9%87%8a +default : ["可以在这里写添加任何注释内容"] +colour : this.commentColor +var code = '{"type": "comment", "text": "'+EvalString_0+'"},\n'; +return code; +*/; + +autoText_s + : '自动剧情文本: 标题' EvalString? '图像' IdString? '对话框效果' EvalString? '时间' Int BGNL? EvalString Newline + + +/* autoText_s +tooltip : autoText:自动剧情文本,用户无法跳过自动剧情文本,大段剧情文本请添加“是否跳过剧情”的提示 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=autotext%EF%BC%9A%E8%87%AA%E5%8A%A8%E5%89%A7%E6%83%85%E6%96%87%E6%9C%AC +default : ["小妖精","fairy","",3000,"用户无法跳过自动剧情文本,大段剧情文本请添加“是否跳过剧情”的提示"] +var title=''; +if (EvalString_0==''){ + if (IdString_0=='')title=''; + else title='\\t['+IdString_0+']'; +} else { + if (IdString_0=='')title='\\t['+EvalString_0+']'; + else title='\\t['+EvalString_0+','+IdString_0+']'; +} +if(EvalString_1 && !(/^(up|down)(,hero)?(,([+-]?\d+),([+-]?\d+))?$/.test(EvalString_1))) { + throw new Error('对话框效果的用法请右键点击帮助'); +} +EvalString_1 = EvalString_1 && ('\\b['+EvalString_1+']'); +var code = '{"type": "autoText", "text": "'+title+EvalString_1+EvalString_2+'", "time" :'+Int_0+'},\n'; +return code; +*/; + +scrollText_s + : '滚动剧情文本:' '时间' Int '行距' Number '不等待执行完毕' Bool? BGNL? EvalString Newline + + +/* scrollText_s +tooltip : scrollText:滚动剧情文本,将从下到上进行滚动显示。 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=scrollText%ef%bc%9a%e6%bb%9a%e5%8a%a8%e5%89%a7%e6%83%85%e6%96%87%e6%9c%ac +default : [5000,1.4,false,"时间是总时间,可以使用setText事件来控制字体、颜色、大小、偏移量等"] +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "scrollText", "text": "'+EvalString_0+'"'+Bool_0+', "time" :'+Int_0+', "lineHeight": '+Number_0+'},\n'; +return code; +*/; + +setText_s + : '设置剧情文本的属性' '位置' SetTextPosition_List '偏移像素' EvalString? '对齐' SetTextAlign_List? BGNL? '标题颜色' EvalString? Colour '正文颜色' EvalString? Colour '背景色' EvalString? Colour BGNL? '粗体' B_1_List '标题字体大小' EvalString? '正文字体大小' EvalString? '打字间隔' EvalString? Newline + + +/* setText_s +tooltip : setText:设置剧情文本的属性,颜色为RGB三元组或RGBA四元组,打字间隔为剧情文字添加的时间间隔,为整数或不填 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=settext%EF%BC%9A%E8%AE%BE%E7%BD%AE%E5%89%A7%E6%83%85%E6%96%87%E6%9C%AC%E7%9A%84%E5%B1%9E%E6%80%A7 +default : [null,"",null,"",'rgba(255,255,255,1)',"",'rgba(255,255,255,1)',"",'rgba(255,255,255,1)',null,"","",""] +SetTextPosition_List_0 =SetTextPosition_List_0==='null'?'': ', "position": "'+SetTextPosition_List_0+'"'; +SetTextAlign_List_0 =SetTextAlign_List_0==='null'?'': ', "align": "'+SetTextAlign_List_0+'"'; +var colorRe = MotaActionFunctions.pattern.colorRe; +if (EvalString_0) { + if (!/^\d+$/.test(EvalString_0))throw new Error('像素偏移量必须是整数或不填'); + EvalString_0 = ', "offset": '+EvalString_0; +} +if (EvalString_1) { + if (!colorRe.test(EvalString_1))throw new Error('颜色格式错误,形如:0~255,0~255,0~255,0~1'); + EvalString_1 = ', "title": ['+EvalString_1+']'; +} +if (EvalString_2) { + if (!colorRe.test(EvalString_2))throw new Error('颜色格式错误,形如:0~255,0~255,0~255,0~1'); + EvalString_2 = ', "text": ['+EvalString_2+']'; +} +if (EvalString_3) { + if (colorRe.test(EvalString_3)) { + EvalString_3 = ', "background": ['+EvalString_3+']'; + } + else if (/^\w+\.png$/.test(EvalString_3)) { + EvalString_3 = ', "background": "'+EvalString_3+'"'; + } + else { + throw new Error('背景格式错误,必须是形如0~255,0~255,0~255,0~1的颜色,或一个WindowSkin的png图片名称'); + } +} +if (EvalString_4) { + if (!/^\d+$/.test(EvalString_4))throw new Error('字体大小必须是整数或不填'); + EvalString_4 = ', "titlefont": '+EvalString_4; +} +if (EvalString_5) { + if (!/^\d+$/.test(EvalString_5))throw new Error('字体大小必须是整数或不填'); + EvalString_5 = ', "textfont": '+EvalString_5; +} +if (EvalString_6) { + if (!/^\d+$/.test(EvalString_6))throw new Error('打字时间间隔必须是整数或不填'); + EvalString_6 = ', "time": '+EvalString_6; +} +B_1_List_0 = B_1_List_0==='null'?'':', "bold": '+B_1_List_0; +var code = '{"type": "setText"'+SetTextPosition_List_0+EvalString_0+SetTextAlign_List_0+EvalString_1+EvalString_2+B_1_List_0+EvalString_3+EvalString_4+EvalString_5+EvalString_6+'},\n'; +return code; +*/; + +tip_s + : '显示提示' ':' EvalString '图标ID' IdString? Newline + + +/* tip_s +tooltip : tip:显示一段提示文字 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=tip%EF%BC%9A%E6%98%BE%E7%A4%BA%E4%B8%80%E6%AE%B5%E6%8F%90%E7%A4%BA%E6%96%87%E5%AD%97 +default : ["这段话将在左上角以气泡形式显示",""] +IdString_0 = IdString_0 && (', "icon": "' + IdString_0 + '"'); +var code = '{"type": "tip", "text": "'+EvalString_0+'"'+IdString_0+'},\n'; +return code; +*/; + +setValue_s + : '数值操作' ':' '名称' idString_e '值' expression Newline + + +/* setValue_s +tooltip : setValue:设置勇士的某个属性、道具个数, 或某个变量/Flag的值 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setvalue%EF%BC%9A%E8%AE%BE%E7%BD%AE%E5%8B%87%E5%A3%AB%E7%9A%84%E6%9F%90%E4%B8%AA%E5%B1%9E%E6%80%A7%E3%80%81%E9%81%93%E5%85%B7%E4%B8%AA%E6%95%B0%EF%BC%8C%E6%88%96%E6%9F%90%E4%B8%AA%E5%8F%98%E9%87%8Fflag%E7%9A%84%E5%80%BC +colour : this.dataColor +var code = '{"type": "setValue", "name": "'+idString_e_0+'", "value": "'+expression_0+'"},\n'; +return code; +*/; + +addValue_s + : '数值增减' ':' '名称' idString_e '+=' expression Newline + + +/* addValue_s +tooltip : addValue:增减勇士的某个属性、道具个数, 或某个变量/Flag的值 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=addValue%ef%bc%9a%e5%a2%9e%e5%87%8f%e5%8b%87%e5%a3%ab%e7%9a%84%e6%9f%90%e4%b8%aa%e5%b1%9e%e6%80%a7%e3%80%81%e9%81%93%e5%85%b7%e4%b8%aa%e6%95%b0%ef%bc%8c%e6%88%96%e6%9f%90%e4%b8%aa%e5%8f%98%e9%87%8f%2fFlag%e7%9a%84%e5%80%bc +colour : this.dataColor +var code = '{"type": "addValue", "name": "'+idString_e_0+'", "value": "'+expression_0+'"},\n'; +return code; +*/; + +setFloor_s + : '设置楼层属性' ':' Floor_Meta_List '楼层名' IdString? '值' EvalString Newline + + +/* setFloor_s +tooltip : setFloor:设置楼层属性;该楼层属性和编辑器中的楼层属性一一对应 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setFloor%ef%bc%9a%e8%ae%be%e7%bd%ae%e6%a5%bc%e5%b1%82%e5%b1%9e%e6%80%a7 +default : ["title","","'字符串类型的值要加引号,其他类型则不用'"] +colour : this.dataColor +IdString_0 = IdString_0 && (', "floorId": "'+IdString_0+'"'); +var code = '{"type": "setFloor", "name": "'+Floor_Meta_List_0+'"'+IdString_0+', "value": "'+EvalString_0+'"},\n'; +return code; +*/; + + +setGlobalAttribute_s + : '设置全局属性' ':' Global_Attribute_List '值' EvalString Newline + + +/* setGlobalAttribute_s +tooltip : setGlobalAttribute:设置全局属性 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setGlobalAttribute%ef%bc%9a%e8%ae%be%e7%bd%ae%e4%b8%80%e4%b8%aa%e5%85%a8%e5%b1%80%e5%b1%9e%e6%80%a7 +default : ["font","Verdana"] +colour : this.dataColor +var code = '{"type": "setGlobalAttribute", "name": "'+Global_Attribute_List_0+'", "value": "'+EvalString_0+'"},\n'; +return code; +*/; + + +setGlobalValue_s + : '设置全局数值' ':' Global_Value_List '值' EvalString Newline + + +/* setGlobalValue_s +tooltip : setGlobalValue:设置全局属性 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setGlobalValue%ef%bc%9a%e8%ae%be%e7%bd%ae%e4%b8%80%e4%b8%aa%e5%85%a8%e5%b1%80%e6%95%b0%e5%80%bc +default : ["lavaDamage","100"] +colour : this.dataColor +var code = '{"type": "setGlobalValue", "name": "'+Global_Value_List_0+'", "value": '+EvalString_0+'},\n'; +return code; +*/; + + +setGlobalFlag_s + : '设置系统开关' ':' Global_Flag_List Bool Newline + + +/* setGlobalFlag_s +tooltip : setGlobalFlag:设置系统开关 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setGlobalFlag%ef%bc%9a%e8%ae%be%e7%bd%ae%e4%b8%80%e4%b8%aa%e7%b3%bb%e7%bb%9f%e5%bc%80%e5%85%b3 +default : ["enableFloor","true"] +colour : this.dataColor +var code = '{"type": "setGlobalFlag", "name": "'+Global_Flag_List_0+'", "value": '+Bool_0+'},\n'; +return code; +*/; + + +show_s + : '显示事件' 'x' EvalString? ',' 'y' EvalString? '楼层' IdString? '动画时间' Int? '不等待执行完毕' Bool? Newline + + +/* show_s +tooltip : show: 将禁用事件启用,楼层和动画时间可不填,xy可用逗号分隔表示多个点 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=show%EF%BC%9A%E5%B0%86%E4%B8%80%E4%B8%AA%E7%A6%81%E7%94%A8%E4%BA%8B%E4%BB%B6%E5%90%AF%E7%94%A8 +default : ["","","",500,false] +colour : this.mapColor +var floorstr = ''; +if (EvalString_0 && EvalString_1) { + var pattern1 = MotaActionFunctions.pattern.id; + if(pattern1.test(EvalString_0) || pattern1.test(EvalString_1)){ + EvalString_0=MotaActionFunctions.PosString_pre(EvalString_0); + EvalString_1=MotaActionFunctions.PosString_pre(EvalString_1); + EvalString_0=[EvalString_0,EvalString_1] + } else { + var pattern2 = /^([+-]?\d+)(,[+-]?\d+)*$/; + if(!pattern2.test(EvalString_0) || !pattern2.test(EvalString_1))throw new Error('坐标格式错误,请右键点击帮助查看格式'); + EvalString_0=EvalString_0.split(','); + EvalString_1=EvalString_1.split(','); + if(EvalString_0.length!==EvalString_1.length)throw new Error('坐标格式错误,请右键点击帮助查看格式'); + for(var ii=0;ii=0) + throw new Error('请勿在此处使用双引号!尝试使用单引号吧~'); + // 检查是不是数组 + try { + if (!(JSON.parse(EvalString_1.replace(/'/g, '"')) instanceof Array)) throw new Error(); + } + catch (e) { + throw new Error('参数列表必须是个有效的数组!'); + } + EvalString_1 = ', "args": ' +EvalString_1; +} +var code = '{"type": "insert", "name": "'+EvalString_0+'"'+EvalString_1+'},\n'; +return code; +*/; + +insert_2_s + : '插入事件' 'x' PosString ',' 'y' PosString Event_List? '楼层' IdString? '参数列表' EvalString? ENewline + + +/* insert_2_s +tooltip : insert: 立即插入另一个地点的事件执行,当前事件不会中断,事件坐标不会改变 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=insert%ef%bc%9a%e6%8f%92%e5%85%a5%e5%85%ac%e5%85%b1%e4%ba%8b%e4%bb%b6%e6%88%96%e5%8f%a6%e4%b8%80%e4%b8%aa%e5%9c%b0%e7%82%b9%e7%9a%84%e4%ba%8b%e4%bb%b6%e5%b9%b6%e6%89%a7%e8%a1%8c +default : ["0","0",null,"",""] +colour : this.eventColor +IdString_0 = IdString_0 && (', "floorId": "'+IdString_0+'"'); +if (EvalString_0) { + if (EvalString_0.indexOf('"')>=0) + throw new Error('请勿在此处使用双引号!尝试使用单引号吧~'); + try { + if (!(JSON.parse(EvalString_0.replace(/'/g, '"')) instanceof Array)) throw new Error(); + } + catch (e) { + throw new Error('参数列表必须是个有效的数组!'); + } + EvalString_0 = ', "args": ' +EvalString_0; +} +if (Event_List_0 && Event_List_0 !=='null') + Event_List_0 = ', "which": "'+Event_List_0+'"'; +else Event_List_0 = ''; +var code = '{"type": "insert", "loc": ['+PosString_0+','+PosString_1+']'+Event_List_0+IdString_0+EvalString_0+'},\n'; +return code; +*/; + +revisit_s + : '重启当前事件' Newline + + +/* revisit_s +tooltip : revisit: 立即重启当前事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=revisit%EF%BC%9A%E7%AB%8B%E5%8D%B3%E9%87%8D%E5%90%AF%E5%BD%93%E5%89%8D%E4%BA%8B%E4%BB%B6 +colour : this.eventColor +var code = '{"type": "revisit"},\n'; +return code; +*/; + +exit_s + : '立刻结束当前事件' Newline + + +/* exit_s +tooltip : exit: 立刻结束当前事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=exit%EF%BC%9A%E7%AB%8B%E5%88%BB%E7%BB%93%E6%9D%9F%E5%BD%93%E5%89%8D%E4%BA%8B%E4%BB%B6 +colour : this.eventColor +var code = '{"type": "exit"},\n'; +return code; +*/; + +setBlock_s + : '转变图块为' EvalString 'x' PosString? ',' 'y' PosString? '楼层' IdString? Newline + + +/* setBlock_s +tooltip : setBlock:设置某个图块,忽略坐标楼层则为当前事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setblock%EF%BC%9A%E8%AE%BE%E7%BD%AE%E6%9F%90%E4%B8%AA%E5%9B%BE%E5%9D%97 +colour : this.mapColor +default : ["yellowDoor","","",""] +var floorstr = ''; +if (PosString_0 && PosString_1) { + floorstr = ', "loc": ['+PosString_0+','+PosString_1+']'; +} +IdString_0 = IdString_0 && (', "floorId": "'+IdString_0+'"'); +var code = '{"type": "setBlock", "number": "'+EvalString_0+'"'+floorstr+IdString_0+'},\n'; +return code; +*/; + +showFloorImg_s + : '显示贴图' 'x' EvalString? ',' 'y' EvalString? '楼层' IdString? Newline + + +/* showFloorImg_s +tooltip : showFloorImg: 显示一个贴图,xy为左上角坐标,可用逗号分隔表示多个点 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=showFloorImg%ef%bc%9a%e6%98%be%e7%a4%ba%e8%b4%b4%e5%9b%be +default : ["","",""] +colour : this.mapColor +var floorstr = ''; +if (EvalString_0 && EvalString_1) { + var pattern1 = MotaActionFunctions.pattern.id; + if(pattern1.test(EvalString_0) || pattern1.test(EvalString_1)){ + EvalString_0=MotaActionFunctions.PosString_pre(EvalString_0); + EvalString_1=MotaActionFunctions.PosString_pre(EvalString_1); + EvalString_0=[EvalString_0,EvalString_1] + } else { + var pattern2 = /^([+-]?\d+)(,[+-]?\d+)*$/; + if(!pattern2.test(EvalString_0) || !pattern2.test(EvalString_1))throw new Error('坐标格式错误,请右键点击帮助查看格式'); + EvalString_0=EvalString_0.split(','); + EvalString_1=EvalString_1.split(','); + if(EvalString_0.length!==EvalString_1.length)throw new Error('坐标格式错误,请右键点击帮助查看格式'); + for(var ii=0;ii50) throw new Error('图片编号在1~50之间'); +var async = Bool_0?', "async": true':''; +var code = '{"type": "showImage", "code": '+Int_0+', "image": "'+EvalString_0+'", "loc": ['+PosString_0+','+PosString_1+'], "opacity": '+Number_0+', "time": '+Int_1+async+'},\n'; +return code; +*/; + +showImage_1_s + : '显示图片' '图片编号' Int '图片' EvalString BGNL? + '裁剪的起点像素' 'x' PosString 'y' PosString '宽' PosString? '高' PosString? '不透明度' Number BGNL? + '绘制的起点像素' 'x' PosString 'y' PosString '宽' PosString? '高' PosString? '时间' Int '不等待执行完毕' Bool Newline + + +/* showImage_1_s +tooltip : showImage_1:显示图片 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=showImage%ef%bc%9a%e6%98%be%e7%a4%ba%e5%9b%be%e7%89%87 +default : [1,"bg.jpg","0","0","","",1,"0","0","","",0,false] +colour : this.printColor +if(Int_0<=0 || Int_0>50) throw new Error('图片编号在1~50之间'); +var async = Bool_0?', "async": true':''; +var code = '{"type": "showImage", "code": '+Int_0+', "image": "'+EvalString_0+'", '+ + '"sloc": ['+PosString_0+','+PosString_1+','+PosString_2+','+PosString_3+'], '+ + '"loc": ['+PosString_4+','+PosString_5+','+PosString_6+','+PosString_7+'], '+ + '"opacity": '+Number_0+', "time": '+Int_1+async+'},\n'; +return code; +*/; + +showTextImage_s + : '显示图片化文本' '文本内容' EvalString BGNL? + '图片编号' Int '起点像素' 'x' PosString 'y' PosString '行距' Number '不透明度' Number '时间' Int '不等待执行完毕' Bool Newline + + +/* showTextImage_s +tooltip : showTextImage:显示图片化文本 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=showTextImage%ef%bc%9a%e6%98%be%e7%a4%ba%e6%96%87%e6%9c%ac%e5%8c%96%e5%9b%be%e7%89%87 +colour : this.printColor +default : ["可以使用setText事件来控制字体、颜色、大小、偏移量等",1,"0","0",1.4,1,0,false] +if(Int_0<=0 || Int_0>50) throw new Error('图片编号在1~50之间'); +var async = Bool_0?', "async": true':''; +var code = '{"type": "showTextImage", "code": '+Int_0+', "text": "'+EvalString_0+'", "loc": ['+PosString_0+','+PosString_1+'], "lineHeight": '+Number_0+', "opacity": '+Number_1+', "time": '+Int_1+async+'},\n'; +return code; +*/; + +hideImage_s + : '清除图片' '图片编号' Int '时间' Int '不等待执行完毕' Bool Newline + + +/* hideImage_s +tooltip : hideImage:清除图片 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=hideImage%ef%bc%9a%e6%b8%85%e9%99%a4%e5%9b%be%e7%89%87 +colour : this.printColor +default : [1,0,false] +if(Int_0<=0 || Int_0>50) throw new Error('图片编号在1~50之间'); +var async = Bool_0?', "async": true':''; +var code = '{"type": "hideImage", "code": '+Int_0+', "time": '+Int_1+async+'},\n'; +return code; +*/; + +showGif_0_s + : '显示动图' EvalString '起点像素位置' 'x' PosString 'y' PosString Newline + + +/* showGif_0_s +tooltip : showGif:显示动图 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=showgif%EF%BC%9A%E6%98%BE%E7%A4%BA%E5%8A%A8%E5%9B%BE +default : ["bg.gif","0","0"] +colour : this.printColor +var code = '{"type": "showGif", "name": "'+EvalString_0+'", "loc": ['+PosString_0+','+PosString_1+']},\n'; +return code; +*/; + +showGif_1_s + : '清除所有动图' Newline + + +/* showGif_1_s +tooltip : showGif:清除所有显示的动图 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=showgif%EF%BC%9A%E6%98%BE%E7%A4%BA%E5%8A%A8%E5%9B%BE +colour : this.printColor +var code = '{"type": "showGif"},\n'; +return code; +*/; + +moveImage_s + : '图片移动' '图片编号' Int '终点像素位置' 'x' PosString? 'y' PosString? BGNL? + '不透明度' EvalString? '移动时间' Int '不等待执行完毕' Bool Newline + + +/* moveImage_s +tooltip : moveImage:图片移动 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=moveImage%ef%bc%9a%e5%9b%be%e7%89%87%e7%a7%bb%e5%8a%a8 +default : [1,'','','',500,false] +colour : this.printColor +if(Int_0<=0 || Int_0>50) throw new Error('图片编号在1~50之间'); +var toloc = ''; +if (PosString_0 && PosString_1) + toloc = ', "to": ['+PosString_0+','+PosString_1+']'; +EvalString_0 = (EvalString_0!=='') ? (', "opacity": '+EvalString_0):''; +var async = Bool_0?', "async": true':''; +var code = '{"type": "moveImage", "code": '+Int_0+toloc+EvalString_0+',"time": '+Int_1+async+'},\n'; +return code; +*/; + +setCurtain_0_s + : '更改画面色调' EvalString Colour '动画时间' Int? '不等待执行完毕' Bool Newline + + +/* 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 = MotaActionFunctions.pattern.colorRe; +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": "setCurtain", "color": ['+EvalString_0+']'+Int_0 +async+'},\n'; +return code; +*/; + +setCurtain_1_s + : '恢复画面色调' '动画时间' Int? '不等待执行完毕' Bool Newline + + +/* 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": "setCurtain"'+Int_0 +async+'},\n'; +return code; +*/; + +screenFlash_s + : '画面闪烁' EvalString Colour '单次时间' Int '执行次数' Int? '不等待执行完毕' Bool Newline + +/* screenFlash_s +tooltip : screenFlash: 画面闪烁,动画时间可不填 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=screenFlash%EF%BC%9A%E7%94%BB%E9%9D%A2%E9%97%AA%E7%83%81 +default : ["255,255,255,1",'rgba(255,255,255,1)',500,1,false] +colour : this.soundColor +var colorRe = MotaActionFunctions.pattern.colorRe; +if (!colorRe.test(EvalString_0))throw new Error('颜色格式错误,形如:0~255,0~255,0~255,0~1'); +Int_1 = Int_1!=='' ?(', "times": '+Int_1):''; +var async = Bool_0?', "async": true':''; +var code = '{"type": "screenFlash", "color": ['+EvalString_0+'], "time": '+Int_0 +Int_1+async+'},\n'; +return code; +*/; + +setWeather_s + : '更改天气' Weather_List '强度' Int Newline + + +/* setWeather_s +tooltip : setWeather:更改天气 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setweather%EF%BC%9A%E6%9B%B4%E6%94%B9%E5%A4%A9%E6%B0%94 +default : [null,1] +colour : this.soundColor +if(Int_0<1 || Int_0>10) throw new Error('天气的强度等级, 在1-10之间'); +var code = '{"type": "setWeather", "name": "'+Weather_List_0+'", "level": '+Int_0+'},\n'; +if(Weather_List_0===''||Weather_List_0==='null'||Weather_List_0==null)code = '{"type": "setWeather"},\n'; +return code; +*/; + +move_s + : '移动事件' 'x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool '不等待执行完毕' Bool BGNL? StepString Newline + + +/* move_s +tooltip : move: 让某个NPC/怪物移动,位置可不填代表当前事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=move%EF%BC%9A%E8%AE%A9%E6%9F%90%E4%B8%AAnpc%E6%80%AA%E7%89%A9%E7%A7%BB%E5%8A%A8 +default : ["","",500,false,false,"上右3下2后4左前2"] +colour : this.mapColor +var floorstr = ''; +if (PosString_0 && PosString_1) { + floorstr = ', "loc": ['+PosString_0+','+PosString_1+']'; +} +Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; +Bool_0 = Bool_0?', "keep": true':''; +Bool_1 = Bool_1?', "async": true':''; +var code = '{"type": "move"'+floorstr+Int_0+Bool_0+Bool_1+', "steps": '+JSON.stringify(StepString_0)+'},\n'; +return code; +*/; + +moveHero_s + : '移动勇士' '动画时间' Int? '不等待执行完毕' Bool BGNL? StepString Newline + + +/* moveHero_s +tooltip : moveHero:移动勇士,用这种方式移动勇士的过程中将无视一切地形, 无视一切事件, 中毒状态也不会扣血 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=movehero%EF%BC%9A%E7%A7%BB%E5%8A%A8%E5%8B%87%E5%A3%AB +default : [500,false,"上右3下2后4左前2"] +colour : this.dataColor +Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "moveHero"'+Int_0+Bool_0+', "steps": '+JSON.stringify(StepString_0)+'},\n'; +return code; +*/; + +jump_s + : '跳跃事件' '起始 x' PosString? ',' 'y' PosString? '终止 x' PosString? ',' 'y' PosString? '动画时间' Int? '不消失' Bool '不等待执行完毕' Bool Newline + + +/* jump_s +tooltip : jump: 让某个NPC/怪物跳跃 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=jump%EF%BC%9A%E8%AE%A9%E6%9F%90%E4%B8%AANPC%2F%E6%80%AA%E7%89%A9%E8%B7%B3%E8%B7%83 +default : ["","","","",500,true,false] +colour : this.mapColor +var floorstr = ''; +if (PosString_0 && PosString_1) { + floorstr += ', "from": ['+PosString_0+','+PosString_1+']'; +} +if (PosString_2 && PosString_3) { + floorstr += ', "to": ['+PosString_2+','+PosString_3+']'; +} +Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; +Bool_0 = Bool_0?', "keep": true':''; +Bool_1 = Bool_1?', "async": true':''; +var code = '{"type": "jump"'+floorstr+''+Int_0+Bool_0+Bool_1+'},\n'; +return code; +*/; + +jumpHero_s + : '跳跃勇士' 'x' PosString? ',' 'y' PosString? '动画时间' Int? '不等待执行完毕' Bool Newline + + +/* jumpHero_s +tooltip : jumpHero: 跳跃勇士 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=jumpHero%EF%BC%9A%E8%B7%B3%E8%B7%83%E5%8B%87%E5%A3%AB +default : ["","",500,false] +colour : this.dataColor +var floorstr = ''; +if (PosString_0 && PosString_1) { + floorstr = ', "loc": ['+PosString_0+','+PosString_1+']'; +} +Int_0 = Int_0!=='' ?(', "time": '+Int_0):''; +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "jumpHero"'+floorstr+Int_0+Bool_0+'},\n'; +return code; +*/; + +playBgm_s + : '播放背景音乐' EvalString Newline + + +/* playBgm_s +tooltip : playBgm: 播放背景音乐 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=playbgm%EF%BC%9A%E6%92%AD%E6%94%BE%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90 +default : ["bgm.mp3"] +colour : this.soundColor +var code = '{"type": "playBgm", "name": "'+EvalString_0+'"},\n'; +return code; +*/; + +pauseBgm_s + : '暂停背景音乐' Newline + + +/* pauseBgm_s +tooltip : pauseBgm: 暂停背景音乐 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=pausebgm%EF%BC%9A%E6%9A%82%E5%81%9C%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90 +colour : this.soundColor +var code = '{"type": "pauseBgm"},\n'; +return code; +*/; + +resumeBgm_s + : '恢复背景音乐' Newline + + +/* resumeBgm_s +tooltip : resumeBgm: 恢复背景音乐 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=resumebgm%EF%BC%9A%E6%81%A2%E5%A4%8D%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90 +colour : this.soundColor +var code = '{"type": "resumeBgm"},\n'; +return code; +*/; + +loadBgm_s + : '预加载背景音乐' EvalString Newline + + +/* loadBgm_s +tooltip : loadBgm: 预加载某个背景音乐,之后可以直接播放 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=loadBgm%ef%bc%9a%e9%a2%84%e5%8a%a0%e8%bd%bd%e4%b8%80%e4%b8%aa%e8%83%8c%e6%99%af%e9%9f%b3%e4%b9%90 +default : ["bgm.mp3"] +colour : this.soundColor +var code = '{"type": "loadBgm", "name": "'+EvalString_0+'"},\n'; +return code; +*/; + +freeBgm_s + : '释放背景音乐的缓存' EvalString Newline + + +/* freeBgm_s +tooltip : freeBgm: 释放背景音乐的缓存 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=freeBgm%ef%bc%9a%e9%87%8a%e6%94%be%e4%b8%80%e4%b8%aa%e8%83%8c%e6%99%af%e9%9f%b3%e4%b9%90%e7%9a%84%e7%bc%93%e5%ad%98 +default : ["bgm.mp3"] +colour : this.soundColor +var code = '{"type": "freeBgm", "name": "'+EvalString_0+'"},\n'; +return code; +*/; + +playSound_s + : '播放音效' 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",false] +colour : this.soundColor +Bool_0 = Bool_0 ? ', "stop": true' : ''; +var code = '{"type": "playSound", "name": "'+EvalString_0+'"'+Bool_0+'},\n'; +return code; +*/; + +stopSound_s + : '停止所有音效' Newline + + +/* stopSound_s +tooltip : stopSound: 停止所有音效 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=stopSound%ef%bc%9a%e5%81%9c%e6%ad%a2%e6%89%80%e6%9c%89%e9%9f%b3%e6%95%88 +colour : this.soundColor +var code = '{"type": "stopSound"},\n'; +return code; +*/; + +setVolume_s + : '设置音量' Int '渐变时间' Int? '不等待执行完毕' Bool Newline + + +/* setVolume_s +tooltip : setVolume: 设置音量 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=setvolume%EF%BC%9A%E8%AE%BE%E7%BD%AE%E9%9F%B3%E9%87%8F +default : [90, 500, false] +colour : this.soundColor +Int_1 = Int_1!==''?(', "time": '+Int_1):"" +var async = Bool_0?', "async": true':''; +var code = '{"type": "setVolume", "value": '+Int_0+Int_1+async+'},\n'; +return code; +*/; + +win_s + : '游戏胜利,结局' ':' EvalString? '不计入榜单' Bool Newline + + +/* win_s +tooltip : win: 获得胜利, 该事件会显示获胜页面, 并重新游戏 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=win%EF%BC%9A%E8%8E%B7%E5%BE%97%E8%83%9C%E5%88%A9 +default : ["",false] +Bool_0 = Bool_0?', "norank": 1':''; +var code = '{"type": "win", "reason": "'+EvalString_0+'"'+Bool_0+'},\n'; +return code; +*/; + +lose_s + : '游戏失败,结局' ':' EvalString? Newline + + +/* lose_s +tooltip : lose: 游戏失败, 该事件会显示失败页面, 并重新开始游戏 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=lose%EF%BC%9A%E6%B8%B8%E6%88%8F%E5%A4%B1%E8%B4%A5 +default : [""] +var code = '{"type": "lose", "reason": "'+EvalString_0+'"},\n'; +return code; +*/; + +restart_s + : '直接回到标题界面' Newline + + +/* restart_s +tooltip : restart: 直接回到标题界面 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=restart%ef%bc%9a%e7%9b%b4%e6%8e%a5%e5%9b%9e%e5%88%b0%e6%a0%87%e9%a2%98%e7%95%8c%e9%9d%a2 +var code = '{"type": "restart"},\n'; +return code; +*/; + +input_s + : '接受用户输入数字,提示' ':' EvalString Newline + + +/* input_s +tooltip : input:接受用户输入数字, 事件只能接受非负整数输入, 所有非法的输入将全部变成0 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=input%ef%bc%9a%e6%8e%a5%e5%8f%97%e7%94%a8%e6%88%b7%e8%be%93%e5%85%a5%e6%95%b0%e5%ad%97 +default : ["请输入一个数"] +colour : this.dataColor +var code = '{"type": "input", "text": "'+EvalString_0+'"},\n'; +return code; +*/; + +input2_s + : '接受用户输入文本,提示' ':' EvalString Newline + + +/* input2_s +tooltip : input2:接受用户输入文本, 允许用户输入任何形式的文本 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=input2%ef%bc%9a%e6%8e%a5%e5%8f%97%e7%94%a8%e6%88%b7%e8%be%93%e5%85%a5%e6%96%87%e6%9c%ac +default : ["请输入文本"] +colour : this.dataColor +var code = '{"type": "input2", "text": "'+EvalString_0+'"},\n'; +return code; +*/; + +if_s + : '如果' ':' expression BGNL? Newline action+ '否则' ':' BGNL? Newline action+ BEND Newline + + +/* if_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', + '"false": [\n',action_1,']\n', +'},\n'].join(''); +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 + + +/* switch_s +tooltip : switch: 多重条件分歧 +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 : ["判别值"] +colour : this.eventColor +var code = ['{"type": "switch", "condition": "',expression_0,'", "caseList": [\n', + switchCase_0, +'], },\n'].join(''); +return code; +*/; + +switchCase + : '如果是' 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 +Bool_0 = Bool_0?', "nobreak": true':''; +var code = '{"case": "'+expression_0+'"'+Bool_0+', "action": [\n'+action_0+']},\n'; +return code; +*/; + +choices_s + : '选项' ':' EvalString? BGNL? '标题' EvalString? '图像' IdString? BGNL? Newline choicesContext+ BEND Newline + + +/* choices_s +tooltip : choices: 给用户提供选项 +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 : ["","流浪者","woman"] +var title=''; +if (EvalString_1==''){ + if (IdString_0=='')title=''; + else title='\\t['+IdString_0+']'; +} else { + if (IdString_0=='')title='\\t['+EvalString_1+']'; + else title='\\t['+EvalString_1+','+IdString_0+']'; +} +EvalString_0 = title+EvalString_0; +EvalString_0 = EvalString_0 ?(', "text": "'+EvalString_0+'"'):''; +var code = ['{"type": "choices"',EvalString_0,', "choices": [\n', + choicesContext_0, +']},\n'].join(''); +return code; +*/; + +choicesContext + : '子选项' 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 : ["提示文字:红钥匙","",""] +colour : this.subColor +if (EvalString_1) { + var colorRe = MotaActionFunctions.pattern.colorRe; + if (colorRe.test(EvalString_1)) + EvalString_1 = ', "color": ['+EvalString_1+']'; + else + EvalString_1 = ', "color": "'+EvalString_1+'"'; +} +IdString_0 = IdString_0?(', "icon": "'+IdString_0+'"'):''; +var code = '{"text": "'+EvalString_0+'"'+IdString_0+EvalString_1+', "action": [\n'+action_0+']},\n'; +return code; +*/; + +confirm_s + : '显示确认框' ':' EvalString BGNL? '确定的场合' ':' '(默认选中' Bool ')' BGNL? Newline action+ '取消的场合' ':' BGNL? Newline action+ BEND Newline + +/* confirm_s +tooltip : 弹出确认框 +helpUrl : https://h5mota.com/games/template/docs/#/ +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(''); +return code; +*/; + +while_s + : '前置条件循环' ':' '当' expression '时' BGNL? Newline action+ BEND Newline + +/* while_s +tooltip : while:前置条件循环 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=while%ef%bc%9a%e5%89%8d%e7%bd%ae%e6%9d%a1%e4%bb%b6%e5%be%aa%e7%8e%af +colour : this.eventColor +var code = ['{"type": "while", "condition": "',expression_0,'",\n', + '"data": [\n',action_0,'],\n', +'},\n'].join(''); +return code; +*/; + +dowhile_s + : '后置条件循环' ':' BGNL? Newline action+ BEND '当' expression '时' Newline + +/* dowhile_s +tooltip : dowhile:后置条件循环 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=dowhile%ef%bc%9a%e5%90%8e%e7%bd%ae%e6%9d%a1%e4%bb%b6%e5%be%aa%e7%8e%af +colour : this.eventColor +var code = ['{"type": "dowhile", "condition": "',expression_0,'",\n', + '"data": [\n',action_0,'],\n', +'},\n'].join(''); +return code; +*/; + +break_s + : '跳出当前循环或公共事件' Newline + +/* break_s +tooltip : break:跳出循环, 如果break事件不在任何循环中被执行,则和exit等价,即会立刻结束当前事件! +helpUrl : https://h5mota.com/games/template/docs/#/event?id=break%EF%BC%9A%E8%B7%B3%E5%87%BA%E5%BE%AA%E7%8E%AF +colour : this.eventColor +var code = '{"type": "break"},\n'; +return code; +*/; + +continue_s + : '继续当前循环' Newline + +/* continue_s +tooltip : continue:继续执行当前循环的下一轮, 如果continue事件不在任何循环中被执行,则和exit等价,即会立刻结束当前事件! +helpUrl : https://h5mota.com/games/template/docs/#/event?id=continue%EF%BC%9A%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%E5%BD%93%E5%89%8D%E5%BE%AA%E7%8E%AF +colour : this.eventColor +var code = '{"type": "continue"},\n'; +return code; +*/; + + +wait_s + : '等待用户操作并获得按键或点击信息' + + +/* wait_s +tooltip : wait: 等待用户操作并获得按键或点击信息(具体用法看文档) +helpUrl : https://h5mota.com/games/template/docs/#/event?id=wait%EF%BC%9A%E7%AD%89%E5%BE%85%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C +colour : this.soundColor +var code = '{"type": "wait"},\n'; +return code; +*/; + + +waitAsync_s + : '等待所有异步事件执行完毕' + + +/* waitAsync_s +tooltip : waitAsync: 等待所有异步事件执行完毕 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=waitAsync%ef%bc%9a%e7%ad%89%e5%be%85%e6%89%80%e6%9c%89%e5%bc%82%e6%ad%a5%e4%ba%8b%e4%bb%b6%e6%89%a7%e8%a1%8c%e5%ae%8c%e6%af%95 +colour : this.soundColor +var code = '{"type": "waitAsync"},\n'; +return code; +*/; + + +callBook_s + : '呼出怪物手册' + + +/* callBook_s +tooltip : callBook: 呼出怪物手册;返回游戏后将继续执行后面的事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=callBook%ef%bc%9a%e5%91%bc%e5%87%ba%e6%80%aa%e7%89%a9%e6%89%8b%e5%86%8c +colour : this.soundColor +var code = '{"type": "callBook"},\n'; +return code; +*/; + + +callSave_s + : '呼出存档页面' + + +/* callSave_s +tooltip : callSave: 呼出存档页面 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=callSave%ef%bc%9a%e5%91%bc%e5%87%ba%e5%ad%98%e6%a1%a3%e7%95%8c%e9%9d%a2 +colour : this.soundColor +var code = '{"type": "callSave"},\n'; +return code; +*/; + + +autoSave_s + : '自动存档' + + +/* autoSave_s +tooltip : autoSave: 自动存档 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=autoSave%ef%bc%9a%e8%87%aa%e5%8a%a8%e5%ad%98%e6%a1%a3 +colour : this.soundColor +var code = '{"type": "autoSave"},\n'; +return code; +*/; + + +callLoad_s + : '呼出读档页面' + + +/* callLoad_s +tooltip : callLoad: 呼出存档页面;返回游戏后将继续执行后面的事件 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=callLoad%ef%bc%9a%e5%91%bc%e5%87%ba%e8%af%bb%e6%a1%a3%e7%95%8c%e9%9d%a2 +colour : this.soundColor +var code = '{"type": "callLoad"},\n'; +return code; +*/; + +unknown_s + : '自定义事件' BGNL? RawEvalString + +/* unknown_s +tooltip : 通过脚本自定义的事件类型, 以及编辑器不识别的事件类型 +helpUrl : https://h5mota.com/games/template/docs/#/ +default : ['{"type":"test", "data": "这是自定义的参数"}'] +colour : this.dataColor +try { + var tempobj = JSON.parse(RawEvalString_0); +} catch (e) {throw new Error("不合法的JSON格式!");} +if (!tempobj.type) throw new Error("自定义事件需要一个type:xxx"); +var code = JSON.stringify(tempobj) +',\n'; +return code; +*/; + +function_s + : '自定义JS脚本' '不自动执行下一个事件' Bool BGNL? Newline RawEvalString Newline BEND Newline + + +/* function_s +tooltip : 可双击多行编辑,请勿使用异步代码。常见API参见文档附录。 +helpUrl : https://h5mota.com/games/template/docs/#/event?id=function%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89js%E8%84%9A%E6%9C%AC +default : [false,"alert(core.getStatus(\"atk\"));"] +colour : this.dataColor +Bool_0 = Bool_0?', "async": true':''; +var code = '{"type": "function"'+Bool_0+', "function": "function(){\\n'+JSON.stringify(RawEvalString_0).slice(1,-1).split('\\\\n').join('\\n')+'\\n}"},\n'; +return code; +*/; + +pass_s + : Newline + + +/* pass_s +var code = ' \n'; +return code; +*/; + +statExprSplit : '=== statement ^ === expression v ===' ; +//===blockly表达式=== + +expression + : expression Arithmetic_List expression + | negate_e + | bool_e + | idString_e + | evFlag_e + | evalString_e + + +/* expression_arithmetic_0 +//todo 修改recieveOrder,根据Arithmetic_List_0不同的值设定不同的recieveOrder +var code = expression_0 + Arithmetic_List_0 + expression_1; +var ops = { + '^': 'Math.pow('+expression_0+','+expression_1+')', + '和': expression_0+' && '+expression_1, + '或': expression_0+' || '+expression_1, +} +if (ops[Arithmetic_List_0])code = ops[Arithmetic_List_0]; +var orders = { + '+': Blockly.JavaScript.ORDER_ADDITION, + '-': Blockly.JavaScript.ORDER_SUBTRACTION, + '*': Blockly.JavaScript.ORDER_MULTIPLICATION, + '/': Blockly.JavaScript.ORDER_DIVISION, + '^': Blockly.JavaScript.ORDER_MEMBER, //recieveOrder : ORDER_COMMA + '==': Blockly.JavaScript.ORDER_EQUALITY, + '!=': Blockly.JavaScript.ORDER_EQUALITY, + '>': Blockly.JavaScript.ORDER_RELATIONAL, + '<': Blockly.JavaScript.ORDER_RELATIONAL, + '>=': Blockly.JavaScript.ORDER_RELATIONAL, + '<=': Blockly.JavaScript.ORDER_RELATIONAL, + '和': Blockly.JavaScript.ORDER_LOGICAL_AND, + '或': Blockly.JavaScript.ORDER_LOGICAL_OR +} +return [code, orders[Arithmetic_List_0]]; +*/; + +negate_e + : '非' expression + + +/* negate_e +//todo 修改recieveOrder : ORDER_LOGICAL_NOT 修改 inputsInline +var code = '!'+expression_0; +return [code, Blockly.JavaScript.ORDER_LOGICAL_NOT]; +*/; + +bool_e + : Bool + + +/* bool_e +var code = Bool_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + + +idString_e + : IdString + + +/* idString_e +colour : this.idstring_eColor +default : ["status:hp"] +var code = IdString_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + +//这一条不会被antlr识别,总是会被归到idString_e +idString_1_e + : Id_List ':' IdText + + +/* idString_1_e +colour : this.idstring_eColor +default : [null,"自定义flag"] +//todo 将其output改成'idString_e' +var code = Id_List_0+':'+IdText_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + +//这一条不会被antlr识别,总是会被归到idString_e +idString_2_e + : FixedId_List + + +/* idString_2_e +colour : this.idstring_eColor +//todo 将其output改成'idString_e' +var code = FixedId_List_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + + +evFlag_e + : '独立开关' Letter_List + + +/* evFlag_e +colour : this.idstring_eColor +default : ["A"] +var code = "switch:"+Letter_List_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + + +evalString_e + : EvalString + + +/* evalString_e +default : ["值"] +var code = EvalString_0; +return [code, Blockly.JavaScript.ORDER_ATOMIC]; +*/; + +//===============lexer=============== + +IdText + : 'sdeirughvuiyasdeb'+ //为了被识别为复杂词法规则 + ; + +RawEvalString + : 'sdeirughvuiyasdbe'+ //为了被识别为复杂词法规则 + ; + +PosString + : 'sdeirughvuiyasbde'+ //为了被识别为复杂词法规则 + ; + +Floor_List + : '楼层ID'|'前一楼'|'后一楼' + /*Floor_List ['floorId',':before',':next']*/; + +Stair_List + : '坐标'|'上楼梯'|'下楼梯'|'保持不变' + /*Stair_List ['loc','upFloor','downFloor','now']*/; + +SetTextPosition_List + : '不改变'|'距离顶部'|'居中'|'距离底部' + /*SetTextPosition_List ['null','up','center','down']*/; + +SetTextAlign_List + : '不改变'|'左对齐'|'左右居中'|'右对齐' + /*SetTextAlign_List ['null','left','center','right']*/; + +ShopUse_List + : '金币' | '经验' + /*ShopUse_List ['money','experience']*/; + +Arithmetic_List + : '+'|'-'|'*'|'/'|'^'|'=='|'!='|'>'|'<'|'>='|'<='|'和'|'或' + ; + +Weather_List + : '无'|'雨'|'雪'|'雾' + /*Weather_List ['null','rain','snow','fog']*/; + +B_0_List + : '不改变'|'不可通行'|'可以通行' + /*B_0_List ['null','true','false']*/; + +B_1_List + : '不改变'|'设为粗体'|'取消粗体' + /*B_1_List ['null','true','false']*/; + +Bg_Fg_List + : '背景层'|'前景层' + /*Bg_Fg_List ['bg','fg']*/; + +Event_List + : '事件'|'战后事件'|'道具后事件'|'开门后事件' + /*Event_List ['null','afterBattle','afterGetItem','afterOpenDoor']*/; + +Floor_Meta_List + : '楼层中文名'|'状态栏名称'|'能否使用楼传'|'能否打开快捷商店'|'是否不可浏览地图'|'是否不可瞬间移动'|'默认地面ID'|'楼层贴图'|'宝石血瓶效果'|'上楼点坐标'|'下楼点坐标'|'背景音乐'|'画面色调'|'天气和强度'|'是否地下层' + /*Floor_Meta_List ['title','name','canFlyTo', 'canUseQuickShop', 'cannotViewMap', 'cannotMoveDirectly', 'defaultGround', 'images', 'item_ratio', 'upFloor', 'downFloor', 'bgm', 'color', 'weather', 'underGround']*/; + +Global_Attribute_List + : '全局字体'|'横屏左侧状态栏背景'|'竖屏上方状态栏背景'|'竖屏下方道具栏背景'|'边框颜色'|'状态栏文字色'|'难度显示文字色'|'楼层转换背景'|'楼层转换文字色'|'装备列表' + /*Global_Attribute_List ['font','statusLeftBackground','statusTopBackground', 'toolsBackground', 'borderColor', 'statusBarColor', 'hardLabelColor', 'floorChangingBackground', 'floorChangingTextColor', 'equipName']*/; + +Global_Value_List + : '血网伤害'|'中毒伤害'|'衰弱效果'|'红宝石效果'|'蓝宝石效果'|'绿宝石效果'|'红血瓶效果'|'蓝血瓶效果'|'黄血瓶效果'|'绿血瓶效果'|'破甲比例'|'反击比例'|'净化比例'|'仇恨增加值'|'行走速度'|'动画时间'|'楼层切换时间' + /*Global_Value_List ['lavaDamage','poisonDamage','weakValue', 'redJewel', 'blueJewel', 'greenJewel', 'redPotion', 'bluePotion', 'yellowPotion', 'greenPotion', 'breakArmor', 'counterAttack', 'purify', 'hatred', 'moveSpeed', 'animateSpeed', 'floorChangeTime']*/; + + +Global_Flag_List + : '显示当前楼层'|'显示勇士图标'|'显示当前等级'|'启用生命上限'|'显示魔力值'|'显示魔防值'|'显示金币值'|'显示经验值'|'允许等级提升'|'升级扣除模式'|'显示钥匙数量'|'显示破炸飞'|'显示毒衰咒'|'显示当前技能'|'楼梯边才能楼传'|'破墙镐四方向'|'炸弹四方向'|'冰冻徽章四方向'|'铁门不需要钥匙'|'开启加点'|'开启负伤'|'仇恨怪战后扣减一半'|'夹击是否上整'|'夹击不超伤害值'|'循环计算临界'|'允许轻按'|'寻路算法不绕血瓶'|'允许走到将死领域'|'允许瞬间移动'|'允许查看禁用商店'|'阻激夹域后禁用快捷商店'|'检查控制台' + /*Global_Flag_List ['enableFloor','enableName','enableLv', 'enableHPMax', 'enableMana', 'enableMDef', 'enableMoney', 'enableExperience', 'enableLevelUp', 'levelUpLeftMode', 'enableKeys', 'enablePZF', 'enableDebuff', 'enableSkill', 'flyNearStair', 'pickaxeFourDirections', 'bombFourDirections', 'snowFourDirections', 'steelDoorWithoutKey', 'enableAddPoint', 'enableNegativeDamage', 'hatredDecrease', 'betweenAttackCeil', 'betweenAttackMax', 'useLoop', 'enableGentleClick', 'potionWhileRouting', 'canGoDeadZone', 'enableMoveDirectly', 'enableDisabledShop', 'disableShopOnDamage', 'checkConsole']*/; + +Colour + : 'sdeirughvuiyasdeb'+ //为了被识别为复杂词法规则 + ; + +Angle + : 'sdeirughvuiyasdeb'+ //为了被识别为复杂词法规则 + ; + +Bool: 'TRUE' + | 'FALSE' + ; + +Int : '0' | [1-9][0-9]* ; // no leading zeros + +Letter_List + : 'A'|'B'|'C'|'D'|'E'|'F' + /*Letter_List ['A','B','C','D','E','F']*/; + + +Number + : '-'? Int '.' Int EXP? // 1.35, 1.35E-9, 0.3, -4.5 + | '-'? Int EXP // 1e10 -3e4 + | '-'? Int // -3, 45 + ; +fragment EXP : [Ee] [+\-]? Int ; // \- since - means "range" inside [...] + +Direction_List + : '上'|'下'|'左'|'右' + /*Direction_List ['up','down','left','right']*/; + +DirectionEx_List + : '不变'|'上'|'下'|'左'|'右' + /*DirectionEx_List ['','up','down','left','right']*/; + +StepString + : (Direction_List Int?)+ + ; + +IdString + : [0-9a-zA-Z_][0-9a-zA-Z_:]* + ; + +FixedId_List + : '生命'|'攻击'|'防御'|'魔防'|'黄钥匙'|'蓝钥匙'|'红钥匙'|'金币'|'经验' + /*FixedId_List ['status:hp','status:atk','status:def','status:mdef','item:yellowKey','item:blueKey','item:redKey','status:money','status:experience']*/; + +Id_List + : '变量' | '状态' | '物品' | '独立开关' | '全局存储' + /*Id_List ['flag','status','item', 'switch', 'global']*/; + +//转blockly后不保留需要加" +EvalString + : Equote_double (ESC_double | ~["\\])* Equote_double + ; + +fragment ESC_double : '\\' (["\\/bfnrt] | UNICODE) ; +fragment UNICODE : 'u' HEX HEX HEX HEX ; +fragment HEX : [0-9a-fA-F] ; + +BGNL + : 'BGNLaergayergfuybgv' + ; + +MeaningfulSplit : '=== meaningful ^ ===' ; + +fragment Equote_double : '"' ; + +BSTART + : '开始' + ; + +BEND: '结束' + ; + +Newline + : ('\r' '\n'?| '\n')// -> skip + ; + +WhiteSpace + : [ \t]+ -> skip + ; + +BlockComment + : '/*' .*? '*/' -> skip + ; + +LineComment + : '//' ~[\r\n]* -> skip + ; + +/* Function_0 +//this.evisitor.recieveOrder='ORDER_NONE'; +this.evisitor.valueColor=330; +this.evisitor.statementColor=70; +this.evisitor.entryColor=250; + +this.evisitor.idstring_eColor=310; +this.evisitor.subColor=190; +this.evisitor.printColor=70; +this.evisitor.dataColor=130; +this.evisitor.eventColor=220; +this.evisitor.soundColor=20; +this.evisitor.commentColor=285; +this.evisitor.mapColor=175; +*/ + +/* Function_1 +delete(this.block('negate_e').inputsInline); +this.block('idString_1_e').output='idString_e'; +this.block('idString_2_e').output='idString_e'; +this.block('evFlag_e').output='idString_e'; +*/ + +/* Functions + +function ActionParser(){ +} + +ActionParser.prototype.parse = function (obj,type) { + switch (type) { + case 'event': + if(!obj)obj={}; + if(typeof(obj)===typeof('')) obj={'data':[obj]}; + if(obj instanceof Array) obj={'data':obj}; + return MotaActionBlocks['event_m'].xmlText([ + obj.trigger==='action',obj.enable,obj.noPass,obj.displayDamage,this.parseList(obj.data) + ]); + + case 'changeFloor': + if(!obj)obj={}; + if(!this.isset(obj.loc)) { + obj.loc=[0,0]; + if (!this.isset(obj.stair)) obj.stair='now'; + } + if (obj.floorId==':before'||obj.floorId==':next') { + obj.floorType=obj.floorId; + delete obj.floorId; + } + if (!this.isset(obj.time)) obj.time=500; + return MotaActionBlocks['changeFloor_m'].xmlText([ + obj.floorType||'floorId',obj.floorId,obj.stair||'loc',obj.loc[0],obj.loc[1],obj.direction, + obj.time,!this.isset(obj.ignoreChangeFloor) + ]); + + case 'level': + if(!obj)obj={}; + var text_choices = null; + for(var ii=obj.length-1,choice;choice=obj[ii];ii--) { + text_choices=MotaActionBlocks['levelCase'].xmlText([ + MotaActionBlocks['evalString_e'].xmlText([choice.need]),choice.title,choice.clear||false,this.parseList(choice.action),text_choices]); + } + return MotaActionBlocks['level_m'].xmlText([text_choices]); + + case 'shop': + var buildsub = function(obj,parser,next){ + var text_choices = null; + for(var ii=obj.choices.length-1,choice;choice=obj.choices[ii];ii--) { + var text_effect = null; + var effectList = choice.effect.split(';'); + for(var jj=effectList.length-1,effect;effect=effectList[jj];jj--) { + if(effect.split('+=').length!==2){ + throw new Error('一个商店效果中必须包含恰好一个"+="'); + } + text_effect=MotaActionBlocks['shopEffect'].xmlText([ + MotaActionBlocks['idString_e'].xmlText([effect.split('+=')[0]]), + MotaActionBlocks['evalString_e'].xmlText([effect.split('+=')[1]]), + text_effect]); + } + text_choices=MotaActionBlocks['shopChoices'].xmlText([ + choice.text,choice.need||'',text_effect,text_choices]); + } + return MotaActionBlocks['shopsub'].xmlText([ + obj.id,obj.name,obj.icon,obj.textInList,obj.commonTimes,obj.mustEnable,obj.use,obj.need,parser.EvalString(obj.text),text_choices,next + ]); + } + var buildcommentevent = function(obj,parser,next){ + if (obj.args instanceof Array) { + try { obj.args = JSON.stringify(obj.args).replace(/"/g, "'"); } + catch (e) {obj.args = '';} + } + else obj.args = null; + return MotaActionBlocks['shopcommonevent'].xmlText([ + obj.id,parser.EvalString(obj.textInList),obj.mustEnable,parser.EvalString(obj.commonEvent),obj.args,next + ]); + } + var next=null; + if(!obj)obj=[]; + while(obj.length){ + var shopobj=obj.pop() + if(shopobj.choices) + next=buildsub(shopobj,this,next); + else if(shopobj.commonEvent) + next=buildcommentevent(shopobj,this,next); + else + throw new Error("[警告]出错啦!\n"+shopobj.id+" 无效的商店"); + } + return MotaActionBlocks['shop_m'].xmlText([next]); + + default: + return MotaActionBlocks[type+'_m'].xmlText([this.parseList(obj)]); + } +} + +////// 开始解析一系列自定义事件 ////// +ActionParser.prototype.parseList = function (list) { + if (!this.isset(list)) return MotaActionBlocks['pass_s'].xmlText([],true); + if (!(list instanceof Array)) { + list = [list]; + } + if (list.length===0) return MotaActionBlocks['pass_s'].xmlText([],true); + this.event = {'id': 'action', 'data': { + 'list': list + }} + this.next = null; + this.result = null; + this.parseAction(); + return this.result; +} + +////// 解析当前自定义事件列表中的最后一个事件 ////// +ActionParser.prototype.parseAction = function() { + + // 事件处理完毕 + if (this.event.data.list.length==0) { + this.result = this.next; + this.next = null; + return; + } + + var data = this.event.data.list.pop(); + this.event.data.current = data; + + // 不同种类的事件 + + // 如果是文字:显示 + if (typeof data == "string") { + data={"type": "text", "text": data} + } + this.event.data.type=data.type; + switch (data.type) { + case "_next": + this.result = this.next; + this.next = data.next; + return; + case "text": // 文字/对话 + this.next = MotaActionBlocks['text_0_s'].xmlText([ + this.EvalString(data.text),this.next]); + break; + case "autoText": // 自动剧情文本 + this.next = MotaActionBlocks['autoText_s'].xmlText([ + '','','',data.time,this.EvalString(data.text),this.next]); + break; + case "scrollText": + this.next = MotaActionBlocks['scrollText_s'].xmlText([ + data.time, data.lineHeight||1.4, data.async||false, this.EvalString(data.text), this.next]); + break; + case "comment": // 注释 + this.next = MotaActionBlocks['comment_s'].xmlText([this.EvalString(data.text),this.next],null,data.text); + break; + case "setText": // 设置剧情文本的属性 + var setTextfunc = function(a){return a?JSON.stringify(a).slice(1,-1):null;} + data.title=setTextfunc(data.title); + data.text=setTextfunc(data.text); + if (!/^\w+\.png$/.test(data.background)) + data.background=setTextfunc(data.background); + this.next = MotaActionBlocks['setText_s'].xmlText([ + data.position,data.offset,data.align,data.title,'rgba('+data.title+')', + data.text,'rgba('+data.text+')',data.background,'rgba('+data.background+')', + data.bold,data.titlefont,data.textfont,data.time,this.next]); + break; + case "tip": + this.next = MotaActionBlocks['tip_s'].xmlText([ + data.text,data.icon||"",this.next]); + break; + case "show": // 显示 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['show_s'].xmlText([ + x_str.join(','),y_str.join(','),data.floorId||'',data.time||0,data.async||false,this.next]); + break; + case "hide": // 消失 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['hide_s'].xmlText([ + x_str.join(','),y_str.join(','),data.floorId||'',data.time||0,data.async||false,this.next]); + break; + case "setBlock": // 设置图块 + data.loc=data.loc||['','']; + this.next = MotaActionBlocks['setBlock_s'].xmlText([ + data.number||0,data.loc[0],data.loc[1],data.floorId||'',this.next]); + break; + case "showFloorImg": // 显示贴图 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['showFloorImg_s'].xmlText([ + x_str.join(','),y_str.join(','),data.floorId||'',this.next]); + break; + case "hideFloorImg": // 隐藏贴图 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['hideFloorImg_s'].xmlText([ + x_str.join(','),y_str.join(','),data.floorId||'',this.next]); + break; + case "showBgFgMap": // 显示图层块 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['showBgFgMap_s'].xmlText([ + data.name||'bg', x_str.join(','),y_str.join(','),data.floorId||'',this.next]); + break; + case "hideBgFgMap": // 隐藏图层块 + data.loc=data.loc||[]; + if (!(data.loc[0] instanceof Array)) + data.loc = [data.loc]; + var x_str=[],y_str=[]; + data.loc.forEach(function (t) { + x_str.push(t[0]); + y_str.push(t[1]); + }) + this.next = MotaActionBlocks['hideBgFgMap_s'].xmlText([ + data.name||'bg', x_str.join(','),y_str.join(','),data.floorId||'',this.next]); + break; + case "setBgFgBlock": // 设置图块 + data.loc=data.loc||['','']; + this.next = MotaActionBlocks['setBgFgBlock_s'].xmlText([ + data.name||"bg", data.number||0,data.loc[0],data.loc[1],data.floorId||'',this.next]); + break; + case "setHeroIcon": // 改变勇士 + this.next = MotaActionBlocks['setHeroIcon_s'].xmlText([ + data.name||"",this.next]); + break; + case "move": // 移动事件 + data.loc=data.loc||['','']; + this.next = MotaActionBlocks['move_s'].xmlText([ + data.loc[0],data.loc[1],data.time||0,data.keep||false,data.async||false,this.StepString(data.steps),this.next]); + break; + case "moveHero": + this.next = MotaActionBlocks['moveHero_s'].xmlText([ + data.time||0,data.async||false,this.StepString(data.steps),this.next]); + break; + case "jump": // 跳跃事件 + data.from=data.from||['','']; + data.to=data.to||['','']; + this.next = MotaActionBlocks['jump_s'].xmlText([ + data.from[0],data.from[1],data.to[0],data.to[1],data.time||0,data.keep||false,data.async||false,this.next]); + break; + case "jumpHero": // 跳跃勇士 + data.loc=data.loc||['',''] + this.next = MotaActionBlocks['jumpHero_s'].xmlText([ + data.loc[0],data.loc[1],data.time||0,data.async||false,this.next]); + break; + case "changeFloor": // 楼层转换 + data.loc=data.loc||['',''] + this.next = MotaActionBlocks['changeFloor_s'].xmlText([ + data.floorId,data.loc[0],data.loc[1],data.direction,data.time||0,this.next]); + break; + case "changePos": // 直接更换勇士位置, 不切换楼层 + if(this.isset(data.loc)){ + this.next = MotaActionBlocks['changePos_0_s'].xmlText([ + data.loc[0],data.loc[1],data.direction,this.next]); + } else { + this.next = MotaActionBlocks['changePos_1_s'].xmlText([ + data.direction,this.next]); + } + break; + case "follow": // 跟随勇士 + this.next = MotaActionBlocks['follow_s'].xmlText([data.name||"", this.next]); + break; + case "unfollow": // 取消跟随 + this.next = MotaActionBlocks['unfollow_s'].xmlText([data.name||"", this.next]); + break; + case "animate": // 显示动画 + var animate_loc = data.loc||''; + if(animate_loc && animate_loc!=='hero')animate_loc = animate_loc[0]+','+animate_loc[1]; + this.next = MotaActionBlocks['animate_s'].xmlText([ + data.name,animate_loc,data.async||false,this.next]); + break; + case "vibrate": // 画面震动 + this.next = MotaActionBlocks['vibrate_s'].xmlText([data.time||0, data.async||false, this.next]); + break; + case "showImage": // 显示图片 + data.loc=data.loc||['',''] + if (data.sloc) { + this.next = MotaActionBlocks['showImage_1_s'].xmlText([ + data.code,data.image||data.name,data.sloc[0],data.sloc[1],data.sloc[2],data.sloc[3],data.opacity, + data.loc[0],data.loc[1],data.loc[2],data.loc[3],data.time||0,data.async||false,this.next + ]); + } + else { + this.next = MotaActionBlocks['showImage_s'].xmlText([ + data.code,data.image||data.name,data.loc[0],data.loc[1],data.opacity,data.time||0,data.async||false,this.next]); + } + break; + case "hideImage": // 清除图片 + this.next = MotaActionBlocks['hideImage_s'].xmlText([ + data.code,data.time||0,data.async||false,this.next]); + break; + case "showTextImage": // 显示图片化文本 + data.loc=data.loc||['',''] + this.next = MotaActionBlocks['showTextImage_s'].xmlText([ + this.EvalString(data.text),data.code,data.loc[0],data.loc[1],data.lineHeight||1.4,data.opacity,data.time||0,data.async||false,this.next]); + break; + case "moveImage": // 移动图片 + data.to=data.to||['',''] + this.next = MotaActionBlocks['moveImage_s'].xmlText([ + data.code, data.to[0], data.to[1], data.opacity, data.time||0, data.async||false, this.next]); + break; + case "showGif": // 显示动图 + if(this.isset(data.name)){ + this.next = MotaActionBlocks['showGif_0_s'].xmlText([ + data.name,data.loc[0],data.loc[1],this.next]); + } else { + this.next = MotaActionBlocks['showGif_1_s'].xmlText([ + this.next]); + } + break; + case "setFg": // 颜色渐变 + case "setCurtain": + if(this.isset(data.color)){ + this.next = MotaActionBlocks['setCurtain_0_s'].xmlText([ + data.color,'rgba('+data.color+')',data.time||0,data.async||false,this.next]); + } else { + this.next = MotaActionBlocks['setCurtain_1_s'].xmlText([ + data.time||0,data.async||false,this.next]); + } + break; + case "screenFlash": // 画面闪烁 + this.next = MotaActionBlocks['screenFlash_s'].xmlText([ + data.color,'rgba('+data.color+')',data.time||500,data.times||1,data.async||false,this.next]); + break; + case "setWeather": // 更改天气 + this.next = MotaActionBlocks['setWeather_s'].xmlText([ + data.name,data.level||1,this.next]); + break; + case "openDoor": // 开一个门, 包括暗墙 + data.loc=data.loc||['',''] + this.next = MotaActionBlocks['openDoor_s'].xmlText([ + data.loc[0],data.loc[1],data.floorId||'',data.needKey||false,data.async||false,this.next]); + break; + case "closeDoor": // 关一个门,需要该点无事件 + data.loc=data.loc||['',''] + this.next = MotaActionBlocks['closeDoor_s'].xmlText([ + data.loc[0],data.loc[1],data.id,data.async||false,this.next]); + break; + case "useItem": // 使用道具 + this.next = MotaActionBlocks['useItem_s'].xmlText([ + data.id,this.next]); + break; + case "openShop": // 打开一个全局商店 + this.next = MotaActionBlocks['openShop_s'].xmlText([ + data.id,this.next]); + break; + case "disableShop": // 禁用一个全局商店 + this.next = MotaActionBlocks['disableShop_s'].xmlText([ + data.id,this.next]); + break; + case "battle": // 强制战斗 + if (data.id) { + this.next = MotaActionBlocks['battle_s'].xmlText([ + data.id,this.next]); + } + else { + data.loc = data.loc || []; + this.next = MotaActionBlocks['battle_1_s'].xmlText([ + data.loc[0],data.loc[1],this.next]); + } + break; + case "trigger": // 触发另一个事件;当前事件会被立刻结束。需要另一个地点的事件是有效的 + this.next = MotaActionBlocks['trigger_s'].xmlText([ + data.loc[0],data.loc[1],data.keep,this.next]); + break; + case "insert": // 强制插入另一个点的事件在当前事件列表执行,当前坐标和楼层不会改变 + if (data.args instanceof Array) { + try { data.args = JSON.stringify(data.args).replace(/"/g, "'"); } + catch (e) {data.args = '';} + } + else data.args = null; + if (this.isset(data.name)) { + this.next = MotaActionBlocks['insert_1_s'].xmlText([ + data.name, data.args||"", this.next]); + } + else { + this.next = MotaActionBlocks['insert_2_s'].xmlText([ + data.loc[0],data.loc[1],data.which,data.floorId||'',data.args||"",this.next]); + } + break; + case "playSound": + this.next = MotaActionBlocks['playSound_s'].xmlText([ + data.name,data.stop,this.next]); + break; + case "playBgm": + this.next = MotaActionBlocks['playBgm_s'].xmlText([ + data.name,this.next]); + break + case "pauseBgm": + this.next = MotaActionBlocks['pauseBgm_s'].xmlText([ + this.next]); + break + case "resumeBgm": + this.next = MotaActionBlocks['resumeBgm_s'].xmlText([ + this.next]); + break + case "loadBgm": + this.next = MotaActionBlocks['loadBgm_s'].xmlText([ + data.name,this.next]); + break + case "freeBgm": + this.next = MotaActionBlocks['freeBgm_s'].xmlText([ + data.name,this.next]); + break + case "stopSound": + this.next = MotaActionBlocks['stopSound_s'].xmlText([ + this.next]); + break + case "setVolume": + this.next = MotaActionBlocks['setVolume_s'].xmlText([ + data.value, data.time||0, data.async||false, this.next]); + break + case "setValue": + this.next = MotaActionBlocks['setValue_s'].xmlText([ + this.tryToUseEvFlag_e('idString_e', [data.name]), + MotaActionBlocks['evalString_e'].xmlText([data.value]), + this.next]); + break; + case "setValue2": + case "addValue": + this.next = MotaActionBlocks['addValue_s'].xmlText([ + this.tryToUseEvFlag_e('idString_e', [data.name]), + MotaActionBlocks['evalString_e'].xmlText([data.value]), + this.next]); + break; + case "setFloor": + this.next = MotaActionBlocks['setFloor_s'].xmlText([ + data.name, data.floorId||null, data.value, this.next]); + break; + case "setGlobalAttribute": + this.next = MotaActionBlocks['setGlobalAttribute_s'].xmlText([ + data.name, data.value, this.next]); + break; + case "setGlobalValue": + this.next = MotaActionBlocks['setGlobalValue_s'].xmlText([ + data.name, data.value, this.next]); + break; + case "setGlobalFlag": + this.next = MotaActionBlocks['setGlobalFlag_s'].xmlText([ + data.name, data.value, this.next]); + break; + case "input": + this.next = MotaActionBlocks['input_s'].xmlText([ + data.text,this.next]); + break; + case "input2": + this.next = MotaActionBlocks['input2_s'].xmlText([ + data.text,this.next]); + break; + case "if": // 条件判断 + 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), data["default"], + this.insertActionList(data["yes"]), + this.insertActionList(data["no"]), + this.next]); + break; + case "switch": // 多重条件分歧 + 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]):"值",caseNow.nobreak,this.insertActionList(caseNow.action),case_caseList]); + } + this.next = MotaActionBlocks['switch_s'].xmlText([ + // MotaActionBlocks['evalString_e'].xmlText([data.condition]), + this.tryToUseEvFlag_e('evalString_e', [data.condition]), + case_caseList,this.next]); + break; + case "choices": // 提供选项 + 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+')',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]); + break; + case "while": // 前置条件循环处理 + this.next = MotaActionBlocks['while_s'].xmlText([ + // MotaActionBlocks['evalString_e'].xmlText([data.condition]), + this.tryToUseEvFlag_e('evalString_e', [data.condition]), + this.insertActionList(data["data"]), + this.next]); + break; + case "dowhile": // 后置条件循环处理 + this.next = MotaActionBlocks['dowhile_s'].xmlText([ + this.insertActionList(data["data"]), + // MotaActionBlocks['evalString_e'].xmlText([data.condition]), + this.tryToUseEvFlag_e('evalString_e', [data.condition]), + this.next]); + break; + case "break": // 跳出循环 + this.next = MotaActionBlocks['break_s'].xmlText([ + this.next]); + break; + case "continue": // 继续执行当前循环 + this.next = MotaActionBlocks['continue_s'].xmlText([ + this.next]); + break; + case "win": + this.next = MotaActionBlocks['win_s'].xmlText([ + data.reason,data.norank?true:false,this.next]); + break; + case "lose": + this.next = MotaActionBlocks['lose_s'].xmlText([ + data.reason,this.next]); + break; + case "restart": + this.next = MotaActionBlocks['restart_s'].xmlText([ + this.next]); + break; + case "function": + var func = data["function"]; + func=func.split('{').slice(1).join('{').split('}').slice(0,-1).join('}').trim().split('\n').join('\\n'); + this.next = MotaActionBlocks['function_s'].xmlText([ + data.async||false,func,this.next]); + break; + case "update": + this.next = MotaActionBlocks['update_s'].xmlText([ + this.next]); + break; + case "showStatusBar": + this.next = MotaActionBlocks['showStatusBar_s'].xmlText([ + this.next]); + break; + case "hideStatusBar": + this.next = MotaActionBlocks['hideStatusBar_s'].xmlText([ + data.toolbox||false,this.next]); + break; + case "updateEnemys": + this.next = MotaActionBlocks['updateEnemys_s'].xmlText([ + this.next]); + break; + case "sleep": // 等待多少毫秒 + this.next = MotaActionBlocks['sleep_s'].xmlText([ + data.time||0,data.noSkip||false,this.next]); + break; + case "wait": // 等待用户操作 + this.next = MotaActionBlocks['wait_s'].xmlText([ + this.next]); + break; + case "waitAsync": // 等待所有异步事件执行完毕 + this.next = MotaActionBlocks['waitAsync_s'].xmlText([ + this.next]); + break; + case "revisit": // 立刻重新执行该事件 + this.next = MotaActionBlocks['revisit_s'].xmlText([ + this.next]); + break; + case "callBook": // 呼出怪物手册 + this.next = MotaActionBlocks['callBook_s'].xmlText([ + this.next]); + break; + case "callSave": // 呼出存档界面 + this.next = MotaActionBlocks['callSave_s'].xmlText([ + this.next]); + break; + case "autoSave": // 自动存档 + this.next = MotaActionBlocks['autoSave_s'].xmlText([ + this.next]); + break; + case "callLoad": // 呼出读档界面 + this.next = MotaActionBlocks['callLoad_s'].xmlText([ + this.next]); + break; + case "exit": // 立刻结束事件 + this.next = MotaActionBlocks['exit_s'].xmlText([ + this.next]); + break; + case "animateImage": // 兼容 animateImage + break; + default: + this.next = MotaActionBlocks['unknown_s'].xmlText([ + JSON.stringify(data),this.next]); + } + this.parseAction(); + return; +} + +////// 往当前事件列表之后添加一个事件组 ////// +ActionParser.prototype.insertActionList = function (actionList) { + if (actionList.length===0) return null; + this.event.data.list.push({"type": "_next", "next": this.next}); + this.event.data.list=this.event.data.list.concat(actionList); + this.next = null; + this.parseAction(); + return this.result; +} + +////// 判断某对象是否不为undefined也不会null ////// +ActionParser.prototype.isset = function (val) { + if (val === undefined || val === null) { + return false; + } + return true +} + +ActionParser.prototype.StepString = function(steplist) { + var stepchar = { + 'up': '上', + 'down': '下', + 'left': '左', + 'right': '右', + 'forward': '前', + 'backward': '后' + } + var StepString = []; + for(var ii=0,obj;obj=steplist[ii];ii++) { + if(typeof(obj)===typeof('')) { + StepString.push(stepchar[obj]); + } else { + StepString.push(stepchar[obj['direction']]); + StepString.push(obj['value']); + } + } + return StepString.join(''); +} + +ActionParser.prototype.EvalString = function(EvalString) { + return EvalString.split('\b').join('\\b').split('\t').join('\\t').split('\n').join('\\n'); +} + +ActionParser.prototype.tryToUseEvFlag_e = function(defaultType, args, isShadow, comment) { + var match=/^switch:([A-F])$/.exec(args[0]) + if(match){ + args[0]=match[1] + return MotaActionBlocks['evFlag_e'].xmlText(args, isShadow, comment); + } + return MotaActionBlocks[defaultType||'evalString_e'].xmlText(args, isShadow, comment); +} + +MotaActionFunctions.actionParser = new ActionParser(); + +MotaActionFunctions.workspace = function(){return workspace} + +MotaActionFunctions.parse = function(obj,type) { + MotaActionFunctions.workspace().clear(); + xml_text = MotaActionFunctions.actionParser.parse(obj,type||'event'); + xml = Blockly.Xml.textToDom(''+xml_text+''); + Blockly.Xml.domToWorkspace(xml, MotaActionFunctions.workspace()); +} + +MotaActionFunctions.EvalString_pre = function(EvalString){ + if (EvalString.indexOf('__door__')!==-1) throw new Error('请修改开门变量__door__,如door1,door2,door3等依次向后。请勿存在两个门使用相同的开门变量。'); + return EvalString.replace(/([^\\])"/g,'$1\\"').replace(/^"/g,'\\"').replace(/""/g,'"\\"'); +} + +MotaActionFunctions.IdString_pre = function(IdString){ + if (IdString.indexOf('__door__')!==-1) throw new Error('请修改开门变量__door__,如door1,door2,door3等依次向后。请勿存在两个门使用相同的开门变量。'); + if (IdString && !(MotaActionFunctions.pattern.id.test(IdString)) && !(MotaActionFunctions.pattern.idWithoutFlag.test(IdString))) + throw new Error('id: '+IdString+'中包含了0-9 a-z A-Z _ - :之外的字符'); + return IdString; +} + +MotaActionFunctions.PosString_pre = function(PosString){ + if (!PosString || /^-?\d+$/.test(PosString)) return PosString; + if (!(MotaActionFunctions.pattern.id.test(PosString)))throw new Error(PosString+'中包含了0-9 a-z A-Z _ 和中文之外的字符,或者是没有以flag: 开头'); + return '"'+PosString+'"'; +} + +MotaActionFunctions.StepString_pre = function(StepString){ + //StepString='上右3下2左上左2' + var route = StepString.replace(/上/g,'U').replace(/下/g,'D').replace(/左/g,'L').replace(/右/g,'R').replace(/前/g,'F').replace(/后/g,'B'); + + //copyed from core.js + var ans=[], index=0; + + var isset = function(a) { + if (a == undefined || a == null) { + return false; + } + return true; + } + var getNumber = function (noparse) { + var num=""; + while (index! 以下均是v2.0时的说明, 未及时改动 + 本目录下所有文件,以及`../editor.html`和`../启动服务.exe`([源码](http://github.com/ckcz123/mota-js-server/))是地图编辑器的所有组件. `editor.js`,`editor_file.js`和`editor_mode.js`耦合较强,`editor_blockly.js`和`editor_multi.js`和`fs.js`基本可以独立使用. @@ -29,7 +33,7 @@ ``` js editor.mapInit();//清空地图 editor.changeFloor('MT2')//切换地图 -editor.guid()//产生一个可以作为id的长随机字符串 +editor.util.guid()//产生一个可以作为id的长随机字符串 ``` `editor.updateMap`中画未定义快的报错 @@ -38,8 +42,6 @@ editor.guid()//产生一个可以作为id的长随机字符串 提供了以下函数进行楼层`map`数组相关的操作 ```javascript -editor.file.getFloorFileList -editor.file.loadFloorFile editor.file.saveFloorFile editor.file.saveFloorFileAs ``` @@ -70,7 +72,7 @@ editor.file.editFunctions(["change","['events']['afterChangeLight']","function(x 生成表格并绑定事件的函数 ```javascript editor.mode.loc(); -editor.mode.emenyitem(); +editor.mode.enemyitem(); editor.mode.floor(); editor.mode.tower(); editor.mode.functions(); @@ -83,7 +85,7 @@ editor.mode.onmode('save');//保存 editor.mode.onmode('nextChange');//下次onmode时前端进行切换 editor.mode.onmode('loc'); -editor.mode.onmode('emenyitem'); +editor.mode.onmode('enemyitem'); editor.mode.onmode('floor'); editor.mode.onmode('tower'); editor.mode.onmode('functions'); @@ -103,23 +105,6 @@ editor_mode.onmode(editor_mode._ids[node.getAttribute('id')]); `editor.mode.listen`中提供了追加素材的支持. -处理注释的特殊指令 -``` -$range(evalstr:thiseval)$end - 限制取值范围,要求修改后的eval(evalstr)为true -$leaf(evalstr:thiseval)$end - 强制指定为叶节点,如果eval(evalstr)为true - -//以下几个中选一个 [ -$select(evalstr)$end - 渲染成 -$textarea(evalstr)$end - 渲染成\n'].join(''); - } - } - editor_mode.prototype.indent = function (field) { var num = '\t'; if (field.indexOf("['main']") === 0) return 0; - if (field.indexOf("['flyRange']") !== -1) return 0; if (field === "['special']") return 0; return num; } @@ -196,75 +52,43 @@ editor_mode = function (editor) { editor_mode.prototype.doActionList = function (mode, actionList) { if (actionList.length == 0) return; printf('修改中...'); + var cb=function(objs_){ + if (objs_.slice(-1)[0] != null) { + printe(objs_.slice(-1)[0]); + throw(objs_.slice(-1)[0]) + } + ;printf('修改成功'); + } switch (mode) { case 'loc': - - editor.file.editLoc(editor_mode.pos.x, editor_mode.pos.y, actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功'); - editor.drawEventBlock(); + editor.file.editLoc(editor_mode.pos.x, editor_mode.pos.y, actionList, function (objs_) { + cb(objs_); + editor.drawPosSelection(); }); break; - case 'emenyitem': - + case 'enemyitem': if (editor_mode.info.images == 'enemys' || editor_mode.info.images == 'enemy48') { - editor.file.editEnemy(editor_mode.info.id, actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功') - }); + editor.file.editEnemy(editor_mode.info.id, actionList, cb); } else if (editor_mode.info.images == 'items') { - editor.file.editItem(editor_mode.info.id, actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功') - }); + editor.file.editItem(editor_mode.info.id, actionList, cb); } else { - editor.file.editMapBlocksInfo(editor_mode.info.idnum, actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功'); - }); + editor.file.editMapBlocksInfo(editor_mode.info.idnum, actionList, cb); } break; case 'floor': - - editor.file.editFloor(actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功'); - }); + editor.file.editFloor(actionList, cb); break; case 'tower': - - editor.file.editTower(actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功') - }); + editor.file.editTower(actionList, cb); break; case 'functions': - - editor.file.editFunctions(actionList, function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printf('修改成功') - }); + editor.file.editFunctions(actionList, cb); + break; + case 'commonevent': + editor.file.editCommonEvent(actionList, cb); + break; + case 'plugins': + editor.file.editPlugins(actionList, cb); break; default: break; @@ -275,7 +99,7 @@ editor_mode = function (editor) { if (editor_mode.mode != mode) { if (mode === 'save') editor_mode.doActionList(editor_mode.mode, editor_mode.actionList); if (editor_mode.mode === 'nextChange' && mode) editor_mode.showMode(mode); - editor_mode.mode = mode; + if (mode !== 'save') editor_mode.mode = mode; editor_mode.actionList = []; } } @@ -285,10 +109,13 @@ editor_mode = function (editor) { editor_mode.dom[name].style = 'z-index:-1;opacity: 0;'; } editor_mode.dom[mode].style = ''; + editor_mode.doubleClickMode='change'; + // clear + editor.drawEventBlock(); if (editor_mode[mode]) editor_mode[mode](); document.getElementById('editModeSelect').value = mode; var tips = tip_in_showMode; - if (!selectBox.isSelected) printf('tips: ' + tips[~~(tips.length * Math.random())]); + if (!selectBox.isSelected()) printf('tips: ' + tips[~~(tips.length * Math.random())]); } editor_mode.prototype.loc = function (callback) { @@ -300,52 +127,55 @@ editor_mode = function (editor) { var objs = []; editor.file.editLoc(editor_mode.pos.x, editor_mode.pos.y, [], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); //只查询不修改时,内部实现不是异步的,所以可以这么写 - var tableinfo = editor_mode.objToTable_(objs[0], objs[1]); + var tableinfo = editor.table.objToTable(objs[0], objs[1]); document.getElementById('table_3d846fc4_7644_44d1_aa04_433d266a73df').innerHTML = tableinfo.HTML; tableinfo.listen(tableinfo.guids); - + editor.drawPosSelection(); if (Boolean(callback)) callback(); } - editor_mode.prototype.emenyitem = function (callback) { + editor_mode.prototype.enemyitem = function (callback) { //editor.info=editor.ids[editor.indexs[201]]; if (!core.isset(editor.info)) return; - if (Object.keys(editor.info).length !== 0) editor_mode.info = editor.info;//避免editor.info被清空导致无法获得是物品还是怪物 + if (Object.keys(editor.info).length !== 0 && editor.info.idnum!=17) editor_mode.info = editor.info;//避免editor.info被清空导致无法获得是物品还是怪物 if (!core.isset(editor_mode.info.id)) { // document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML = ''; - document.getElementById('enemyItemTable').style.display = 'none'; document.getElementById('newIdIdnum').style.display = 'block'; + document.getElementById('enemyItemTable').style.display = 'none'; + document.getElementById('changeId').style.display = 'none'; return; } + document.getElementById('newIdIdnum').style.display = 'none'; document.getElementById('enemyItemTable').style.display = 'block'; + document.getElementById('changeId').style.display = 'block'; var objs = []; if (editor_mode.info.images == 'enemys' || editor_mode.info.images == 'enemy48') { editor.file.editEnemy(editor_mode.info.id, [], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); } else if (editor_mode.info.images == 'items') { editor.file.editItem(editor_mode.info.id, [], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); } else { /* document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML=''; return; */ editor.file.editMapBlocksInfo(editor_mode.info.idnum, [], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); } //只查询不修改时,内部实现不是异步的,所以可以这么写 - var tableinfo = editor_mode.objToTable_(objs[0], objs[1]); + var tableinfo = editor.table.objToTable(objs[0], objs[1]); document.getElementById('table_a3f03d4c_55b8_4ef6_b362_b345783acd72').innerHTML = tableinfo.HTML; tableinfo.listen(tableinfo.guids); @@ -356,10 +186,10 @@ editor_mode = function (editor) { var objs = []; editor.file.editFloor([], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); //只查询不修改时,内部实现不是异步的,所以可以这么写 - var tableinfo = editor_mode.objToTable_(objs[0], objs[1]); + var tableinfo = editor.table.objToTable(objs[0], objs[1]); document.getElementById('table_4a3b1b09_b2fb_4bdf_b9ab_9f4cdac14c74').innerHTML = tableinfo.HTML; tableinfo.listen(tableinfo.guids); if (Boolean(callback)) callback(); @@ -369,10 +199,10 @@ editor_mode = function (editor) { var objs = []; editor.file.editTower([], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); //只查询不修改时,内部实现不是异步的,所以可以这么写 - var tableinfo = editor_mode.objToTable_(objs[0], objs[1]); + var tableinfo = editor.table.objToTable(objs[0], objs[1]); document.getElementById('table_b6a03e4c_5968_4633_ac40_0dfdd2c9cde5').innerHTML = tableinfo.HTML; tableinfo.listen(tableinfo.guids); if (Boolean(callback)) callback(); @@ -382,274 +212,46 @@ editor_mode = function (editor) { var objs = []; editor.file.editFunctions([], function (objs_) { objs = objs_; - /*console.log(objs_)*/ + //console.log(objs_) }); //只查询不修改时,内部实现不是异步的,所以可以这么写 - var tableinfo = editor_mode.objToTable_(objs[0], objs[1]); + var tableinfo = editor.table.objToTable(objs[0], objs[1]); document.getElementById('table_e260a2be_5690_476a_b04e_dacddede78b3').innerHTML = tableinfo.HTML; tableinfo.listen(tableinfo.guids); if (Boolean(callback)) callback(); } + editor_mode.prototype.commonevent = function (callback) { + var objs = []; + editor.file.editCommonEvent([], function (objs_) { + objs = objs_; + //console.log(objs_) + }); + //只查询不修改时,内部实现不是异步的,所以可以这么写 + var tableinfo = editor.table.objToTable(objs[0], objs[1]); + document.getElementById('table_b7bf0124_99fd_4af8_ae2f_0017f04a7c7d').innerHTML = tableinfo.HTML; + tableinfo.listen(tableinfo.guids); + if (Boolean(callback)) callback(); + } + + editor_mode.prototype.plugins = function (callback) { + var objs = []; + editor.file.editPlugins([], function (objs_) { + objs = objs_; + //console.log(objs_) + }); + //只查询不修改时,内部实现不是异步的,所以可以这么写 + var tableinfo = editor.table.objToTable(objs[0], objs[1]); + document.getElementById('table_e2c034ec_47c6_48ae_8db8_4f8f32fea2d6').innerHTML = tableinfo.HTML; + tableinfo.listen(tableinfo.guids); + if (Boolean(callback)) callback(); + } + ///////////////////////////////////////////////////////////////////////////// editor_mode.prototype.listen = function (callback) { - var newIdIdnum = document.getElementById('newIdIdnum'); - newIdIdnum.children[2].onclick = function () { - if (newIdIdnum.children[0].value && newIdIdnum.children[1].value) { - var id = newIdIdnum.children[0].value; - var idnum = parseInt(newIdIdnum.children[1].value); - if (!core.isset(idnum)) { - printe('不合法的idnum'); - return; - } - if (!/^[0-9a-zA-Z_]+$/.test(id)) { - printe('不合法的id,请使用字母、数字或下划线') - return; - } - editor.file.changeIdAndIdnum(id, idnum, editor_mode.info, function (err) { - if (err) { - printe(err); - throw(err) - } - printe('添加id的idnum成功,请F5刷新编辑器'); - }); - } else { - printe('请输入id和idnum'); - } - } - - newIdIdnum.children[4].onclick = function () { - editor.file.autoRegister(editor_mode.info, function (err) { - if (err) { - printe(err); - throw(err) - } - printe('该列所有剩余项全部自动注册成功,请F5刷新编辑器'); - }) - } - - var selectFloor = document.getElementById('selectFloor'); - editor.file.getFloorFileList(function (floors) { - var outstr = []; - floors[0].forEach(function (floor) { - outstr.push(["\n'].join('')); - }); - selectFloor.innerHTML = outstr.join(''); - selectFloor.value = core.status.floorId; - selectFloor.onchange = function () { - editor_mode.onmode('nextChange'); - editor_mode.onmode('floor'); - editor.changeFloor(selectFloor.value); - } - }); - - var saveFloor = document.getElementById('saveFloor'); - saveFloor.onclick = function () { - editor_mode.onmode(''); - editor.file.saveFloorFile(function (err) { - if (err) { - printe(err); - throw(err) - } - ;printf('保存成功'); - }); - } - - var newMap = document.getElementById('newMap'); - var newFileName = document.getElementById('newFileName'); - newMap.onclick = function () { - if (!newFileName.value) return; - if (core.floorIds.indexOf(newFileName.value)>=0) { - printe("该楼层已存在!"); - return; - } - if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(newFileName.value)) { - printe("楼层名不合法!请使用字母、数字、下划线,且不能以数字开头!"); - return; - } - var width = parseInt(document.getElementById('newMapWidth').value); - var height = parseInt(document.getElementById('newMapHeight').value); - if (!core.isset(width) || !core.isset(height) || width<13 || height<13 || width*height>1000) { - printe("新建地图的宽高都不得小于13,且宽高之积不能超过1000"); - return; - } - - editor_mode.onmode(''); - editor.file.saveNewFile(newFileName.value, function (err) { - if (err) { - printe(err); - throw(err) - } - core.floorIds.push(newFileName.value); - editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printe('新建成功,请F5刷新编辑器生效'); - }); - }); - } - - var ratio = 1; - var appendPicCanvas = document.getElementById('appendPicCanvas'); - var bg = appendPicCanvas.children[0]; - var source = appendPicCanvas.children[1]; - var picClick = appendPicCanvas.children[2]; - var sprite = appendPicCanvas.children[3]; - var appendPicSelection = document.getElementById('appendPicSelection'); - - var selectAppend = document.getElementById('selectAppend'); - var selectAppend_str = []; - ["terrains", "animates", "enemys", "enemy48", "items", "npcs", "npc48"].forEach(function (image) { - selectAppend_str.push(["\n'].join('')); - }); - selectAppend.innerHTML = selectAppend_str.join(''); - selectAppend.onchange = function () { - var value = selectAppend.value; - var ysize = selectAppend.value.indexOf('48') === -1 ? 32 : 48; - editor_mode.appendPic.imageName = value; - var img = editor.material.images[value]; - editor_mode.appendPic.toImg = img; - var num = ~~img.width / 32; - editor_mode.appendPic.num = num; - editor_mode.appendPic.index = 0; - var selectStr = ''; - for (var ii = 0; ii < num; ii++) { - appendPicSelection.children[ii].style = 'left:0;top:0;height:' + (ysize - 6) + 'px'; - selectStr += '{"x":0,"y":0},' - } - editor_mode.appendPic.selectPos = eval('[' + selectStr + ']'); - for (var jj = num; jj < 4; jj++) { - appendPicSelection.children[jj].style = 'display:none'; - } - sprite.style.width = (sprite.width = img.width) / ratio + 'px'; - sprite.style.height = (sprite.height = img.height + ysize) / ratio + 'px'; - sprite.getContext('2d').drawImage(img, 0, 0); - } - selectAppend.onchange(); - - var selectFileBtn = document.getElementById('selectFileBtn'); - selectFileBtn.onclick = function () { - var loadImage = function (content, callback) { - var image = new Image(); - try { - image.src = content; - if (image.complete) { - callback(image); - return; - } - image.onload = function () { - callback(image); - } - } - catch (e) { - printe(e); - } - } - core.readFile(function (content) { - loadImage(content, function (image) { - editor_mode.appendPic.img = image; - editor_mode.appendPic.width = image.width; - editor_mode.appendPic.height = image.height; - var ysize = selectAppend.value.indexOf('48') === -1 ? 32 : 48; - for (var ii = 0; ii < 3; ii++) { - var newsprite = appendPicCanvas.children[ii]; - newsprite.style.width = (newsprite.width = Math.floor(image.width / 32) * 32) / ratio + 'px'; - newsprite.style.height = (newsprite.height = Math.floor(image.height / ysize) * ysize) / ratio + 'px'; - } - - //画灰白相间的格子 - var bgc = bg.getContext('2d'); - var colorA = ["#f8f8f8", "#cccccc"]; - var colorIndex; - var sratio = 4; - for (var ii = 0; ii < image.width / 32 * sratio; ii++) { - colorIndex = 1 - ii % 2; - for (var jj = 0; jj < image.height / 32 * sratio; jj++) { - bgc.fillStyle = colorA[colorIndex]; - colorIndex = 1 - colorIndex; - bgc.fillRect(ii * 32 / sratio, jj * 32 / sratio, 32 / sratio, 32 / sratio); - } - } - - //把导入的图片画出 - source.getContext('2d').drawImage(image, 0, 0); - - //重置临时变量 - selectAppend.onchange(); - }); - }, null, 'img'); - - return; - } - - var left1 = document.getElementById('left1'); - var eToLoc = function (e) { - var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft - var scrollTop = document.documentElement.scrollTop || document.body.scrollTop - var loc = { - 'x': scrollLeft + e.clientX + appendPicCanvas.scrollLeft - left1.offsetLeft - appendPicCanvas.offsetLeft, - 'y': scrollTop + e.clientY + appendPicCanvas.scrollTop - left1.offsetTop - appendPicCanvas.offsetTop, - 'size': 32, - 'ysize': selectAppend.value.indexOf('48') === -1 ? 32 : 48 - }; - return loc; - }//返回可用的组件内坐标 - - var locToPos = function (loc) { - var pos = {'x': ~~(loc.x / loc.size), 'y': ~~(loc.y / loc.ysize), 'ysize': loc.ysize} - return pos; - } - - picClick.onclick = function (e) { - var loc = eToLoc(e); - var pos = locToPos(loc); - /*console.log(e,loc,pos);*/ - var num = editor_mode.appendPic.num; - var ii = editor_mode.appendPic.index; - if (ii + 1 >= num) editor_mode.appendPic.index = ii + 1 - num; - else editor_mode.appendPic.index++; - editor_mode.appendPic.selectPos[ii] = pos; - appendPicSelection.children[ii].style = [ - 'left:', pos.x * 32, 'px;', - 'top:', pos.y * pos.ysize, 'px;', - 'height:', pos.ysize - 6, 'px;' - ].join(''); - } - - var appendConfirm = document.getElementById('appendConfirm'); - appendConfirm.onclick = function () { - var ysize = selectAppend.value.indexOf('48') === -1 ? 32 : 48; - var sprited = sprite.getContext('2d'); - //sprited.drawImage(img, 0, 0); - var height = editor_mode.appendPic.toImg.height; - var sourced = source.getContext('2d'); - for (var ii = 0, v; v = editor_mode.appendPic.selectPos[ii]; ii++) { - // var imgData = sourced.getImageData(v.x * 32, v.y * ysize, 32, ysize); - // sprited.putImageData(imgData, ii * 32, height); - sprited.drawImage(editor_mode.appendPic.img, v.x * 32, v.y * ysize, 32, ysize, ii * 32, height, 32, ysize) - } - var imgbase64 = sprite.toDataURL().split(',')[1]; - fs.writeFile('./project/images/' + editor_mode.appendPic.imageName + '.png', imgbase64, 'base64', function (err, data) { - if (err) { - printe(err); - throw(err) - } - printe('追加素材成功,请F5刷新编辑器'); - }); - } - - var editModeSelect = document.getElementById('editModeSelect'); - editModeSelect.onchange = function () { - editor_mode.onmode('nextChange'); - editor_mode.onmode(editModeSelect.value); - if(editor.isMobile)editor.showdataarea(false); - } - - if (Boolean(callback)) callback(); + // 移动至 editor_unsorted_2.js } var editor_mode = new editor_mode(); diff --git a/_server/editor_multi.js b/_server/editor_multi.js index 2c426991..476536bb 100644 --- a/_server/editor_multi.js +++ b/_server/editor_multi.js @@ -1,3 +1,5 @@ + + editor_multi = function () { var editor_multi = {}; @@ -9,22 +11,24 @@ editor_multi = function () { tabSize: 4, indentWithTabs: true, smartIndent: true, - mode: {name: "javascript", globalVars: true, localVars: true}, + mode: { name: "javascript", globalVars: true, localVars: true }, lineWrapping: true, continueComments: "Enter", gutters: ["CodeMirror-lint-markers"], lint: true, autocomplete: true, autoCloseBrackets: true, - highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true} + highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true } }); + editor_multi.codeEditor = codeEditor; + codeEditor.on("keyup", function (cm, event) { if (codeEditor.getOption("autocomplete") && !event.ctrlKey && ( (event.keyCode >= 65 && event.keyCode <= 90) || - (!event.shiftKey && event.keyCode == 190) || (event.shiftKey && event.keyCode == 189))){ + (!event.shiftKey && event.keyCode == 190) || (event.shiftKey && event.keyCode == 189))) { try { - CodeMirror.commands.autocomplete(cm, null, {completeSingle: false}); + CodeMirror.commands.autocomplete(cm, null, { completeSingle: false }); } catch (e) { } } @@ -35,7 +39,7 @@ editor_multi = function () { editor_multi.lintAutocomplete = false; editor_multi.show = function () { - if (typeof(selectBox) !== typeof(undefined)) selectBox.isSelected = false; + if (typeof (selectBox) !== typeof (undefined)) selectBox.isSelected(false); var valueNow = codeEditor.getValue(); //try{eval('function _asdygakufyg_() { return '+valueNow+'\n}');editor_multi.lintAutocomplete=true;}catch(ee){} if (valueNow.slice(0, 8) === 'function') editor_multi.lintAutocomplete = true; @@ -56,10 +60,28 @@ editor_multi = function () { } editor_multi.indent = function (field) { - if (typeof(editor) !== typeof(undefined) && editor && editor.mode && editor.mode.indent) return editor.mode.indent(field); + if (typeof (editor) !== typeof (undefined) && editor && editor.mode && editor.mode.indent) return editor.mode.indent(field); return '\t'; } + var _format = function () { + if (!editor_multi.lintAutocomplete) return; + codeEditor.setValue(js_beautify(codeEditor.getValue(), { + brace_style: "collapse-preserve-inline", + indent_with_tabs: true, + jslint_happy: true + })); + } + + editor_multi.format = function () { + if (!editor_multi.lintAutocomplete) { + alert("只有代码才能进行格式化操作!"); + return; + } + _format(); + } + + editor_multi.import = function (id_, args) { var thisTr = document.getElementById(id_); if (!thisTr) return false; @@ -80,8 +102,8 @@ editor_multi = function () { eval('var tobj=' + (input.value || 'null')); var tmap = {}; var tstr = JSON.stringify(tobj, function (k, v) { - if (typeof(v) === typeof('') && v.slice(0, 8) === 'function') { - var id_ = editor.guid(); + if (typeof (v) === typeof ('') && v.slice(0, 8) === 'function') { + var id_ = editor.util.guid(); tmap[id_] = v.toString(); return id_; } else return v @@ -106,11 +128,22 @@ editor_multi = function () { editor_multi.id = ''; return; } + if (editor_multi.id === 'callFromBlockly') { + // ----- 自动格式化 + _format(); editor_multi.id = ''; editor_multi.multiLineDone(); return; } + + if (editor_multi.id === 'importFile') { + _format(); + editor_multi.id = ''; + editor_multi.writeFileDone(); + return; + } + var setvalue = function (value) { var thisTr = document.getElementById(editor_multi.id); editor_multi.id = ''; @@ -122,7 +155,7 @@ editor_multi = function () { var tmap = {}; var tstr = JSON.stringify(tobj, function (k, v) { if (v instanceof Function) { - var id_ = editor.guid(); + var id_ = editor.util.guid(); tmap[id_] = v.toString(); return id_; } else return v @@ -135,6 +168,8 @@ editor_multi = function () { editor_multi.hide(); input.onchange(); } + // ----- 自动格式化 + _format(); setvalue(codeEditor.getValue() || ''); } @@ -155,6 +190,49 @@ editor_multi = function () { multiLineArgs[2](newvalue, multiLineArgs[0], multiLineArgs[1]) } + var _fileValues = [''] + editor_multi.importFile = function (filename) { + editor_multi.id = 'importFile' + _fileValues[0] = filename + codeEditor.setValue('loading') + editor_multi.show(); + fs.readFile(filename, 'base64', function (e, d) { + if (e) { + codeEditor.setValue('加载文件失败:\n' + e) + editor_multi.id = '' + return; + } + var str = editor.util.decode64(d) + codeEditor.setValue(str) + _fileValues[1] = str + }) + } + + editor_multi.writeFileDone = function () { + fs.writeFile(_fileValues[0], editor.util.encode64(codeEditor.getValue() || ''), 'base64', function (err, data) { + if (err) printe('文件写入失败,请手动粘贴至' + _fileValues[0] + '\n' + err); + else { + editor_multi.hide(); + printf(_fileValues[0] + " 写入成功,F5刷新后生效"); + } + }); + } + + editor_multi.editCommentJs = function (mod) { + var dict = { + loc: '_server/table/comment.js', + enemyitem: '_server/table/comment.js', + floor: '_server/table/comment.js', + tower: '_server/table/data.comment.js', + functions: '_server/table/functions.comment.js', + commonevent: '_server/table/events.comment.js', + plugins: '_server/table/plugins.comment.js', + } + editor_multi.lintAutocomplete = true + editor_multi.setLint() + editor_multi.importFile(dict[mod]) + } + return editor_multi; } //editor_multi=editor_multi(); \ No newline at end of file diff --git a/_server/editor_table.js b/_server/editor_table.js new file mode 100644 index 00000000..e141e5ff --- /dev/null +++ b/_server/editor_table.js @@ -0,0 +1,383 @@ +editor_table_wrapper = function (editor) { + + editor_table = function () { + + } + + ///////////////////////////////////////////////////////////////////////////// + // HTML模板 + + editor_table.prototype.select = function (value, values) { + let content = editor.table.option(value) + + values.map(function (v) { + return editor.table.option(v) + }).join('') + return /* html */`\n` + } + editor_table.prototype.option = function (value) { + return /* html */`\n` + } + editor_table.prototype.text = function (value) { + return /* html */`\n` + } + editor_table.prototype.checkbox = function (value) { + return /* html */`\n` + } + editor_table.prototype.textarea = function (value, indent) { + return /* html */`\n` + } + + editor_table.prototype.title = function () { + return /* html */`\n条目注释值\n` + } + + editor_table.prototype.gap = function (field) { + return /* html */`--------${field}\n` + } + + editor_table.prototype.tr = function (guid, field, shortField, commentHTMLescape, cobjstr, shortCommentHTMLescape, tdstr) { + return /* html */` + ${shortField} + ${shortCommentHTMLescape} +
${tdstr}
+ \n` + } + + + ///////////////////////////////////////////////////////////////////////////// + // 表格生成的控制 + + /** + * 注释对象的默认值 + */ + editor_table.prototype.defaultcobj = { + // 默认是文本域 + _type: 'textarea', + _data: '', + _string: function (args) {//object~[field,cfield,vobj,cobj] + var thiseval = args.vobj; + return (typeof (thiseval) === typeof ('')) && thiseval[0] === '"'; + }, + // 默认情况下 非对象和数组的视为叶节点 + _leaf: function (args) {//object~[field,cfield,vobj,cobj] + var thiseval = args.vobj; + if (thiseval == null || thiseval == undefined) return true;//null,undefined + if (typeof (thiseval) === typeof ('')) return true;//字符串 + if (Object.keys(thiseval).length === 0) return true;//数字,true,false,空数组,空对象 + return false; + }, + } + + /** + * 把来自数据文件的obj和来自*comment.js的commentObj组装成表格 + * commentObj在无视['_data']的意义下与obj同形 + * 即: commentObj['_data']['a']['_data']['b'] 与 obj['a']['b'] 是对应的 + * 在此意义下, 两者的结构是一致的 + * 在commentObj没有被定义的obj的分支, 会取defaultcobj作为默认值 + * 因此在深度优先遍历时,维护 + * field="['a']['b']" + * cfield="['_data']['a']['_data']['b']" + * vobj=obj['a']['b'] + * cobj=commentObj['_data']['a']['_data']['b'] + * cobj + * cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii]) + * 每一项若未定义,就从defaultcobj中取 + * 当其是函数不是具体值时,把args = {field: field, cfield: cfield, vobj: vobj, cobj: cobj}代入算出该值 + * 得到的叶节点的结构如下 + * tr>td[title=field] + * >td[title=comment,cobj=cobj:json] + * >td>div>input[value=thiseval] + * 返回结果 + * 返回一个对象, 假设被命名为tableinfo + * 在把一个 table 的 innerHTML 赋值为 tableinfo.HTML 后 + * 再调 tableinfo.listen(tableinfo.guids) 进行绑定事件 + * @param {Object} obj + * @param {Object} commentObj + * @returns {{"HTML":String,"guids":String[],"listen":Function}} + */ + editor_table.prototype.objToTable = function (obj, commentObj) { + // 表格抬头 + var outstr = [editor.table.title()]; + var guids = []; + var defaultcobj = this.defaultcobj + /** + * 深度优先遍历, p*即为父节点的四个属性 + * @param {String} pfield + * @param {String} pcfield + * @param {Object} pvobj + * @param {Object} pcobj + */ + var recursionParse = function (pfield, pcfield, pvobj, pcobj) { + var keysForTableOrder = {}; + var voidMark = {}; + // 1. 按照pcobj排序生成 + if (pcobj && pcobj['_data']) { + for (var ii in pcobj['_data']) keysForTableOrder[ii] = voidMark; + } + // 2. 对每个pvobj且不在pcobj的,再添加到最后 + keysForTableOrder = Object.assign(keysForTableOrder, pvobj) + for (var ii in keysForTableOrder) { + // 3. 对于pcobj有但是pvobj中没有的, 弹出提示, (正常情况下editor_file会补全成null) + // 事实上能执行到这一步工程没崩掉打不开,就继续吧.. + if (keysForTableOrder[ii] === voidMark) { + if (typeof id_815975ad_ee6f_4684_aac7_397b7e392702 === "undefined") { + // alert('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈') + console.error('comment和data不匹配,请在群 HTML5造塔技术交流群 959329661 内反馈') + id_815975ad_ee6f_4684_aac7_397b7e392702 = 1; + } + pvobj[ii] = null; + } + var field = pfield + "['" + ii + "']"; + var cfield = pcfield + "['_data']['" + ii + "']"; + var vobj = pvobj[ii]; + var cobj = null; + if (pcobj && pcobj['_data'] && pcobj['_data'][ii]) { + // cobj存在时直接取 + cobj = Object.assign({}, defaultcobj, pcobj['_data'][ii]); + } else { + // 当其函数时代入参数算出cobj, 不存在时只取defaultcobj + if (pcobj && (pcobj['_data'] instanceof Function)) cobj = Object.assign({}, defaultcobj, pcobj['_data'](ii)); + else cobj = Object.assign({}, defaultcobj); + } + var args = { field: field, cfield: cfield, vobj: vobj, cobj: cobj } + // 当cobj的参数为函数时,代入args算出值 + for (var key in cobj) { + if (key === '_data') continue; + if (cobj[key] instanceof Function) cobj[key] = cobj[key](args); + } + // 标记为_hide的属性不展示 + if (cobj._hide) continue; + if (!cobj._leaf) { + // 不是叶节点时, 插入展开的标记并继续遍历, 此处可以改成按钮用来添加新项或折叠等 + outstr.push(editor.table.gap(field)); + recursionParse(field, cfield, vobj, cobj); + } else { + // 是叶节点时, 调objToTr_渲染 + var leafnode = editor.table.objToTr(obj, commentObj, field, cfield, vobj, cobj); + outstr.push(leafnode[0]); + guids.push(leafnode[1]); + } + } + } + // 开始遍历 + recursionParse("", "", obj, commentObj); + + var listen = function (guids) { + // 每个叶节点的事件绑定 + guids.forEach(function (guid) { + editor.table.guidListen(guid, obj, commentObj) + }); + } + return { "HTML": outstr.join(''), "guids": guids, "listen": listen }; + } + + /** + * 返回叶节点形如 + * tr>td[title=field] + * >td[title=comment,cobj=cobj:json] + * >td>div>input[value=thiseval] + * 参数意义在 objToTable 中已解释 + * @param {Object} obj + * @param {Object} commentObj + * @param {String} field + * @param {String} cfield + * @param {Object} vobj + * @param {Object} cobj + */ + editor_table.prototype.objToTr = function (obj, commentObj, field, cfield, vobj, cobj) { + var guid = editor.util.guid(); + var thiseval = vobj; + var comment = String(cobj._data); + + var charlength = 10; + // "['a']['b']" => "b" + var shortField = field.split("']").slice(-2)[0].split("['").slice(-1)[0]; + // 把长度超过 charlength 的字符改成 固定长度+...的形式 + shortField = (shortField.length < charlength ? shortField : shortField.slice(0, charlength) + '...'); + + // 完整的内容转义后供悬停查看 + var commentHTMLescape = editor.util.HTMLescape(comment); + // 把长度超过 charlength 的字符改成 固定长度+...的形式 + var shortCommentHTMLescape = (comment.length < charlength ? commentHTMLescape : editor.util.HTMLescape(comment.slice(0, charlength)) + '...'); + + var cobjstr = Object.assign({}, cobj); + delete cobjstr._data; + // 把cobj塞到第二个td的[cobj]中, 方便绑定事件时取 + cobjstr = editor.util.HTMLescape(JSON.stringify(cobjstr)); + + var tdstr = editor.table.objToTd(obj, commentObj, field, cfield, vobj, cobj) + var outstr = editor.table.tr(guid, field, shortField, commentHTMLescape, cobjstr, shortCommentHTMLescape, tdstr) + return [outstr, guid]; + } + + editor_table.prototype.objToTd = function (obj, commentObj, field, cfield, vobj, cobj) { + var thiseval = vobj; + if (cobj._select) { + var values = cobj._select.values; + return editor.table.select(thiseval, values); + } else if (cobj._input) { + return editor.table.text(thiseval); + } else if (cobj._bool) { + return editor.table.checkbox(thiseval); + } else { + var indent = 0; + return editor.table.textarea(thiseval, indent); + } + } + + ///////////////////////////////////////////////////////////////////////////// + // 表格的用户交互 + + /** + * 检查一个值是否允许被设置为当前输入 + * @param {Object} cobj + * @param {*} thiseval + */ + editor_table.prototype.checkRange = function (cobj, thiseval) { + if (cobj._range) { + return eval(cobj._range); + } + if (cobj._select) { + return cobj._select.values.indexOf(thiseval) !== -1; + } + if (cobj._bool) { + return [true, false].indexOf(thiseval) !== -1; + } + return true; + } + + /** + * 监听一个guid对应的表格项 + * @param {String} guid + */ + editor_table.prototype.guidListen = function (guid, obj, commentObj) { + // tr>td[title=field] + // >td[title=comment,cobj=cobj:json] + // >td>div>input[value=thiseval] + var thisTr = document.getElementById(guid); + var input = thisTr.children[2].children[0].children[0]; + var field = thisTr.children[0].getAttribute('title'); + var cobj = JSON.parse(thisTr.children[1].getAttribute('cobj')); + var modeNode = thisTr.parentNode; + while (!editor_mode._ids.hasOwnProperty(modeNode.getAttribute('id'))) { + modeNode = modeNode.parentNode; + } + input.onchange = function () { + editor.table.onchange(guid, obj, commentObj, thisTr, input, field, cobj, modeNode) + } + // 用检测两次单击的方式来实现双击(以支持手机端的双击) + var doubleClickCheck = [0]; + thisTr.onclick = function () { + var newClick = new Date().getTime(); + var lastClick = doubleClickCheck.shift(); + doubleClickCheck.push(newClick); + if (newClick - lastClick < 500) { + editor.table.dblclickfunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode) + } + } + } + + /** + * 表格的值变化时 + */ + editor_table.prototype.onchange = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + var thiseval = null; + if (input.checked != null) input.value = input.checked; + try { + thiseval = JSON.parse(input.value); + } catch (ee) { + printe(field + ' : ' + ee); + throw ee; + } + if (editor.table.checkRange(cobj, thiseval)) { + editor_mode.addAction(['change', field, thiseval]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } else { + printe(field + ' : 输入的值不合要求,请鼠标放置在注释上查看说明'); + } + } + + /** + * 双击表格时 + * 正常编辑: 尝试用事件编辑器或多行文本编辑器打开 + * 添加: 在该项的同一级创建一个内容为null新的项, 刷新后生效并可以继续编辑 + * 删除: 删除该项, 刷新后生效 + * 在点击按钮 添加/删除 后,下一次双击将被视为 添加/删除 + */ + editor_table.prototype.dblclickfunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) { + if (editor_mode.doubleClickMode === 'change') { + if (cobj._type === 'event') editor_blockly.import(guid, { type: cobj._event }); + if (cobj._type === 'textarea') editor_multi.import(guid, { lint: cobj._lint, string: cobj._string }); + } else if (editor_mode.doubleClickMode === 'add') { + editor_mode.doubleClickMode = 'change'; + editor.table.addfunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode) + } else if (editor_mode.doubleClickMode === 'delete') { + editor_mode.doubleClickMode = 'change'; + editor.table.deletefunc(guid, obj, commentObj, thisTr, input, field, cobj, modeNode) + } + } + + /** + * 删除表格项 + */ + editor_table.prototype.deletefunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + if (editor.table.checkRange(cobj, null)) { + editor_mode.addAction(['delete', field, undefined]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } else { + printe(field + ' : 该值不允许为null,无法删除'); + } + } + + /** + * 添加表格项 + */ + editor_table.prototype.addfunc = function (guid, obj, commentObj, thisTr, input, field, cobj, modeNode) { + editor_mode.onmode(editor_mode._ids[modeNode.getAttribute('id')]); + + var mode = document.getElementById('editModeSelect').value; + + // 1.输入id + var newid = prompt('请输入新项的ID(仅公共事件支持中文ID)'); + if (newid == null || newid.length == 0) { + return; + } + + // 检查commentEvents + if (mode !== 'commonevent') { + // 2.检查id是否符合规范或与已有id重复 + if (!/^[a-zA-Z0-9_]+$/.test(newid)) { + printe('id不符合规范, 请使用大小写字母数字下划线来构成'); + return; + } + } + + var conflict = true; + var basefield = field.replace(/\[[^\[]*\]$/, ''); + if (basefield === "['main']") { + printe("全塔属性 ~ ['main'] 不允许添加新值"); + return; + } + try { + var baseobj = eval('obj' + basefield); + conflict = newid in baseobj; + } catch (ee) { + // 理论上这里不会发生错误 + printe(ee); + throw ee; + } + if (conflict) { + printe('id已存在, 请直接修改该项的值'); + return; + } + // 3.添加 + editor_mode.addAction(['add', basefield + "['" + newid + "']", null]); + editor_mode.onmode('save');//自动保存 删掉此行的话点保存按钮才会保存 + } + + ///////////////////////////////////////////////////////////////////////////// + editor.constructor.prototype.table = new editor_table(); +} +//editor_table_wrapper(editor); \ No newline at end of file diff --git a/_server/editor_unsorted_1.js b/_server/editor_unsorted_1.js new file mode 100644 index 00000000..0d3861d8 --- /dev/null +++ b/_server/editor_unsorted_1.js @@ -0,0 +1,839 @@ +editor_unsorted_1_wrapper=function(editor){ + +editor.constructor.prototype.listen=function () { + document.body.onmousedown = function (e) { + //console.log(e); + var clickpath = []; + var getpath=function(e) { + var path = []; + var currentElem = e.target; + while (currentElem) { + path.push(currentElem); + currentElem = currentElem.parentElement; + } + if (path.indexOf(window) === -1 && path.indexOf(document) === -1) + path.push(document); + if (path.indexOf(window) === -1) + path.push(window); + return path; + } + getpath(e).forEach(function (node) { + if (!node.getAttribute) return; + var id_ = node.getAttribute('id'); + if (id_) { + if (['left', 'left1', 'left2', 'left3', 'left4', 'left5', 'left8', 'mobileview'].indexOf(id_) !== -1) clickpath.push('edit'); + clickpath.push(id_); + } + }); + + var unselect=true; + for(var ii=0,thisId;thisId=['edit','tip','brushMod','brushMod2','brushMod3','layerMod','layerMod2','layerMod3','viewportButtons'][ii];ii++){ + if (clickpath.indexOf(thisId) !== -1){ + unselect=false; + break; + } + } + if (unselect) { + if (clickpath.indexOf('eui') === -1) { + if (selectBox.isSelected()) { + editor_mode.onmode(''); + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('地图保存成功'); + }); + } + selectBox.isSelected(false); + editor.info = {}; + } + } + //editor.mode.onmode(''); + if (e.button!=2 && !editor.isMobile){ + editor.hideMidMenu(); + } + if (clickpath.indexOf('down') !== -1 && editor.isMobile && clickpath.indexOf('midMenu') === -1){ + editor.hideMidMenu(); + } + if(clickpath.length>=2 && clickpath[0].indexOf('id_')===0){editor.lastClickId=clickpath[0]} + } + + var eui=document.getElementById('eui'); + var uc = eui.getContext('2d'); + + function fillPos(pos) { + uc.fillStyle = '#' + ~~(Math.random() * 8) + ~~(Math.random() * 8) + ~~(Math.random() * 8); + uc.fillRect(pos.x * 32 + 12 - core.bigmap.offsetX, pos.y * 32 + 12 - core.bigmap.offsetY, 8, 8); + }//在格子内画一个随机色块 + + function eToLoc(e) { + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop + var xx=e.clientX,yy=e.clientY + if(editor.isMobile){xx=e.touches[0].clientX,yy=e.touches[0].clientY} + editor.loc = { + 'x': scrollLeft + xx - mid.offsetLeft - mapEdit.offsetLeft, + 'y': scrollTop + yy - mid.offsetTop - mapEdit.offsetTop, + 'size': editor.isMobile?(32*innerWidth*0.96/core.__PIXELS__):32 + }; + return editor.loc; + }//返回可用的组件内坐标 + + function locToPos(loc, addViewportOffset) { + var offsetX=0, offsetY=0; + if (addViewportOffset){ + offsetX=core.bigmap.offsetX/32; + offsetY=core.bigmap.offsetY/32; + } + editor.pos = {'x': ~~(loc.x / loc.size)+offsetX, 'y': ~~(loc.y / loc.size)+offsetY} + return editor.pos; + } + + var holdingPath = 0; + var stepPostfix = null;//用于存放寻路检测的第一个点之后的后续移动 + + var mouseOutCheck = 2; + + function clear1() { + if (mouseOutCheck > 1) { + mouseOutCheck--; + setTimeout(clear1, 1000); + return; + } + holdingPath = 0; + stepPostfix = []; + uc.clearRect(0, 0, core.__PIXELS__, core.__PIXELS__); + }//用于鼠标移出canvas时的自动清除状态 + + eui.oncontextmenu=function(e){e.preventDefault()} + + eui.ondblclick = function(e) { + // 双击地图可以选中素材 + var loc = eToLoc(e); + var pos = locToPos(loc,true); + editor.setSelectBoxFromInfo(editor[editor.layerMod][pos.y][pos.x]); + return; + } + + eui.onmousedown = function (e) { + if (e.button==2){ + var loc = eToLoc(e); + var pos = locToPos(loc,true); + editor.showMidMenu(e.clientX,e.clientY); + return; + } + if (!selectBox.isSelected()) { + var loc = eToLoc(e); + var pos = locToPos(loc,true); + editor_mode.onmode('nextChange'); + editor_mode.onmode('loc'); + //editor_mode.loc(); + //tip.whichShow(1); + if(editor.isMobile)editor.showMidMenu(e.clientX,e.clientY); + return; + } + + + holdingPath = 1; + mouseOutCheck = 2; + setTimeout(clear1); + e.stopPropagation(); + uc.clearRect(0, 0, core.__PIXELS__, core.__PIXELS__); + var loc = eToLoc(e); + var pos = locToPos(loc,true); + stepPostfix = []; + stepPostfix.push(pos); + fillPos(pos); + } + + eui.onmousemove = function (e) { + if (!selectBox.isSelected()) { + //tip.whichShow(1); + return; + } + + if (holdingPath == 0) { + return; + } + mouseOutCheck = 2; + e.stopPropagation(); + var loc = eToLoc(e); + var pos = locToPos(loc,true); + var pos0 = stepPostfix[stepPostfix.length - 1] + var directionDistance = [pos.y - pos0.y, pos0.x - pos.x, pos0.y - pos.y, pos.x - pos0.x] + var max = 0, index = 4; + for (var i = 0; i < 4; i++) { + if (directionDistance[i] > max) { + index = i; + max = directionDistance[i]; + } + } + var pos = [{'x': 0, 'y': 1}, {'x': -1, 'y': 0}, {'x': 0, 'y': -1}, {'x': 1, 'y': 0}, false][index] + if (pos) { + pos.x += pos0.x; + pos.y += pos0.y; + stepPostfix.push(pos); + fillPos(pos); + } + } + + eui.onmouseup = function (e) { + if (!selectBox.isSelected()) { + //tip.whichShow(1); + return; + } + holdingPath = 0; + e.stopPropagation(); + if (stepPostfix && stepPostfix.length) { + editor.preMapData = JSON.parse(JSON.stringify({map:editor.map,fgmap:editor.fgmap,bgmap:editor.bgmap})); + if(editor.brushMod!=='line'){ + var x0=stepPostfix[0].x; + var y0=stepPostfix[0].y; + var x1=stepPostfix[stepPostfix.length-1].x; + var y1=stepPostfix[stepPostfix.length-1].y; + if(x0>x1){x0^=x1;x1^=x0;x0^=x1;}//swap + if(y0>y1){y0^=y1;y1^=y0;y0^=y1;}//swap + stepPostfix=[]; + for(var jj=y0;jj<=y1;jj++){ + for(var ii=x0;ii<=x1;ii++){ + stepPostfix.push({x:ii,y:jj}) + } + } + } + currDrawData.pos = JSON.parse(JSON.stringify(stepPostfix)); + currDrawData.info = JSON.parse(JSON.stringify(editor.info)); + reDo = null; + // console.log(stepPostfix); + if(editor.brushMod==='tileset' && core.tilesets.indexOf(editor.info.images)!==-1){ + var imgWidth=~~(core.material.images.tilesets[editor.info.images].width/32); + var x0=stepPostfix[0].x; + var y0=stepPostfix[0].y; + var idnum=editor.info.idnum; + for (var ii = 0; ii < stepPostfix.length; ii++){ + if(stepPostfix[ii].y!=y0){ + y0++; + idnum+=imgWidth; + } + editor[editor.layerMod][stepPostfix[ii].y][stepPostfix[ii].x] = editor.ids[editor.indexs[idnum+stepPostfix[ii].x-x0]]; + } + } else { + for (var ii = 0; ii < stepPostfix.length; ii++) + editor[editor.layerMod][stepPostfix[ii].y][stepPostfix[ii].x] = editor.info; + } + // console.log(editor.map); + editor.updateMap(); + holdingPath = 0; + stepPostfix = []; + uc.clearRect(0, 0, core.__PIXELS__, core.__PIXELS__); + } + } + + /* + document.getElementById('mid').onkeydown = function (e) { + console.log(e); + if (e.keyCode==37) { + editor.moveViewport(-1, 0); + } + if (e.keyCode==38) { + editor.moveViewport(0, -1); + } + if (e.keyCode==39) { + editor.moveViewport(1, 0); + } + if (e.keyCode==40) { + editor.moveViewport(0, 1); + } + } + */ + + document.getElementById('mid').onmousewheel = function (e) { + e.preventDefault(); + var wheel = function (direct) { + var index=editor.core.floorIds.indexOf(editor.currentFloorId); + var toId = editor.currentFloorId; + + if (direct>0 && index0) + toId = editor.core.floorIds[index-1]; + else return; + + editor_mode.onmode('nextChange'); + editor_mode.onmode('floor'); + document.getElementById('selectFloor').value = toId; + editor.changeFloor(toId); + } + + try { + if (e.wheelDelta) + wheel(Math.sign(e.wheelDelta)) + else if (e.detail) + wheel(Math.sign(e.detail)); + } + catch (ee) { + console.log(ee); + } + } + + editor.preMapData = null; + var currDrawData = { + pos: [], + info: {} + }; + var reDo = null; + var shortcut = core.getLocalStorage('shortcut',{48: 0, 49: 0, 50: 0, 51: 0, 52: 0, 53: 0, 54: 0, 55: 0, 56: 0, 57: 0}); + document.body.onkeydown = function (e) { + + // 监听Ctrl+S保存 + if (e.ctrlKey && e.keyCode == 83) { + e.preventDefault(); + if (editor_multi.id != "") { + editor_multi.confirm(); // 保存脚本编辑器 + } + else if (editor_blockly.id != "") { + editor_blockly.confirm(); // 保存事件编辑器 + } + else { + editor_mode.saveFloor(); + } + return; + } + + // 如果是开启事件/脚本编辑器状态,则忽略 + if (editor_multi.id!="" || editor_blockly.id!="") + return; + + // 禁止快捷键的默认行为 + if (e.ctrlKey && [89, 90, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1) + e.preventDefault(); + if (e.altKey && [48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1) + e.preventDefault(); + //Ctrl+z 撤销上一步undo + if (e.keyCode == 90 && e.ctrlKey && editor.preMapData && currDrawData.pos.length && selectBox.isSelected()) { + editor.map = JSON.parse(JSON.stringify(editor.preMapData.map)); + editor.fgmap = JSON.parse(JSON.stringify(editor.preMapData.fgmap)); + editor.bgmap = JSON.parse(JSON.stringify(editor.preMapData.bgmap)); + editor.updateMap(); + reDo = JSON.parse(JSON.stringify(currDrawData)); + currDrawData = {pos: [], info: {}}; + editor.preMapData = null; + } + //Ctrl+y 重做一步redo + if (e.keyCode == 89 && e.ctrlKey && reDo && reDo.pos.length && selectBox.isSelected()) { + editor.preMapData = JSON.parse(JSON.stringify({map:editor.map,fgmap:editor.fgmap,bgmap:editor.bgmap})); + for (var j = 0; j < reDo.pos.length; j++) + editor.map[reDo.pos[j].y][reDo.pos[j].x] = JSON.parse(JSON.stringify(reDo.info)); + + editor.updateMap(); + currDrawData = JSON.parse(JSON.stringify(reDo)); + reDo = null; + } + + // PGUP和PGDOWN切换楼层 + if (e.keyCode==33) { + e.preventDefault(); + var index=editor.core.floorIds.indexOf(editor.currentFloorId); + if (index0) { + var toId = editor.core.floorIds[index-1]; + editor_mode.onmode('nextChange'); + editor_mode.onmode('floor'); + document.getElementById('selectFloor').value = toId; + editor.changeFloor(toId); + } + } + //ctrl + 0~9 切换到快捷图块 + if (e.ctrlKey && [48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1){ + editor.setSelectBoxFromInfo(JSON.parse(JSON.stringify(shortcut[e.keyCode]||0))); + } + //alt + 0~9 改变快捷图块 + if (e.altKey && [48, 49, 50, 51, 52, 53, 54, 55, 56, 57].indexOf(e.keyCode) !== -1){ + var infoToSave = JSON.stringify(editor.info||0); + if(infoToSave==JSON.stringify({}))return; + shortcut[e.keyCode]=JSON.parse(infoToSave); + printf('已保存该快捷图块, ctrl + '+(e.keyCode-48)+' 使用.') + core.setLocalStorage('shortcut',shortcut); + } + var focusElement = document.activeElement; + if (!focusElement || focusElement.tagName.toLowerCase()=='body') { + // wasd平移大地图 + if (e.keyCode==87) + editor.moveViewport(0,-1) + else if (e.keyCode==65) + editor.moveViewport(-1,0) + else if (e.keyCode==83) + editor.moveViewport(0,1); + else if (e.keyCode==68) + editor.moveViewport(1,0); + } + } + + var getScrollBarHeight = function () { + var outer = document.createElement("div"); + outer.style.visibility = "hidden"; + outer.style.width = "100px"; + outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps + + document.body.appendChild(outer); + + var widthNoScroll = outer.offsetWidth; + // force scrollbars + outer.style.overflow = "scroll"; + + // add innerdiv + var inner = document.createElement("div"); + inner.style.width = "100%"; + outer.appendChild(inner); + + var widthWithScroll = inner.offsetWidth; + + // remove divs + outer.parentNode.removeChild(outer); + + return widthNoScroll - widthWithScroll; + } + var scrollBarHeight = getScrollBarHeight(); + + var dataSelection = document.getElementById('dataSelection'); + var iconLib=document.getElementById('iconLib'); + iconLib.onmousedown = function (e) { + e.stopPropagation(); + if (!editor.isMobile && e.clientY>=((core.__SIZE__==13?630:655) - scrollBarHeight)) return; + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + var loc = { + 'x': scrollLeft + e.clientX + iconLib.scrollLeft - right.offsetLeft - iconLib.offsetLeft, + 'y': scrollTop + e.clientY + iconLib.scrollTop - right.offsetTop - iconLib.offsetTop, + 'size': 32 + }; + editor.loc = loc; + var pos = locToPos(loc); + for (var spriter in editor.widthsX) { + if (pos.x >= editor.widthsX[spriter][1] && pos.x < editor.widthsX[spriter][2]) { + var ysize = spriter.indexOf('48') === -1 ? 32 : 48; + loc.ysize = ysize; + pos.images = editor.widthsX[spriter][0]; + pos.y = ~~(loc.y / loc.ysize); + if(core.tilesets.indexOf(pos.images)==-1)pos.x = editor.widthsX[spriter][1]; + var autotiles = core.material.images['autotile']; + if (pos.images == 'autotile') { + var imNames = Object.keys(autotiles); + if ((pos.y + 1) * ysize > editor.widthsX[spriter][3]) + pos.y = ~~(editor.widthsX[spriter][3] / ysize) - 4; + else { + for (var i = 0; i < imNames.length; i++) { + if (pos.y >= 4 * i && pos.y < 4 * (i + 1)) { + pos.images = imNames[i]; + pos.y = 4 * i; + } + } + } + } else if ((pos.y + 1) * ysize > editor.widthsX[spriter][3]) + pos.y = ~~(editor.widthsX[spriter][3] / ysize) - 1; + + selectBox.isSelected(true); + // console.log(pos,core.material.images[pos.images].height) + dataSelection.style.left = pos.x * 32 + 'px'; + dataSelection.style.top = pos.y * ysize + 'px'; + dataSelection.style.height = ysize - 6 + 'px'; + + if (pos.x == 0 && pos.y == 0) { + // editor.info={idnum:0, id:'empty','images':'清除块', 'y':0}; + editor.info = 0; + } else if(pos.x == 0 && pos.y == 1){ + editor.info = editor.ids[editor.indexs[17]]; + } else { + if (Object.prototype.hasOwnProperty.call(autotiles, pos.images)) editor.info = {'images': pos.images, 'y': 0}; + else if (pos.images == 'terrains') editor.info = {'images': pos.images, 'y': pos.y - 2}; + else if (core.tilesets.indexOf(pos.images)!=-1) editor.info = {'images': pos.images, 'y': pos.y, 'x': pos.x-editor.widthsX[spriter][1]}; + else editor.info = {'images': pos.images, 'y': pos.y}; + + for (var ii = 0; ii < editor.ids.length; ii++) { + if ((core.tilesets.indexOf(pos.images)!=-1 && editor.info.images == editor.ids[ii].images + && editor.info.y == editor.ids[ii].y && editor.info.x == editor.ids[ii].x) + || (Object.prototype.hasOwnProperty.call(autotiles, pos.images) && editor.info.images == editor.ids[ii].id + && editor.info.y == editor.ids[ii].y) + || (core.tilesets.indexOf(pos.images)==-1 && editor.info.images == editor.ids[ii].images + && editor.info.y == editor.ids[ii].y ) + ) { + + editor.info = editor.ids[ii]; + break; + } + } + } + tip.infos(JSON.parse(JSON.stringify(editor.info))); + editor_mode.onmode('nextChange'); + editor_mode.onmode('enemyitem'); + //editor_mode.enemyitem(); + } + } + } + + var midMenu=document.getElementById('midMenu'); + midMenu.oncontextmenu=function(e){e.preventDefault()} + editor.lastRightButtonPos=[{x:0,y:0},{x:0,y:0}]; + editor.showMidMenu=function(x,y){ + editor.lastRightButtonPos=JSON.parse(JSON.stringify( + [editor.pos,editor.lastRightButtonPos[0]] + )); + var locStr='('+editor.lastRightButtonPos[1].x+','+editor.lastRightButtonPos[1].y+')'; + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + + // 检测是否是上下楼 + var thisevent = editor.map[editor.pos.y][editor.pos.x]; + if (thisevent.id=='upFloor') { + addFloorEvent.style.display='block'; + addFloorEvent.children[0].innerHTML='绑定上楼事件'; + } + else if (thisevent.id=='downFloor') { + addFloorEvent.style.display='block'; + addFloorEvent.children[0].innerHTML='绑定下楼事件'; + } + else addFloorEvent.style.display='none'; + + chooseThis.children[0].innerHTML='选中此点'+'('+editor.pos.x+','+editor.pos.y+')' + copyLoc.children[0].innerHTML='复制事件'+locStr+'到此处'; + moveLoc.children[0].innerHTML='交换事件'+locStr+'与此事件的位置'; + midMenu.style='top:'+(y+scrollTop)+'px;left:'+(x+scrollLeft)+'px;'; + } + editor.hideMidMenu=function(){ + if(editor.isMobile){ + setTimeout(function(){ + midMenu.style='display:none'; + },200) + } else { + midMenu.style='display:none'; + } + } + + var addFloorEvent = document.getElementById('addFloorEvent'); + addFloorEvent.onmousedown = function(e) { + editor.hideMidMenu(); + e.stopPropagation(); + var thisevent = editor.map[editor.pos.y][editor.pos.x]; + if (thisevent.id=='upFloor') { + editor.currentFloorData.changeFloor[editor.pos.x+","+editor.pos.y] = {"floorId": ":next", "stair": "downFloor"}; + } + else if (thisevent.id=='downFloor') { + editor.currentFloorData.changeFloor[editor.pos.x+","+editor.pos.y] = {"floorId": ":before", "stair": "upFloor"}; + } + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('添加楼梯事件成功'); + editor.drawPosSelection(); + editor_mode.showMode('loc'); + }); + } + + var chooseThis = document.getElementById('chooseThis'); + chooseThis.onmousedown = function(e){ + editor.hideMidMenu(); + e.stopPropagation(); + selectBox.isSelected(false); + + editor_mode.onmode('nextChange'); + editor_mode.onmode('loc'); + //editor_mode.loc(); + //tip.whichShow(1); + if(editor.isMobile)editor.showdataarea(false); + } + + var chooseInRight = document.getElementById('chooseInRight'); + chooseInRight.onmousedown = function(e){ + editor.hideMidMenu(); + e.stopPropagation(); + var thisevent = editor[editor.layerMod][editor.pos.y][editor.pos.x]; + editor.setSelectBoxFromInfo(thisevent); + } + + var fields = Object.keys(editor.file.comment._data.floors._data.loc._data); + + var copyLoc = document.getElementById('copyLoc'); + copyLoc.onmousedown = function(e){ + editor.hideMidMenu(); + e.stopPropagation(); + editor.preMapData = null; + reDo = null; + editor_mode.onmode(''); + var now = editor.pos; + var last = editor.lastRightButtonPos[1]; + var lastevent = editor.map[last.y][last.x]; + var lastinfo = 0; + if(lastevent==0){ + lastinfo = 0; + } else { + var ids=editor.indexs[lastevent.idnum]; + ids=ids[0]?ids[0]:ids; + lastinfo=editor.ids[ids]; + } + editor.map[now.y][now.x]=lastinfo; + editor.updateMap(); + fields.forEach(function(v){ + editor.currentFloorData[v][now.x+','+now.y]=editor.currentFloorData[v][last.x+','+last.y] + }) + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('复制事件成功'); + editor.drawPosSelection(); + }); + } + + var moveLoc = document.getElementById('moveLoc'); + moveLoc.onmousedown = function(e){ + editor.hideMidMenu(); + e.stopPropagation(); + editor.preMapData = null; + reDo = null; + var thisevent = editor.map[editor.pos.y][editor.pos.x]; + if(thisevent==0){ + editor.info = 0; + } else { + var ids=editor.indexs[thisevent.idnum]; + ids=ids[0]?ids[0]:ids; + editor.info=editor.ids[ids]; + } + editor_mode.onmode(''); + var now = editor.pos; + var last = editor.lastRightButtonPos[1]; + + var lastevent = editor.map[last.y][last.x]; + var lastinfo = 0; + if(lastevent==0){ + lastinfo = 0; + } else { + var ids=editor.indexs[lastevent.idnum]; + ids=ids[0]?ids[0]:ids; + lastinfo=editor.ids[ids]; + } + editor.map[last.y][last.x]=editor.info; + editor.map[now.y][now.x]=lastinfo; + editor.updateMap(); + + fields.forEach(function(v){ + var temp_atsfcytaf=editor.currentFloorData[v][now.x+','+now.y]; + editor.currentFloorData[v][now.x+','+now.y]=editor.currentFloorData[v][last.x+','+last.y]; + editor.currentFloorData[v][last.x+','+last.y]=temp_atsfcytaf; + }) + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('两位置的事件已互换'); + editor.drawPosSelection(); + }); + } + + var _clearPoint = function (clearPoint) { + editor.hideMidMenu(); + editor.preMapData = null; + reDo = null; + editor.info = 0; + editor_mode.onmode(''); + var now = editor.pos; + if (clearPoint) + editor.map[now.y][now.x]=editor.info; + editor.updateMap(); + fields.forEach(function(v){ + delete editor.currentFloorData[v][now.x+','+now.y]; + }) + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf(clearPoint?'清空该点和事件成功':'只清空该点事件成功'); + editor.drawPosSelection(); + }); + } + + var clearEvent = document.getElementById('clearEvent'); + clearEvent.onmousedown = function (e) { + e.stopPropagation(); + _clearPoint(false); + } + + var clearLoc = document.getElementById('clearLoc'); + clearLoc.onmousedown = function(e){ + e.stopPropagation(); + _clearPoint(true); + } + + var brushMod=document.getElementById('brushMod'); + brushMod.onchange=function(){ + editor.brushMod=brushMod.value; + } + + var brushMod2=document.getElementById('brushMod2'); + if(brushMod2)brushMod2.onchange=function(){ + editor.brushMod=brushMod2.value; + } + + var brushMod3=document.getElementById('brushMod3'); + if(brushMod3)brushMod3.onchange=function(){ + editor.brushMod=brushMod3.value; + } + + var bgc = document.getElementById('bg'), fgc = document.getElementById('fg'), + evc = document.getElementById('event'), ev2c = document.getElementById('event2'); + + var layerMod=document.getElementById('layerMod'); + layerMod.onchange=function(){ + editor.layerMod=layerMod.value; + [bgc,fgc,evc,ev2c].forEach(function (x) { + x.style.opacity = 1; + }); + + // 手机端.... + if (editor.isMobile) { + if (layerMod.value == 'bgmap') { + [fgc,evc,ev2c].forEach(function (x) { + x.style.opacity = 0.3; + }); + } + if (layerMod.value == 'fgmap') { + [bgc,evc,ev2c].forEach(function (x) { + x.style.opacity = 0.3; + }); + } + } + } + + var layerMod2=document.getElementById('layerMod2'); + if(layerMod2)layerMod2.onchange=function(){ + editor.layerMod=layerMod2.value; + [fgc,evc,ev2c].forEach(function (x) { + x.style.opacity = 0.3; + }); + bgc.style.opacity = 1; + } + + var layerMod3=document.getElementById('layerMod3'); + if(layerMod3)layerMod3.onchange=function(){ + editor.layerMod=layerMod3.value; + [bgc,evc,ev2c].forEach(function (x) { + x.style.opacity = 0.3; + }); + fgc.style.opacity = 1; + } + + var viewportButtons=document.getElementById('viewportButtons'); + for(var ii=0,node;node=viewportButtons.children[ii];ii++){ + (function(x,y){ + node.onclick=function(){ + editor.moveViewport(x,y); + } + })([-1,0,0,1][ii],[0,-1,1,0][ii]); + } +} + +editor.constructor.prototype.mobile_listen=function () { + if(!editor.isMobile)return; + + var mobileview=document.getElementById('mobileview'); + var editModeSelect=document.getElementById('editModeSelect'); + var mid=document.getElementById('mid'); + var right=document.getElementById('right'); + var mobileeditdata=document.getElementById('mobileeditdata'); + + + editor.showdataarea=function(callShowMode){ + mid.style='z-index:-1;opacity: 0;'; + right.style='z-index:-1;opacity: 0;'; + mobileeditdata.style=''; + if(callShowMode)editor.mode.showMode(editModeSelect.value); + editor.hideMidMenu(); + } + mobileview.children[0].onclick=function(){ + editor.showdataarea(true) + } + mobileview.children[1].onclick=function(){ + mid.style=''; + right.style='z-index:-1;opacity: 0;'; + mobileeditdata.style='z-index:-1;opacity: 0;'; + editor.lastClickId=''; + } + mobileview.children[3].onclick=function(){ + mid.style='z-index:-1;opacity: 0;'; + right.style=''; + mobileeditdata.style='z-index:-1;opacity: 0;'; + editor.lastClickId=''; + } + + + var gettrbyid=function(){ + if(!editor.lastClickId)return false; + thisTr = document.getElementById(editor.lastClickId); + input = thisTr.children[2].children[0].children[0]; + field = thisTr.children[0].getAttribute('title'); + cobj = JSON.parse(thisTr.children[1].getAttribute('cobj')); + return [thisTr,input,field,cobj]; + } + mobileeditdata.children[0].onclick=function(){ + var info = gettrbyid() + if(!info)return; + info[1].ondblclick() + } + mobileeditdata.children[1].onclick=function(){ + var info = gettrbyid() + if(!info)return; + printf(info[2]) + } + mobileeditdata.children[2].onclick=function(){ + var info = gettrbyid() + if(!info)return; + printf(info[0].children[1].getAttribute('title')) + } + + //===== + + document.body.ontouchstart=document.body.onmousedown; + document.body.onmousedown=null; + + + var eui=document.getElementById('eui'); + eui.ontouchstart=eui.onmousedown + eui.onmousedown=null + eui.ontouchmove=eui.onmousemove + eui.onmousemove=null + eui.ontouchend=eui.onmouseup + eui.onmouseup=null + + + var chooseThis = document.getElementById('chooseThis'); + chooseThis.ontouchstart=chooseThis.onmousedown + chooseThis.onmousedown=null + var chooseInRight = document.getElementById('chooseInRight'); + chooseInRight.ontouchstart=chooseInRight.onmousedown + chooseInRight.onmousedown=null + var copyLoc = document.getElementById('copyLoc'); + copyLoc.ontouchstart=copyLoc.onmousedown + copyLoc.onmousedown=null + var moveLoc = document.getElementById('moveLoc'); + moveLoc.ontouchstart=moveLoc.onmousedown + moveLoc.onmousedown=null + var clearLoc = document.getElementById('clearLoc'); + clearLoc.ontouchstart=clearLoc.onmousedown + clearLoc.onmousedown=null +} + +} \ No newline at end of file diff --git a/_server/editor_unsorted_2.js b/_server/editor_unsorted_2.js new file mode 100644 index 00000000..17b8092e --- /dev/null +++ b/_server/editor_unsorted_2.js @@ -0,0 +1,636 @@ +editor_unsorted_2_wrapper=function(editor_mode){ + + editor_mode.constructor.prototype.listen=function (callback) { + var newIdIdnum = document.getElementById('newIdIdnum'); + newIdIdnum.children[2].onclick = function () { + if (newIdIdnum.children[0].value && newIdIdnum.children[1].value) { + var id = newIdIdnum.children[0].value; + var idnum = parseInt(newIdIdnum.children[1].value); + if (!core.isset(idnum)) { + printe('不合法的idnum'); + return; + } + if (!/^[0-9a-zA-Z_]+$/.test(id)) { + printe('不合法的id,请使用字母、数字或下划线') + return; + } + editor.file.changeIdAndIdnum(id, idnum, editor_mode.info, function (err) { + if (err) { + printe(err); + throw(err) + } + printe('添加id和idnum成功,请F5刷新编辑器'); + }); + } else { + printe('请输入id和idnum'); + } + } + + newIdIdnum.children[4].onclick = function () { + editor.file.autoRegister(editor_mode.info, function (err) { + if (err) { + printe(err); + throw(err) + } + printe('该列所有剩余项全部自动注册成功,请F5刷新编辑器'); + }) + } + + var changeId = document.getElementById('changeId'); + changeId.children[1].onclick = function () { + var id = changeId.children[0].value; + if (id) { + if (!/^[0-9a-zA-Z_]+$/.test(id)) { + printe('不合法的id,请使用字母、数字或下划线') + return; + } + editor.file.changeIdAndIdnum(id, null, editor_mode.info, function (err) { + if (err) { + printe(err); + throw(err); + } + printe('修改id成功,请F5刷新编辑器'); + }); + } else { + printe('请输入要修改到的ID'); + } + } + + var selectFloor = document.getElementById('selectFloor'); + editor.game.getFloorFileList(function (floors) { + var outstr = []; + floors[0].forEach(function (floor) { + outstr.push(["\n'].join('')); + }); + selectFloor.innerHTML = outstr.join(''); + selectFloor.value = core.status.floorId; + selectFloor.onchange = function () { + editor_mode.onmode('nextChange'); + editor_mode.onmode('floor'); + editor.changeFloor(selectFloor.value); + } + }); + + var saveFloor = document.getElementById('saveFloor'); + editor_mode.saveFloor = function () { + editor_mode.onmode(''); + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('保存成功'); + }); + } + saveFloor.onclick = editor_mode.saveFloor; + + var newMap = document.getElementById('newMap'); + var newFileName = document.getElementById('newFileName'); + newMap.onclick = function () { + if (!newFileName.value) return; + if (core.floorIds.indexOf(newFileName.value)>=0) { + printe("该楼层已存在!"); + return; + } + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(newFileName.value)) { + printe("楼层名不合法!请使用字母、数字、下划线,且不能以数字开头!"); + return; + } + var width = parseInt(document.getElementById('newMapWidth').value); + var height = parseInt(document.getElementById('newMapHeight').value); + if (!core.isset(width) || !core.isset(height) || width1000) { + printe("新建地图的宽高都不得小于"+core.__SIZE__+",且宽高之积不能超过1000"); + return; + } + + editor_mode.onmode(''); + editor.file.saveNewFile(newFileName.value, function (err) { + if (err) { + printe(err); + throw(err) + } + core.floorIds.push(newFileName.value); + editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {//console.log(objs_); + if (objs_.slice(-1)[0] != null) { + printe(objs_.slice(-1)[0]); + throw(objs_.slice(-1)[0]) + } + ;printe('新建成功,请F5刷新编辑器生效'); + }); + }); + } + + var newMaps = document.getElementById('newMaps'); + var newFloors = document.getElementById('newFloors'); + newMaps.onclick = function () { + if (newFloors.style.display == 'none') newFloors.style.display = 'block'; + else newFloors.style.display = 'none'; + } + + var createNewMaps = document.getElementById('createNewMaps'); + createNewMaps.onclick = function () { + var floorIds = document.getElementById('newFloorIds').value; + if (!floorIds) return; + var from = parseInt(document.getElementById('newMapsFrom').value), + to = parseInt(document.getElementById('newMapsTo').value); + if (!core.isset(from) || !core.isset(to) || from>to || from<0 || to<0) { + printe("请输入有效的起始和终止楼层"); + return; + } + if (to-from >= 100) { + printe("一次最多创建99个楼层"); + return; + } + var floorIdList = []; + for (var i = from; i<=to; i++) { + var floorId = floorIds.replace(/\${(.*?)}/g, function (word, value) { + return eval(value); + }); + if (core.floorIds.indexOf(floorId)>=0) { + printe("要创建的楼层 "+floorId+" 已存在!"); + return; + } + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(floorId)) { + printe("楼层名 "+floorId+" 不合法!请使用字母、数字、下划线,且不能以数字开头!"); + return; + } + if (floorIdList.indexOf(floorId)>=0) { + printe("尝试重复创建楼层 "+floorId+" !"); + return; + } + floorIdList.push(floorId); + } + + var width = parseInt(document.getElementById('newMapsWidth').value); + var height = parseInt(document.getElementById('newMapsHeight').value); + if (!core.isset(width) || !core.isset(height) || width1000) { + printe("新建地图的宽高都不得小于"+core.__SIZE__+",且宽高之积不能超过1000"); + return; + } + editor_mode.onmode(''); + + editor.file.saveNewFiles(floorIdList, from, to, function (err) { + if (err) { + printe(err); + throw(err) + } + core.floorIds = core.floorIds.concat(floorIdList); + editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {//console.log(objs_); + if (objs_.slice(-1)[0] != null) { + printe(objs_.slice(-1)[0]); + throw(objs_.slice(-1)[0]) + } + ;printe('批量创建 '+floorIdList[0]+'~'+floorIdList[floorIdList.length-1]+' 成功,请F5刷新编辑器生效'); + }); + }); + } + + var changeFloorId = document.getElementById('changeFloorId'); + changeFloorId.children[1].onclick = function () { + var floorId = changeFloorId.children[0].value; + if (floorId) { + if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(floorId)) { + printe("楼层名 "+floorId+" 不合法!请使用字母、数字、下划线,且不能以数字开头!"); + return; + } + if (main.floorIds.indexOf(floorId)>=0) { + printe("楼层名 "+floorId+" 已存在!"); + return; + } + var currentFloorId = editor.currentFloorId; + editor.currentFloorId = floorId; + editor.currentFloorData.floorId = floorId; + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err); + } + core.floorIds[core.floorIds.indexOf(currentFloorId)] = floorId; + editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {//console.log(objs_); + if (objs_.slice(-1)[0] != null) { + printe(objs_.slice(-1)[0]); + throw(objs_.slice(-1)[0]) + } + alert("修改floorId成功,需要刷新编辑器生效。\n请注意,原始的楼层文件没有删除,请根据需要手动删除。"); + window.location.reload(); + }); + }); + } else { + printe('请输入要修改到的floorId'); + } + } + + var ratio = 1; + var appendPicCanvas = document.getElementById('appendPicCanvas'); + var bg = appendPicCanvas.children[0]; + var source = appendPicCanvas.children[1]; + var source_ctx=source.getContext('2d'); + var picClick = appendPicCanvas.children[2]; + var sprite = appendPicCanvas.children[3]; + var sprite_ctx=sprite.getContext('2d'); + var appendPicSelection = document.getElementById('appendPicSelection'); + + [source_ctx,sprite_ctx].forEach(function(ctx){ + ctx.mozImageSmoothingEnabled = false; + ctx.webkitImageSmoothingEnabled = false; + ctx.msImageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = false; + }) + + var selectAppend = document.getElementById('selectAppend'); + var selectAppend_str = []; + ["terrains", "animates", "enemys", "enemy48", "items", "npcs", "npc48", "autotile"].forEach(function (image) { + selectAppend_str.push(["\n'].join('')); + }); + selectAppend.innerHTML = selectAppend_str.join(''); + selectAppend.onchange = function () { + + var value = selectAppend.value; + + if (value == 'autotile') { + editor_mode.appendPic.imageName = 'autotile'; + for (var jj=0;jj<4;jj++) appendPicSelection.children[jj].style = 'display:none'; + if (editor_mode.appendPic.img) { + sprite.style.width = (sprite.width = editor_mode.appendPic.img.width) / ratio + 'px'; + sprite.style.height = (sprite.height = editor_mode.appendPic.img.height) / ratio + 'px'; + sprite_ctx.clearRect(0, 0, sprite.width, sprite.height); + sprite_ctx.drawImage(editor_mode.appendPic.img, 0, 0); + } + return; + } + + var ysize = selectAppend.value.endsWith('48') ? 48 : 32; + editor_mode.appendPic.imageName = value; + var img = core.material.images[value]; + editor_mode.appendPic.toImg = img; + var num = ~~img.width / 32; + editor_mode.appendPic.num = num; + editor_mode.appendPic.index = 0; + var selectStr = ''; + for (var ii = 0; ii < num; ii++) { + appendPicSelection.children[ii].style = 'left:0;top:0;height:' + (ysize - 6) + 'px'; + selectStr += '{"x":0,"y":0},' + } + editor_mode.appendPic.selectPos = eval('[' + selectStr + ']'); + for (var jj = num; jj < 4; jj++) { + appendPicSelection.children[jj].style = 'display:none'; + } + sprite.style.width = (sprite.width = img.width) / ratio + 'px'; + sprite.style.height = (sprite.height = img.height + ysize) / ratio + 'px'; + sprite_ctx.drawImage(img, 0, 0); + } + selectAppend.onchange(); + + var getPixel=editor.util.getPixel + var setPixel=editor.util.setPixel + + var autoAdjust = function (image, callback) { + var changed = false; + + // Step 1: 检测白底 + var tempCanvas = document.createElement('canvas').getContext('2d'); + tempCanvas.canvas.width = image.width; + tempCanvas.canvas.height = image.height; + tempCanvas.mozImageSmoothingEnabled = false; + tempCanvas.webkitImageSmoothingEnabled = false; + tempCanvas.msImageSmoothingEnabled = false; + tempCanvas.imageSmoothingEnabled = false; + tempCanvas.drawImage(image, 0, 0); + var imgData = tempCanvas.getImageData(0, 0, image.width, image.height); + var trans = 0, white = 0, black=0; + for (var i=0;iblack && white>trans*10 && confirm("看起来这张图片是以纯白为底色,是否自动调整为透明底色?")) { + for (var i=0;iwhite && black>trans*10 && confirm("看起来这张图片是以纯黑为底色,是否自动调整为透明底色?")) { + for (var i=0;i= num) editor_mode.appendPic.index = ii + 1 - num; + else editor_mode.appendPic.index++; + editor_mode.appendPic.selectPos[ii] = pos; + appendPicSelection.children[ii].style = [ + 'left:', pos.x * 32, 'px;', + 'top:', pos.y * pos.ysize, 'px;', + 'height:', pos.ysize - 6, 'px;' + ].join(''); + } + + var appendConfirm = document.getElementById('appendConfirm'); + appendConfirm.onclick = function () { + + var confirmAutotile = function () { + var image = editor_mode.appendPic.img; + if (image.width % 96 !=0 || image.height != 128) { + printe("不合法的Autotile图片!"); + return; + } + var imgData = source_ctx.getImageData(0,0,image.width,image.height); + sprite_ctx.putImageData(imgData, 0, 0); + var imgbase64 = sprite.toDataURL().split(',')[1]; + + // Step 1: List文件名 + fs.readdir('./project/images', function (err, data) { + if (err) { + printe(err); + throw(err); + } + + // Step 2: 选择Autotile文件名 + var filename; + for (var i=1;;++i) { + filename = 'autotile'+i; + if (data.indexOf(filename+".png")==-1) break; + } + + // Step 3: 写入文件 + fs.writeFile('./project/images/'+filename+".png", imgbase64, 'base64', function (err, data) { + if (err) { + printe(err); + throw(err); + } + // Step 4: 自动注册 + editor.file.registerAutotile(filename, function (err) { + if (err) { + printe(err); + throw(err); + } + printe('自动元件'+filename+'注册成功,请F5刷新编辑器'); + }) + + }) + + }) + + } + + if (selectAppend.value == 'autotile') { + confirmAutotile(); + return; + } + + var ysize = selectAppend.value.endsWith('48') ? 48 : 32; + for (var ii = 0, v; v = editor_mode.appendPic.selectPos[ii]; ii++) { + // var imgData = source_ctx.getImageData(v.x * 32, v.y * ysize, 32, ysize); + // sprite_ctx.putImageData(imgData, ii * 32, sprite.height - ysize); + // sprite_ctx.drawImage(editor_mode.appendPic.img, v.x * 32, v.y * ysize, 32, ysize, ii * 32, height, 32, ysize) + + sprite_ctx.drawImage(source_ctx.canvas, v.x*32, v.y*ysize, 32, ysize, 32*ii, sprite.height - ysize, 32, ysize); + } + var dt = sprite_ctx.getImageData(0, 0, sprite.width, sprite.height); + var imgbase64 = sprite.toDataURL().split(',')[1]; + fs.writeFile('./project/images/' + editor_mode.appendPic.imageName + '.png', imgbase64, 'base64', function (err, data) { + if (err) { + printe(err); + throw(err) + } + printe('追加素材成功,请F5刷新编辑器,或继续追加当前素材'); + sprite.style.height = (sprite.height = (sprite.height+ysize)) + "px"; + sprite_ctx.putImageData(dt, 0, 0); + }); + } + + var editModeSelect = document.getElementById('editModeSelect'); + editModeSelect.onchange = function () { + editor_mode.onmode('nextChange'); + editor_mode.onmode(editModeSelect.value); + if(editor.isMobile)editor.showdataarea(false); + } + + editor_mode.checkUnique = function (thiseval) { + if (!(thiseval instanceof Array)) return false; + var map = {}; + for (var i = 0; i0) + printe(mapEditArea.errors[value-1]) + } + return mapEditArea._error +} +mapEditArea.drawMap= function () { + // var mapArray = mapEditArea.mapArr().split(/\D+/).join(' ').trim().split(' '); + var mapArray = JSON.parse('[' + mapEditArea.mapArr() + ']'); + var sy=editor.map.length,sx=editor.map[0].length; + for (var y = 0; y < sy; y++) + for (var x = 0; x < sx; x++) { + var num = mapArray[y][x]; + if (num == 0) + editor.map[y][x] = 0; + else if (typeof(editor.indexs[num][0]) == 'undefined') { + mapEditArea.error(2); + editor.map[y][x] = undefined; + } else editor.map[y][x] = editor.ids[[editor.indexs[num][0]]]; + } + + editor.updateMap(); + +} +mapEditArea.formatArr= function () { + var formatArrStr = ''; + console.log(1) + + var si=editor.map.length,sk=editor.map[0].length; + if (mapEditArea.mapArr().split(/\D+/).join(' ').trim().split(' ').length != si*sk) return false; + var arr = mapEditArea.mapArr().replace(/\s+/g, '').split('],['); + + if (arr.length != si) return; + for (var i = 0; i < si; i++) { + var a = []; + formatArrStr += '['; + if (i == 0 || i == si-1) a = arr[i].split(/\D+/).join(' ').trim().split(' '); + else a = arr[i].split(/\D+/); + if (a.length != sk) { + formatArrStr = ''; + return; + } + + for (var k = 0; k < sk; k++) { + var num = parseInt(a[k]); + formatArrStr += Array(Math.max(4 - String(num).length, 0)).join(' ') + num + (k == sk-1 ? '' : ','); + } + formatArrStr += ']' + (i == si-1 ? '' : ',\n'); + } + return formatArrStr; +} +copyMap=document.getElementById('copyMap') +copyMap.err='' +copyMap.onclick=function(){ + tip.whichShow(0); + if (pout.value.trim() != '') { + if (mapEditArea.error()) { + copyMap.err = mapEditArea.errors[mapEditArea.error() - 1]; + tip.whichShow(5) + return; + } + try { + pout.focus(); + pout.setSelectionRange(0, pout.value.length); + document.execCommand("Copy"); + tip.whichShow(6); + } catch (e) { + copyMap.err = e; + tip.whichShow(5); + } + } else { + tip.whichShow(7); + } +} +clearMapButton=document.getElementById('clearMapButton') +clearMapButton.onclick=function () { + editor.mapInit(); + editor_mode.onmode(''); + editor.file.saveFloorFile(function (err) { + if (err) { + printe(err); + throw(err) + } + ;printf('地图清除成功'); + }); + editor.updateMap(); + clearTimeout(mapEditArea.formatTimer); + clearTimeout(tip.timer); + pout.value = ''; + mapEditArea.mapArr(''); + tip.whichShow(4); + mapEditArea.error(0); +} +deleteMap=document.getElementById('deleteMap') +deleteMap.onclick=function () { + editor_mode.onmode(''); + var index = core.floorIds.indexOf(editor.currentFloorId); + if (index>=0) { + core.floorIds.splice(index,1); + editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {//console.log(objs_); + if (objs_.slice(-1)[0] != null) { + printe(objs_.slice(-1)[0]); + throw(objs_.slice(-1)[0]) + } + ;printe('删除成功,请F5刷新编辑器生效'); + }); + } + else printe('删除成功,请F5刷新编辑器生效'); +} +printf = function (str_, type) { + selectBox.isSelected(false); + if (!type) { + tip.whichShow(11); + } else { + tip.whichShow(12); + } + setTimeout(function () { + if (!type) { + tip.msgs[11] = String(str_); + tip.whichShow(12); + } else { + tip.msgs[10] = String(str_); + tip.whichShow(11); + } + }, 1); +} +printe = function (str_) { + printf(str_, 'error') +} +tip_in_showMode = [ + '涉及图片的更改需要F5刷新浏览器来生效', + '文本域可以通过双击,在文本编辑器或事件编辑器中编辑', + '事件编辑器中的显示文本和自定义脚本的方块也可以双击', + "画出的地图要点击\"保存地图\"才会写入到文件中", +]; +tip=document.getElementById('tip') +tip._infos= {} +tip.infos=function(value){ + if(value!=null){ + var val=value + var oldval=tip._infos + + tip.isClearBlock(false); + tip.isAirwall(false); + if (typeof(val) != 'undefined') { + if (val == 0) { + tip.isClearBlock(true); + return; + } + if ('id' in val) { + if (val.idnum == 17) { + tip.isAirwall(true); + return; + } + tip.hasId = true; + } else { + tip.hasId = false; + } + tip.isAutotile = false; + if (val.images == "autotile" && tip.hasId) tip.isAutotile = true; + document.getElementById('isAirwall-else').innerHTML=(tip.hasId?`

图块编号:${ value['idnum'] }

+

图块ID:${ value['id'] }

`:` +

该图块无对应的数字或ID存在,请先前往icons.js和maps.js中进行定义!

`)+` +

图块所在素材:${ value['images'] + (tip.isAutotile ? '( '+value['id']+' )' : '') } +

+

图块索引:${ value['y'] }

` + } + + tip._infos=value + } + return tip._infos +} +tip.hasId= true +tip.isAutotile= false +tip._isSelectedBlock= false +tip.isSelectedBlock=function(value){ + if(value!=null){ + var dshow=document.getElementById('isSelectedBlock-if') + var dhide=document.getElementById('isSelectedBlock-else') + if(!value){ + var dtemp=dshow + dshow=dhide + dhide=dtemp + } + dshow.style.display='' + dhide.style.display='none' + tip._isSelectedBlock=value + } + return tip._isSelectedBlock +} +tip._isClearBlock= false +tip.isClearBlock=function(value){ + if(value!=null){ + var dshow=document.getElementById('isClearBlock-if') + var dhide=document.getElementById('isClearBlock-else') + if(!value){ + var dtemp=dshow + dshow=dhide + dhide=dtemp + } + dshow.style.display='' + dhide.style.display='none' + tip._isClearBlock=value + } + return tip._isClearBlock +} +tip._isAirwall= false +tip.isAirwall=function(value){ + if(value!=null){ + var dshow=document.getElementById('isAirwall-if') + var dhide=document.getElementById('isAirwall-else') + if(!value){ + var dtemp=dshow + dshow=dhide + dhide=dtemp + } + dshow.style.display='' + dhide.style.display='none' + tip._isAirwall=value + } + return tip._isAirwall +} +tip.geneMapSuccess= false +tip.timer= null +tip.msgs= [ //分别编号1,2,3,4,5,6,7,8,9,10;奇数警告,偶数成功 + "当前未选择任何图块,请先在右边选择要画的图块!", + "生成地图成功!可点击复制按钮复制地图数组到剪切板", + "生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!", + "地图清除成功!", + "复制失败!", + "复制成功!可直接粘贴到楼层文件的地图数组中。", + "复制失败!当前还没有数据", + "修改成功!可点击复制按钮复制地图数组到剪切板", + "选择背景图片失败!文件名格式错误或图片不存在!", + "更新背景图片成功!", + "11:警告", + "12:成功" +] +tip._mapMsg= '' +tip.mapMsg=function(value){ + if(value!=null){ + document.getElementById('whichShow-if').innerText=value + tip._mapMsg=value + } + return tip._mapMsg +} +tip._whichShow= 0 +tip.whichShow=function(value){ + if(value!=null){ + + var dshow=document.getElementById('whichShow-if') + var dhide=null + if(!value){ + var dtemp=dshow + dshow=dhide + dhide=dtemp + } + if(dshow)dshow.style.display='' + if(dhide)dhide.style.display='none' + + if(dshow)dshow.setAttribute('class',(value%2) ? 'warnText' : 'successText') + + tip.mapMsg(''); + tip.msgs[4] = "复制失败!" + editTip.err; + clearTimeout(tip.timer); + if (value) { + tip.mapMsg(tip.msgs[value - 1]); + tip.timer = setTimeout(function () { + if (!(value % 2)) + value = 0; + }, 5000); //5秒后自动清除success,warn不清除 + } + tip._whichShow=value + } + return tip._whichShow +} +selectBox=document.getElementById('selectBox') +dataSelection=document.getElementById('dataSelection') +selectBox._isSelected=false +selectBox.isSelected=function(value){ + if(value!=null){ + selectBox._isSelected=value; + tip.isSelectedBlock(value); + tip.whichShow(0); + clearTimeout(tip.timer); + dataSelection.style.display=value?'':'none' + } + return selectBox._isSelected +} + diff --git a/_server/editor_util.js b/_server/editor_util.js new file mode 100644 index 00000000..6f81a8c1 --- /dev/null +++ b/_server/editor_util.js @@ -0,0 +1,173 @@ +editor_util_wrapper = function (editor) { + + editor_util = function () { + + } + + editor_util.prototype.guid = function () { + return 'id_' + 'xxxxxxxx_xxxx_4xxx_yxxx_xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + + editor_util.prototype.HTMLescape = function (str_) { + return String(str_).split('').map(function (v) { + return '&#' + v.charCodeAt(0) + ';' + }).join(''); + } + + editor_util.prototype.getPixel = function (imgData, x, y) { + var offset = (x + y * imgData.width) * 4; + var r = imgData.data[offset + 0]; + var g = imgData.data[offset + 1]; + var b = imgData.data[offset + 2]; + var a = imgData.data[offset + 3]; + return [r, g, b, a]; + } + + editor_util.prototype.setPixel = function (imgData, x, y, rgba) { + var offset = (x + y * imgData.width) * 4; + imgData.data[offset + 0] = rgba[0]; + imgData.data[offset + 1] = rgba[1]; + imgData.data[offset + 2] = rgba[2]; + imgData.data[offset + 3] = rgba[3]; + } + + // rgbToHsl hue2rgb hslToRgb from https://github.com/carloscabo/colz.git + //-------------------------------------------- + // The MIT License (MIT) + // + // Copyright (c) 2014 Carlos Cabo + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + //-------------------------------------------- + // https://github.com/carloscabo/colz/blob/master/public/js/colz.class.js + var round = Math.round; + var rgbToHsl = function (rgba) { + var arg, r, g, b, h, s, l, d, max, min; + + arg = rgba; + + if (typeof arg[0] === 'number') { + r = arg[0]; + g = arg[1]; + b = arg[2]; + } else { + r = arg[0][0]; + g = arg[0][1]; + b = arg[0][2]; + } + + r /= 255; + g /= 255; + b /= 255; + + max = Math.max(r, g, b); + min = Math.min(r, g, b); + l = (max + min) / 2; + + if (max === min) { + h = s = 0; // achromatic + } else { + d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + + h /= 6; + } + + //CARLOS + h = round(h * 360); + s = round(s * 100); + l = round(l * 100); + + return [h, s, l]; + } + // + var hue2rgb = function (p, q, t) { + if (t < 0) { t += 1; } + if (t > 1) { t -= 1; } + if (t < 1 / 6) { return p + (q - p) * 6 * t; } + if (t < 1 / 2) { return q; } + if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } + return p; + } + var hslToRgb = function (hsl) { + var arg, r, g, b, h, s, l, q, p; + + arg = hsl; + + if (typeof arg[0] === 'number') { + h = arg[0] / 360; + s = arg[1] / 100; + l = arg[2] / 100; + } else { + h = arg[0][0] / 360; + s = arg[0][1] / 100; + l = arg[0][2] / 100; + } + + if (s === 0) { + r = g = b = l; // achromatic + } else { + + q = l < 0.5 ? l * (1 + s) : l + s - l * s; + p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + return [round(r * 255), round(g * 255), round(b * 255)]; + } + editor_util.prototype.rgbToHsl = rgbToHsl + editor_util.prototype.hue2rgb = hue2rgb + editor_util.prototype.hslToRgb = hslToRgb + + editor_util.prototype.encode64 = function (str) { + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode(parseInt(p1, 16)) + })) + } + + editor_util.prototype.decode64 = function (str) { + return decodeURIComponent(atob(str.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }).join('')) + } + + editor_util.prototype.isset = function (val) { + return val != null && !(typeof val == 'number' && isNaN(val)); + } + + editor_util.prototype.checkCallback=function(callback){ + if (!editor.util.isset(callback)) { + editor.printe('未设置callback'); + throw('未设置callback') + } + } + + editor.constructor.prototype.util = new editor_util(); +} +//editor_util_wrapper(editor); \ No newline at end of file diff --git a/_server/fs.js b/_server/fs.js index c95065d3..dddfbbec 100644 --- a/_server/fs.js +++ b/_server/fs.js @@ -56,7 +56,7 @@ callback(null, data); } }, function (e) { - console.log(e); + main.log(e); callback(e+":请检查启动服务是否处于正常运行状态。"); }, "text/plain; charset=x-user-defined"); } @@ -119,6 +119,10 @@ throw 'Type Error in fs.writeFile'; } + fs.writeMultiFiles = function (filenames, datastrs, callback) { + postsomething('name='+filenames.join(';')+'&value='+datastrs.join(';'), '/writeMultiFiles', callback); + } + fs.readdir = function (path, callback) { //callback:function(err, data) //path:支持"/"做分隔符,不以"/"结尾 diff --git a/_server/fsTest_cs.html b/_server/fsTest_cs.html index a6455ad4..9a392e4a 100644 --- a/_server/fsTest_cs.html +++ b/_server/fsTest_cs.html @@ -34,6 +34,12 @@ console.log(d); }) }, 4000); + setTimeout(function () { + fs.writeMultiFiles(['_test.txt','_test_multi.txt'], ['abc=','abe='], function (e, d) { + console.log(e); + console.log(d); + }) + }, 5000); diff --git a/_server/functions.comment.js b/_server/functions.comment.js deleted file mode 100644 index 0b2901ae..00000000 --- a/_server/functions.comment.js +++ /dev/null @@ -1,139 +0,0 @@ -functions_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = -{ - "_leaf": false, - "_type": "object", - "_data": { - "events": { - "_leaf": false, - "_type": "object", - "_data": { - "initGame": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "游戏开始前的一些初始化操作" - }, - "setInitData": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "不同难度分别设置初始属性" - }, - "win": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "游戏获胜事件" - }, - "lose": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "游戏失败事件" - }, - "afterChangeFloor": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "转换楼层结束的事件" - }, - "addPoint": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "加点事件" - }, - "afterBattle": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "战斗结束后触发的事件" - }, - "afterOpenDoor": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "开一个门后触发的事件" - }, - "afterChangeLight": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "改变亮灯之后,可以触发的事件" - }, - "afterPushBox": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "推箱子后的事件" - }, - "afterUseBomb": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "使用炸弹/圣锤后的事件" - }, - "beforeSaveData": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "即将存档前可以执行的操作" - }, - "afterLoadData": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "读档事件后,载入事件前,可以执行的操作" - } - } - }, - "enemys": { - "_leaf": false, - "_type": "object", - "_data": { - "getSpecials": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "怪物特殊属性的定义(获得怪物的特殊属性)" - }, - "getDamageInfo": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "获得战斗伤害信息(实际伤害计算函数)" - }, - "updateEnemys": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "更新怪物数据,可以在这里对怪物属性和数据进行动态更新" - } - } - }, - "ui": { - "_leaf": false, - "_type": "object", - "_data": { - "drawAbout": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "绘制“关于”界面" - } - } - }, - "plugins": { - "_leaf": false, - "_type": "object", - "_data": { - "plugin": { - "_leaf": true, - "_type": "textarea", - "_lint": true, - "_data": "自定义插件编写" - } - } - } - } -} \ No newline at end of file diff --git a/_server/refactoring.md b/_server/refactoring.md new file mode 100644 index 00000000..8ea8c365 --- /dev/null +++ b/_server/refactoring.md @@ -0,0 +1,125 @@ +# 重构 + +总体思路 ++ 按功能拆分文件 ++ 左侧页面模块化, 方便添加 ++ 不同的模式的文件操作尽可能模块化 + +目前主要在重构editor_file, 思路是editor.file负责把editor.game内的游戏数据格式化成字符串以及写入到文件, 由editor.game来修改数据 ++ editor.file维护一些标记, 描述哪些数据需要格式化并写入, 在save时写入文件(自动保存的话就是每次修改数据都触发save) ++ editor.game修改数据, 并修改editor.file中的标记 ++ 此思路下editor.file的大部分内容会挪到editor.game, editor.game和editor.table可能会再进一步合并拆分 + +editor_file之后是更改editor.map的储存方式, 现有的存对象的模式要在对象和数字间来回转换, 非常繁琐和奇怪 + +再之后是把editor_unsorted_*.js整理清晰 + +## 文件结构 + ++ [ ] editor_blockly 图块化事件编辑器 ++ [ ] editor_multi 多行文本编辑器 ++ [x] editor_table 处理表格的生成, 及其响应的事件, 从原editor\_mode中分离 ++ [ ] editor_file 调用fs.js编辑文件, 把原editor\_file模块化, 并且只负责文件写入 ++ [ ] editor_game 处理游戏数据, 导入为editor的数据, 编辑数据, 从原editor和editor_file中抽离. **只有此文件允许`\s(main|core)`形式的调用**(以及其初始化`editor_game_wrapper(editor, main, core);`) ++ [x] editor_util 生成guid/处理颜色 等函数, 从editor分离 ++ [ ] editor 执行初始化流程加组合各组件 ++ [ ] 原editor_mode 移除 ++ [x] 原vm 移除 ++ [x] \*comment.js 表格注释与结构, 移至table/\*comment.js + +## 对象结构 + +``` +editor: { + __proto__: { + fs + util + file + table + multi + blockly + game + } + config: 编辑器配置 + mode: 当前的模式(左侧的选择) + map: 当前编辑层的地图 + isMobile: 编辑器是否是手机端 + currentFloorData: 当前编辑的楼层数据 + ... +} +``` + +--- + +## 某些注意到的点&准备修改的内容 + ++ 插入公共事件的参数的转义处理, .g4中添加ObjectString, 要求其中的值可以JSON.parse, 生成的code中也是作为对象而不是字符串出现 + ++ 修改editor.multi中的转义处理, 目前双击某些方块使用文本编辑的处理, 一部分在editor.blockly, 一部分在editor.multi, 比较混乱 + ++ 地图的编辑与其他(如全塔属性和楼层属性), 现在的文件操作的模式是完全不同的 + 楼层文件的储存与其他不同 + ++ [x] editor.file在修改时不再返回obj和commentobj,只在查询时返回 + ++ editor.file中的各个条目, 非常相似, 但是细节的不同处理非常麻烦. 是类似的代码复制后修改一部分, 尝试模块化(或者重写) + ++ functions和plugins的借助JSON.stringify的replacer特殊处理, 与其他项的处理完全不同, 改成用统一的方法处理(为了统一,全部使用这种不直观的replacer的处理) + ++ 怪物/物品/地图选点事件的处理, field中怪物id等明显与其他节地位不等, 处理起来很繁琐 + ++ 目前editor.map中储存的是info\, 准备改为和core一致只储存数字 + ++ editor.widthX特别不直观 + +## 功能改进 + ++ [ ] 大地图 + 在切换时, 每次都回到最左上->每个楼层记录一个位置 + 四个箭头支持长按 + ? 滚动条 + ++ [ ] ? 表格折叠 + 变为四栏, 可以折叠展开 + ++ [x] blockly对于无法识别的图块原样返回 + ++ [ ] ? 简洁的事件方块注册 + `editor.registerEvent('log',[['test','Int','测试',0],['floorId','Idstring','楼层','MT0']])` + ++ [ ] 一个显示所有快捷键的文本 + ++ [ ] 更多快捷键 + 【全塔属性】、【楼层属性】等常用的编辑栏切换 + ++ [ ] ? 地图编辑优化 + 常用的地图编辑快捷键/命令:复制ctrl+c、粘贴ctrl+v、(复制可绑定为现在的“选中xx位置事件” 粘贴为复制xx事件到此处),撤回ctrl+z、取消撤回ctrl+y + 可以按住拖动图块与事件。 + ++ [ ] ? 自由建立快捷键到命令的注册表。 + ++ [ ] 画地图也自动保存 + ++ [x] 修改系统的触发器(下拉菜单增加新项) + 在编辑器修改`comment.js`:现场发readFile请求读文件,然后开脚本编辑器进行编辑 + ++ [ ] ? 删除注册项/修改图块ID + ++ [ ] ? 怪物和道具也能像其他类型那样查看“图块信息”(而不只是具体的怪物属性) + ++ [ ] 素材区自动换列 + 怪物或道具太多时, 按照每100个进行拆分新开列来显示 + ++ [ ] 多帧素材只显示第一帧 + ++ [ ] `显示文章`以及`选项`等方块, 把`标题`和`图像`从字符串提取出填回相应的空 + ++ [ ] blockly中某些需要选点的填空, 增加按钮, 点击后从缩略图中点击位置 + +## 左侧页面模式 + +标题? 保存按钮? 添加按钮? 删除按钮? + +自定义内容? + +表格? diff --git a/_server/table/comment.js b/_server/table/comment.js new file mode 100644 index 00000000..ef03953d --- /dev/null +++ b/_server/table/comment.js @@ -0,0 +1,496 @@ +/* + * 表格配置项。 + * 在这里可以对表格中的各项显示进行配置,包括表格项、提示内容等内容。具体写法照葫芦画瓢即可。 + * 本配置项包括:道具、怪物、图块属性、楼层属性等内容。 + */ + +var comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { + "_type": "object", + "_data": { + // --------------------------- 【道具】相关的表格配置 --------------------------- // + "items": { + "_type": "object", + "_data": { + "items": { + "_type": "object", + "_data": { + "cls": { + "_leaf": true, + "_type": "select", + "_select": { + "values": [ + "keys", + "items", + "constants", + "tools", + "equips" + ] + }, + "_data": "只能取keys(钥匙) items(宝石、血瓶) constants(永久物品) tools(消耗道具) equips(装备)" + }, + "name": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "名称" + }, + "text": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "道具在道具栏中显示的描述" + }, + "equip": { + "_leaf": true, + "_type": "textarea", + "_data": "装备属性设置,仅对cls为equips有效。\n如果此项不为null,需要是一个对象,里面可含\"type\",\"atk\",\"def\",\"mdef\",\"animate\"五项,分别对应装备部位、攻防魔防和动画。\n具体详见文档(元件说明-装备)和已有的几个装备的写法。" + }, + "hideInReplay": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否回放时绘制道具栏。\n如果此项为true,则在回放录像时使用本道具将不会绘制道具栏页面,而是直接使用。\n此项建议在会频繁连续多次使用的道具开启(如开启技能,或者《镜子》那样的镜像切换等等)" + } + } + }, + "itemEffect": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "即捡即用类物品的效果,仅对cls为items有效。" + }, + "itemEffectTip": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "即捡即用类物品在获得时提示的文字,仅对cls为items有效。" + }, + "useItemEffect": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "道具效果,仅对cls为tools或constants有效。" + }, + "canUseItemEffect": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "当前能否使用该道具,仅对cls为tools或constants有效。" + }, + "equipCondition": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "能装备某个装备的条件,仅对cls为equips有效。\n与canUseItemEffect不同,这里null代表可以装备。" + } + } + }, + "items_template": { 'cls': 'items', 'name': '新物品' }, + + + // --------------------------- 【怪物】相关的表格配置 --------------------------- // + "enemys": { + "_type": "object", + "_data": { + "name": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "名称" + }, + "displayIdInBook": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "在怪物手册中映射到的怪物ID。如果此项不为null,则在怪物手册中,将用目标ID来替换该怪物原本的ID。\n此项应被运用在同一个怪物的多朝向上。\n例如,如果想定义同一个怪物的向下和向左的行走图,则需要建立两个属性完全相同的怪物。\n但是这样会导致在怪物手册中同时存在向下和向左的两种怪物的显示。\n可以将朝向左的怪物的displayIdInBook项指定为朝向下的怪物ID,这样在怪物手册中则会归一化,只显示一个。" + }, + "hp": { + "_leaf": true, + "_type": "textarea", + "_data": "生命值" + }, + "atk": { + "_leaf": true, + "_type": "textarea", + "_data": "攻击力" + }, + "def": { + "_leaf": true, + "_type": "textarea", + "_data": "防御力" + }, + "money": { + "_leaf": true, + "_type": "textarea", + "_data": "金币" + }, + "experience": { + "_leaf": true, + "_type": "textarea", + "_data": "经验" + }, + "point": { + "_leaf": true, + "_type": "textarea", + "_data": "加点" + }, + "special": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null || thiseval instanceof Array || (thiseval==~~thiseval && thiseval>=0)", + "_data": "特殊属性\n\n0:无,1:先攻,2:魔攻,3:坚固,4:2连击,\n5:3连击,6:n连击,7:破甲,8:反击,9:净化,\n10:模仿,11:吸血,12:中毒,13:衰弱,14:诅咒,\n15:领域,16:夹击,17:仇恨,18:阻击,19:自爆,\n20:无敌,21:退化,22:固伤,23:重生,24:激光,25:光环\n\n多个属性例如用[1,4,11]表示先攻2连击吸血" + }, + "value": { + "_leaf": true, + "_type": "textarea", + "_data": "特殊属性的数值\n如:领域/阻激/激光怪的伤害值;吸血怪的吸血比例;光环怪增加生命的比例" + }, + "zoneSquare": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "领域怪是否九宫格伤害" + }, + "range": { + "_leaf": true, + "_type": "textarea", + "_range": "(thiseval==~~thiseval && thiseval>0)||thiseval==null", + "_data": "领域伤害的范围;不加默认为1" + }, + "notBomb": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该怪物不可被炸" + }, + "n": { + "_leaf": true, + "_type": "textarea", + "_range": "(thiseval==~~thiseval && thiseval>0)||thiseval==null", + "_data": "多连击的连击数" + }, + "add": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "吸血后是否加到自身;光环是否叠加" + }, + "atkValue": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==~~thiseval||thiseval==null", + "_data": "退化时勇士下降的攻击力点数;光环怪增加攻击的比例" + }, + "defValue": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==~~thiseval||thiseval==null", + "_data": "退化时勇士下降的防御力点数;光环怪增加防御的比例" + }, + "damage": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==~~thiseval||thiseval==null", + "_data": "战前扣血的点数" + } + } + }, + "enemys_template": { 'name': '新敌人', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'point': 0, 'special': 0 }, + + + // --------------------------- 【图块属性】相关的表格配置 --------------------------- // + "maps": { + "_type": "object", + "_data": { + "id": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "图块ID" + }, + "idnum": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "图块数字" + }, + "cls": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "图块类别" + }, + "trigger": { + "_leaf": true, + "_type": "select", + "_select": { + "values": [ + "null", + "openDoor", + "passNet", + "changeLight", + "pushBox", + "custom" + ] + }, + "_data": "该图块的默认触发器" + }, + "noPass": { + "_leaf": true, + "_type": "select", + "_select": { + "values": [ + "null", + "true", + "false" + ] + }, + "_data": "该图块是否不可通行;true代表不可通行,false代表可通行,null代表使用系统缺省值" + }, + "canBreak": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该图块是否可被破墙或地震" + }, + "cannotOut": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null||(thiseval instanceof Array)", + "_data": "该图块的不可出方向\n可以在这里定义在该图块时不能前往哪个方向,可以达到悬崖之类的效果\n例如 [\"up\", \"left\"] 代表在该图块时不能往上和左走\n此值对背景层、事件层、前景层上的图块均有效" + }, + "cannotIn": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null||(thiseval instanceof Array)", + "_data": "该图块的不可入方向\n可以在这里定义不能朝哪个方向进入该图块,可以达到悬崖之类的效果\n例如 [\"down\"] 代表不能从该图块的上方点朝向下进入此图块\n此值对背景层、事件层、前景层上的图块均有效" + }, + "animate": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==~~thiseval||thiseval==null", + "_data": "该图块的全局动画帧数。\n如果此项为null,则对于除了npc48外,使用素材默认帧数;npc48默认是1帧(即静止)。" + }, + "faceIds": { + "_leaf": true, + "_type": "textarea", + "_data": "行走图朝向,仅对NPC有效。可以在这里定义同一个NPC的多个朝向行走图。\n比如 {\"up\":\"N333\",\"down\":\"N334\",\"left\":\"N335\",\"right\":\"N336\"} 就将该素材的上下左右朝向分别绑定到N333,N334,N335和N336四个图块。\n在勇士撞上NPC时,或NPC在移动时,会自动选择最合适的朝向图块(如果存在定义)来进行绘制。" + } + } + }, + + + // --------------------------- 【楼层属性】相关的表格配置 --------------------------- // + "floors": { + "_type": "object", + "_data": { + "floor": { + "_type": "object", + "_data": { + "floorId": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "文件名和floorId需要保持完全一致 \n楼层唯一标识符仅能由字母、数字、下划线组成,且不能由数字开头 \n推荐用法:第20层就用MT20,第38层就用MT38,地下6层就用MT_6(用下划线代替负号),隐藏3层用MT3h(h表示隐藏),等等 \n楼层唯一标识符,需要和名字完全一致 \n这里不能更改floorId,请通过另存为来实现" + }, + "title": { + "_leaf": true, + "_type": "textarea", + "_data": "楼层中文名,将在切换楼层和浏览地图时显示" + }, + "name": { + "_leaf": true, + "_type": "textarea", + "_data": "显示在状态栏中的层数" + }, + "width": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "地图x方向大小,这里不能更改,仅能在新建地图时设置,null视为13" + }, + "height": { + "_leaf": true, + "_type": "textarea", + "_range": "false", + "_data": "地图y方向大小,这里不能更改,仅能在新建地图时设置,null视为13" + }, + "canFlyTo": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该楼能否被楼传器飞到(不能的话在该楼也不允许使用楼传器)" + }, + "canUseQuickShop": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该层是否允许使用快捷商店" + }, + "cannotViewMap": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该层是否不允许被浏览地图看到;如果勾上则浏览地图会跳过该层" + }, + "cannotMoveDirectly": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "该层是否不允许瞬间移动;如果勾上则不可在此层进行瞬移" + }, + "firstArrive": { + "_leaf": true, + "_type": "event", + "_event": "firstArrive", + "_data": "第一次到该楼层触发的事件,可以双击进入事件编辑器。" + }, + "eachArrive": { + "_leaf": true, + "_type": "event", + "_event": "eachArrive", + "_data": "每次到该楼层触发的事件,可以双击进入事件编辑器;该事件会在firstArrive执行后再执行。" + }, + "parallelDo": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_lint": true, + "_data": "在该层楼时执行的并行事件处理。\n可以在这里写上任意需要自动执行的脚本,比如打怪自动开门等。\n详见文档-事件-并行事件处理。" + }, + "upFloor": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null||((thiseval instanceof Array) && thiseval.length==2)", + "_data": "该层上楼点,如[2,3]。\n如果此项不为null,则楼层转换时的stair:upFloor,以及楼传器的落点会被替换成该点而不是该层的上楼梯。" + }, + "downFloor": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null||((thiseval instanceof Array) && thiseval.length==2)", + "_data": "该层下楼点,如[2,3]。\n如果此项不为null,则楼层转换时的stair:downFloor,以及楼传器的落点会被替换成该点而不是该层的下楼梯。" + }, + "defaultGround": { + "_leaf": true, + "_type": "select", + "_select": { + "values": Object.keys(editor.core.icons.icons.terrains) + }, + "_data": "默认地面的图块ID,此项修改后需要刷新才能看到效果。" + }, + "images": { + "_leaf": true, + "_type": "textarea", + "_data": "背景/前景图;你可以选择若干张图片来作为背景/前景素材。详细用法请参见文档“自定义素材”中的说明。" + }, + "color": { + "_leaf": true, + "_type": "textarea", + "_data": "该层的默认画面色调。本项可不写(代表无色调),如果写需要是一个RGBA数组如[255,0,0,0.3]" + }, + "weather": { + "_leaf": true, + "_type": "textarea", + "_data": "该层的默认天气。本项可忽略表示晴天,如果写则第一项为\"rain\",\"snow\"或\"fog\"代表雨雪雾,第二项为1-10之间的数代表强度。\n如[\"rain\", 8]代表8级雨天。" + }, + "bgm": { + "_leaf": true, + "_type": "select", + "_select": { + "values": [null].concat(Object.keys(editor.core.material.bgms)) + }, + "_data": "到达该层后默认播放的BGM。本项可忽略,或者为一个定义过的背景音乐如\"bgm.mp3\"。" + }, + "item_ratio": { + "_leaf": true, + "_type": "textarea", + "_range": "(thiseval==~~thiseval && thiseval>=0)||thiseval==null", + "_data": "每一层的宝石/血瓶效果,即获得宝石和血瓶时框内\"ratio\"的值。" + }, + "underGround": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否是地下层;如果该项为true则同层传送将传送至上楼梯" + } + } + }, + "loc": { + "_type": "object", + "_data": { + "events": { + "_leaf": true, + "_type": "event", + "_event": "event", + "_data": "该点的可能事件列表,可以双击进入事件编辑器。" + }, + "changeFloor": { + "_leaf": true, + "_type": "event", + "_event": "changeFloor", + "_data": "该点楼层转换事件;该事件不能和上面的events同时出现,否则会被覆盖" + }, + "afterBattle": { + "_leaf": true, + "_type": "event", + "_event": "afterBattle", + "_data": "该点战斗后可能触发的事件列表,可以双击进入事件编辑器。" + }, + "afterGetItem": { + "_leaf": true, + "_type": "event", + "_event": "afterGetItem", + "_data": "该点获得道具后可能触发的事件列表,可以双击进入事件编辑器。" + }, + "afterOpenDoor": { + "_leaf": true, + "_type": "event", + "_event": "afterOpenDoor", + "_data": "该点开完门后可能触发的事件列表,可以双击进入事件编辑器。" + }, + "cannotMove": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null||(thiseval instanceof Array)", + "_data": "该点不可通行的方向 \n 可以在这里定义该点不能前往哪个方向,可以达到悬崖之类的效果\n例如 [\"up\", \"left\"] 代表该点不能往上和左走" + } + } + } + } + }, + + "floors_template": { + "floorId": "to be covered", + "title": "new floor", + "name": "new floor", + "width": 13, + "height": 13, + "canFlyTo": true, + "canUseQuickShop": true, + "cannotViewMap": false, + "cannotMoveDirectly": false, + "images": [], + "item_ratio": 1, + "defaultGround": "ground", + "bgm": null, + "upFloor": null, + "downFloor": null, + "color": null, + "weather": null, + "firstArrive": [], + "eachArrive": [], + "parallelDo": "", + "events": {}, + "changeFloor": {}, + "afterBattle": {}, + "afterGetItem": {}, + "afterOpenDoor": {}, + "cannotMove": {} + } + } +} \ No newline at end of file diff --git a/_server/table/data.comment.js b/_server/table/data.comment.js new file mode 100644 index 00000000..2a2addfd --- /dev/null +++ b/_server/table/data.comment.js @@ -0,0 +1,697 @@ +/* + * 表格配置项。 + * 在这里可以对表格中的各项显示进行配置,包括表格项、提示内容等内容。具体写法照葫芦画瓢即可。 + * 本配置项包括:全塔属性的配置项。 + */ + +var data_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { + "_type": "object", + "_data": { + "main": { + "_type": "object", + "_data": { + "floorIds": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkFloorIds(thiseval)", + "_data": "在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器、浏览地图和上/下楼器的顺序" + }, + "images": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkUnique(thiseval)", + "_data": "在此存放所有可能使用的图片(tilesets除外) \n图片可以被作为背景图(的一部分),也可以直接用自定义事件进行显示。 \n 图片名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 \n 建议对于较大的图片,在网上使用在线的“图片压缩工具(http://compresspng.com/zh/)”来进行压缩,以节省流量 \n 依次向后添加" + }, + "tilesets": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkUnique(thiseval)", + "_data": "在此存放额外素材的图片名, \n可以自定导入任意张素材图片,无需PS,无需注册,即可直接在游戏中使用 \n 形式如[\"1.png\", \"2.png\"] ,将需要的素材图片放在images目录下 \n 素材的宽高必须都是32的倍数,且图片上的总图块数不超过1000(即最多有1000个32*32的图块在该图片上)" + }, + "animates": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkUnique(thiseval)", + "_data": "在此存放所有可能使用的动画,必须是animate格式,在这里不写后缀名 \n动画必须放在animates目录下;文件名不能使用中文,不能带空格或特殊字符 \n \"jianji\", \"thunder\" 根据需求自行添加" + }, + "bgms": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkUnique(thiseval)", + "_data": "在此存放所有的bgm,和文件名一致。 \n音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好" + }, + "sounds": { + "_leaf": true, + "_type": "textarea", + "_range": "editor.mode.checkUnique(thiseval)", + "_data": "在此存放所有的SE,和文件名一致 \n音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好" + }, + "nameMap": { + "_leaf": true, + "_type": "textarea", + "_data": "文件名映射,目前仅对images, animates, bgms, sounds有效。\n例如定义 {\"精灵石.mp3\":\"jinglingshi.mp3\"} 就可以使用\ncore.playBgm(\"精灵石.mp3\") 或对应的事件来播放该bgm。" + }, + "startBackground": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "标题界面的背景,建议使用jpg格式以压缩背景图空间" + }, + "startLogoStyle": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "标题样式:可以改变颜色,也可以写\"display: none\"来隐藏标题" + }, + "levelChoose": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Array && thiseval.length>=1 && thiseval[0] instanceof Array && thiseval[0].length==2", + "_data": "难度选择:每个数组的第一个是其在标题界面显示的难度,第二个是在游戏内部传输的字符串,会显示在状态栏,修改此处后需要在project/functions中作相应更改。\n如果需直接开始游戏将下面的startDirectly开关打开即可。" + }, + "equipName": { + "_leaf": true, + "_type": "textarea", + "_range": "(thiseval instanceof Array && thiseval.length<=6)||thiseval==null", + "_data": "装备位名称,为不超过6个的数组,此项的顺序与equiptype数值关联;例如可写[\"武器\",\"防具\",\"首饰\"]等等。" + }, + "startBgm": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "在标题界面应该播放的bgm内容" + }, + "statusLeftBackground": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "横屏时左侧状态栏的背景样式,可以定义背景图、平铺方式等。\n具体请网上搜索\"css background\"了解写法。\n如果弄一张图片作为背景图,推荐写法:\n\"url(project/images/XXX.png) 0 0/100% 100% no-repeat\"\n图片最好进行一些压缩等操作节省流量。" + }, + "statusTopBackground": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "竖屏时上方状态栏的背景样式,可以定义背景图、平铺方式等。\n具体请网上搜索\"css background\"了解写法。\n如果弄一张图片作为背景图,推荐写法:\n\"url(project/images/XXX.png) 0 0/100% 100% no-repeat\"\n图片最好进行一些压缩等操作节省流量。" + }, + "toolsBackground": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "竖屏时下方道具栏的背景样式,可以定义背景图、平铺方式等。\n具体请网上搜索\"css background\"了解写法。\n如果弄一张图片作为背景图,推荐写法:\n\"url(project/images/XXX.png) 0 0/100% 100% no-repeat\"\n图片最好进行一些压缩等操作节省流量。" + }, + "borderColor": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "边框颜色,包括游戏边界的边框和对话框边框等。" + }, + "statusBarColor": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "状态栏的文字颜色,默认是白色" + }, + "hardLabelColor": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "难度显示的颜色,默认是红色" + }, + "floorChangingBackground": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "楼层转换界面的背景样式;可以使用纯色(默认值black),也可以使用图片(参见状态栏的图片写法)" + }, + "floorChangingTextColor": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "楼层转换界面的文字颜色,默认是白色" + }, + "font": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "游戏中使用的字体,默认是Verdana" + } + } + }, + "firstData": { + "_type": "object", + "_data": { + "title": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "游戏名,将显示在标题页面以及切换楼层的界面中" + }, + "name": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_range": "/^[a-zA-Z0-9_]{1,30}$/.test(thiseval)", + "_data": "游戏的唯一英文标识符。由英文、数字、下划线组成,不能超过30个字符。\n此项必须修改,其将直接影响到存档的定位!" + }, + "version": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "当前游戏版本;版本不一致的存档不能通用。" + }, + "floorId": { + "_leaf": true, + "_type": "select", + "_select": { + "values": data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.floorIds + }, + "_range": "data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d.main.floorIds.indexOf(thiseval)!==-1", + "_data": "初始楼层的ID" + }, + "hero": { + "_type": "object", + "_data": { + "name": { + "_leaf": true, + "_type": "textarea", + "_string": true, + "_data": "勇士名;可以改成喜欢的" + }, + "lv": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==~~thiseval &&thiseval>0", + "_data": "初始等级,该项必须为正整数" + }, + "hpmax": { + "_leaf": true, + "_type": "textarea", + "_data": "初始生命上限,只有在enableHPMax开启时才有效" + }, + "hp": { + "_leaf": true, + "_type": "textarea", + "_data": "初始生命值" + }, + "manamax": { + "_leaf": true, + "_type": "textarea", + "_data": "魔力上限;此项非负才会生效(null或小于0都不会生效)" + }, + "mana": { + "_leaf": true, + "_type": "textarea", + "_data": "初始魔力值,只在enableMana开启时才有效" + }, + "atk": { + "_leaf": true, + "_type": "textarea", + "_data": "初始攻击" + }, + "def": { + "_leaf": true, + "_type": "textarea", + "_data": "初始防御" + }, + "mdef": { + "_leaf": true, + "_type": "textarea", + "_data": "初始魔防" + }, + "money": { + "_leaf": true, + "_type": "textarea", + "_data": "初始金币" + }, + "experience": { + "_leaf": true, + "_type": "textarea", + "_data": "初始经验" + }, + "equipment": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Array", + "_data": "初始装上的装备,此处建议请直接留空数组" + }, + "items": { + "_type": "object", + "_data": { + "keys": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Object && !(thiseval instanceof Array)", + "_data": "初始三种钥匙个数" + }, + "constants": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Object && !(thiseval instanceof Array)", + "_data": "初始永久道具个数,例如初始送手册可以写 {\"book\": 1}" + }, + "tools": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Object && !(thiseval instanceof Array)", + "_data": "初始消耗道具个数,例如初始有两破可以写 {\"pickaxe\": 2}" + }, + "equips": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Object && !(thiseval instanceof Array)", + "_data": "初始装备个数,例如初始送铁剑可以写 {\"sword1\": 1}" + } + } + }, + "loc": { + "_type": "object", + "_data": { + "direction": { + "_leaf": true, + "_type": "select", + "_data": "勇士初始方向", + "_select": { + "values": [ + "up", + "down", + "left", + "right" + ] + }, + }, + "x": { + "_leaf": true, + "_type": "textarea", + "_data": "勇士初始x坐标" + }, + "y": { + "_leaf": true, + "_type": "textarea", + "_data": "勇士初始y坐标" + } + } + }, + "flags": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval instanceof Object && !(thiseval instanceof Array)", + "_data": "游戏过程中的变量或flags" + }, + "steps": { + "_leaf": true, + "_type": "textarea", + "_data": "行走步数统计" + } + } + }, + "startCanvas": { + "_leaf": true, + "_type": "event", + "_event": "firstArrive", + "_range": "thiseval==null || thiseval instanceof Array", + "_data": "标题界面事件化,可以使用事件流的形式来绘制开始界面等。\n需要开启startUsingCanvas这个开关。\n详见文档-个性化-标题界面事件化。" + }, + "startText": { + "_leaf": true, + "_type": "event", + "_event": "firstArrive", + "_range": "thiseval==null || thiseval instanceof Array", + "_data": "游戏开始前剧情,可以执行任意自定义事件。\n双击进入事件编辑器。\n如果无剧情直接留一个空数组即可。" + }, + "shops": { + "_leaf": true, + "_type": "event", + "_event": "shop", + "_range": "thiseval instanceof Array", + "_data": "全局商店,是一个数组,可以双击进入事件编辑器。" + }, + "levelUp": { + "_leaf": true, + "_type": "event", + "_event": "level", + "_range": "thiseval==null || thiseval instanceof Array", + "_data": "经验升级所需要的数值,是一个数组,可以双击进行编辑。 \n 第一项为初始等级,仅title生效 \n 每一个里面可以含有三个参数 need, title, action \n need为所需要的经验数值,可以是个表达式。请确保need依次递增 \n title为该等级的名称,也可以省略代表使用系统默认值;本项将显示在状态栏中 \n action为本次升级所执行的事件,可由若干项组成" + } + } + }, + "values": { + "_type": "object", + "_data": { + "lavaDamage": { + "_leaf": true, + "_type": "textarea", + "_data": "经过血网受到的伤害" + }, + "poisonDamage": { + "_leaf": true, + "_type": "textarea", + "_data": "中毒后每步受到的伤害" + }, + "weakValue": { + "_leaf": true, + "_type": "textarea", + "_data": "衰弱状态下攻防减少的数值\n如果此项不小于1,则作为实际下降的数值(比如10就是攻防各下降10)\n如果在0到1之间则为下降的比例(比如0.3就是下降30%的攻防)" + }, + "redJewel": { + "_leaf": true, + "_type": "textarea", + "_data": "红宝石加攻击的数值" + }, + "blueJewel": { + "_leaf": true, + "_type": "textarea", + "_data": "蓝宝石加防御的数值" + }, + "greenJewel": { + "_leaf": true, + "_type": "textarea", + "_data": "绿宝石加魔防的数值" + }, + "redPotion": { + "_leaf": true, + "_type": "textarea", + "_data": "红血瓶加血数值" + }, + "bluePotion": { + "_leaf": true, + "_type": "textarea", + "_data": "蓝血瓶加血数值" + }, + "yellowPotion": { + "_leaf": true, + "_type": "textarea", + "_data": "黄血瓶加血数值" + }, + "greenPotion": { + "_leaf": true, + "_type": "textarea", + "_data": "绿血瓶加血数值" + }, + "breakArmor": { + "_leaf": true, + "_type": "textarea", + "_data": "破甲的比例(战斗前,怪物附加角色防御的x倍作为伤害)" + }, + "counterAttack": { + "_leaf": true, + "_type": "textarea", + "_data": "反击的比例(战斗时,怪物每回合附加角色攻击的x倍作为伤害,无视角色防御)" + }, + "purify": { + "_leaf": true, + "_type": "textarea", + "_data": "净化的比例(战斗前,怪物附加勇士魔防的x倍作为伤害)" + }, + "hatred": { + "_leaf": true, + "_type": "textarea", + "_data": "仇恨属性中,每杀死一个怪物获得的仇恨值" + }, + "moveSpeed": { + "_leaf": true, + "_type": "textarea", + "_data": "行走速度,即勇士每走一格的时间,一般100比较合适" + }, + "animateSpeed": { + "_leaf": true, + "_type": "textarea", + "_data": "全局动画时间,即怪物振动频率,一般300比较合适" + }, + "floorChangeTime": { + "_leaf": true, + "_type": "textarea", + "_data": "默认楼层切换时间" + } + } + }, + "flags": { + "_type": "object", + "_data": { + "enableFloor": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示当前楼层" + }, + "enableName": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示勇士名字" + }, + "enableLv": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示当前等级" + }, + "enableHPMax": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否是否启用生命上限" + }, + "enableMana": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否开启魔力值" + }, + "enableMDef": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏及战斗界面显示魔防(护盾)" + }, + "enableMoney": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏、怪物手册及战斗界面显示金币" + }, + "enableExperience": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏、怪物手册及战斗界面显示经验" + }, + "enableLevelUp": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否允许等级提升(进阶);如果上面enableExperience为false,则此项恒视为false" + }, + "levelUpLeftMode": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "进阶使用扣除模式,即在状态栏显示距离下个等级所需要的经验值;只有enableExperience和enableLevelUp均开启时才有效。" + }, + "enableKeys": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示三色钥匙数量" + }, + "enablePZF": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示破炸飞数量" + }, + "enableDebuff": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在状态栏显示毒衰咒" + }, + "enableSkill": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否启用技能栏" + }, + "flyNearStair": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否需要在楼梯边使用传送器" + }, + "pickaxeFourDirections": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "使用破墙镐是否四个方向都破坏;如果false则只破坏面前的墙壁" + }, + "bombFourDirections": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "使用炸弹是否四个方向都会炸;如果false则只炸面前的怪物(即和圣锤等价)" + }, + "snowFourDirections": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "使用冰冻徽章是否四个方向都会消除熔岩;如果false则只消除面前的熔岩" + }, + "bigKeyIsBox": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "如果此项为true,则视为钥匙盒,红黄蓝钥匙+1;若为false,则视为大黄门钥匙" + }, + "steelDoorWithoutKey": { + "_left": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "铁门是否不需要钥匙开启。如果此项为true,则无需钥匙也可以开铁门。" + }, + "equipment": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "剑和盾是否作为装备。如果此项为true,则作为装备,需要在装备栏使用,否则将直接加属性。" + }, + "equipboxButton": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "状态栏的装备按钮。若此项为true则将状态栏中的楼层转换器按钮换为装备栏按钮" + }, + "iconInEquipbox": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "在装备栏中的属性变化,是否绘制图标;如果此项开启,则会绘制图标而不是文字" + }, + "enableAddPoint": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否支持加点" + }, + "enableNegativeDamage": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否支持负伤害(回血)" + }, + "hatredDecrease": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在和仇恨怪战斗后减一半的仇恨值,此项为false则和仇恨怪不会扣减仇恨值。" + }, + "betweenAttackCeil": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "夹击上整还是下整。如果此项为true则夹击伤害值向上取整,为false则为向下取整" + }, + "betweenAttackMax": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "夹击伤害是否不超过怪物伤害值。" + }, + "useLoop": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否循环计算临界;如果此项为true则使用循环法(而不是回合数计算法)来算临界\n从V2.5.3开始,对于大数据的循环法将改为使用二分法进行计算" + }, + "startUsingCanvas": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否开始菜单canvas化;如果此项为true,则将使用canvas来绘制开始菜单" + }, + "startDirectly": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面" + }, + "statusCanvas": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否状态栏canvas化,即手动自定义绘制状态栏。\n如果此项开启,则可在脚本编辑的drawStatusBar中自定义绘制菜单栏。" + }, + "statusCanvasRowsOnMobile": { + "_leaf": true, + "_type": "textarea", + "_range": "thiseval==null || (thiseval>0 && thiseval<=4)", + "_data": "竖屏模式下,顶端状态栏canvas化后的行数。\n此项将决定竖屏的状态栏高度,如果设置则不小于1且不大于4。\n仅在statusCanvas开启时才有效" + }, + "displayEnemyDamage": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否地图怪物显伤;用户可以手动在菜单栏中开关" + }, + "displayCritical": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否地图显示临界;用户可以手动在菜单栏中开关" + }, + "displayExtraDamage": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否地图高级显伤(领域、夹击等);用户可以手动在菜单栏中开关" + }, + "enableGentleClick": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否允许轻触(获得面前物品)" + }, + "potionWhileRouting": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "寻路算法是否经过血瓶;如果该项为false,则寻路算法会自动尽量绕过血瓶" + }, + "ignoreChangeFloor": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "经过楼梯、传送门时是否能“穿透”。\n穿透的意思是,自动寻路得到的的路径中间经过了楼梯,行走时是否触发楼层转换事件" + }, + "canGoDeadZone": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否允许走到将死的领域上。如果此项为true,则可以走到将死的领域上" + }, + "enableMoveDirectly": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否允许瞬间移动" + }, + "enableDisabledShop": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否允许查看未开启状态的快捷商店内容;如果此项为真,则对于未开启状态的商店允许查看其内容(但不能购买)" + }, + "disableShopOnDamage": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否在经过领域/夹击/路障等伤害后禁用快捷商店。" + }, + "checkConsole": { + "_leaf": true, + "_type": "checkbox", + "_bool": "bool", + "_data": "是否检查控制台的开启情况。" + } + } + } + } +} \ No newline at end of file diff --git a/_server/table/events.comment.js b/_server/table/events.comment.js new file mode 100644 index 00000000..4432901f --- /dev/null +++ b/_server/table/events.comment.js @@ -0,0 +1,46 @@ +/* + * 表格配置项。 + * 在这里可以对表格中的各项显示进行配置,包括表格项、提示内容等内容。具体写法照葫芦画瓢即可。 + * 本配置项包括:公共事件。 + */ + +var events_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { + "_type": "object", + "_data": { + "commonEvent": { + "_type": "object", + "_data": function (key) { + var obj = { + "加点事件": { + "_leaf": true, + "_type": "event", + "_range": "thiseval instanceof Array", + "_event": "commonEvent", + "_data": "打败怪物后加点" + }, + "毒衰咒处理": { + "_leaf": true, + "_type": "event", + "_range": "thiseval instanceof Array", + "_event": "commonEvent", + "_data": "毒衰咒效果处理" + }, + "滑冰事件": { + "_leaf": true, + "_type": "event", + "_range": "thiseval instanceof Array", + "_event": "commonEvent", + "_data": "滑冰事件" + }, + } + if (obj[key]) return obj[key]; + return { + "_leaf": true, + "_type": "event", + "_event": "commonEvent", + "_data": "自定义公共事件,可以双击进入事件编辑器" + } + } + } + } +} \ No newline at end of file diff --git a/_server/table/functions.comment.js b/_server/table/functions.comment.js new file mode 100644 index 00000000..44396901 --- /dev/null +++ b/_server/table/functions.comment.js @@ -0,0 +1,216 @@ +/* + * 表格配置项。 + * 在这里可以对表格中的各项显示进行配置,包括表格项、提示内容等内容。具体写法照葫芦画瓢即可。 + * 本配置项包括:脚本编辑。 + */ + +var functions_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { + "_type": "object", + "_data": { + "events": { + "_type": "object", + "_data": { + "resetGame": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "重置整个游戏" + }, + "setInitData": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "设置初始属性" + }, + "win": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "游戏获胜事件" + }, + "lose": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "游戏失败事件" + }, + "changingFloor": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "切换楼层中" + }, + "afterChangeFloor": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "切换楼层后" + }, + "flyTo": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "楼层飞行" + }, + "beforeBattle": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "战前事件" + }, + "afterBattle": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "战后事件" + }, + "afterOpenDoor": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "开门后事件" + }, + "afterGetItem": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "获得道具后事件" + }, + "afterChangeLight": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "改变亮灯事件" + }, + "afterPushBox": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "推箱子事件" + }, + "afterUseBomb": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "炸弹事件" + }, + "canUseQuickShop": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "能否用快捷商店" + } + } + }, + "enemys": { + "_type": "object", + "_data": { + "getSpecials": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "怪物特殊属性定义" + }, + "getEnemyInfo": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "获得怪物真实属性" + }, + "getDamageInfo": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "获得战斗伤害信息" + }, + "updateEnemys": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "更新怪物数据" + } + } + }, + "actions": { + "_type": "object", + "_data": { + "onKeyUp": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "按键处理" + } + } + }, + "control": { + "_type": "object", + "_data": { + "saveData": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "存档操作" + }, + "loadData": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "读档操作" + }, + "updateStatusBar": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "更新状态栏" + }, + "updateCheckBlock": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "阻激夹域伤害" + }, + "moveOneStep": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "每一步后的操作" + }, + "moveDirectly": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "瞬间移动处理" + }, + "parallelDo": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "并行事件处理" + } + } + }, + "ui": { + "_type": "object", + "_data": { + "drawStatusBar": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "自绘状态栏" + }, + "drawStatistics": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "地图数据统计" + }, + "drawAbout": { + "_leaf": true, + "_type": "textarea", + "_lint": true, + "_data": "绘制关于界面" + } + } + } + } +} \ No newline at end of file diff --git a/_server/maps.comment.js b/_server/table/maps.comment.js similarity index 98% rename from _server/maps.comment.js rename to _server/table/maps.comment.js index ddf8e8e6..187119a9 100644 --- a/_server/maps.comment.js +++ b/_server/table/maps.comment.js @@ -1,4 +1,4 @@ -maps_comment_90f36752_8815_4be8_b32b_d7fad1d0542e = +var maps_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { ////////////////////////// 地形部分 ////////////////////////// diff --git a/_server/table/plugins.comment.js b/_server/table/plugins.comment.js new file mode 100644 index 00000000..fc4f5144 --- /dev/null +++ b/_server/table/plugins.comment.js @@ -0,0 +1,32 @@ +/* + * 表格配置项。 + * 在这里可以对表格中的各项显示进行配置,包括表格项、提示内容等内容。具体写法照葫芦画瓢即可。 + * 本配置项包括:插件编写。 + */ + +var plugins_comment_c456ea59_6018_45ef_8bcc_211a24c627dc = { + "_type": "object", + "_data": function (key) { + var obj = { + "init": { + "_leaf": true, + "_type": "textarea", + "_range": "typeof(thiseval)=='string'", + "_data": "自定义插件" + }, + "drawLight": { + "_leaf": true, + "_type": "textarea", + "_range": "typeof(thiseval)=='string' || thiseval==null", + "_data": "绘制灯光效果" + }, + } + if (obj[key]) return obj[key]; + return { + "_leaf": true, + "_type": "textarea", + "_range": "typeof(thiseval)=='string' || thiseval==null", + "_data": "自定义插件" + } + } +} \ No newline at end of file diff --git a/_server/vm.js b/_server/vm.js deleted file mode 100644 index 7f932169..00000000 --- a/_server/vm.js +++ /dev/null @@ -1,405 +0,0 @@ -// vue 相关处理 -document.body.onmousedown = function (e) { - //console.log(e); - var clickpath = []; - var getpath=function(e) { - var path = []; - var currentElem = e.target; - while (currentElem) { - path.push(currentElem); - currentElem = currentElem.parentElement; - } - if (path.indexOf(window) === -1 && path.indexOf(document) === -1) - path.push(document); - if (path.indexOf(window) === -1) - path.push(window); - return path; - } - getpath(e).forEach(function (node) { - if (!node.getAttribute) return; - var id_ = node.getAttribute('id'); - if (id_) { - if (['left', 'left1', 'left2', 'left3', 'left4', 'left5', 'left8', 'mobileview'].indexOf(id_) !== -1) clickpath.push('edit'); - clickpath.push(id_); - } - }); - - var unselect=true; - for(var ii=0,thisId;thisId=['edit','tip','brushMod','brushMod2','viewportButtons'][ii];ii++){ - if (clickpath.indexOf(thisId) !== -1){ - unselect=false; - break; - } - } - if (unselect) { - if (clickpath.indexOf('eui') === -1) { - if (selectBox.isSelected) { - editor_mode.onmode(''); - editor.file.saveFloorFile(function (err) { - if (err) { - printe(err); - throw(err) - } - ;printf('地图保存成功'); - }); - } - selectBox.isSelected = false; - editor.info = {}; - } - } - //editor.mode.onmode(''); - if (e.button!=2 && !editor.isMobile){ - editor.hideMidMenu(); - } - if (clickpath.indexOf('down') !== -1 && editor.isMobile && clickpath.indexOf('midMenu') === -1){ - editor.hideMidMenu(); - } - if(clickpath.length>=2 && clickpath[0].indexOf('id_')===0){editor.lastClickId=clickpath[0]} -} -iconLib.onmousedown = function (e) { - e.stopPropagation(); -} -var exportMap = new Vue({ - el: '#exportMap', - data: { - isExport: false, - }, - methods: { - exportMap: function () { - editor.updateMap(); - var sx=editor.map.length-1,sy=editor.map[0].length-1; - - var filestr = ''; - for (var yy = 0; yy <= sy; yy++) { - filestr += '[' - for (var xx = 0; xx <= sx; xx++) { - var mapxy = editor.map[yy][xx]; - if (typeof(mapxy) == typeof({})) { - if ('idnum' in mapxy) mapxy = mapxy.idnum; - else { - // mapxy='!!?'; - tip.whichShow = 3; - return; - } - } else if (typeof(mapxy) == 'undefined') { - tip.whichShow = 3; - return; - } - mapxy = String(mapxy); - mapxy = Array(Math.max(4 - mapxy.length, 0)).join(' ') + mapxy; - filestr += mapxy + (xx == sx ? '' : ',') - } - - filestr += ']' + (yy == sy ? '' : ',\n'); - } - pout.value = filestr; - editArea.mapArr = filestr; - this.isExport = true; - editArea.error = 0; - tip.whichShow = 2; - } - } -}) -var editArea = new Vue({ - el: '#editArea', - data: { - mapArr: '', - errors: [ // 编号1,2,3,4 - "格式错误!请使用正确格式(width*height数组,如不清楚,可先点击生成地图查看正确格式)", - "当前有未定义ID(在地图区域显示红块),请修改ID或者到icons.js和maps.js中进行定义!", - "ID越界(在地图区域显示红块),当前编辑器暂时支持编号小于400,请修改编号!", - // "发生错误!", - ], - error: 0, - formatTimer: null, - }, - watch: { - mapArr: function (val, oldval) { - var that = this; - if (val == '') return; - if (exportMap.isExport) { - exportMap.isExport = false; - return; - } - if (that.formatArr()) { - that.error = 0; - - setTimeout(function () { - that.mapArr = that.formatArr(); - that.drawMap(); - tip.whichShow = 8 - }, 1000); - that.formatTimer = setTimeout(function () { - pout.value = that.formatArr(); - }, 5000); //5s后再格式化,不然光标跳到最后很烦 - } else { - that.error = 1; - } - }, - error: function () { - // console.log(editArea.mapArr); - } - }, - methods: { - drawMap: function () { - var that = this; - - // var mapArray = that.mapArr.split(/\D+/).join(' ').trim().split(' '); - var mapArray = JSON.parse('[' + that.mapArr + ']'); - var sy=editor.map.length,sx=editor.map[0].length; - for (var y = 0; y < sy; y++) - for (var x = 0; x < sx; x++) { - var num = mapArray[y][x]; - if (num == 0) - editor.map[y][x] = 0; - else if (num >= 1000) { - that.error = 3; - editor.map[y][x] = undefined; - } else if (typeof(editor.indexs[num][0]) == 'undefined') { - that.error = 2; - editor.map[y][x] = undefined; - } else editor.map[y][x] = editor.ids[[editor.indexs[num][0]]]; - } - - editor.updateMap(); - - }, - formatArr: function () { - var formatArrStr = ''; - var that = this; - clearTimeout(that.formatTimer); - var si=editor.map.length,sk=editor.map[0].length; - if (this.mapArr.split(/\D+/).join(' ').trim().split(' ').length != si*sk) return false; - var arr = this.mapArr.replace(/\s+/g, '').split('],['); - - if (arr.length != si) return; - for (var i = 0; i < si; i++) { - var a = []; - formatArrStr += '['; - if (i == 0 || i == si-1) a = arr[i].split(/\D+/).join(' ').trim().split(' '); - else a = arr[i].split(/\D+/); - if (a.length != sk) { - formatArrStr = ''; - return; - } - - for (var k = 0; k < sk; k++) { - var num = parseInt(a[k]); - formatArrStr += Array(Math.max(4 - String(num).length, 0)).join(' ') + num + (k == sk-1 ? '' : ','); - } - formatArrStr += ']' + (i == si-1 ? '' : ',\n'); - } - return formatArrStr; - } - } -}); -var copyMap = new Vue({ - el: '#copyMap', - data: { - err: '' - }, - methods: { - copyMap: function () { - - tip.whichShow = 0; - if (pout.value.trim() != '') { - if (editArea.error) { - this.err = editArea.errors[editArea.error - 1]; - tip.whichShow = 5 - return; - } - try { - pout.focus(); - pout.setSelectionRange(0, pout.value.length); - document.execCommand("Copy"); - tip.whichShow = 6; - } catch (e) { - this.err = e; - tip.whichShow = 5; - } - } else { - tip.whichShow = 7; - } - } - }, -}) -var clearMap = new Vue({ - el: '#clearMap', - - methods: { - clearMap: function () { - editor.mapInit(); - editor_mode.onmode(''); - editor.file.saveFloorFile(function (err) { - if (err) { - printe(err); - throw(err) - } - ;printf('地图清除成功'); - }); - editor.updateMap(); - clearTimeout(editArea.formatTimer); - clearTimeout(tip.timer); - pout.value = ''; - editArea.mapArr = ''; - tip.whichShow = 4; - editArea.error = 0; - } - } -}) -var deleteMap = new Vue({ - el: '#deleteMap', - methods: { - deleteMap: function () { - editor_mode.onmode(''); - var index = core.floorIds.indexOf(editor.currentFloorId); - if (index>=0) { - core.floorIds.splice(index,1); - editor.file.editTower([['change', "['main']['floorIds']", core.floorIds]], function (objs_) {/*console.log(objs_);*/ - if (objs_.slice(-1)[0] != null) { - printe(objs_.slice(-1)[0]); - throw(objs_.slice(-1)[0]) - } - ;printe('删除成功,请F5刷新编辑器生效'); - }); - } - else printe('删除成功,请F5刷新编辑器生效'); - } - } -}) -printf = function (str_, type) { - selectBox.isSelected = false; - if (!type) { - tip.whichShow = 11; - } else { - tip.whichShow = 12; - } - setTimeout(function () { - if (!type) { - tip.msgs[11] = String(str_); - tip.whichShow = 12; - } else { - tip.msgs[10] = String(str_); - tip.whichShow = 11; - } - }, 1); -} -printe = function (str_) { - printf(str_, 'error') -} -tip_in_showMode = [ - '涉及图片的更改需要F5刷新浏览器来生效', - '文本域可以通过双击,在文本编辑器或事件编辑器中编辑', - '事件编辑器中的显示文本和自定义脚本的方块也可以双击', - "画出的地图要点击\"保存地图\"才会写入到文件中", -]; -var tip = new Vue({ - el: '#tip', - data: { - infos: {}, - hasId: true, - isAutotile: false, - isSelectedBlock: false, - isClearBlock: false, - geneMapSuccess: false, - timer: null, - msgs: [ //分别编号1,2,3,4,5,6,7,8,9,10;奇数警告,偶数成功 - "当前未选择任何图块,请先在右边选择要画的图块!", - "生成地图成功!可点击复制按钮复制地图数组到剪切板", - "生成失败! 地图中有未定义的图块,建议先用其他有效图块覆盖或点击清除地图!", - "地图清除成功!", - "复制失败!", - "复制成功!可直接粘贴到楼层文件的地图数组中。", - "复制失败!当前还没有数据", - "修改成功!可点击复制按钮复制地图数组到剪切板", - "选择背景图片失败!文件名格式错误或图片不存在!", - "更新背景图片成功!", - "11:警告", - "12:成功" - ], - mapMsg: '', - whichShow: 0, - }, - watch: { - infos: { - handler: function (val, oldval) { - this.isClearBlock = false; - if (typeof(val) != 'undefined') { - if (val == 0) { - this.isClearBlock = true; - return; - } - if ('id' in val) { - this.hasId = true; - } else { - this.hasId = false; - } - this.isAutotile = false; - if (val.images == "autotile" && this.hasId) this.isAutotile = true; - } - }, - deep: true - }, - - whichShow: function () { - var that = this; - that.mapMsg = ''; - that.msgs[4] = "复制失败!" + editTip.err; - clearTimeout(that.timer); - if (that.whichShow) { - that.mapMsg = that.msgs[that.whichShow - 1]; - that.timer = setTimeout(function () { - if (!(that.whichShow % 2)) - that.whichShow = 0; - }, 5000); //5秒后自动清除success,warn不清除 - } - } - } -}) - -var selectBox = new Vue({ - el: '#selectBox', - data: { - isSelected: false - }, - watch: { - isSelected: function () { - tip.isSelectedBlock = this.isSelected; - tip.whichShow = 0; - clearTimeout(tip.timer); - } - } -}) - -var bgSelect = new Vue({ - el: '#bgSelect', - data: { - bgs: {}, - selectedBg: 'ground', - imgname: '' - }, - watch: { - selectedBg: function () { - editor.bgY = this.bgs.indexOf(this.selectedBg); - editor.drawMapBg(); - } - }, - methods: { - updatebg: function () { - tip.whichShow = 0; - var regx = /\S+\.(png|bmp|jpg|jpeg|gif)$/i; - if (regx.test(this.imgname)) { - var url = 'images/' + this.imgname; - editor.loadImg(url).then(function (img) { - editor.drawMapBg(img); - tip.whichShow = 10; - }).catch(function (err) { - console.log(err); - tip.whichShow = 9; - }); - } else { - tip.whichShow = 9; - } - } - } -}) \ No newline at end of file diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/_api.md b/docs/_api.md deleted file mode 100644 index f64ed2ac..00000000 --- a/docs/_api.md +++ /dev/null @@ -1,367 +0,0 @@ -# 附录:API列表 - -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * - -所有系统支持的API都列在了这里。所有可能被用到的API都在前面用\*标记。 - -可以在chrome浏览器的控制台中(`ctrl+shift+I`,找到Console)中直接进行调用,以查看效果。 - -!> **`main.js`:游戏入口,所有其他JS文件都是被此文件加载。** - -``` js -main.init // 初始化 -main.loaderJs // 动态加载所有核心JS文件 -main,loaderFloors // 动态加载所有楼层(剧本) -main.loadMod // 加载某一个JS文件 -main.loadFloor // 加载某一个楼层 -main.setMainTipsText // 加载过程提示 -window.onresize // 窗口大小变化时 -main.dom.body.onkeydown // 在界面上按下某按键时 -main.dom.body.onkeydown // 在界面上放开某按键时 -main.dom.body.onselectstart // 开始选择时 -main.dom.data.onmousedown // 鼠标按下时 -main.dom.data.onmousemove // 鼠标移动时 -main.dom.data.onmouseup // 鼠标放开时 -main.dom.data.onmousewheel // 鼠标滑轮滚动时 -main.dom.data.ontouchstart // 手指在触摸屏开始触摸时 -main.dom.data.ontouchmove // 手指在触摸屏上移动时 -main.dom.data.ontouchend // 手指离开触摸屏时 -main.statusBar.image.book.onclick // 点击状态栏中的怪物手册时 -main.statusBar.image.fly.onclick // 点击状态栏中的楼层传送器时 -main.statusBar.image.toolbox.onclick // 点击状态栏中的工具箱时 -main.statusBar.image.shop.onclick // 点击状态栏中的快捷商店时 -main.statusBar.image.save.onclick // 点击状态栏中的存档按钮时 -main.statusBar.image.load.onclick // 点击状态栏中的读档按钮时 -main.statusBar.image.settings.onclick // 点击状态栏中的系统菜单时 -main.dom.playGame.onclick // 点击“开始游戏”时 -main.dom.loadGame.onclick // 点击“载入游戏”时 -main.dom.replayGame.onclick // 点击“录像回放”时 -main.dom.easyLevel.onclick // 点击“简单难度”时 -main.dom.normalLevel.onclick // 点击“普通难度”时 -main.dom.hardLevel.onclick // 点击“困难难度”时 -``` - -!> **`core.js`:系统核心文件。所有核心逻辑处理都在此文件完成。** - -``` js -* core.status.floorId // 获得当前层floorId -* core.status.thisMap // 获得当前层的地图信息 -* core.status.maps // 获得所有楼层的地图信息 -* core.floors // 获得所有楼层的剧本 - -// ------ 初始化部分 ------ -core.init // 初始化 -core.showStartAnimate // 显示游戏开始界面 -core.hideStartAnimate // 隐藏游戏开始界面 -core.setStartProgressVal // 设置加载进度条进度 -core.setStartLoadTipText // 设置加载进度条提示文字 -core.loader // 加载图片和音频 -core.loadAutotile // 加载Autotile -core.loadImage // 加载图片 -core.loadMusic // 加载音频 -core.isPlaying // 游戏是否已经开始 -core.clearStatus // 清除游戏状态和数据 -core.resetStatus // 重置游戏状态和初始数据 -core.startGame // 开始游戏 -* core.restart // 重新开始游戏;此函数将回到标题页面 - -// ------ 键盘、鼠标事件 ------ -core.onKeyDown // 按下某个键时 -core.onKeyUp // 放开某个键时 -core.pressKey // 按住某个键时 -core.keyDown // 根据按下键的code来执行一系列操作 -core.keyUp // 根据放开键的code来执行一系列操作 -core.ondown // 点击(触摸)事件按下时 -core.onmove // 当在触摸屏上滑动时 -core.onup // 当点击(触摸)事件放开时 -core.getClickLoc // 获得点击事件相对左上角的坐标(0到12之间) -core.onclick // 具体点击屏幕上(x,y)点时,执行的操作 -core.onmousewheel // 滑动鼠标滚轮时的操作 - -// ------ 自动寻路代码相关 ------ -core.clearAutomaticRouteNode // 清除自动寻路路线 -core.stopAutomaticRoute // 停止自动寻路操作 -core.continueAutomaticRoute // 继续剩下的自动寻路操作 -core.clearContinueAutomaticRoute // 清空剩下的自动寻路列表 -core.setAutomaticRoute // 设置自动寻路路线 -core.automaticRoute // 自动寻路算法,找寻最优路径 -core.fillPosWithPoint // 显示离散的寻路点 -core.clearStepPostfix // 清除已经寻路过的部分 - -// ------ 自动行走,行走控制 ------ -core.stopAutoHeroMove // 停止勇士的自动行走 -core.setAutoHeroMove // 设置勇士的自动行走路线 -core.autoHeroMove // 让勇士开始自动行走 -core.setHeroMoveInterval // 设置行走的效果动画 -core.setHeroMoveTriggerInterval // 设置勇士行走过程中对事件的触发检测 -core.moveAction // 实际每一步的行走过程 -* core.turnHero(direction) // 设置勇士的方向(转向) -core.canMoveHero // 勇士能否前往某方向 -core.moveHero // 让勇士开始移动 -core.eventMoveHero // 使用事件让勇士移动。这个函数将不会触发任何事件。 -core.moveOneStep // 每移动一格后执行的事件。中毒时在这里进行扣血判断。 -core.waitHeroToStop(callback) // 停止勇士的一切行动,等待勇士行动结束后,再执行callback回调函数。 -core.stopHero // 停止勇士的移动状态。 -core.drawHero // 绘制勇士。 -* core.setHeroLoc(name, value) // 设置勇士的位置。name为”direction”,”x”,”y” -* core.getHeroLoc(name) // 获得勇士的位置。 -* core.nextX // 获得勇士面对位置的x坐标 -* core.nextY // 获得勇士面对位置的y坐标 - -// ------ 地图和事件处理 ------ -* core.openDoor(id, x, y, needKey, callback) // 打开一扇位于 (x,y) 的门 -* core.battle(id, x, y, force, callback) // 进行战斗;force表示是否强制战斗 -core.afterBattle // 战斗完毕 -core.trigger(x,y) // 触发x,y点的事件 -* core.changeFloor(floorId, stair, heroLoc, time, callback) // 楼层切换。floorId为目标楼层Id,stair可指定为上/下楼梯,time动画时间 -core.mapChangeAnimate // 地图切换动画效果 -core.clearMap // 清除地图 -core.fillText // 在某个canvas上绘制一段文字 -core.fillRect // 在某个canvas上绘制一个矩形 -core.strokeRect // 在某个canvas上绘制一个矩形的边框 -core.drawLine // 在某个canvas上绘制一条线 -core.setFont // 设置某个canvas的文字字体 -core.setLineWidth // 设置某个canvas的线宽度 -core.saveCanvas // 保存某个canvas状态 -core.loadCanvas // 加载某个canvas状态 -core.setStrokeStyle // 设置某个canvas边框属性 -core.setAlpha // 设置某个canvas的alpha值 -core.setOpacity // 设置某个canvas的透明度 -core.setFillStyle // 设置某个canvas的绘制属性(如颜色等) -* core.drawMap(mapId, callback) // 绘制某张地图。mapId为地图Id,绘制完毕将执行callback回调函数。 -core.drawAutotile // 绘制Autotile -* core.noPassExists(x,y) // 某个点是否不可通行 -core.noPass // 某个点是否在区域内且不可通行 -* core.npcExists(x,y) // 某个点是否存在NPC -* core.terrainExists(x,y) // 某个点是否存在(指定的)地形 -* core.stairExists(x,y) // 某个点是否存在楼梯 -* core.nearStair // 当前位置是否在楼梯边 -* core.enemyExists(x,y) // 某个点是否存在(指定的)怪物 -* core.getBlock(x, y, floorId, needEnable) // 获得某个点的block。floorId指定目标楼层,needEnable如果为false则即使该点的事件处于禁用状态也将被返回(否则只有事件启用的点才被返回) -core.moveBlock // 显示移动某块的动画,达到{“type”:”move”}的效果 -core.animateBlock // 显示/隐藏某个块时的动画效果 -core.showBlock // 将某个块从禁用变成启用状态 -core.removeBlock // 将某个块从启用变成禁用状态 -core.removeBlockById // 根据block的索引删除该块 -core.removeBlockByIds // 一次性删除多个block -core.addGlobalAnimate // 添加一个全局动画 -core.removeGlobalAnimate // 删除一个或所有全局动画 -core.setGlobalAnimate // 设置全局动画的显示效果 -core.syncGlobalAnimate // 同步所有的全局动画效果 -core.drawBoxAnimate // 绘制UI层的box动画 -core.updateCheckBlock // 更新领域、夹击、阻击的伤害地图 -core.checkBlock // 检查并执行领域、夹击、阻击事件 -core.snipe // 阻击事件(动画效果) -core.setFg // 更改画面色调 -* core.updateFg // 更新全地图显伤 -* core.itemCount // 获得某个物品的个数 -* core.hasItem // 是否存在某个物品 -* core.setItem // 设置某个物品的个数 -* core.removeItem // 删除某个物品 -* core.useItem // 使用某个物品;直接调用items.js中的useItem函数。 -* core.canUseItem // 能否使用某个物品。直接调用items.js中的canUseItem函数。 -* core.addItem // 增加某个物品的个数 -core.getNextItem // 获得面前的物品(轻按) -* core.getItem // 获得某个物品 -* core.drawTip // 左上角绘制一段提示 -* core.drawText // 地图中间绘制一段文字 - -// ------ 系统机制 ------ -core.replaceText // 将文字中的${和}(表达式)进行替换 -core.calValue // 计算表达式的值 -core.doEffect // 执行一个表达式的effect操作 -core.splitLines // 字符串自动换行的分割 -core.unshift // 向某个数组前插入另一个数组或元素 -core.setLocalStorage // 设置本地存储 -core.getLocalStorage // 获得本地存储 -core.removeLocalStorage // 移除本地存储 -core.clone // 深拷贝一个对象 -core.formatDate // 格式化时间为字符串 -core.formatDate2 // 格式化时间为最简字符串 -core.setTwoDigits // 两位数显示 -core.debug // 进入Debug模式,攻防血和钥匙都调成很高的数值 -core.replay // 开始回放 -core.checkStatus // 判断当前能否进入某个事件 -core.openBook // 点击怪物手册时的打开操作 -core.useFly // 点击楼层传送器时的打开操作 -core.openToolbox // 点击工具栏时的打开操作 -core.openQuickShop // 点击快捷商店时的打开操作 -core.save // 点击保存按钮时的打开操作 -core.load // 点击读取按钮时的打开操作 -core.openSettings // 点击设置按钮时的打开操作 -core.autosave // 自动存档 -core.doSL // 实际进行存读档事件 -core.syncSave // 存档同步操作 -core.saveData // 存档到本地 -core.loadData // 从本地读档 -core.encodeRoute // 将路线压缩 -core.decodeRoute // 将路线解压缩 -* core.setStatus // 设置勇士属性 -* core.getStatus // 获得勇士属性 -core.getLvName // 获得某个等级的名称 -* core.setFlag // 设置某个自定义变量或flag -* core.getFlag // 获得某个自定义变量或flag -* core.hasFlag // 是否存在某个自定义变量或flag,且值为true -core.insertAction // 往当前事件列表之前插入一系列事件 -* core.lockControl // 锁定状态栏,常常用于事件处理 -* core.unlockControl // 解锁状态栏 -* core.isset // 判断某对象是否不为undefined也不会null -core.readFile // 读取一个本地文件内容 -core.download // 下载文件到本地 -core.copy // 复制一段文字到剪切板 -* core.playBgm // 播放背景音乐 -* core.pauseBgm // 暂停背景音乐的播放 -* core.resumeBgm // 恢复背景音乐的播放 -* core.playSound // 播放音频 -core.show // 动画显示某对象 -core.hide // 动画使某对象消失 -core.clearStatusBar // 清空状态栏 -core.updateStatusBar // 更新状态栏 -core.resize // 屏幕分辨率改变后重新自适应 -core.domRenderer // 渲染DOM - -// ------ core.js 结束 ------ -``` - -!> **`data.js` 定义了一些初始化的数据信息。** - -!> **`enemys.js` 定义了怪物信息。** - -``` js -core.enemys.init // 初始化 -* core.enemys.getEnemys // 获得一个或所有怪物数据 -* core.enemys.hasSpecial // 判断是否含有某特殊属性 -* core.enemys.getSpecialText // 获得所有特殊属性的名称 -* core.enemys.getSpecialHint // 获得每个特殊属性的说明 -* core.enemys.getDamage // 获得某个怪物的伤害 -* core.enemys.getExtraDamage // 获得某个怪物的额外伤害 -* core.enemys.getCritical // 临界值计算 -* core.enemys.getCriticalDamage // 临界减伤计算 -* core.enemys.getDefDamage // 1防减伤计算 -* core.enemys.calDamage // 具体的伤害计算公式 -core.enemys.getCurrentEnemys // 获得当前楼层的怪物列表 -``` - -!> **`events.js` 定义了各个事件的处理流程。** - -``` js -core.events.init // 初始化 -core.events.getEvents // 获得一个或所有系统事件类型 -core.events.startGame // 游戏开始事件 -* core.events.setInitData // 不同难度分别设置初始属性 -* core.events.win // 游戏获胜事件 -* core.events.lose // 游戏失败事件 -core.evens.gameOver // 游戏结束 -core.events.afterChangeFloor // 转换楼层结束的事件 -core.events.doEvents // 开始执行一系列自定义事件 -core.events.doAction // 执行当前自定义事件列表中的下一个事件 -core.events.insertAction // 往当前事件列表之前添加一个或多个事件 -core.events.openShop // 打开一个全局商店 -core.events.disableQuickShop // 禁用一个全局商店 -* core.events.canUseQuickShop // 当前能否使用快捷商店 -* core.events.checkLvUp // 检查升级事件 -* core.events.useItem // 尝试使用道具 -core.events.addPoint // 加点事件 -core.events.afterBattle // 战斗结束后触发的事件 -core.events.afterOpenDoor // 开一个门后触发的事件 -core.events.passNet // 经过一个路障 -core.events.changeLight // 改变亮灯(感叹号)的事件 -* core.events.afterChangeLight // 改变亮灯之后,可以触发的事件 -* core.events.afterUseBomb // 使用炸弹/圣锤后的事件 -* core.events.beforeSaveData // 即将存档前可以执行的操作 -* core.events.afterLoadData // 读档事件后,载入事件前,可以执行的操作 - -// ------ 点击事件和键盘事件的处理 ------ -core.events.longClick // 长按 -core.events.keyDownCtrl // 按下Ctrl键时(快捷跳过对话) -core.events.clickConfirmBox // 确认框界面时的点击操作 -core.events.keyUpConfirmBox // 确认框界面时,放开某个键的操作 -core.events.clickAction // 自定义事件时的点击操作 -core.events.keyDownAction // 自定义事件时,按下某个键的操作 -core.events.keyUpAction // 自定义事件时,放开某个键的操作 -core.events.clickBook // 怪物手册界面的点击操作 -core.events.keyDownBook // 怪物手册界面时,按下某个键的操作 -core.events.keyUpBook // 怪物手册界面时,放开某个键的操作 -core.events.clickBookDetail // 怪物手册属性显示界面时的点击操作 -core.events.clickFly // 楼层传送器界面时的点击操作 -core.events.keyDownFly // 楼层传送器界面时,按下某个键的操作 -core.events.keyUpFly // 楼层传送器界面时,放开某个键的操作 -core.events.clickViewMaps // 浏览地图界面时的点击操作 -core.events.keyDownViewMaps // 浏览地图界面时,按下某个键的操作 -core.events.keyUpViewMaps // 浏览地图界面时,放开某个键的操作 -core.events.clickShop // 商店界面时的点击操作 -core.events.keyDownShop // 商店界面时,按下某个键的操作 -core.events.keyUpShop // 商店界面时,放开某个键的操作 -core.events.clickQuickShop // 快捷商店界面时的点击操作 -core.events.keyDownQuickShop // 快捷商店界面时,按下某个键的操作 -core.events.keyUpQuickShop // 快捷商店界面时,放开某个键的操作 -core.events.clickToolbox // 工具栏界面时的点击操作 -core.events.clickToolboxIndex // 选择工具栏界面中某个Index后的操作 -core.events.keyDownToolbox // 工具栏界面时,按下某个键的操作 -core.events.keyUpToolbox // 工具栏界面时,放开某个键的操作 -core.events.clickSL // 存读档界面时的点击操作 -core.events.keyDownSL // 存读档界面时,按下某个键的操作 -core.events.keyUpSL // 存读档界面时,放开某个键的操作 -core.events.clickSwitchs // 系统设置界面时的点击操作 -core.events.keyDownSwitchs // 系统设置界面时,按下某个键的操作 -core.events.keyUpSwitchs // 系统设置界面时,放开某个键的操作 -core.events.clickSettings // 系统菜单栏界面时的点击事件 -core.events.keyDownSettings // 系统菜单栏界面时,按下某个键的操作 -core.events.keyUpSettings // 系统菜单栏界面时,放开某个键的操作 -core.events.clickSyncSave // 同步存档界面时的点击操作 -core.events.keyDownSyncSave // 同步存档界面时,按下某个键的操作 -core.events.keyUpSyncSave // 同步存档界面时,放开某个键的操作 -core.events.clickKeyBoard // 虚拟键盘界面时的点击操作 -core.events.clickAbout // “关于”界面时的点击操作 -``` - -!> `icons.js` 定义了素材ID和它在图片上的索引的对应关系。 - -!> `items.js` 定义了每个道具的名称,以及使用效果。 - -``` js -core.items.init // 初始化 -core.items.getItems // 获得所有道具 -core.items.getItemEffect // “即捡即用类”道具的使用效果 -core.items.getItemEffectTip // “即捡即用类”道具的文字提示 -* core.items.useItem // 使用道具 -* core.items.cauUseItem // 当前能否使用道具 -``` - -!> `maps.js` 定义了数字-ID的对应关系。 - -``` js -core.maps.loadFloor // 加载某个楼层(从剧本或存档中) -core.maps.getBlock // 数字和ID的对应关系 -core.maps.addEvent // 向该楼层添加剧本的自定义事件 -core.maps.addChangeFloor // 向该楼层添加剧本的楼层转换事件 -core.maps.initMaps // 初始化所有地图 -core.maps.save // 将当前地图重新变成数字,以便于存档 -core.maps.load // 将存档中的地图信息重新读取出来 -core.maps.getMapArray // 将当前地图重新变成二维数组形式 -``` - -!> `ui.js` 定义了各种界面的绘制。 - -``` js -core.ui.closePanel // 结束一切事件和绘制,关闭UI窗口,返回游戏进程 -core.ui.drawTextBox // 绘制一个对话框 -core.ui.drawChoices // 绘制一个选项界面 -core.ui.drawConfirmBox // 绘制一个确认/取消的警告页面 -core.ui.drawSwitchs // 绘制系统设置界面 -core.ui.drawSettings // 绘制系统菜单栏 -core.ui.drawQuickShop // 绘制快捷商店选择栏 -core.ui.drawBattleAnimate // 绘制战斗动画 -core.ui.drawWaiting // 绘制等待界面 -core.ui.drawSyncSave // 绘制存档同步界面 -core.ui.drawPagination // 绘制分页 -core.ui.drawEnemyBook // 绘制怪物手册 -core.ui.drawBookDetail // 绘制怪物属性的详细信息 -core.ui.drawFly // 绘制楼层传送器 -core.ui.drawMaps // 绘制浏览地图界面 -core.ui.drawToolbox // 绘制道具栏 -core.ui.drawSLPanel // 绘制存档/读档界面 -core.ui.drawThumbnail // 绘制一个缩略图 -core.ui.drawAbout // 绘制“关于”界面 -core.ui.drawHelp // 绘制帮助界面 -``` diff --git a/docs/_start.md b/docs/_start.md deleted file mode 100644 index 5b3d91dc..00000000 --- a/docs/_start.md +++ /dev/null @@ -1,222 +0,0 @@ -# 快速上手 - -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * - -在这一节中,将详细介绍做一部塔的流程。现在,让我们来做一部单层塔! - -## 前置需求 - -你需要有满足如下条件才能进行制作: - -- Windows 8以上操作系统;Windows 7需要安装.Net Framework 4.0。(能打开同目录下的“启动服务.exe”即可) -- Chrome浏览器。其他浏览器可能会导致本地服务器产生闪退等现象。 -- 一个很好的文本编辑器。推荐带有高亮染色、错误提示等效果。例如:WebStorm,VSCode,或者至少也要Sublime Text。 - - ([VSCode下载地址](https://code.visualstudio.com/),群里的群文件中也有,强烈推荐之。) - - **2.0版本可以不需要,但是仍然强烈推荐有一个,从而能对H5造塔有更深的了解。** - -只要满足了上述条件,你就可以开始做自己的塔啦! - -## V2.0的使用 - -目前版本已经更新到V2.0,在2.0版本中,我们可以进行全GUI造塔。 - -下面的文档主要是讲如何通过代码编辑的方式来造,仍然强烈建议进行阅读从而有着一定的了解。 - -如果想直接知道如何在V2.0造塔,可以直接参见[V2.0版本介绍](V2.0)的说明,或者看[B站视频教程](http://www.bilibili.com/video/av17608025/)。 - -## 启动HTTP服务 - -在根目录下有一个“启动服务.exe”,运行之。 - -![启动服务](img/server.png) - -* “启动游戏”按钮将打开一个网页,你能在里面看到现在游戏的效果。 -* “地图编辑器”允许你以可视化的方式进行编辑地图。 -* “便捷PS工具”能让你很方便的对自定义素材进行添加。参见[自定义素材](personalization#自定义素材)。 -* “地图生成器”能让你从已有的截图(如RMXP项目)中立刻生成可被本样板识别的地图数据。 -* “RM动画导出器”能让你从RMXP中导出动画而被H5魔塔使用。 -* “JS代码压缩工具”能对JS代码进行压缩,从而减少IO请求数和文件大小。 -* “伤害和临界值计算器”是一个很便捷的小工具,能对怪物的伤害和临界值进行计算。 - -!> **警告:** 非Chrome浏览器(如Edge/IE等)下本地服务器可能表现不正常,会出现闪退等现象。请务必下载安装Chrome浏览器。 - -## 新建剧本 - -类似于RMXP,本塔每层楼都是一个“剧本”,剧本内主要定义了本层的地图和各种事件。主函数将读取每个剧本,并生成实际的地图供游戏使用。 - -我们打开 `project/floors/` 目录,这个目录是所有剧本的目录。我们需要指定一个楼层名,例如MT1;然后,我们可以将`MT0.js`(模板)复制重命名为为`MT1.js`,并使用文本编辑器打开。 - -![新建剧本](./img/script.png) - -然后将楼层名改为MT1,floorId改名为MT1;title可以改成任意内容,将在切换楼层时进行显示(比如可以改成“1层小塔”)。 - -具体样板文件的每个要素如下: -- **`floorId`** 楼层唯一标识符;必须和文件名,以及 `main.floors.xxx` 完全一致 -- **`title`** 楼层中文名,将在切换楼层时进行显示 -- **`name`** 楼层再状态栏显示的名称 -- **`canFlyTo`** 当前楼层可否被楼传器飞到。如果该层不能飞到,则也在该层也不允许使用楼传器。 -- **`canUseQuickShop`** 当前楼层可否使用快捷商店。 -- **`defaultGround`** 该层的背景(地面)素材。 -- **`images`** 该层默认显示的前景/背景图片 -- **`color`** 该层的画面色调。 -- **`bgm`** 到达该层后默认播放的BGM。本项可忽略。 -- **`item_ratio`** 该层的宝石/血瓶倍率 -- **`map`** 本层地图,需要是13x13数组,建议使用地图生成器或者可视化地图编辑器制作。 -- **`firstArrive`** 第一次到该楼层触发的事件 -- **`events`** 该楼的所有可能事件列表 -- **`changeFloor`** 楼层转换事件;该事件不能和上面的events有冲突(同位置点),否则会被覆盖 -- **`afterBattle`** 战斗后可能触发的事件列表 -- **`afterGetItem`** 获得道具后可能触发的事件列表 -- **`afterOpenDoor`** 开完门后可能触发的事件列表 -- **`cannotMove`** 每个图块不可通行的方向,也就是悬崖效果 - -我们最终的任务其实是,将每个楼层的剧本(地图&事件)给写完即可。 - -换句话说,只需要简单的复制操作,我们就可以新建一个剧本了。 - -## 绘制地图 - -有两种绘制地图的方式:从头绘制地图;从RMXP中导入已有的地图。 - -### 从头绘制地图 - -我们直接打开“地图编辑器”,可以看到一个可视化的UI界面。 - -![地图编辑器](img/mapgui.png) - -然后可以在上面任意进行绘制地图。 - -!> **如果地图的数字和ID未被定义,则会进行提示:数字和ID未被定义!此时要对素材的ID和数字进行定义,请参见[自定义素材](personalization#自定义素材)。** - -绘制地图完毕后,点击"导出地图",即可在左边看到对应的JSON数组,并且已经复制到了剪切板。将其粘贴到剧本中的map位置即可。 - -![地图数组](./img/maparray.png) - -!> V2.0版本可以直接将当前地图进行保存或另存为,不需要这样手动打开进行编辑。 - -### 从RMXP导入已有的地图 - -如果我们想复刻一个现有的,已经被RMXP所制作的塔,也有很便捷的方式,那就是用到我们的“地图生成器”。 - -首先,我们打开RMXP和对应的项目,可以看到它的地图。 - -![绘制地图](./img/rmxp2.png) - -我们打开Windows自带的“截图工具”,并将整个地图有效区域截图下来,并将其复制到剪切板。 - -![绘制地图](./img/rmxp3.png) - -截图时请注意:**只截取有效游戏空间内数据,并且有效空间内的范围必须是13x13。(如果地图小于13*13,请用星空或墙壁填充到13x13)。** - -确认地图的图片文件已经复制到剪切板后,我们打开“地图生成器”,并点“加载图片”。大约1-2秒后,可以得到地图的数据。 - -![生成地图](./img/map1.png) - -然后点击“复制地图”,即可将地图数据复制到剪切板。 - -!> **如果有识别不一致的存在,即生成的地图和实际的地图不符,我们可以在地图编辑器中粘贴,再可视化进行编辑。** - -!> **地图生成器默认只支持经典素材。如果有自定义素材需求(例如原版的1层小塔那种素材),请参见[自定义素材](personalization#自定义素材)。** - -!> **请确保截图范围刚好为13x13,并且保证每个位置的像素都是32x32。** - - -## 录入数据 - -有了地图后,我们下一步需要做的就是录入数据。数据主要包括如下几种: - -- 勇士初始的属性 -- 全局变量(宝石效果、全局Flag等) -- 怪物数据(每个怪物的攻防血金币经验等等) - -下面依次进行说明。 - -我们打开`data.js`文件,这里面定义了各种全局属性和勇士初始值。 - -!> V2.0版本可以直接在地图编辑器的`全塔属性`中进行修改! - -我们可以将本塔标题改名为“1层小塔”, - -游戏的唯一标识符叫onefloor,然后可以直接修改勇士的各项初始数据. - -!> **注:name作为游戏的唯一标识符必须进行修改,否则可能会导致存档等出现问题。** - -![初始数据](./img/init.png) - -!> **请注意,勇士的初始位置一栏,x为横坐标,y为纵坐标;即,x为从左到右第几列,y为从上到下第几行,均从0开始计算。** - -修改完初始化信息后,接下来我们需要修改道具的信息(比如宝石加攻防的数值,血瓶加生命的数值等)。还是在这个`data.js`文件,往下拉,找到values一项,并进行相应的设置 - -![修改数据](./img/moddata.png) - -然后,再设置一些系统Flag,以进行游戏。继续将`data.js`往下拉,我们注意到本塔是存在魔防的,不存在经验,因此我们可以简单地将enableMDef改为true,enableExperience改成false,enableDebuff改成false。 - -同理,本塔的破墙镐只能破面前的墙壁,因此`pickaxeFourDirections`需要改成`false`。 - -![系统标志](./img/flag.png) - -其他的几项暂时不会被涉及到,因此不用考虑。 - -全局变量修改完毕后,我们需要告诉主函数加载该楼层。打开`data.js`,找到`floorIds`项,将其值改为楼层ID即MT1。 - -最后一步就是录入怪物数据。打开`enemys.js`文件,依次输入你在本塔内使用到的所有怪物的攻防血的数据。其中怪物的特殊属性(special项)与该文件下面的getSpecialText对应。 - -!> V2.0版本可以直接在“图块属性”一栏进行修改怪物属性! - -![怪物数据](./img/enemyarray.png) - -只需要修改自己用到的怪物属性即可,其他没有用到的怪物完全无所谓。 - -做完后保存所有文件,在本地服务器中“启动游戏”,就能立刻看到自己的塔并开始游戏啦!是不是很简单呢! - -![保存](./img/save.png) - -## 压缩与发布 - -当你将上述步骤完成后,你实际上已经做出来了一个魔塔,并且可以发布给大家进行游戏了。 -目前而言发布有如下几种方式: - -- 离线版本:直接将游戏文件夹打包,放到百度网盘等地方供用户下载;用户下载到本地后直接使用浏览器打开`index.html`进行游戏。 - - 手机端的部分浏览器如chrome也支持本地网页,可以下载到手机然后直接打开进行游戏。 -- 在线版本:将游戏放到某个服务器上,大家在线联网游戏。 - -**离线版本的好处是:先全部下载后再游戏,无需考虑流量的问题,也可以支持高清音乐的播放。坏处是:没办法在多平台之间迁移,无法及时获得游戏更新(需要重新下载),而浏览器打开本地文件有丢失存档的风险。 -在线版本的好处是:随时随地可以玩,可以多平台接档,还可以在后台看到一些统计信息,可以随时对游戏进行更新;坏处是需要一个服务器,且还要考虑到用户流量的问题。** - -在此我们只讨论在线版本。当你决定发布游戏时,强烈建议先将JS代码进行压缩以节省可能的IO请求以及网络流量。直接打开同目录下的“JS代码压缩工具”进行压缩即可。 - -压缩完毕后,你还需要将`main.js`中的useCompress从false改为true,这样方才只会加载压缩后的文件。 - -如果你需要发布到服务器上且你没有服务器,可以直接将本塔发给我(`艾之葵`),我会给负责你部署上去的。我的服务器至少能保证两年内有效。 - -然后就是发布帖子、链接二维码,能让任何人在任何时候任何平台上都能进行游戏啦!是不是很简单呢! - -## 注意事项和常见FAQ - -1. 截图请务必刚好截取13x13的图片,并需要保证每个位置必须为32x32像素。一般无放缩的RMXP符合条件。 -2. 游戏的唯一标识符name请务必修改。如果不修改可能会导致存档出现异常。 -3. 别忘了data.js中要修改floorIds指明所用到的所有楼层哦~ - -下面是几个常见的FAQ: - -**Q: 为什么截图识别不出来?** - -**A:** 请保证刚好为13x13,且每个位置必须32x32像素。如果不确定,可以保存你的截图,右键属性查看详细信息,看像素的宽高是不是在416左右。多少几十像素都是没关系的。 - -![图片大小](./img/imginfo.png) - -**Q: 打开游戏时卡死在了xxx.js加载完毕!无法进入游戏。** - -**A:** 最大的可能是因为少了逗号,或者反括号等等。一般而言VSCode都会有错误提示,你哪里少了东西。 - -如果没有,可以采用如下方式debug: - -Ctrl+Shift+I 打开Chrome的控制台,找到Console。 - -如果出现了语法错误,会有红色提示 **Unexpected xxx** ,找到后面文件名和行号,打开,使用VSCode检查该处是否存在问题,即可。 - -![检查错误](./img/chrome.png) - -========================================================================================== - -[继续阅读下一章:元件说明](element) \ No newline at end of file diff --git a/docs/element.md b/docs/element.md deleted file mode 100644 index b5496f4a..00000000 --- a/docs/element.md +++ /dev/null @@ -1,322 +0,0 @@ -# 元件说明 - -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * - -在本章中,将对样板里的各个元件进行说明。各个元件主要包括道具、门、怪物、楼梯等等。 - -请打开样板0层 `sample0.js` 进行参照对比。 - -![生成地图](./img/sample0.png) - -## 道具 - -本塔目前支持的所有道具列表在样板0层中已全部给出。当你在样板0层中拿到某个宝物时会有提示,这里不再赘述,详见拿到该道具的说明。 - -大多数宝物都有默认的效果,屠龙匕首暂未定义,如有自己的需求可参见[自定义道具效果](personalization#自定义道具效果)。 - -拿到道具后将触发`afterGetItem`事件,有关事件的详细介绍请参见[事件](event)。 - -如需修改某个道具的效果,在不同区域宝石数据发生变化等问题,请参见[自定义道具效果](personalization#自定义道具效果)的说明。 - -**有关轻按,在data.js的系统变量中有定义。如果`enableGentleClick`为true,则鼠标(触摸屏)通过双击勇士,键盘通过空格可达到轻按效果,即不向前移动而获得前方物品。** - -## 装备 - -如果需要让剑盾等变成装备,可以直接在`data.js`中设置`'equipment': true`即可。 - -有关装备更为详细的资料可参见[自定义装备](personalization#自定义装备)的说明。 - -## 门 - -本塔支持6种门,黄蓝红绿铁花。前五种门需要有对应的钥匙打开,花门只能通过调用`openDoor`事件进行打开。 - -开门后可触发该层的`afterOpenDoor`事件,有关事件的详细介绍请参见第四章。 - -## 暗墙 - -本塔支持暗墙。 - -要制作一个暗墙非常简单:在该点直接放一个普通墙壁,然后事件写“开门”,坐标为该点就行。 - -``` js -// 该点画一个普通的墙壁,比如`yellowWall` - -// 在该点的事件events中: -"x,y": [ - {"type": "openDoor", "loc": [x,y]} // 直接使用开门事件,坐标需写当前点坐标。 -] -``` - -系统会自动调用animates中的开暗墙动画。 - -目前只有如下ID支持以这种方式开门: - -``` text -yellowDoor, blueDoor, redDoor, greenDoor, specialDoor, steelDoor, -yellowWall, blueWall, whiteWall, lava, star -``` - -## 怪物 - -本塔支持的怪物列表参见`project/enemys.js`。其与images目录下的`enemys.png`素材按顺序一一对应。 - -如有自己的怪物素材需求请参见[自定义素材](personalization#自定义素材)的内容。 - -怪物可以有特殊属性,每个怪物可以有多个自定义属性。 - -怪物的特殊属性所对应的数字(special)在`libs/enemys.js`中的`getSpecialText`中定义,请勿对已有的属性进行修改。 - -``` js -enemys.prototype.getSpecialText = function (enemyId) { - if (enemyId == undefined) return ""; - var enemy = this.enemys[enemyId]; - var special = enemy.special; - var text = []; - if (this.hasSpecial(special, 1)) text.push("先攻"); - if (this.hasSpecial(special, 2)) text.push("魔攻"); - if (this.hasSpecial(special, 3)) text.push("坚固"); - if (this.hasSpecial(special, 4)) text.push("2连击"); - if (this.hasSpecial(special, 5)) text.push("3连击"); - if (this.hasSpecial(special, 6)) text.push((enemy.n||4)+"连击"); - if (this.hasSpecial(special, 7)) text.push("破甲"); - if (this.hasSpecial(special, 8)) text.push("反击"); - if (this.hasSpecial(special, 9)) text.push("净化"); - if (this.hasSpecial(special, 10)) text.push("模仿"); - if (this.hasSpecial(special, 11)) text.push("吸血"); - if (this.hasSpecial(special, 12)) text.push("中毒"); - if (this.hasSpecial(special, 13)) text.push("衰弱"); - if (this.hasSpecial(special, 14)) text.push("诅咒"); - if (this.hasSpecial(special, 15)) text.push("领域"); - if (this.hasSpecial(special, 16)) text.push("夹击"); - if (this.hasSpecial(special, 17)) text.push("仇恨"); - if (this.hasSpecial(special, 18)) text.push("阻击"); - if (this.hasSpecial(special, 19)) text.push("自爆"); - if (this.hasSpecial(special, 20)) text.push("无敌"); - if (this.hasSpecial(special, 21)) text.push("退化"); - if (this.hasSpecial(special, 22)) text.push("固伤"); - if (this.hasSpecial(special, 23)) text.push("重生"); - return text.join(" "); -} -``` - -多属性可采用数组的写法,比如`'special': [1,3]`视为同时拥有先攻和坚固属性;`'special': [5,10,14,18]`视为拥有3连击、魔防、诅咒、阻击四个属性。 - -本塔支持战斗动画,在`data.js`中存在三个全局选项:`canOpenBattleAnimate`, `showBattleAnimateConfirm`, `battleAnimate`。 - -- `canOpenBattleAnimate`代表是否允许用户开启战斗动画。如果你添加了一些自定义属性,且不想修改战斗界面的UI,则可以将其关闭。 -- `showBattleAnimateConfirm`代表是否在游戏开始时给用户提供开启动画的选项。对于一些偏向于萌新的塔,可以开启此项。 -- `battleAnimate`代表是否默认开启战斗动画。此项会被用户存储的设置给覆盖。 -- 如果`canOpenBattleAnimate`为false,则后面两个也强制为false。 - -怪物可以负伤,在`data.js`的全局变量`enableNegativeDamage`中指定。 - -下面的`getSpecialHint`函数则给定了每个特殊属性的详细描述。这个描述将在怪物手册中看到。 - -打败怪物后可以进行加点操作。有关加点塔的制作可参见[加点事件](event#加点事件)。 - -如果`data.js`中的enableExperience为false,即不启用经验的话,怪物手册里将不显示怪物的经验值,打败怪物也不获得任何经验。 - -拿到幸运金币后,打怪获得的金币将翻倍。 - -如果怪物有`"notBomb": true`,则该系列诖怪物均不可被炸。 - -N连击怪物的special是6,且我们可以为它定义n代表实际连击数。参见样板中剑王的写法。 - -吸血怪需要给怪物设置value,代表吸血的比例。 - -可以给吸血怪添加`'add': true`来将吸血的数值加到自身上。 - -中毒怪让勇士中毒后,每步扣减的生命值由`data.js`中的values定义。 - -衰弱怪让勇士衰弱后,攻防会下降一定比例或固定数值(直到衰弱状态解除恢复);其在`data.js`中的values定义。 - -诅咒怪将让勇士陷入诅咒状态,诅咒状态下杀怪不获得金币和经验值。 - -领域怪需要在怪物后添加value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 - -领域是十字伤害还是九宫格伤害由`zoneSquare`设定,如设置为true则为九宫格伤害,不指定或为false则为十字伤害。 - -领域怪还可以设置`range`选项代表该领域怪的范围,不写则默认为1。 - -阻击怪同样需要设置value,代表领域伤害的数值。如果勇士生命值扣减到0,则直接死亡触发lose事件。 - -!> 阻击怪后退的地点不能有任何事件存在,即使是已经被禁用的自定义事件! - -请注意如果吸血、领域、阻击中任何两个同时存在,则value会冲突。**因此请勿将吸血、领域或阻击放置在同一个怪物身上。** - -退化怪需要设置'atkValue'和'defValue'表示退化的数值;也可以不设置默认为0。 - -固伤怪则需要设置`damage`选项,代表战前扣血数值。 - -如有额外需求,可参见[自定义怪物属性](personalization#自定义自定义怪物属性),里面讲了如何设置一个新的怪物属性。 - -## 路障,楼梯,传送门 - -血网的伤害数值、中毒后每步伤害数值、衰弱时暂时攻防下降的数值,都在 `data.js` 的values内定义。 - -路障同样会尽量被自动寻路绕过。 - -有关楼梯和传送门,必须在该层样板的changeFloor里指定传送点的目标。 - -![楼层转换](./img/changefloor.png) - -!> **请注意这里的`"x,y"`代表该点的横坐标为x,纵坐标为y;即从左到右第x列,从上到下的第y行(从0开始计算)。如(6,0)代表最上面一行的正中间一列。** - -floorId指定的是目标楼层的唯一标识符(ID)。 - -也可以写`"floorId": ":before"`和`"floorId": ":next"`表示上一楼和下一楼。 - -后面可以写stair到upFloor或downFloor,表示将前往目标楼层的上楼梯/下楼梯位置。你也可以写loc然后指定目标点的坐标。 - -请注意的是,如果目标楼层有多个楼梯,写stair可能会导致到达的楼梯不确定,这时候请使用loc方式来指定具体的点位置。 - -可以指定direction为up/left/right/down,指定后勇士将面向该方向。 - -可以指定time,指定后切换动画时长为指定的数值。 - -**从2.1.1开始,楼层属性中提供了`upFloor`和`downFloor`两项。如果设置此项(比如`"upFloor": [2,3]`),则写stair:upFloor或者楼传器的落点将用此点来替换楼梯位置(即类似于RM中的上箭头)。** - -## 动画和天气系统 - -现在我们的H5魔塔支持播放动画,也支持天气系统了。 - -要播放动画,你需要先使用“RM动画导出器”将动画导出,放在animates目录下,然后再data.js中定义。 - -``` js -"animates": [// 在此存放所有可能使用的动画,必须是animate格式,在这里不写后缀名 - // 动画必须放在animates目录下;文件名不能使用中文,不能带空格或特殊字符 - "hand", "sword", "zone", "yongchang", "thunder" // 根据需求自行添加 -] -``` - -!> 动画必须是animate格式,名称不能使用中文,不能带空格或特殊字符。 - -导出动画时可能会进行一些压缩以节省流量,因此清晰度可能不如原版。 - -从2.3.2开始,动画可以同时导出所用的音效。**如果导出音效,请确保将所用到的音效复制到了`sounds`目录下,并且在全塔属性中注册过。** - -动画播放时,是按照每秒20帧的速度(即50ms/帧)。 - -定义完毕后,我们可以调用`animate`事件来播放该动画,有关事件的详细介绍请参见[事件](event)。 - -!> 播放录像时,将默认忽略所有动画。 - -目前天气系统只支持雨和雪两种天气。 - -在每层楼的剧本文件里存在一个weather选项,表示该层楼的默认天气。 - -``` js -// 该层的默认天气。本项可忽略表示晴天,如果写则第一项为"rain"或"snow"代表雨雪,第二项为1-10之间的数代表强度。 -"weather": ["snow",5] -``` - -我们也可以使用`setWeather`事件来设置当前天气,有关事件的详细介绍请参见[事件](event)。 - -## 背景音乐 - -本塔支持BGM和SE的播放。 - -要播放音乐和音效,你需要将对应的文件放在sounds目录下,然后在全塔属性中进行定义 - -``` js -"bgms": [ // 在此存放所有的bgm,和文件名一致。第一项为默认播放项 - // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 - 'bgm.mp3' -]; -"sounds": [ // 在此存放所有的SE,和文件名一致 - // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 - 'floor.mp3', 'attack.mp3', 'door.mp3', 'item.mp3', 'zone.mp3' -] -``` - -!> 音频名不能使用中文,不能带空格或特殊字符。 - -目前BGM支持主流的音乐格式,如mp3, ogg, mid格式等。SE则不支持mid格式的播放。 - -!> mid格式是通过数学方法模拟出来的音乐效果,质量可能会和实际效果差距较大。 - -!> **警告!** mid格式在手机端播放可能会特别卡,仍推荐直接使用mp3/ogg来播放。 - -定义完毕后,我们可以调用`playBgm`/`playSound`事件来播放对应的音乐/音效,有关事件的详细介绍请参见[事件](event)。 - -**另外,考虑到用户的流量问题,将遵循如下规则:** - -- **如果用户当前使用的电脑,则默认开启音乐效果,并播放默认BGM** -- **如果用户当前使用的手机,且处于Wifi状态,则默认开启音乐效果,并播放默认BGM** -- **其他情况,将默认关闭音乐效果,只有在用户在菜单栏中点击“音乐开关”后才会播放音乐** - -!> iOS平台以及部分浏览器不支持获得当前网络状态,此时即使在使用Wifi也必须要用户点击“音乐开关”才能播放音乐。 - -## 录像 - -HTML5魔塔一大亮点就是存在录像系统,可以很方便进行录像回放。 - -当你在游戏的过程中,随着你的操作,录像也会被依次记录。游戏结束后将提示是否下载录像,上传成绩时也会上传你的录像信息。 - -在菜单栏-同步存档中,可以直接对当前录像进行下载。 - -!> 录像记录的是你当前的路线(本质上是模拟键盘操作),是一个纯文本文件,占用空间很小! - -录像的回放主要有两种方式: - -1. 保存成的录像文件(.h5route文件):在标题界面点录像回放,再选择文件即可。 -2. 游戏过程中时的当前录像:随时按R可以进行回放;手机端则长按任何位置3秒以上调出虚拟键盘,再按R。 - -录像播放过程中,可以进行如下操作: - -- **暂停/播放:** 按空格可以随时暂停或播放录像。 -- **加速:** 按X可以加速录像播放,最高可达6倍速。 -- **减速:** 按Z可以减速录像播放,最低可达0.3倍速。 -- **停止:** 按ESC可以立刻停止录像播放,并返回正常游戏。 -- **回退:** 按A可以回退到上一个录像节点(录像播放过程中每50步存一个录像节点)。 -- **存档:** 按S可以在录像播放过程中进行存档。 -- **查看手册:** 按C可以在录像播放过程中查看怪物手册。 - -上述操作在手机端均有工具栏的对应按钮可点击操作。 - -如果录像出现问题,请加群539113091找小艾反馈Bug。 - -## 操作说明 - -本塔主要支持鼠标(触摸屏)操作和键盘操作。 - -鼠标(触摸屏)操作说明如下: - -- **点状态栏中图标:** 进行对应的操作 -- **点任意块:** 寻路并移动 -- **点任意块并拖动:** 指定寻路路线 -- **单击勇士:** 转向 -- **双击勇士:** 轻按(仅在轻按开关打开时有效) -- **长按任意位置:** 打开虚拟键盘 - -键盘操作快捷键如下: - -- **[CTRL]** 跳过对话 -- **[Z]** 转向 -- **[X]** 打开/关闭怪物手册 -- **[G]** 打开/关闭楼层传送器 -- **[A]** 读取自动存档 -- **[S/D]** 打开/关闭存/读档页面 -- **[K]** 打开/关闭快捷商店选择列表 -- **[T]** 打开/关闭工具栏 -- **[ESC]** 打开/关闭系统菜单 -- **[H]** 打开帮助页面 -- **[R]** 回放录像 -- **[SPACE]** 轻按(仅在轻按开关打开时有效) -- **[1]** 快捷使用破墙镐 -- **[2]** 快捷使用炸弹/圣锤 -- **[3]** 快捷使用中心对称飞行器 - -以上快捷键也能在游戏菜单中的操作说明中看到。 - -  - -  - -上面就是整个样板中的各个元件说明。通过这种方式,你就已经可以做出一部没有任何事件的塔了。 - -尝试着做一个两到三层的塔吧! - -========================================================================================== - -[继续阅读下一章:事件](event) diff --git a/docs/img/plugin.png b/docs/img/plugin.png deleted file mode 100644 index 4e87fa84..00000000 Binary files a/docs/img/plugin.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html index cb5df153..a1ea8b98 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,58 +1 @@ - - - - - HTML5魔塔样板 - - - - - - - - - - -
- - - - + \ No newline at end of file diff --git a/docs/personalization.md b/docs/personalization.md deleted file mode 100644 index 0c2a6075..00000000 --- a/docs/personalization.md +++ /dev/null @@ -1,794 +0,0 @@ -# 个性化 - -?> 目前版本**v2.3.3**,上次更新时间:* {docsify-updated} * - -有时候只靠样板本身可能是不够的。我们需要一些个性化、自定义的素材,道具效果,怪物属性,等等。 - -## 图层的说明 - -HTML5魔塔是使用画布(canvas)来绘制,存在若干个图层,它们之间有一个覆盖关系,后面的图层将覆盖前面的图层。 - -所有图层从低往高依次如下: - -- bg:背景层;绘制地面素材,或者作为背景的图片素材 -- event:事件层;所有事件(道具、墙壁、NPC、怪物等)都绘制在这一层进行处理 -- hero:勇士层;主要用来绘制勇士 -- event2:事件2层;本层主要用来绘制48x32的图片素材的上半部分(避免和勇士错位),也可以用来绘制该层的前景图片素材 -- fg:显伤层;主要用来绘制怪物显伤和领域显伤 -- animate:动画层;主要用来绘制动画,图块的淡入/淡出效果,图块的移动。showImage事件绘制的图片也是在这一层。 -- weather:天气层;主要用来绘制天气(雨/雪) -- curtain:色调层;用来控制当前楼层的画面色调 -- route:路线层;主要用来绘制勇士的行走路线图 -- ui:UI层;用来绘制一切UI窗口,如剧情文本、怪物手册、楼传器、系统菜单等等 -- data:数据层;用来绘制一些顶层的或更新比较快的数据,如左上角的提示,战斗界面中数据的变化等等。 - -## 自定义素材 - -所有素材的图片都在`images`目录下。 -- `animates.png` 为所有动画效果。主要是星空熔岩,开门,毒网,传送门之类的效果。为四帧。 -- `autotile.png` 为Autotile块。 -- `enemys.png` 为所有怪物的图片。 -- `enemy48.png` 为所有48x32怪物的图片。 -- `heros.png` 为勇士行走图。 -- `items.png` 为所有道具的图标。 -- `npcs.png` 为所有NPC的图标。 -- `npc48.png` 为所有48x32的NPC图标。 -- `terrains.png` 为所有地形的图标。 - -系统会读取`icon.js`文件,并获取每个ID对应的图标所在的位置。 - -### 使用预定义的素材 - -在images目录的“默认素材”下给定了若干预定义的自定义素材。 - -如果你需要某个素材已经存在,则可以直接将其覆盖images目录下的同名文件,就能看到效果。 - -### 使用自己的图片作为某层楼的背景/前景素材 - -由于HTML5功能(素材)有限,导致了对很多比较复杂的素材(比如房子内)等无法有着较好的绘图方式。 - -为了解决这个问题,我们允许用户自己放置一张或多张图片作为某一层的背景素材。 - -要启用这个功能,我们首先需要在`data.js`中将可能的图片进行加载。 - -``` js -"images": [ // 在此存放所有可能使用的图片 - // 图片可以被作为背景图(的一部分),也可以直接用自定义事件进行显示。 - // 图片名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 - // 建议对于较大的图片,在网上使用在线的“图片压缩工具(http://compresspng.com/zh/)”来进行压缩,以节省流量 - "bg.jpg", "house.png", "bed.png"// 依次向后添加 -]; -``` - -!> 请使用网上的一些[在线图片压缩工具](http://compresspng.com/zh/)对图片进行压缩,以节省流量。 - -之后,我们可以在每层剧本的`"images"`里来定义该层的默认背景图片素材。 - -``` js -"images": [[3,5,"bg.jpg",0]], // 背景图;你可以选择一张或多张图片来作为背景/前景素材。 -"images": [], // 无任何背景图 -"images": [[1,1,"house.png",0], [6,7,"bed.png",1]] // 在(1,1)放一个house.png在背景层,且(6,7)放bed.png在前景层 -"images": [[3,5,"tree.png",2]] // 如果写2,则会自动调节遮挡效果 -``` - -images为一个数组,代表当前层所有作为背景素材的图片信息。 - -每一项为一个四元组,分别为该背景素材的x,y,图片名和遮挡方式。其中x和y分别为横纵坐标,在0-12之间;图片名则必须在上面的images中定义过。 - -第四项为遮挡方式,定义如下: - -- 0:该图片将全部画在背景层,被勇士所遮挡。举例:某些特殊地形等。 -- 1:该图片将全部画在前景层,可以遮挡勇士。举例:云彩等效果。 -- 2:该图片将上部分画在前景层,下部分画在背景层。从而可以达到一个“自动调节遮挡的效果”。举例:树、房子等等。 - -!> 如果写2的话,请确保图片高度是32的倍数! - -**如果你需要让某些点不可通行(比如你建了个房子,墙壁和家具等位置不让通行),则需在`events`中指定`{"noPass": false}`,参见[自定义事件](event#自定义事件)的写法。** - -``` js -"events": { - "x,y": {"noPass": true} // (x,y)点不可通行 -} -``` - -### 使用便捷PS工具生成素材 - -如果我们有更多的素材要求,我们可以使用“便捷PS工具”进行处理。 - -![便捷PS工具](img/ps.png) - -我们可以打开有需求改变的素材,和我们需要被替换的素材,然后简单的Ctrl+C和Ctrl+V操作即可。 - -便捷PS工具同样支持图片色相的修改,和RMXP几乎完全相同。 - -用这种方式,我们能极快地替换或素材,包括需要新增的怪物。 - -### 添加素材到游戏 - -在使用地图编辑器编辑的过程中,我们有可能会出现“该数字和ID未被定义”的错误提示。 - -这是因为,该素材没有被定义,无法被游戏所识别。 - -!> 在V2.0中,我们可以简单的在地图编辑器中新增素材,以及定义新增素材的ID和数字,但是仍然**强烈建议**对素材的机制进行了解。 - -#### 素材的机制 - -本塔所有的素材都拥有三个属性:**ID**,**索引**,**数字**。 -- **ID** 为该素材的唯一标识符,任何两个素材的ID都不能相同。 -- **索引** 为该素材的在对应图片上的图标索引,即该素材是图片上的第几个。 -- **数字** 为该素材的对应数字,以方便地图的生成和存储。 - -**`ID-索引` 对应关系定义在icons.js文件中。该文件将唯一确定一个ID在图片上所在的位置。** - -**`ID-数字` 对应关系定义在maps.js文件中。该文件将唯一确定一个ID对应的数字是多少。** - -在V2.0中,我们可以在地图编辑器中很方便查看每个图块的三个属性信息。 - -#### 注册素材 - -在V2.0的地图编辑器中,要注册新素材,我们只需要在图块属性一栏输入新素材的ID和数字。 - -![素材注册](./img/register.png) - -ID必须由数字字母下划线组成,数字在1000以内,且均不能和已有的进行重复。 - -之后刷新编辑器即可。 - -对于怪物和道具,我们也可以进行自动注册,只需要点击“自动注册”按钮,将对该栏下所有未注册的素材进行自动注册(自动分配ID和数字)。 - -素材注册完毕后,即可在游戏中正常使用,也可以被地图生成器所识别(需要重开地图生成器)。 - -#### Autotile的注册 - -但是,通过上面这种方式,我们是没办法新增并注册Autotile的。 - -除了替换样板现有的几个外,如果我们还需要新添加Autotile,则: - -1. 将新的Autotile图片复制到images目录下。文件名必须是字母数字和下划线组成。 -2. 进入icons.js,在autotile分类下进行添加该文件的名称,索引简单的写0。 -3. 指定一个数字,在maps.js中类似进行添加。 - -!> Autotile的ID和文件名应确保完全相同! - - - -### 地图生成器使用自定义素材 - -地图生成器是直接从js文件中读取数字-图标对应关系的。 - -因此,在你修改了icons.js和maps.js两个文件,也就是将素材添加到游戏后,地图生成器的对应关系也将同步更新。 - -## 自定义道具效果 - -本节中将继续介绍如何自己编辑一个道具的效果。 - -道具效果的具体实现都在`items.js`中。 - -### 即捡即用类道具(cls: items) - -对于即捡即用类道具,如宝石、血瓶、剑盾等,我们可以简单地修改`data.js`中的value一栏即可。 - -如果你想要同种宝石在不同层效果不同的话,可以进行如下操作: - -1. 在楼层的item_ratio中定义宝石的比率(比如1-10的写1,11-20层写2等) -2. 修改获得道具的itemEffect函数(编辑器中双击进行编辑) - -``` js -// ratio为楼层的item_ratio值,可以进行翻倍宝石属性 -core.status.hero.atk += core.values.redJewel * ratio -``` - -这里我们可以直接写ratio来取用该楼层中定义的`item_ratio`的值。 - -如果不是倍数增加(比如线性增加)也可以类似来写 - -``` js -// 一个二倍线性增加的例子 -core.status.hero.atk += core.values.redJewel + 2*ratio -``` - -### 消耗类道具(cls: tools);永久类道具(cls: constants) - -如果要自己实现消耗类道具或永久类道具的使用效果,则需修改`items.js`中的canUseItem和useItem两个函数。 - -具体过程比较复杂,需要一定的JS能力,在这里就不多说了,有需求可以找`艾之葵`进行了解。 - -但值得一提的是,我们可以使用`core.hasItem(name)` 来判断是否某个道具是否存在。例如下面是passNet(通过路障处理)的一部分: - -``` js -/****** 经过路障 ******/ -events.prototype.passNet = function (data) { - // 有鞋子 - if (core.hasItem('shoes')) return; - if (data.event.id=='lavaNet') { // 血网 -// ... 下略 -``` - -我们进行了一个简单的判断,如果拥有绿鞋,则不进行任何路障的处理。 - -### 实战!拿到神圣盾后免疫吸血、领域、夹击效果 - -1. 在itemEffect中修改拿到神圣盾时的效果,标记一个自定义Flag。 -``` js -core.status.hero.def += core.values.shield5 * ratio; -core.setFlag("shield5", true); // 增加一个自定义Flag:已经拿到神圣盾 -``` -2. 免疫吸血效果:在`enemys.js`的伤害计算中,编辑成如果存在神圣盾标记,吸血伤害为0。 -``` js -enemys.prototype.calDamage = function (monster, hero_hp, hero_atk, hero_def, hero_mdef) { -// ... 上略 - // 吸血 - if (this.hasSpecial(mon_special, 11)) { - var vampireDamage = hero_hp * monster.value; - - // 如果有神圣盾免疫吸血等可以在这里写 - if (core.hasFlag("shield5")) vampireDamage = 0; // 存在神圣盾,吸血伤害为0 - - vampireDamage = Math.floor(vampireDamage) || 0; - // 加到自身 - if (monster.add) // 如果加到自身 - mon_hp += vampireDamage; - - initDamage += vampireDamage; - } -// ... 下略 -``` -3. 免疫领域、夹击、阻击效果:在`control.js`中,找到checkBlock函数,并编辑成如果有神圣盾标记,则将伤害变成0。 -``` js -// 检查领域、夹击、阻击事件 -control.prototype.checkBlock = function () { - var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); - var damage = core.status.checkBlock.damage[x+core.bigmap.width*y]; - if (damage>0) { - if (core.hasFlag("shield5")) damage = 0; // 如果存在神圣盾,则将伤害变成0 - core.status.hero.hp -= damage; - - // 检查阻击事件 - var snipe = []; - var scan = { - 'up': {'x': 0, 'y': -1}, - 'left': {'x': -1, 'y': 0}, - 'down': {'x': 0, 'y': 1}, - 'right': {'x': 1, 'y': 0} - } -// ... 下略 -``` -4. 如果有更高的需求,例如想让吸血效果变成一半,则还是在上面这些地方进行对应的修改即可。 - -## 新增门和对应的钥匙 - -如果要新增一个门和对应的钥匙,需要进行如下几步: - -1. 在terrains.png中添加新的门的素材,并在地图编辑器中注册门的ID。该ID必须是以`Door`结尾,例如`abcDoor`。 -2. 在animates.png中添加开门的四格动画,然后直接打开icons.js文件,在animates下直接添加ID和索引信息,例如`'abcDoor': 34`。 -3. 在items.png中添加钥匙的素材,并在地图编辑器中注册钥匙的ID。该ID必须是和门对应且以`Key`结尾,例如`abcKey`。 -4. 该道具的cls应为`tools`,可以自行写道具描述,最下面几项均留`null`即可。 - -!> **请勿在animates中对门的动画素材进行注册!而是请直接打开icons.js文件并添加ID和索引信息!!!** - -!> terrains和animates的门ID必须完全一致,且以`Door`结尾;所对应的钥匙ID应当是把`Door`换成`Key`,这样才能对应的上! - -## 覆盖楼传事件 - -对于特殊的塔,我们可以考虑修改楼传事件来完成一些特殊的要求,比如镜子可以按楼传来切换表里。 - -要修改楼传事件,需要进行如下几步: - -1. 截获楼传的点击事件。在control.js中找到useFly函数,并将其替换成如下内容: -``` js -////// 点击楼层传送器时的打开操作 ////// -control.prototype.useFly = function (need) { - if (!core.status.heroStop) { - core.drawTip("请先停止勇士行动"); - return; - } - if (core.canUseItem('fly')) core.useItem('fly'); - else core.drawTip("当前无法使用"+core.material.items.fly.name); -} -``` -2. 让录像记下楼传的使用。在items.js的useItem函数中找到记录路线的那几行,修改为: -``` js - // 记录路线 - if (itemId!='book') { // 把 `&& itemId!='fly'` 给删除 - core.status.route.push("item:"+itemId); - } -``` -3. 修改楼传的使用事件。和其他永久道具一样,在地图编辑器的图块属性中修改楼传的useItemEffect和canUseItemEffect两个内容。例如: -``` js -"useItemEffect": "core.insertAction([...])" // 执行某段自定义事件,或者其他脚本 -"canUseItemEffect": "true" // 任何时候可用 -``` -修改时,请先把`null`改成空字符串`""`,然后再双击进行编辑。 - - -## 自定义装备 - -由于HTML5魔塔并不像RM那样存在一个装备界面可供我们对装备进行调整,但是我们也可以使用一个替代的方式实现这个目标。 - -### 装备的实现原理 - -在HTML5中,装备将全部看成是永久道具(constants),同时对于每个装备位置,勇士都定义一个flag域表示正在装备的内容。 - -例如:当勇士获得银剑时,将获得一个永久道具“银剑”;当使用这个银剑,首先检查勇士当前是否装备了武器(比如铁剑),如果 -装备了则先脱掉装备(减去已装备的铁剑所加的属性,并获得永久道具铁剑),然后再穿上新的银剑。 - -同时由于脱剑术和脱盾术的存在,我们还需要一个“脱掉装备”的永久道具(比如sword0),使用它可以脱掉对应位置的装备。 - -### 装备加值的修改 - -要启用装备,首先需要在data.js(全塔属性)中设置`'equipment': true`。此时,游戏内将自动将剑盾变成装备的存在,其 -所加的数值就是全塔属性中对应的数值(比如铁剑sword1就加的全塔属性中sword1的值)。 - -有时候,我们还会有一个装备加多种属性的需求,此时需要将对应的项从数值改变成一个对象。 - -``` js -"sword1": {"atk": 10, "def": 0, "mdef": 5}, // 铁剑加10攻和5魔防 -"shield1": {"atk": 0, "def": 10, "mdef": 10}, // 铁盾加10防和10魔防 -``` - -通过这种方式,当穿上装备时,将会给你的三围分别加上对应项的数值(支持负数,比如装剑减防御)。 - -### 新增剑盾 - -样板默认提供了铁剑(盾),银剑(盾),骑士剑(盾),圣剑(盾)和神圣剑(盾)这五类装备。但有时候,五类是不够的, -我们可能还需要更多的剑盾。 - -要增加更多的剑盾,我们需要进行如下步骤:(以新增剑为例) - -1. 新注册一个素材到游戏;**其ID必须是sword+数字的形式,比如sword6等。**(同理如果是盾就必须是shield+数字的形式比如shield6。) -2. 将其cls设置为`constants`,`useItemEffect`和`canUseItem`直接复制其他几个剑盾的内容。 -(即:`useItemEffect`必须为`"core.plugin.useEquipment(itemId)"`,`canUseItemEffect`必须为`"true"`)。 -3. 使用VSCode或其他文本编辑器直接打开`data.js`文件,并在`"values"`中仿照已有的内容添加剑盾的属性。 -(例如:`"sword6": 100`,或者`"sword6": `{"atk": 100, "def": 0, "mdef": 50}`) - -!> 请注意:新的剑的ID必须是sword+数字,新的盾的ID必须是shield+数字的形式,不然装备将无效! - -### 新增其他部位的装备 - -如果我们还需要新增更多部位的装备,比如戒指、首饰等等,也是可以的,只需要依次进行如下步骤:(以新建戒指为例) - -1. 选择一个装备的部位的ID;比如假设我们的装备部位为戒指,那么我们可以选择"ring"作为装备的部位。 -2. 定义一个空戒指,相当于脱掉戒指。注册一个素材到游戏,**其ID必须是ring0**(即部位ID+数字0)。 -3. 同上述新建剑盾的方式来设置ring0这个空戒指的属性。请注意打开data.js后,设置的其值必须为0。 -(即:必须是`"ring0": 0`) -3. 创建更多的戒指;每加一个新的戒指到游戏,其ID必须是**ring+数字**的形式,比如`ring1`, `ring2`,等等。 -4. 对于每一个创建的戒指,按照上述新建剑盾的方式设置属性(图块属性,打开data.js直接编辑)。 -(例如:`"ring2": {"atk": 3, "def": 5, "mdef": 8}`) -5. **切换到脚本编辑 - 自定义插件编写,在`this.useEquipment`中仿照着新增一行代表戒指的使用。** - -``` js -this.useEquipment = function (itemId) { // 使用装备 - _useEquipment(itemId, "sword", "atk"); // 剑 - _useEquipment(itemId, "shield", "def"); // 盾 - _useEquipment(itemId, "ring", "mdef"); // 新增的戒指 -} -``` - -我们仿照着新增一行`_useEquipment(itemId, "ring", "mdef")`。 - -其中第二项为装备部位的ID,比如我们上述定义的是ring;第三项表示“如果values中该值为数字,则加到什么属性上”。 -比如如果我们这里写mdef,那么我们假设在data.js的values中设置`"ring1": 20`,则使用该戒指会增加20点魔防。 - -如果你定义的是对象`{"atk": xxx, "def": xxx, "mdef": xxx}`的形式,则不会受到第三项的影响。 - -!> 必须对于每一个装备部位定义唯一一个不同的ID;脱掉装备(空装备)必须是该ID加数字0的形式,其他有效装备必须是 -该ID+正整数的形式,不然会出错! - -## 自定义怪物属性 - -如果你对现有的怪物不满意,想自行添加怪物属性(例如让怪物拥有双属性乃至更多属性),也是可以的。具体参见`enemys.js`文件。 - -你需自己指定一个special数字,修改getSpecialText函数(属性名)和getSpecialHint函数(属性提示文字)。 - -如果要修改伤害计算公式,请修改下面的getDamageInfo函数。请注意,如果无法战斗,该函数必须返回`null`。 - -对于毒衰弱怪物的战斗后结算在`functions.js`中的afterBattle函数中。 - -对于领域、夹击、阻击怪物的检查在`control.js`中的checkBlock函数中。 - -`getCritical`, `getCriticalDamage`和`getDefDamage`三个函数依次计算的是该怪物的临界值、临界减伤和1防减伤。也可以适当进行修改。 - -## 自定义快捷键 - -如果需要绑定某个快捷键为处理一段事件,也是可行的。 - -要修改按键,我们可以在`actions.js`的`keyUp`进行处理: - -比如,我们设置一个快捷键进行绑定,比如`W`,其keycode是87。(有关每个键的keycode搜一下就能得到) - -然后在`actions.js`的`keyUp`函数的`switch`中进行处理。 - -``` js -case 87: // W - if (core.status.heroStop) { - // ... 在这里写你要执行脚本 - // 请使用同步脚本,请勿执行任何异步代码,否则可能导致游戏过程或录像出现问题。 - core.insertAction([...]) // 例如,插入一段自定义事件并执行。 - - core.status.route.push("key:"+keyCode); // 录像的支持!这句话必须要加,不然录像回放会出错! - } - break; -``` - - -在勇士处于停止的条件下,按下W键时,将执行你写的脚本代码。请只使用同步脚本而不要使用异步代码,不然可能导致游戏出现问题。 - -`core.status.route.push("key:"+keyCode);` 这句话是对录像的支持,一定要加(这样录像播放时也会模拟该按键)。 - -!> H5不支持组合快捷键,所以不存在`W+1`这种组合快捷键的说法! - -!> 手机端可以通过长按任何位置调出虚拟键盘,再进行按键,和键盘按键是等价的效果! - -## 公共事件 - -在RM中,存在公共事件的说法;也就是通过某个指令来调用一系列事件的触发。 - -在H5中,我们可以使用“插件”的形式来达成这个效果。具体参见“脚本编辑 - 插件编写”。 - -![插件编写](./img/plugin.png) - -当我们在这上面定义了自己需要的函数(插件后),就可以通过任何方式进行调用。 - -在这个插件编写的过程中,我们可以使用任何[常见API](api)里面的代码调用;也可以通过`core.insertAction`来插入自定义事件执行。 - -下面是一个很简单的例子,我编写一个公共事件(插件),其效果是让勇士生命值变成原来的x倍,并令面前的图块消失。 - -``` js -this.myfunc = function(x) { - core.status.hero.hp *= x; // 勇士生命翻若干倍 - core.insertAction([ // 自定义事件:令面前的图块消失。 - {"type": "setValue", "name": "flag:x", "value": "core.nextX()"}, - {"type": "setValue", "name": "flag:y", "value": "core.nextY()"}, - {"type": "hide", "loc": ["flag:x", "flag:y"]} - ]); -} -``` - -然后比如我们在某个道具的使用效果 `useItemEffect` 中写 `core.plugin.myfunc(2)` 即可调用此公共事件(插件)。也可以在战后事件或自定义脚本等位置来写。 - -通过这种,将脚本和自定义事件混用的方式,可以达到和RM中公共事件类似的效果,即一个调用触发一系列事件。 - -## 自定义状态栏(新增显示项) - -在V2.2以后,我们可以自定义状态栏背景图(全塔属性 - statusLeftBackground)等等。 - -但是,如果我们还想新增其他项目的显示,比如技能塔所需要的魔力值(气息),该怎么办? - -需要进行如下几个操作: - -1. 定义图标ID;比如魔力我就定义mana,气息可以简单的定义qixi;你也可以定义其他的ID,但是不能和已有的重复。这里以mana为例。 -2. 在index.html的statusBar中(44行起),进行该状态栏项的定义。仿照其他几项,插在其应当显示的位置,注意替换掉相应的ID。 -``` html -
- -

-
-``` -3. 在editor.html中的statusBar(305行起),仿照第二点同样添加;这一项如果不进行则会地图编辑器报错。 -4. 使用便捷PS工具,打开icons.png,新增一行并将魔力的图标P上去;记下其索引比如23(减速播放图标的下方)。 -5. 在main.js的this.statusBar中增加图片、图标和内容的定义。 -``` js -this.statusBar = { - 'images': { - // ...其他略 - 'mana': document.getElementById("img-mana"), // 图片的定义 - }, - 'icons': { - // ...其他略 - 'mana': 23, // 图标的定义,这里对应的是icons.png中的索引 - }, - // ...其他略 - 'mana': document.getElementById('mana'), // 显示内容(数据)的定义 -} -``` -6. 显示内容的设置。在control.js的updateStatusBar函数,可以对该状态栏显示内容进行设置,下面是几个例子。 -``` js -core.statusBar.mana.innerHTML = core.getFlag('mana', 0); // 设置其显示内容为flag:mana值。 -core.statusBar.mana.innerHTML = core.getFlag('mana', 0) + '/' + core.getFlag('manaMax', 0); // 显示内容将类似 "32/60" 这样。 -core.statusBar.mana.style.fontStyle = 'normal'; // 这一行会取消斜体。如果是汉字(比如技能名)的话,斜体起来会非常难看,可以通过这一句取消。 -``` -7. 在control.js的clearStatusBar函数,`statusList`里面也要增加mana项,这样清空状态栏时也会对其清空。 - -## 技能塔的支持 - -其实,在HTML5上制作技能塔是完全可行的。 - -要支持技能塔,可能需要如下几个方面: - -- 魔力(和上限)的定义添加 -- 状态栏的显示 -- 技能的触发(按键与录像问题) -- 技能的效果 - -下面依次进行描述。 - -### 魔力的定义添加 - -当我们定义了魔力的ID,比如`mana`后,要使用它,一般有两种方式:属性获取`status:mana`或者flag标记`flag:mana`。 - -如果要属性获取,则需要打开`data.js`文件,并在`hero`中添加定义。 - -通过这种方式定义的,可以通过`core.setStatus('mana', 0)`以及`core.getStatus('mana')`来设置或获取。 - -``` js -'hero': { - // ... 上略 - 'mana': 0, // 增添mana定义,可以放在experience之后。同理可定义manaMax表示当前最大魔力值。 -} -``` - -如果要flag标记,则无需额外在任何地方进行定义。只需要在设置或取用的时候使用 `core.setFlag('mana', 0)` 或 `core.getFlag('mana', 0)` 即可。 - -下面我都使用属性获取的方式来进行说明。 - -### 状态栏的显示 - -首先我们需要额外新增一个状态栏;请参见[自定义状态栏(新增显示项)](#自定义状态栏(新增显示项))。 - -我们可以在魔力那一行显示当前值和最大值: - -``` js -core.setStatus('mana', Math.min(core.getStatus('mana'), core.getStatus('manaMax'))); // 如果魔力存在上限,则不能超过其上限值 -core.statusBar.mana.innerHTML = core.getStatus('mana') + '/' + core.getStatus('manaMax', 0); // 显示比如 6/30 这样 -``` - -如果我们还需要显示当前使用的技能名,也是可以的;定义一个ID为skill,然后按照上面的做法新增一行。 - -请注意,如果是中文字符,需要取消斜体(不然会非常难看的)! - -``` js -core.statusBar.skill.style.fontStyle = 'normal'; // 取消斜体显示 -core.statusBar.skill.innerHTML = core.getFlag('skillName', '无'); // 使用flag:skillName表示当前激活的技能名。 -``` - -### 技能的触发 - -我们可以按键触发技能。有关绑定按键请参见[自定义快捷键](#自定义快捷键)。 - -下面是一个很简单的例子,当勇士按下W后,如果魔力不小于5点则允许开启技能"二倍斩",再次按W则关闭技能。 - -``` js -case 87: // W - if (core.status.heroStop) { // 当前停止状态;这个if需要加,不能在行走过程中触发不然容易出错。 - if (core.getFlag('skill', 0)==0) { // 判断当前是否已经开了技能 - if (core.getStatus('mana')>=5) { // 这里要写当前能否开技能的条件判断,比如魔力值至少要多少 - core.setFlag('skill', 1); // 开技能1 - core.setFlag('skillName', '二倍斩'); // 设置技能名 - } - else { - core.drawTip("魔力不足,无法开技能"); - } - } - else { // 关闭技能 - core.setFlag('skill', 0); // 关闭技能状态 - core.setFlag('skillName', '无'); - } - core.updateStatusBar(); // 立刻更新状态栏和地图显伤 - core.status.route.push("key:"+keyCode); // 录像的支持!这句话必须要加,不然录像回放会出错! - } - break; -``` - -简单的说,用flag:skill判断当前开启的技能,flag:skillName表示该技能名。(可在状态栏显示) - -在勇士处于停止的条件下,按下W键时,判断当前是否开启了技能,如果开启则关闭,没开则再判断是否允许开启(魔力值够不够等)。 - -`core.status.route.push("key:"+keyCode);` 这句话是对录像的支持,一定要加(这样录像播放时也会模拟该按键)。 - -!> 1,2,3这三个键被默认绑定到了破炸飞;如果想用的话也是一样,只不过是把已有的实现进行替换。 - -!> 手机端可以通过长按任何位置调出虚拟键盘,再进行按键,和键盘按键是等价的效果! - -### 技能的效果 - -最后一点就是技能的效果;其实到了这里就和RM差不多了。 - -技能的效果要分的话有地图类技能,战斗效果类技能,后续影响类技能什么的,这里只介绍最简单的战斗效果类技能。 -其他的几类技能根据需求可能更为麻烦,有兴趣可自行进行研究。 - -战斗效果内技能要改两个地方:战斗伤害计算,战后扣除魔力值。 - -战斗伤害计算在`enenmys.js`的`getDamageInfo`函数,有需求直接修改这个函数即可。 - -战后扣除魔力值则在脚本编辑的`afterBattle`中进行编辑即可。 - -举个例子,我设置一个勇士的技能:二倍斩,开启技能消耗5点魔力,下一场战斗攻击力翻倍。 - -那么,直接在`getDamageInfo`中进行判断: - -``` js -if (core.getFlag('skill', 0)==1) { // 开启了技能1 - hero_atk *= 2; // 计算时攻击力翻倍 -} -``` - -然后在脚本编辑的战后事件中进行魔力值的扣除: - -``` js -if (core.getFlag('skill', 0)==1) { // 开启了技能1 - core.status.hero.mana -= 5; // 扣除5点魔力值 - core.setFlag('skill', 0); // 自动关闭技能 - core.setFlag('skillName', '无'); -} -``` - -  - -通过上述这几种方式,我们就能成功的让H5支持技能啦! - -## 多角色的支持 - -其实,我们的样板还能支持多角色的制作。比如《黑·白·间》之类的塔也是完全可以刻的。 - -你只需要如下几步来达到多角色的效果。 - -1. 每个角色弄一张行走图。相关信息参见[自定义事件:setHeroIcon](event#setHeroIcon:更改角色行走图)。 -2. [覆盖楼传事件](#覆盖楼传事件),这样可以通过点工具栏的楼层传送按钮来切换角色。当然你也完全可以自己写一个道具,或[自定义快捷键](#自定义快捷键)来进行绑定。 -3. 将下述代码直接贴入脚本编辑 - 插件编写中。(写在`var _useEquipment = ...`之前。) - ``` 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)`来切换到某个具体的勇士上。 - -!> 如果道具不共用,需要在初始定义那里写 `'items': {"keys": {"yellowKey": 0, "blueKey": 0, "redKey": 0}, "tools": {}, "constants": {}}` - -## 根据难度分歧来自定义地图 - -遗憾的是,所有地图数据必须在剧本的map中指定,换句话说,我们无法在游戏进行中动态修改地图,比如为简单难度增加一个血瓶。 - -幸运的是,我们可以采用如下方式进行难度分歧,为用户简单难度下增加额外的血瓶或宝石。 - -``` js -"firstArrive": [ // 第一次到该楼层触发的事件 - {"type": "if", "condition": "flag:hard!=3", // 判断是否困难难度 - "true": [ // 不为困难,则为普通或简单难度 - {"type": "show", "loc": [3,6]} // 显示血瓶 - {"type": "if", "condition": "flag:hard==1", // 判断是否是简单难度 - "true": [ - {"type": "show", "loc": [3,7]} // 简单难度则显示宝石 - ], - "false": [] // 普通难度则只显示血瓶 - }, - ], - "false": [] // 困难难度,不进行任何操作 - }, -], -"events": { - "3,6": {"enable": false} // 比如[3,6]点是一个血瓶,初始不可见 - "3,7": {"enable": false} // 比如[3,7]点是一个宝石,初始不可见 -} -``` - -如上所示,我们在地图上设置一个额外的血瓶和宝石,并初始时设为禁用状态。 - -当第一次到达该楼层时,进行一次判断;如果不为困难难度,则将血瓶显示出来;再判断是否为简单难度,如果是则再把宝石显示出来。 - -通过对`flag:hard`进行判断的方式,我们也可以达成“对于不同的难度有着不同的地图效果”。 - -========================================================================================== - -[继续阅读附录:所有API列表](api) diff --git a/editor-mobile.html b/editor-mobile.html index 70d5f8b0..67db31dd 100644 --- a/editor-mobile.html +++ b/editor-mobile.html @@ -12,40 +12,60 @@ if(innerWidth>innerHeight){ //pic:1242*2208 | chrome info:1340*2380 confirm('宽大于高的设备请使用正常版本的editor, 点击确定跳转')?(window.location='./editor.html'):''; } + if (location.protocol.indexOf("http")!=0) { + alert("请在启动服务中打开本编辑器!不然包括编辑在内的绝大多数功能都无法使用。"); + }
-
- -

{{ errors[error-1] }}

+
+
-
- - +
+ - 保留属性 + 保留楼层属性 +
+
-
- - - - +
+ + + + +
+ +
-
- -
-
- -
-

追加素材

@@ -56,6 +76,15 @@

+

+ 色相: + +

@@ -71,7 +100,7 @@
-

地图选点   +

地图选点    

0,0

@@ -88,17 +117,10 @@
-
-

图块属性   +
+

图块属性        

-
- - - -
- -
@@ -112,10 +134,21 @@
+
+ + + +
+ +
+
+ + +
-

楼层属性   +

楼层属性        

@@ -129,10 +162,14 @@
+
+ + +
-

全塔属性   +

全塔属性      

@@ -158,16 +195,27 @@ + +
+ + +
+

@@ -182,13 +230,14 @@
+ 语法检查
-

脚本编辑   +

脚本编辑    

@@ -204,6 +253,40 @@
+
+

公共事件         +

+
+
+ + + + + + + + +
条目注释
+
+
+
+
+

插件编写         +

+
+
+ + + + + + + + +
条目注释
+
+
+
@@ -211,6 +294,7 @@ +
@@ -218,88 +302,80 @@
-
-
-

当前选择为清除块,可擦除地图上块

-
-

图块编号:{{ infos['idnum'] }}

-

图块ID:{{ infos['id'] }}

-

该图块无对应的数字或ID存在,请先前往icons.js和maps.js中进行定义!

-

图块所在素材:{{ infos['images'] + (isAutotile ? '( '+infos['id']+' )' : '') }} -

-

图块索引:{{ infos['y'] }}

-
+
+ -
-

{{ mapMsg }}

+
+
+
- + -
- - - - + +
+ + + +
- + - - -
@@ -308,6 +384,7 @@