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.
- package/lib/index.js +869 -234
- 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,
|
|
@@ -350,7 +644,7 @@ ${itemsText || "空空如也"}`;
|
|
|
350
644
|
if (character.lastTrainDate && isSameDay(character.lastTrainDate, today)) {
|
|
351
645
|
return "今天已经训练过了,要注意劳逸结合哦!";
|
|
352
646
|
}
|
|
353
|
-
const statusCheck =
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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 =
|
|
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
|
|
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:
|
|
783
|
+
chargeTime: parseFloat(preyRates.chargeTime),
|
|
784
|
+
nextAttackTime: 0
|
|
464
785
|
};
|
|
465
|
-
if (!battleManager.startBattle(channelId,
|
|
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
|
|
807
|
+
const today = getTodayDateString();
|
|
504
808
|
const updates = {
|
|
505
|
-
lastHuntDate:
|
|
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
|
|
829
|
+
const today = getTodayDateString();
|
|
526
830
|
await ctx.database.set("maple_warriors", { userId }, {
|
|
527
|
-
lastHuntDate:
|
|
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("猫武士/使用物品 <
|
|
538
|
-
if (!
|
|
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 =
|
|
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 (
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
607
|
-
|
|
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
|
-
|
|
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("猫武士/睡眠", "
|
|
949
|
+
ctx.command("猫武士/睡眠", "进入睡眠状态(时间结束后回满HP)").example("睡眠").action(async ({ session }) => {
|
|
615
950
|
const userId = session.userId;
|
|
616
951
|
let character = await getOrCreateCharacter(userId);
|
|
617
|
-
const statusCheck =
|
|
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 `进入睡眠状态,剩余
|
|
965
|
+
return `进入睡眠状态,剩余4h`;
|
|
631
966
|
});
|
|
632
|
-
ctx.command("猫武士/治疗", "
|
|
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 `进入治疗状态,剩余
|
|
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
|
|
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("猫武士/添加物品
|
|
715
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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
|
-
|
|
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 `修改物品成功!`;
|
|
1163
|
+
updates.createdAt = /* @__PURE__ */ new Date();
|
|
1164
|
+
await ctx.database.set("maple_warriors_items", { name: itemName }, updates);
|
|
1165
|
+
return `修改物品"${itemName}"成功!`;
|
|
776
1166
|
} else {
|
|
777
|
-
|
|
1167
|
+
const newItem = {
|
|
778
1168
|
name: itemName,
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
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
|
|
787
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
800
|
-
let output =
|
|
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
|
-
|
|
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 += `输入"
|
|
1309
|
+
output += `输入"物品列表${typeFilter ? " " + typeFilter : ""} ${pageNum - 1}"查看上一页
|
|
811
1310
|
`;
|
|
812
1311
|
}
|
|
813
1312
|
if (pageNum < totalPages) {
|
|
814
|
-
output += `输入"
|
|
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 =
|
|
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 =
|
|
1364
|
+
const opponentStatusCheck = checkInstructionStatus(player2, "战斗训练");
|
|
848
1365
|
if (!opponentStatusCheck.canAct) {
|
|
849
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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({
|