discord.js开发Discord机器人,如何匹配用户输入带属性参数并赋值变量
实现方案
核心改造方向
- 替换原有的硬编码顺序取参逻辑,实现通用参数解析器,支持任意顺序的标签式输入,兼容
at: 5、at:5这类带/不带空格的写法 - 给所有计算参数配置和原规则对齐的默认值,解决用户漏输参数导致计算出
NaN的问题,不需要用户每次输全所有值 - 单独维护纯开关参数集合,识别到无附带值的开关标签时,自动触发对应计算分支并入最终输出
- 保留原有顺序输入的兼容性,不破坏老用户的使用习惯
参数解析逻辑设计
- 提前定义短标签和内部参数名的映射关系:
at对应攻击数、sk对应命中技能、st对应力量值、t对应坚韧值、sv对应护甲豁免值 - 单独维护开关参数集合,包含
fnp、res这类无值即可触发逻辑的标签 - 遍历所有输入参数逐段解析:
- 遇到带冒号的参数时,拆分键值对,校验值为有效数字后更新对应内部参数
- 遇到不带冒号、且属于开关集合的参数时,将对应开关标记设为触发状态
- 检测到用户没有输入任何标签/开关参数时,自动回退到原有的顺序解析逻辑
- 输出拼接时可根据需求判断参数是否被用户覆盖,若需要排除缺失参数的对应计算内容,直接跳过对应段落的拼接即可
改造后完整可运行代码
const { Message } = require('discord.js') // 短标签与内部参数映射表 const PARAM_TAG_MAP = { at: 'attack', sk: 'skill', st: 'strength', t: 'toughness', sv: 'save' } // 纯开关参数集合:出现即触发逻辑,无需附带值 const SWITCH_PARAMS = new Set(['fnp', 'res']) module.exports = { name: 'odds', aliases: ['stats', 'probability'], usage: '支持两种输入格式:\n- 兼容旧版顺序输入: !odds [攻击数] [命中技能] [力量] [坚韧] [护甲豁免值]\n- 新版自定义标签输入: !odds at:数值 sk:数值 st:数值 t:数值 sv:数值 可追加开关参数(fnp/res)', description: '伤害概率均值计算器', execute(message, args) { // 初始化参数,配置和原规则对齐的默认值 const params = { attack: 1, skill: 3, strength: 4, toughness: 4, save: 7, switchFlags: { fnp: false, res: false } } // 逐段解析输入参数 let hasCustomInput = false args.forEach(rawArg => { const arg = rawArg.trim().toLowerCase() // 解析带冒号的键值标签参数 if (arg.includes(':')) { const [tag, rawVal] = arg.split(':').map(s => s.trim()) // 标签存在、值为有效数字时才更新参数,避免非法输入覆盖默认值 if (PARAM_TAG_MAP[tag] && !isNaN(Number(rawVal))) { params[PARAM_TAG_MAP[tag]] = Number(rawVal) hasCustomInput = true } } // 解析纯开关参数 else if (SWITCH_PARAMS.has(arg)) { params.switchFlags[arg] = true hasCustomInput = true } }) // 无自定义标签输入时,回退到旧版顺序解析逻辑 if (!hasCustomInput && args.length > 0) { if (args[0]) params.attack = Number(args[0]) if (args[1]) params.skill = Math.min(7, Number(args[1])) // 技能超过7按自动命中处理 if (args[2]) params.strength = Number(args[2]) if (args[3]) params.toughness = Number(args[3]) if (args[4]) params.save = Math.max(2, Number(args[4])) // 豁免低于2按6+保护处理 } // 计算逻辑与原实现完全对齐 const hits = parseFloat((params.attack * ((7 - params.skill)/6)).toFixed(2)) const hitSixes = parseFloat((params.attack * (1/6)).toFixed(2)) let woundFactor = 6 if (params.strength >= (2 * params.toughness)) woundFactor = 2 else if (params.strength > params.toughness) woundFactor = 3 else if (params.strength === params.toughness) woundFactor = 4 else if (params.toughness >= (2 * params.strength)) woundFactor = 6 else if (params.strength < params.toughness) woundFactor = 5 const wounds = parseFloat((hits * ((7 - woundFactor)/6)).toFixed(2)) const woundSixes = parseFloat((hits * (1/6)).toFixed(2)) const unsavedWounds = parseFloat((wounds * (params.save <= 6 ? (7 - params.save)/6 : 0)).toFixed(2)) // 接入开关参数的额外计算分支 let extraResult = '' if (params.switchFlags.fnp) { // 按标准3+无痛感规则计算,可根据实际规则调整概率值 const afterFnp = parseFloat((unsavedWounds * (4/6)).toFixed(2)) extraResult += `\n经过3+无痛感(Feel No Pain)判定后,剩余平均伤害为 ${afterFnp}` } if (params.switchFlags.res) { // 按标准6+抗性规则计算 const afterRes = parseFloat((unsavedWounds * (5/6)).toFixed(2)) extraResult += `\n经过6+抗性(Resistant)判定后,剩余平均伤害为 ${afterRes}` } // 发送最终计算结果 message.channel.send(`平均计算结果:\n${hits} 次成功命中(其中包含 ${hitSixes} 次自然6)\n${wounds} 次成功造伤(其中包含 ${woundSixes} 次自然6)\n最终平均造成 ${unsavedWounds} 点无豁免伤害${extraResult}`) } }
兼容性说明
- 标签识别不区分大小写,输入
AT:5、At:5、at:5均可正常解析 - 参数校验做了容错:如果用户输入的标签值不是有效数字,不会覆盖默认值,避免计算出错
- 原有计算逻辑完全保留,不会因为参数解析改造改变原有输出结果
内容的提问来源于stack exchange,提问作者Rik Powell
相关产品推荐
相关产品推荐

