koishi-plugin-maple-warriors 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/index.d.ts +52 -0
  2. package/lib/index.js +946 -276
  3. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -27,65 +27,231 @@ __export(src_exports, {
27
27
  });
28
28
  module.exports = __toCommonJS(src_exports);
29
29
  var import_koishi = require("koishi");
30
- var using = ["database"];
31
30
  var name = "maple-warriors";
31
+ var using = ["database"];
32
32
  var DEFAULT_PREY_DATA = [
33
- "老鼠 5 1 1 1 1 1-20",
34
- "野兔 1 3-5 2-4 2-4 1-3 5-50"
33
+ "老鼠 100 1 1-5 1 1 1-20",
34
+ "松鼠 20 1-5 1 1 1 5-50",
35
+ "野兔 10 3-5 2-4 2-4 1-3 5-50",
36
+ "獾 1 100 100 100 100 100"
35
37
  ];
36
38
  var Config = import_koishi.Schema.object({
37
- preyData: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default(DEFAULT_PREY_DATA).description("猎物数据,每行格式:猎物名 权重 体质 攻击 韧性 暴击 敏捷(权重是固定数值,属性可以是固定数值或范围,如1-10)"),
39
+ preyData: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default(DEFAULT_PREY_DATA).description("猎物数据,每行格式:猎物名 权重 体质 攻击 防御 暴击 敏捷(权重是固定数值,属性可以是固定数值或范围,如1-10)"),
38
40
  battleMessageInterval: import_koishi.Schema.number().min(1).max(5).default(2).description("战斗消息发送间隔(秒)")
39
41
  });
42
+ var INITIAL_ITEMS = [
43
+ {
44
+ name: "老鼠",
45
+ type: "猎物",
46
+ sellPrice: 1,
47
+ shopPrice: 5,
48
+ hpBonus: 1,
49
+ constitutionBonus: 0,
50
+ attackBonus: 0,
51
+ toughnessBonus: 0,
52
+ critBonus: 0,
53
+ agilityBonus: 0,
54
+ buff: null,
55
+ description: "随处可见的普通猎物"
56
+ },
57
+ {
58
+ name: "松鼠",
59
+ type: "猎物",
60
+ sellPrice: 2,
61
+ shopPrice: 10,
62
+ hpBonus: 2,
63
+ constitutionBonus: 0,
64
+ attackBonus: 0,
65
+ toughnessBonus: 0,
66
+ critBonus: 0,
67
+ agilityBonus: 0,
68
+ buff: null,
69
+ description: "上蹿下跳!"
70
+ },
71
+ {
72
+ name: "野兔",
73
+ type: "猎物",
74
+ sellPrice: 5,
75
+ shopPrice: 50,
76
+ hpBonus: 5,
77
+ constitutionBonus: 0,
78
+ attackBonus: 0,
79
+ toughnessBonus: 0,
80
+ critBonus: 0,
81
+ agilityBonus: 0,
82
+ buff: null,
83
+ description: "跑得很快的猎物"
84
+ },
85
+ {
86
+ name: "獾",
87
+ type: "猎物",
88
+ sellPrice: 50,
89
+ shopPrice: -1,
90
+ hpBonus: 10,
91
+ constitutionBonus: 0,
92
+ attackBonus: 0,
93
+ toughnessBonus: 0,
94
+ critBonus: 0,
95
+ agilityBonus: 0,
96
+ buff: null,
97
+ description: "危险!而且味道很大!"
98
+ }
99
+ ];
40
100
  function calculateRates(character) {
41
101
  const resistance = (character.toughness / (character.toughness + 90)).toFixed(4);
42
102
  const critRate = (character.crit / (character.crit + 90)).toFixed(4);
43
- const chargeTime = (1 + 280 / (character.agility + 70)).toFixed(2);
103
+ const chargeTime = (1 + 720 / (character.agility + 80)).toFixed(2);
44
104
  const dodgeRate = (0.2 * character.agility / (character.agility + 90)).toFixed(4);
45
105
  return { resistance, critRate, chargeTime, dodgeRate };
46
106
  }
47
107
  __name(calculateRates, "calculateRates");
