diff --git a/docs/event.md b/docs/event.md index 3fa2df43..f9cab896 100644 --- a/docs/event.md +++ b/docs/event.md @@ -4,7 +4,7 @@ ## 事件的机制 -本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`,包括勇士到达一个指定的`checkBlock`也可以触发一个检查领域、夹击的事件。上面说的这些事件都是系统本身自带的,即类似于RMXP中的公共事件。 +本塔所有的事件都是依靠触发`trigger`完成的。例如,勇士碰到一个门可以触发一个事件`openDoor`,勇士碰到怪物可以触发一个事件`battle`,勇士碰到一个(上面定义的)楼层传送点可以触发一个事件`changeFloor`,勇士穿过路障可以触发一个事件`passNet`,等等。上面说的这些事件都是系统本身自带的,即类似于RMXP中的公共事件。 上述这些默认的事件已经存在处理机制,不需要我们操心。我们真正所需要关心的,其实只是一个自定义的事件。 @@ -829,7 +829,7 @@ core.insertAction(list) //往当前事件列表中插入一系列事件。使用 请注意,快捷商店默认是不可被使用的。直到至少调用一次自定义事件中的 `{"type": "openShop"}` 打开商店后,才能真正在快捷栏中被使用。 -``` java +``` js "1,0": [ // 金币商店 // 打开商店前,你也可以添加自己的剧情 // 例如,通过if来事件来判断是不是第一次访问商店,是的则显示一段文字(类似宿命的华音那样) diff --git a/images/lv.png b/images/lv.png new file mode 100644 index 00000000..3b9cf96c Binary files /dev/null and b/images/lv.png differ diff --git a/images/up.png b/images/up.png new file mode 100644 index 00000000..705189d0 Binary files /dev/null and b/images/up.png differ diff --git a/index.html b/index.html index fb79075f..ae063d97 100644 --- a/index.html +++ b/index.html @@ -47,10 +47,14 @@

-
+

+
+ +

+

@@ -67,7 +71,7 @@

-
+

@@ -75,12 +79,16 @@

+
+ +

+
-
+
@@ -96,13 +104,15 @@

