koishi-plugin-maple-warriors 0.0.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.d.ts +47 -0
- package/lib/index.js +1017 -0
- package/package.json +18 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const using: readonly ["database"];
|
|
3
|
+
declare module 'koishi' {
|
|
4
|
+
interface Tables {
|
|
5
|
+
maple_warriors: MapleWarrior;
|
|
6
|
+
maple_warriors_items: MapleItem;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export interface MapleWarrior {
|
|
10
|
+
id: number;
|
|
11
|
+
userId: string;
|
|
12
|
+
name: string;
|
|
13
|
+
level: number;
|
|
14
|
+
constitution: number;
|
|
15
|
+
attack: number;
|
|
16
|
+
toughness: number;
|
|
17
|
+
crit: number;
|
|
18
|
+
agility: number;
|
|
19
|
+
currentHp: number;
|
|
20
|
+
maxHp: number;
|
|
21
|
+
status: string;
|
|
22
|
+
previousStatus: string;
|
|
23
|
+
statusEndTime: Date | null;
|
|
24
|
+
items: string;
|
|
25
|
+
lastTrainDate: string;
|
|
26
|
+
lastHuntDate: string;
|
|
27
|
+
updatedAt: Date;
|
|
28
|
+
}
|
|
29
|
+
export interface MapleItem {
|
|
30
|
+
id: number;
|
|
31
|
+
name: string;
|
|
32
|
+
hpBonus: number;
|
|
33
|
+
constitutionBonus: number;
|
|
34
|
+
attackBonus: number;
|
|
35
|
+
toughnessBonus: number;
|
|
36
|
+
critBonus: number;
|
|
37
|
+
agilityBonus: number;
|
|
38
|
+
description: string;
|
|
39
|
+
createdAt: Date;
|
|
40
|
+
}
|
|
41
|
+
export declare const name = "maple-warriors";
|
|
42
|
+
export interface Config {
|
|
43
|
+
preyData: string[];
|
|
44
|
+
battleMessageInterval: number;
|
|
45
|
+
}
|
|
46
|
+
export declare const Config: Schema<Config>;
|
|
47
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
name: () => name,
|
|
26
|
+
using: () => using
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
var import_koishi = require("koishi");
|
|
30
|
+
var using = ["database"];
|
|
31
|
+
var name = "maple-warriors";
|
|
32
|
+
var DEFAULT_PREY_DATA = [
|
|
33
|
+
"老鼠 5 1 1 1 1 1-20",
|
|
34
|
+
"野兔 1 3-5 2-4 2-4 1-3 5-50"
|
|
35
|
+
];
|
|
36
|
+
var Config = import_koishi.Schema.object({
|
|
37
|
+
preyData: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default(DEFAULT_PREY_DATA).description("猎物数据,每行格式:猎物名 权重 体质 攻击 韧性 暴击 敏捷(权重是固定数值,属性可以是固定数值或范围,如1-10)"),
|
|
38
|
+
battleMessageInterval: import_koishi.Schema.number().min(1).max(5).default(2).description("战斗消息发送间隔(秒)")
|
|
39
|
+
});
|
|
40
|
+
function calculateRates(character) {
|
|
41
|
+
const resistance = (character.toughness / (character.toughness + 90)).toFixed(4);
|
|
42
|
+
const critRate = (character.crit / (character.crit + 90)).toFixed(4);
|
|
43
|
+
const chargeTime = (1 + 280 / (character.agility + 70)).toFixed(2);
|
|
44
|
+
const dodgeRate = (0.2 * character.agility / (character.agility + 90)).toFixed(4);
|
|
45
|
+
return { resistance, critRate, chargeTime, dodgeRate };
|
|
46
|
+
}
|
|
47
|
+
__name(calculateRates, "calculateRates");
|
|
48
|
+
var BattleManager = class {
|
|
49
|
+
static {
|
|
50
|
+
__name(this, "BattleManager");
|
|
51
|
+
}
|
|
52
|
+
battles = /* @__PURE__ */ new Map();
|
|
53
|
+
startBattle(channelId, playerHp, preyName) {
|
|
54
|
+
if (this.isBattling(channelId)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
this.battles.set(channelId, {
|
|
58
|
+
isBattling: true,
|
|
59
|
+
abortController: new AbortController(),
|
|
60
|
+
playerHp,
|
|
61
|
+
preyName
|
|
62
|
+
});
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
endBattle(channelId, finalPlayerHp) {
|
|
66
|
+
const battle = this.battles.get(channelId);
|
|
67
|
+
if (battle && battle.isBattling) {
|
|
68
|
+
if (battle.abortController) {
|
|
69
|
+
battle.abortController.abort();
|
|
70
|
+
}
|
|
71
|
+
if (finalPlayerHp !== void 0) {
|
|
72
|
+
battle.playerHp = finalPlayerHp;
|
|
73
|
+
}
|
|
74
|
+
this.battles.delete(channelId);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
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
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function parseAttributeRange(value) {
|
|
90
|
+
if (value.includes("-")) {
|
|
91
|
+
const [min, max] = value.split("-").map((v) => parseInt(v.trim()));
|
|
92
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
93
|
+
}
|
|
94
|
+
return parseInt(value) || 1;
|
|
95
|
+
}
|
|
96
|
+
__name(parseAttributeRange, "parseAttributeRange");
|
|
97
|
+
function parseItemsString(itemsStr) {
|
|
98
|
+
if (!itemsStr) return {};
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(itemsStr);
|
|
101
|
+
} catch {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
__name(parseItemsString, "parseItemsString");
|
|
106
|
+
function serializeItems(items) {
|
|
107
|
+
return JSON.stringify(items);
|
|
108
|
+
}
|
|
109
|
+
__name(serializeItems, "serializeItems");
|
|
110
|
+
function formatRemainingTime(endTime) {
|
|
111
|
+
const now = /* @__PURE__ */ new Date();
|
|
112
|
+
const diffMs = endTime.getTime() - now.getTime();
|
|
113
|
+
if (diffMs <= 0) return "0秒";
|
|
114
|
+
const hours = Math.floor(diffMs / (1e3 * 60 * 60));
|
|
115
|
+
const minutes = Math.floor(diffMs % (1e3 * 60 * 60) / (1e3 * 60));
|
|
116
|
+
const seconds = Math.floor(diffMs % (1e3 * 60) / 1e3);
|
|
117
|
+
const parts = [];
|
|
118
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
119
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
120
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
121
|
+
return parts.join("");
|
|
122
|
+
}
|
|
123
|
+
__name(formatRemainingTime, "formatRemainingTime");
|
|
124
|
+
function getTodayDateString() {
|
|
125
|
+
const now = /* @__PURE__ */ new Date();
|
|
126
|
+
return now.toISOString().split("T")[0];
|
|
127
|
+
}
|
|
128
|
+
__name(getTodayDateString, "getTodayDateString");
|
|
129
|
+
function isSameDay(dateString1, dateString2) {
|
|
130
|
+
return dateString1 === dateString2;
|
|
131
|
+
}
|
|
132
|
+
__name(isSameDay, "isSameDay");
|
|
133
|
+
function generateRandomAttributes() {
|
|
134
|
+
let points = 25 - 5;
|
|
135
|
+
const attributes = {
|
|
136
|
+
constitution: 1,
|
|
137
|
+
attack: 1,
|
|
138
|
+
toughness: 1,
|
|
139
|
+
crit: 1,
|
|
140
|
+
agility: 1
|
|
141
|
+
};
|
|
142
|
+
const keys = ["constitution", "attack", "toughness", "crit", "agility"];
|
|
143
|
+
for (let i = 0; i < points; i++) {
|
|
144
|
+
const randomAttr = keys[Math.floor(Math.random() * keys.length)];
|
|
145
|
+
attributes[randomAttr]++;
|
|
146
|
+
}
|
|
147
|
+
return attributes;
|
|
148
|
+
}
|
|
149
|
+
__name(generateRandomAttributes, "generateRandomAttributes");
|
|
150
|
+
function parsePreyData(preyData) {
|
|
151
|
+
const preyList = [];
|
|
152
|
+
for (const line of preyData) {
|
|
153
|
+
const parts = line.trim().split(/\s+/);
|
|
154
|
+
if (parts.length === 7) {
|
|
155
|
+
preyList.push({
|
|
156
|
+
name: parts[0],
|
|
157
|
+
weight: parseInt(parts[1]) || 1,
|
|
158
|
+
constitution: parts[2],
|
|
159
|
+
attack: parts[3],
|
|
160
|
+
toughness: parts[4],
|
|
161
|
+
crit: parts[5],
|
|
162
|
+
agility: parts[6]
|
|
163
|
+
});
|
|
164
|
+
} else {
|
|
165
|
+
console.error("猎物数据格式错误:", line);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return preyList;
|
|
169
|
+
}
|
|
170
|
+
__name(parsePreyData, "parsePreyData");
|
|
171
|
+
function selectRandomPrey(preyList) {
|
|
172
|
+
const totalWeight = preyList.reduce((sum, prey) => sum + prey.weight, 0);
|
|
173
|
+
let random = Math.random() * totalWeight;
|
|
174
|
+
for (const prey of preyList) {
|
|
175
|
+
random -= prey.weight;
|
|
176
|
+
if (random <= 0) {
|
|
177
|
+
return prey;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return preyList[0];
|
|
181
|
+
}
|
|
182
|
+
__name(selectRandomPrey, "selectRandomPrey");
|
|
183
|
+
function apply(ctx, config) {
|
|
184
|
+
const battleManager = new BattleManager();
|
|
185
|
+
ctx.model.extend("maple_warriors", {
|
|
186
|
+
id: "unsigned",
|
|
187
|
+
userId: "string",
|
|
188
|
+
name: "string",
|
|
189
|
+
level: "unsigned",
|
|
190
|
+
constitution: "unsigned",
|
|
191
|
+
attack: "unsigned",
|
|
192
|
+
toughness: "unsigned",
|
|
193
|
+
crit: "unsigned",
|
|
194
|
+
agility: "unsigned",
|
|
195
|
+
currentHp: "unsigned",
|
|
196
|
+
maxHp: "unsigned",
|
|
197
|
+
status: "string",
|
|
198
|
+
previousStatus: "string",
|
|
199
|
+
statusEndTime: "timestamp",
|
|
200
|
+
items: "text",
|
|
201
|
+
lastTrainDate: "string",
|
|
202
|
+
lastHuntDate: "string",
|
|
203
|
+
updatedAt: "timestamp"
|
|
204
|
+
}, {
|
|
205
|
+
primary: "id",
|
|
206
|
+
autoInc: true
|
|
207
|
+
});
|
|
208
|
+
ctx.model.extend("maple_warriors_items", {
|
|
209
|
+
id: "unsigned",
|
|
210
|
+
name: "string",
|
|
211
|
+
hpBonus: "integer",
|
|
212
|
+
constitutionBonus: "integer",
|
|
213
|
+
attackBonus: "integer",
|
|
214
|
+
toughnessBonus: "integer",
|
|
215
|
+
critBonus: "integer",
|
|
216
|
+
agilityBonus: "integer",
|
|
217
|
+
description: "string",
|
|
218
|
+
createdAt: "timestamp"
|
|
219
|
+
}, {
|
|
220
|
+
primary: "id",
|
|
221
|
+
autoInc: true,
|
|
222
|
+
unique: ["name"]
|
|
223
|
+
});
|
|
224
|
+
async function formatCharacterInfo(character) {
|
|
225
|
+
const rates = calculateRates({
|
|
226
|
+
toughness: character.toughness,
|
|
227
|
+
crit: character.crit,
|
|
228
|
+
agility: character.agility
|
|
229
|
+
});
|
|
230
|
+
const items = parseItemsString(character.items);
|
|
231
|
+
const itemsText = Object.entries(items).map(([name2, count]) => `${name2}*${count}`).join("\n");
|
|
232
|
+
return `名字: ${character.name}
|
|
233
|
+
等级: ${character.level}
|
|
234
|
+
体质: ${character.constitution} (HP: ${character.currentHp}/${character.maxHp})
|
|
235
|
+
攻击: ${character.attack}
|
|
236
|
+
韧性: ${character.toughness} (抗性: ${(parseFloat(rates.resistance) * 100).toFixed(2)}%)
|
|
237
|
+
暴击: ${character.crit} (暴击率: ${(parseFloat(rates.critRate) * 100).toFixed(2)}%)
|
|
238
|
+
敏捷: ${character.agility} (蓄力: ${rates.chargeTime}s 闪避率: ${(parseFloat(rates.dodgeRate) * 100).toFixed(2)}%)
|
|
239
|
+
状态: ${character.status}${character.statusEndTime ? " 剩余" + formatRemainingTime(character.statusEndTime) : ""}
|
|
240
|
+
──────────
|
|
241
|
+
物品栏:
|
|
242
|
+
${itemsText || "空空如也"}`;
|
|
243
|
+
}
|
|
244
|
+
__name(formatCharacterInfo, "formatCharacterInfo");
|
|
245
|
+
async function getOrCreateCharacter(userId, name2) {
|
|
246
|
+
let characters = await ctx.database.get("maple_warriors", { userId });
|
|
247
|
+
if (characters.length === 0) {
|
|
248
|
+
const attributes = generateRandomAttributes();
|
|
249
|
+
const maxHp = attributes.constitution * 5;
|
|
250
|
+
const character = {
|
|
251
|
+
userId,
|
|
252
|
+
name: name2 || '请输入"修改名字 角色名"修改',
|
|
253
|
+
level: 1,
|
|
254
|
+
constitution: attributes.constitution,
|
|
255
|
+
attack: attributes.attack,
|
|
256
|
+
toughness: attributes.toughness,
|
|
257
|
+
crit: attributes.crit,
|
|
258
|
+
agility: attributes.agility,
|
|
259
|
+
currentHp: maxHp,
|
|
260
|
+
maxHp,
|
|
261
|
+
status: "无",
|
|
262
|
+
previousStatus: "无",
|
|
263
|
+
statusEndTime: null,
|
|
264
|
+
items: "{}",
|
|
265
|
+
lastTrainDate: "",
|
|
266
|
+
lastHuntDate: "",
|
|
267
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
268
|
+
};
|
|
269
|
+
await ctx.database.create("maple_warriors", character);
|
|
270
|
+
characters = await ctx.database.get("maple_warriors", { userId });
|
|
271
|
+
}
|
|
272
|
+
return characters[0];
|
|
273
|
+
}
|
|
274
|
+
__name(getOrCreateCharacter, "getOrCreateCharacter");
|
|
275
|
+
function checkCharacterStatus(character) {
|
|
276
|
+
const now = /* @__PURE__ */ new Date();
|
|
277
|
+
if (character.statusEndTime && now >= character.statusEndTime) {
|
|
278
|
+
const updates = {
|
|
279
|
+
status: "无",
|
|
280
|
+
previousStatus: "无",
|
|
281
|
+
statusEndTime: null,
|
|
282
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
283
|
+
};
|
|
284
|
+
if (character.status === "睡眠" || character.status === "治疗") {
|
|
285
|
+
updates.currentHp = character.maxHp;
|
|
286
|
+
}
|
|
287
|
+
ctx.database.set("maple_warriors", { id: character.id }, updates);
|
|
288
|
+
return { canAct: true, message: "" };
|
|
289
|
+
}
|
|
290
|
+
if (character.status === "受伤") {
|
|
291
|
+
return { canAct: false, message: '你受伤了,快输入"治疗"吧!' };
|
|
292
|
+
}
|
|
293
|
+
if (character.status === "睡眠") {
|
|
294
|
+
return { canAct: false, message: "你想在梦里做这个吗?" };
|
|
295
|
+
}
|
|
296
|
+
if (character.status === "治疗") {
|
|
297
|
+
return { canAct: false, message: "正在治疗中,请好好养伤!" };
|
|
298
|
+
}
|
|
299
|
+
return { canAct: true, message: "" };
|
|
300
|
+
}
|
|
301
|
+
__name(checkCharacterStatus, "checkCharacterStatus");
|
|
302
|
+
ctx.command("猫武士", "猫武士插件 - 帮助菜单");
|
|
303
|
+
ctx.command("猫武士/修改名字 <name>", "修改角色名字").example("修改名字 火焰猫").example("修改名字 闪电虎").action(async ({ session }, name2) => {
|
|
304
|
+
if (!name2 || name2.trim().length === 0) {
|
|
305
|
+
return "格式错误!正确格式:修改名字 角色名";
|
|
306
|
+
}
|
|
307
|
+
const userId = session.userId;
|
|
308
|
+
const trimmedName = name2.trim();
|
|
309
|
+
let characters = await ctx.database.get("maple_warriors", { userId });
|
|
310
|
+
if (characters.length === 0) {
|
|
311
|
+
const attributes = generateRandomAttributes();
|
|
312
|
+
const maxHp = attributes.constitution * 5;
|
|
313
|
+
const character = {
|
|
314
|
+
userId,
|
|
315
|
+
name: trimmedName,
|
|
316
|
+
level: 1,
|
|
317
|
+
constitution: attributes.constitution,
|
|
318
|
+
attack: attributes.attack,
|
|
319
|
+
toughness: attributes.toughness,
|
|
320
|
+
crit: attributes.crit,
|
|
321
|
+
agility: attributes.agility,
|
|
322
|
+
currentHp: maxHp,
|
|
323
|
+
maxHp,
|
|
324
|
+
status: "无",
|
|
325
|
+
previousStatus: "无",
|
|
326
|
+
statusEndTime: null,
|
|
327
|
+
items: "{}",
|
|
328
|
+
lastTrainDate: "",
|
|
329
|
+
lastHuntDate: "",
|
|
330
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
331
|
+
};
|
|
332
|
+
await ctx.database.create("maple_warriors", character);
|
|
333
|
+
} else {
|
|
334
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
335
|
+
name: trimmedName,
|
|
336
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return "修改成功!";
|
|
340
|
+
});
|
|
341
|
+
ctx.command("猫武士/我的角色", "查看我的角色信息").example("我的角色").action(async ({ session }) => {
|
|
342
|
+
const userId = session.userId;
|
|
343
|
+
let character = await getOrCreateCharacter(userId);
|
|
344
|
+
return await formatCharacterInfo(character);
|
|
345
|
+
});
|
|
346
|
+
ctx.command("猫武士/训练 <attrs>", "训练提升属性").example("训练 体质 体质 攻击").example("训练 体质").example("训练 随机").action(async ({ session }, attrs) => {
|
|
347
|
+
const userId = session.userId;
|
|
348
|
+
let character = await getOrCreateCharacter(userId);
|
|
349
|
+
const statusCheck = checkCharacterStatus(character);
|
|
350
|
+
if (!statusCheck.canAct) {
|
|
351
|
+
return statusCheck.message;
|
|
352
|
+
}
|
|
353
|
+
const today = getTodayDateString();
|
|
354
|
+
if (character.lastTrainDate && isSameDay(character.lastTrainDate, today)) {
|
|
355
|
+
return "今天已经训练过了,要注意劳逸结合哦!";
|
|
356
|
+
}
|
|
357
|
+
if (!attrs) {
|
|
358
|
+
return "格式错误!正确格式:训练 属性1 属性2 属性3(可重复,若只指定一项,则其余两项随机提升)或 训练 随机";
|
|
359
|
+
}
|
|
360
|
+
const attrList = attrs.trim().split(/\s+/);
|
|
361
|
+
if (attrList.length === 1 && attrList[0] === "随机") {
|
|
362
|
+
const validAttrs2 = ["体质", "攻击", "韧性", "暴击", "敏捷"];
|
|
363
|
+
const attrMap2 = {
|
|
364
|
+
"体质": "constitution",
|
|
365
|
+
"攻击": "attack",
|
|
366
|
+
"韧性": "toughness",
|
|
367
|
+
"暴击": "crit",
|
|
368
|
+
"敏捷": "agility"
|
|
369
|
+
};
|
|
370
|
+
const updates2 = {};
|
|
371
|
+
const attributeCounts2 = {};
|
|
372
|
+
for (let i = 0; i < 3; i++) {
|
|
373
|
+
const randomAttr = validAttrs2[Math.floor(Math.random() * validAttrs2.length)];
|
|
374
|
+
const field = attrMap2[randomAttr];
|
|
375
|
+
updates2[field] = (updates2[field] || character[field] || 0) + 1;
|
|
376
|
+
attributeCounts2[randomAttr] = (attributeCounts2[randomAttr] || 0) + 1;
|
|
377
|
+
}
|
|
378
|
+
const changes2 = [];
|
|
379
|
+
for (const [attr, count] of Object.entries(attributeCounts2)) {
|
|
380
|
+
changes2.push(`${attr}+${count}`);
|
|
381
|
+
}
|
|
382
|
+
updates2.lastTrainDate = today;
|
|
383
|
+
updates2.updatedAt = /* @__PURE__ */ new Date();
|
|
384
|
+
await ctx.database.set("maple_warriors", { userId }, updates2);
|
|
385
|
+
return `训练成功!你好像领悟了什么……
|
|
386
|
+
${changes2.join(" ")}`;
|
|
387
|
+
}
|
|
388
|
+
if (attrList.length > 3) {
|
|
389
|
+
return "只能指定3个属性(可以重复),不要太贪心哦!";
|
|
390
|
+
}
|
|
391
|
+
const validAttrs = ["体质", "攻击", "韧性", "暴击", "敏捷"];
|
|
392
|
+
const attrMap = {
|
|
393
|
+
"体质": "constitution",
|
|
394
|
+
"攻击": "attack",
|
|
395
|
+
"韧性": "toughness",
|
|
396
|
+
"暴击": "crit",
|
|
397
|
+
"敏捷": "agility"
|
|
398
|
+
};
|
|
399
|
+
for (const attr of attrList) {
|
|
400
|
+
if (!validAttrs.includes(attr)) {
|
|
401
|
+
return `属性名错误!有效的属性有:${validAttrs.join("、")}`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const updates = {};
|
|
405
|
+
const attributeCounts = {};
|
|
406
|
+
for (const attr of attrList) {
|
|
407
|
+
const field = attrMap[attr];
|
|
408
|
+
updates[field] = (updates[field] || character[field] || 0) + 1;
|
|
409
|
+
attributeCounts[attr] = (attributeCounts[attr] || 0) + 1;
|
|
410
|
+
}
|
|
411
|
+
for (let i = 0; i < 3 - attrList.length; i++) {
|
|
412
|
+
const randomAttr = validAttrs[Math.floor(Math.random() * validAttrs.length)];
|
|
413
|
+
const field = attrMap[randomAttr];
|
|
414
|
+
updates[field] = (updates[field] || character[field] || 0) + 1;
|
|
415
|
+
attributeCounts[randomAttr] = (attributeCounts[randomAttr] || 0) + 1;
|
|
416
|
+
}
|
|
417
|
+
const changes = [];
|
|
418
|
+
for (const [attr, count] of Object.entries(attributeCounts)) {
|
|
419
|
+
changes.push(`${attr}+${count}`);
|
|
420
|
+
}
|
|
421
|
+
updates.lastTrainDate = today;
|
|
422
|
+
updates.updatedAt = /* @__PURE__ */ new Date();
|
|
423
|
+
await ctx.database.set("maple_warriors", { userId }, updates);
|
|
424
|
+
return `训练成功!你好像领悟了什么……
|
|
425
|
+
${changes.join(" ")}`;
|
|
426
|
+
});
|
|
427
|
+
ctx.command("猫武士/捕猎", "捕猎随机猎物").example("捕猎").action(async ({ session }) => {
|
|
428
|
+
const channelId = session.channelId;
|
|
429
|
+
const userId = session.userId;
|
|
430
|
+
if (battleManager.isBattling(channelId)) {
|
|
431
|
+
return "请等待当前战斗结束!";
|
|
432
|
+
}
|
|
433
|
+
let character = await getOrCreateCharacter(userId);
|
|
434
|
+
const statusCheck = checkCharacterStatus(character);
|
|
435
|
+
if (!statusCheck.canAct) {
|
|
436
|
+
return statusCheck.message;
|
|
437
|
+
}
|
|
438
|
+
const today = getTodayDateString();
|
|
439
|
+
if (character.lastHuntDate && isSameDay(character.lastHuntDate, today)) {
|
|
440
|
+
return "今天已经捕猎过了,要注意劳逸结合哦!";
|
|
441
|
+
}
|
|
442
|
+
const preyList = parsePreyData(config.preyData);
|
|
443
|
+
if (preyList.length === 0) {
|
|
444
|
+
return "未找到猎物数据,请在控制台配置猎物数据。";
|
|
445
|
+
}
|
|
446
|
+
const selectedPrey = selectRandomPrey(preyList);
|
|
447
|
+
const constitution = parseAttributeRange(selectedPrey.constitution);
|
|
448
|
+
const attack = parseAttributeRange(selectedPrey.attack);
|
|
449
|
+
const toughness = parseAttributeRange(selectedPrey.toughness);
|
|
450
|
+
const crit = parseAttributeRange(selectedPrey.crit);
|
|
451
|
+
const agility = parseAttributeRange(selectedPrey.agility);
|
|
452
|
+
const preyHp = constitution * 5;
|
|
453
|
+
const chargeTime = parseFloat((1 + 280 / (agility + 70)).toFixed(2));
|
|
454
|
+
const prey = {
|
|
455
|
+
name: selectedPrey.name,
|
|
456
|
+
constitution,
|
|
457
|
+
attack,
|
|
458
|
+
toughness,
|
|
459
|
+
crit,
|
|
460
|
+
agility,
|
|
461
|
+
hp: preyHp,
|
|
462
|
+
maxHp: preyHp,
|
|
463
|
+
chargeTime,
|
|
464
|
+
nextAttackTime: chargeTime
|
|
465
|
+
};
|
|
466
|
+
if (!battleManager.startBattle(channelId, character.currentHp, selectedPrey.name)) {
|
|
467
|
+
return "战斗开始失败,请重试!";
|
|
468
|
+
}
|
|
469
|
+
const preyRates = calculateRates({
|
|
470
|
+
toughness: prey.toughness,
|
|
471
|
+
crit: prey.crit,
|
|
472
|
+
agility: prey.agility
|
|
473
|
+
});
|
|
474
|
+
await session.send(`发现猎物: ${prey.name}
|
|
475
|
+
体质: ${prey.constitution} (HP: ${prey.hp})
|
|
476
|
+
攻击: ${prey.attack}
|
|
477
|
+
韧性: ${prey.toughness} (抗性: ${(parseFloat(preyRates.resistance) * 100).toFixed(2)}%)
|
|
478
|
+
暴击: ${prey.crit} (暴击率: ${(parseFloat(preyRates.critRate) * 100).toFixed(2)}%)
|
|
479
|
+
敏捷: ${prey.agility} (蓄力: ${preyRates.chargeTime}s 闪避率: ${(parseFloat(preyRates.dodgeRate) * 100).toFixed(2)}%)`);
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
481
|
+
const abortController = battleManager.getAbortController(channelId);
|
|
482
|
+
try {
|
|
483
|
+
const player = {
|
|
484
|
+
name: character.name,
|
|
485
|
+
constitution: character.constitution,
|
|
486
|
+
attack: character.attack,
|
|
487
|
+
toughness: character.toughness,
|
|
488
|
+
crit: character.crit,
|
|
489
|
+
agility: character.agility,
|
|
490
|
+
hp: character.currentHp,
|
|
491
|
+
maxHp: character.maxHp,
|
|
492
|
+
chargeTime: parseFloat((1 + 280 / (character.agility + 70)).toFixed(2)),
|
|
493
|
+
nextAttackTime: 0
|
|
494
|
+
};
|
|
495
|
+
const today2 = getTodayDateString();
|
|
496
|
+
const updates = {
|
|
497
|
+
lastHuntDate: today2,
|
|
498
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
499
|
+
};
|
|
500
|
+
if (player.hp <= 0) {
|
|
501
|
+
updates.status = "受伤";
|
|
502
|
+
updates.previousStatus = "无";
|
|
503
|
+
updates.statusEndTime = null;
|
|
504
|
+
updates.currentHp = 0;
|
|
505
|
+
} else {
|
|
506
|
+
updates.currentHp = player.hp;
|
|
507
|
+
if (prey.hp <= 0) {
|
|
508
|
+
const items = parseItemsString(character.items);
|
|
509
|
+
items[selectedPrey.name] = (items[selectedPrey.name] || 0) + 1;
|
|
510
|
+
updates.items = serializeItems(items);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
await ctx.database.set("maple_warriors", { userId }, updates);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (error.name === "AbortError") {
|
|
516
|
+
const today2 = getTodayDateString();
|
|
517
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
518
|
+
lastHuntDate: today2,
|
|
519
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
} finally {
|
|
525
|
+
battleManager.endBattle(channelId);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
ctx.command("猫武士/使用物品 <itemInput>", "使用物品栏中的物品").example("使用物品 老鼠").example("使用物品 老鼠 2").example("使用物品 老鼠*2").action(async ({ session }, itemInput) => {
|
|
529
|
+
if (!itemInput) {
|
|
530
|
+
return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
|
|
531
|
+
}
|
|
532
|
+
const userId = session.userId;
|
|
533
|
+
let character = await getOrCreateCharacter(userId);
|
|
534
|
+
const statusCheck = checkCharacterStatus(character);
|
|
535
|
+
if (!statusCheck.canAct) {
|
|
536
|
+
return statusCheck.message;
|
|
537
|
+
}
|
|
538
|
+
let itemName = "";
|
|
539
|
+
let count = 1;
|
|
540
|
+
if (itemInput.includes("*")) {
|
|
541
|
+
const parts = itemInput.split("*");
|
|
542
|
+
itemName = parts[0].trim();
|
|
543
|
+
count = parseInt(parts[1]) || 1;
|
|
544
|
+
} else {
|
|
545
|
+
const parts = itemInput.trim().split(/\s+/);
|
|
546
|
+
itemName = parts[0];
|
|
547
|
+
if (parts[1]) {
|
|
548
|
+
count = parseInt(parts[1]) || 1;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (!itemName) {
|
|
552
|
+
return "格式错误!正确格式:使用物品 物品名 数量 或 使用物品 物品名*数量";
|
|
553
|
+
}
|
|
554
|
+
const items = parseItemsString(character.items);
|
|
555
|
+
if (!items[itemName] || items[itemName] < count) {
|
|
556
|
+
return `使用失败!${!items[itemName] ? `梦里才有${itemName}吧……` : `没有那么多${itemName}!`}`;
|
|
557
|
+
}
|
|
558
|
+
const itemInfo = await ctx.database.get("maple_warriors_items", { name: itemName });
|
|
559
|
+
const item = itemInfo.length > 0 ? itemInfo[0] : null;
|
|
560
|
+
items[itemName] -= count;
|
|
561
|
+
if (items[itemName] <= 0) {
|
|
562
|
+
delete items[itemName];
|
|
563
|
+
}
|
|
564
|
+
const updates = {
|
|
565
|
+
items: serializeItems(items),
|
|
566
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
567
|
+
};
|
|
568
|
+
const changes = [];
|
|
569
|
+
if (item) {
|
|
570
|
+
if (item.hpBonus) {
|
|
571
|
+
let newHp = character.currentHp + item.hpBonus * count;
|
|
572
|
+
if (newHp > character.maxHp) newHp = character.maxHp;
|
|
573
|
+
if (newHp < 0) newHp = 0;
|
|
574
|
+
updates.currentHp = newHp;
|
|
575
|
+
changes.push(`HP+${item.hpBonus * count}`);
|
|
576
|
+
}
|
|
577
|
+
if (item.constitutionBonus) {
|
|
578
|
+
updates.constitution = character.constitution + item.constitutionBonus * count;
|
|
579
|
+
changes.push(`体质+${item.constitutionBonus * count}`);
|
|
580
|
+
}
|
|
581
|
+
if (item.attackBonus) {
|
|
582
|
+
updates.attack = character.attack + item.attackBonus * count;
|
|
583
|
+
changes.push(`攻击+${item.attackBonus * count}`);
|
|
584
|
+
}
|
|
585
|
+
if (item.toughnessBonus) {
|
|
586
|
+
updates.toughness = character.toughness + item.toughnessBonus * count;
|
|
587
|
+
changes.push(`韧性+${item.toughnessBonus * count}`);
|
|
588
|
+
}
|
|
589
|
+
if (item.critBonus) {
|
|
590
|
+
updates.crit = character.crit + item.critBonus * count;
|
|
591
|
+
changes.push(`暴击+${item.critBonus * count}`);
|
|
592
|
+
}
|
|
593
|
+
if (item.agilityBonus) {
|
|
594
|
+
updates.agility = character.agility + item.agilityBonus * count;
|
|
595
|
+
changes.push(`敏捷+${item.agilityBonus * count}`);
|
|
596
|
+
}
|
|
597
|
+
if (updates.constitution) {
|
|
598
|
+
updates.maxHp = updates.constitution * 5;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
await ctx.database.set("maple_warriors", { userId }, updates);
|
|
602
|
+
const changesText = changes.length > 0 ? "\n" + changes.join(" ") : "";
|
|
603
|
+
return `使用成功!消耗了${itemName}*${count}${changesText}`;
|
|
604
|
+
});
|
|
605
|
+
ctx.command("猫武士/睡眠", "让角色进入睡眠状态").example("睡眠").action(async ({ session }) => {
|
|
606
|
+
const userId = session.userId;
|
|
607
|
+
let character = await getOrCreateCharacter(userId);
|
|
608
|
+
const statusCheck = checkCharacterStatus(character);
|
|
609
|
+
if (!statusCheck.canAct) {
|
|
610
|
+
return statusCheck.message;
|
|
611
|
+
}
|
|
612
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
613
|
+
endTime.setHours(endTime.getHours() + 4);
|
|
614
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
615
|
+
status: "睡眠",
|
|
616
|
+
previousStatus: character.status,
|
|
617
|
+
// 保存之前的状态
|
|
618
|
+
statusEndTime: endTime,
|
|
619
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
620
|
+
});
|
|
621
|
+
return `进入睡眠状态,剩余4h0m0s`;
|
|
622
|
+
});
|
|
623
|
+
ctx.command("猫武士/治疗", "治疗受伤的角色").example("治疗").action(async ({ session }) => {
|
|
624
|
+
const userId = session.userId;
|
|
625
|
+
let character = await getOrCreateCharacter(userId);
|
|
626
|
+
if (character.currentHp > 0 && character.status !== "受伤") {
|
|
627
|
+
return "你的状态良好,无需治疗。";
|
|
628
|
+
}
|
|
629
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
630
|
+
endTime.setHours(endTime.getHours() + 4);
|
|
631
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
632
|
+
status: "治疗",
|
|
633
|
+
previousStatus: character.status,
|
|
634
|
+
// 保存之前的状态
|
|
635
|
+
statusEndTime: endTime,
|
|
636
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
637
|
+
});
|
|
638
|
+
return `进入治疗状态,剩余4h0m0s`;
|
|
639
|
+
});
|
|
640
|
+
ctx.command("猫武士/结束 <action>", "结束当前状态").example("结束 战斗").example("结束 睡眠").example("结束 治疗").action(async ({ session }, action) => {
|
|
641
|
+
if (!action) {
|
|
642
|
+
return "格式错误!正确格式:结束 战斗/睡眠/治疗";
|
|
643
|
+
}
|
|
644
|
+
const userId = session.userId;
|
|
645
|
+
const channelId = session.channelId;
|
|
646
|
+
switch (action) {
|
|
647
|
+
case "战斗":
|
|
648
|
+
if (battleManager.isBattling(channelId)) {
|
|
649
|
+
const battleData = battleManager.getBattleData(channelId);
|
|
650
|
+
if (battleData && battleData.playerHp !== void 0 && !battleData.isTraining) {
|
|
651
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
652
|
+
currentHp: battleData.playerHp,
|
|
653
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
battleManager.endBattle(channelId);
|
|
657
|
+
} else {
|
|
658
|
+
return "当前没有正在进行的战斗。";
|
|
659
|
+
}
|
|
660
|
+
case "睡眠":
|
|
661
|
+
case "治疗":
|
|
662
|
+
let character = await getOrCreateCharacter(userId);
|
|
663
|
+
if (character.status === action) {
|
|
664
|
+
await ctx.database.set("maple_warriors", { userId }, {
|
|
665
|
+
status: character.previousStatus || "无",
|
|
666
|
+
previousStatus: "无",
|
|
667
|
+
statusEndTime: null,
|
|
668
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
669
|
+
});
|
|
670
|
+
return `结束了当前的${action}`;
|
|
671
|
+
} else {
|
|
672
|
+
return `当前没有处于${action}状态。`;
|
|
673
|
+
}
|
|
674
|
+
default:
|
|
675
|
+
return "格式错误!正确格式:结束 战斗/睡眠/治疗";
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
ctx.command("猫武士/查询物品 <itemName>", "查询物品信息").example("查询物品 老鼠").action(async ({ session }, itemName) => {
|
|
679
|
+
if (!itemName) {
|
|
680
|
+
return "格式错误!正确格式:查询物品 物品名";
|
|
681
|
+
}
|
|
682
|
+
const items = await ctx.database.get("maple_warriors_items", { name: itemName });
|
|
683
|
+
if (items.length === 0) {
|
|
684
|
+
return `物品表中没有${itemName}!`;
|
|
685
|
+
}
|
|
686
|
+
const item = items[0];
|
|
687
|
+
const bonuses = [];
|
|
688
|
+
if (item.hpBonus) bonuses.push(`HP+${item.hpBonus}`);
|
|
689
|
+
if (item.constitutionBonus) bonuses.push(`体质+${item.constitutionBonus}`);
|
|
690
|
+
if (item.attackBonus) bonuses.push(`攻击+${item.attackBonus}`);
|
|
691
|
+
if (item.toughnessBonus) bonuses.push(`韧性+${item.toughnessBonus}`);
|
|
692
|
+
if (item.critBonus) bonuses.push(`暴击+${item.critBonus}`);
|
|
693
|
+
if (item.agilityBonus) bonuses.push(`敏捷+${item.agilityBonus}`);
|
|
694
|
+
let result = `物品: ${item.name}
|
|
695
|
+
`;
|
|
696
|
+
if (bonuses.length > 0) {
|
|
697
|
+
result += bonuses.join(" ") + "\n";
|
|
698
|
+
}
|
|
699
|
+
if (item.description) {
|
|
700
|
+
result += `"${item.description}"`;
|
|
701
|
+
}
|
|
702
|
+
return result.trim();
|
|
703
|
+
});
|
|
704
|
+
ctx.command("猫武士/添加物品 <...args>", "添加或修改物品").example("添加物品 老鼠 体质 1 很常见的老鼠").example("添加物品 治疗药水 hp 10 恢复生命值").action(async ({ session }, ...args) => {
|
|
705
|
+
if (args.length < 1) {
|
|
706
|
+
return "格式错误!正确格式:添加物品 物品名 [属性 增加数 ...] [介绍]";
|
|
707
|
+
}
|
|
708
|
+
let itemName = args[0];
|
|
709
|
+
let description = "";
|
|
710
|
+
const updates = {
|
|
711
|
+
hpBonus: 0,
|
|
712
|
+
constitutionBonus: 0,
|
|
713
|
+
attackBonus: 0,
|
|
714
|
+
toughnessBonus: 0,
|
|
715
|
+
critBonus: 0,
|
|
716
|
+
agilityBonus: 0
|
|
717
|
+
};
|
|
718
|
+
let i = 1;
|
|
719
|
+
const validAttrs = ["HP", "hp", "体质", "攻击", "韧性", "暴击", "敏捷"];
|
|
720
|
+
const attrMap = {
|
|
721
|
+
"HP": "hpBonus",
|
|
722
|
+
"hp": "hpBonus",
|
|
723
|
+
"体质": "constitutionBonus",
|
|
724
|
+
"攻击": "attackBonus",
|
|
725
|
+
"韧性": "toughnessBonus",
|
|
726
|
+
"暴击": "critBonus",
|
|
727
|
+
"敏捷": "agilityBonus"
|
|
728
|
+
};
|
|
729
|
+
while (i < args.length) {
|
|
730
|
+
const arg = args[i];
|
|
731
|
+
const normalizedAttr = arg.toLowerCase() === "hp" ? "HP" : arg;
|
|
732
|
+
if (!validAttrs.includes(normalizedAttr)) {
|
|
733
|
+
description = args.slice(i).join(" ");
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
if (i + 1 >= args.length) {
|
|
737
|
+
return `格式错误!属性"${arg}"缺少数值`;
|
|
738
|
+
}
|
|
739
|
+
const nextArg = args[i + 1];
|
|
740
|
+
const value = parseInt(nextArg);
|
|
741
|
+
if (isNaN(value)) {
|
|
742
|
+
const nextNormalized = nextArg.toLowerCase() === "hp" ? "HP" : nextArg;
|
|
743
|
+
if (validAttrs.includes(nextNormalized)) {
|
|
744
|
+
return `格式错误!属性"${arg}"缺少数值`;
|
|
745
|
+
} else {
|
|
746
|
+
description = args.slice(i + 1).join(" ");
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const fieldName = attrMap[normalizedAttr];
|
|
751
|
+
if (fieldName) {
|
|
752
|
+
updates[fieldName] = value;
|
|
753
|
+
} else {
|
|
754
|
+
return `属性名错误!有效的属性有:${["HP", "体质", "攻击", "韧性", "暴击", "敏捷"].join("、")}`;
|
|
755
|
+
}
|
|
756
|
+
i += 2;
|
|
757
|
+
}
|
|
758
|
+
const existingItems = await ctx.database.get("maple_warriors_items", { name: itemName });
|
|
759
|
+
if (existingItems.length > 0) {
|
|
760
|
+
await ctx.database.set("maple_warriors_items", { name: itemName }, {
|
|
761
|
+
...updates,
|
|
762
|
+
description,
|
|
763
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
764
|
+
});
|
|
765
|
+
return `修改${itemName}成功!`;
|
|
766
|
+
} else {
|
|
767
|
+
await ctx.database.create("maple_warriors_items", {
|
|
768
|
+
name: itemName,
|
|
769
|
+
...updates,
|
|
770
|
+
description,
|
|
771
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
772
|
+
});
|
|
773
|
+
return `添加${itemName}成功!`;
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
ctx.command("猫武士/物品列表 [page]", "查看物品列表").example("物品列表").example("物品列表 2").action(async ({ session }, page = "1") => {
|
|
777
|
+
const pageNum = parseInt(page) || 1;
|
|
778
|
+
if (pageNum < 1) {
|
|
779
|
+
return "页码必须大于0!";
|
|
780
|
+
}
|
|
781
|
+
const allItems = await ctx.database.get("maple_warriors_items", {});
|
|
782
|
+
allItems.sort((a, b) => a.name.localeCompare(b.name));
|
|
783
|
+
const pageSize = 10;
|
|
784
|
+
const totalPages = Math.ceil(allItems.length / pageSize);
|
|
785
|
+
if (pageNum > totalPages && totalPages > 0) {
|
|
786
|
+
return `页码超出范围!总页数:${totalPages}`;
|
|
787
|
+
}
|
|
788
|
+
const startIndex = (pageNum - 1) * pageSize;
|
|
789
|
+
const pageItems = allItems.slice(startIndex, startIndex + pageSize);
|
|
790
|
+
let output = `物品列表 第 ${pageNum}/${totalPages} 页(共 ${allItems.length} 个物品)
|
|
791
|
+
|
|
792
|
+
`;
|
|
793
|
+
pageItems.forEach((item, index) => {
|
|
794
|
+
const displayIndex = startIndex + index + 1;
|
|
795
|
+
output += `${displayIndex}. ${item.name}
|
|
796
|
+
`;
|
|
797
|
+
});
|
|
798
|
+
output += "\n──────────\n";
|
|
799
|
+
if (pageNum > 1) {
|
|
800
|
+
output += `输入"物品列表 ${pageNum - 1}"查看上一页
|
|
801
|
+
`;
|
|
802
|
+
}
|
|
803
|
+
if (pageNum < totalPages) {
|
|
804
|
+
output += `输入"物品列表 ${pageNum + 1}"查看下一页
|
|
805
|
+
`;
|
|
806
|
+
}
|
|
807
|
+
output += `输入"查询物品 物品名"查看物品详情`;
|
|
808
|
+
return output.trim();
|
|
809
|
+
});
|
|
810
|
+
ctx.command("猫武士/战斗训练 <target:user>", "与指定用户的角色进行训练战斗(不消耗HP)").example("战斗训练 @火星").action(async ({ session }, target) => {
|
|
811
|
+
const channelId = session.channelId;
|
|
812
|
+
const userId = session.userId;
|
|
813
|
+
if (battleManager.isBattling(channelId)) {
|
|
814
|
+
return "请等待当前战斗结束!";
|
|
815
|
+
}
|
|
816
|
+
if (!target) {
|
|
817
|
+
return "请指定训练对手!正确格式:战斗训练 @用户名";
|
|
818
|
+
}
|
|
819
|
+
if (target === userId) {
|
|
820
|
+
return "不能和自己进行战斗训练哦!";
|
|
821
|
+
}
|
|
822
|
+
let player1 = await getOrCreateCharacter(userId);
|
|
823
|
+
const statusCheck = checkCharacterStatus(player1);
|
|
824
|
+
if (!statusCheck.canAct) {
|
|
825
|
+
return statusCheck.message;
|
|
826
|
+
}
|
|
827
|
+
let player2 = await getOrCreateCharacter(target);
|
|
828
|
+
const opponentStatusCheck = checkCharacterStatus(player2);
|
|
829
|
+
if (!opponentStatusCheck.canAct) {
|
|
830
|
+
return `对手目前状态不佳,无法进行战斗训练:${opponentStatusCheck.message}`;
|
|
831
|
+
}
|
|
832
|
+
if (!battleManager.startBattle(channelId, player1.currentHp, void 0)) {
|
|
833
|
+
return "战斗开始失败,请重试!";
|
|
834
|
+
}
|
|
835
|
+
const player1Char = {
|
|
836
|
+
name: player1.name,
|
|
837
|
+
constitution: player1.constitution,
|
|
838
|
+
attack: player1.attack,
|
|
839
|
+
toughness: player1.toughness,
|
|
840
|
+
crit: player1.crit,
|
|
841
|
+
agility: player1.agility,
|
|
842
|
+
hp: player1.currentHp,
|
|
843
|
+
// 使用当前HP
|
|
844
|
+
maxHp: player1.maxHp,
|
|
845
|
+
chargeTime: parseFloat((1 + 280 / (player1.agility + 70)).toFixed(2)),
|
|
846
|
+
nextAttackTime: 0
|
|
847
|
+
};
|
|
848
|
+
const player2Char = {
|
|
849
|
+
name: player2.name,
|
|
850
|
+
constitution: player2.constitution,
|
|
851
|
+
attack: player2.attack,
|
|
852
|
+
toughness: player2.toughness,
|
|
853
|
+
crit: player2.crit,
|
|
854
|
+
agility: player2.agility,
|
|
855
|
+
hp: player2.currentHp,
|
|
856
|
+
// 使用当前HP
|
|
857
|
+
maxHp: player2.maxHp,
|
|
858
|
+
chargeTime: parseFloat((1 + 280 / (player2.agility + 70)).toFixed(2)),
|
|
859
|
+
nextAttackTime: 0
|
|
860
|
+
};
|
|
861
|
+
const abortController = battleManager.getAbortController(channelId);
|
|
862
|
+
try {
|
|
863
|
+
await executeBattle(
|
|
864
|
+
session,
|
|
865
|
+
player1Char,
|
|
866
|
+
player2Char,
|
|
867
|
+
config.battleMessageInterval,
|
|
868
|
+
true,
|
|
869
|
+
// 是训练模式
|
|
870
|
+
abortController
|
|
871
|
+
);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (error.name !== "AbortError") {
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
} finally {
|
|
877
|
+
battleManager.endBattle(channelId);
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
setInterval(async () => {
|
|
881
|
+
try {
|
|
882
|
+
const now = /* @__PURE__ */ new Date();
|
|
883
|
+
const characters = await ctx.database.get("maple_warriors", {});
|
|
884
|
+
for (const character of characters) {
|
|
885
|
+
if (character.statusEndTime && now >= character.statusEndTime) {
|
|
886
|
+
const updates = {
|
|
887
|
+
status: "无",
|
|
888
|
+
previousStatus: "无",
|
|
889
|
+
statusEndTime: null,
|
|
890
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
891
|
+
};
|
|
892
|
+
if (character.status === "睡眠" || character.status === "治疗") {
|
|
893
|
+
updates.currentHp = character.maxHp;
|
|
894
|
+
}
|
|
895
|
+
await ctx.database.set("maple_warriors", { id: character.id }, updates);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} catch (error) {
|
|
899
|
+
console.error("状态检查错误:", error);
|
|
900
|
+
}
|
|
901
|
+
}, 6e4);
|
|
902
|
+
}
|
|
903
|
+
__name(apply, "apply");
|
|
904
|
+
async function executeBattle(session, player1, player2, battleMessageInterval, isTraining = false, abortController) {
|
|
905
|
+
const originalHp1 = player1.hp;
|
|
906
|
+
const originalHp2 = player2.hp;
|
|
907
|
+
const rates1 = calculateRates({
|
|
908
|
+
toughness: player1.toughness,
|
|
909
|
+
crit: player1.crit,
|
|
910
|
+
agility: player1.agility
|
|
911
|
+
});
|
|
912
|
+
const rates2 = calculateRates({
|
|
913
|
+
toughness: player2.toughness,
|
|
914
|
+
crit: player2.crit,
|
|
915
|
+
agility: player2.agility
|
|
916
|
+
});
|
|
917
|
+
const dodgeRate1 = parseFloat(rates1.dodgeRate);
|
|
918
|
+
const dodgeRate2 = parseFloat(rates2.dodgeRate);
|
|
919
|
+
const critRate1 = parseFloat(rates1.critRate);
|
|
920
|
+
const critRate2 = parseFloat(rates2.critRate);
|
|
921
|
+
player1.nextAttackTime = parseFloat(rates1.chargeTime);
|
|
922
|
+
player2.nextAttackTime = parseFloat(rates2.chargeTime);
|
|
923
|
+
let currentTime = 0;
|
|
924
|
+
const maxBattleTime = 180;
|
|
925
|
+
const battleType = isTraining ? "训练战斗" : "战斗";
|
|
926
|
+
await session.send(`${player1.name} vs ${player2.name},${battleType}开始!`);
|
|
927
|
+
try {
|
|
928
|
+
while (currentTime <= maxBattleTime && player1.hp > 0 && player2.hp > 0) {
|
|
929
|
+
if (abortController?.signal.aborted) {
|
|
930
|
+
await session.send("战斗被强制结束!");
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
await new Promise((resolve) => setTimeout(resolve, battleMessageInterval * 1e3));
|
|
934
|
+
let attacker;
|
|
935
|
+
let defender;
|
|
936
|
+
let attackTime = Math.min(player1.nextAttackTime, player2.nextAttackTime);
|
|
937
|
+
if (currentTime < attackTime) {
|
|
938
|
+
currentTime = attackTime;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (Math.abs(player1.nextAttackTime - attackTime) < 0.01) {
|
|
942
|
+
attacker = player1;
|
|
943
|
+
defender = player2;
|
|
944
|
+
player1.nextAttackTime = currentTime + parseFloat(rates1.chargeTime);
|
|
945
|
+
} else {
|
|
946
|
+
attacker = player2;
|
|
947
|
+
defender = player1;
|
|
948
|
+
player2.nextAttackTime = currentTime + parseFloat(rates2.chargeTime);
|
|
949
|
+
}
|
|
950
|
+
let battleMessage = `${attacker.name}发动攻击!`;
|
|
951
|
+
const defenderDodgeRate = defender === player1 ? dodgeRate1 : dodgeRate2;
|
|
952
|
+
if (Math.random() < defenderDodgeRate) {
|
|
953
|
+
battleMessage += ` ${defender.name}闪避了!`;
|
|
954
|
+
await session.send(battleMessage);
|
|
955
|
+
currentTime = attackTime;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const baseDamage = attacker.attack * (0.5 + Math.random());
|
|
959
|
+
let initialDamage = Math.ceil(baseDamage);
|
|
960
|
+
let totalDamage = initialDamage;
|
|
961
|
+
const attackerCritRate = attacker === player1 ? critRate1 : critRate2;
|
|
962
|
+
let isCritical = false;
|
|
963
|
+
if (Math.random() < attackerCritRate) {
|
|
964
|
+
totalDamage *= 2;
|
|
965
|
+
isCritical = true;
|
|
966
|
+
battleMessage += ` 暴击!`;
|
|
967
|
+
}
|
|
968
|
+
const reduceAmount = defender.toughness * Math.random();
|
|
969
|
+
const reducedDamage = Math.min(Math.ceil(reduceAmount), totalDamage);
|
|
970
|
+
const finalDamage = totalDamage - reducedDamage;
|
|
971
|
+
if (finalDamage <= 0) {
|
|
972
|
+
if (isCritical) {
|
|
973
|
+
battleMessage += ` 造成了${totalDamage}点伤害。`;
|
|
974
|
+
}
|
|
975
|
+
battleMessage += ` 被${defender.name}抵挡住了!`;
|
|
976
|
+
await session.send(battleMessage);
|
|
977
|
+
currentTime = attackTime;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
defender.hp -= finalDamage;
|
|
981
|
+
if (defender.hp < 0) defender.hp = 0;
|
|
982
|
+
if (isCritical) {
|
|
983
|
+
battleMessage += ` 造成了${totalDamage}点伤害。`;
|
|
984
|
+
} else {
|
|
985
|
+
battleMessage += ` 造成了${initialDamage}点伤害。`;
|
|
986
|
+
}
|
|
987
|
+
battleMessage += ` ${defender.name}抵挡了${reducedDamage}点伤害,实际造成${finalDamage}点伤害。${defender.name} HP: ${defender.hp}`;
|
|
988
|
+
await session.send(battleMessage);
|
|
989
|
+
if (defender.hp <= 0) {
|
|
990
|
+
await session.send(`战斗结束!${attacker.name} 胜利!`);
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
currentTime = attackTime;
|
|
994
|
+
}
|
|
995
|
+
if (currentTime > maxBattleTime && player1.hp > 0 && player2.hp > 0) {
|
|
996
|
+
if (player1.hp > player2.hp) {
|
|
997
|
+
await session.send(`战斗超时!${player1.name} 剩余HP更高,获得胜利!`);
|
|
998
|
+
} else if (player2.hp > player1.hp) {
|
|
999
|
+
await session.send(`战斗超时!${player2.name} 剩余HP更高,获得胜利!`);
|
|
1000
|
+
} else {
|
|
1001
|
+
await session.send(`战斗超时!双方平局!`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if (error.name !== "AbortError") {
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
__name(executeBattle, "executeBattle");
|
|
1011
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1012
|
+
0 && (module.exports = {
|
|
1013
|
+
Config,
|
|
1014
|
+
apply,
|
|
1015
|
+
name,
|
|
1016
|
+
using
|
|
1017
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-maple-warriors",
|
|
3
|
+
"description": "-",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"maple"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"koishi": "^4.18.7"
|
|
17
|
+
}
|
|
18
|
+
}
|