48
- var BattleManager = class {
49
- static {
50
- __name(this, "BattleManager");
108
+ function parseBuffs(buffsStr) {
109
+ if (!buffsStr) return [];
110
+ try {
111
+ return JSON.parse(buffsStr);
112
+ } catch {
113
+ return [];
51
114
  }
52
- battles = /* @__PURE__ */ new Map();
53
- startBattle(channelId, playerHp, preyName) {
54
- if (this.isBattling(channelId)) {
55
- return false;
56
- }
57
- this.battles.set(channelId, {
58
- isBattling: true,
59
- abortController: new AbortController(),
60
- playerHp,
61
- preyName
62
- });
63
- return true;
115
+ }
116
+ __name(parseBuffs, "parseBuffs");
117
+ function serializeBuffs(buffs) {
118
+ return JSON.stringify(buffs);
119
+ }
120
+ __name(serializeBuffs, "serializeBuffs");
121
+ function parseItemBuff(buffStr) {
122
+ if (!buffStr) return null;
123
+ try {
124
+ return JSON.parse(buffStr);
125
+ } catch {
126
+ return null;
64
127
  }
65
- endBattle(channelId, finalPlayerHp) {
66
- const battle = this.battles.get(channelId);
67
- if (battle && battle.isBattling) {
68
- if (battle.abortController) {
69
- battle.abortController.abort();
70
- }
71
- if (finalPlayerHp !== void 0) {
72
- battle.playerHp = finalPlayerHp;
73
- }
74
- this.battles.delete(channelId);
75
- return true;
128
+ }
129
+ __name(parseItemBuff, "parseItemBuff");
130
+ function calculateCharacterAttributes(character) {
131
+ const buffs = parseBuffs(character.buffs);
132
+ const now = /* @__PURE__ */ new Date();
133
+ const activeBuffs = buffs.filter((buff) => {
134
+ return new Date(buff.endTime) > now;
135
+ });
136
+ const baseAttributes = {
137
+ constitution: character.constitution,
138
+ attack: character.attack,
139
+ toughness: character.toughness,
140
+ crit: character.crit,
141
+ agility: character.agility
142
+ };
143
+ let constitutionBonus = 0;
144
+ let attackBonus = 0;
145
+ let toughnessBonus = 0;
146
+ let critBonus = 0;
147
+ let agilityBonus = 0;
148
+ activeBuffs.forEach((buff) => {
149
+ if (buff.effects) {
150
+ buff.effects.forEach((effect) => {
151
+ const percentage = effect.percentage || 0;
152
+ const max = effect.max || 0;
153
+ let bonus = 0;
154
+ switch (effect.attribute) {
155
+ case "体质":
156
+ bonus = Math.min(Math.floor(character.constitution * percentage / 100), max);
157
+ constitutionBonus += bonus;
158
+ break;
159
+ case "攻击":
160
+ bonus = Math.min(Math.floor(character.attack * percentage / 100), max);
161
+ attackBonus += bonus;
162
+ break;
163
+ case "防御":
164
+ bonus = Math.min(Math.floor(character.toughness * percentage / 100), max);
165
+ toughnessBonus += bonus;
166
+ break;
167
+ case "暴击":
168
+ bonus = Math.min(Math.floor(character.crit * percentage / 100), max);
169
+ critBonus += bonus;
170
+ break;
171
+ case "敏捷":
172
+ bonus = Math.min(Math.floor(character.agility * percentage / 100), max);
173
+ agilityBonus += bonus;
174
+ break;
175
+ }
176
+ });
177
+ }
178
+ });
179
+ const buffBonuses = {
180
+ constitution: constitutionBonus,
181
+ attack: attackBonus,
182
+ toughness: toughnessBonus,
183
+ crit: critBonus,
184
+ agility: agilityBonus
185
+ };
186
+ const totalAttributes = {
187
+ constitution: character.constitution + constitutionBonus,
188
+ attack: character.attack + attackBonus,
189
+ toughness: character.toughness + toughnessBonus,
190
+ crit: character.crit + critBonus,
191
+ agility: character.agility + agilityBonus
192
+ };
193
+ const rates = calculateRates(totalAttributes);
194
+ const maxHp = totalAttributes.constitution * 5;
195
+ return {
196
+ base: baseAttributes,
197
+ buffs: buffBonuses,
198
+ total: totalAttributes,
199
+ rates,
200
+ maxHp
201
+ };
202
+ }
203
+ __name(calculateCharacterAttributes, "calculateCharacterAttributes");
204
+ function getBattleCharacter(character) {
205
+ const attrs = calculateCharacterAttributes(character);
206
+ return {
207
+ name: character.name,
208
+ constitution: attrs.total.constitution,
209
+ attack: attrs.total.attack,
210
+ toughness: attrs.total.toughness,
211
+ crit: attrs.total.crit,
212
+ agility: attrs.total.agility,
213
+ hp: Math.min(character.currentHp, attrs.maxHp),
214
+ // 确保当前HP不超过最大HP
215
+ maxHp: attrs.maxHp,
216
+ chargeTime: parseFloat(attrs.rates.chargeTime),
217
+ nextAttackTime: 0
218
+ };
219
+ }
220
+ __name(getBattleCharacter, "getBattleCharacter");
221
+ function parseTimeString(timeStr) {
222
+ let totalSeconds = 0;
223
+ const timeRegex = /(\d+)([hms])/g;
224
+ let match;
225
+ while ((match = timeRegex.exec(timeStr)) !== null) {
226
+ const value = parseInt(match[1]);
227
+ const unit = match[2];
228
+ switch (unit) {
229
+ case "h":
230
+ totalSeconds += value * 3600;
231
+ break;
232
+ case "m":
233
+ totalSeconds += value * 60;
234
+ break;
235
+ case "s":
236
+ totalSeconds += value;
237
+ break;
76
238
  }
77
- return false;
78
- }
79
- isBattling(channelId) {
80
- return this.battles.has(channelId) && this.battles.get(channelId).isBattling;
81
- }
82
- getAbortController(channelId) {
83
- return this.battles.get(channelId)?.abortController;
84
- }
85
- getBattleData(channelId) {
86
- return this.battles.get(channelId);
87
239
  }
88
- };
240
+ return totalSeconds;
241
+ }
242
+ __name(parseTimeString, "parseTimeString");
243
+ function formatTimeRemaining(seconds) {
244
+ if (seconds <= 0) return "0s";
245
+ const hours = Math.floor(seconds / 3600);
246
+ const minutes = Math.floor(seconds % 3600 / 60);
247
+ const secs = seconds % 60;
248
+ const parts = [];
249
+ if (hours > 0) parts.push(`${hours}h`);
250
+ if (minutes > 0) parts.push(`${minutes}m`);
251
+ if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
252
+ return parts.join("");
253
+ }
254
+ __name(formatTimeRemaining, "formatTimeRemaining");
89
255
  function parseAttributeRange(value) {
90
256
  if (value.includes("-")) {
91
257
  const [min, max] = value.split("-").map((v) => parseInt(v.trim()));
@@ -121,6 +287,10 @@ function formatRemainingTime(endTime) {
121
287
  return parts.join("");
122
288
  }
123
289
  __name(formatRemainingTime, "formatRemainingTime");
290
+ function formatGold(amount) {
291
+ return amount.toLocaleString("en-US");
292
+ }
293
+ __name(formatGold, "formatGold");
124
294
  function getTodayDateString() {
125
295
  const now = /* @__PURE__ */ new Date();
126
296
  return now.toISOString().split("T")[0];
@@ -180,6 +350,47 @@ function selectRandomPrey(preyList) {
180
350
  return preyList[0];
181
351
  }
182
352
  __name(selectRandomPrey, "selectRandomPrey");
353
+ var BattleManager = class {
354
+ static {
355
+ __name(this, "BattleManager");
356
+ }
357
+ battles = /* @__PURE__ */ new Map();
358
+ startBattle(channelId, playerHp, preyName) {
359
+ if (this.isBattling(channelId)) {
360
+ return false;
361
+ }
362
+ this.battles.set(channelId, {
363
+ isBattling: true,
364
+ abortController: new AbortController(),
365
+ playerHp,
366
+ preyName
367
+ });
368
+ return true;
369
+ }
370
+ endBattle(channelId, finalPlayerHp) {
371
+ const battle = this.battles.get(channelId);
372
+ if (battle && battle.isBattling) {
373
+ if (battle.abortController) {
374
+ battle.abortController.abort();
375
+ }
376
+ if (finalPlayerHp !== void 0) {
377
+ battle.playerHp = finalPlayerHp;
378
+ }
379
+ this.battles.delete(channelId);
380
+ return true;
381
+ }
382
+ return false;
383
+ }
384
+ isBattling(channelId) {
385
+ return this.battles.has(channelId) && this.battles.get(channelId).isBattling;
386
+ }
387
+ getAbortController(channelId) {
388
+ return this.battles.get(channelId)?.abortController;
389
+ }
390
+ getBattleData(channelId) {
391
+ return this.battles.get(channelId);
392
+ }
393
+ };
183
394
  function apply(ctx, config) {
184
395
  const battleManager = new BattleManager();
185
396
  ctx.model.extend("maple_warriors", {
@@ -187,16 +398,19 @@ function apply(ctx, config) {
187
398
  userId: "string",
188
399
  name: "string",
189
400
  level: "unsigned",
401
+ gold: "unsigned",
402
+ // 新增:金币
190
403
  constitution: "unsigned",
191
404
  attack: "unsigned",
192
405
  toughness: "unsigned",
193
406
  crit: "unsigned",
194
407
  agility: "unsigned",
195
408
  currentHp: "unsigned",
196
- maxHp: "unsigned",
197
409
  status: "string",
198
410
  previousStatus: "string",
199
411
  statusEndTime: "timestamp",
412
+ buffs: "text",
413
+ // 新增:当前生效的buff(JSON格式)
200
414
  items: "text",
201
415
  lastTrainDate: "string",
202
416
  lastHuntDate: "string",
@@ -208,6 +422,14 @@ function apply(ctx, config) {
208
422
  ctx.model.extend("maple_warriors_items", {
209
423
  id: "unsigned",
210
424
  name: "string",
425
+ type: "string",
426
+ // 新增:物品类型
427
+ sellPrice: "integer",
428
+ // 新增:出售价
429
+ shopPrice: "integer",
430
+ // 新增:商店价
431
+ buff: "text",
432
+ // 新增:buff效果(JSON格式)
211
433
  hpBonus: "integer",
212
434
  constitutionBonus: "integer",
213
435
  attackBonus: "integer",
@@ -221,22 +443,55 @@ function apply(ctx, config) {
221
443
  autoInc: true,
222
444
  unique: ["name"]
223
445
  });
446
+ ctx.on("ready", async () => {
447
+ try {
448
+ const existingItems = await ctx.database.get("maple_warriors_items", {});
449
+ if (existingItems.length === 0) {
450
+ for (const itemData of INITIAL_ITEMS) {
451
+ await ctx.database.create("maple_warriors_items", {
452
+ ...itemData,
453
+ createdAt: /* @__PURE__ */ new Date()
454
+ });
455
+ }
456
+ const logger = ctx.logger("maple-warriors");
457
+ logger.info(`检测到物品表为空,已自动初始化 ${INITIAL_ITEMS.length} 个物品到物品表`);
458
+ }
459
+ } catch (error) {
460
+ const logger = ctx.logger("maple-warriors");
461
+ logger.error("初始化物品表失败:", error);
462
+ }
463
+ });
224
464
  async function formatCharacterInfo(character) {
225
- const rates = calculateRates({
226
- toughness: character.toughness,
227
- crit: character.crit,
228
- agility: character.agility
229
- });
465
+ const attrs = calculateCharacterAttributes(character);
230
466
  const items = parseItemsString(character.items);
231
467
  const itemsText = Object.entries(items).map(([name2, count]) => `${name2}*${count}`).join("\n");
468
+ const buffs = parseBuffs(character.buffs);
469
+ const now = /* @__PURE__ */ new Date();
470
+ const activeBuffs = buffs.filter((buff) => new Date(buff.endTime) > now);
471
+ let buffsText = "无";
472
+ if (activeBuffs.length > 0) {
473
+ buffsText = activeBuffs.map((buff) => {
474
+ const endTime = new Date(buff.endTime);
475
+ const remainingSeconds = Math.max(0, (endTime.getTime() - now.getTime()) / 1e3);
476
+ return `${buff.name}(${formatTimeRemaining(Math.floor(remainingSeconds))})`;
477
+ }).join(" ");
478
+ }
479
+ const constitutionDisplay = attrs.buffs.constitution > 0 ? `${attrs.base.constitution}(+${attrs.buffs.constitution})` : attrs.base.constitution.toString();
480
+ const attackDisplay = attrs.buffs.attack > 0 ? `${attrs.base.attack}(+${attrs.buffs.attack})` : attrs.base.attack.toString();
481
+ const toughnessDisplay = attrs.buffs.toughness > 0 ? `${attrs.base.toughness}(+${attrs.buffs.toughness})` : attrs.base.toughness.toString();
482
+ const critDisplay = attrs.buffs.crit > 0 ? `${attrs.base.crit}(+${attrs.buffs.crit})` : attrs.base.crit.toString();
483
+ const agilityDisplay = attrs.buffs.agility > 0 ? `${attrs.base.agility}(+${attrs.buffs.agility})` : attrs.base.agility.toString();
484
+ const currentHp = Math.min(character.currentHp, attrs.maxHp);
232
485
  return `名字: ${character.name}
233
486
  等级: ${character.level}
234
- 体质: ${character.constitution} (HP: ${character.currentHp}/${character.maxHp})
235
- 攻击: ${character.attack}
236
- 韧性: ${character.toughness} (抗性: ${(parseFloat(rates.resistance) * 100).toFixed(2)}%)
237
- 暴击: ${character.crit} (暴击率: ${(parseFloat(rates.critRate) * 100).toFixed(2)}%)
238
- 敏捷: ${character.agility} (蓄力: ${rates.chargeTime}s 闪避率: ${(parseFloat(rates.dodgeRate) * 100).toFixed(2)}%)
487
+ 金币: ${formatGold(character.gold)}
488
+ 体质: ${constitutionDisplay} (HP: ${currentHp}/${attrs.maxHp})
489
+ 攻击: ${attackDisplay}
490
+ 防御: ${toughnessDisplay} (韧性: ${(parseFloat(attrs.rates.resistance) * 100).toFixed(2)}%)
491
+ 暴击: ${critDisplay} (暴击率: ${(parseFloat(attrs.rates.critRate) * 100).toFixed(2)}%)
492
+ 敏捷: ${agilityDisplay} (蓄力: ${attrs.rates.chargeTime}s 闪避率: ${(parseFloat(attrs.rates.dodgeRate) * 100).toFixed(2)}%)
239
493
  状态: ${character.status}${character.statusEndTime ? " 剩余" + formatRemainingTime(character.statusEndTime) : ""}
494
+ buff: ${buffsText}
240
495
  ──────────
241
496
  物品栏:
242
497
  ${itemsText || "空空如也"}`;
@@ -251,16 +506,19 @@ ${itemsText || "空空如也"}`;
251
506
  userId,
252
507
  name: name2 || '请输入"修改名字 角色名"修改',
253
508
  level: 1,
509
+ gold: 0,
510
+ // 新增:初始化金币为0
254
511
  constitution: attributes.constitution,
255
512
  attack: attributes.attack,
256
513
  toughness: attributes.toughness,
257
514
  crit: attributes.crit,
258
515
  agility: attributes.agility,
259
516
  currentHp: maxHp,
260
- maxHp,
261
517
  status: "无",
262
518
  previousStatus: "无",
263
519
  statusEndTime: null,
520
+ buffs: "[]",
521
+ // 初始化 buffs 为空数组
264
522
  items: "{}",
265
523
  lastTrainDate: "",
266
524
  lastHuntDate: "",
@@ -272,7 +530,38 @@ ${itemsText || "空空如也"}`;
272
530
  return characters[0];
273
531
  }
274
532
  __name(getOrCreateCharacter, "getOrCreateCharacter");
275
- function checkCharacterStatus(character) {
533
+ const statusRules = {
534
+ // 状态列表:
535
+ // 无,受伤,睡眠,治疗
536
+ // 受影响的指令列表(角色操作指令):
537
+ // 训练,捕猎,睡眠,治疗,战斗训练,使用物品
538
+ "受伤": {
539
+ // 可以使用物品,治疗
540
+ "训练": { message: '你受伤了,快输入"治疗"吧!' },
541
+ "捕猎": { message: '你受伤了,快输入"治疗"吧!' },
542
+ "睡眠": { message: '你受伤了,快输入"治疗"吧!' },
543
+ "战斗训练": { message: '你受伤了,快输入"治疗"吧!' }
544
+ },
545
+ "睡眠": {
546
+ // 都不行
547
+ "训练": { message: "你想在梦里训练吗?" },
548
+ "捕猎": { message: "你想在梦里捕猎吗?" },
549
+ "治疗": { message: "你想在梦里治疗吗?" },
550
+ "使用物品": { message: "你想在梦里使用物品吗?" },
551
+ "战斗训练": { message: "你想在梦里战斗训练吗?" },
552
+ "睡眠": { message: "已经处于睡眠中!" }
553
+ },
554
+ "治疗": {
555
+ // 都不行
556
+ "训练": { message: "正在治疗中,请好好养伤!" },
557
+ "捕猎": { message: "正在治疗中,请好好养伤!" },
558
+ "睡眠": { message: "正在治疗中,请好好养伤!" },
559
+ "使用物品": { message: "正在治疗中,请好好养伤!" },
560
+ "战斗训练": { message: "正在治疗中,请好好养伤!" },
561
+ "治疗": { message: "已经处于治疗中!" }
562
+ }
563
+ };
564
+ function checkInstructionStatus(character, instruction) {
276
565
  const now = /* @__PURE__ */ new Date();
277
566
  if (character.statusEndTime && now >= character.statusEndTime) {
278
567
  const updates = {
@@ -282,25 +571,29 @@ ${itemsText || "空空如也"}`;
282
571
  updatedAt: /* @__PURE__ */ new Date()
283
572
  };
284
573
  if (character.status === "睡眠" || character.status === "治疗") {
285
- updates.currentHp = character.maxHp;
574
+ const attrs = calculateCharacterAttributes(character);
575
+ updates.currentHp = attrs.maxHp;
286
576
  }
287
577
  ctx.database.set("maple_warriors", { id: character.id }, updates);
288
578
  return { canAct: true, message: "" };
289
579
  }
290
- if (character.status === "受伤") {
291
- return { canAct: false, message: '你受伤了,快输入"治疗"吧!' };
580
+ const status = character.status;
581
+ if (status === "") {
582
+ return { canAct: true, message: "" };
292
583
  }
293
- if (character.status === "睡眠") {
294
- return { canAct: false, message: "你想在梦里做这个吗?" };
584
+ const statusRule = statusRules[status];
585
+ if (!statusRule) {
586
+ return { canAct: true, message: "" };
295
587
  }
296
- if (character.status === "治疗") {
297
- return { canAct: false, message: "正在治疗中,请好好养伤!" };
588
+ const instructionRule = statusRule[instruction];
589
+ if (!instructionRule) {
590
+ return { canAct: true, message: "" };
298
591
  }
299
- return { canAct: true, message: "" };
592
+ return { canAct: false, message: instructionRule.message };
300
593
  }
301
- __name(checkCharacterStatus, "checkCharacterStatus");
594
+ __name(checkInstructionStatus, "checkInstructionStatus");
302
595
  ctx.command("猫武士", "猫武士插件 - 帮助菜单");
303
- ctx.command("猫武士/修改名字 <name>", "修改角色名字").example("修改名字 火焰猫").example("修改名字 闪电虎").action(async ({ session }, name2) => {
596
+ ctx.command("猫武士/修改名字 <name>", "修改角色名字").example("修改名字 火星").action(async ({ session }, name2) => {
304
597
  if (!name2 || name2.trim().length === 0) {
305
598
  return "格式错误!正确格式:修改名字 角色名";
306
599
  }
@@ -314,13 +607,14 @@ ${itemsText || "空空如也"}`;
314
607
  userId,
315
608
  name: trimmedName,
316
609
  level: 1,
610
+ gold: 0,
611
+ // 新增:初始化金币为0
317
612
  constitution: attributes.constitution,
318
613
  attack: attributes.attack,
319
614
  toughness: attributes.toughness,
320
615
  crit: attributes.crit,
321
616
  agility: attributes.agility,
322
617
  currentHp: maxHp,
323
- maxHp,
324
618
  status: "无",
325
619
  previousStatus: "无",
326
620
  statusEndTime: null,
@@ -341,79 +635,135 @@ ${itemsText || "空空如也"}`;
341
635
  ctx.command("猫武士/我的角色", "查看我的角色信息").example("我的角色").action(async ({ session }) => {
342
636
  const userId = session.userId;
343
637
  let character = await getOrCreateCharacter(userId);
344
- return await formatCharacterInfo(character);
638
+ const attrs = calculateCharacterAttributes(character);
639
+ const items = parseItemsString(character.items);
640
+ const itemsText = Object.entries(items).map(([name2, count]) => `${name2}*${count}`).join("\n");
641
+ const buffs = parseBuffs(character.buffs);
642
+ const now = /* @__PURE__ */ new Date();
643
+ const activeBuffs = buffs.filter((buff) => new Date(buff.endTime) > now);
644
+ let buffsText = "无";
645
+ if (activeBuffs.length > 0) {
646
+ buffsText = activeBuffs.map((buff) => {
647
+ const endTime = new Date(buff.endTime);
648
+ const remainingSeconds = Math.max(0, (endTime.getTime() - now.getTime()) / 1e3);
649
+ return `${buff.name}(${formatTimeRemaining(Math.floor(remainingSeconds))})`;
650
+ }).join(" ");
651
+ }
652
+ const constitutionDisplay = attrs.buffs.constitution > 0 ? `${attrs.total.constitution}(+${attrs.buffs.constitution})` : attrs.total.constitution.toString();
653
+ const attackDisplay = attrs.buffs.attack > 0 ? `${attrs.total.attack}(+${attrs.buffs.attack})` : attrs.total.attack.toString();
654
+ const toughnessDisplay = attrs.buffs.toughness > 0 ? `${attrs.total.toughness}(+${attrs.buffs.toughness})` : attrs.total.toughness.toString();
655
+ const critDisplay = attrs.buffs.crit > 0 ? `${attrs.total.crit}(+${attrs.buffs.crit})` : attrs.total.crit.toString();
656
+ const agilityDisplay = attrs.buffs.agility > 0 ? `${attrs.total.agility}(+${attrs.buffs.agility})` : attrs.total.agility.toString();
657
+ const currentHp = Math.min(character.currentHp, attrs.maxHp);
658
+ return `名字: ${character.name}
659
+ 等级: ${character.level}
660
+ 体质: ${constitutionDisplay} (HP: ${currentHp}/${attrs.maxHp})
661
+ 攻击: ${attackDisplay}
662
+ 防御: ${toughnessDisplay} (韧性: ${(parseFloat(attrs.rates.resistance) * 100).toFixed(2)}%)
663
+ 暴击: ${critDisplay} (暴击率: ${(parseFloat(attrs.rates.critRate) * 100).toFixed(2)}%)
664
+ 敏捷: ${agilityDisplay} (蓄力: ${attrs.rates.chargeTime}s 闪避率: ${(parseFloat(attrs.rates.dodgeRate) * 100).toFixed(2)}%)
665
+ 状态: ${character.status}${character.statusEndTime ? " 剩余" + formatRemainingTime(character.statusEndTime) : ""}
666
+ buff: ${buffsText}
667
+ ──────────
668
+ 物品栏:
669
+ ${itemsText || "空空如也"}`;
345
670
  });
346
- ctx.command("猫武士/训练 <attrs>", "训练提升属性").example("训练 体质 体质 攻击").example("训练 体质").example("训练 随机").action(async ({ session }, attrs) => {
671
+ ctx.command("猫武士/训练 <...args>", "训练提升属性").example("训练 体质 体质 攻击").example("训练 体质").example("训练 随机").action(async ({ session }, ...args) => {
347
672
  const userId = session.userId;
348
673
  let character = await getOrCreateCharacter(userId);
349
- const statusCheck = checkCharacterStatus(character);
350
- if (!statusCheck.canAct) {
351
- return statusCheck.message;
352
- }
353
674
  const today = getTodayDateString();
354
675
  if (character.lastTrainDate && isSameDay(character.lastTrainDate, today)) {
355
676
  return "今天已经训练过了,要注意劳逸结合哦!";
356
677
  }
357
- if (!attrs) {
678
+ const statusCheck = checkInstructionStatus(character, "训练");
679
+ if (!statusCheck.canAct) {
680
+ return statusCheck.message;
681
+ }
682
+ if (args.length === 0) {
358
683
  return "格式错误!正确格式:训练 属性1 属性2 属性3(可重复,若只指定一项,则其余两项随机提升)或 训练 随机";
359
684
  }
360
- const attrList = attrs.trim().split(/\s+/);
361
- if (attrList.length === 1 && attrList[0] === "随机") {
362
- const validAttrs2 = ["体质", "攻击", "韧性", "暴击", "敏捷"];
685
+ if (args.length === 1 && args[0] === "随机") {
686
+ const validAttrs2 = ["体质", "攻击", "防御", "暴击", "敏捷"];
363
687
  const attrMap2 = {
364
688
  "体质": "constitution",
365
689
  "攻击": "attack",
366
- "韧性": "toughness",
690
+ "防御": "toughness",
367
691
  "暴击": "crit",
368
692
  "敏捷": "agility"
369
693
  };
370
694
  const updates2 = {};
371
695
  const attributeCounts2 = {};
696
+ for (const key of Object.keys(attrMap2)) {
697
+ const field = attrMap2[key];
698
+ updates2[field] = character[field] || 0;
699
+ }
372
700
  for (let i = 0; i < 3; i++) {
373
701
  const randomAttr = validAttrs2[Math.floor(Math.random() * validAttrs2.length)];
374
702
  const field = attrMap2[randomAttr];
375
- updates2[field] = (updates2[field] || character[field] || 0) + 1;
703
+ updates2[field] = updates2[field] + 1;
376
704
  attributeCounts2[randomAttr] = (attributeCounts2[randomAttr] || 0) + 1;
377
705
  }
378
706
  const changes2 = [];
379
707
  for (const [attr, count] of Object.entries(attributeCounts2)) {
380
708
  changes2.push(`${attr}+${count}`);
381
709
  }
710
+ if (attributeCounts2["体质"]) {
711
+ const constitutionIncrease = attributeCounts2["体质"];
712
+ let newHp = character.currentHp + constitutionIncrease * 5;
713
+ const currentAttrs = calculateCharacterAttributes(character);
714
+ const newMaxHp = currentAttrs.maxHp + constitutionIncrease * 5;
715
+ if (newHp > newMaxHp) newHp = newMaxHp;
716
+ if (newHp < 0) newHp = 0;
717
+ updates2.currentHp = newHp;
718
+ }
382
719
  updates2.lastTrainDate = today;
383
720
  updates2.updatedAt = /* @__PURE__ */ new Date();
384
721
  await ctx.database.set("maple_warriors", { userId }, updates2);
385
722
  return `训练成功!你好像领悟了什么……
386
723
  ${changes2.join(" ")}`;
387
724
  }
388
- if (attrList.length > 3) {
725
+ if (args.length > 3) {
389
726
  return "只能指定3个属性(可以重复),不要太贪心哦!";
390
727
  }
391
- const validAttrs = ["体质", "攻击", "韧性", "暴击", "敏捷"];
728
+ const validAttrs = ["体质", "攻击", "防御", "暴击", "敏捷"];
392
729
  const attrMap = {
393
730
  "体质": "constitution",
394
731
  "攻击": "attack",
395
- "韧性": "toughness",
732
+ "防御": "toughness",
396
733
  "暴击": "crit",
397
734
  "敏捷": "agility"
398
735
  };
399
- for (const attr of attrList) {
736
+ for (const attr of args) {
400
737
  if (!validAttrs.includes(attr)) {
401
738
  return `属性名错误!有效的属性有:${validAttrs.join("、")}`;
402
739
  }
403
740
  }
404
741
  const updates = {};
405
742
  const attributeCounts = {};
406
- for (const attr of attrList) {
743
+ for (const key of Object.keys(attrMap)) {
744
+ const field = attrMap[key];
745
+ updates[field] = character[field] || 0;
746
+ }
747
+ for (const attr of args) {
407
748
  const field = attrMap[attr];
408
- updates[field] = (updates[field] || character[field] || 0) + 1;
749
+ updates[field] = updates[field] + 1;
409
750
  attributeCounts[attr] = (attributeCounts[attr] || 0) + 1;
410
751
  }
411
- for (let i = 0; i < 3 - attrList.length; i++) {
752
+ for (let i = 0; i < 3 - args.length; i++) {
412
753
  const randomAttr = validAttrs[Math.floor(Math.random() * validAttrs.length)];
413
754
  const field = attrMap[randomAttr];
414
- updates[field] = (updates[field] || character[field] || 0) + 1;
755
+ updates[field] = updates[field] + 1;
415
756
  attributeCounts[randomAttr] = (attributeCounts[randomAttr] || 0) + 1;
416
757
  }
758
+ if (attributeCounts["体质"]) {
759
+ const constitutionIncrease = attributeCounts["体质"];
760
+ let newHp = character.currentHp + constitutionIncrease * 5;
761
+ const currentAttrs = calculateCharacterAttributes(character);
762
+ const newMaxHp = currentAttrs.maxHp + constitutionIncrease * 5;
763
+ if (newHp > newMaxHp) newHp = newMaxHp;
764
+ if (newHp < 0) newHp = 0;
765
+ updates.currentHp = newHp;
766
+ }
417
767
  const changes = [];
418
768
  for (const [attr, count] of Object.entries(attributeCounts)) {
419
769
  changes.push(`${attr}+${count}`);
@@ -431,14 +781,10 @@ ${changes.join(" ")}`;
431
781
  return "请等待当前战斗结束!";
432
782
  }
433
783
  let character = await getOrCreateCharacter(userId);
434
- const statusCheck = checkCharacterStatus(character);
784
+ const statusCheck = checkInstructionStatus(character, "捕猎");
435
785
  if (!statusCheck.canAct) {
436
786
  return statusCheck.message;
437
787
  }
438
- const today = getTodayDateString();
439
- if (character.lastHuntDate && isSameDay(character.lastHuntDate, today)) {
440
- return "今天已经捕猎过了,要注意劳逸结合哦!";
441
- }
442
788
  const preyList = parsePreyData(config.preyData);
443
789
  if (preyList.length === 0) {
444
790
  return "未找到猎物数据,请在控制台配置猎物数据。";
@@ -450,7 +796,12 @@ ${changes.join(" ")}`;
450
796
  const crit = parseAttributeRange(selectedPrey.crit);
451
797
  const agility = parseAttributeRange(selectedPrey.agility);
452
798
  const preyHp = constitution * 5;
453
- const chargeTime = parseFloat((1 + 280 / (agility + 70)).toFixed(2));
799
+ const preyRates = calculateRates({
800
+ toughness,
801
+ crit,
802
+ agility
803
+ });
804
+ const player = getBattleCharacter(character);
454
805
  const prey = {
455
806
  name: selectedPrey.name,
456
807
  constitution,
@@ -460,38 +811,21 @@ ${changes.join(" ")}`;
460
811
  agility,
461
812
  hp: preyHp,
462
813
  maxHp: preyHp,
463
- chargeTime,
464
- nextAttackTime: chargeTime
814
+ chargeTime: parseFloat(preyRates.chargeTime),
815
+ nextAttackTime: 0
465
816
  };
466
- if (!battleManager.startBattle(channelId, character.currentHp, selectedPrey.name)) {
817
+ if (!battleManager.startBattle(channelId, player.hp, selectedPrey.name)) {
467
818
  return "战斗开始失败,请重试!";
468
819
  }
469
- const preyRates = calculateRates({
470
- toughness: prey.toughness,
471
- crit: prey.crit,
472
- agility: prey.agility
473
- });
474
820
  await session.send(`发现猎物: ${prey.name}
475
821
  体质: ${prey.constitution} (HP: ${prey.hp})
476
822
  攻击: ${prey.attack}
477
- 韧性: ${prey.toughness} (抗性: ${(parseFloat(preyRates.resistance) * 100).toFixed(2)}%)
823
+ 防御: ${prey.toughness} (韧性: ${(parseFloat(preyRates.resistance) * 100).toFixed(2)}%)
478
824
  暴击: ${prey.crit} (暴击率: ${(parseFloat(preyRates.critRate) * 100).toFixed(2)}%)
479
825
  敏捷: ${prey.agility} (蓄力: ${preyRates.chargeTime}s 闪避率: ${(parseFloat(preyRates.dodgeRate) * 100).toFixed(2)}%)`);
480
826
  await new Promise((resolve) => setTimeout(resolve, 2e3));
481
827
  const abortController = battleManager.getAbortController(channelId);
482
828
  try {
483
- const player = {
484
- name: character.name,
485
- constitution: character.constitution,
486
- attack: character.attack,
487
- toughness: character.toughness,
488
- crit: character.crit,
489
- agility: character.agility,
490
- hp: character.currentHp,
491
- maxHp: character.maxHp,
492
- chargeTime: parseFloat((1 + 280 / (character.agility + 70)).toFixed(2)),
493
- nextAttackTime: 0
494
- };
495
829
  await executeBattle(
496
830
  session,
497
831
  player,
@@ -501,9 +835,9 @@ ${changes.join(" ")}`;
501
835
  // 不是训练模式
502
836
  abortController
503
837
  );
504
- const today2 = getTodayDateString();
838
+ const today = getTodayDateString();
505
839
  const updates = {
506
- lastHuntDate: today2,
840
+ lastHuntDate: today,
507
841
  updatedAt: /* @__PURE__ */ new Date()
508
842
  };
509
843
  if (player.hp <= 0) {
@@ -517,14 +851,15 @@ ${changes.join(" ")}`;
517
851
  const items = parseItemsString(character.items);
518
852
  items[selectedPrey.name] = (items[selectedPrey.name] || 0) + 1;
519
853
  updates.items = serializeItems(items);
854
+ await session.send(`捕猎成功!获得物品 ${selectedPrey.name}*1`);
520
855
  }
521
856
  }
522
857
  await ctx.database.set("maple_warriors", { userId }, updates);
523
858
  } catch (error) {
524
859
  if (error.name === "AbortError") {
525
- const today2 = getTodayDateString();
860
+ const today = getTodayDateString();
526
861
  await ctx.database.set("maple_warriors", { userId }, {
527
- lastHuntDate: today2,
862
+ lastHuntDate: today,
528
863
  updatedAt: /* @__PURE__ */ new Date()
529
864
  });
530
865
  } else {
@@ -534,31 +869,23 @@ ${changes.join(" ")}`;
534
869
  battleManager.endBattle(channelId);
535
870
  }
536
871
  });
537
- ctx.command("猫武士/使用物品 <itemInput>", "使用物品栏中的物品").example("使用物品 老鼠").example("使用物品 老鼠 2").example("使用物品 老鼠*2").action(async ({ session }, itemInput) => {
538
- if (!itemInput) {
539
- return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
872
+ ctx.command("猫武士/使用物品 <itemName> [count]", "使用物品栏中的物品").example("使用物品 老鼠").example("使用物品 老鼠 2").action(async ({ session }, itemName, countStr) => {
873
+ if (!itemName) {
874
+ return "格式错误!正确格式:使用物品 物品名 [数量]";
540
875
  }
541
876
  const userId = session.userId;
542
877
  let character = await getOrCreateCharacter(userId);
543
- const statusCheck = checkCharacterStatus(character);
878
+ const statusCheck = checkInstructionStatus(character, "使用物品");
544
879
  if (!statusCheck.canAct) {
545
880
  return statusCheck.message;
546
881
  }
547
- let itemName = "";
548
882
  let count = 1;
549
- if (itemInput.includes("*")) {
550
- const parts = itemInput.split("*");
551
- itemName = parts[0].trim();
552
- count = parseInt(parts[1]) || 1;
553
- } else {
554
- const parts = itemInput.trim().split(/\s+/);
555
- itemName = parts[0];
556
- if (parts[1]) {
557
- count = parseInt(parts[1]) || 1;
883
+ if (countStr) {
884
+ const parsedCount = parseInt(countStr);
885
+ if (isNaN(parsedCount) || parsedCount <= 0) {
886
+ return "数量必须是正整数";
558
887
  }
559
- }
560
- if (!itemName) {
561
- return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
888
+ count = parsedCount;
562
889
  }
563
890
  const items = parseItemsString(character.items);
564
891
  if (!items[itemName] || items[itemName] < count) {
@@ -575,46 +902,85 @@ ${changes.join(" ")}`;
575
902
  updatedAt: /* @__PURE__ */ new Date()
576
903
  };
577
904
  const changes = [];
905
+ const currentAttrs = calculateCharacterAttributes(character);
578
906
  if (item) {
579
- if (item.hpBonus) {
907
+ if (item.hpBonus && item.hpBonus !== 0) {
580
908
  let newHp = character.currentHp + item.hpBonus * count;
581
- if (newHp > character.maxHp) newHp = character.maxHp;
909
+ const maxHp = currentAttrs.maxHp;
910
+ if (newHp > maxHp) newHp = maxHp;
582
911
  if (newHp < 0) newHp = 0;
583
912
  updates.currentHp = newHp;
584
913
  changes.push(`HP+${item.hpBonus * count}`);
585
914
  }
586
- if (item.constitutionBonus) {
587
- updates.constitution = character.constitution + item.constitutionBonus * count;
915
+ if (item.constitutionBonus && item.constitutionBonus !== 0) {
916
+ const oldConstitution = character.constitution;
917
+ updates.constitution = oldConstitution + item.constitutionBonus * count;
918
+ const constitutionChange = item.constitutionBonus * count;
919
+ let newHp = character.currentHp + constitutionChange * 5;
920
+ const newMaxHp = updates.constitution * 5;
921
+ if (newHp > newMaxHp) newHp = newMaxHp;
922
+ if (newHp < 0) newHp = 0;
923
+ updates.currentHp = newHp;
588
924
  changes.push(`体质+${item.constitutionBonus * count}`);
589
925
  }
590
- if (item.attackBonus) {
926
+ if (item.attackBonus && item.attackBonus !== 0) {
591
927
  updates.attack = character.attack + item.attackBonus * count;
592
928
  changes.push(`攻击+${item.attackBonus * count}`);
593
929
  }
594
- if (item.toughnessBonus) {
930
+ if (item.toughnessBonus && item.toughnessBonus !== 0) {
595
931
  updates.toughness = character.toughness + item.toughnessBonus * count;
596
- changes.push(`韧性+${item.toughnessBonus * count}`);
932
+ changes.push(`防御+${item.toughnessBonus * count}`);
597
933
  }
598
- if (item.critBonus) {
934
+ if (item.critBonus && item.critBonus !== 0) {
599
935
  updates.crit = character.crit + item.critBonus * count;
600
936
  changes.push(`暴击+${item.critBonus * count}`);
601
937
  }
602
- if (item.agilityBonus) {
938
+ if (item.agilityBonus && item.agilityBonus !== 0) {
603
939
  updates.agility = character.agility + item.agilityBonus * count;
604
940
  changes.push(`敏捷+${item.agilityBonus * count}`);
605
941
  }
606
- if (updates.constitution) {
607
- updates.maxHp = updates.constitution * 5;
942
+ const buffs = parseBuffs(character.buffs);
943
+ const now = /* @__PURE__ */ new Date();
944
+ if (item.buff && item.type) {
945
+ const itemBuff = parseItemBuff(item.buff);
946
+ if (itemBuff && itemBuff.duration && itemBuff.effects) {
947
+ const endTime = new Date(now.getTime() + itemBuff.duration * 1e3);
948
+ const newBuff = {
949
+ name: itemName,
950
+ type: item.type,
951
+ endTime: endTime.toISOString(),
952
+ effects: itemBuff.effects
953
+ };
954
+ const filteredBuffs = buffs.filter((b) => b.type !== item.type);
955
+ filteredBuffs.push(newBuff);
956
+ updates.buffs = serializeBuffs(filteredBuffs);
957
+ changes.push(`获得${itemName}的buff效果`);
958
+ }
608
959
  }
609
960
  }
610
961
  await ctx.database.set("maple_warriors", { userId }, updates);
611
962
  const changesText = changes.length > 0 ? "\n" + changes.join(" ") : "";
612
- return `使用成功!消耗了${itemName}*${count}${changesText}`;
963
+ let buffInfo = "";
964
+ if (item && item.buff) {
965
+ try {
966
+ const buff = JSON.parse(item.buff);
967
+ if (buff.effects && buff.effects.length > 0) {
968
+ const buffEffects = buff.effects.map((effect) => {
969
+ return `${effect.attribute}+${effect.percentage}%(最大${effect.max})`;
970
+ }).join(" ");
971
+ const duration = formatTimeRemaining(buff.duration);
972
+ buffInfo = `
973
+ buff: ${buffEffects} 持续${duration}`;
974
+ }
975
+ } catch (e) {
976
+ }
977
+ }
978
+ return `使用成功!消耗了${itemName}*${count}${changesText}${buffInfo}`;
613
979
  });
614
- ctx.command("猫武士/睡眠", "让角色进入睡眠状态").example("睡眠").action(async ({ session }) => {
980
+ ctx.command("猫武士/睡眠", "进入睡眠状态(时间结束后回满HP)").example("睡眠").action(async ({ session }) => {
615
981
  const userId = session.userId;
616
982
  let character = await getOrCreateCharacter(userId);
617
- const statusCheck = checkCharacterStatus(character);
983
+ const statusCheck = checkInstructionStatus(character, "睡眠");
618
984
  if (!statusCheck.canAct) {
619
985
  return statusCheck.message;
620
986
  }
@@ -627,11 +993,15 @@ ${changes.join(" ")}`;
627
993
  statusEndTime: endTime,
628
994
  updatedAt: /* @__PURE__ */ new Date()
629
995
  });
630
- return `进入睡眠状态,剩余4h0m0s`;
996
+ return `进入睡眠状态,剩余4h`;
631
997
  });
632
- ctx.command("猫武士/治疗", "治疗受伤的角色").example("治疗").action(async ({ session }) => {
998
+ ctx.command("猫武士/治疗", "治疗受伤的角色(时间结束后回满HP)").example("治疗").action(async ({ session }) => {
633
999
  const userId = session.userId;
634
1000
  let character = await getOrCreateCharacter(userId);
1001
+ const statusCheck = checkInstructionStatus(character, "治疗");
1002
+ if (!statusCheck.canAct) {
1003
+ return statusCheck.message;
1004
+ }
635
1005
  if (character.currentHp > 0 && character.status !== "受伤") {
636
1006
  return "你的状态良好,无需治疗。";
637
1007
  }
@@ -644,7 +1014,7 @@ ${changes.join(" ")}`;
644
1014
  statusEndTime: endTime,
645
1015
  updatedAt: /* @__PURE__ */ new Date()
646
1016
  });
647
- return `进入治疗状态,剩余4h0m0s`;
1017
+ return `进入治疗状态,剩余4h`;
648
1018
  });
649
1019
  ctx.command("猫武士/结束 <action>", "结束当前状态").example("结束 战斗").example("结束 睡眠").example("结束 治疗").action(async ({ session }, action) => {
650
1020
  if (!action) {
@@ -663,6 +1033,7 @@ ${changes.join(" ")}`;
663
1033
  });
664
1034
  }
665
1035
  battleManager.endBattle(channelId);
1036
+ return;
666
1037
  } else {
667
1038
  return "当前没有正在进行的战斗。";
668
1039
  }
@@ -676,7 +1047,7 @@ ${changes.join(" ")}`;
676
1047
  statusEndTime: null,
677
1048
  updatedAt: /* @__PURE__ */ new Date()
678
1049
  });
679
- return `结束了当前的${action}`;
1050
+ return `${action}被强制结束!`;
680
1051
  } else {
681
1052
  return `当前没有处于${action}状态。`;
682
1053
  }
@@ -697,176 +1068,339 @@ ${changes.join(" ")}`;
697
1068
  if (item.hpBonus) bonuses.push(`HP+${item.hpBonus}`);
698
1069
  if (item.constitutionBonus) bonuses.push(`体质+${item.constitutionBonus}`);
699
1070
  if (item.attackBonus) bonuses.push(`攻击+${item.attackBonus}`);
700
- if (item.toughnessBonus) bonuses.push(`韧性+${item.toughnessBonus}`);
1071
+ if (item.toughnessBonus) bonuses.push(`防御+${item.toughnessBonus}`);
701
1072
  if (item.critBonus) bonuses.push(`暴击+${item.critBonus}`);
702
1073
  if (item.agilityBonus) bonuses.push(`敏捷+${item.agilityBonus}`);
703
- let result = `物品: ${item.name}
1074
+ let result = `物品: ${item.name} [${item.type || "无类型"}] ${item.sellPrice || 0}/${item.shopPrice >= 0 ? item.shopPrice : "-"}
704
1075
  `;
705
1076
  if (bonuses.length > 0) {
706
1077
  result += bonuses.join(" ") + "\n";
707
1078
  }
1079
+ if (item.buff) {
1080
+ try {
1081
+ const buff = JSON.parse(item.buff);
1082
+ if (buff.effects && buff.effects.length > 0) {
1083
+ const buffEffects = buff.effects.map((effect) => {
1084
+ return `${effect.attribute}+${effect.percentage}%(最大${effect.max})`;
1085
+ }).join(" ");
1086
+ const duration = formatTimeRemaining(buff.duration);
1087
+ result += `buff: ${buffEffects} 持续${duration}
1088
+ `;
1089
+ }
1090
+ } catch (e) {
1091
+ }
1092
+ }
708
1093
  if (item.description) {
709
1094
  result += `"${item.description}"`;
710
1095
  }
711
1096
  return result.trim();
712
1097
  });
713
- ctx.command("猫武士/添加物品 <...args>", "添加或修改物品").example("添加物品 老鼠 体质 1 很常见的老鼠").example("添加物品 治疗药水 hp 10 恢复生命值").action(async ({ session }, ...args) => {
714
- if (args.length < 1) {
715
- return "格式错误!正确格式:添加物品 物品名 [属性 增加数 ...] [介绍]";
1098
+ ctx.command("猫武士/添加物品 <itemName>", "添加或修改物品").option("type", "-t <type:string> 物品类型").option("money", "-m <money:number> 出售价格").option("shop", "-s <shop:number> 商店价格").option("desc", "-d <desc:string> 物品介绍").option("characteristics", "-c <chars:string> 属性提升,格式:属性名,数值,属性名,数值,...").option("buff", "-b <buff:string> buff效果,格式:时间,属性名,百分比,上限,属性名,百分比,上限,...").example('添加物品 老鼠 -t 猎物 -m 1 -s 5 -c "HP,1"').example('添加物品 药水 -t 药品 -b "2m,体质,10%,5"').action(async ({ session, options }, itemName) => {
1099
+ if (!itemName) {
1100
+ return "格式错误!正确格式:添加物品 物品名 [选项]";
716
1101
  }
717
- let itemName = args[0];
718
- let description = "";
719
- const updates = {
720
- hpBonus: 0,
721
- constitutionBonus: 0,
722
- attackBonus: 0,
723
- toughnessBonus: 0,
724
- critBonus: 0,
725
- agilityBonus: 0
726
- };
727
- let i = 1;
728
- const validAttrs = ["HP", "hp", "体质", "攻击", "韧性", "暴击", "敏捷"];
729
- const attrMap = {
730
- "HP": "hpBonus",
731
- "hp": "hpBonus",
732
- "体质": "constitutionBonus",
733
- "攻击": "attackBonus",
734
- "韧性": "toughnessBonus",
735
- "暴击": "critBonus",
736
- "敏捷": "agilityBonus"
737
- };
738
- while (i < args.length) {
739
- const arg = args[i];
740
- const normalizedAttr = arg.toLowerCase() === "hp" ? "HP" : arg;
741
- if (!validAttrs.includes(normalizedAttr)) {
742
- description = args.slice(i).join(" ");
743
- break;
744
- }
745
- if (i + 1 >= args.length) {
746
- return `格式错误!属性"${arg}"缺少数值`;
1102
+ const existingItems = await ctx.database.get("maple_warriors_items", { name: itemName });
1103
+ const existingItem = existingItems.length > 0 ? existingItems[0] : null;
1104
+ if (existingItem) {
1105
+ const updates = {};
1106
+ if (options.type !== void 0) updates.type = options.type;
1107
+ if (options.money !== void 0) updates.sellPrice = options.money;
1108
+ if (options.shop !== void 0) updates.shopPrice = options.shop;
1109
+ if (options.desc !== void 0) updates.description = options.desc;
1110
+ const numericFields = ["hpBonus", "constitutionBonus", "attackBonus", "toughnessBonus", "critBonus", "agilityBonus"];
1111
+ const attrMap = {
1112
+ "HP": "hpBonus",
1113
+ "hp": "hpBonus",
1114
+ "体质": "constitutionBonus",
1115
+ "攻击": "attackBonus",
1116
+ "防御": "toughnessBonus",
1117
+ "暴击": "critBonus",
1118
+ "敏捷": "agilityBonus"
1119
+ };
1120
+ if (options.characteristics !== void 0) {
1121
+ numericFields.forEach((field) => {
1122
+ updates[field] = 0;
1123
+ });
1124
+ if (options.characteristics.trim() !== "") {
1125
+ const params = options.characteristics.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1126
+ if (params.length % 2 !== 0) {
1127
+ return "属性提升格式错误!格式应为:属性名,数值,属性名,数值,...";
1128
+ }
1129
+ for (let i = 0; i < params.length; i += 2) {
1130
+ const attrName = params[i];
1131
+ const attrValue = params[i + 1];
1132
+ const normalizedAttr = attrName.toLowerCase() === "hp" ? "HP" : attrName;
1133
+ const fieldName = attrMap[normalizedAttr];
1134
+ if (!fieldName) {
1135
+ return `属性名错误!有效的属性有:${Object.keys(attrMap).join("、")}`;
1136
+ }
1137
+ const value = parseInt(attrValue);
1138
+ if (isNaN(value)) {
1139
+ return `属性"${attrName}"的数值必须是数字`;
1140
+ }
1141
+ updates[fieldName] = value;
1142
+ }
1143
+ }
747
1144
  }
748
- const nextArg = args[i + 1];
749
- const value = parseInt(nextArg);
750
- if (isNaN(value)) {
751
- const nextNormalized = nextArg.toLowerCase() === "hp" ? "HP" : nextArg;
752
- if (validAttrs.includes(nextNormalized)) {
753
- return `格式错误!属性"${arg}"缺少数值`;
1145
+ if (options.buff !== void 0) {
1146
+ if (options.buff.trim() === "") {
1147
+ updates.buff = null;
754
1148
  } else {
755
- description = args.slice(i + 1).join(" ");
756
- break;
1149
+ const params = options.buff.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1150
+ if (params.length < 4 || (params.length - 1) % 3 !== 0) {
1151
+ return "buff效果格式错误!格式应为:时间,属性名,百分比,上限,属性名,百分比,上限,...";
1152
+ }
1153
+ const durationStr = params[0];
1154
+ const durationSeconds = parseTimeString(durationStr);
1155
+ if (durationSeconds <= 0) {
1156
+ return "有效时间格式错误!请使用如2m、1h30m、1h等格式";
1157
+ }
1158
+ const effects = [];
1159
+ const validAttrs = ["体质", "攻击", "防御", "暴击", "敏捷"];
1160
+ for (let i = 1; i < params.length; i += 3) {
1161
+ const attribute = params[i];
1162
+ if (!validAttrs.includes(attribute)) {
1163
+ return `buff属性名错误!有效的属性有:${validAttrs.join("、")}`;
1164
+ }
1165
+ const percentageStr = params[i + 1];
1166
+ if (!percentageStr.endsWith("%")) {
1167
+ return `buff属性"${attribute}"的百分比必须以%结尾`;
1168
+ }
1169
+ const percentage = parseInt(percentageStr.slice(0, -1));
1170
+ if (isNaN(percentage) || percentage <= 0) {
1171
+ return `buff属性"${attribute}"的百分比必须是正整数`;
1172
+ }
1173
+ const max = parseInt(params[i + 2]);
1174
+ if (isNaN(max) || max <= 0) {
1175
+ return `buff属性"${attribute}"的最大上限必须是正整数`;
1176
+ }
1177
+ effects.push({
1178
+ attribute,
1179
+ percentage,
1180
+ max
1181
+ });
1182
+ }
1183
+ if (effects.length > 0) {
1184
+ const buff = {
1185
+ duration: durationSeconds,
1186
+ effects
1187
+ };
1188
+ updates.buff = JSON.stringify(buff);
1189
+ } else {
1190
+ updates.buff = null;
1191
+ }
757
1192
  }
758
1193
  }
759
- const fieldName = attrMap[normalizedAttr];
760
- if (fieldName) {
761
- updates[fieldName] = value;
762
- } else {
763
- return `属性名错误!有效的属性有:${["HP", "体质", "攻击", "韧性", "暴击", "敏捷"].join("、")}`;
764
- }
765
- i += 2;
766
- }
767
- const existingItems = await ctx.database.get("maple_warriors_items", { name: itemName });
768
- if (existingItems.length > 0) {
769
- await ctx.database.set("maple_warriors_items", { name: itemName }, {
770
- ...updates,
771
- description,
772
- createdAt: /* @__PURE__ */ new Date()
773
- });
774
- return `修改${itemName}成功!`;
1194
+ updates.createdAt = /* @__PURE__ */ new Date();
1195
+ await ctx.database.set("maple_warriors_items", { name: itemName }, updates);
1196
+ return `修改物品"${itemName}"成功!`;
775
1197
  } else {
776
- await ctx.database.create("maple_warriors_items", {
1198
+ const newItem = {
777
1199
  name: itemName,
778
- ...updates,
779
- description,
1200
+ type: options.type || "其他",
1201
+ sellPrice: options.money !== void 0 ? options.money : 0,
1202
+ shopPrice: options.shop !== void 0 ? options.shop : -1,
1203
+ description: options.desc || "",
780
1204
  createdAt: /* @__PURE__ */ new Date()
1205
+ };
1206
+ const numericFields = ["hpBonus", "constitutionBonus", "attackBonus", "toughnessBonus", "critBonus", "agilityBonus"];
1207
+ numericFields.forEach((field) => {
1208
+ newItem[field] = 0;
781
1209
  });
782
- return `添加${itemName}成功!`;
1210
+ const attrMap = {
1211
+ "HP": "hpBonus",
1212
+ "hp": "hpBonus",
1213
+ "体质": "constitutionBonus",
1214
+ "攻击": "attackBonus",
1215
+ "防御": "toughnessBonus",
1216
+ "暴击": "critBonus",
1217
+ "敏捷": "agilityBonus"
1218
+ };
1219
+ if (options.characteristics !== void 0 && options.characteristics.trim() !== "") {
1220
+ const params = options.characteristics.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1221
+ if (params.length % 2 !== 0) {
1222
+ return "属性提升格式错误!格式应为:属性名,数值,属性名,数值,...";
1223
+ }
1224
+ for (let i = 0; i < params.length; i += 2) {
1225
+ const attrName = params[i];
1226
+ const attrValue = params[i + 1];
1227
+ const normalizedAttr = attrName.toLowerCase() === "hp" ? "HP" : attrName;
1228
+ const fieldName = attrMap[normalizedAttr];
1229
+ if (!fieldName) {
1230
+ return `属性名错误!有效的属性有:${Object.keys(attrMap).join("、")}`;
1231
+ }
1232
+ const value = parseInt(attrValue);
1233
+ if (isNaN(value)) {
1234
+ return `属性"${attrName}"的数值必须是数字`;
1235
+ }
1236
+ newItem[fieldName] = value;
1237
+ }
1238
+ }
1239
+ if (options.buff !== void 0 && options.buff.trim() !== "") {
1240
+ const params = options.buff.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1241
+ if (params.length < 4 || (params.length - 1) % 3 !== 0) {
1242
+ return "buff效果格式错误!格式应为:时间,属性名,百分比,上限,属性名,百分比,上限,...";
1243
+ }
1244
+ const durationStr = params[0];
1245
+ const durationSeconds = parseTimeString(durationStr);
1246
+ if (durationSeconds <= 0) {
1247
+ return "有效时间格式错误!请使用如2m、1h30m、1h等格式";
1248
+ }
1249
+ const effects = [];
1250
+ const validAttrs = ["体质", "攻击", "防御", "暴击", "敏捷"];
1251
+ for (let i = 1; i < params.length; i += 3) {
1252
+ const attribute = params[i];
1253
+ if (!validAttrs.includes(attribute)) {
1254
+ return `buff属性名错误!有效的属性有:${validAttrs.join("、")}`;
1255
+ }
1256
+ const percentageStr = params[i + 1];
1257
+ if (!percentageStr.endsWith("%")) {
1258
+ return `buff属性"${attribute}"的百分比必须以%结尾`;
1259
+ }
1260
+ const percentage = parseInt(percentageStr.slice(0, -1));
1261
+ if (isNaN(percentage) || percentage <= 0) {
1262
+ return `buff属性"${attribute}"的百分比必须是正整数`;
1263
+ }
1264
+ const max = parseInt(params[i + 2]);
1265
+ if (isNaN(max) || max <= 0) {
1266
+ return `buff属性"${attribute}"的最大上限必须是正整数`;
1267
+ }
1268
+ effects.push({
1269
+ attribute,
1270
+ percentage,
1271
+ max
1272
+ });
1273
+ }
1274
+ if (effects.length > 0) {
1275
+ const buff = {
1276
+ duration: durationSeconds,
1277
+ effects
1278
+ };
1279
+ newItem.buff = JSON.stringify(buff);
1280
+ }
1281
+ } else {
1282
+ newItem.buff = null;
1283
+ }
1284
+ await ctx.database.create("maple_warriors_items", newItem);
1285
+ return `添加物品"${itemName}"成功!`;
783
1286
  }
784
1287
  });
785
- ctx.command("猫武士/物品列表 [page]", "查看物品列表").example("物品列表").example("物品列表 2").action(async ({ session }, page = "1") => {
786
- const pageNum = parseInt(page) || 1;
1288
+ ctx.command("猫武士/物品列表 [typeOrPage] [page]", "查看物品列表").example("物品列表").example("物品列表 2").example("物品列表 药品").example("物品列表 药品 2").action(async ({ session }, typeOrPage, page) => {
1289
+ let typeFilter = "";
1290
+ let pageNum = 1;
1291
+ if (typeOrPage) {
1292
+ const parsedPage = parseInt(typeOrPage);
1293
+ if (!isNaN(parsedPage)) {
1294
+ pageNum = parsedPage;
1295
+ } else {
1296
+ typeFilter = typeOrPage;
1297
+ if (page) {
1298
+ const parsedPage2 = parseInt(page);
1299
+ if (!isNaN(parsedPage2)) {
1300
+ pageNum = parsedPage2;
1301
+ }
1302
+ }
1303
+ }
1304
+ }
787
1305
  if (pageNum < 1) {
788
1306
  return "页码必须大于0!";
789
1307
  }
790
1308
  const allItems = await ctx.database.get("maple_warriors_items", {});
791
- allItems.sort((a, b) => a.name.localeCompare(b.name));
1309
+ let filteredItems = allItems;
1310
+ if (typeFilter) {
1311
+ filteredItems = allItems.filter((item) => item.type === typeFilter);
1312
+ if (filteredItems.length === 0) {
1313
+ return `没有找到类型为"${typeFilter}"的物品!`;
1314
+ }
1315
+ }
1316
+ filteredItems.sort((a, b) => a.name.localeCompare(b.name));
792
1317
  const pageSize = 10;
793
- const totalPages = Math.ceil(allItems.length / pageSize);
1318
+ const totalPages = Math.ceil(filteredItems.length / pageSize);
794
1319
  if (pageNum > totalPages && totalPages > 0) {
795
1320
  return `页码超出范围!总页数:${totalPages}`;
796
1321
  }
797
1322
  const startIndex = (pageNum - 1) * pageSize;
798
- const pageItems = allItems.slice(startIndex, startIndex + pageSize);
799
- let output = `物品列表 第 ${pageNum}/${totalPages} 页(共 ${allItems.length} 个物品)
1323
+ const pageItems = filteredItems.slice(startIndex, startIndex + pageSize);
1324
+ let output = `物品列表`;
1325
+ if (typeFilter) {
1326
+ output += ` [类型: ${typeFilter}]`;
1327
+ }
1328
+ output += ` 第 ${pageNum}/${totalPages} 页(共 ${filteredItems.length} 个物品)
800
1329
 
801
1330
  `;
802
1331
  pageItems.forEach((item, index) => {
803
1332
  const displayIndex = startIndex + index + 1;
804
- output += `${displayIndex}. ${item.name}
1333
+ const sellPrice = item.sellPrice || 0;
1334
+ const shopPrice = item.shopPrice >= 0 ? item.shopPrice : "-";
1335
+ output += `${displayIndex}. ${item.name} [${item.type || "无类型"}] ${sellPrice}/${shopPrice}
805
1336
  `;
806
1337
  });
807
1338
  output += "\n──────────\n";
808
1339
  if (pageNum > 1) {
809
- output += `输入"物品列表 ${pageNum - 1}"查看上一页
1340
+ output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum - 1}"查看上一页
810
1341
  `;
811
1342
  }
812
1343
  if (pageNum < totalPages) {
813
- output += `输入"物品列表 ${pageNum + 1}"查看下一页
1344
+ output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum + 1}"查看下一页
814
1345
  `;
815
1346
  }
816
1347
  output += `输入"查询物品 物品名"查看物品详情`;
817
1348
  return output.trim();
818
1349
  });
1350
+ ctx.command("猫武士/set <itemName> [count]", "直接获取指定数量的物品").option("user", "-u <userId:string> 指定用户ID").example("set 老鼠 5").example("set 1级敏捷药水 3").action(async ({ session, options }, itemName, countStr) => {
1351
+ if (!itemName) {
1352
+ return "格式错误!正确格式:set 物品名 [数量]";
1353
+ }
1354
+ const targetUserId = options.user ? options.user : session.userId;
1355
+ const count = countStr ? parseInt(countStr) : 1;
1356
+ if (isNaN(count) || count <= 0) {
1357
+ return "数量必须是正整数";
1358
+ }
1359
+ let character = await getOrCreateCharacter(targetUserId);
1360
+ const items = parseItemsString(character.items);
1361
+ items[itemName] = (items[itemName] || 0) + count;
1362
+ await ctx.database.set("maple_warriors", { userId: targetUserId }, {
1363
+ items: serializeItems(items),
1364
+ updatedAt: /* @__PURE__ */ new Date()
1365
+ });
1366
+ return `成功获得${itemName}*${count}`;
1367
+ });
819
1368
  ctx.command("猫武士/战斗训练 <target:user>", "与指定用户的角色进行训练战斗(不消耗HP)").example("战斗训练 @火星").action(async ({ session }, target) => {
820
1369
  const channelId = session.channelId;
821
1370
  const userId = session.userId;
1371
+ let targetUserId = target;
1372
+ if (target.includes(":")) {
1373
+ const parts = target.split(":");
1374
+ targetUserId = parts[parts.length - 1];
1375
+ }
822
1376
  if (battleManager.isBattling(channelId)) {
823
1377
  return "请等待当前战斗结束!";
824
1378
  }
825
- if (!target) {
1379
+ if (!targetUserId) {
826
1380
  return "请指定训练对手!正确格式:战斗训练 @用户名";
827
1381
  }
828
- if (target === userId) {
829
- return "不能和自己进行战斗训练哦!";
1382
+ if (targetUserId === userId) {
1383
+ return "不能和自己进行战斗训练!";
830
1384
  }
831
1385
  let player1 = await getOrCreateCharacter(userId);
832
- const statusCheck = checkCharacterStatus(player1);
1386
+ const statusCheck = checkInstructionStatus(player1, "战斗训练");
833
1387
  if (!statusCheck.canAct) {
834
1388
  return statusCheck.message;
835
1389
  }
836
- let player2 = await getOrCreateCharacter(target);
837
- const opponentStatusCheck = checkCharacterStatus(player2);
1390
+ let player2Characters = await ctx.database.get("maple_warriors", { userId: targetUserId });
1391
+ if (player2Characters.length === 0) {
1392
+ return "战斗失败!对方尚未生成角色";
1393
+ }
1394
+ let player2 = player2Characters[0];
1395
+ const opponentStatusCheck = checkInstructionStatus(player2, "战斗训练");
838
1396
  if (!opponentStatusCheck.canAct) {
839
- return `对手目前状态不佳,无法进行战斗训练:${opponentStatusCheck.message}`;
1397
+ return `对方${player2.status}中,无法进行战斗训练!`;
840
1398
  }
841
1399
  if (!battleManager.startBattle(channelId, player1.currentHp, void 0)) {
842
1400
  return "战斗开始失败,请重试!";
843
1401
  }
844
- const player1Char = {
845
- name: player1.name,
846
- constitution: player1.constitution,
847
- attack: player1.attack,
848
- toughness: player1.toughness,
849
- crit: player1.crit,
850
- agility: player1.agility,
851
- hp: player1.currentHp,
852
- // 使用当前HP
853
- maxHp: player1.maxHp,
854
- chargeTime: parseFloat((1 + 280 / (player1.agility + 70)).toFixed(2)),
855
- nextAttackTime: 0
856
- };
857
- const player2Char = {
858
- name: player2.name,
859
- constitution: player2.constitution,
860
- attack: player2.attack,
861
- toughness: player2.toughness,
862
- crit: player2.crit,
863
- agility: player2.agility,
864
- hp: player2.currentHp,
865
- // 使用当前HP
866
- maxHp: player2.maxHp,
867
- chargeTime: parseFloat((1 + 280 / (player2.agility + 70)).toFixed(2)),
868
- nextAttackTime: 0
869
- };
1402
+ const player1Char = getBattleCharacter(player1);
1403
+ const player2Char = getBattleCharacter(player2);
870
1404
  const abortController = battleManager.getAbortController(channelId);
871
1405
  try {
872
1406
  await executeBattle(
@@ -886,6 +1420,126 @@ ${changes.join(" ")}`;
886
1420
  battleManager.endBattle(channelId);
887
1421
  }
888
1422
  });
1423
+ ctx.command("猫武士/删除角色", "删除角色").option("user", "-u <userId:string> 指定要删除角色的用户ID").example("删除角色").action(async ({ session, options }) => {
1424
+ const userId = options.user ? options.user : session.userId;
1425
+ const characters = await ctx.database.get("maple_warriors", { userId });
1426
+ if (characters.length === 0) {
1427
+ return "删除失败!找不到用户的角色";
1428
+ }
1429
+ const character = characters[0];
1430
+ await session.send(`确定删除角色【${character.name}】吗?回复"确认"进行删除`);
1431
+ try {
1432
+ const confirm = await session.prompt(3e4);
1433
+ if (confirm === "确认") {
1434
+ await ctx.database.remove("maple_warriors", { userId });
1435
+ return `删除【${character.name}】成功。输入"我的角色"将新建一个角色`;
1436
+ } else {
1437
+ return "删除失败!无法识别文本";
1438
+ }
1439
+ } catch (error) {
1440
+ return "删除失败!回复超时";
1441
+ }
1442
+ });
1443
+ ctx.command("猫武士/出售 <itemName> [count]", "出售物品").example("出售 老鼠").example("出售 老鼠 2").example("出售 老鼠 全部").action(async ({ session }, itemName, countStr) => {
1444
+ if (!itemName) {
1445
+ return "格式错误!正确格式:出售 物品名 [数量|全部]";
1446
+ }
1447
+ const userId = session.userId;
1448
+ let character = await getOrCreateCharacter(userId);
1449
+ const items = parseItemsString(character.items);
1450
+ if (!items[itemName]) {
1451
+ return `物品栏中没有${itemName}!`;
1452
+ }
1453
+ let count = 1;
1454
+ let sellAll = false;
1455
+ if (countStr === "全部") {
1456
+ count = items[itemName];
1457
+ sellAll = true;
1458
+ } else if (countStr) {
1459
+ const parsedCount = parseInt(countStr);
1460
+ if (isNaN(parsedCount) || parsedCount <= 0) {
1461
+ return '数量必须是正整数或"全部"';
1462
+ }
1463
+ count = Math.min(parsedCount, items[itemName]);
1464
+ } else {
1465
+ count = 1;
1466
+ }
1467
+ if (count > items[itemName]) {
1468
+ return `物品栏中没有那么多${itemName}!`;
1469
+ }
1470
+ const itemInfo = await ctx.database.get("maple_warriors_items", { name: itemName });
1471
+ const item = itemInfo.length > 0 ? itemInfo[0] : null;
1472
+ const sellPrice = item?.sellPrice || 0;
1473
+ const totalGold = sellPrice * count;
1474
+ await session.send(`出售${itemName}*${count}将获得${formatGold(totalGold)}金币,回复"确认"进行出售!`);
1475
+ try {
1476
+ const confirm = await session.prompt(3e4);
1477
+ if (confirm !== "确认") {
1478
+ return "回复错误,出售失败!";
1479
+ }
1480
+ items[itemName] -= count;
1481
+ if (items[itemName] <= 0) {
1482
+ delete items[itemName];
1483
+ }
1484
+ const newGold = character.gold + totalGold;
1485
+ await ctx.database.set("maple_warriors", { userId }, {
1486
+ items: serializeItems(items),
1487
+ gold: newGold,
1488
+ updatedAt: /* @__PURE__ */ new Date()
1489
+ });
1490
+ return `出售了${itemName}*${count},获得${formatGold(totalGold)}金币`;
1491
+ } catch (error) {
1492
+ return "回复错误,出售失败!";
1493
+ }
1494
+ });
1495
+ ctx.command("猫武士/金币排行", "查看金币排行榜").example("金币排行").action(async ({ session }) => {
1496
+ const allCharacters = await ctx.database.get("maple_warriors", {});
1497
+ const sortedCharacters = allCharacters.sort((a, b) => b.gold - a.gold);
1498
+ const topCharacters = sortedCharacters.slice(0, 5);
1499
+ let output = "【金币排行】\n";
1500
+ topCharacters.forEach((character, index) => {
1501
+ const rank = index + 1;
1502
+ output += `${rank}. ${character.name} 金币: ${formatGold(character.gold)}
1503
+ `;
1504
+ });
1505
+ return output.trim();
1506
+ });
1507
+ ctx.command("猫武士/购买物品 <itemName> [count]", "从商店购买物品").example("购买物品 老鼠").example("购买物品 老鼠 2").action(async ({ session }, itemName, countStr) => {
1508
+ if (!itemName) {
1509
+ return "格式错误!正确格式:购买物品 物品名 [数量]";
1510
+ }
1511
+ const userId = session.userId;
1512
+ let character = await getOrCreateCharacter(userId);
1513
+ const itemInfo = await ctx.database.get("maple_warriors_items", { name: itemName });
1514
+ if (itemInfo.length === 0) {
1515
+ return `购买失败!商店中没有${itemName}!`;
1516
+ }
1517
+ const item = itemInfo[0];
1518
+ if (item.shopPrice < 0) {
1519
+ return `购买失败!商店中没有${itemName}!`;
1520
+ }
1521
+ let count = 1;
1522
+ if (countStr) {
1523
+ const parsedCount = parseInt(countStr);
1524
+ if (isNaN(parsedCount) || parsedCount <= 0) {
1525
+ return "数量必须是正整数";
1526
+ }
1527
+ count = parsedCount;
1528
+ }
1529
+ const requiredGold = item.shopPrice * count;
1530
+ if (character.gold < requiredGold) {
1531
+ return `购买失败!需要${formatGold(requiredGold)}金币,金币不足!`;
1532
+ }
1533
+ const items = parseItemsString(character.items);
1534
+ items[itemName] = (items[itemName] || 0) + count;
1535
+ const newGold = character.gold - requiredGold;
1536
+ await ctx.database.set("maple_warriors", { userId }, {
1537
+ items: serializeItems(items),
1538
+ gold: newGold,
1539
+ updatedAt: /* @__PURE__ */ new Date()
1540
+ });
1541
+ return `购买成功!消耗${formatGold(requiredGold)}金币,获得了${itemName}*${count}`;
1542
+ });
889
1543
  setInterval(async () => {
890
1544
  try {
891
1545
  const now = /* @__PURE__ */ new Date();
@@ -899,20 +1553,42 @@ ${changes.join(" ")}`;
899
1553
  updatedAt: /* @__PURE__ */ new Date()
900
1554
  };
901
1555
  if (character.status === "睡眠" || character.status === "治疗") {
902
- updates.currentHp = character.maxHp;
1556
+ const attrs = calculateCharacterAttributes(character);
1557
+ updates.currentHp = attrs.maxHp;
903
1558
  }
904
1559
  await ctx.database.set("maple_warriors", { id: character.id }, updates);
905
1560
  }
906
1561
  }
1562
+ for (const character of characters) {
1563
+ const buffs = parseBuffs(character.buffs);
1564
+ if (buffs.length === 0) continue;
1565
+ const activeBuffs = buffs.filter((buff) => {
1566
+ return new Date(buff.endTime) > now;
1567
+ });
1568
+ if (activeBuffs.length !== buffs.length) {
1569
+ await ctx.database.set("maple_warriors", { id: character.id }, {
1570
+ buffs: serializeBuffs(activeBuffs),
1571
+ updatedAt: /* @__PURE__ */ new Date()
1572
+ });
1573
+ }
1574
+ }
907
1575
  } catch (error) {
908
- console.error("状态检查错误:", error);
1576
+ console.error("定时器错误:", error);
909
1577
  }
910
1578
  }, 6e4);
911
1579
  }
912
1580
  __name(apply, "apply");
913
1581
  async function executeBattle(session, player1, player2, battleMessageInterval, isTraining = false, abortController) {
914
- const originalHp1 = player1.hp;
915
- const originalHp2 = player2.hp;
1582
+ if (player1.hp <= 0) {
1583
+ await session.send(`战斗失败!${player1.name}的HP为0!`);
1584
+ return;
1585
+ }
1586
+ if (player2.hp <= 0) {
1587
+ await session.send(`战斗失败!${player2.name}的HP为0!`);
1588
+ return;
1589
+ }
1590
+ const initialHp1 = player1.hp;
1591
+ const initialHp2 = player2.hp;
916
1592
  const rates1 = calculateRates({
917
1593
  toughness: player1.toughness,
918
1594
  crit: player1.crit,
@@ -966,21 +1642,18 @@ async function executeBattle(session, player1, player2, battleMessageInterval, i
966
1642
  }
967
1643
  const baseDamage = attacker.attack * (0.5 + Math.random());
968
1644
  let initialDamage = Math.ceil(baseDamage);
969
- let totalDamage = initialDamage;
970
1645
  const attackerCritRate = attacker === player1 ? critRate1 : critRate2;
971
1646
  let isCritical = false;
972
1647
  if (Math.random() < attackerCritRate) {
973
- totalDamage *= 2;
1648
+ initialDamage *= 2;
974
1649
  isCritical = true;
975
1650
  battleMessage += ` 暴击!`;
976
1651
  }
1652
+ battleMessage += ` 造成了${initialDamage}点伤害。`;
977
1653
  const reduceAmount = defender.toughness * Math.random();
978
- const reducedDamage = Math.min(Math.ceil(reduceAmount), totalDamage);
979
- const finalDamage = totalDamage - reducedDamage;
1654
+ const reducedDamage = Math.min(Math.ceil(reduceAmount), initialDamage);
1655
+ const finalDamage = initialDamage - reducedDamage;
980
1656
  if (finalDamage <= 0) {
981
- if (isCritical) {
982
- battleMessage += ` 造成了${totalDamage}点伤害。`;
983
- }
984
1657
  battleMessage += ` 被${defender.name}抵挡住了!`;
985
1658
  await session.send(battleMessage);
986
1659
  currentTime = attackTime;
@@ -988,11 +1661,6 @@ async function executeBattle(session, player1, player2, battleMessageInterval, i
988
1661
  }
989
1662
  defender.hp -= finalDamage;
990
1663
  if (defender.hp < 0) defender.hp = 0;
991
- if (isCritical) {
992
- battleMessage += ` 造成了${totalDamage}点伤害。`;
993
- } else {
994
- battleMessage += ` 造成了${initialDamage}点伤害。`;
995
- }
996
1664
  battleMessage += ` ${defender.name}抵挡了${reducedDamage}点伤害,实际造成${finalDamage}点伤害。${defender.name} HP: ${defender.hp}`;
997
1665
  await session.send(battleMessage);
998
1666
  if (defender.hp <= 0) {
@@ -1002,10 +1670,12 @@ async function executeBattle(session, player1, player2, battleMessageInterval, i
1002
1670
  currentTime = attackTime;
1003
1671
  }
1004
1672
  if (currentTime > maxBattleTime && player1.hp > 0 && player2.hp > 0) {
1005
- if (player1.hp > player2.hp) {
1006
- await session.send(`战斗超时!${player1.name} 剩余HP更高,获得胜利!`);
1673
+ const player1Loss = initialHp1 - player1.hp;
1674
+ const player2Loss = initialHp2 - player2.hp;
1675
+ if (player1Loss < player2Loss) {
1676
+ await session.send(`战斗超时!${player1.name} 损失HP较少,获得胜利!`);
1007
1677
  } else if (player2.hp > player1.hp) {
1008
- await session.send(`战斗超时!${player2.name} 剩余HP更高,获得胜利!`);
1678
+ await session.send(`战斗超时!${player2.name} 损失HP较少,获得胜利!`);
1009
1679
  } else {
1010
1680
  await session.send(`战斗超时!双方平局!`);
1011
1681
  }