From 4b01056f872b753bfceeb8b7efcd58f6568e5215 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sun, 27 Jun 2021 04:19:56 +0800 Subject: [PATCH] feat(adventure): enhance item picker --- packages/plugin-adventure/src/event.ts | 30 ++++++++++------------- packages/plugin-adventure/src/index.ts | 19 +++++++++++---- packages/plugin-adventure/src/item.ts | 33 ++++++++++++++------------ packages/plugin-adventure/src/luck.ts | 22 ++++++++--------- packages/plugin-adventure/src/phase.ts | 8 +++++++ 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/packages/plugin-adventure/src/event.ts b/packages/plugin-adventure/src/event.ts index a3dc214a5d..73957c355b 100644 --- a/packages/plugin-adventure/src/event.ts +++ b/packages/plugin-adventure/src/event.ts @@ -99,13 +99,13 @@ namespace Event { // Money - export const loseMoney = (value: Adventurer.Infer): Event => ({ user }) => { + export const loseMoney = (value: Adventurer.Infer): Visible => ({ user }) => { const loss = Math.min(getValue(value, user), user.money) user.money -= loss return `$s 损失了 ${+loss.toFixed(1)}¥!` } - export const gainMoney = (value: Adventurer.Infer): Event => ({ user }) => { + export const gainMoney = (value: Adventurer.Infer): Visible => ({ user }) => { const gain = Math.min(getValue(value, user), user.money) user.money += gain user.wealth += gain @@ -140,22 +140,20 @@ namespace Event { if (result) output.push(result) } session.app.emit('adventure/gain', itemMap, session, output) - const result = Item.checkOverflow(session, Object.keys(itemMap)) - if (result) output.push(result) return output.join('\n') } const rarities = ['N', 'R', 'SR', 'SSR', 'EX'] as Item.Rarity[] - export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { + export const gainRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { const _count = getValue(count, session.user) - const gainListOriginal: string[] = [] - const gainListFormatted: string[] = [] + const itemMap: Record = {} + const gainList: string[] = [] const data = {} as Record for (const rarity of rarities) { - data[rarity] = Item.data[rarity].filter(({ name, condition }) => { - return !exclude.includes(name) && (!condition || condition(session.user, false)) + data[rarity] = Item.data[rarity].filter(({ name, beforePick }) => { + return !exclude.includes(name) && !beforePick?.(session) }).map(({ name }) => name) } @@ -167,15 +165,13 @@ namespace Event { session._item = item const result = Item.gain(session, item) if (result) output.push(result) - gainListOriginal.push(item) - gainListFormatted.push(session._item) + session._gains.add(item) + itemMap[item] = (itemMap[item] || 0) + 1 + gainList.push(session._item) } - output.unshift(`$s 获得了 ${_count} 件随机物品:${Item.format(gainListFormatted)}!`) - const itemMap = toItemMap(gainListOriginal) + output.unshift(`$s 获得了 ${_count} 件随机物品:${Item.format(gainList)}!`) session.app.emit('adventure/gain', itemMap, session, output) - const result = Item.checkOverflow(session, Object.keys(itemMap)) - if (result) output.push(result) return output.join('\n') } @@ -192,7 +188,7 @@ namespace Event { return output.join('\n') } - export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Event => (session) => { + export const loseRandom = (count: Adventurer.Infer, exclude: readonly string[] = []): Visible => (session) => { const lostList: string[] = [] let length = 0 @@ -222,7 +218,7 @@ namespace Event { return output.join('\n') } - export const loseRecent = (count: Adventurer.Infer): Event => (session) => { + export const loseRecent = (count: Adventurer.Infer): Visible => (session) => { const _count = getValue(count, session.user) const recent = session.user.recent.slice(0, _count) if (!recent.length) return diff --git a/packages/plugin-adventure/src/index.ts b/packages/plugin-adventure/src/index.ts index 0ee46c29f2..44a14b610d 100644 --- a/packages/plugin-adventure/src/index.ts +++ b/packages/plugin-adventure/src/index.ts @@ -1,5 +1,5 @@ import { Context, User, isInteger } from 'koishi-core' -import { Show } from './utils' +import { Adventurer, Show } from './utils' import Achievement from './achv' import Affinity from './affinity' import Buff from './buff' @@ -56,12 +56,23 @@ export function apply(ctx: Context, config?: Config) { ctx.plugin(Show) ctx.command('user.add-item', '添加物品', { authority: 4 }) - .userFields(['warehouse']) - .adminUser(({ target }, item, count = '1') => { + .option('effect', '-e 触发效果') + .userFields(({ options }, fields) => { + if (!options.effect) { + return fields.add('warehouse') + } + for (const field of Adventurer.fields) { + fields.add(field) + } + }) + .adminUser(({ target, options, session }, item, count = '1') => { if (!Item.data[item]) return `未找到物品“${item}”。` - const currentCount = target.warehouse[item] || 0 const nCount = Number(count) if (!isInteger(nCount) || nCount <= 0) return '参数错误。' + if (options.effect) { + return Event.gain({ [item]: nCount })(session).replace(/\$s/g, session.username) + } + const currentCount = target.warehouse[item] || 0 target.warehouse[item] = currentCount + nCount }) diff --git a/packages/plugin-adventure/src/item.ts b/packages/plugin-adventure/src/item.ts index 6462bb59ba..7886b6d689 100644 --- a/packages/plugin-adventure/src/item.ts +++ b/packages/plugin-adventure/src/item.ts @@ -5,7 +5,7 @@ import Phase from './phase' import Rank from './rank' type Note = (user: Pick) => string -type Condition = (user: ReadonlyUser, isLast: boolean) => boolean +type BeforePick = (session: Adventurer.Session) => boolean interface Item { name: string @@ -15,12 +15,12 @@ interface Item { value?: number bid?: number onGain?: Event - onLose?: Event<'usage'> + onLose?: Event + beforePick?: BeforePick lottery?: number fishing?: number plot?: boolean note?: Note - condition?: Condition } namespace Item { @@ -54,10 +54,6 @@ namespace Item { data[name].note = note } - export function condition(name: string, condition: Condition) { - data[name].condition = condition - } - export function onGain(name: string, event: Event) { data[name].onGain = event } @@ -66,20 +62,27 @@ namespace Item { data[name].onLose = event } + export function beforePick(name: string, event: BeforePick) { + data[name].beforePick = event + } + export interface Config { createBuyer?: (user: User.Observed<'timers'>) => (name: string) => number createSeller?: (user: User.Observed<'timers'>) => (name: string) => number } - export function pick(items: Item[], user: ReadonlyUser, isLast = false) { - const weightEntries = items.filter(({ lottery, condition }) => { - return lottery !== 0 && (!condition || condition(user, isLast)) - }).map(({ name, lottery }) => [name, lottery ?? 1] as const) - const weight = Object.fromEntries(weightEntries) - return Item.data[Random.weightedPick(weight)] + type Keys = { [K in keyof O]: O[K] extends T ? K : never }[keyof O] + + export function pick(items: Item[], session: Adventurer.Session, key: Keys, fallback: number) { + const weightEntries = items.map<[string, number]>((item) => { + const probability = item[key] ?? fallback + if (!probability || item.beforePick?.(session)) return [item.name, 0] + return [item.name, probability] + }) + return Item.data[Random.weightedPick(Object.fromEntries(weightEntries))] } - export function lose(session: Session<'usage' | 'warehouse'>, name: string, count = 1) { + export function lose(session: Adventurer.Session, name: string, count = 1) { if (session.user.warehouse[name]) { session.user.warehouse[name] -= count } @@ -132,7 +135,7 @@ namespace Item { } } - export function checkOverflow(session: Adventurer.Session, names = Object.keys(session.user.warehouse)) { + export function checkOverflow(session: Adventurer.Session, names: Iterable = session._gains) { const itemMap: Record = {} for (const name of names) { const { maxCount, value } = Item.data[name] diff --git a/packages/plugin-adventure/src/luck.ts b/packages/plugin-adventure/src/luck.ts index 9994244b65..df1fb74e72 100644 --- a/packages/plugin-adventure/src/luck.ts +++ b/packages/plugin-adventure/src/luck.ts @@ -88,7 +88,7 @@ namespace Luck { if (options.tenTimes) { const output = [`恭喜 ${session.username} 获得了:`] for (let index = 10; index > 0; index--) { - const prize = Item.pick(Item.data[Random.weightedPick(probabilities)], user) + const prize = Item.pick(Item.data[Random.weightedPick(probabilities)], session, 'lottery', 1) output.push(`${prize.name}(${prize.rarity})`) } return output.join('\n') @@ -96,15 +96,15 @@ namespace Luck { const affinity = Affinity.get(user) const maxUsage = Math.floor(affinity / 30) + 5 - let times = maxUsage - getUsage('lottery', user) - if (times <= 0) { + session._lotteryLast = maxUsage - getUsage('lottery', user) + if (session._lotteryLast <= 0) { return '调用次数已达上限。' } - const gainList: string[] = [] + session._gains = new Set() const output: string[] = [] function getPrize(output: string[]) { - times -= 1 + session._lotteryLast -= 1 const weights = user.noSR >= 9 ? allowanceProbabilities : probabilities const rarity = Luck.use(user).weightedPick(weights) if (rarity === 'R' || rarity === 'N') { @@ -112,10 +112,10 @@ namespace Luck { } else { user.noSR = 0 } - const item = Item.pick(Item.data[rarity], user, !times) + const item = Item.pick(Item.data[rarity], session, 'lottery', 1) const { name, description } = item const isOld = item.name in user.warehouse - gainList.push(name) + session._gains.add(name) session._item = name const result = Item.gain(session, item.name) if (options.simple && options.quick) { @@ -131,18 +131,18 @@ namespace Luck { getPrize(output) } else { if (options.simple) output.push(`恭喜 ${session.username} 获得了:`) - while (times && !checkTimer('$lottery', user)) { + while (session._lotteryLast && !checkTimer('$lottery', user)) { getPrize(output) } } - const result = Item.checkOverflow(session, gainList) + const result = Item.checkOverflow(session) if (result) output.push(result) - user.usage.lottery = maxUsage - times + user.usage.lottery = maxUsage - session._lotteryLast session.app.emit('adventure/check', session, output) await user._update() - if (!times) output.push('您本日的抽奖次数已用完,请明天再试吧~') + if (!session._lotteryLast) output.push('您本日的抽奖次数已用完,请明天再试吧~') return output.join('\n').replace(/\$s/g, session.username) }) } diff --git a/packages/plugin-adventure/src/phase.ts b/packages/plugin-adventure/src/phase.ts index d6ae3a54ef..c34ba48a6d 100644 --- a/packages/plugin-adventure/src/phase.ts +++ b/packages/plugin-adventure/src/phase.ts @@ -17,6 +17,10 @@ declare module 'koishi-core' { _canSkip?: boolean /** 即将获得的道具名 */ _item: string + /** 当前获得的物品列表 */ + _gains: Set + /** 剩余抽卡次数 */ + _lotteryLast: number } } @@ -296,6 +300,8 @@ export namespace Phase { /** handle events */ async function epilog(session: Adventurer.Session, events: Event[] = []) { + session._gains = new Set() + const hints: string[] = [] for (const event of events || []) { const result = event(session) @@ -306,6 +312,8 @@ export namespace Phase { } } + const result = Item.checkOverflow(session) + if (result) hints.push(result) session.app.emit('adventure/check', session, hints) await sendEscaped(session, hints.join('\n')) }