koishi-plugin-maple-warriors 0.0.3 → 0.1.1

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 (2) hide show
  1. package/lib/index.js +869 -234
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -27,16 +27,76 @@ __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
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);
@@ -45,47 +105,153 @@ function calculateRates(character) {
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,23 +571,27 @@ ${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
596
  ctx.command("猫武士/修改名字 <name>", "修改角色名字").example("修改名字 火星").action(async ({ session }, name2) => {
304
597
  if (!name2 || name2.trim().length === 0) {
@@ -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,
@@ -350,7 +644,7 @@ ${itemsText || "空空如也"}`;
350
644
  if (character.lastTrainDate && isSameDay(character.lastTrainDate, today)) {
351
645
  return "今天已经训练过了,要注意劳逸结合哦!";
352
646
  }
353
- const statusCheck = checkCharacterStatus(character);
647
+ const statusCheck = checkInstructionStatus(character, "训练");
354
648
  if (!statusCheck.canAct) {
355
649
  return statusCheck.message;
356
650
  }
@@ -368,16 +662,29 @@ ${itemsText || "空空如也"}`;
368
662
  };
369
663
  const updates2 = {};
370
664
  const attributeCounts2 = {};
665
+ for (const key of Object.keys(attrMap2)) {
666
+ const field = attrMap2[key];
667
+ updates2[field] = character[field] || 0;
668
+ }
371
669
  for (let i = 0; i < 3; i++) {
372
670
  const randomAttr = validAttrs2[Math.floor(Math.random() * validAttrs2.length)];
373
671
  const field = attrMap2[randomAttr];
374
- updates2[field] = (updates2[field] || character[field] || 0) + 1;
672
+ updates2[field] = updates2[field] + 1;
375
673
  attributeCounts2[randomAttr] = (attributeCounts2[randomAttr] || 0) + 1;
376
674
  }
377
675
  const changes2 = [];
378
676
  for (const [attr, count] of Object.entries(attributeCounts2)) {
379
677
  changes2.push(`${attr}+${count}`);
380
678
  }
679
+ if (attributeCounts2["体质"]) {
680
+ const constitutionIncrease = attributeCounts2["体质"];
681
+ let newHp = character.currentHp + constitutionIncrease * 5;
682
+ const currentAttrs = calculateCharacterAttributes(character);
683
+ const newMaxHp = currentAttrs.maxHp + constitutionIncrease * 5;
684
+ if (newHp > newMaxHp) newHp = newMaxHp;
685
+ if (newHp < 0) newHp = 0;
686
+ updates2.currentHp = newHp;
687
+ }
381
688
  updates2.lastTrainDate = today;
382
689
  updates2.updatedAt = /* @__PURE__ */ new Date();
383
690
  await ctx.database.set("maple_warriors", { userId }, updates2);
@@ -402,17 +709,30 @@ ${changes2.join(" ")}`;
402
709
  }
403
710
  const updates = {};
404
711
  const attributeCounts = {};
712
+ for (const key of Object.keys(attrMap)) {
713
+ const field = attrMap[key];
714
+ updates[field] = character[field] || 0;
715
+ }
405
716
  for (const attr of args) {
406
717
  const field = attrMap[attr];
407
- updates[field] = (updates[field] || character[field] || 0) + 1;
718
+ updates[field] = updates[field] + 1;
408
719
  attributeCounts[attr] = (attributeCounts[attr] || 0) + 1;
409
720
  }
410
721
  for (let i = 0; i < 3 - args.length; i++) {
411
722
  const randomAttr = validAttrs[Math.floor(Math.random() * validAttrs.length)];
412
723
  const field = attrMap[randomAttr];
413
- updates[field] = (updates[field] || character[field] || 0) + 1;
724
+ updates[field] = updates[field] + 1;
414
725
  attributeCounts[randomAttr] = (attributeCounts[randomAttr] || 0) + 1;
415
726
  }
727
+ if (attributeCounts["体质"]) {
728
+ const constitutionIncrease = attributeCounts["体质"];
729
+ let newHp = character.currentHp + constitutionIncrease * 5;
730
+ const currentAttrs = calculateCharacterAttributes(character);
731
+ const newMaxHp = currentAttrs.maxHp + constitutionIncrease * 5;
732
+ if (newHp > newMaxHp) newHp = newMaxHp;
733
+ if (newHp < 0) newHp = 0;
734
+ updates.currentHp = newHp;
735
+ }
416
736
  const changes = [];
417
737
  for (const [attr, count] of Object.entries(attributeCounts)) {
418
738
  changes.push(`${attr}+${count}`);
@@ -430,14 +750,10 @@ ${changes.join(" ")}`;
430
750
  return "请等待当前战斗结束!";
431
751
  }
432
752
  let character = await getOrCreateCharacter(userId);
433
- const statusCheck = checkCharacterStatus(character);
753
+ const statusCheck = checkInstructionStatus(character, "捕猎");
434
754
  if (!statusCheck.canAct) {
435
755
  return statusCheck.message;
436
756
  }
437
- const today = getTodayDateString();
438
- if (character.lastHuntDate && isSameDay(character.lastHuntDate, today)) {
439
- return "今天已经捕猎过了,要注意劳逸结合哦!";
440
- }
441
757
  const preyList = parsePreyData(config.preyData);
442
758
  if (preyList.length === 0) {
443
759
  return "未找到猎物数据,请在控制台配置猎物数据。";
@@ -449,7 +765,12 @@ ${changes.join(" ")}`;
449
765
  const crit = parseAttributeRange(selectedPrey.crit);
450
766
  const agility = parseAttributeRange(selectedPrey.agility);
451
767
  const preyHp = constitution * 5;
452
- const chargeTime = parseFloat((1 + 720 / (agility + 80)).toFixed(2));
768
+ const preyRates = calculateRates({
769
+ toughness,
770
+ crit,
771
+ agility
772
+ });
773
+ const player = getBattleCharacter(character);
453
774
  const prey = {
454
775
  name: selectedPrey.name,
455
776
  constitution,
@@ -459,17 +780,12 @@ ${changes.join(" ")}`;
459
780
  agility,
460
781
  hp: preyHp,
461
782
  maxHp: preyHp,
462
- chargeTime,
463
- nextAttackTime: chargeTime
783
+ chargeTime: parseFloat(preyRates.chargeTime),
784
+ nextAttackTime: 0
464
785
  };
465
- if (!battleManager.startBattle(channelId, character.currentHp, selectedPrey.name)) {
786
+ if (!battleManager.startBattle(channelId, player.hp, selectedPrey.name)) {
466
787
  return "战斗开始失败,请重试!";
467
788
  }
468
- const preyRates = calculateRates({
469
- toughness: prey.toughness,
470
- crit: prey.crit,
471
- agility: prey.agility
472
- });
473
789
  await session.send(`发现猎物: ${prey.name}
474
790
  体质: ${prey.constitution} (HP: ${prey.hp})
475
791
  攻击: ${prey.attack}
@@ -479,18 +795,6 @@ ${changes.join(" ")}`;
479
795
  await new Promise((resolve) => setTimeout(resolve, 2e3));
480
796
  const abortController = battleManager.getAbortController(channelId);
481
797
  try {
482
- const player = {
483
- name: character.name,
484
- constitution: character.constitution,
485
- attack: character.attack,
486
- toughness: character.toughness,
487
- crit: character.crit,
488
- agility: character.agility,
489
- hp: character.currentHp,
490
- maxHp: character.maxHp,
491
- chargeTime: parseFloat((1 + 720 / (character.agility + 80)).toFixed(2)),
492
- nextAttackTime: 0
493
- };
494
798
  await executeBattle(
495
799
  session,
496
800
  player,
@@ -500,9 +804,9 @@ ${changes.join(" ")}`;
500
804
  // 不是训练模式
501
805
  abortController
502
806
  );
503
- const today2 = getTodayDateString();
807
+ const today = getTodayDateString();
504
808
  const updates = {
505
- lastHuntDate: today2,
809
+ lastHuntDate: today,
506
810
  updatedAt: /* @__PURE__ */ new Date()
507
811
  };
508
812
  if (player.hp <= 0) {
@@ -522,9 +826,9 @@ ${changes.join(" ")}`;
522
826
  await ctx.database.set("maple_warriors", { userId }, updates);
523
827
  } catch (error) {
524
828
  if (error.name === "AbortError") {
525
- const today2 = getTodayDateString();
829
+ const today = getTodayDateString();
526
830
  await ctx.database.set("maple_warriors", { userId }, {
527
- lastHuntDate: today2,
831
+ lastHuntDate: today,
528
832
  updatedAt: /* @__PURE__ */ new Date()
529
833
  });
530
834
  } else {
@@ -534,31 +838,23 @@ ${changes.join(" ")}`;
534
838
  battleManager.endBattle(channelId);
535
839
  }
536
840
  });
537
- ctx.command("猫武士/使用物品 <itemInput>", "使用物品栏中的物品").example("使用物品 老鼠").example("使用物品 老鼠 2").example("使用物品 老鼠*2").action(async ({ session }, itemInput) => {
538
- if (!itemInput) {
539
- return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
841
+ ctx.command("猫武士/使用物品 <itemName> [count]", "使用物品栏中的物品").example("使用物品 老鼠").example("使用物品 老鼠 2").action(async ({ session }, itemName, countStr) => {
842
+ if (!itemName) {
843
+ return "格式错误!正确格式:使用物品 物品名 [数量]";
540
844
  }
541
845
  const userId = session.userId;
542
846
  let character = await getOrCreateCharacter(userId);
543
- const statusCheck = checkCharacterStatus(character);
847
+ const statusCheck = checkInstructionStatus(character, "使用物品");
544
848
  if (!statusCheck.canAct) {
545
849
  return statusCheck.message;
546
850
  }
547
- let itemName = "";
548
851
  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;
852
+ if (countStr) {
853
+ const parsedCount = parseInt(countStr);
854
+ if (isNaN(parsedCount) || parsedCount <= 0) {
855
+ return "数量必须是正整数";
558
856
  }
559
- }
560
- if (!itemName) {
561
- return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
857
+ count = parsedCount;
562
858
  }
563
859
  const items = parseItemsString(character.items);
564
860
  if (!items[itemName] || items[itemName] < count) {
@@ -575,46 +871,85 @@ ${changes.join(" ")}`;
575
871
  updatedAt: /* @__PURE__ */ new Date()
576
872
  };
577
873
  const changes = [];
874
+ const currentAttrs = calculateCharacterAttributes(character);
578
875
  if (item) {
579
- if (item.hpBonus) {
876
+ if (item.hpBonus && item.hpBonus !== 0) {
580
877
  let newHp = character.currentHp + item.hpBonus * count;
581
- if (newHp > character.maxHp) newHp = character.maxHp;
878
+ const maxHp = currentAttrs.maxHp;
879
+ if (newHp > maxHp) newHp = maxHp;
582
880
  if (newHp < 0) newHp = 0;
583
881
  updates.currentHp = newHp;
584
882
  changes.push(`HP+${item.hpBonus * count}`);
585
883
  }
586
- if (item.constitutionBonus) {
587
- updates.constitution = character.constitution + item.constitutionBonus * count;
884
+ if (item.constitutionBonus && item.constitutionBonus !== 0) {
885
+ const oldConstitution = character.constitution;
886
+ updates.constitution = oldConstitution + item.constitutionBonus * count;
887
+ const constitutionChange = item.constitutionBonus * count;
888
+ let newHp = character.currentHp + constitutionChange * 5;
889
+ const newMaxHp = updates.constitution * 5;
890
+ if (newHp > newMaxHp) newHp = newMaxHp;
891
+ if (newHp < 0) newHp = 0;
892
+ updates.currentHp = newHp;
588
893
  changes.push(`体质+${item.constitutionBonus * count}`);
589
894
  }
590
- if (item.attackBonus) {
895
+ if (item.attackBonus && item.attackBonus !== 0) {
591
896
  updates.attack = character.attack + item.attackBonus * count;
592
897
  changes.push(`攻击+${item.attackBonus * count}`);
593
898
  }
594
- if (item.toughnessBonus) {
899
+ if (item.toughnessBonus && item.toughnessBonus !== 0) {
595
900
  updates.toughness = character.toughness + item.toughnessBonus * count;
596
901
  changes.push(`防御+${item.toughnessBonus * count}`);
597
902
  }
598
- if (item.critBonus) {
903
+ if (item.critBonus && item.critBonus !== 0) {
599
904
  updates.crit = character.crit + item.critBonus * count;
600
905
  changes.push(`暴击+${item.critBonus * count}`);
601
906
  }
602
- if (item.agilityBonus) {
907
+ if (item.agilityBonus && item.agilityBonus !== 0) {
603
908
  updates.agility = character.agility + item.agilityBonus * count;
604
909
  changes.push(`敏捷+${item.agilityBonus * count}`);
605
910
  }
606
- if (updates.constitution) {
607
- updates.maxHp = updates.constitution * 5;
911
+ const buffs = parseBuffs(character.buffs);
912
+ const now = /* @__PURE__ */ new Date();
913
+ if (item.buff && item.type) {
914
+ const itemBuff = parseItemBuff(item.buff);
915
+ if (itemBuff && itemBuff.duration && itemBuff.effects) {
916
+ const endTime = new Date(now.getTime() + itemBuff.duration * 1e3);
917
+ const newBuff = {
918
+ name: itemName,
919
+ type: item.type,
920
+ endTime: endTime.toISOString(),
921
+ effects: itemBuff.effects
922
+ };
923
+ const filteredBuffs = buffs.filter((b) => b.type !== item.type);
924
+ filteredBuffs.push(newBuff);
925
+ updates.buffs = serializeBuffs(filteredBuffs);
926
+ changes.push(`获得${itemName}的buff效果`);
927
+ }
608
928
  }
609
929
  }
610
930
  await ctx.database.set("maple_warriors", { userId }, updates);
611
931
  const changesText = changes.length > 0 ? "\n" + changes.join(" ") : "";
612
- return `使用成功!消耗了${itemName}*${count}${changesText}`;
932
+ let buffInfo = "";
933
+ if (item && item.buff) {
934
+ try {
935
+ const buff = JSON.parse(item.buff);
936
+ if (buff.effects && buff.effects.length > 0) {
937
+ const buffEffects = buff.effects.map((effect) => {
938
+ return `${effect.attribute}+${effect.percentage}%(最大${effect.max})`;
939
+ }).join(" ");
940
+ const duration = formatTimeRemaining(buff.duration);
941
+ buffInfo = `
942
+ buff: ${buffEffects} 持续${duration}`;
943
+ }
944
+ } catch (e) {
945
+ }
946
+ }
947
+ return `使用成功!消耗了${itemName}*${count}${changesText}${buffInfo}`;
613
948
  });
614
- ctx.command("猫武士/睡眠", "让角色进入睡眠状态").example("睡眠").action(async ({ session }) => {
949
+ ctx.command("猫武士/睡眠", "进入睡眠状态(时间结束后回满HP)").example("睡眠").action(async ({ session }) => {
615
950
  const userId = session.userId;
616
951
  let character = await getOrCreateCharacter(userId);
617
- const statusCheck = checkCharacterStatus(character);
952
+ const statusCheck = checkInstructionStatus(character, "睡眠");
618
953
  if (!statusCheck.canAct) {
619
954
  return statusCheck.message;
620
955
  }
@@ -627,11 +962,15 @@ ${changes.join(" ")}`;
627
962
  statusEndTime: endTime,
628
963
  updatedAt: /* @__PURE__ */ new Date()
629
964
  });
630
- return `进入睡眠状态,剩余4h0m0s`;
965
+ return `进入睡眠状态,剩余4h`;
631
966
  });
632
- ctx.command("猫武士/治疗", "治疗受伤的角色").example("治疗").action(async ({ session }) => {
967
+ ctx.command("猫武士/治疗", "治疗受伤的角色(时间结束后回满HP)").example("治疗").action(async ({ session }) => {
633
968
  const userId = session.userId;
634
969
  let character = await getOrCreateCharacter(userId);
970
+ const statusCheck = checkInstructionStatus(character, "治疗");
971
+ if (!statusCheck.canAct) {
972
+ return statusCheck.message;
973
+ }
635
974
  if (character.currentHp > 0 && character.status !== "受伤") {
636
975
  return "你的状态良好,无需治疗。";
637
976
  }
@@ -644,7 +983,7 @@ ${changes.join(" ")}`;
644
983
  statusEndTime: endTime,
645
984
  updatedAt: /* @__PURE__ */ new Date()
646
985
  });
647
- return `进入治疗状态,剩余4h0m0s`;
986
+ return `进入治疗状态,剩余4h`;
648
987
  });
649
988
  ctx.command("猫武士/结束 <action>", "结束当前状态").example("结束 战斗").example("结束 睡眠").example("结束 治疗").action(async ({ session }, action) => {
650
989
  if (!action) {
@@ -677,7 +1016,7 @@ ${changes.join(" ")}`;
677
1016
  statusEndTime: null,
678
1017
  updatedAt: /* @__PURE__ */ new Date()
679
1018
  });
680
- return `结束了当前的${action}`;
1019
+ return `${action}被强制结束!`;
681
1020
  } else {
682
1021
  return `当前没有处于${action}状态。`;
683
1022
  }
@@ -701,122 +1040,300 @@ ${changes.join(" ")}`;
701
1040
  if (item.toughnessBonus) bonuses.push(`防御+${item.toughnessBonus}`);
702
1041
  if (item.critBonus) bonuses.push(`暴击+${item.critBonus}`);
703
1042
  if (item.agilityBonus) bonuses.push(`敏捷+${item.agilityBonus}`);
704
- let result = `物品: ${item.name}
1043
+ let result = `物品: ${item.name} [${item.type || "无类型"}] ${item.sellPrice || 0}/${item.shopPrice >= 0 ? item.shopPrice : "-"}
705
1044
  `;
706
1045
  if (bonuses.length > 0) {
707
1046
  result += bonuses.join(" ") + "\n";
708
1047
  }
1048
+ if (item.buff) {
1049
+ try {
1050
+ const buff = JSON.parse(item.buff);
1051
+ if (buff.effects && buff.effects.length > 0) {
1052
+ const buffEffects = buff.effects.map((effect) => {
1053
+ return `${effect.attribute}+${effect.percentage}%(最大${effect.max})`;
1054
+ }).join(" ");
1055
+ const duration = formatTimeRemaining(buff.duration);
1056
+ result += `buff: ${buffEffects} 持续${duration}
1057
+ `;
1058
+ }
1059
+ } catch (e) {
1060
+ }
1061
+ }
709
1062
  if (item.description) {
710
1063
  result += `"${item.description}"`;
711
1064
  }
712
1065
  return result.trim();
713
1066
  });
714
- ctx.command("猫武士/添加物品 <...args>", "添加或修改物品").example("添加物品 老鼠 hp 1 很常见的老鼠").example("添加物品 治疗药水 hp 10 恢复生命值").action(async ({ session }, ...args) => {
715
- if (args.length < 1) {
716
- return "格式错误!正确格式:添加物品 物品名 [属性 增加数 ...] [介绍]";
1067
+ 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) => {
1068
+ if (!itemName) {
1069
+ return "格式错误!正确格式:添加物品 物品名 [选项]";
717
1070
  }
718
- let itemName = args[0];
719
- let description = "";
720
- const updates = {
721
- hpBonus: 0,
722
- constitutionBonus: 0,
723
- attackBonus: 0,
724
- toughnessBonus: 0,
725
- critBonus: 0,
726
- agilityBonus: 0
727
- };
728
- let i = 1;
729
- const validAttrs = ["HP", "hp", "体质", "攻击", "防御", "暴击", "敏捷"];
730
- const attrMap = {
731
- "HP": "hpBonus",
732
- "hp": "hpBonus",
733
- "体质": "constitutionBonus",
734
- "攻击": "attackBonus",
735
- "防御": "toughnessBonus",
736
- "暴击": "critBonus",
737
- "敏捷": "agilityBonus"
738
- };
739
- while (i < args.length) {
740
- const arg = args[i];
741
- const normalizedAttr = arg.toLowerCase() === "hp" ? "HP" : arg;
742
- if (!validAttrs.includes(normalizedAttr)) {
743
- description = args.slice(i).join(" ");
744
- break;
745
- }
746
- if (i + 1 >= args.length) {
747
- return `格式错误!属性"${arg}"缺少数值`;
1071
+ const existingItems = await ctx.database.get("maple_warriors_items", { name: itemName });
1072
+ const existingItem = existingItems.length > 0 ? existingItems[0] : null;
1073
+ if (existingItem) {
1074
+ const updates = {};
1075
+ if (options.type !== void 0) updates.type = options.type;
1076
+ if (options.money !== void 0) updates.sellPrice = options.money;
1077
+ if (options.shop !== void 0) updates.shopPrice = options.shop;
1078
+ if (options.desc !== void 0) updates.description = options.desc;
1079
+ const numericFields = ["hpBonus", "constitutionBonus", "attackBonus", "toughnessBonus", "critBonus", "agilityBonus"];
1080
+ const attrMap = {
1081
+ "HP": "hpBonus",
1082
+ "hp": "hpBonus",
1083
+ "体质": "constitutionBonus",
1084
+ "攻击": "attackBonus",
1085
+ "防御": "toughnessBonus",
1086
+ "暴击": "critBonus",
1087
+ "敏捷": "agilityBonus"
1088
+ };
1089
+ if (options.characteristics !== void 0) {
1090
+ numericFields.forEach((field) => {
1091
+ updates[field] = 0;
1092
+ });
1093
+ if (options.characteristics.trim() !== "") {
1094
+ const params = options.characteristics.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1095
+ if (params.length % 2 !== 0) {
1096
+ return "属性提升格式错误!格式应为:属性名,数值,属性名,数值,...";
1097
+ }
1098
+ for (let i = 0; i < params.length; i += 2) {
1099
+ const attrName = params[i];
1100
+ const attrValue = params[i + 1];
1101
+ const normalizedAttr = attrName.toLowerCase() === "hp" ? "HP" : attrName;
1102
+ const fieldName = attrMap[normalizedAttr];
1103
+ if (!fieldName) {
1104
+ return `属性名错误!有效的属性有:${Object.keys(attrMap).join("、")}`;
1105
+ }
1106
+ const value = parseInt(attrValue);
1107
+ if (isNaN(value)) {
1108
+ return `属性"${attrName}"的数值必须是数字`;
1109
+ }
1110
+ updates[fieldName] = value;
1111
+ }
1112
+ }
748
1113
  }
749
- const nextArg = args[i + 1];
750
- const value = parseInt(nextArg);
751
- if (isNaN(value)) {
752
- const nextNormalized = nextArg.toLowerCase() === "hp" ? "HP" : nextArg;
753
- if (validAttrs.includes(nextNormalized)) {
754
- return `格式错误!属性"${arg}"缺少数值`;
1114
+ if (options.buff !== void 0) {
1115
+ if (options.buff.trim() === "") {
1116
+ updates.buff = null;
755
1117
  } else {
756
- description = args.slice(i + 1).join(" ");
757
- break;
1118
+ const params = options.buff.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1119
+ if (params.length < 4 || (params.length - 1) % 3 !== 0) {
1120
+ return "buff效果格式错误!格式应为:时间,属性名,百分比,上限,属性名,百分比,上限,...";
1121
+ }
1122
+ const durationStr = params[0];
1123
+ const durationSeconds = parseTimeString(durationStr);
1124
+ if (durationSeconds <= 0) {
1125
+ return "有效时间格式错误!请使用如2m、1h30m、1h等格式";
1126
+ }
1127
+ const effects = [];
1128
+ const validAttrs = ["体质", "攻击", "防御", "暴击", "敏捷"];
1129
+ for (let i = 1; i < params.length; i += 3) {
1130
+ const attribute = params[i];
1131
+ if (!validAttrs.includes(attribute)) {
1132
+ return `buff属性名错误!有效的属性有:${validAttrs.join("、")}`;
1133
+ }
1134
+ const percentageStr = params[i + 1];
1135
+ if (!percentageStr.endsWith("%")) {
1136
+ return `buff属性"${attribute}"的百分比必须以%结尾`;
1137
+ }
1138
+ const percentage = parseInt(percentageStr.slice(0, -1));
1139
+ if (isNaN(percentage) || percentage <= 0) {
1140
+ return `buff属性"${attribute}"的百分比必须是正整数`;
1141
+ }
1142
+ const max = parseInt(params[i + 2]);
1143
+ if (isNaN(max) || max <= 0) {
1144
+ return `buff属性"${attribute}"的最大上限必须是正整数`;
1145
+ }
1146
+ effects.push({
1147
+ attribute,
1148
+ percentage,
1149
+ max
1150
+ });
1151
+ }
1152
+ if (effects.length > 0) {
1153
+ const buff = {
1154
+ duration: durationSeconds,
1155
+ effects
1156
+ };
1157
+ updates.buff = JSON.stringify(buff);
1158
+ } else {
1159
+ updates.buff = null;
1160
+ }
758
1161
  }
759
1162
  }
760
- const fieldName = attrMap[normalizedAttr];
761
- if (fieldName) {
762
- updates[fieldName] = value;
763
- } else {
764
- return `属性名错误!有效的属性有:${["HP", "体质", "攻击", "防御", "暴击", "敏捷"].join("、")}`;
765
- }
766
- i += 2;
767
- }
768
- const existingItems = await ctx.database.get("maple_warriors_items", { name: itemName });
769
- if (existingItems.length > 0) {
770
- await ctx.database.set("maple_warriors_items", { name: itemName }, {
771
- ...updates,
772
- description,
773
- createdAt: /* @__PURE__ */ new Date()
774
- });
775
- return `修改物品成功!`;
1163
+ updates.createdAt = /* @__PURE__ */ new Date();
1164
+ await ctx.database.set("maple_warriors_items", { name: itemName }, updates);
1165
+ return `修改物品"${itemName}"成功!`;
776
1166
  } else {
777
- await ctx.database.create("maple_warriors_items", {
1167
+ const newItem = {
778
1168
  name: itemName,
779
- ...updates,
780
- description,
1169
+ type: options.type || "其他",
1170
+ sellPrice: options.money !== void 0 ? options.money : 0,
1171
+ shopPrice: options.shop !== void 0 ? options.shop : -1,
1172
+ description: options.desc || "",
781
1173
  createdAt: /* @__PURE__ */ new Date()
1174
+ };
1175
+ const numericFields = ["hpBonus", "constitutionBonus", "attackBonus", "toughnessBonus", "critBonus", "agilityBonus"];
1176
+ numericFields.forEach((field) => {
1177
+ newItem[field] = 0;
782
1178
  });
783
- return `添加物品成功!`;
1179
+ const attrMap = {
1180
+ "HP": "hpBonus",
1181
+ "hp": "hpBonus",
1182
+ "体质": "constitutionBonus",
1183
+ "攻击": "attackBonus",
1184
+ "防御": "toughnessBonus",
1185
+ "暴击": "critBonus",
1186
+ "敏捷": "agilityBonus"
1187
+ };
1188
+ if (options.characteristics !== void 0 && options.characteristics.trim() !== "") {
1189
+ const params = options.characteristics.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1190
+ if (params.length % 2 !== 0) {
1191
+ return "属性提升格式错误!格式应为:属性名,数值,属性名,数值,...";
1192
+ }
1193
+ for (let i = 0; i < params.length; i += 2) {
1194
+ const attrName = params[i];
1195
+ const attrValue = params[i + 1];
1196
+ const normalizedAttr = attrName.toLowerCase() === "hp" ? "HP" : attrName;
1197
+ const fieldName = attrMap[normalizedAttr];
1198
+ if (!fieldName) {
1199
+ return `属性名错误!有效的属性有:${Object.keys(attrMap).join("、")}`;
1200
+ }
1201
+ const value = parseInt(attrValue);
1202
+ if (isNaN(value)) {
1203
+ return `属性"${attrName}"的数值必须是数字`;
1204
+ }
1205
+ newItem[fieldName] = value;
1206
+ }
1207
+ }
1208
+ if (options.buff !== void 0 && options.buff.trim() !== "") {
1209
+ const params = options.buff.split(/[,,]/).map((s) => s.trim()).filter((s) => s);
1210
+ if (params.length < 4 || (params.length - 1) % 3 !== 0) {
1211
+ return "buff效果格式错误!格式应为:时间,属性名,百分比,上限,属性名,百分比,上限,...";
1212
+ }
1213
+ const durationStr = params[0];
1214
+ const durationSeconds = parseTimeString(durationStr);
1215
+ if (durationSeconds <= 0) {
1216
+ return "有效时间格式错误!请使用如2m、1h30m、1h等格式";
1217
+ }
1218
+ const effects = [];
1219
+ const validAttrs = ["体质", "攻击", "防御", "暴击", "敏捷"];
1220
+ for (let i = 1; i < params.length; i += 3) {
1221
+ const attribute = params[i];
1222
+ if (!validAttrs.includes(attribute)) {
1223
+ return `buff属性名错误!有效的属性有:${validAttrs.join("、")}`;
1224
+ }
1225
+ const percentageStr = params[i + 1];
1226
+ if (!percentageStr.endsWith("%")) {
1227
+ return `buff属性"${attribute}"的百分比必须以%结尾`;
1228
+ }
1229
+ const percentage = parseInt(percentageStr.slice(0, -1));
1230
+ if (isNaN(percentage) || percentage <= 0) {
1231
+ return `buff属性"${attribute}"的百分比必须是正整数`;
1232
+ }
1233
+ const max = parseInt(params[i + 2]);
1234
+ if (isNaN(max) || max <= 0) {
1235
+ return `buff属性"${attribute}"的最大上限必须是正整数`;
1236
+ }
1237
+ effects.push({
1238
+ attribute,
1239
+ percentage,
1240
+ max
1241
+ });
1242
+ }
1243
+ if (effects.length > 0) {
1244
+ const buff = {
1245
+ duration: durationSeconds,
1246
+ effects
1247
+ };
1248
+ newItem.buff = JSON.stringify(buff);
1249
+ }
1250
+ } else {
1251
+ newItem.buff = null;
1252
+ }
1253
+ await ctx.database.create("maple_warriors_items", newItem);
1254
+ return `添加物品"${itemName}"成功!`;
784
1255
  }
785
1256
  });
786
- ctx.command("猫武士/物品列表 [page]", "查看物品列表").example("物品列表").example("物品列表 2").action(async ({ session }, page = "1") => {
787
- const pageNum = parseInt(page) || 1;
1257
+ ctx.command("猫武士/物品列表 [typeOrPage] [page]", "查看物品列表").example("物品列表").example("物品列表 2").example("物品列表 药品").example("物品列表 药品 2").action(async ({ session }, typeOrPage, page) => {
1258
+ let typeFilter = "";
1259
+ let pageNum = 1;
1260
+ if (typeOrPage) {
1261
+ const parsedPage = parseInt(typeOrPage);
1262
+ if (!isNaN(parsedPage)) {
1263
+ pageNum = parsedPage;
1264
+ } else {
1265
+ typeFilter = typeOrPage;
1266
+ if (page) {
1267
+ const parsedPage2 = parseInt(page);
1268
+ if (!isNaN(parsedPage2)) {
1269
+ pageNum = parsedPage2;
1270
+ }
1271
+ }
1272
+ }
1273
+ }
788
1274
  if (pageNum < 1) {
789
1275
  return "页码必须大于0!";
790
1276
  }
791
1277
  const allItems = await ctx.database.get("maple_warriors_items", {});
792
- allItems.sort((a, b) => a.name.localeCompare(b.name));
1278
+ let filteredItems = allItems;
1279
+ if (typeFilter) {
1280
+ filteredItems = allItems.filter((item) => item.type === typeFilter);
1281
+ if (filteredItems.length === 0) {
1282
+ return `没有找到类型为"${typeFilter}"的物品!`;
1283
+ }
1284
+ }
1285
+ filteredItems.sort((a, b) => a.name.localeCompare(b.name));
793
1286
  const pageSize = 10;
794
- const totalPages = Math.ceil(allItems.length / pageSize);
1287
+ const totalPages = Math.ceil(filteredItems.length / pageSize);
795
1288
  if (pageNum > totalPages && totalPages > 0) {
796
1289
  return `页码超出范围!总页数:${totalPages}`;
797
1290
  }
798
1291
  const startIndex = (pageNum - 1) * pageSize;
799
- const pageItems = allItems.slice(startIndex, startIndex + pageSize);
800
- let output = `物品列表 第 ${pageNum}/${totalPages} 页(共 ${allItems.length} 个物品)
1292
+ const pageItems = filteredItems.slice(startIndex, startIndex + pageSize);
1293
+ let output = `物品列表`;
1294
+ if (typeFilter) {
1295
+ output += ` [类型: ${typeFilter}]`;
1296
+ }
1297
+ output += ` 第 ${pageNum}/${totalPages} 页(共 ${filteredItems.length} 个物品)
801
1298
 
802
1299
  `;
803
1300
  pageItems.forEach((item, index) => {
804
1301
  const displayIndex = startIndex + index + 1;
805
- output += `${displayIndex}. ${item.name}
1302
+ const sellPrice = item.sellPrice || 0;
1303
+ const shopPrice = item.shopPrice >= 0 ? item.shopPrice : "-";
1304
+ output += `${displayIndex}. ${item.name} [${item.type || "无类型"}] ${sellPrice}/${shopPrice}
806
1305
  `;
807
1306
  });
808
1307
  output += "\n──────────\n";
809
1308
  if (pageNum > 1) {
810
- output += `输入"物品列表 ${pageNum - 1}"查看上一页
1309
+ output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum - 1}"查看上一页
811
1310
  `;
812
1311
  }
813
1312
  if (pageNum < totalPages) {
814
- output += `输入"物品列表 ${pageNum + 1}"查看下一页
1313
+ output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum + 1}"查看下一页
815
1314
  `;
816
1315
  }
817
1316
  output += `输入"查询物品 物品名"查看物品详情`;
818
1317
  return output.trim();
819
1318
  });
1319
+ ctx.command("猫武士/set <itemName> [count]", "直接获取指定数量的物品").option("user", "-u <userId:string> 指定用户ID").example("set 老鼠 5").example("set 1级敏捷药水 3").action(async ({ session, options }, itemName, countStr) => {
1320
+ if (!itemName) {
1321
+ return "格式错误!正确格式:set 物品名 [数量]";
1322
+ }
1323
+ const targetUserId = options.user ? options.user : session.userId;
1324
+ const count = countStr ? parseInt(countStr) : 1;
1325
+ if (isNaN(count) || count <= 0) {
1326
+ return "数量必须是正整数";
1327
+ }
1328
+ let character = await getOrCreateCharacter(targetUserId);
1329
+ const items = parseItemsString(character.items);
1330
+ items[itemName] = (items[itemName] || 0) + count;
1331
+ await ctx.database.set("maple_warriors", { userId: targetUserId }, {
1332
+ items: serializeItems(items),
1333
+ updatedAt: /* @__PURE__ */ new Date()
1334
+ });
1335
+ return `成功获得${itemName}*${count}`;
1336
+ });
820
1337
  ctx.command("猫武士/战斗训练 <target:user>", "与指定用户的角色进行训练战斗(不消耗HP)").example("战斗训练 @火星").action(async ({ session }, target) => {
821
1338
  const channelId = session.channelId;
822
1339
  const userId = session.userId;
@@ -832,10 +1349,10 @@ ${changes.join(" ")}`;
832
1349
  return "请指定训练对手!正确格式:战斗训练 @用户名";
833
1350
  }
834
1351
  if (targetUserId === userId) {
835
- return "不能和自己进行战斗训练哦!";
1352
+ return "不能和自己进行战斗训练!";
836
1353
  }
837
1354
  let player1 = await getOrCreateCharacter(userId);
838
- const statusCheck = checkCharacterStatus(player1);
1355
+ const statusCheck = checkInstructionStatus(player1, "战斗训练");
839
1356
  if (!statusCheck.canAct) {
840
1357
  return statusCheck.message;
841
1358
  }
@@ -844,39 +1361,15 @@ ${changes.join(" ")}`;
844
1361
  return "战斗失败!对方尚未生成角色";
845
1362
  }
846
1363
  let player2 = player2Characters[0];
847
- const opponentStatusCheck = checkCharacterStatus(player2);
1364
+ const opponentStatusCheck = checkInstructionStatus(player2, "战斗训练");
848
1365
  if (!opponentStatusCheck.canAct) {
849
- return `对手目前状态不佳,无法进行战斗训练:${opponentStatusCheck.message}`;
1366
+ return `对方${player2.status}中,无法进行战斗训练!`;
850
1367
  }
851
1368
  if (!battleManager.startBattle(channelId, player1.currentHp, void 0)) {
852
1369
  return "战斗开始失败,请重试!";
853
1370
  }
854
- const player1Char = {
855
- name: player1.name,
856
- constitution: player1.constitution,
857
- attack: player1.attack,
858
- toughness: player1.toughness,
859
- crit: player1.crit,
860
- agility: player1.agility,
861
- hp: player1.currentHp,
862
- // 使用当前HP
863
- maxHp: player1.maxHp,
864
- chargeTime: parseFloat((1 + 720 / (player1.agility + 80)).toFixed(2)),
865
- nextAttackTime: 0
866
- };
867
- const player2Char = {
868
- name: player2.name,
869
- constitution: player2.constitution,
870
- attack: player2.attack,
871
- toughness: player2.toughness,
872
- crit: player2.crit,
873
- agility: player2.agility,
874
- hp: player2.currentHp,
875
- // 使用当前HP
876
- maxHp: player2.maxHp,
877
- chargeTime: parseFloat((1 + 720 / (player2.agility + 80)).toFixed(2)),
878
- nextAttackTime: 0
879
- };
1371
+ const player1Char = getBattleCharacter(player1);
1372
+ const player2Char = getBattleCharacter(player2);
880
1373
  const abortController = battleManager.getAbortController(channelId);
881
1374
  try {
882
1375
  await executeBattle(
@@ -896,6 +1389,126 @@ ${changes.join(" ")}`;
896
1389
  battleManager.endBattle(channelId);
897
1390
  }
898
1391
  });
1392
+ ctx.command("猫武士/删除角色", "删除角色").option("user", "-u <userId:string> 指定要删除角色的用户ID").example("删除角色").action(async ({ session, options }) => {
1393
+ const userId = options.user ? options.user : session.userId;
1394
+ const characters = await ctx.database.get("maple_warriors", { userId });
1395
+ if (characters.length === 0) {
1396
+ return "删除失败!找不到用户的角色";
1397
+ }
1398
+ const character = characters[0];
1399
+ await session.send(`确定删除角色【${character.name}】吗?回复"确认"进行删除`);
1400
+ try {
1401
+ const confirm = await session.prompt(3e4);
1402
+ if (confirm === "确认") {
1403
+ await ctx.database.remove("maple_warriors", { userId });
1404
+ return `删除【${character.name}】成功。输入"我的角色"将新建一个角色`;
1405
+ } else {
1406
+ return "删除失败!无法识别文本";
1407
+ }
1408
+ } catch (error) {
1409
+ return "删除失败!回复超时";
1410
+ }
1411
+ });
1412
+ ctx.command("猫武士/出售 <itemName> [count]", "出售物品").example("出售 老鼠").example("出售 老鼠 2").example("出售 老鼠 全部").action(async ({ session }, itemName, countStr) => {
1413
+ if (!itemName) {
1414
+ return "格式错误!正确格式:出售 物品名 [数量|全部]";
1415
+ }
1416
+ const userId = session.userId;
1417
+ let character = await getOrCreateCharacter(userId);
1418
+ const items = parseItemsString(character.items);
1419
+ if (!items[itemName]) {
1420
+ return `物品栏中没有${itemName}!`;
1421
+ }
1422
+ let count = 1;
1423
+ let sellAll = false;
1424
+ if (countStr === "全部") {
1425
+ count = items[itemName];
1426
+ sellAll = true;
1427
+ } else if (countStr) {
1428
+ const parsedCount = parseInt(countStr);
1429
+ if (isNaN(parsedCount) || parsedCount <= 0) {
1430
+ return '数量必须是正整数或"全部"';
1431
+ }
1432
+ count = Math.min(parsedCount, items[itemName]);
1433
+ } else {
1434
+ count = 1;
1435
+ }
1436
+ if (count > items[itemName]) {
1437
+ return `物品栏中没有那么多${itemName}!`;
1438
+ }
1439
+ const itemInfo = await ctx.database.get("maple_warriors_items", { name: itemName });
1440
+ const item = itemInfo.length > 0 ? itemInfo[0] : null;
1441
+ const sellPrice = item?.sellPrice || 0;
1442
+ const totalGold = sellPrice * count;
1443
+ await session.send(`出售${itemName}*${count}将获得${formatGold(totalGold)}金币,回复"确认"进行出售!`);
1444
+ try {
1445
+ const confirm = await session.prompt(3e4);
1446
+ if (confirm !== "确认") {
1447
+ return "回复错误,出售失败!";
1448
+ }
1449
+ items[itemName] -= count;
1450
+ if (items[itemName] <= 0) {
1451
+ delete items[itemName];
1452
+ }
1453
+ const newGold = character.gold + totalGold;
1454
+ await ctx.database.set("maple_warriors", { userId }, {
1455
+ items: serializeItems(items),
1456
+ gold: newGold,
1457
+ updatedAt: /* @__PURE__ */ new Date()
1458
+ });
1459
+ return `出售了${itemName}*${count},获得${formatGold(totalGold)}金币`;
1460
+ } catch (error) {
1461
+ return "回复错误,出售失败!";
1462
+ }
1463
+ });
1464
+ ctx.command("猫武士/金币排行", "查看金币排行榜").example("金币排行").action(async ({ session }) => {
1465
+ const allCharacters = await ctx.database.get("maple_warriors", {});
1466
+ const sortedCharacters = allCharacters.sort((a, b) => b.gold - a.gold);
1467
+ const topCharacters = sortedCharacters.slice(0, 5);
1468
+ let output = "【金币排行】\n";
1469
+ topCharacters.forEach((character, index) => {
1470
+ const rank = index + 1;
1471
+ output += `${rank}. ${character.name} 金币: ${formatGold(character.gold)}
1472
+ `;
1473
+ });
1474
+ return output.trim();
1475
+ });
1476
+ ctx.command("猫武士/购买物品 <itemName> [count]", "从商店购买物品").example("购买物品 老鼠").example("购买物品 老鼠 2").action(async ({ session }, itemName, countStr) => {
1477
+ if (!itemName) {
1478
+ return "格式错误!正确格式:购买物品 物品名 [数量]";
1479
+ }
1480
+ const userId = session.userId;
1481
+ let character = await getOrCreateCharacter(userId);
1482
+ const itemInfo = await ctx.database.get("maple_warriors_items", { name: itemName });
1483
+ if (itemInfo.length === 0) {
1484
+ return `购买失败!商店中没有${itemName}!`;
1485
+ }
1486
+ const item = itemInfo[0];
1487
+ if (item.shopPrice < 0) {
1488
+ return `购买失败!商店中没有${itemName}!`;
1489
+ }
1490
+ let count = 1;
1491
+ if (countStr) {
1492
+ const parsedCount = parseInt(countStr);
1493
+ if (isNaN(parsedCount) || parsedCount <= 0) {
1494
+ return "数量必须是正整数";
1495
+ }
1496
+ count = parsedCount;
1497
+ }
1498
+ const requiredGold = item.shopPrice * count;
1499
+ if (character.gold < requiredGold) {
1500
+ return `购买失败!需要${formatGold(requiredGold)}金币,金币不足!`;
1501
+ }
1502
+ const items = parseItemsString(character.items);
1503
+ items[itemName] = (items[itemName] || 0) + count;
1504
+ const newGold = character.gold - requiredGold;
1505
+ await ctx.database.set("maple_warriors", { userId }, {
1506
+ items: serializeItems(items),
1507
+ gold: newGold,
1508
+ updatedAt: /* @__PURE__ */ new Date()
1509
+ });
1510
+ return `购买成功!消耗${formatGold(requiredGold)}金币,获得了${itemName}*${count}`;
1511
+ });
899
1512
  setInterval(async () => {
900
1513
  try {
901
1514
  const now = /* @__PURE__ */ new Date();
@@ -909,18 +1522,40 @@ ${changes.join(" ")}`;
909
1522
  updatedAt: /* @__PURE__ */ new Date()
910
1523
  };
911
1524
  if (character.status === "睡眠" || character.status === "治疗") {
912
- updates.currentHp = character.maxHp;
1525
+ const attrs = calculateCharacterAttributes(character);
1526
+ updates.currentHp = attrs.maxHp;
913
1527
  }
914
1528
  await ctx.database.set("maple_warriors", { id: character.id }, updates);
915
1529
  }
916
1530
  }
1531
+ for (const character of characters) {
1532
+ const buffs = parseBuffs(character.buffs);
1533
+ if (buffs.length === 0) continue;
1534
+ const activeBuffs = buffs.filter((buff) => {
1535
+ return new Date(buff.endTime) > now;
1536
+ });
1537
+ if (activeBuffs.length !== buffs.length) {
1538
+ await ctx.database.set("maple_warriors", { id: character.id }, {
1539
+ buffs: serializeBuffs(activeBuffs),
1540
+ updatedAt: /* @__PURE__ */ new Date()
1541
+ });
1542
+ }
1543
+ }
917
1544
  } catch (error) {
918
- console.error("状态检查错误:", error);
1545
+ console.error("定时器错误:", error);
919
1546
  }
920
1547
  }, 6e4);
921
1548
  }
922
1549
  __name(apply, "apply");
923
1550
  async function executeBattle(session, player1, player2, battleMessageInterval, isTraining = false, abortController) {
1551
+ if (player1.hp <= 0) {
1552
+ await session.send(`战斗失败!${player1.name}的HP为0!`);
1553
+ return;
1554
+ }
1555
+ if (player2.hp <= 0) {
1556
+ await session.send(`战斗失败!${player2.name}的HP为0!`);
1557
+ return;
1558
+ }
924
1559
  const initialHp1 = player1.hp;
925
1560
  const initialHp2 = player2.hp;
926
1561
  const rates1 = calculateRates({