/// var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { fiveLayer: function () { // 注册插件 Mota.Plugin.register('fiveLayer_g', { init }, init); // 创建新图层 function createCanvas(name, zIndex) { if (!name) return; var canvas = document.createElement('canvas'); canvas.id = name; canvas.className = 'gameCanvas no-anti-aliasing'; // 编辑器模式下设置zIndex会导致加入的图层覆盖优先级过高 if (main.mode != 'editor') canvas.style.zIndex = zIndex || 0; // 将图层插入进游戏内容 document.getElementById('gameDraw').appendChild(canvas); var ctx = canvas.getContext('2d'); core.canvas[name] = ctx; return canvas; } function init() { var bg2Canvas = createCanvas('bg2', 20); var fg2Canvas = createCanvas('fg2', 63); // 大地图适配 core.bigmap.canvas = [ 'bg2', 'fg2', 'bg', 'event', 'event2', 'fg', 'damage' ]; core.initStatus.bg2maps = {}; core.initStatus.fg2maps = {}; if (main.mode == 'editor') { /*插入编辑器的图层 不做此步新增图层无法在编辑器显示*/ // 编辑器图层覆盖优先级 eui > efg > fg(前景层) > event2(48*32图块的事件层) > event(事件层) > bg(背景层) // 背景层2(bg2) 插入事件层(event)之前(即bg与event之间) document .getElementById('mapEdit') .insertBefore(bg2Canvas, document.getElementById('event')); // 前景层2(fg2) 插入编辑器前景(efg)之前(即fg之后) document .getElementById('mapEdit') .insertBefore(fg2Canvas, document.getElementById('ebm')); // 原本有三个图层 从4开始添加 var num = 4; // 新增图层存入editor.dom中 editor.dom.bg2c = core.canvas.bg2.canvas; editor.dom.bg2Ctx = core.canvas.bg2; editor.dom.fg2c = core.canvas.fg2.canvas; editor.dom.fg2Ctx = core.canvas.fg2; editor.dom.maps.push('bg2map', 'fg2map'); editor.dom.canvas.push('bg2', 'fg2'); // 创建编辑器上的按钮 var createCanvasBtn = name => { // 电脑端创建按钮 var input = document.createElement('input'); // layerMod4/layerMod5 var id = 'layerMod' + num++; // bg2map/fg2map var value = name + 'map'; input.type = 'radio'; input.name = 'layerMod'; input.id = id; input.value = value; editor.dom[id] = input; input.onchange = () => { editor.uifunctions.setLayerMod(value); }; return input; }; var createCanvasBtn_mobile = name => { // 手机端往选择列表中添加子选项 var input = document.createElement('option'); var id = 'layerMod' + num++; var value = name + 'map'; input.name = 'layerMod'; input.value = value; editor.dom[id] = input; return input; }; if (!editor.isMobile) { var input = createCanvasBtn('bg2'); var input2 = createCanvasBtn('fg2'); // 获取事件层及其父节点 var child = document.getElementById('layerMod'), parent = child.parentNode; // 背景层2插入事件层前 parent.insertBefore(input, child); // 不能直接更改背景层2的innerText 所以创建文本节点 var txt = document.createTextNode('背2'); // 插入事件层前(即新插入的背景层2前) parent.insertBefore(txt, child); // 向最后插入前景层2(即插入前景层后) parent.appendChild(input2); var txt2 = document.createTextNode('前2'); parent.appendChild(txt2); } else { var input = createCanvasBtn_mobile('bg2'); var input2 = createCanvasBtn_mobile('fg2'); // 手机端因为是选项 所以可以直接改innerText input.innerText = '背景2'; input2.innerText = '前景2'; var parent = document.getElementById('layerMod'); parent.insertBefore(input, parent.children[1]); parent.appendChild(input2); } } maps.prototype._loadFloor_doNotCopy = function () { return [ 'firstArrive', 'eachArrive', 'blocks', 'parallelDo', 'map', 'bgmap', 'fgmap', 'bg2map', 'fg2map', 'events', 'changeFloor', 'afterBattle', 'afterGetItem', 'afterOpenDoor', 'cannotMove', 'enemy' ]; }; ////// 绘制背景和前景层 ////// maps.prototype._drawBg_draw = function ( floorId, toDrawCtx, cacheCtx, config ) { config.ctx = cacheCtx; core.maps._drawBg_drawBackground(floorId, config); // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。 core.maps._drawFloorImages( floorId, config.ctx, 'bg', null, null, config.onMap ); core.maps._drawBgFgMap(floorId, 'bg', config); if (config.onMap) { core.drawImage( toDrawCtx, cacheCtx.canvas, core.bigmap.v2 ? -32 : 0, core.bigmap.v2 ? -32 : 0 ); core.clearMap('bg2'); core.clearMap(cacheCtx); } core.maps._drawBgFgMap(floorId, 'bg2', config); if (config.onMap) core.drawImage( 'bg2', cacheCtx.canvas, core.bigmap.v2 ? -32 : 0, core.bigmap.v2 ? -32 : 0 ); config.ctx = toDrawCtx; }; maps.prototype._drawFg_draw = function ( floorId, toDrawCtx, cacheCtx, config ) { config.ctx = cacheCtx; // ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制前景图块;后绘制的覆盖先绘制的。 core.maps._drawFloorImages( floorId, config.ctx, 'fg', null, null, config.onMap ); core.maps._drawBgFgMap(floorId, 'fg', config); if (config.onMap) { core.drawImage( toDrawCtx, cacheCtx.canvas, core.bigmap.v2 ? -32 : 0, core.bigmap.v2 ? -32 : 0 ); core.clearMap('fg2'); core.clearMap(cacheCtx); } core.maps._drawBgFgMap(floorId, 'fg2', config); if (config.onMap) core.drawImage( 'fg2', cacheCtx.canvas, core.bigmap.v2 ? -32 : 0, core.bigmap.v2 ? -32 : 0 ); config.ctx = toDrawCtx; }; ////// 移动判定 ////// maps.prototype._generateMovableArray_arrays = function (floorId) { return { bgArray: this.getBgMapArray(floorId), fgArray: this.getFgMapArray(floorId), eventArray: this.getMapArray(floorId), bg2Array: this._getBgFgMapArray('bg2', floorId), fg2Array: this._getBgFgMapArray('fg2', floorId) }; }; } }, uiRewrite: function () { Mota.Plugin.register('ui_g', { init }, init); function init() { const { mainUi, fixedUi } = Mota.requireAll('var'); ui.prototype.drawBook = function () { if (!core.isReplaying()) return mainUi.open('book'); }; ui.prototype._drawToolbox = function () { if (!core.isReplaying()) return mainUi.open('toolbox'); }; ui.prototype._drawEquipbox = function () { if (!core.isReplaying()) return mainUi.open('equipbox'); }; ui.prototype.drawFly = function () { if (!core.isReplaying()) return mainUi.open('fly'); }; control.prototype.updateStatusBar_update = function () { core.control.updateNextFrame = false; if (!core.isPlaying() || core.hasFlag('__statistics__')) return; core.control.controldata.updateStatusBar(); if (!core.control.noAutoEvents) core.checkAutoEvents(); core.control._updateStatusBar_setToolboxIcon(); core.clearRouteFolding(); core.control.noAutoEvents = true; // 更新vue状态栏 updateVueStatusBar(); Mota.require('var', 'hook').emit('statusBarUpdate'); }; // todo: 多个状态栏分离与控制 control.prototype.showStatusBar = function () { if (main.mode == 'editor') return; core.removeFlag('hideStatusBar'); if (!fixedUi.hasName('statusBar')) { fixedUi.open('statusBar'); } core.dom.tools.hard.style.display = 'block'; core.dom.toolBar.style.display = 'block'; }; control.prototype.hideStatusBar = function (showToolbox) { if (main.mode == 'editor') return; // 如果原本就是隐藏的,则先显示 if (!core.domStyle.showStatusBar) this.showStatusBar(); if (core.isReplaying()) showToolbox = true; fixedUi.closeByName('statusBar'); var toolItems = core.dom.tools; core.setFlag('hideStatusBar', true); core.setFlag('showToolbox', showToolbox || null); if ( (!core.domStyle.isVertical && !core.flags.extendToolbar) || !showToolbox ) { for (var i = 0; i < toolItems.length; ++i) toolItems[i].style.display = 'none'; } if (!core.domStyle.isVertical && !core.flags.extendToolbar) { core.dom.toolBar.style.display = 'none'; } }; } function updateVueStatusBar() { Mota.r(() => { const status = Mota.require('var', 'status'); status.value = !status.value; }); } }, special: function () { // 这个插件负责定义怪物属性 const specials = Mota.require('var', 'enemySpecials'); /** * @param {string | ((enemy: Enemy) => string)} func * @param {Enemy} enemy */ const fromFunc = (func, enemy) => { return typeof func === 'string' ? func : func(enemy); }; // 怪物特殊属性包含四个信息 // code: 索引,必须与该属性在数组内的索引一致,实际判断的时候也是根据索引判断,不会根据code判断 // name: 特殊属性名称,可以是一个函数,接受 enemy 作为参数,返回字符串 // desc: 特殊属性说明,也可以是一个函数,接受 enemy 作为参数,返回字符串 // color: 特殊属性颜色,会在怪物手册中显示出来 specials.push( { code: 0, name: '空', desc: '空', color: '#fff' }, { code: 1, name: '先攻', desc: `怪物首先攻击`, color: '#fc3' }, { code: 2, name: '魔攻', desc: '怪物攻击无视勇士的防御', color: '#bbb0ff' }, { code: 3, name: '坚固', desc: '怪物防御不小于勇士攻击-1', color: '#c0b088' }, { code: 4, name: '2连击', desc: '怪物每回合攻击2次', color: '#fe7' }, { code: 5, name: '3连击', desc: '怪物每回合攻击3次', color: '#fe7' }, { code: 6, name: enemy => `${enemy.n ?? 4}连击`, desc: enemy => `怪物每回合攻击${enemy.n ?? 4}次`, color: '#fe7' }, { code: 7, name: '破甲', desc: enemy => `战斗前,怪物附加角色防御的${Math.floor( 100 * (enemy.breakArmor ?? core.values.breakArmor) )}%作为伤害"`, color: '#b67' }, { code: 8, name: '反击', desc: enemy => `战斗时,怪物每回合附加角色攻击的${Math.floor( 100 * (enemy.counterAttack ?? core.values.counterAttack) )}%作为伤害,无视角色防御`, color: '#fa4' }, { code: 9, name: '净化', desc: enemy => `战斗前,怪物附加角色护盾的${ enemy.purify ?? core.values.purify }倍作为伤害`, color: '#80eed6' }, { code: 10, name: '模仿', desc: `怪物的攻防和角色攻防相等`, color: '#b0c0dd' }, { code: 11, name: '吸血', desc: enemy => { const vampire = enemy.vampire || 0; const hp = Mota.require('fn', 'getHeroStatusOn')('hp'); return ( `战斗前,怪物首先吸取角色的${Math.floor( 100 * vampire )}%生命(约${Math.floor(hp * vampire)}点)作为伤害` + (enemy.add ? ',并把伤害数值加到自身生命上' : '') ); }, color: '#ff00d2' }, { code: 12, name: '中毒', desc: `战斗后,角色陷入中毒状态,每一步损失生命${core.data.values.poisonDamage}点`, color: '#9e8' }, { code: 13, name: '衰弱', desc: `战斗后,角色陷入衰弱状态,攻防暂时下降` + (core.data.values.weakValue >= 1 ? core.data.values.weakValue + '点' : parseInt(core.data.values.weakValue * 100) + '%'), color: '#bbb0ff' }, { code: 14, name: '诅咒', desc: '战斗后,角色陷入诅咒状态,战斗无法获得金币和经验', color: '#bbeef0' }, { code: 15, name: '领域', desc: enemy => '经过怪物周围' + (enemy.zoneSquare ? '九宫格' : '十字') + '范围内' + (enemy.range || 1) + '格时自动减生命' + (enemy.zone || 0) + '点', color: '#c677dd' }, { code: 16, name: '夹击', desc: '经过两只相同的怪物中间,角色生命值变成一半', color: '#fff' }, { code: 17, name: '仇恨', desc: `战斗前,怪物附加之前积累的仇恨值作为伤害;战斗后,释放一半的仇恨值。(每杀死一个怪物获得${ core.data.values.hatred || 0 }点仇恨值)`, color: '#b0b666' }, { code: 18, name: '阻击', desc: enemy => '经过怪物周围' + (enemy.zoneSquare ? '九宫格' : '十字') + '时自动减生命' + (enemy.repulse || 0) + '点,同时怪物后退一格', color: '#8888e6' }, { code: 19, name: '自爆', desc: '战斗后角色的生命值变成1', color: '#f66' }, { code: 20, name: '无敌', desc: `角色无法打败怪物,除非拥有十字架`, color: '#aaa' }, { code: 21, name: '退化', desc: enemy => '战斗后角色永久下降' + enemy.atkValue + '点攻击和' + enemy.defValue + '点防御', color: 'cyan' }, { code: 22, name: '固伤', desc: enemy => '战斗前,怪物对角色造成' + enemy.damage + '点固定伤害,未开启负伤时无视角色护盾。', color: '#d8a' }, { code: 23, name: '重生', desc: '怪物被击败后,角色转换楼层则怪物将再次出现', color: '#ffd' }, { code: 24, name: '激光', desc: enemy => '经过怪物同行或同列时自动减生命' + enemy.laser + '点', color: '#dda0dd' }, { code: 25, name: '光环', desc: enemy => (enemy.range != null ? (enemy.haloSquare ? '该怪物九宫格' : '该怪物十字') + enemy.haloRange + '格范围内' : '同楼层所有') + '怪物生命提升' + (enemy.hpBuff || 0) + '%,攻击提升' + (enemy.atkBuff || 0) + '%,防御提升' + (enemy.defBuff || 0) + '%,' + (enemy.haloAdd ? '可叠加' : '不可叠加'), color: '#e6e099' }, { code: 26, name: '支援', desc: '当周围一圈的怪物受到攻击时将上前支援,并组成小队战斗。', color: '#77c0b6' }, { code: 27, name: '捕捉', desc: enemy => '当走到怪物周围' + (enemy.zoneSquare ? '九宫格' : '十字') + '时会强制进行战斗。', color: '#c0ddbb' }, { code: 28, name: '特殊光环', desc: enemy => { let content = ''; enemy.specialHalo?.forEach((v, i) => { content += ' '.repeat(8) + `${i + 1}. ${fromFunc( specials[v].name, enemy )}: ${fromFunc( specials[v].desc, enemy )}
`; }); return ( `怪物周围${enemy.haloSquare ? '九宫格' : '十字'}${ enemy.haloRange }格范围内所有怪物获得以下特殊属性,` + `特殊属性数值间为${ enemy.specialMultiply ? '相乘' : '相加' }关系:
` + content ); }, color: '#ff0' } ); }, battle: function () { // 这个插件负责战斗相关内容 // 注意,对于电脑作者,极度推荐使用 vscode 进行代码编写,可以享受到新版的类型标注 // 同时由于类型标注过于复杂,样板编辑器无法部署,因此样板编辑器也无法享受到新API的代码补全等 // 因此极度推荐使用 vscode 进行编写 // --------------- 战后脚本 // enemy: DamageEnemy实例,也就是怪物本身 // x, y: 怪物坐标 Mota.rewrite(core.events, 'afterBattle', 'full', (enemy, x, y) => { const { has } = Mota.Plugin.require('utils_g'); const floorId = core.status.floorId; const special = enemy.info.special; // 播放战斗动画 let animate = 'hand'; // 检查当前装备是否存在攻击动画 const equipId = core.getEquip(0); if (equipId && (core.material.items[equipId].equip || {}).animate) animate = core.material.items[equipId].equip.animate; // 检查该动画是否存在SE,如果不存在则使用默认音效 if (!core.material.animates[animate]?.se) core.playSound('attack.mp3'); // 战斗伤害 const info = enemy.calDamage(core.status.hero); const damage = info.damage; // 判定是否致死 if (damage >= core.status.hero.hp) { core.status.hero.hp = 0; core.updateStatusBar(false, true); core.events.lose('战斗失败'); return; } // 扣减体力值并记录统计数据 core.status.hero.hp -= damage; core.status.hero.statistics.battleDamage += damage; core.status.hero.statistics.battle++; // 获得金币 let money = enemy.enemy.money; let exp = enemy.enemy.exp; if (enemy.info.guard) { enemy.info.guard.forEach(v => { money += v.enemy.money; exp += v.enemy.exp; }); } core.status.hero.money += money; core.status.hero.statistics.money += money; // 获得经验 core.status.hero.exp += exp; core.status.hero.statistics.exp += exp; const hint = '打败 ' + enemy.enemy.name + ',金币+' + money + ',经验+' + exp; core.drawTip(hint, enemy.id); // 中毒 if (special.includes(12)) { core.triggerDebuff('get', 'poison'); } // 衰弱 if (special.includes(13)) { core.triggerDebuff('get', 'weak'); } // 诅咒 if (special.includes(14)) { core.triggerDebuff('get', 'curse'); } // 仇恨怪物将仇恨值减半 if (special.includes(17)) { core.setFlag( 'hatred', Math.floor(core.getFlag('hatred', 0) / 2) ); } // 自爆 if (special.includes(19)) { core.status.hero.statistics.battleDamage += core.status.hero.hp - 1; core.status.hero.hp = 1; } // 退化 if (special.includes(21)) { core.status.hero.atk -= enemy.atkValue || 0; core.status.hero.def -= enemy.defValue || 0; if (core.status.hero.atk < 0) core.status.hero.atk = 0; if (core.status.hero.def < 0) core.status.hero.def = 0; } // 增加仇恨值 core.setFlag( 'hatred', core.getFlag('hatred', 0) + core.values.hatred ); // 事件的处理 const todo = []; // 战后事件 if (has(core.status.floorId)) { const loc = `${x},${y}`; todo.push( ...(core.floors[core.status.floorId].afterBattle[loc] ?? []) ); } todo.push(...(enemy.enemy.afterBattle ?? [])); // 如果事件不为空,将其插入 if (todo.length > 0) core.insertAction(todo, x, y); if (has(x) && has(y)) { core.drawAnimate(animate, x, y); if (special.includes(23)) core.hideBlock(x, y); else core.removeBlock(x, y); } else core.drawHeroAnimate(animate); // 如果已有事件正在处理中 if (core.status.event.id == null) core.continueAutomaticRoute(); else core.clearContinueAutomaticRoute(); // 打怪特效 Mota.r(() => { const setting = Mota.require('var', 'mainSetting'); const { applyFragWith } = Mota.Plugin.require('frag_r'); if (setting.getValue('fx.frag') && has(x) && has(y)) { const frame = core.status.globalAnimateStatus % 2; const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32; core.drawIcon(canvas, enemy.id, 0, 0, 32, 32, frame); const manager = applyFragWith(canvas); const frag = manager.canvas; frag.style.imageRendering = 'pixelated'; frag.style.width = `${frag.width * core.domStyle.scale}px`; frag.style.height = `${ frag.height * core.domStyle.scale }px`; const left = (x * 32 + 16 - frag.width / 2 - core.bigmap.offsetX) * core.domStyle.scale; const top = (y * 32 + 16 - frag.height / 2 - core.bigmap.offsetY) * core.domStyle.scale; frag.style.left = `${left}px`; frag.style.top = `${top}px`; frag.style.zIndex = '45'; frag.style.position = 'absolute'; frag.style.filter = 'sepia(20%)brightness(120%)'; core.dom.gameDraw.appendChild(frag); manager.onEnd.then(() => { frag.remove(); }); } }); }); // --------------- 战斗伤害 const { getHeroStatusOn } = Mota.requireAll('fn'); const Damage = Mota.require('module', 'Damage'); // 这个数组常量控制着在战斗时哪些属性计算真实属性,也就是经过buff加成的属性 // 如果有属性不会经过buff加成等,请将其去除,可以提高性能表现 Damage.realStatus = ['atk', 'def', 'mdef', 'hpmax']; // 怪物属性计算,用于获取怪物的初始属性,不经过任何光环加成 // 一般对于坚固、模仿等怪物会在这计算 Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'calAttribute', 'full', function () { const { has } = Mota.Plugin.require('utils_g'); if (this.progress !== 1 && has(this.x) && has(this.floorId)) return; this.progress = 2; const special = this.info.special; const info = this.info; const floorId = this.floorId ?? core.status.floorId; const status = getHeroStatusOn(Damage.realStatus, floorId); // 坚固 if (special.includes(3)) { info.def = status.atk - 1; } // 模仿 if (special.includes(10)) { info.atk = status.atk; info.def = status.def; } } ); // 复写系统的伤害计算函数即可,全量复写 // 函数接受两个参数,分别是怪物信息和勇士信息,返回一个数字作为伤害 // 返回null表示不能战斗,返回Infinity也可以 Mota.rewrite(Damage, 'calDamageWith', 'full', (info, hero) => { // 获取勇士属性,这几个属性直接从core.status.hero获取 const { hp, mana, manamax } = core.status.hero; // 获取勇士属性,这几个属性从勇士真实属性获取 // 分开获取是因为获取勇士真实属性会对性能造成一定影响 let { atk, def, mdef, hpmax } = hero; // 获取怪物信息,是在某点的信息 let { hp: monHp, atk: monAtk, def: monDef, special, enemy } = info; /** 总伤害 */ let damage = 0; /** 勇士单回合伤害 */ let heroPerDamage; /** 战斗回合 */ let turn = 0; // 无敌 if (special.includes(20) && !core.hasItem('cross')) { return null; } heroPerDamage = atk - monDef; if (heroPerDamage <= 0) return null; // 吸血 if (special.includes(11)) { const vampire = hp * (info.vampire ?? 0); if (info.add) monHp += vampire; damage += vampire; } /** 怪物单回合伤害 */ let enemyPerDamage; // 魔攻 if (special.includes(2)) { enemyPerDamage = monAtk; } else { enemyPerDamage = monAtk - def; if (enemyPerDamage < 0) enemyPerDamage = 0; } // 先攻 if (special.includes(1)) { damage += enemyPerDamage; } // 连击 if (special.includes(4)) enemyPerDamage *= 2; if (special.includes(5)) enemyPerDamage *= 3; if (special.includes(6)) enemyPerDamage *= info.n ?? 4; // 破甲 if (special.includes(7)) { damage += def * (info.breakArmor ?? core.values.breakArmor); } // 反击 if (special.includes(8)) { enemyPerDamage += atk * (info.counterAttack ?? core.values.counterAttack); } // 净化 if (special.includes(9)) { damage += mdef * (info.purify ?? core.values.purify); } turn = Math.ceil(monHp / heroPerDamage); // 支援,支援信息由光环计算而得,直接使用即可 if (info.guard) { const guardFirst = false; const inGuard = core.getFlag('__inGuard__'); if (!inGuard) core.setFlag('__extraTurn__', guardFirst ? 0 : turn); core.setFlag('__inGuard__', true); for (const enemy of info.guard) { const info = enemy.getRealInfo(); damage += Damage.calDamageWith(info, { ...hero, mdef: 0 }) ?? Infinity; if (!isFinite(damage)) return null; } if (!inGuard) core.removeFlag('__inGuard__'); turn += core.getFlag('__extraTurn__', 0); core.removeFlag('__extraTurn__'); } // 计算最终伤害 damage += (turn - 1) * enemyPerDamage; damage -= mdef; if (!core.flags.enableNegativeDamage) damage = Math.max(0, damage); // 仇恨 if (special.includes(17)) { damage += core.getFlag('hatred', 0); } // 固伤 if (special.includes(22)) { damage += info.damage; } return damage; }); // --------------- 秒杀伤害计算 // 用于计算一些特殊属性怪物在一回合内秒杀所需的攻击,依此计算临界的上界 // 函数没有参数,返回一个数字,表示临界上界,Infinity表示没有上界,不计算临界 // 不能返回数字型外的量 Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'getSeckillAtk', 'full', function () { // 获取怪物的属性 const info = this.getRealInfo(); // 对于一般的怪物,应该是怪物防御加上怪物血量 const add = info.def + info.hp; // 坚固,不可能通过攻击秒杀 if (info.special.includes(3)) { return Infinity; } // 模仿,不计算临界 if (info.special.includes(10)) { return Infinity; } // 吸血 if (info.special.includes(11) && info.add) { return add + core.status.hero.hp * (info.vampire ?? 0); } return add; } ); // --------------- 地图伤害 const caledBetween = new Set(); // 全量复写地图伤害计算,这个计算会调用所有的 DamageEnemy 的地图伤害计算 Mota.rewrite( Mota.require('class', 'EnemyCollection').prototype, 'calMapDamage', 'full', function () { this.mapDamage = {}; caledBetween.clear(); const hero = getHeroStatusOn(Damage.realStatus, this.floorId); this.list.forEach(v => { v.calMapDamage(this.mapDamage, hero); }); } ); // 全量复写单个怪物地图伤害的计算函数,注意此处不能使用箭头函数,因为这是在原型上的函数,其this指向实例,也即怪物(DamageEnemy实例) // 函数接收两个参数,damage和hero,前者表示要将结果存入的对象,后者是勇士真实属性 // 直接将damage返回即可,返回其他值有可能会引起出错 // 计算出伤害后直接调用this.setMapDamage即可将伤害传到对象中 Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'calMapDamage', 'full', function (damage = {}, hero = getHeroStatusOn(Damage.realStatus)) { // 功能函数,计算曼哈顿距离,和判断一个值是否存在 const { manhattan, has } = Mota.Plugin.require('utils_g'); // 判断这个怪物是不是在地图上 if ( !has(this.x) || !has(this.y) || !has(this.floorId) || !has(this.col) ) { return damage; } const enemy = this.info; const floor = core.status.maps[this.floorId]; const w = floor.width; const h = floor.height; // 领域 if (this.info.special.includes(15)) { const range = enemy.range ?? 1; const startX = Math.max(0, this.x - range); const startY = Math.max(0, this.y - range); const endX = Math.min(floor.width - 1, this.x + range); const endY = Math.min(floor.height - 1, this.y + range); const dam = Math.max(enemy.zone ?? 0, 0); for (let x = startX; x <= endX; x++) { for (let y = startY; y <= endY; y++) { if ( !enemy.zoneSquare && manhattan(x, y, this.x, this.y) > range ) { continue; } const loc = `${x},${y}`; this.setMapDamage(damage, loc, dam, '领域'); } } } // 激光 if (this.info.special.includes(24)) { const dirs = ['left', 'down', 'up', 'right']; const dam = Math.max(enemy.laser ?? 0, 0); for (const dir of dirs) { let x = this.x; let y = this.y; const { x: dx, y: dy } = core.utils.scan[dir]; while (x >= 0 && y >= 0 && x < w && y < h) { x += dx; y += dy; const loc = `${x},${y}`; this.setMapDamage(damage, loc, dam, '激光'); } } } // 夹击 if (this.info.special.includes(16)) { const dirs = ['left', 'down', 'up', 'right']; const dam = Math.floor(core.status.hero.hp / 2); for (const dir of dirs) { let x = this.x; let y = this.y; const { x: dx, y: dy } = core.utils.scan[dir]; if (caledBetween.has(`${x + dx},${y + dy}`)) continue; const e = this.col.list.find(v => { return v.x === x + dx * 2 && v.y === y + dy * 2; }); if (e && e.info.special.includes(16)) { const loc = `${x + dx},${y + dy}`; this.setMapDamage(damage, loc, dam, '夹击'); caledBetween.add(loc); } } } // 阻击 if (this.info.special.includes(18)) { const range = 1; const startX = Math.max(0, this.x - range); const startY = Math.max(0, this.y - range); const endX = Math.min(floor.width - 1, this.x + range); const endY = Math.min(floor.height - 1, this.y + range); const dam = Math.max(enemy.repulse ?? 0, 0); for (let x = startX; x <= endX; x++) { for (let y = startY; y <= endY; y++) { if ( !enemy.zoneSquare && manhattan(x, y, this.x, this.y) > range ) { continue; } const loc = `${x},${y}`; this.setMapDamage(damage, loc, dam, '阻击'); damage[loc].repulse = damage[loc].repulse ?? []; damage[loc].repulse.push([this.x, this.y]); } } } // 捕捉 if (this.info.special.includes(27)) { const dirs = ['left', 'down', 'up', 'right']; for (const dir of dirs) { let x = this.x; let y = this.y; const { x: dx, y: dy } = core.utils.scan[dir]; const loc = `${x + dx},${y + dy}`; this.setMapDamage(damage, loc, 0); damage[loc].ambush = damage[loc].ambush ?? []; damage[loc].ambush.push(this); } } return damage; } ); // --------------- 光环处理 // 光环分为两类,一类是会增强光环或者给怪物加光环的光环,另一类就是普通光环,这两种光环处理方式不同 // 对于前者,光环将会优先递归计算,同时每个光环将会确保只计算一次,直到没有光环需要计算 // 对于后者,不进行递归计算,只进行单次遍历计算。 // 光环使用 provideHalo 和 injectHalo 作为api,表示提供光环和接受光环 // 光环属性列表,是一个集合Set,你可以在这里配置会被视为光环的属性 const haloSpecials = Mota.require('module', 'Damage').haloSpecials; haloSpecials.add(25).add(26).add(28); // ----- 计算第二类光环,即普通光环,这类光环更常见,因此放到前面了 Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'provideHalo', 'full', function () { // 这部分用于判断当前是否应该计算光环,即计算光环的函数是否在不应该被调用的时刻调用了 // 一般不需要改动 if (this.progress !== 2) return; this.progress = 3; if (!this.floorId) return; const { has } = Mota.Plugin.require('utils_g'); if (!has(this.x) || !has(this.y)) return; const col = this.col ?? core.status.maps[this.floorId].enemy; if (!col) return; // 获取所有还没有计算的光环,注意这里不能直接获取haloSpecial const special = this.getHaloSpecials(); // e 是被加成怪的属性,enemy 是施加光环的怪 for (const halo of special) { switch (halo) { // 普通光环 case 25: { const e = this.enemy; const type = e.haloSquare ? 'square' : 'manhattan'; const r = Math.floor(e.haloRange); const d = type === 'square' ? r * 2 + 1 : r; const range = { x: this.x, y: this.y, d }; // 施加光环 col.applyHalo(type, range, this, (e, enemy) => { e.atkBuff_ += enemy.enemy.atkBuff ?? 0; e.defBuff_ += enemy.enemy.defBuff ?? 0; e.hpBuff_ += enemy.enemy.hpBuff ?? 0; }); // 向已施加的光环列表中添加 this.providedHalo.add(25); break; } case 26: { const range = { x: this.x, y: this.y, d: 1 }; // 支援 col.applyHalo('square', range, this, (e, enemy) => { e.guard = e.guard ?? []; e.guard.push(this); }); this.providedHalo.add(26); break; } } } } ); // ----- 计算第一类光环 // 特殊属性对应的特殊属性数值 const changeable = Mota.require('module', 'Damage').changeableHaloValue; changeable .set(21, ['atkValue', 'defValue']) .set(7, ['breakArmor']) .set(8, ['counterAttack']) .set(22, ['damage']) .set(25, ['haloRange']) .set(24, ['laser']) .set(6, ['n']) .set(9, ['purify']) .set(15, ['range']) .set(18, ['repulse']) .set(11, ['vampire']) .set(15, ['zone']); Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'preProvideHalo', 'full', function () { if (this.progress !== 0) return; this.progress = 1; const special = this.getHaloSpecials(); const col = this.col ?? core.status.maps[this.floorId].enemy; for (const halo of special) { switch (halo) { case 28: { // 特殊光环 const e = this.enemy; const type = e.haloSquare ? 'square' : 'manhattan'; const r = Math.floor(e.haloRange); const d = type === 'square' ? r * 2 + 1 : r; const range = { x: this.x, y: this.y, d }; // 这一句必须放到applyHalo之前 this.providedHalo.add(28); col.applyHalo( type, range, this, (e, enemy) => { const s = enemy.specialHalo; for (const spe of s) { // 防止重复 if (!e.special.includes(spe)) e.special.push(spe); } // 如果是自身,就不进行特殊属性数值处理了 if (e === this.info) return; // 然后计算特殊属性数值 for (const spec of s) { const toChange = changeable.get(spec); if (!toChange) continue; for (const key of toChange) { // 这种光环应该获取怪物的原始数值,而不是真实数值 if (enemy.enemy.specialMultiply) { e[key] = s[key] ?? 1; e[key] *= enemy[key]; } else { e[key] = s[key] ?? 0; e[key] += enemy[key]; } } } }, // true表示递归计算,视为第一类光环 true ); } } } } ); // ----- 接受光环处理 Mota.rewrite( Mota.require('class', 'DamageEnemy').prototype, 'injectHalo', 'full', function (halo, enemy) { // 这里的 halo 是光环函数,enemy 是施加光环的怪物,this.info 是当前怪物信息 halo(this.info, enemy); } ); }, checkBlock: function () { Mota.rewrite(core.control, 'checkBlock', 'full', function () { const x = core.getHeroLoc('x'), y = core.getHeroLoc('y'), loc = x + ',' + y; const info = core.status.thisMap.enemy.mapDamage[loc]; const damage = info?.damage; const floor = core.status.thisMap; if (damage) { // 伤害弹出,在渲染进程中执行 Mota.r(() => { Mota.Plugin.require('pop_r').addPop( (x - core.bigmap.offsetX / 32) * 32 + 12, (y - core.bigmap.offsetY / 32) * 32 + 20, (-damage).toString() ); }); core.status.hero.hp -= damage; const type = [...info.type]; const text = type.join(',') || '伤害'; core.drawTip('受到' + text + damage + '点'); core.drawHeroAnimate('zone'); this._checkBlock_disableQuickShop(); core.status.hero.statistics.extraDamage += damage; if (core.status.hero.hp <= 0) { core.status.hero.hp = 0; core.updateStatusBar(); core.events.lose(); return; } else { core.updateStatusBar(); } } const { findDir, ofDir } = Mota.Plugin.require('utils_g'); // 阻击处理 if (info?.repulse) { const actions = []; for (const [ex, ey] of info.repulse) { const dir = findDir({ x, y }, { x: ex, y: ey }); const [tx, ty] = ofDir(ex, ey, dir); if ( tx < 0 || ty < 0 || tx >= floor.width || ty >= floor.height || core.getBlock(tx, ty) ) { continue; } actions.push({ type: 'move', loc: [ex, ey], steps: [findDir({ x, y }, { x: ex, y: ey })], time: 250, keep: true, async: true }); } actions.push({ type: 'waitAsync' }); core.insertAction(actions); } // 捕捉处理 if (info?.ambush) { const actions = []; for (const enemy of info.ambush) { actions.push({ type: 'move', loc: [enemy.x, enemy.y], steps: [findDir(enemy, { x, y })], time: 250, keep: false, async: true }); } actions.push({ type: 'waitAsync' }); // 强制战斗 for (const enemy of info.ambush) { actions.push({ type: 'function', function: () => { core.battle(enemy, void 0, true, core.doAction); }, async: true }); } core.insertAction(actions); } }); }, misc: function () { // 把一些杂项放在这了 const { loading } = Mota.requireAll('var'); Mota.r(() => { // 楼层滤镜配置 loading.once('coreInit', () => { const { filterMap } = Mota.Plugin.require('gameCanvas_r'); // 楼层滤镜是一系列数组,数组第一项是一个数组,表示所有使用这个滤镜的楼层,第二项是滤镜内容 filterMap.push( [ ['sample0', 'sample1', 'sample2'], // 楼层列表 'brightness(80%)' // 滤镜内容 ], [['MT0'], 'contrast(120%)'] ); }); // 点光源配置,参考插件库点光源插件的配置方式 loading.once('coreInit', () => { const { shadowInfo, backgroundInfo, blurInfo, immersionInfo } = Mota.Plugin.require('gameShadow_r'); const { pColor } = Mota.require('module', 'RenderUtils'); // 光源信息 shadowInfo.MT0 = [ { id: 'mt0_1', x: 144, y: 144, decay: 20, r: 150, color: pColor('#e953'), noShelter: true } ]; // 背景色 backgroundInfo.MT0 = pColor('#0006'); // 虚化程度 blurInfo.MT0 = 3; // 浸入墙壁程度 immersionInfo.MT0 = 4; }); }); } };