+
- + 此浏览器不支持HTML5
+ \ No newline at end of file diff --git a/libs/core.js b/libs/core.js index f0a0a18c..e572fe5b 100644 --- a/libs/core.js +++ b/libs/core.js @@ -7,12 +7,14 @@ function core() { this.statusBar = {}; this.canvas = {}; this.images = []; - this.sounds = {}; + this.bgms = []; + this.sounds = []; this.floorIds = []; this.floors = {}; this.firstData = {}; this.material = { 'images': {}, + 'bgms': {}, 'sounds': {}, 'ground': null, 'items': {}, @@ -35,12 +37,12 @@ function core() { 'openDoorAnimate': null } this.musicStatus = { - 'isIOS': false, - 'loaded': false, - 'bgmStatus': false, - 'soundStatus': true, - 'playedSound': null, - 'playedBgm': null, + 'audioContext': null, // WebAudioContext + 'startDirectly': false, // 是否直接播放(加载)音乐 + 'bgmStatus': false, // 是否播放BGM + 'soundStatus': true, // 是否播放SE + 'playingBgm': null, // 正在播放的BGM + 'isPlaying': false, } // 样式 this.domStyle = { @@ -57,7 +59,7 @@ function core() { 'floorId': null, 'thisMap': null, 'maps': null, - 'checkBlock': [], // 显伤伤害 + 'checkBlock': {}, // 显伤伤害 // 勇士状态;自动寻路相关 'heroMoving': false, @@ -87,6 +89,7 @@ function core() { 'selection': null, 'ui': null, }, + 'curtainColor': null, 'usingCenterFly':false, 'openingDoor': null, @@ -101,11 +104,12 @@ function core() { /////////// 系统事件相关 /////////// -core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds, floors, coreData) { +core.prototype.init = function (dom, statusBar, canvas, images, bgms, sounds, floorIds, floors, coreData) { core.dom = dom; core.statusBar = statusBar; core.canvas = canvas; core.images = images; + core.bgms = bgms; core.sounds = sounds; core.floorIds = floorIds; core.floors = floors; @@ -113,6 +117,13 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds core[key] = coreData[key]; } core.flags = core.clone(core.data.flags); + if (!core.flags.enableExperience) + core.flags.enableLevelUp = false; + if (!core.flags.canOpenBattleAnimate) { + core.flags.showBattleAnimateConfirm = false; + core.flags.battleAnimate = false; + core.setLocalStorage('battleAnimate', false); + } core.values = core.clone(core.data.values); core.firstData = core.data.getFirstData(); core.initStatus.shops = core.firstData.shops; @@ -126,16 +137,43 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds core.material.icons = core.icons.getIcons(); core.material.events = core.events.getEvents(); - // test if iOS - core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); - var userAgent = navigator.userAgent; - if (userAgent.indexOf('iPhone') > -1 || userAgent.indexOf('iPad') > -1) { - console.log("你的设备为iphone,不自动播放音乐!"); - core.musicStatus.isIOS = true; - core.musicStatus.soundStatus = false; + + if (location.protocol.indexOf("http")==0) { + window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext; + try { + core.musicStatus.audioContext = new window.AudioContext(); + } catch (e) { + console.log("该浏览器不支持AudioContext"); + core.musicStatus.audioContext = null; + } } + // 音效设置部分 + var isPC = true; + ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"].forEach(function (t) { + if (navigator.userAgent.indexOf(t)>=0) isPC=false; + }); + if (isPC) { + // 如果是PC端直接加载 + core.musicStatus.startDirectly = true; + } + else { + var connection = navigator.connection; + if (core.isset(connection) && connection.type=='wifi') + core.musicStatus.startDirectly = true; + } + + // 先从存储中读取BGM状态 + core.musicStatus.bgmStatus = core.getLocalStorage('bgmStatus', true); + if (!core.musicStatus.startDirectly) // 如果当前网络环境不允许 + core.musicStatus.bgmStatus = false; + core.setLocalStorage('bgmStatus', core.musicStatus.bgmStatus); + + core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); + core.setLocalStorage('soundStatus', core.musicStatus.soundStatus); + + // switchs core.flags.battleAnimate = core.getLocalStorage('battleAnimate', core.flags.battleAnimate); core.flags.displayEnemyDamage = core.getLocalStorage('enemyDamage', core.flags.displayEnemyDamage); @@ -210,31 +248,24 @@ core.prototype.loader = function (callback) { for (var i = 0; i < core.images.length; i++) { core.loadImage(core.images[i], function (imgName, image) { core.setStartLoadTipText('正在加载图片 ' + imgName + "..."); - imgName = imgName.split('-'); - imgName = imgName[0]; core.material.images[imgName] = image; loadedImageNum++; core.setStartLoadTipText(imgName + ' 加载完毕...'); core.setStartProgressVal(loadedImageNum * (100 / allImageNum)); if (loadedImageNum == allImageNum) { - // 加载音频 - for (var key in core.sounds) { - for (var i = 0; i < core.sounds[key].length; i++) { - var soundName=core.sounds[key][i]; - soundName = soundName.split('-'); - var sound = new Audio(); - sound.preload = 'none'; - sound.src = 'sounds/' + soundName[0] + '.' + key; - if (soundName[1] == 'loop') { - sound.loop = 'loop'; + + // 加载Autotile + core.material.images.autotile={}; + var autotileIds = Object.keys(core.material.icons.autotile); + for (var x=0;x0) return; - core.musicStatus.bgmStatus=1; - if (core.musicStatus.soundStatus) - core.playBgm('bgm', 'mp3'); - return; - } - var item = toLoadList.shift(); - item.oncanplay = function() { - core.loadSoundItem(toLoadList); - } - item.load(); + core.sounds.forEach(function (t) { + + if (core.musicStatus.audioContext != null) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'sounds/'+t, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function(e) { //下载完成 + try { + core.musicStatus.audioContext.decodeAudioData(this.response, function (buffer) { + core.material.sounds[t] = buffer; + }, function (e) { + console.log(e); + core.material.sounds[t] = null; + }) + } + catch (ee) { + console.log(ee); + core.material.sounds[t] = null; + } + }; + + xhr.ontimeout = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.onerror = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.send(); + } + else { + var music = new Audio(); + music.src = 'sounds/'+t; + core.material.sounds[t] = music; + } + + }); + + // 直接开始播放 + if (core.musicStatus.startDirectly && core.bgms.length>0) + core.playBgm(core.bgms[0]); + + callback(); } core.prototype.isPlaying = function() { @@ -296,7 +394,6 @@ core.prototype.isPlaying = function() { return false; } - core.prototype.clearStatus = function() { // 停止各个Timeout和Interval for (var i in core.interval) { @@ -444,14 +541,6 @@ core.prototype.keyDown = function(keyCode) { core.events.keyDownSyncSave(keyCode); return; } - - /* - if (core.status.event.id == 'save' || core.status.event.id == 'load') { - if (keyCode==37) core.ui.drawSLPanel(core.status.event.data-1); - else if (keyCode==39) core.ui.drawSLPanel(core.status.event.data+1); - return; - } - */ return; } if(!core.status.played) { @@ -470,7 +559,7 @@ core.prototype.keyDown = function(keyCode) { case 40: core.moveHero('down'); break; - case 13: case 32: case 51: // 快捷键3:飞 + case 13: case 32: case 67: case 51: // 快捷键3:飞 // 因为加入了两次的检测机制,从keydown转移到keyup,同时保证位置信息正确,但以下情况会触发作图的bug: // 在鼠标的路线移动中使用飞,绿块会滞后一格,显示的位置不对,同时也不会倍以下的代码清除 if (core.status.heroStop && core.hasItem('centerFly')) { @@ -486,9 +575,9 @@ core.prototype.keyDown = function(keyCode) { core.status.usingCenterFly = false; } else if (keyCode==51) { core.status.usingCenterFly = true; - var fillstyle = 'rgba(255,0,0,0.5)'; - if (core.canUseItem('centerFly')) fillstyle = 'rgba(0,255,0,0.5)'; - core.fillRect('ui',(12-core.getHeroLoc('x'))*32,(12-core.getHeroLoc('y'))*32,32,32,fillstyle); + core.setAlpha('ui', 0.5); + core.fillRect('ui',(12-core.getHeroLoc('x'))*32,(12-core.getHeroLoc('y'))*32,32,32,core.canUseItem('centerFly')?'#00FF00':'#FF0000'); + core.setAlpha('ui', 1); core.drawTip("请确认当前中心对称飞行器的位置"); } } @@ -525,6 +614,10 @@ core.prototype.keyUp = function(keyCode) { core.events.keyUpBook(keyCode); return; } + if (core.status.event.id=='book-detail' && (keyCode==13 || keyCode==32 || keyCode==67)) { + core.events.clickBookDetail(); + return; + } if (core.status.event.id=='fly') { core.events.keyUpFly(keyCode); return; @@ -767,6 +860,12 @@ core.prototype.onclick = function (x, y, stepPostfix) { return; } + // 怪物详细信息 + if (core.status.event.id == 'book-detail') { + core.events.clickBookDetail(x,y); + return; + } + // 楼层飞行器 if (core.status.event.id == 'fly') { core.events.clickFly(x,y); @@ -873,7 +972,8 @@ core.prototype.onmousewheel = function (direct) { /////////// 寻路代码相关 /////////// core.prototype.clearAutomaticRouteNode = function (x, y) { - core.canvas.ui.clearRect(x * 32 + 5, y * 32 + 5, 27, 27); + if (core.status.event.id==null) + core.canvas.ui.clearRect(x * 32 + 5, y * 32 + 5, 27, 27); } core.prototype.stopAutomaticRoute = function () { @@ -1085,8 +1185,8 @@ core.prototype.automaticRoute = function (destX, destY) { // 绕过可能的夹击点 // if (nextBlock.block.event.trigger == 'checkBlock') deepAdd=200; } - if (core.status.checkBlock[nid]>0) - deepAdd = core.status.checkBlock[nid]; + if (core.status.checkBlock.damage[nid]>0) + deepAdd = core.status.checkBlock.damage[nid]; if (nx == destX && ny == destY) { route[nid] = direction; @@ -1257,9 +1357,9 @@ core.prototype.setHeroMoveTriggerInterval = function () { core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); } core.trigger(core.getHeroLoc('x'), core.getHeroLoc('y')); - core.checkBlock(); clearInterval(core.interval.heroMoveInterval); core.status.heroMoving = false; + core.checkBlock(); }); } }, 50); @@ -1458,7 +1558,7 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { } } // open - core.playSound("door", "ogg"); + core.playSound("door.ogg"); var state = 0; var doorId = id; if (!(doorId.substring(doorId.length-4)=="Door")) { @@ -1504,7 +1604,7 @@ core.prototype.battle = function (id, x, y, force, callback) { }); } else { - core.playSound('attack', 'ogg'); + core.playSound('attack.ogg'); core.afterBattle(id, x, y, callback); } } @@ -1524,18 +1624,18 @@ core.prototype.afterBattle = function(id, x, y, callback) { var experience = core.material.enemys[id].experience; if (core.hasFlag('curse')) experience=0; core.status.hero.experience += experience; - core.updateStatusBar(); if (core.isset(x) && core.isset(y)) { core.removeBlock(x, y); core.canvas.event.clearRect(32 * x, 32 * y, 32, 32); } - core.updateFg(); - var hint = "打败 " + core.material.enemys[id].name + ",金币+" + money; + // core.updateStatusBar(); + var hint = "打败 " + core.material.enemys[id].name; + if (core.flags.enableMoney) + hint += ",金币+" + money; if (core.flags.enableExperience) hint += ",经验+" + core.material.enemys[id].experience; core.drawTip(hint); - core.updateCheckBlockMap(); // 打完怪物,触发事件 core.events.afterBattle(id,x,y,callback); @@ -1577,15 +1677,19 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) core.clearContinueAutomaticRoute(); core.dom.floorNameLabel.innerHTML = core.status.maps[floorId].title; if (core.isset(stair)) { - // find heroLoc - heroLoc = core.status.hero.loc; + if (!core.isset(heroLoc)) heroLoc={}; var blocks = core.status.maps[floorId].blocks; for (var i in blocks) { if (core.isset(blocks[i].event) && !(core.isset(blocks[i].enable) && !blocks[i].enable) && blocks[i].event.id === stair) { heroLoc.x = blocks[i].x; heroLoc.y = blocks[i].y; + break; } } + if (!core.isset(heroLoc.x)) { + heroLoc.x=core.status.hero.loc.x; + heroLoc.y=core.status.hero.loc.y; + } } if (core.status.maps[floorId].canFlyTo && core.status.hero.flyRange.indexOf(floorId)<0) { if (core.floorIds.indexOf(floorId)>core.floorIds.indexOf(core.status.floorId)) @@ -1595,11 +1699,38 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) } window.setTimeout(function () { - // console.log('地图切换到' + floorId); - core.playSound('floor', 'mp3'); + core.playSound('floor.mp3'); core.mapChangeAnimate('show', time/2, function () { - core.statusBar.floor.innerHTML = core.status.maps[floorId].name; - core.updateStatusBar(); + + // 根据文字判断是否斜体 + var floorName = core.status.maps[floorId].name; + if (!core.isset(floorName) || floorName=="") floorName=" " + core.statusBar.floor.innerHTML = floorName; + if (/^[+-]?\d+$/.test(floorName)) + core.statusBar.floor.style.fontStyle = 'italic'; + else core.statusBar.floor.style.fontStyle = 'normal'; + + // 不存在事件时,更改画面色调 + if (core.status.event.id == null) { + // 默认画面色调 + if (core.isset(core.floors[floorId].color)) { + var color = core.floors[floorId].color; + + // 直接变色 + var nowR = parseInt(color[0]), nowG = parseInt(color[1]), nowB = parseInt(color[2]); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + if (core.isset(color[3])) + core.dom.curtain.style.opacity = color[3]; + else core.dom.curtain.style.opacity=1; + core.status.curtainColor = color; + } + else { + core.dom.curtain.style.background = "#000000"; + core.dom.curtain.style.opacity = 0; + } + } + core.drawMap(floorId, function () { setTimeout(function() { core.mapChangeAnimate('hide', time/4, function () { @@ -1612,9 +1743,7 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) core.setHeroLoc('x', heroLoc.x); core.setHeroLoc('y', heroLoc.y); core.drawHero(core.getHeroLoc('direction'), core.getHeroLoc('x'), core.getHeroLoc('y'), 'stop'); - core.updateCheckBlockMap(); - core.updateCheckBlock(); - core.updateFg(); + core.updateStatusBar(); }, 15) }); }); @@ -1774,7 +1903,7 @@ core.prototype.drawMap = function (mapName, callback) { if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) { if (block.event.cls == 'autotile') { // core.drawAutotile(); - autotileMaps[13*block.x + block.y] = true; + autotileMaps[13*block.x + block.y] = block.event.id; continue; } else { @@ -1792,17 +1921,29 @@ core.prototype.drawMap = function (mapName, callback) { callback(); } -core.prototype.drawAutotile = function (floorId, canvas, autotileMaps, left, top, size) { +core.prototype.drawAutotile = function (floorId, canvas, autotileMaps, left, top, size, autotileId) { + + if (!core.isset(autotileId)) { + var autotileIds = {}; + autotileMaps.forEach(function (t) { + if (core.isset(t)) autotileIds[t]=true; + }); + Object.keys(autotileIds).forEach(function (t) { + core.drawAutotile(floorId, canvas, autotileMaps, left, top, size, t); + }) + return; + } + var isAutotile = function(x, y) { - if (x<0 || x>12 || y<0 || y>12) return 0; - return autotileMaps[13*x+y]?1:0; + if (x<0 || x>12 || y<0 || y>12) return 1; + return autotileMaps[13*x+y]==autotileId?1:0; } for (var xx=0;xx<13;xx++) { for (var yy=0;yy<13;yy++) { if (isAutotile(xx, yy)) { // 绘制autotile var id=isAutotile(xx, yy - 1) + 2 * isAutotile(xx - 1, yy) + 4 * isAutotile(xx, yy + 1) + 8 * isAutotile(xx + 1, yy); - core.drawAutotileBlock(floorId, canvas, left + xx * size, top + yy * size, size, core.material.images.autotile, id); + core.drawAutotileBlock(floorId, canvas, left + xx * size, top + yy * size, size, core.material.images.autotile[autotileId], id); } } } @@ -1810,16 +1951,16 @@ core.prototype.drawAutotile = function (floorId, canvas, autotileMaps, left, top for (var yy=0;yy<13;yy++) { if (isAutotile(xx, yy) + isAutotile(xx + 1, yy) + isAutotile(xx + 1, yy + 1) + isAutotile(xx, yy + 1) != 3) continue; if (!isAutotile(xx, yy)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size, size, core.material.images.autotile, 16); + core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size, size, core.material.images.autotile[autotileId], 16); } if (!isAutotile(xx + 1, yy)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size, size, core.material.images.autotile, 17); + core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size, size, core.material.images.autotile[autotileId], 17); } if (!isAutotile(xx + 1, yy + 1)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size / 2, size, core.material.images.autotile, 18); + core.drawAutotileBlock(floorId, canvas, left + xx * size + size / 2, top + yy * size + size / 2, size, core.material.images.autotile[autotileId], 18); } if (!isAutotile(xx, yy + 1)) { - core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size / 2, size, core.material.images.autotile, 19); + core.drawAutotileBlock(floorId, canvas, left + xx * size + size, top + yy * size + size / 2, size, core.material.images.autotile[autotileId], 19); } } } @@ -1950,7 +2091,7 @@ core.prototype.nearStair = function() { core.prototype.enemyExists = function (x, y, id,floorId) { var block = core.getBlock(x,y,floorId); if (block==null) return false; - return block.block.event.cls=='enemys' && block.block.event.id==id; + return block.block.event.cls=='enemys' && (core.isset(id)?block.block.event.id==id:true); } core.prototype.getBlock = function (x, y, floorId, needEnable) { @@ -2116,7 +2257,6 @@ core.prototype.addBlock = function(x,y,floodId) { // 本身是禁用事件,启用之 if (core.isset(block.enable) && !block.enable) { block.enable = true; - core.updateCheckBlockMap(); // 在本层,添加动画 if (floodId == core.status.floorId && core.isset(block.event)) { blockIcon = core.material.icons[block.event.cls][block.event.id]; @@ -2158,17 +2298,12 @@ core.prototype.removeBlockById = function (index, floorId) { if (!core.isset(event)) event = core.floors[floorId].changeFloor[x+","+y]; - var shouldUpdateBlockMap = blocks[index].event.cls == 'enemys'; // 不存在事件,直接删除 if (!core.isset(event)) { blocks.splice(index,1); - if (shouldUpdateBlockMap) - core.updateCheckBlockMap(); return; } blocks[index].enable = false; - if (shouldUpdateBlockMap) - core.updateCheckBlockMap(); } core.prototype.removeBlockByIds = function (floorId, ids) { @@ -2245,6 +2380,15 @@ core.prototype.setGlobalAnimate = function (speed) { }, speed / 2); } +core.prototype.syncGlobalAnimate = function () { + core.status.twoAnimateObjs.forEach(function (t) { + t.status=0; + }) + core.status.fourAnimateObjs.forEach(function (t) { + t.status=0; + }) +} + core.prototype.setBoxAnimate = function () { clearInterval(core.interval.boxAnimate); if (core.status.boxAnimateObjs.length > 0) { @@ -2267,70 +2411,96 @@ core.prototype.drawBoxAnimate = function (background) { } } -core.prototype.updateCheckBlockMap = function() { - // 更新领域、夹击地图 - core.status.checkBlockMap = []; +// 更新领域、显伤 +core.prototype.updateCheckBlock = function() { + core.status.checkBlock = {}; + if (!core.isset(core.status.thisMap)) return; var blocks = core.status.thisMap.blocks; + + // Step1: 更新怪物地图 + core.status.checkBlock.map = []; // 记录怪物地图 for (var n=0;n12 || ny<0 || ny>12) continue; + if (!zoneSquare && Math.abs(dx)+Math.abs(dy)>range) continue; + core.status.checkBlock.damage[13*nx+ny]+=enemy.value; + } + } + } + // 存在阻击 + if (core.enemys.hasSpecial(enemy.special, 18)) { + for (var dx=-1;dx<=1;dx++) { + for (var dy=-1;dy<=1;dy++) { + if (dx==0 && dy==0) continue; + var nx=x+dx, ny=y+dy; + if (nx<0 || nx>12 || ny<0 || ny>12 || Math.abs(dx)+Math.abs(dy)>1) continue; + core.status.checkBlock.damage[13*nx+ny]+=enemy.value; + } + } } } } } -} -// 更新领域、显伤点 -core.prototype.updateCheckBlock = function() { - if (!core.isset(core.status.thisMap)) return; - if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); - core.status.checkBlock = []; + + // Step3: 更新夹击点坐标,并将夹击伤害加入到damage中 + core.status.checkBlock.betweenAttack = []; // 记录(x,y)点是否有夹击 for (var x=0;x<13;x++) { for (var y=0;y<13;y++) { - // 计算(x,y)点伤害 - var damage = 0; - if (!core.enemyExists(x,y)) { // 如果该点本身不存在怪物(打死怪物会调用更新) - - // 领域 - [[-1,0],[0,-1],[1,0],[0,1]].forEach(function (dir) { - var nx = x+dir[0], ny = y+dir[1]; - if (nx<0 || nx>12 || ny<0 || ny>12) return; - if (core.status.checkBlockMap[13*nx+ny]%1000000>0) { - damage += core.status.checkBlockMap[13*nx+ny] % 1000000; - } - }) - - var leftValue = core.status.hero.hp - damage; - if (leftValue>1) { - var has = false; - // 夹击 - if (x>0 && x<12) { - var id1=parseInt(core.status.checkBlockMap[13*(x-1)+y]/1000000), - id2=parseInt(core.status.checkBlockMap[13*(x+1)+y]/1000000); - if (id1>0 && id1==id2) - has = true; - } - if (y>0 && y<12) { - var id1=parseInt(core.status.checkBlockMap[13*x+y-1]/1000000), - id2=parseInt(core.status.checkBlockMap[13*x+y+1]/1000000); - if (id1>0 && id1==id2) - has = true; - } - if (has) { - damage += parseInt((leftValue+1) / 2); + var has=false; + if (x>0 && x<12) { + var id1=core.status.checkBlock.map[13*(x-1)+y], + id2=core.status.checkBlock.map[13*(x+1)+y]; + if (core.isset(id1) && core.isset(id2) && id1==id2) { + var enemy = core.enemys.getEnemys(id1); + if (core.enemys.hasSpecial(enemy.special, 16)) { + has = true; } } } - core.status.checkBlock[13*x+y] = damage; + if (y>0 && y<12) { + var id1=core.status.checkBlock.map[13*x+y-1], + id2=core.status.checkBlock.map[13*x+y+1]; + if (core.isset(id1) && core.isset(id2) && id1==id2) { + var enemy = core.enemys.getEnemys(id1); + if (core.enemys.hasSpecial(enemy.special, 16)) { + has = true; + } + } + } + // 存在夹击 + if (has) { + core.status.checkBlock.betweenAttack[13*x+y]=true; + var leftHp = core.status.hero.hp - core.status.checkBlock.damage[13*x+y]; + if (leftHp>1) + core.status.checkBlock.damage[13*x+y] += parseInt((leftHp+1)/2); + } } } } @@ -2338,13 +2508,39 @@ core.prototype.updateCheckBlock = function() { core.prototype.checkBlock = function () { // 检查领域、夹击事件 var x=core.getHeroLoc('x'), y=core.getHeroLoc('y'); - if (core.status.checkBlock[13*x+y]>0) { - core.status.hero.hp -= core.status.checkBlock[13*x+y]; - if (core.hasBetweenAttack(x,y)) { + var damage = core.status.checkBlock.damage[13*x+y]; + if (damage>0) { + core.status.hero.hp -= damage; + + // 检查阻击事件 + var snipe = []; + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + } + for (var direction in scan) { + var nx = x+scan[direction].x, ny=y+scan[direction].y; + if (nx<0 || nx>12 || ny<0 || ny>12) continue; + var id=core.status.checkBlock.map[13*nx+ny]; + if (core.isset(id)) { + var enemy = core.enemys.getEnemys(id); + if (core.isset(enemy) && core.enemys.hasSpecial(enemy.special, 18)) { + snipe.push({'direction': direction, 'x': nx, 'y': ny}); + } + } + } + + if (core.status.checkBlock.betweenAttack[13*x+y]) { core.drawTip('受到夹击,生命变成一半'); } - else if (core.hasZone(x,y)) { - core.drawTip('受到领域伤害'+core.status.checkBlock[13*x+y]+'点'); + // 阻击 + else if (snipe.length>0) { + core.drawTip('受到阻击伤害'+damage+'点'); + } + else { + core.drawTip('受到领域伤害'+damage+'点'); } if (core.status.hero.hp<=0) { core.status.hero.hp=0; @@ -2352,67 +2548,154 @@ core.prototype.checkBlock = function () { core.events.lose('zone'); return; } + snipe = snipe.filter(function (t) { + var x=t.x, y=t.y, direction = t.direction; + var nx = x+scan[direction].x, ny=y+scan[direction].y; + + return nx>=0 && nx<=12 && ny>=0 && ny<=12 && core.getBlock(nx, ny, core.status.floorId, false)==null; + }); core.updateStatusBar(); + if (snipe.length>0) + core.snipe(snipe); } } -core.prototype.hasZone = function (x,y) { - if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); - var isZone = false; - // 领域 - [[-1,0],[0,-1],[1,0],[0,1]].forEach(function (dir) { - var nx = x+dir[0], ny = y+dir[1]; - if (nx<0 || nx>12 || ny<0 || ny>12) return; - if (core.status.checkBlockMap[13*nx+ny]%1000000>0) { - isZone = true; - } - }) - return isZone; -} +core.prototype.snipe = function (snipes) { + core.waitHeroToStop(function() { + core.lockControl(); -core.prototype.hasBetweenAttack = function(x,y) { - if (!core.isset(core.status.checkBlockMap)) core.updateCheckBlockMap(); - // 夹击 - if (x>0 && x<12) { - var id1=parseInt(core.status.checkBlockMap[13*(x-1)+y]/1000000), - id2=parseInt(core.status.checkBlockMap[13*(x+1)+y]/1000000); - if (id1>0 && id1==id2) - return true; - } - if (y>0 && y<12) { - var id1=parseInt(core.status.checkBlockMap[13*x+y-1]/1000000), - id2=parseInt(core.status.checkBlockMap[13*x+y+1]/1000000); - if (id1>0 && id1==id2) - return true; - } + var scan = { + 'up': {'x': 0, 'y': -1}, + 'left': {'x': -1, 'y': 0}, + 'down': {'x': 0, 'y': 1}, + 'right': {'x': 1, 'y': 0} + }; + + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; + snipe.nx = x+scan[snipe.direction].x; + snipe.ny = y+scan[snipe.direction].y; + + core.removeGlobalAnimate(x, y); + + var block = core.getBlock(x,y).block; + + snipe.blockIcon = core.material.icons[block.event.cls][block.event.id]; + snipe.blockImage = core.material.images[block.event.cls]; + var damage = core.enemys.getDamage(block.event.id); + + var color = "#000000"; + if (damage <= 0) color = '#00FF00'; + else if (damage < core.status.hero.hp / 3) color = '#FFFFFF'; + else if (damage < core.status.hero.hp * 2 / 3) color = '#FFFF00'; + else if (damage < core.status.hero.hp) color = '#FF7F00'; + else color = '#FF0000'; + + if (damage >= 999999999) damage = "???"; + else if (damage > 100000) damage = (damage / 10000).toFixed(1) + "w"; + + snipe.damage = damage; + snipe.color = color; + snipe.block = core.clone(block); + }) + + var time = 500, step = 0; + + var animateValue = 2; + var animateCurrent = 0; + var animateTime = 0; + + core.canvas.fg.textAlign = 'left'; + + var animate=window.setInterval(function() { + + step++; + animateTime += time / 16; + if (animateTime >= core.values.animateSpeed * 2 / animateValue) { + animateCurrent++; + animateTime = 0; + if (animateCurrent>=animateValue) animateCurrent=0; + } + + snipes.forEach(function (snipe) { + var x=snipe.x, y=snipe.y, direction = snipe.direction; + + var nowX=32*x+scan[direction].x*2*step, nowY=32*y+scan[direction].y*2*step; + + // 清空上一次 + core.clearMap('event', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + core.clearMap('fg', nowX-2*scan[direction].x, nowY-2*scan[direction].y, 32, 32); + + core.canvas.event.drawImage(snipe.blockImage, animateCurrent*32, snipe.blockIcon*32, 32, 32, nowX, nowY, 32, 32); + + if (core.hasItem('book')) { + // drawFG + core.setFillStyle('fg', '#000000'); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 30); + core.canvas.fg.fillText(snipe.damage, nowX + 2, nowY + 32); + core.canvas.fg.fillText(snipe.damage, nowX, nowY + 32); + + core.setFillStyle('fg', snipe.color); + core.canvas.fg.fillText(snipe.damage, nowX + 1, nowY + 31); + } + + }) + + if (step==16) { // 移动完毕 + clearInterval(animate); + snipes.forEach(function (t) { + core.removeBlock(t.x, t.y); + var nBlock = core.clone(t.block); + nBlock.x = t.nx; nBlock.y = t.ny; + core.status.thisMap.blocks.push(nBlock); + core.addGlobalAnimate(animateValue, 32*t.nx, 32*t.ny, t.blockIcon, t.blockImage); + }); + core.syncGlobalAnimate(); + core.updateStatusBar(); + // 不存在自定义事件 + if (core.status.event.id==null) + core.unLockControl(); + } + }, time/16); + + + + }); } core.prototype.setFg = function(color, time, callback) { - time = time || 750; - core.setOpacity('fg', 1); + if (!core.isset(time)) time=750; + if (time<=0) time=0; - var reset = false; - - if (!core.isset(core.status.event.data.currentColor)) { - core.status.event.data.currentColor = [0,0,0,0]; + if (!core.isset(core.status.curtainColor)) { + core.status.curtainColor = [0,0,0,0]; } - var fromColor = core.status.event.data.currentColor; + var fromColor = core.status.curtainColor; - if (!core.isset(color)) { + if (!core.isset(color)) color = [0,0,0,0]; - reset = true; - } - if (color.length==3) { + if (color.length==3) color.push(1); - } if (color[3]<0) color[3]=0; if (color[3]>1) color[3]=1; + + if (time==0) { + // 直接变色 + var nowR = parseInt(color[0]), nowG = parseInt(color[1]), nowB = parseInt(color[2]); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + core.dom.curtain.style.opacity = color[3]; + core.status.curtainColor = color; + if (core.isset(callback)) callback(); + return; + } + var step=0; var changeAnimate = setInterval(function() { step++; - core.clearMap('fg', 0, 0, 416, 416); var nowAlpha = fromColor[3]+(color[3]-fromColor[3])*step/25; var nowR = parseInt(fromColor[0]+(color[0]-fromColor[0])*step/25); @@ -2422,19 +2705,13 @@ core.prototype.setFg = function(color, time, callback) { if (nowG<0) nowG=0; if (nowG>255) nowG=255; if (nowB<0) nowB=0; if (nowB>255) nowB=255; - core.setAlpha('fg', nowAlpha); - var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1) - core.fillRect('fg', 0, 0, 416, 416, toRGB); + var toRGB = "#"+((1<<24)+(nowR<<16)+(nowG<<8)+nowB).toString(16).slice(1); + core.dom.curtain.style.background = toRGB; + core.dom.curtain.style.opacity = nowAlpha; if (step>=25) { clearInterval(changeAnimate); - if (reset) { - core.clearMap('fg', 0, 0, 416, 416); - delete core.status.event.data.currentColor; - core.setAlpha('fg', 1); - core.updateFg(); - } - else core.status.event.data.currentColor = color; + core.status.curtainColor = color; if (core.isset(callback)) callback(); } }, time/25); @@ -2484,14 +2761,6 @@ core.prototype.nextY = function () { */ core.prototype.updateFg = function () { - // 如果存在颜色 - if (core.isset(core.status.event.data) && core.isset(core.status.event.data.currentColor)) { - var color=core.status.event.data.currentColor; - core.setAlpha('fg', color[3]); - core.fillRect("fg",0,0,416,416,"#"+((1<<24)+(color[0]<<16)+(color[1]<<8)+color[2]).toString(16).slice(1)); - return; - } - if (!core.isset(core.status.thisMap) || !core.isset(core.status.thisMap.blocks)) return; // 更新显伤 var mapBlocks = core.status.thisMap.blocks; @@ -2536,7 +2805,7 @@ core.prototype.updateFg = function () { core.canvas.fg.textAlign = 'center'; for (var x=0;x<13;x++) { for (var y=0;y<13;y++) { - var damage = core.status.checkBlock[13*x+y]; + var damage = core.status.checkBlock.damage[13*x+y]; if (damage>0) { core.setFillStyle('fg', '#000000'); core.canvas.fg.fillText(damage, 32 * x + 17, 32 * (y + 1) - 13); @@ -2621,7 +2890,7 @@ core.prototype.getNextItem = function() { core.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { // core.getItemAnimate(itemId, itemNum, itemX, itemY); - core.playSound('item', 'ogg'); + core.playSound('item.ogg'); var itemCls = core.material.items[itemId].cls; core.items.getItemEffect(itemId, itemNum); core.removeBlock(itemX, itemY); @@ -2756,6 +3025,21 @@ core.prototype.calValue = function (value) { return eval(value); } +core.prototype.doEffect = function (expression) { + // 必须使用"+=" + var arr = expression.split("+="); + if (arr.length!=2) return; + var name=arr[0], value=core.calValue(arr[1]); + if (name.indexOf("status:")==0) { + var status=name.substring(7); + core.setStatus(status, core.getStatus(status)+value); + } + else if (name.indexOf("item:")==0) { + var itemId=name.substring(5); + core.setItem(itemId, core.itemCount(itemId)+value); + } +} + core.prototype.splitLines = function(canvas, text, maxLength, font) { if (core.isset(font)) core.setFont(canvas, font); @@ -3169,6 +3453,11 @@ core.prototype.getStatus = function (statusName) { return core.status.hero[statusName]; } +core.prototype.getLvName = function () { + if (core.status.hero.lv>core.firstData.levelUp.length) return core.status.hero.lv; + return core.firstData.levelUp[core.status.hero.lv-1].name || core.status.hero.lv; +} + core.prototype.setFlag = function(flag, value) { if (!core.isset(core.status.hero)) return; core.status.hero.flags[flag]=value; @@ -3206,23 +3495,106 @@ core.prototype.isset = function (val) { return true } -core.prototype.playSound = function (soundName, soundType) { - if (!core.musicStatus.soundStatus || !core.musicStatus.loaded) { +core.prototype.playBgm = function (bgm) { + + // 如果不允许播放 + if (!core.musicStatus.bgmStatus) return; + // 音频不存在 + if (!core.isset(core.material.bgms[bgm])) return; + + // 延迟播放 + if (core.material.bgms[bgm] == 'loading') { + core.material.bgms[bgm] = 'starting'; return; } - if (!core.isset(core.material.sounds[soundType][soundName])) return; - core.musicStatus.playedSound = core.material.sounds[soundType][soundName]; - core.musicStatus.playedSound.play(); + + try { + // 如果当前正在播放,且和本BGM相同,直接忽略 + if (core.musicStatus.playingBgm == bgm && core.musicStatus.isPlaying) { + return; + } + // 如果正在播放中,暂停 + if (core.isset(core.musicStatus.playingBgm) && core.musicStatus.isPlaying) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + // 播放当前BGM + core.musicStatus.playingBgm = bgm; + core.material.bgms[bgm].play(); + core.musicStatus.isPlaying = true; + + } + catch (e) { + console.log("无法播放BGM "+bgm); + console.log(e); + core.musicStatus.playingBgm = null; + } } -core.prototype.playBgm = function (bgmName, bgmType) { - if (core.musicStatus.isIOS || !core.musicStatus.loaded) return; - if (core.isset(core.musicStatus.playedBgm)) { - core.musicStatus.playedBgm.pause(); +core.prototype.pauseBgm = function () { + // 直接暂停播放 + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + core.musicStatus.isPlaying = false; + } + catch (e) { + console.log("无法暂停BGM "+bgm); + console.log(e); + } +} + +core.prototype.resumeBgm = function () { + // 恢复BGM + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].play(); + core.musicStatus.isPlaying = true; + } + else { + if (core.bgms.length>0) { + core.playBgm(core.bgms[0]); + core.musicStatus.isPlaying = true; + } + } + } + catch (e) { + console.log("无法恢复BGM "+bgm); + console.log(e); + } +} + +core.prototype.playSound = function (sound) { + + // 如果不允许播放 + if (!core.musicStatus.soundStatus) return; + // 音频不存在 + if (!core.isset(core.material.sounds[sound])) return; + + try { + if (core.musicStatus.audioContext != null) { + var source = core.musicStatus.audioContext.createBufferSource(); + source.buffer = core.material.sounds[sound]; + source.connect(core.musicStatus.audioContext.destination); + try { + source.start(0); + } + catch (e) { + try { + source.noteOn(0); + } + catch (ee) { + } + } + } + else { + core.material.sounds[sound].play(); + } + } + catch (eee) { + console.log("无法播放SE "+bgm); + console.log(eee); } - core.musicStatus.playedBgm = core.material.sounds[bgmType][bgmName]; - if (core.musicStatus.soundStatus) - core.musicStatus.playedBgm.play(); } core.prototype.changeSoundStatus = function () { @@ -3289,9 +3661,9 @@ core.prototype.hide = function (obj, speed, callback) { ////// 状态栏相关 ////// core.prototype.clearStatusBar = function() { - var statusList = ['floor', 'hp', 'atk', 'def', 'mdef', 'money', 'experience', 'yellowKey', 'blueKey', 'redKey', 'poison', 'weak', 'curse', 'hard']; + var statusList = ['floor', 'lv', 'hp', 'atk', 'def', 'mdef', 'money', 'experience', 'up', 'yellowKey', 'blueKey', 'redKey', 'poison', 'weak', 'curse', 'hard']; statusList.forEach(function (e) { - core.statusBar[e].innerHTML = ""; + core.statusBar[e].innerHTML = " "; }); core.statusBar.image.book.style.opacity = 0.3; core.statusBar.image.fly.style.opacity = 0.3; @@ -3302,16 +3674,33 @@ core.prototype.clearStatusBar = function() { */ core.prototype.updateStatusBar = function () { - // 上限999999 + // 检查等级 + core.checkLvUp(); + + // 检查HP上限 if (core.values.HPMAX>0) { core.setStatus('hp', Math.min(core.values.HPMAX, core.getStatus('hp'))); } - // core.statusBar.floor.innerHTML = core.maps.maps[core.status.floorId].name; + // 更新领域、阻击、显伤 + core.updateCheckBlock(); + + var lvName = core.getLvName(); + core.statusBar.lv.innerHTML = lvName; + if (/^[+-]?\d+$/.test(lvName)) + core.statusBar.lv.style.fontStyle = 'italic'; + else core.statusBar.lv.style.fontStyle = 'normal'; + var statusList = ['hp', 'atk', 'def', 'mdef', 'money', 'experience']; statusList.forEach(function (item) { core.statusBar[item].innerHTML = core.getStatus(item); }); + // 进阶 + if (core.flags.enableLevelUp && core.status.hero.lv=core.firstData.levelUp.length) return; + // 计算下一个所需要的数值 + var need=core.firstData.levelUp[core.status.hero.lv].need; + if (!core.isset(need)) return; + if (core.status.hero.experience>=need) { + // 升级 + core.status.hero.lv++; + var effect = core.firstData.levelUp[core.status.hero.lv-1].effect; + if (typeof effect == "string") { + if (effect.indexOf("function")==0) { + eval("("+effect+")()"); + } + else { + effect.split(";").forEach(function (t) { + core.doEffect(t); + }); + } + } + else if (effect instanceof Function) { + effect(); + } + core.checkLvUp(); + } +} + core.prototype.resize = function(clientWidth, clientHeight) { // 默认画布大小 @@ -3364,18 +3778,22 @@ core.prototype.resize = function(clientWidth, clientHeight) { statusWidth, statusHeight, statusMaxWidth,statusLabelsLH, toolBarWidth, toolBarHeight, toolBarTop, toolBarBorder, toolsWidth, toolsHeight,toolsMargin,toolsPMaxwidth, - fontSize, margin; + fontSize, toolbarFontSize, margin; - var count = 9; + var count = 11; + if (!core.flags.enableFloor) count--; + if (!core.flags.enableLv) count--; if (!core.flags.enableMDef) count--; + if (!core.flags.enableMoney) count--; if (!core.flags.enableExperience) count--; + if (!core.flags.enableLevelUp) count--; if (!core.flags.enableDebuff) count--; - var statusLineHeight = BASE_LINEHEIGHT * 9/count; + var statusLineHeight = BASE_LINEHEIGHT * 9 / count; + var statusLineFontSize = DEFAULT_FONT_SIZE; + if (count>9) statusLineFontSize = statusLineFontSize * 9 / count; - var shopDisplay, mdefDisplay, expDisplay; - mdefDisplay = core.flags.enableMDef ? 'block' : 'none'; - expDisplay = core.flags.enableExperience ? 'block' : 'none'; + var shopDisplay; statusBarBorder = '3px #fff solid'; toolBarBorder = '3px #fff solid'; @@ -3395,13 +3813,13 @@ core.prototype.resize = function(clientWidth, clientHeight) { var scale = core.domStyle.scale var tempWidth = DEFAULT_CANVAS_WIDTH * scale; - fontSize = DEFAULT_FONT_SIZE * scale; if(!isHorizontal){ //竖屏 core.domStyle.screenMode = 'vertical'; //显示快捷商店图标 shopDisplay = 'block'; //判断应该显示几行 - var col = core.flags.enableMDef || core.flags.enableExperience || core.flags.enableDebuff ? 3 : 2; + // var col = core.flags.enableMDef || core.flags.enableExperience || core.flags.enableDebuff ? 3 : 2; + var col = parseInt((count-1)/3)+1; var tempTopBarH = scale * (BASE_LINEHEIGHT * col + SPACE * 2) + 6; var tempBotBarH = scale * (BASE_LINEHEIGHT + SPACE * 4) + 6; @@ -3428,6 +3846,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { margin = scale * SPACE * 2; toolsMargin = scale * SPACE * 4; + fontSize = DEFAULT_FONT_SIZE * scale; + toolbarFontSize = DEFAULT_FONT_SIZE * scale; }else { //横屏 core.domStyle.screenMode = 'horizontal'; shopDisplay = 'none'; @@ -3445,6 +3865,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { toolBarTop = scale*statusLineHeight * count + SPACE * 2; toolBarBorder = '3px #fff solid'; toolsHeight = scale * BASE_LINEHEIGHT; + fontSize = statusLineFontSize * scale; + toolbarFontSize = DEFAULT_FONT_SIZE * scale; borderRight = ''; statusMaxWidth = scale * DEFAULT_BAR_WIDTH; toolsPMaxwidth = scale * DEFAULT_BAR_WIDTH; @@ -3474,7 +3896,8 @@ core.prototype.resize = function(clientWidth, clientHeight) { toolsHeight = BASE_LINEHEIGHT; borderRight = ''; - fontSize = DEFAULT_FONT_SIZE; + fontSize = statusLineFontSize; + toolbarFontSize = DEFAULT_FONT_SIZE; statusMaxWidth = DEFAULT_BAR_WIDTH; toolsPMaxwidth = DEFAULT_BAR_WIDTH * .9; margin = SPACE * 2; @@ -3499,17 +3922,24 @@ core.prototype.resize = function(clientWidth, clientHeight) { height: canvasWidth + unit, top: canvasTop + unit, right: 0, - // left: canvasLeft + unit, - border: '3px #fff solid', } }, + { + id: 'curtain', + rules: { + width: (canvasWidth - SPACE*2) + unit, + height:(canvasWidth - SPACE*2) + unit, + top: (canvasTop + SPACE) + unit, + right: SPACE + unit, + } + }, { id: 'floorMsgGroup', rules:{ width: (canvasWidth - SPACE*2) + unit, - height:(canvasWidth - SPACE*2) + unit, - top: (canvasTop + SPACE) + unit, + height: (gameGroupHeight - SPACE*2) + unit, + top: SPACE + unit, right: SPACE + unit, } }, @@ -3555,7 +3985,7 @@ core.prototype.resize = function(clientWidth, clientHeight) { borderBottom: toolBarBorder, borderLeft: toolBarBorder, borderRight: borderRight, - fontSize: fontSize + unit + fontSize: toolbarFontSize + unit } }, { @@ -3574,17 +4004,53 @@ core.prototype.resize = function(clientWidth, clientHeight) { } }, { - id: 'expCol', + id: 'floorCol', rules: { - display: expDisplay + display: core.flags.enableFloor ? 'block': 'none' + } + }, + { + id: 'lvCol', + rules: { + display: core.flags.enableLv ? 'block': 'none' } }, { id: 'mdefCol', rules: { - display: mdefDisplay + display: core.flags.enableMDef ? 'block': 'none' } }, + { + id: 'moneyCol', + rules: { + display: core.flags.enableMoney ? 'block': 'none' + } + }, + { + id: 'expCol', + rules: { + display: core.flags.enableExperience ? 'block': 'none' + } + }, + { + id: 'upCol', + rules: { + display: core.flags.enableLevelUp ? 'block': 'none' + } + }, + { + 'id': 'debuffCol', + rules: { + display: core.flags.enableDebuff ? 'block': 'none' + } + }, + { + id: 'hard', + rules: { + lineHeight: toolsHeight + unit + } + } ] core.domRenderer(); } diff --git a/libs/data.js b/libs/data.js index fab64b02..870ffb38 100644 --- a/libs/data.js +++ b/libs/data.js @@ -10,12 +10,13 @@ data.prototype.init = function() { "floorId": "sample0", // 初始楼层ID "hero": { // 勇士初始数据 "name": "阳光", // 勇士名;可以改成喜欢的 + 'lv': 1, // 初始等级,该项必须为正整数 "hp": 1000, // 初始生命值 "atk": 100, // 初始攻击 "def": 100, // 初始防御 "mdef": 100, // 初始魔防 "money": 100, // 初始金币 - "experience": 1000, // 初始经验 + "experience": 0, // 初始经验 "items": { // 初始道具个数 "keys": { "yellowKey": 0, @@ -57,10 +58,11 @@ data.prototype.init = function() { {"text": "防御+4", "effect": "status:def+=4"}, {"text": "魔防+10", "effect": "status:mdef+=10"} // effect只能对status和item进行操作,不能修改flag值。 - // 中间只能用+=符号(也就是只能增加某个属性或道具) + // 必须是X+=Y的形式,其中Y可以是一个表达式,以status:xxx或item:xxx为参数 // 其他effect样例: // "item:yellowKey+=1" 黄钥匙+1 // "item:pickaxe+=3" 破墙镐+3 + // "status:hp+=2*(status:atk+status:def)" 将生命提升攻防和的数值的两倍 ] }, "expShop1": { // 商店唯一ID @@ -73,13 +75,33 @@ data.prototype.init = function() { "choices": [ // 在choices中写need,可以针对每个选项都有不同的需求。 // 这里的need同样可以以times作为参数,比如 "need": "100+20*times" - {"text": "等级+1", "need": "100", "effect": "status:hp+=1000;status:atk+=7;status:def+=7"}, + {"text": "等级+1", "need": "100", "effect": "status:lv+=1;status:hp+=1000;status:atk+=7;status:def+=7"}, // 多个effect直接以分号分开即可。如上面的意思是生命+1000,攻击+7,防御+7。 {"text": "攻击+5", "need": "30", "effect": "status:atk+=5"}, {"text": "防御+5", "need": "30", "effect": "status:def+=5"}, ] }, }, + "levelUp": [ // 经验升级所需要的数值,是一个数组 + {}, // 第一项为初始等级,可以简单留空,也可以写name + + // 每一个里面可以含有三个参数 need, name, effect + // need为所需要的经验数值,是一个正整数。请确保need所需的依次递增 + // name为该等级的名称,也可以省略代表使用系统默认值;本项将显示在状态栏中 + // effect为本次升级所执行的操作,可由若干项组成,由分号分开 + // 其中每一项写法和上面的商店完全相同,同样必须是X+=Y的形式,Y是一个表达式,同样可以使用status:xxx或item:xxx代表勇士的某项数值/道具个数 + {"need": 20, "name": "第二级", "effect": "status:hp+=2*(status:atk+status:def);status:atk+=10;status:def+=10"}, // 先将生命提升攻防和的2倍;再将攻击+10,防御+10 + + // effect也允许写一个function,代表本次升级将会执行的操作 + {"need": 40, "effect": function () { + core.drawText("恭喜升级!"); + core.status.hero.hp *= 2; + core.status.hero.atk += 100; + core.status.hero.def += 100; + }}, + + // 依次往下写需要的数值即可 + ] } // 各种数值;一些数值可以在这里设置 this.values = { @@ -117,17 +139,26 @@ data.prototype.init = function() { } // 系统FLAG,在游戏运行中中请不要修改它。 this.flags = { - /****** 角色状态相关 ******/ - "enableMDef": true, // 是否涉及勇士的魔防值;如果此项为false,则状态栏不会显示勇士的魔防值 - "enableExperience": true, // 是否涉及经验值;如果此项为false,则状态栏和怪物手册均将不会显示经验值 + /****** 状态栏相关 ******/ + "enableFloor": true, // 是否在状态栏显示当前楼层 + "enableLv": false, // 是否在状态栏显示当前等级 + "enableMDef": true, // 是否在状态栏及战斗界面显示魔防(护盾) + "enableMoney": true, // 是否在状态栏、怪物手册及战斗界面显示金币 + "enableExperience": true, // 是否在状态栏、怪物手册及战斗界面显示经验 + "enableLevelUp": false, // 是否允许等级提升(进阶);如果上面enableExperience为false,则此项恒视为false "enableDebuff": true, // 是否涉及毒衰咒;如果此项为false则不会在状态栏中显示毒衰咒的debuff + ////// 上述的几个开关将直接影响状态栏的显示效果 ////// /****** 道具相关 ******/ "flyNearStair": true, // 是否需要在楼梯边使用传送器 "pickaxeFourDirections": true, // 使用破墙镐是否四个方向都破坏;如果false则只破坏面前的墙壁 "bombFourDirections": true, // 使用炸弹是否四个方向都会炸;如果false则只炸面前的怪物(即和圣锤等价) "bigKeyIsBox": false, // 如果此项为true,则视为钥匙盒,红黄蓝钥匙+1;若为false,则视为大黄门钥匙 + /****** 怪物相关 ******/ + "enableNegativeDamage": true, // 是否支持负伤害(回血) + "zoneSquare": false, // 领域类型。如果此项为true则为九宫格伤害,为false则为十字伤害 /****** 系统相关 ******/ "startDirectly": false, // 点击“开始游戏”后是否立刻开始游戏而不显示难度选择界面 + "canOpenBattleAnimate": true, // 是否允许用户开启战斗过程;如果此项为false,则下面两项均强制视为false "showBattleAnimateConfirm": true, // 是否在游戏开始时提供“是否开启战斗动画”的选项 "battleAnimate": true, // 是否默认显示战斗动画;用户可以手动在菜单栏中开关 "displayEnemyDamage": true, // 是否地图怪物显伤;用户可以手动在菜单栏中开关 diff --git a/libs/enemys.js b/libs/enemys.js index a5e8b71f..f9e29705 100644 --- a/libs/enemys.js +++ b/libs/enemys.js @@ -5,10 +5,10 @@ function enemys() { enemys.prototype.init = function () { // 怪物属性初始化定义: this.enemys = { - 'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 0, 'special': 0}, + 'greenSlime': {'name': '绿头怪', 'hp': 100, 'atk': 120, 'def': 0, 'money': 1, 'experience': 1, 'special': [1,5,7,8]}, 'redSlime': {'name': '红头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'blackSlime': {'name': '青头怪', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 9}, + 'slimelord': {'name': '怪王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': [1,9]}, 'bat': {'name': '小蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 2, 'experience': 0, 'special': 1}, 'bigBat': {'name': '大蝙蝠', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redBat': {'name': '红蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 5, 'experience': 0, 'special': 4}, @@ -23,8 +23,8 @@ enemys.prototype.init = function () { 'slimeMan': {'name': '影子战士', 'hp': 100, 'atk': 0, 'def': 0, 'money': 11, 'experience': 0, 'special': 10}, // 模仿怪的攻防设为0就好 'bluePriest': {'name': '初级法师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 3, 'experience': 0, 'special': 2}, 'redPriest': {'name': '高级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100}, // 领域怪需要加value表示领域伤害的数值 - 'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200}, + 'brownWizard': {'name': '初级巫师', 'hp': 100, 'atk': 120, 'def': 0, 'money': 16, 'experience': 0, 'special': 15, 'value': 100, 'zoneSquare': true}, // 领域怪需要加value表示领域伤害的数值;zoneSquare代表是否九宫格伤害 + 'redWizard': {'name': '高级巫师', 'hp': 1000, 'atk': 1200, 'def': 0, 'money': 160, 'experience': 0, 'special': 15, 'value': 200, 'range': 2}, // range可选,代表领域伤害的范围;不加默认为1 'yellowGuard': {'name': '初级卫兵', 'hp': 100, 'atk': 120, 'def': 0, 'money': 10, 'experience': 0, 'special': 0}, 'blueGuard': {'name': '中级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redGuard': {'name': '高级卫兵', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, @@ -41,7 +41,7 @@ enemys.prototype.init = function () { 'poisonSkeleton': {'name': '紫骷髅', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'poisonBat': {'name': '紫蝙蝠', 'hp': 100, 'atk': 120, 'def': 0, 'money': 14, 'experience': 0, 'special': 13}, 'steelRock': {'name': '铁面人', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'skeletonPriest': {'name': '骷髅法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, + 'skeletonPriest': {'name': '骷髅法师', 'hp': 100, 'atk': 100, 'def': 0, 'money': 0, 'experience': 0, 'special': 18, 'value': 20}, 'skeletonKing': {'name': '骷髅王', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'skeletonWizard': {'name': '骷髅巫师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'redSkeletonCaption': {'name': '骷髅武士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'special': 0}, @@ -58,7 +58,7 @@ enemys.prototype.init = function () { 'badPrincess': {'name': '痛苦魔女', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'badFairy': {'name': '黑暗仙子', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'grayPriest': {'name': '中级法师', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, - 'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6}, + 'redSwordsman': {'name': '剑王', 'hp': 100, 'atk': 120, 'def': 0, 'money': 7, 'experience': 0, 'special': 6, 'n': 8}, // 多连击需要在后面指定n代表是几连击 'whiteGhost': {'name': '水银战士', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, 'poisonZombie': {'name': '绿兽人', 'hp': 100, 'atk': 120, 'def': 0, 'money': 13, 'experience': 0, 'special': 12}, 'magicDragon': {'name': '魔龙', 'hp': 0, 'atk': 0, 'def': 0, 'money': 0, 'experience': 0, 'special': 0}, @@ -76,19 +76,20 @@ enemys.prototype.getEnemys = function (enemyId) { } enemys.prototype.hasSpecial = function (special, test) { - return special!=0 && (special%100 == test || this.hasSpecial(parseInt(special/100), test)); + return (special instanceof Array)?special.indexOf(test)>=0:(special!=0&&(special%100==test||this.hasSpecial(parseInt(special/100), test))); } enemys.prototype.getSpecialText = function (enemyId) { if (enemyId == undefined) return ""; - var special = this.enemys[enemyId].special; + var enemy = this.enemys[enemyId]; + var special = enemy.special; var text = []; if (this.hasSpecial(special, 1)) text.push("先攻"); if (this.hasSpecial(special, 2)) text.push("魔攻"); if (this.hasSpecial(special, 3)) text.push("坚固"); if (this.hasSpecial(special, 4)) text.push("2连击"); if (this.hasSpecial(special, 5)) text.push("3连击"); - if (this.hasSpecial(special, 6)) text.push("4连击"); + if (this.hasSpecial(special, 6)) text.push((enemy.n||4)+"连击"); if (this.hasSpecial(special, 7)) text.push("破甲"); if (this.hasSpecial(special, 8)) text.push("反击"); if (this.hasSpecial(special, 9)) text.push("净化"); @@ -100,14 +101,57 @@ enemys.prototype.getSpecialText = function (enemyId) { if (this.hasSpecial(special, 15)) text.push("领域"); if (this.hasSpecial(special, 16)) text.push("夹击"); if (this.hasSpecial(special, 17)) text.push("仇恨"); - return text.join(" "); + if (this.hasSpecial(special, 18)) text.push("阻击"); + if (this.hasSpecial(special, 19)) text.push("自爆"); + if (this.hasSpecial(special, 20)) text.push("无敌"); + return text; +} + +////// 获得每个属性的文字提示 ////// +enemys.prototype.getSpecialHint = function (enemy, special) { + if (!core.isset(special)) { + var hints = []; + for (var i=1;i<100;i++) { + if (this.hasSpecial(enemy.special, i)) { + var hint=this.getSpecialHint(enemy, i); + if (hint!='') + hints.push(hint); + } + } + return hints; + } + + switch (special) { + case 1: return "先攻:怪物首先攻击"; + case 2: return "魔攻:怪物无视勇士的魔防"; + case 3: return "坚固:勇士每回合最多只能对怪物造成1点伤害"; + case 4: return "2连击:怪物每回合攻击2次"; + case 5: return "3连击:怪物每回合攻击3次"; + case 6: return (enemy.n||4)+"连击: 怪物每回合攻击"+(enemy.n||4)+"次"; + case 7: return "破甲:战斗前,怪物附加角色防御的"+parseInt(100*core.values.breakArmor)+"%作为伤害"; + case 8: return "反击:战斗时,怪物每回合附加角色攻击的"+parseInt(100*core.values.counterAttack)+"%作为伤害,无视角色防御"; + case 9: return "净化:战斗前,怪物附加勇士魔防的"+core.values.purify+"倍作为伤害"; + case 10: return "模仿:怪物的攻防和勇士攻防相等"; + case 11: return "吸血:战斗前,怪物首先吸取角色的"+parseInt(100*enemy.value)+"%生命作为伤害"; + case 12: return "中毒:战斗后,勇士陷入中毒状态,每一步损失生命"+core.values.poisonDamage+"点"; + case 13: return "衰弱:战斗后,勇士陷入衰弱状态,攻防暂时下降"+core.values.weakValue+"点"; + case 14: return "诅咒:战斗后,勇士陷入诅咒状态,战斗无法获得金币和经验"; + case 15: return "领域:经过怪物周围"+(enemy.range||1)+"格时自动减生命"+enemy.value+"点"; + case 16: return "夹击:经过两只相同的怪物中间,勇士生命值变成一半"; + case 17: return "仇恨:战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得"+core.values.hatred+"点仇恨值)"; + case 18: return "阻击:经过怪物的十字领域时自动减生命"+enemy.value+"点,同时怪物后退一格"; + case 19: return "自爆:战斗后勇士的生命值变成1"; + case 20: return "无敌:勇士无法打败怪物,除非拥有十字架"; + default: break; + } + return "" } enemys.prototype.getDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; var hero_atk = core.status.hero.atk, hero_def = core.status.hero.def, hero_mdef = core.status.hero.mdef; var mon_hp = monster.hp, mon_atk = monster.atk, mon_def = monster.def, mon_special = monster.special; - var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special); + var damage = this.calDamage(hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, monster.n); if (damage == 999999999) return damage; return damage + this.getExtraDamage(monster); } @@ -130,12 +174,12 @@ enemys.prototype.getCritical = function (monsterId) { var monster = core.material.enemys[monsterId]; if (this.hasSpecial(monster.special, 3) || this.hasSpecial(monster.special, 10)) return "???"; var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); - if (last == 0) return 0; + monster.hp, monster.atk, monster.def, monster.special, monster.n); + if (last <= 0) return 0; for (var i = core.status.hero.atk + 1; i <= monster.hp + monster.def; i++) { var damage = this.calDamage(i, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); if (damage < last) return i - core.status.hero.atk; last = damage; @@ -147,36 +191,41 @@ enemys.prototype.getCritical = function (monsterId) { enemys.prototype.getCriticalDamage = function (monsterId) { var c = this.getCritical(monsterId); if (c == '???') return '???'; - if (c == 0) return 0; + if (c <= 0) return 0; var monster = core.material.enemys[monsterId]; - // if (c<=0) return 0; var last = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); if (last == 999999999) return '???'; return last - this.calDamage(core.status.hero.atk + c, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special); + monster.hp, monster.atk, monster.def, monster.special, monster.n); } // 1防减伤计算 enemys.prototype.getDefDamage = function (monsterId) { var monster = core.material.enemys[monsterId]; - return this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special) - - this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef, - monster.hp, monster.atk, monster.def, monster.special) + var nowDamage = this.calDamage(core.status.hero.atk, core.status.hero.def, core.status.hero.mdef, + monster.hp, monster.atk, monster.def, monster.special, monster.n); + var nextDamage = this.calDamage(core.status.hero.atk, core.status.hero.def + 1, core.status.hero.mdef, + monster.hp, monster.atk, monster.def, monster.special, monster.n); + if (nowDamage == 999999999 || nextDamage == 999999999) return "???"; + return nowDamage - nextDamage; } -enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special) { - // 魔攻 - if (this.hasSpecial(mon_special,2)) hero_def = 0; - // 坚固 - if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1; +enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mon_atk, mon_def, mon_special, n) { + + if (this.hasSpecial(mon_special, 20) && !core.hasItem("cross")) // 如果是无敌属性,且勇士未持有十字架 + return 999999999; // 返回无限大 + // 模仿 if (this.hasSpecial(mon_special,10)) { mon_atk = hero_atk; mon_def = hero_def; } + // 魔攻 + if (this.hasSpecial(mon_special,2)) hero_def = 0; + // 坚固 + if (this.hasSpecial(mon_special,3) && mon_def < hero_atk - 1) mon_def = hero_atk - 1; if (hero_atk <= mon_def) return 999999999; // 不可战斗时请直接返回999999999 var per_damage = mon_atk - hero_def; @@ -185,7 +234,7 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo if (this.hasSpecial(mon_special, 4)) per_damage *= 2; if (this.hasSpecial(mon_special, 5)) per_damage *= 3; - if (this.hasSpecial(mon_special, 6)) per_damage *= 4; + if (this.hasSpecial(mon_special, 6)) per_damage *= (n||4); var counterDamage = 0; // 反击 @@ -202,11 +251,7 @@ enemys.prototype.calDamage = function (hero_atk, hero_def, hero_mdef, mon_hp, mo var ans = damage + turn * per_damage + (turn + 1) * counterDamage; ans -= hero_mdef; - // 魔防回血 - // return ans; - - // 魔防不回血 - return ans <= 0 ? 0 : ans; + return core.flags.enableNegativeDamage?ans:Math.max(0, ans); } // 获得当前楼层的怪物列表 @@ -229,6 +274,10 @@ enemys.prototype.getCurrentEnemys = function () { mon_def=core.status.hero.def; } + var specialText = core.enemys.getSpecialText(monsterId); + if (specialText.length>=3) specialText = "多属性..."; + else specialText = specialText.join(" "); + enemys.push({ 'id': monsterId, 'name': monster.name, @@ -237,7 +286,7 @@ enemys.prototype.getCurrentEnemys = function () { 'def': mon_def, 'money': monster.money, 'experience': monster.experience, - 'special': core.enemys.getSpecialText(monsterId), + 'special': specialText, 'damage': this.getDamage(monsterId), 'critical': this.getCritical(monsterId), 'criticalDamage': this.getCriticalDamage(monsterId), diff --git a/libs/events.js b/libs/events.js index 91091425..cd866e90 100644 --- a/libs/events.js +++ b/libs/events.js @@ -20,12 +20,11 @@ events.prototype.init = function () { callback(); }, 'changeFloor': function (data, core, callback) { - var heroLoc = null; - if (core.isset(data.event.data.loc)) { + var heroLoc = {}; + if (core.isset(data.event.data.loc)) heroLoc = {'x': data.event.data.loc[0], 'y': data.event.data.loc[1]}; - if (core.isset(data.event.data.direction)) - heroLoc.direction = data.event.data.direction; - } + if (core.isset(data.event.data.direction)) + heroLoc.direction = data.event.data.direction; core.changeFloor(data.event.data.floorId, data.event.data.stair, heroLoc, data.event.data.time, callback); }, @@ -134,6 +133,17 @@ events.prototype.afterChangeFloor = function (floorId) { this.doEvents(core.floors[floorId].firstArrive); core.setFlag("visited_"+floorId, true); } + + // 播放BGM + if (floorId == 'sample0') { + core.playBgm('bgm.mp3'); + } + if (floorId == 'sample1') { + core.playBgm('star.mid'); + } + if (floorId == 'sample2') { + core.playBgm('qianjin.mid'); + } } ////// 实际事件的处理 ////// @@ -286,11 +296,6 @@ events.prototype.doAction = function() { block = block.block; if (core.isset(block.event) && block.event.trigger=='action') { // 触发 - /* - core.status.event = {'id': 'action', 'data': { - 'list': core.clone(block.event.data), 'x': block.x, 'y': block.y, 'callback': core.status.event.data.callback - }} - */ core.status.event.data.list = core.clone(block.event.data); core.status.event.data.x=block.x; core.status.event.data.y=block.y; @@ -299,11 +304,21 @@ events.prototype.doAction = function() { this.doAction(); break; case "playSound": - var name=data.name.split("."); - if (name.length==2) - core.playSound(name[0],name[1]); + core.playSound(data.name); this.doAction(); break; + case "playBgm": + core.playBgm(data.name); + this.doAction(); + break + case "pauseBgm": + core.pauseBgm(); + this.doAction(); + break + case "resumeBgm": + core.resumeBgm(); + this.doAction(); + break case "setValue": try { var value=core.calValue(data.value); @@ -330,7 +345,6 @@ events.prototype.doAction = function() { core.status.hero.hp=0; core.updateStatusBar(); core.events.lose('damage'); - } else { core.updateStatusBar(); @@ -354,8 +368,14 @@ events.prototype.doAction = function() { core.events.lose(data.reason); break; case "function": - if (core.isset(data["function"])) - data["function"](); + var func = data["function"]; + if (core.isset(func)) { + if ((typeof func == "string") && func.indexOf("function")==0) { + eval('('+func+')()'); + } + else if (func instanceof Function) + func(); + } this.doAction(); break; case "update": @@ -390,7 +410,12 @@ events.prototype.doAction = function() { ////// 往当前事件列表之前添加一个或多个事件 ////// events.prototype.insertAction = function (action) { - core.unshift(core.status.event.data.list, action) + if (core.status.event.id == null) { + this.doEvents(action); + } + else { + core.unshift(core.status.event.data.list, action) + } } ////// 打开商店 ////// @@ -497,6 +522,30 @@ events.prototype.useItem = function(itemId) { else core.drawTip("当前无法使用"+core.material.items[itemId].name); } +////// 加点 ////// +events.prototype.addPoint = function (enemy) { + var point = enemy.point; + if (!core.isset(point) || point<=0) return []; + + // 加点,返回一个choices事件 + return [ + {"type": "choices", + "choices": [ + {"text": "生命+"+(200*point), "action": [ + {"type": "setValue", "name": "status:hp", "value": "status:hp+"+(200*point)} + ]}, + {"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)} + ]}, + ] + } + ]; + +} + /****** 打完怪物 ******/ events.prototype.afterBattle = function(enemyId,x,y,callback) { @@ -520,47 +569,63 @@ events.prototype.afterBattle = function(enemyId,x,y,callback) { if (core.enemys.hasSpecial(special, 17)) { core.setFlag('hatred', parseInt(core.getFlag('hatred', 0)/2)); } + // 自爆 + if (core.enemys.hasSpecial(special, 19)) { + core.status.hero.hp = 1; + } // 增加仇恨值 core.setFlag('hatred', core.getFlag('hatred',0)+core.values.hatred); core.updateStatusBar(); - // 如果已有事件正在处理中 - if (core.status.lockControl) { - if (core.isset(callback)) callback(); - return; + + // 事件的处理 + var todo = []; + // 如果不为阻击,且该点存在,且有事件 + if (!core.enemys.hasSpecial(special, 18) && core.isset(x) && core.isset(y)) { + var event = core.floors[core.status.floorId].afterBattle[x+","+y]; + if (core.isset(event)) { + // 插入事件 + core.unshift(todo, event); + } + } + // 如果有加点 + var point = core.material.enemys[enemyId].point; + if (core.isset(point) && point>0) { + core.unshift(todo, core.events.addPoint(core.material.enemys[enemyId])); } - // 检查处理后的事件。 - var event = core.floors[core.status.floorId].afterBattle[x+","+y]; - if (core.isset(event)) { - core.events.doEvents(event, x, y, callback); + // 如果事件不为空,将其插入 + if (todo.length>0) { + this.insertAction(todo); } - //继续行走 - else { + + // 如果已有事件正在处理中 + if (core.status.event.id == null) { core.continueAutomaticRoute(); - if (core.isset(callback)) callback(); } + if (core.isset(callback)) callback(); + } /****** 开完门 ******/ events.prototype.afterOpenDoor = function(doorId,x,y,callback) { - // 如果已有事件正在处理中 - if (core.status.lockControl) { - if (core.isset(callback)) callback(); - return; + var todo = []; + if (core.isset(x) && core.isset(y)) { + var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y]; + if (core.isset(event)) { + core.unshift(todo, event); + } } - // 检查处理后的事件。 - var event = core.floors[core.status.floorId].afterOpenDoor[x+","+y]; - if (core.isset(event)) { - core.events.doEvents(event, x, y, callback); + if (todo.length>0) { + this.insertAction(todo); } - //继续行走 - else { + + if (core.status.event.id == null) { core.continueAutomaticRoute(); - if (core.isset(callback)) callback(); } + if (core.isset(callback)) callback(); } /****** 经过路障 ******/ @@ -613,6 +678,12 @@ events.prototype.changeLight = function(x, y) { // 改变灯后的事件 events.prototype.afterChangeLight = function(x,y) { +} + +// 使用炸弹/圣锤后的事件 +events.prototype.afterUseBomb = function () { + + } // 存档事件前一刻的处理 @@ -641,6 +712,13 @@ events.prototype.keyDownCtrl = function () { } } +events.prototype.clickConfirmBox = function (x,y) { + if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) + core.status.event.data.yes(); + if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) + core.status.event.data.no(); +} + events.prototype.keyUpConfirmBox = function (keycode) { if (keycode==37) { core.status.event.selection=0; @@ -663,12 +741,6 @@ events.prototype.keyUpConfirmBox = function (keycode) { } } } -events.prototype.clickConfirmBox = function (x,y) { - if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) - core.status.event.data.yes(); - if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) - core.status.event.data.no(); -} // 正在处理事件时的点击操作... events.prototype.clickAction = function (x,y) { @@ -733,22 +805,38 @@ events.prototype.keyUpAction = function (keycode) { events.prototype.clickBook = function(x,y) { // 上一页 if ((x == 3 || x == 4) && y == 12) { - core.ui.drawEnemyBook(core.status.event.data - 1); + core.ui.drawEnemyBook(core.status.event.data - 6); + return; } // 下一页 if ((x == 8 || x == 9) && y == 12) { - core.ui.drawEnemyBook(core.status.event.data + 1); + core.ui.drawEnemyBook(core.status.event.data + 6); + return; } // 返回 if (x>=10 && x<=12 && y==12) { core.ui.closePanel(true); + return; + } + // 怪物信息 + // var index = parseInt(y/2); + var data = core.status.event.data; + if (core.isset(data) && y<12) { + var page=parseInt(data/6); + var index=6*page+parseInt(y/2); + core.ui.drawEnemyBook(index); + core.ui.drawBookDetail(index); } return; } events.prototype.keyDownBook = function (keycode) { - if (keycode==37 || keycode==38) core.ui.drawEnemyBook(core.status.event.data - 1); - else if (keycode==39 || keycode==40) core.ui.drawEnemyBook(core.status.event.data + 1); + if (keycode==37) core.ui.drawEnemyBook(core.status.event.data-6); + if (keycode==38) core.ui.drawEnemyBook(core.status.event.data-1); + if (keycode==39) core.ui.drawEnemyBook(core.status.event.data+6); + if (keycode==40) core.ui.drawEnemyBook(core.status.event.data+1); + if (keycode==33) core.ui.drawEnemyBook(core.status.event.data-6); + if (keycode==34) core.ui.drawEnemyBook(core.status.event.data+6); return; } @@ -757,9 +845,22 @@ events.prototype.keyUpBook = function (keycode) { core.ui.closePanel(true); return; } + if (keycode==13 || keycode==32 || keycode==67) { + var data=core.status.event.data; + if (core.isset(data)) { + this.clickBook(6, 2*(data%6)); + } + return; + } +} + +events.prototype.clickBookDetail = function (x,y) { + core.clearMap('data', 0, 0, 416, 416); + + core.status.event.id = 'book'; + } -// 飞行器 events.prototype.clickFly = function(x,y) { if ((x==10 || x==11) && y==9) core.ui.drawFly(core.status.event.data-1); if ((x==10 || x==11) && y==5) core.ui.drawFly(core.status.event.data+1); @@ -818,12 +919,7 @@ events.prototype.clickShop = function(x,y) { // 更新属性 choice.effect.split(";").forEach(function (t) { - if (t.indexOf("status:")==0) { - eval(t.replace("status:", "core.status.hero.")); - } - else if (t.indexOf("item:")==0) { - eval(t.replace("item:", "core.getItem('").replace("+=", "', ")+")"); - } + core.doEffect(t); }); core.updateStatusBar(); shop.times++; @@ -1110,38 +1206,49 @@ events.prototype.keyUpSL = function (keycode) { events.prototype.clickSwitchs = function (x,y) { if (x<5 || x>7) return; var choices = [ - "背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" + "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y> 4; + event.channel = eventTypeByte & 0x0f; + event.type = 'channel'; + switch (eventType) { + case 0x08: + event.subtype = 'noteOff'; + event.noteNumber = param1; + event.velocity = stream.readInt8(); + return event; + case 0x09: + event.noteNumber = param1; + event.velocity = stream.readInt8(); + if (event.velocity == 0) { + event.subtype = 'noteOff'; + } else { + event.subtype = 'noteOn'; + } + return event; + case 0x0a: + event.subtype = 'noteAftertouch'; + event.noteNumber = param1; + event.amount = stream.readInt8(); + return event; + case 0x0b: + event.subtype = 'controller'; + event.controllerType = param1; + event.value = stream.readInt8(); + return event; + case 0x0c: + event.subtype = 'programChange'; + event.programNumber = param1; + return event; + case 0x0d: + event.subtype = 'channelAftertouch'; + event.amount = param1; + return event; + case 0x0e: + event.subtype = 'pitchBend'; + event.value = param1 + (stream.readInt8() << 7); + return event; + default: + throw "Unrecognised MIDI event type: " + eventType + } + } + } + + stream = Stream(data); + var headerChunk = readChunk(stream); + if (headerChunk.id != 'MThd' || headerChunk.length != 6) { + throw "Bad .mid file - header not found"; + } + var headerStream = Stream(headerChunk.data); + var formatType = headerStream.readInt16(); + var trackCount = headerStream.readInt16(); + var timeDivision = headerStream.readInt16(); + + if (timeDivision & 0x8000) { + throw "Expressing time division in SMTPE frames is not supported yet" + } else { + ticksPerBeat = timeDivision; + } + + var header = { + 'formatType': formatType, + 'trackCount': trackCount, + 'ticksPerBeat': ticksPerBeat + } + var tracks = []; + for (var i = 0; i < header.trackCount; i++) { + tracks[i] = []; + var trackChunk = readChunk(stream); + if (trackChunk.id != 'MTrk') { + throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id; + } + var trackStream = Stream(trackChunk.data); + while (!trackStream.eof()) { + var event = readEvent(trackStream); + tracks[i].push(event); + //console.log(event); + } + } + + return { + 'header': header, + 'tracks': tracks + } +} +function Replayer(midiFile, synth) { + var trackStates = []; + var beatsPerMinute = 120; + var ticksPerBeat = midiFile.header.ticksPerBeat; + var channelCount = 16; + var channels = []; + var nextEventInfo; + var samplesToNextEvent; + + function Channel() { + + var generatorsByNote = {}; + var currentProgram = PianoProgram; + + function noteOn(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + /* playing same note before releasing the last one. BOO */ + generatorsByNote[note].noteOff(); /* TODO: check whether we ought to be passing a velocity in */ + } + generator = currentProgram.createNote(note, velocity); + synth.addGenerator(generator); + generatorsByNote[note] = generator; + } + function noteOff(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + generatorsByNote[note].noteOff(velocity); + } + } + function setProgram(programNumber) { + currentProgram = PROGRAMS[programNumber] || PianoProgram; + } + + return { + 'noteOn': noteOn, + 'noteOff': noteOff, + 'setProgram': setProgram + } + } + + function getNextEvent() { + var ticksToNextEvent = null; + var nextEventTrack = null; + var nextEventIndex = null; + + for (var i = 0; i < trackStates.length; i++) { + if ( + trackStates[i].ticksToNextEvent != null + && (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent) + ) { + ticksToNextEvent = trackStates[i].ticksToNextEvent; + nextEventTrack = i; + nextEventIndex = trackStates[i].nextEventIndex; + } + } + if (nextEventTrack != null) { + /* consume event from that track */ + var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex]; + if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) { + trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime; + } else { + trackStates[nextEventTrack].ticksToNextEvent = null; + } + trackStates[nextEventTrack].nextEventIndex += 1; + /* advance timings on all tracks by ticksToNextEvent */ + for (var i = 0; i < trackStates.length; i++) { + if (trackStates[i].ticksToNextEvent != null) { + trackStates[i].ticksToNextEvent -= ticksToNextEvent + } + } + nextEventInfo = { + 'ticksToEvent': ticksToNextEvent, + 'event': nextEvent, + 'track': nextEventTrack + } + var beatsToNextEvent = ticksToNextEvent / ticksPerBeat; + var secondsToNextEvent = beatsToNextEvent / (beatsPerMinute / 60); + samplesToNextEvent += secondsToNextEvent * synth.sampleRate; + } else { + nextEventInfo = null; + samplesToNextEvent = null; + self.finished = true; + } + } + + function generate(samples) { + var data = new Array(samples*2); + var samplesRemaining = samples; + var dataOffset = 0; + + while (true) { + if (samplesToNextEvent != null && samplesToNextEvent <= samplesRemaining) { + /* generate samplesToNextEvent samples, process event and repeat */ + var samplesToGenerate = Math.ceil(samplesToNextEvent); + if (samplesToGenerate > 0) { + synth.generateIntoBuffer(samplesToGenerate, data, dataOffset); + dataOffset += samplesToGenerate * 2; + samplesRemaining -= samplesToGenerate; + samplesToNextEvent -= samplesToGenerate; + } + + handleEvent(); + getNextEvent(); + } else { + /* generate samples to end of buffer */ + if (samplesRemaining > 0) { + synth.generateIntoBuffer(samplesRemaining, data, dataOffset); + samplesToNextEvent -= samplesRemaining; + } + break; + } + } + return data; + } + + function handleEvent() { + var event = nextEventInfo.event; + switch (event.type) { + case 'meta': + switch (event.subtype) { + case 'setTempo': + beatsPerMinute = 60000000 / event.microsecondsPerBeat + } + break; + case 'channel': + switch (event.subtype) { + case 'noteOn': + channels[event.channel].noteOn(event.noteNumber, event.velocity); + break; + case 'noteOff': + channels[event.channel].noteOff(event.noteNumber, event.velocity); + break; + case 'programChange': + //console.log('program change to ' + event.programNumber); + channels[event.channel].setProgram(event.programNumber); + break; + } + break; + } + } + + function reset() { + for (var i = 0; i < midiFile.tracks.length; i++) { + trackStates[i] = { + 'nextEventIndex': 0, + 'ticksToNextEvent': ( + midiFile.tracks[i].length ? + midiFile.tracks[i][0].deltaTime : + null + ) + }; + } + for (var i = 0; i < channelCount; i++) { + channels[i] = Channel(); + } + samplesToNextEvent = 0; + getNextEvent(); + } + + reset(); + + var self = { + 'reset': reset, + 'generate': generate, + 'finished': false + } + return self; +} +/* Wrapper for accessing strings through sequential reads */ +function Stream(str) { + var position = 0; + + function read(length) { + var result = str.substr(position, length); + position += length; + return result; + } + + /* read a big-endian 32-bit integer */ + function readInt32() { + var result = ( + (str.charCodeAt(position) << 24) + + (str.charCodeAt(position + 1) << 16) + + (str.charCodeAt(position + 2) << 8) + + str.charCodeAt(position + 3)); + position += 4; + return result; + } + + /* read a big-endian 16-bit integer */ + function readInt16() { + var result = ( + (str.charCodeAt(position) << 8) + + str.charCodeAt(position + 1)); + position += 2; + return result; + } + + /* read an 8-bit integer */ + function readInt8(signed) { + var result = str.charCodeAt(position); + if (signed && result > 127) result -= 256; + position += 1; + return result; + } + + function eof() { + return position >= str.length; + } + + /* read a MIDI-style variable-length integer + (big-endian value in groups of 7 bits, + with top bit set to signify that another byte follows) + */ + function readVarInt() { + var result = 0; + while (true) { + var b = readInt8(); + if (b & 0x80) { + result += (b & 0x7f); + result <<= 7; + } else { + /* b is the last byte */ + return result + b; + } + } + } + + return { + 'eof': eof, + 'read': read, + 'readInt32': readInt32, + 'readInt16': readInt16, + 'readInt8': readInt8, + 'readVarInt': readVarInt + } +} +function SineGenerator(freq) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var phase = t / period; + var result = Math.sin(phase * 2 * Math.PI); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function SquareGenerator(freq, phase) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var result = ( (t / period) % 1 > phase ? 1 : -1); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) { + var self = {'alive': true} + var attackTime = sampleRate * attackTimeS; + var decayTime = sampleRate * (attackTimeS + decayTimeS); + var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime); + var releaseTime = null; /* not known yet */ + var endTime = null; /* not known yet */ + var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS); + var t = 0; + + self.noteOff = function() { + if (self.released) return; + releaseTime = t; + self.released = true; + endTime = releaseTime + sampleRate * releaseTimeS; + } + + self.generate = function(buf, offset, count) { + if (!self.alive) return; + var input = new Array(count * 2); + for (var i = 0; i < count*2; i++) { + input[i] = 0; + } + child.generate(input, 0, count); + + childOffset = 0; + while(count) { + if (releaseTime != null) { + if (t < endTime) { + /* release */ + while(count && t < endTime) { + var ampl = sustainAmplitude - releaseRate * (t - releaseTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* dead */ + self.alive = false; + return; + } + } else if (t < attackTime) { + /* attack */ + while(count && t < attackTime) { + var ampl = attackAmplitude * t / attackTime; + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else if (t < decayTime) { + /* decay */ + while(count && t < decayTime) { + var ampl = attackAmplitude - decayRate * (t - attackTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* sustain */ + while(count) { + buf[offset++] += input[childOffset++] * sustainAmplitude; + buf[offset++] += input[childOffset++] * sustainAmplitude; + t++; + count--; + } + } + } + } + + return self; +} + +function midiToFrequency(note) { + return 440 * Math.pow(2, (note-69)/12); +} + +PianoProgram = { + 'attackAmplitude': 0.2, + 'sustainAmplitude': 0.1, + 'attackTime': 0.02, + 'decayTime': 0.3, + 'releaseTime': 0.02, + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128), + this.attackTime, this.decayTime, this.releaseTime + ); + } +} + +StringProgram = { + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + 0.5 * (velocity / 128), 0.2 * (velocity / 128), + 0.4, 0.8, 0.4 + ); + } +} + +PROGRAMS = { + 41: StringProgram, + 42: StringProgram, + 43: StringProgram, + 44: StringProgram, + 45: StringProgram, + 46: StringProgram, + 47: StringProgram, + 49: StringProgram, + 50: StringProgram +}; + +function Synth(sampleRate) { + + var generators = []; + + function addGenerator(generator) { + generators.push(generator); + } + + function generate(samples) { + var data = new Array(samples*2); + generateIntoBuffer(samples, data, 0); + return data; + } + + function generateIntoBuffer(samplesToGenerate, buffer, offset) { + for (var i = offset; i < offset + samplesToGenerate * 2; i++) { + buffer[i] = 0; + } + for (var i = generators.length - 1; i >= 0; i--) { + generators[i].generate(buffer, offset, samplesToGenerate); + if (!generators[i].alive) generators.splice(i, 1); + } + } + + return { + 'sampleRate': sampleRate, + 'addGenerator': addGenerator, + 'generate': generate, + 'generateIntoBuffer': generateIntoBuffer + } +} diff --git a/libs/ui.js b/libs/ui.js index 6ac8143a..f9480f9c 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -334,7 +334,8 @@ ui.prototype.drawSwitchs = function() { core.status.event.id = 'switchs'; var choices = [ - "背景音乐:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), + "背景音乐:"+(core.musicStatus.bgmStatus ? "[ON]" : "[OFF]"), + "背景音效:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), "战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"), "怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), "领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), @@ -389,12 +390,12 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) { hero_hp -= core.enemys.getExtraDamage(monster); - if (core.enemys.hasSpecial(mon_special, 2)) hero_def=0; // 魔攻 - if (core.enemys.hasSpecial(mon_special, 3) && mon_def=enemys.length) index=enemys.length-1; var perpage = 6; + var page=parseInt(index/perpage)+1; var totalPage = parseInt((enemys.length - 1) / perpage) + 1; - if (page < 1) page = 1; - if (page > totalPage) page = totalPage; - core.status.event.data = page; + core.status.event.data = index; var start = (page - 1) * perpage, end = Math.min(page * perpage, enemys.length); enemys = enemys.slice(start, end); @@ -796,25 +797,34 @@ ui.prototype.drawEnemyBook = function (page) { core.fillText('ui', enemy.atk, 285, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana'); core.fillText('ui', '防御', 335, 62 * i + 32, '#DDDDDD', '13px Verdana'); core.fillText('ui', enemy.def, 365, 62 * i + 32, '#DDDDDD', 'bold 13px Verdana'); - core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana'); - core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); - var damage_offset = 326; + var expOffset = 165; + if (core.flags.enableMoney) { + core.fillText('ui', '金币', 165, 62 * i + 50, '#DDDDDD', '13px Verdana'); + core.fillText('ui', enemy.money, 195, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); + expOffset = 255; + } + if (core.flags.enableExperience) { core.canvas.ui.textAlign = "left"; - core.fillText('ui', '经验', 255, 62 * i + 50, '#DDDDDD', '13px Verdana'); - core.fillText('ui', enemy.experience, 285, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); - damage_offset = 361; + core.fillText('ui', '经验', expOffset, 62 * i + 50, '#DDDDDD', '13px Verdana'); + core.fillText('ui', enemy.experience, expOffset + 30, 62 * i + 50, '#DDDDDD', 'bold 13px Verdana'); } + var damageOffet = 281; + if (core.flags.enableMoney && core.flags.enableExperience) + damageOffet = 361; + else if (core.flags.enableMoney || core.flags.enableExperience) + damageOffet = 326; + + core.canvas.ui.textAlign = "center"; var damage = enemy.damage; var color = '#FFFF00'; if (damage >= core.status.hero.hp) color = '#FF0000'; - if (damage == 0) color = '#00FF00'; + if (damage <= 0) color = '#00FF00'; if (damage >= 999999999) damage = '无法战斗'; - var length = core.canvas.ui.measureText(damage).width; - core.fillText('ui', damage, damage_offset, 62 * i + 50, color, 'bold 13px Verdana'); + core.fillText('ui', damage, damageOffet, 62 * i + 50, color, 'bold 13px Verdana'); core.canvas.ui.textAlign = "left"; @@ -825,11 +835,77 @@ ui.prototype.drawEnemyBook = function (page) { core.fillText('ui', '1防', 335, 62 * i + 68, '#DDDDDD', '13px Verdana'); core.fillText('ui', enemy.defDamage, 365, 62 * i + 68, '#DDDDDD', 'bold 13px Verdana'); + if (index == start+i) { + core.strokeRect('ui', 10, 62 * i + 13, 416-10*2, 62, '#FFD700'); + } + } core.setBoxAnimate(); this.drawPagination(page, totalPage); } +ui.prototype.drawBookDetail = function (index) { + var enemys = core.enemys.getCurrentEnemys(); + if (enemys.length==0) return; + if (index<0) index=0; + if (index>=enemys.length) index=enemys.length-1; + + var enemy = enemys[index]; + var enemyId=enemy.id; + var hints=core.enemys.getSpecialHint(core.enemys.getEnemys(enemyId)); + + if (hints.length==0) { + core.drawTip("该怪物无特殊属性!"); + return; + } + var content=hints.join("\n"); + + core.status.event.id = 'book-detail'; + clearInterval(core.interval.tipAnimate); + + core.clearMap('data', 0, 0, 416, 416); + core.setOpacity('data', 1); + + var left=10, right=416-2*left; + var content_left = left + 25; + + var validWidth = right-(content_left-left)-13; + var contents = core.splitLines("data", content, validWidth, '16px Verdana'); + + var height = 416 - 10 - Math.min(416-24*(contents.length+1)-65, 250); + var top = (416-height)/2, bottom = height; + + // var left = 97, top = 64, right = 416 - 2 * left, bottom = 416 - 2 * top; + core.setAlpha('data', 0.9); + core.fillRect('data', left, top, right, bottom, '#000000'); + core.setAlpha('data', 1); + core.strokeRect('data', left - 1, top - 1, right + 1, bottom + 1, '#FFFFFF', 2); + + // 名称 + core.canvas.data.textAlign = "left"; + + core.fillText('data', enemy.name, content_left, top + 30, '#FFD700', 'bold 22px Verdana'); + var content_top = top + 57; + + for (var i=0;i=0) { + var x1 = text.substring(0, index+1); + core.fillText('data', x1, content_left, content_top, '#FF6A6A', 'bold 16px Verdana'); + var len=core.canvas.data.measureText(x1).width; + core.fillText('data', text.substring(index+1), content_left+len, content_top, '#FFFFFF', '16px Verdana'); + } + else { + core.fillText('data', contents[i], content_left, content_top, '#FFFFFF', '16px Verdana'); + } + content_top+=24; + } + + core.fillText('data', '<点击任意位置继续>', 270, top+height-13, '#CCCCCC', '13px Verdana'); +} + /** * 绘制楼传器 * @param page @@ -1044,7 +1120,7 @@ ui.prototype.drawThumbnail = function(floorId, canvas, blocks, x, y, size, heroL if (core.isset(block.event) && !(core.isset(block.enable) && !block.enable)) { if (block.event.cls == 'autotile') { // core.drawAutotile(); - autotileMaps[13*block.x + block.y] = true; + autotileMaps[13*block.x + block.y] = block.event.id; continue; } else { diff --git a/main.js b/main.js index f173fa80..e56ca78f 100644 --- a/main.js +++ b/main.js @@ -20,6 +20,7 @@ function main() { 'toolBar': document.getElementById('toolBar'), 'tools': document.getElementsByClassName('tools'), 'gameCanvas': document.getElementsByClassName('gameCanvas'), + 'curtain': document.getElementById('curtain'), 'startButtons': document.getElementById('startButtons'), 'playGame': document.getElementById('playGame'), 'loadGame': document.getElementById('loadGame'), @@ -30,31 +31,40 @@ function main() { 'hardLevel': document.getElementById('hardLevel'), 'data': document.getElementById('data'), 'statusLabels': document.getElementsByClassName('statusLabel'), + 'floorCol': document.getElementById('floorCol'), + 'lvCol': document.getElementById('lvCol'), 'mdefCol': document.getElementById('mdefCol'), + 'moneyCol': document.getElementById('moneyCol'), 'expCol': document.getElementById('expCol'), + 'upCol': document.getElementById('upCol'), + 'debuffCol': document.getElementById('debuffCol'), + 'hard': document.getElementById('hard'), }; - // console.log('加载游戏容器和开始界面dom对象完成 如下'); - // console.log(this.dom); this.loadList = [ 'items', 'icons', 'maps', 'enemys', 'events', 'data', 'ui', 'core' ]; - // console.log('加载js文件列表加载完成' + this.loadList); this.images = [ - 'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains', "autotile" + 'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains' ]; - this.sounds = { - 'mp3': ['bgm-loop', 'floor'], - 'ogg': ['attack', 'door', 'item'] - } + this.bgms = [ // 在此存放所有的bgm,和文件名一致。第一项为默认播放项 + // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 + '058-Slow01.mid', 'bgm.mp3', 'qianjin.mid', 'star.mid' + ]; + this.sounds = [ // 在此存放所有的SE,和文件名一致 + // 音频名不能使用中文,不能带空格或特殊字符;可以直接改名拼音就好 + 'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg' + ] this.statusBar = { 'image': { 'floor': document.getElementById('img-floor'), + 'lv': document.getElementById('img-lv'), 'hp': document.getElementById("img-hp"), 'atk': document.getElementById("img-atk"), 'def': document.getElementById("img-def"), 'mdef': document.getElementById("img-mdef"), 'money': document.getElementById("img-money"), 'experience': document.getElementById("img-experience"), + 'up': document.getElementById("img-up"), 'book': document.getElementById("img-book"), 'fly': document.getElementById("img-fly"), 'toolbox': document.getElementById("img-toolbox"), @@ -64,12 +74,14 @@ function main() { 'settings': document.getElementById("img-settings") }, 'floor': document.getElementById('floor'), + 'lv': document.getElementById('lv'), 'hp': document.getElementById('hp'), 'atk': document.getElementById('atk'), 'def': document.getElementById("def"), 'mdef': document.getElementById('mdef'), 'money': document.getElementById("money"), 'experience': document.getElementById("experience"), + 'up': document.getElementById('up'), 'yellowKey': document.getElementById("yellowKey"), 'blueKey': document.getElementById("blueKey"), 'redKey': document.getElementById("redKey"), @@ -78,6 +90,8 @@ function main() { 'curse': document.getElementById('curse'), 'hard': document.getElementById("hard") } + + //------------------------ 用户修改内容 ------------------------// this.version = "0.1"; // 游戏版本号;如果更改了游戏内容建议修改此version以免造成缓存问题。 this.useCompress = false; // 是否使用压缩文件 @@ -88,6 +102,8 @@ function main() { this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序 "sample0", "sample1", "sample2" ] + //------------------------ 用户修改内容 END ------------------------// + this.floors = {} this.instance = {}; this.canvas = {}; @@ -106,7 +122,7 @@ main.prototype.init = function () { coreData[name] = main[name]; } main.loaderFloors(function() { - main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.sounds, main.floorIds, main.floors, coreData); + main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.bgms, main.sounds, main.floorIds, main.floors, coreData); main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); }) }); @@ -209,18 +225,6 @@ main.dom.body.onselectstart = function () { return false; } -document.onmousemove = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - -document.ontouchstart = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - main.dom.data.onmousedown = function (e) { try { e.stopPropagation(); diff --git a/sounds/058-Slow01.mid b/sounds/058-Slow01.mid new file mode 100644 index 00000000..05ce9228 Binary files /dev/null and b/sounds/058-Slow01.mid differ diff --git a/sounds/qianjin.mid b/sounds/qianjin.mid new file mode 100644 index 00000000..b4236a96 Binary files /dev/null and b/sounds/qianjin.mid differ diff --git a/sounds/star.mid b/sounds/star.mid new file mode 100644 index 00000000..cc78e590 Binary files /dev/null and b/sounds/star.mid differ diff --git a/styles.css b/styles.css index c63e5e92..6523b3b8 100644 --- a/styles.css +++ b/styles.css @@ -21,7 +21,7 @@ position: fixed; top: 10px; left: 10px; - z-index: 12; + z-index: 13; } #startPanel { @@ -32,7 +32,7 @@ left: 0; background-color: #fff; overflow: hidden; - z-index: 8; + z-index: 9; } #startTop { @@ -42,7 +42,7 @@ top: 0; left: 0; background-color: #000; - z-index: 11; + z-index: 12; } #startTopProgressBar { @@ -52,7 +52,7 @@ position: absolute; top: 5%; background-color: #fff; - z-index: 12; + z-index: 13; } #startTopProgress { @@ -67,7 +67,7 @@ position: absolute; top: 8%; left: 5%; - z-index: 12; + z-index: 13; } #startBackground { @@ -77,12 +77,12 @@ height: 100%; width: auto; transform:translate(-50%,-50%); - z-index: 9; + z-index: 10; } #startLogo { position: absolute; - z-index: 9; + z-index: 10; left: 0; right: 0; margin-left: auto; @@ -95,7 +95,7 @@ #startTitle { position: absolute; - z-index: 10; + z-index: 11; } #startButtonGroup { @@ -106,7 +106,7 @@ background-color: #000; opacity: 0.85; display: none; - z-index: 9; + z-index: 10; bottom: 0; margin-bottom: 7%; } @@ -142,7 +142,7 @@ display: none; color: #fff; background-color: #000; - z-index: 7; + z-index: 8; } #logoLabel { @@ -170,7 +170,7 @@ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; background: url(images/ground.png) round; - z-index: 6; + z-index: 7; display: none; } #statusBar .status{ @@ -180,8 +180,9 @@ } .status img{ vertical-align: middle; - width: 1.6em; - height: 1.6em; + width: auto; + height: 100%; + max-height: 1.6em; } #statusBar span{ color: white; @@ -198,7 +199,7 @@ #toolBar { position: absolute; background: url(images/ground.png) round; - z-index: 5; + z-index: 6; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -232,6 +233,13 @@ span#poison, span#weak, span#curse { -webkit-box-sizing: border-box; } +#curtain { + z-index: 5; + position: absolute; + opacity: 0; + background: #000000; +} + #bg { z-index: 1; } @@ -240,20 +248,20 @@ span#poison, span#weak, span#curse { z-index: 2; } -#hero { +#fg { z-index: 3; } -#fg { +#hero { z-index: 4; } #ui { - z-index: 5; + z-index: 6; } #data { - z-index: 6; + z-index: 7; } .clearfix:before, diff --git a/常用工具/便捷PS工具.exe b/常用工具/便捷PS工具.exe index 7ec2fc98..a85e29fd 100644 Binary files a/常用工具/便捷PS工具.exe and b/常用工具/便捷PS工具.exe differ diff --git a/常用工具/地图生成器.exe b/常用工具/地图生成器.exe index 541b3626..286aa26b 100644 Binary files a/常用工具/地图生成器.exe and b/常用工具/地图生成器.exe differ diff --git a/更新说明.txt b/更新说明.txt index b0e69a89..83a62ff8 100644 --- a/更新说明.txt +++ b/更新说明.txt @@ -1,6 +1,12 @@ 全键盘操作 √ +经验升级(进阶) √ +增加阻击、多连击等属性;属性显示;加点; √ +九宫格领域、大范围领域 √ Ctrl快速跳过对话 √ +增加负伤 √ +背景音乐 √ 支持不同层使用不同的地面素材 √ -直接内嵌了诸多默认的terrains素材 +支持多个Autotile同时存在 √ +直接内嵌了诸多默认的terrains素材 √ 自动定位到上次存/读档位置 √ -设置储存 √ +每一层可以默认色调 √