koishi-plugin-maple-warriors 0.0.3 → 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.
- package/lib/index.d.ts +52 -0
- package/lib/index.js +901 -235
- 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
|
-
"老鼠
|
|
34
|
-
"
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
108
|
+
function parseBuffs(buffsStr) {
|
|
109
|
+
if (!buffsStr) return [];
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(buffsStr);
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
51
114
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
580
|
+
const status = character.status;
|
|
581
|
+
if (status === "无") {
|
|
582
|
+
return { canAct: true, message: "" };
|
|
292
583
|
}
|
|
293
|
-
|
|
294
|
-
|
|
584
|
+
const statusRule = statusRules[status];
|
|
585
|
+
if (!statusRule) {
|
|
586
|
+
return { canAct: true, message: "" };
|
|
295
587
|
}
|
|
296
|
-
|
|
297
|
-
|
|
588
|
+
const instructionRule = statusRule[instruction];
|
|
589
|
+
if (!instructionRule) {
|
|
590
|
+
return { canAct: true, message: "" };
|
|
298
591
|
}
|
|
299
|
-
return { canAct:
|
|
592
|
+
return { canAct: false, message: instructionRule.message };
|
|
300
593
|
}
|
|
301
|
-
__name(
|
|
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,
|
|
@@ -341,7 +635,38 @@ ${itemsText || "空空如也"}`;
|
|
|
341
635
|
ctx.command("猫武士/我的角色", "查看我的角色信息").example("我的角色").action(async ({ session }) => {
|
|
342
636
|
const userId = session.userId;
|
|
343
637
|
let character = await getOrCreateCharacter(userId);
|
|
344
|
-
|
|
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
671
|
ctx.command("猫武士/训练 <...args>", "训练提升属性").example("训练 体质 体质 攻击").example("训练 体质").example("训练 随机").action(async ({ session }, ...args) => {
|
|
347
672
|
const userId = session.userId;
|
|
@@ -350,7 +675,7 @@ ${itemsText || "空空如也"}`;
|
|
|
350
675
|
if (character.lastTrainDate && isSameDay(character.lastTrainDate, today)) {
|
|
351
676
|
return "今天已经训练过了,要注意劳逸结合哦!";
|
|
352
677
|
}
|
|
353
|
-
const statusCheck =
|
|
678
|
+
const statusCheck = checkInstructionStatus(character, "训练");
|
|
354
679
|
if (!statusCheck.canAct) {
|
|
355
680
|
return statusCheck.message;
|
|
356
681
|
}
|
|
@@ -368,16 +693,29 @@ ${itemsText || "空空如也"}`;
|
|
|
368
693
|
};
|
|
369
694
|
const updates2 = {};
|
|
370
695
|
const attributeCounts2 = {};
|
|
696
|
+
for (const key of Object.keys(attrMap2)) {
|
|
697
|
+
const field = attrMap2[key];
|
|
698
|
+
updates2[field] = character[field] || 0;
|
|
699
|
+
}
|
|
371
700
|
for (let i = 0; i < 3; i++) {
|
|
372
701
|
const randomAttr = validAttrs2[Math.floor(Math.random() * validAttrs2.length)];
|
|
373
702
|
const field = attrMap2[randomAttr];
|
|
374
|
-
updates2[field] =
|
|
703
|
+
updates2[field] = updates2[field] + 1;
|
|
375
704
|
attributeCounts2[randomAttr] = (attributeCounts2[randomAttr] || 0) + 1;
|
|
376
705
|
}
|
|
377
706
|
const changes2 = [];
|
|
378
707
|
for (const [attr, count] of Object.entries(attributeCounts2)) {
|
|
379
708
|
changes2.push(`${attr}+${count}`);
|
|
380
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
|
+
}
|
|
381
719
|
updates2.lastTrainDate = today;
|
|
382
720
|
updates2.updatedAt = /* @__PURE__ */ new Date();
|
|
383
721
|
await ctx.database.set("maple_warriors", { userId }, updates2);
|
|
@@ -402,17 +740,30 @@ ${changes2.join(" ")}`;
|
|
|
402
740
|
}
|
|
403
741
|
const updates = {};
|
|
404
742
|
const attributeCounts = {};
|
|
743
|
+
for (const key of Object.keys(attrMap)) {
|
|
744
|
+
const field = attrMap[key];
|
|
745
|
+
updates[field] = character[field] || 0;
|
|
746
|
+
}
|
|
405
747
|
for (const attr of args) {
|
|
406
748
|
const field = attrMap[attr];
|
|
407
|
-
updates[field] =
|
|
749
|
+
updates[field] = updates[field] + 1;
|
|
408
750
|
attributeCounts[attr] = (attributeCounts[attr] || 0) + 1;
|
|
409
751
|
}
|
|
410
752
|
for (let i = 0; i < 3 - args.length; i++) {
|
|
411
753
|
const randomAttr = validAttrs[Math.floor(Math.random() * validAttrs.length)];
|
|
412
754
|
const field = attrMap[randomAttr];
|
|
413
|
-
updates[field] =
|
|
755
|
+
updates[field] = updates[field] + 1;
|
|
414
756
|
attributeCounts[randomAttr] = (attributeCounts[randomAttr] || 0) + 1;
|
|
415
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
|
+
}
|
|
416
767
|
const changes = [];
|
|
417
768
|
for (const [attr, count] of Object.entries(attributeCounts)) {
|
|
418
769
|
changes.push(`${attr}+${count}`);
|
|
@@ -430,14 +781,10 @@ ${changes.join(" ")}`;
|
|
|
430
781
|
return "请等待当前战斗结束!";
|
|
431
782
|
}
|
|
432
783
|
let character = await getOrCreateCharacter(userId);
|
|
433
|
-
const statusCheck =
|
|
784
|
+
const statusCheck = checkInstructionStatus(character, "捕猎");
|
|
434
785
|
if (!statusCheck.canAct) {
|
|
435
786
|
return statusCheck.message;
|
|
436
787
|
}
|
|
437
|
-
const today = getTodayDateString();
|
|
438
|
-
if (character.lastHuntDate && isSameDay(character.lastHuntDate, today)) {
|
|
439
|
-
return "今天已经捕猎过了,要注意劳逸结合哦!";
|
|
440
|
-
}
|
|
441
788
|
const preyList = parsePreyData(config.preyData);
|
|
442
789
|
if (preyList.length === 0) {
|
|
443
790
|
return "未找到猎物数据,请在控制台配置猎物数据。";
|
|
@@ -449,7 +796,12 @@ ${changes.join(" ")}`;
|
|
|
449
796
|
const crit = parseAttributeRange(selectedPrey.crit);
|
|
450
797
|
const agility = parseAttributeRange(selectedPrey.agility);
|
|
451
798
|
const preyHp = constitution * 5;
|
|
452
|
-
const
|
|
799
|
+
const preyRates = calculateRates({
|
|
800
|
+
toughness,
|
|
801
|
+
crit,
|
|
802
|
+
agility
|
|
803
|
+
});
|
|
804
|
+
const player = getBattleCharacter(character);
|
|
453
805
|
const prey = {
|
|
454
806
|
name: selectedPrey.name,
|
|
455
807
|
constitution,
|
|
@@ -459,17 +811,12 @@ ${changes.join(" ")}`;
|
|
|
459
811
|
agility,
|
|
460
812
|
hp: preyHp,
|
|
461
813
|
maxHp: preyHp,
|
|
462
|
-
chargeTime,
|
|
463
|
-
nextAttackTime:
|
|
814
|
+
chargeTime: parseFloat(preyRates.chargeTime),
|
|
815
|
+
nextAttackTime: 0
|
|
464
816
|
};
|
|
465
|
-
if (!battleManager.startBattle(channelId,
|
|
817
|
+
if (!battleManager.startBattle(channelId, player.hp, selectedPrey.name)) {
|
|
466
818
|
return "战斗开始失败,请重试!";
|
|
467
819
|
}
|
|
468
|
-
const preyRates = calculateRates({
|
|
469
|
-
toughness: prey.toughness,
|
|
470
|
-
crit: prey.crit,
|
|
471
|
-
agility: prey.agility
|
|
472
|
-
});
|
|
473
820
|
await session.send(`发现猎物: ${prey.name}
|
|
474
821
|
体质: ${prey.constitution} (HP: ${prey.hp})
|
|
475
822
|
攻击: ${prey.attack}
|
|
@@ -479,18 +826,6 @@ ${changes.join(" ")}`;
|
|
|
479
826
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
480
827
|
const abortController = battleManager.getAbortController(channelId);
|
|
481
828
|
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
829
|
await executeBattle(
|
|
495
830
|
session,
|
|
496
831
|
player,
|
|
@@ -500,9 +835,9 @@ ${changes.join(" ")}`;
|
|
|
500
835
|
// 不是训练模式
|
|
501
836
|
abortController
|
|
502
837
|
);
|
|
503
|
-
const
|
|
838
|
+
const today = getTodayDateString();
|
|
504
839
|
const updates = {
|
|
505
|
-
lastHuntDate:
|
|
840
|
+
lastHuntDate: today,
|
|
506
841
|
updatedAt: /* @__PURE__ */ new Date()
|
|
507
842
|
};
|
|
508
843
|
if (player.hp <= 0) {
|
|
@@ -522,9 +857,9 @@ ${changes.join(" ")}`;
|
|
|
522
857
|
await ctx.database.set("maple_warriors", { userId }, updates);
|
|
523
858
|
} catch (error) {
|
|
524
859
|
if (error.name === "AbortError") {
|
|
525
|
-
const
|
|
860
|
+
const today = getTodayDateString();
|
|
526
861
|
await ctx.database.set("maple_warriors", { userId }, {
|
|
527
|
-
lastHuntDate:
|
|
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("猫武士/使用物品 <
|
|
538
|
-
if (!
|
|
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 =
|
|
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 (
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
607
|
-
|
|
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
|
-
|
|
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("猫武士/睡眠", "
|
|
980
|
+
ctx.command("猫武士/睡眠", "进入睡眠状态(时间结束后回满HP)").example("睡眠").action(async ({ session }) => {
|
|
615
981
|
const userId = session.userId;
|
|
616
982
|
let character = await getOrCreateCharacter(userId);
|
|
617
|
-
const statusCheck =
|
|
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 `进入睡眠状态,剩余
|
|
996
|
+
return `进入睡眠状态,剩余4h`;
|
|
631
997
|
});
|
|
632
|
-
ctx.command("猫武士/治疗", "
|
|
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 `进入治疗状态,剩余
|
|
1017
|
+
return `进入治疗状态,剩余4h`;
|
|
648
1018
|
});
|
|
649
1019
|
ctx.command("猫武士/结束 <action>", "结束当前状态").example("结束 战斗").example("结束 睡眠").example("结束 治疗").action(async ({ session }, action) => {
|
|
650
1020
|
if (!action) {
|
|
@@ -677,7 +1047,7 @@ ${changes.join(" ")}`;
|
|
|
677
1047
|
statusEndTime: null,
|
|
678
1048
|
updatedAt: /* @__PURE__ */ new Date()
|
|
679
1049
|
});
|
|
680
|
-
return
|
|
1050
|
+
return `${action}被强制结束!`;
|
|
681
1051
|
} else {
|
|
682
1052
|
return `当前没有处于${action}状态。`;
|
|
683
1053
|
}
|
|
@@ -701,122 +1071,300 @@ ${changes.join(" ")}`;
|
|
|
701
1071
|
if (item.toughnessBonus) bonuses.push(`防御+${item.toughnessBonus}`);
|
|
702
1072
|
if (item.critBonus) bonuses.push(`暴击+${item.critBonus}`);
|
|
703
1073
|
if (item.agilityBonus) bonuses.push(`敏捷+${item.agilityBonus}`);
|
|
704
|
-
let result = `物品: ${item.name}
|
|
1074
|
+
let result = `物品: ${item.name} [${item.type || "无类型"}] ${item.sellPrice || 0}/${item.shopPrice >= 0 ? item.shopPrice : "-"}
|
|
705
1075
|
`;
|
|
706
1076
|
if (bonuses.length > 0) {
|
|
707
1077
|
result += bonuses.join(" ") + "\n";
|
|
708
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
|
+
}
|
|
709
1093
|
if (item.description) {
|
|
710
1094
|
result += `"${item.description}"`;
|
|
711
1095
|
}
|
|
712
1096
|
return result.trim();
|
|
713
1097
|
});
|
|
714
|
-
ctx.command("猫武士/添加物品
|
|
715
|
-
if (
|
|
716
|
-
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 "格式错误!正确格式:添加物品 物品名 [选项]";
|
|
717
1101
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
agilityBonus
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
+
}
|
|
748
1144
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const nextNormalized = nextArg.toLowerCase() === "hp" ? "HP" : nextArg;
|
|
753
|
-
if (validAttrs.includes(nextNormalized)) {
|
|
754
|
-
return `格式错误!属性"${arg}"缺少数值`;
|
|
1145
|
+
if (options.buff !== void 0) {
|
|
1146
|
+
if (options.buff.trim() === "") {
|
|
1147
|
+
updates.buff = null;
|
|
755
1148
|
} else {
|
|
756
|
-
|
|
757
|
-
|
|
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
|
+
}
|
|
758
1192
|
}
|
|
759
1193
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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 `修改物品成功!`;
|
|
1194
|
+
updates.createdAt = /* @__PURE__ */ new Date();
|
|
1195
|
+
await ctx.database.set("maple_warriors_items", { name: itemName }, updates);
|
|
1196
|
+
return `修改物品"${itemName}"成功!`;
|
|
776
1197
|
} else {
|
|
777
|
-
|
|
1198
|
+
const newItem = {
|
|
778
1199
|
name: itemName,
|
|
779
|
-
|
|
780
|
-
|
|
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 || "",
|
|
781
1204
|
createdAt: /* @__PURE__ */ new Date()
|
|
1205
|
+
};
|
|
1206
|
+
const numericFields = ["hpBonus", "constitutionBonus", "attackBonus", "toughnessBonus", "critBonus", "agilityBonus"];
|
|
1207
|
+
numericFields.forEach((field) => {
|
|
1208
|
+
newItem[field] = 0;
|
|
782
1209
|
});
|
|
783
|
-
|
|
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}"成功!`;
|
|
784
1286
|
}
|
|
785
1287
|
});
|
|
786
|
-
ctx.command("猫武士/物品列表 [page]", "查看物品列表").example("物品列表").example("物品列表 2").action(async ({ session }, page
|
|
787
|
-
|
|
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
|
+
}
|
|
788
1305
|
if (pageNum < 1) {
|
|
789
1306
|
return "页码必须大于0!";
|
|
790
1307
|
}
|
|
791
1308
|
const allItems = await ctx.database.get("maple_warriors_items", {});
|
|
792
|
-
|
|
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));
|
|
793
1317
|
const pageSize = 10;
|
|
794
|
-
const totalPages = Math.ceil(
|
|
1318
|
+
const totalPages = Math.ceil(filteredItems.length / pageSize);
|
|
795
1319
|
if (pageNum > totalPages && totalPages > 0) {
|
|
796
1320
|
return `页码超出范围!总页数:${totalPages}`;
|
|
797
1321
|
}
|
|
798
1322
|
const startIndex = (pageNum - 1) * pageSize;
|
|
799
|
-
const pageItems =
|
|
800
|
-
let output =
|
|
1323
|
+
const pageItems = filteredItems.slice(startIndex, startIndex + pageSize);
|
|
1324
|
+
let output = `物品列表`;
|
|
1325
|
+
if (typeFilter) {
|
|
1326
|
+
output += ` [类型: ${typeFilter}]`;
|
|
1327
|
+
}
|
|
1328
|
+
output += ` 第 ${pageNum}/${totalPages} 页(共 ${filteredItems.length} 个物品)
|
|
801
1329
|
|
|
802
1330
|
`;
|
|
803
1331
|
pageItems.forEach((item, index) => {
|
|
804
1332
|
const displayIndex = startIndex + index + 1;
|
|
805
|
-
|
|
1333
|
+
const sellPrice = item.sellPrice || 0;
|
|
1334
|
+
const shopPrice = item.shopPrice >= 0 ? item.shopPrice : "-";
|
|
1335
|
+
output += `${displayIndex}. ${item.name} [${item.type || "无类型"}] ${sellPrice}/${shopPrice}
|
|
806
1336
|
`;
|
|
807
1337
|
});
|
|
808
1338
|
output += "\n──────────\n";
|
|
809
1339
|
if (pageNum > 1) {
|
|
810
|
-
output += `输入"
|
|
1340
|
+
output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum - 1}"查看上一页
|
|
811
1341
|
`;
|
|
812
1342
|
}
|
|
813
1343
|
if (pageNum < totalPages) {
|
|
814
|
-
output += `输入"
|
|
1344
|
+
output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum + 1}"查看下一页
|
|
815
1345
|
`;
|
|
816
1346
|
}
|
|
817
1347
|
output += `输入"查询物品 物品名"查看物品详情`;
|
|
818
1348
|
return output.trim();
|
|
819
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
|
+
});
|
|
820
1368
|
ctx.command("猫武士/战斗训练 <target:user>", "与指定用户的角色进行训练战斗(不消耗HP)").example("战斗训练 @火星").action(async ({ session }, target) => {
|
|
821
1369
|
const channelId = session.channelId;
|
|
822
1370
|
const userId = session.userId;
|
|
@@ -832,10 +1380,10 @@ ${changes.join(" ")}`;
|
|
|
832
1380
|
return "请指定训练对手!正确格式:战斗训练 @用户名";
|
|
833
1381
|
}
|
|
834
1382
|
if (targetUserId === userId) {
|
|
835
|
-
return "
|
|
1383
|
+
return "不能和自己进行战斗训练!";
|
|
836
1384
|
}
|
|
837
1385
|
let player1 = await getOrCreateCharacter(userId);
|
|
838
|
-
const statusCheck =
|
|
1386
|
+
const statusCheck = checkInstructionStatus(player1, "战斗训练");
|
|
839
1387
|
if (!statusCheck.canAct) {
|
|
840
1388
|
return statusCheck.message;
|
|
841
1389
|
}
|
|
@@ -844,39 +1392,15 @@ ${changes.join(" ")}`;
|
|
|
844
1392
|
return "战斗失败!对方尚未生成角色";
|
|
845
1393
|
}
|
|
846
1394
|
let player2 = player2Characters[0];
|
|
847
|
-
const opponentStatusCheck =
|
|
1395
|
+
const opponentStatusCheck = checkInstructionStatus(player2, "战斗训练");
|
|
848
1396
|
if (!opponentStatusCheck.canAct) {
|
|
849
|
-
return
|
|
1397
|
+
return `对方${player2.status}中,无法进行战斗训练!`;
|
|
850
1398
|
}
|
|
851
1399
|
if (!battleManager.startBattle(channelId, player1.currentHp, void 0)) {
|
|
852
1400
|
return "战斗开始失败,请重试!";
|
|
853
1401
|
}
|
|
854
|
-
const player1Char =
|
|
855
|
-
|
|
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
|
-
};
|
|
1402
|
+
const player1Char = getBattleCharacter(player1);
|
|
1403
|
+
const player2Char = getBattleCharacter(player2);
|
|
880
1404
|
const abortController = battleManager.getAbortController(channelId);
|
|
881
1405
|
try {
|
|
882
1406
|
await executeBattle(
|
|
@@ -896,6 +1420,126 @@ ${changes.join(" ")}`;
|
|
|
896
1420
|
battleManager.endBattle(channelId);
|
|
897
1421
|
}
|
|
898
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
|
+
});
|
|
899
1543
|
setInterval(async () => {
|
|
900
1544
|
try {
|
|
901
1545
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -909,18 +1553,40 @@ ${changes.join(" ")}`;
|
|
|
909
1553
|
updatedAt: /* @__PURE__ */ new Date()
|
|
910
1554
|
};
|
|
911
1555
|
if (character.status === "睡眠" || character.status === "治疗") {
|
|
912
|
-
|
|
1556
|
+
const attrs = calculateCharacterAttributes(character);
|
|
1557
|
+
updates.currentHp = attrs.maxHp;
|
|
913
1558
|
}
|
|
914
1559
|
await ctx.database.set("maple_warriors", { id: character.id }, updates);
|
|
915
1560
|
}
|
|
916
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
|
+
}
|
|
917
1575
|
} catch (error) {
|
|
918
|
-
console.error("
|
|
1576
|
+
console.error("定时器错误:", error);
|
|
919
1577
|
}
|
|
920
1578
|
}, 6e4);
|
|
921
1579
|
}
|
|
922
1580
|
__name(apply, "apply");
|
|
923
1581
|
async function executeBattle(session, player1, player2, battleMessageInterval, isTraining = false, abortController) {
|
|
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
|
+
}
|
|
924
1590
|
const initialHp1 = player1.hp;
|
|
925
1591
|
const initialHp2 = player2.hp;
|
|
926
1592
|
const rates1 = calculateRates({
|