koishi-plugin-xxck-1 0.1.21 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib/index.js +125 -68
  2. package/package.json +2 -2
  3. package/readme.md +106 -1
package/lib/index.js CHANGED
@@ -61,7 +61,7 @@ function getToday() {
61
61
  __name(getToday, "getToday");
62
62
  async function _warehouse_draw_image(ctx, user_cards, show_name) {
63
63
  try {
64
- logger.info("开始绘制 WWC 仓库图片(去重+最高等级)");
64
+ logger.info("开始绘制 WWC 仓库图片(限定卡独立显示)");
65
65
  const STAR_COLOR = {
66
66
  7: "#FF00FF",
67
67
  6: "#C00000",
@@ -78,9 +78,28 @@ async function _warehouse_draw_image(ctx, user_cards, show_name) {
78
78
  const ROW_HEIGHT = 26;
79
79
  const PADDING = 20;
80
80
  const TITLE_HEIGHT = 50;
81
- const allCards = await ctx.database.get("gacha_card_pool", {});
81
+ const [normalCards, eventCards] = await Promise.all([
82
+ ctx.database.get("gacha_card_pool", {}),
83
+ ctx.database.get("gacha_event_pool", {})
84
+ // 读取所有限时卡池
85
+ ]);
82
86
  const cardPool = /* @__PURE__ */ new Map();
83
- for (const c of allCards) cardPool.set(c.card_id, c);
87
+ normalCards.forEach((c) => cardPool.set(c.card_id, {
88
+ name: c.card_name,
89
+ star: c.card_star,
90
+ isLimit: false
91
+ // 标记普通卡
92
+ }));
93
+ eventCards.forEach((c) => {
94
+ if (!cardPool.has(c.card_id)) {
95
+ cardPool.set(c.card_id, {
96
+ name: c.card_name,
97
+ star: c.card_star,
98
+ isLimit: true
99
+ // 标记限定卡
100
+ });
101
+ }
102
+ });
84
103
  const cardMap = /* @__PURE__ */ new Map();
85
104
  for (const item of user_cards || []) {
86
105
  const card_id = item.card_id;
@@ -99,13 +118,15 @@ async function _warehouse_draw_image(ctx, user_cards, show_name) {
99
118
  const pool = cardPool.get(item.card_id) || {};
100
119
  return {
101
120
  id: item.card_id,
102
- name: pool.card_name || "未知",
121
+ name: pool.name || "未知",
103
122
  level: item.card_level ?? 0,
104
- // ✅ 正确读取等级
105
- star: pool.card_star || 0
123
+ star: pool.star || 0,
124
+ isLimit: pool.isLimit || false
125
+ // 传递限定标记
106
126
  };
107
127
  });
108
128
  const sortedCards = [...validCards].sort((a, b) => {
129
+ if (a.isLimit !== b.isLimit) return b.isLimit ? 1 : -1;
109
130
  if (b.star !== a.star) return b.star - a.star;
110
131
  return String(a.id).localeCompare(String(b.id), void 0, { numeric: true });
111
132
  });
@@ -123,18 +144,19 @@ async function _warehouse_draw_image(ctx, user_cards, show_name) {
123
144
  ctx2d.textAlign = "left";
124
145
  ctx2d.font = "20px Microsoft YaHei";
125
146
  ctx2d.fillStyle = "#000";
126
- ctx2d.fillText(`玩家:${show_name}`, PADDING, PADDING);
147
+ ctx2d.fillText(`玩家:${show_name}(带【限定】标记为限时卡)`, PADDING, PADDING);
127
148
  const getStarText = /* @__PURE__ */ __name((star) => {
128
149
  const s = Math.max(0, Math.min(7, star));
129
150
  return "★".repeat(s) + "☆".repeat(7 - s);
130
151
  }, "getStarText");
131
152
  sortedCards.forEach((card, index) => {
132
- const { level, id, name: name2, star } = card;
153
+ const { level, id, name: name2, star, isLimit } = card;
133
154
  const col = Math.floor(index / COL_MAX);
134
155
  const row = index % COL_MAX;
135
156
  const x = PADDING + col * (COLUMN_WIDTH + COLUMN_GAP);
136
157
  const y = TITLE_HEIGHT + row * ROW_HEIGHT;
137
- const text = `[${level}] ${id.padStart(4, "0")} ${name2} ${getStarText(star)}`;
158
+ const limitTag = isLimit ? "【限定】" : "";
159
+ const text = `[${level}] ${id.padStart(4, "0")} ${name2} ${limitTag}${getStarText(star)}`;
138
160
  ctx2d.font = "16px Microsoft YaHei";
139
161
  ctx2d.fillStyle = STAR_COLOR[star] || "#333";
140
162
  ctx2d.fillText(text, x, y);
@@ -195,23 +217,13 @@ async function _shop_draw_image(ctx, shopCards, user_disturbance, user_water_vap
195
217
  }
196
218
  __name(_shop_draw_image, "_shop_draw_image");
197
219
  async function _collection_draw_image(ctx, show_name, total_collected, total_all, categoryProgress, categoryCards) {
198
- logger.info("开始绘制收藏图片(绝对对齐+带?+支持4字名)");
220
+ logger.info("开始绘制收藏图片(含限定卡,仅红色加粗边框)");
199
221
  try {
200
222
  if (!ctx.canvas) return null;
201
223
  const BASE_X = 40;
202
224
  const CARD_SIZE = 70;
203
225
  const CARD_GAP = 10;
204
226
  const ROW_HEIGHT = CARD_SIZE + CARD_GAP;
205
- let totalHeight = 140;
206
- const years = Object.keys(categoryProgress);
207
- for (const year of years) {
208
- const cardCount = categoryCards[year]?.length || 0;
209
- const rows = Math.ceil(cardCount / 10);
210
- totalHeight += 35 + 40 + rows * ROW_HEIGHT + 30;
211
- }
212
- totalHeight += 40;
213
- const canvas = ctx.canvas.createCanvas(900, totalHeight);
214
- const ctx2d = canvas.getContext("2d");
215
227
  const STAR_COLOR = {
216
228
  7: "#FF00FF",
217
229
  6: "#C00000",
@@ -222,64 +234,82 @@ async function _collection_draw_image(ctx, show_name, total_collected, total_all
222
234
  1: "#56CA85",
223
235
  0: "#33CAFD"
224
236
  };
237
+ const years = Object.keys(categoryProgress).sort();
238
+ let totalHeight = 140;
239
+ if (years.length > 0) {
240
+ for (const year of years) {
241
+ const cardCount = categoryCards[year]?.length || 0;
242
+ const rows = Math.ceil(cardCount / 10);
243
+ totalHeight += 35 + 40 + rows * ROW_HEIGHT + 30;
244
+ }
245
+ }
246
+ totalHeight = Math.max(totalHeight, 300);
247
+ const canvas = ctx.canvas.createCanvas(900, totalHeight);
248
+ const ctx2d = canvas.getContext("2d");
249
+ if (!ctx2d) return null;
225
250
  ctx2d.fillStyle = "#ffffff";
226
251
  ctx2d.fillRect(0, 0, 900, totalHeight);
227
252
  ctx2d.fillStyle = "#000";
228
253
  ctx2d.textAlign = "center";
229
254
  ctx2d.font = "28px Microsoft YaHei";
230
- ctx2d.fillText(`${show_name} 的收藏进度`, 450, 50);
255
+ ctx2d.fillText(`${show_name} 的收藏进度(含限定卡)`, 450, 50);
256
+ const pct = total_all > 0 ? (total_collected / total_all * 100).toFixed(1) : "0.0";
231
257
  ctx2d.font = "22px Microsoft YaHei";
232
258
  ctx2d.fillStyle = "#E53E3E";
233
- const pct = total_all > 0 ? (total_collected / total_all * 100).toFixed(1) : "0.0";
234
259
  ctx2d.fillText(`总进度:${total_collected}/${total_all}(${pct}%)`, 450, 85);
235
260
  let y = 140;
236
261
  ctx2d.textAlign = "left";
237
262
  ctx2d.textBaseline = "alphabetic";
238
263
  ctx2d.font = "20px Microsoft YaHei";
239
- for (const year of years) {
240
- const [collected, total, cardMap] = categoryProgress[year];
241
- const cids = categoryCards[year] || [];
242
- const cardCount = cids.length;
243
- ctx2d.fillStyle = "#222";
244
- ctx2d.fillText(`【${year}】`, BASE_X, y);
245
- y += 35;
246
- ctx2d.font = "18px Microsoft YaHei";
264
+ if (years.length === 0) {
247
265
  ctx2d.fillStyle = "#666";
248
- const cpct = total > 0 ? (collected / total * 100).toFixed(1) : "0.0";
249
- ctx2d.fillText(`进度:${collected}/${total}(${cpct}%)`, BASE_X, y);
250
- y += 40;
251
- let x = BASE_X;
252
- for (let i = 0; i < cardCount; i++) {
253
- const cid = cids[i];
254
- const card = cardMap[cid];
255
- const hasCard = !!card;
256
- if (i > 0 && i % 10 === 0) {
257
- x = BASE_X;
258
- y += ROW_HEIGHT;
259
- }
260
- ctx2d.fillStyle = "#fff";
261
- ctx2d.strokeStyle = "#e5e5e5";
262
- ctx2d.fillRect(x, y, CARD_SIZE, CARD_SIZE);
263
- ctx2d.strokeRect(x, y, CARD_SIZE, CARD_SIZE);
264
- ctx2d.textAlign = "center";
265
- ctx2d.font = "bold 16px Microsoft YaHei";
266
- if (hasCard) {
267
- const star = card.star || 0;
268
- ctx2d.fillStyle = STAR_COLOR[star];
269
- ctx2d.fillText(`${card.name}`, x + CARD_SIZE / 2, y + CARD_SIZE / 2 + 5);
270
- } else {
271
- ctx2d.fillStyle = "#999";
272
- ctx2d.fillText("?", x + CARD_SIZE / 2, y + CARD_SIZE / 2 + 5);
273
- }
266
+ ctx2d.font = "20px Microsoft YaHei";
267
+ ctx2d.textAlign = "center";
268
+ ctx2d.fillText("暂无任何卡片数据", 450, 200);
269
+ } else {
270
+ for (const year of years) {
271
+ const [collected, total, cardMap] = categoryProgress[year];
272
+ const cids = categoryCards[year] || [];
273
+ const cardCount = cids.length;
274
274
  ctx2d.textAlign = "left";
275
- x += CARD_SIZE + CARD_GAP;
275
+ ctx2d.fillStyle = "#222";
276
+ ctx2d.fillText(`【${year}】`, BASE_X, y);
277
+ y += 35;
278
+ ctx2d.font = "18px Microsoft YaHei";
279
+ ctx2d.fillStyle = "#666";
280
+ const cpct = total > 0 ? (collected / total * 100).toFixed(1) : "0.0";
281
+ ctx2d.fillText(`进度:${collected}/${total}(${cpct}%)`, BASE_X, y);
282
+ y += 40;
283
+ let x = BASE_X;
284
+ for (let i = 0; i < cardCount; i++) {
285
+ const cid = cids[i];
286
+ const card = cardMap[cid] || {};
287
+ if (i > 0 && i % 10 === 0) {
288
+ x = BASE_X;
289
+ y += ROW_HEIGHT;
290
+ }
291
+ ctx2d.fillStyle = "#fff";
292
+ ctx2d.strokeStyle = card.isLimit ? "#FF0000" : "#e5e5e5";
293
+ ctx2d.lineWidth = card.isLimit ? 2 : 1;
294
+ ctx2d.fillRect(x, y, CARD_SIZE, CARD_SIZE);
295
+ ctx2d.strokeRect(x, y, CARD_SIZE, CARD_SIZE);
296
+ ctx2d.textAlign = "center";
297
+ ctx2d.font = "bold 14px Microsoft YaHei";
298
+ if (card.name) {
299
+ ctx2d.fillStyle = STAR_COLOR[card.star] || "#333";
300
+ ctx2d.fillText(card.name, x + CARD_SIZE / 2, y + CARD_SIZE / 2 + 5);
301
+ } else {
302
+ ctx2d.fillStyle = "#999";
303
+ ctx2d.fillText("?", x + CARD_SIZE / 2, y + CARD_SIZE / 2 + 5);
304
+ }
305
+ x += CARD_SIZE + CARD_GAP;
306
+ }
307
+ y += (cardCount > 0 ? CARD_SIZE : 0) + 30;
308
+ ctx2d.font = "20px Microsoft YaHei";
276
309
  }
277
- if (cardCount > 0) y += CARD_SIZE + 30;
278
- else y += 30;
279
- ctx2d.font = "20px Microsoft YaHei";
280
310
  }
281
311
  const buffer = await canvas.toBuffer("image/png");
282
- logger.info("收藏图片生成成功 ✅");
312
+ logger.info("收藏图片生成成功(限定卡仅红色加粗边框)✅");
283
313
  return import_koishi.h.image(buffer, "image/png");
284
314
  } catch (err) {
285
315
  logger.error("收藏绘图失败:", err);
@@ -865,7 +895,7 @@ async function _do_exchange(ctx, session, exchange_num) {
865
895
  }
866
896
  __name(_do_exchange, "_do_exchange");
867
897
  async function _do_collection(ctx, session) {
868
- logger.info("收到指令:WWC收藏");
898
+ logger.info("收到指令:WWC收藏(含限定卡统计)");
869
899
  try {
870
900
  const qq = session.userId;
871
901
  const user = await ctx.database.get("gacha_user", { qq });
@@ -873,15 +903,41 @@ async function _do_collection(ctx, session) {
873
903
  const show_name = user[0].status === 1 ? user[0].nickname : qq;
874
904
  const userCards = await ctx.database.get("gacha_user_card", { qq });
875
905
  const cardIds = userCards.map((uc) => uc.card_id);
876
- const allCards = await ctx.database.get("gacha_card_pool", {});
906
+ const [normalCards, eventCards] = await Promise.all([
907
+ ctx.database.get("gacha_card_pool", {}),
908
+ ctx.database.get("gacha_event_pool", {})
909
+ ]);
910
+ const allCardMap = /* @__PURE__ */ new Map();
911
+ normalCards.forEach((c) => allCardMap.set(c.card_id, {
912
+ name: c.card_name,
913
+ star: c.card_star,
914
+ category: c.category,
915
+ isLimit: false
916
+ }));
917
+ eventCards.forEach((c) => {
918
+ if (!allCardMap.has(c.card_id)) {
919
+ allCardMap.set(c.card_id, {
920
+ name: c.card_name,
921
+ star: c.card_star,
922
+ category: c.card_type,
923
+ isLimit: true
924
+ });
925
+ }
926
+ });
927
+ const allCards = Array.from(allCardMap.values());
877
928
  const collectedSet = /* @__PURE__ */ new Set();
878
929
  const cardStarMap = {};
879
930
  for (const card of allCards) {
880
931
  if (!card.category) continue;
881
- if (cardIds.includes(card.card_id)) {
882
- collectedSet.add(`${card.category}-${card.card_id}`);
932
+ const cardId = Array.from(allCardMap.keys()).find((id) => allCardMap.get(id) === card);
933
+ if (cardIds.includes(cardId)) {
934
+ collectedSet.add(`${card.category}-${cardId}`);
883
935
  if (!cardStarMap[card.category]) cardStarMap[card.category] = {};
884
- cardStarMap[card.category][card.card_id] = { name: card.card_name, star: card.card_star };
936
+ cardStarMap[card.category][cardId] = {
937
+ name: card.name,
938
+ star: card.star,
939
+ isLimit: card.isLimit
940
+ };
885
941
  }
886
942
  }
887
943
  const total_collected = collectedSet.size;
@@ -890,11 +946,12 @@ async function _do_collection(ctx, session) {
890
946
  const categoryTotal = {};
891
947
  for (const card of allCards) {
892
948
  if (!card.category) continue;
949
+ const cardId = Array.from(allCardMap.keys()).find((id) => allCardMap.get(id) === card);
893
950
  if (!categoryCards[card.category]) {
894
951
  categoryCards[card.category] = [];
895
952
  categoryTotal[card.category] = 0;
896
953
  }
897
- categoryCards[card.category].push(card.card_id);
954
+ categoryCards[card.category].push(cardId);
898
955
  categoryTotal[card.category]++;
899
956
  }
900
957
  const categoryProgress = {};
@@ -902,7 +959,7 @@ async function _do_collection(ctx, session) {
902
959
  const collected = Object.keys(cardStarMap[category] || {}).length;
903
960
  categoryProgress[category] = [collected, categoryTotal[category], cardStarMap[category] || {}];
904
961
  }
905
- logger.info("收藏数据获取成功,开始生成图片");
962
+ logger.info("收藏数据获取成功(含限定卡),开始生成图片");
906
963
  const img_base64 = await _collection_draw_image(ctx, show_name, total_collected, total_all, categoryProgress, categoryCards);
907
964
  if (!img_base64) {
908
965
  logger.error("收藏图片生成失败!");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-xxck-1",
3
- "description": "自用(?)抽卡系统(自行设置卡池内容,0-7星划分)(加入限时卡池系统,商店,收藏,排行,仓库)(部分内容开发中,大部分已经可用)",
4
- "version": "0.1.21",
3
+ "description": "自用(?)抽卡系统 📋 WWC抽卡系统 完整指令说明(见readme)",
4
+ "version": "0.1.25",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -2,4 +2,109 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-xxck-1?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-xxck-1)
4
4
 
5
- 自用(?)抽卡系统(自行设置卡池内容,0-7星划分)(加入限时卡池系统,商店,收藏,排行,仓库)(部分内容开发中,大部分已经可用)
5
+ 自用(?)抽卡系统 📋 WWC抽卡系统 完整指令说明(见readme)
6
+ ==========================================
7
+
8
+ ### 一、基础功能
9
+ 1. **注册**:首次使用必须注册
10
+ - 指令:WWC注册 [昵称]
11
+ - 示例:WWC注册 风之旅行者
12
+ - 说明:注册后可参与抽卡、签到、兑换等所有功能
13
+
14
+ 2. **签到**:每日领取抽数
15
+ - 指令:WWC签到
16
+ - 示例:WWC签到
17
+ - 说明:每日可签到1次,获得40抽,重置时间由管理员配置
18
+
19
+ 3. **仓库**:查看已拥有的卡片(含等级/星级)
20
+ - 指令:WWC仓库
21
+ - 示例:WWC仓库
22
+ - 说明:卡片按星级降序排列,显示等级和编号
23
+
24
+ 4. **收藏**:查看卡片收藏进度
25
+ - 指令:WWC收藏
26
+ - 示例:WWC收藏
27
+ - 说明:生成收藏进度图片,显示各分类收集率
28
+
29
+ 5. **排名**:查看全服收藏排行
30
+ - 指令:WWC排名1 / WWC排行1
31
+ - 示例:WWC排名1
32
+ - 说明:显示前20名玩家,同时展示自己的排名和收藏数
33
+
34
+ ### 二、抽卡功能
35
+ 1. **普通抽卡**:消耗普通抽数抽卡
36
+ - 指令:WWC抽卡 [次数](1-20连)
37
+ - 示例:WWC抽卡 10(10连抽)
38
+ - 说明:单次最多20连,6/7星高星卡会单独播报
39
+
40
+ 2. **限时抽卡**:参与UP卡池抽卡(普通卡池+限时UP卡)
41
+ - 指令1:WWC限时抽卡 [期数] [次数](多卡池开放时)
42
+ - 指令2:WWC限时抽卡 [次数](单卡池开放时)
43
+ - 示例1:WWC限时抽卡 2601 20(2601期20连)
44
+ - 示例2:WWC限时抽卡 5(单卡池5连)
45
+ - 说明:需先兑换限时抽数,6/7星UP概率不同
46
+
47
+ ### 三、兑换/升格功能
48
+ 1. **普通兑换**:用水汽兑换普通抽数
49
+ - 指令:WWC兑换 [次数](1-40抽)
50
+ - 示例:WWC兑换 30(兑换30抽)
51
+ - 说明:100水汽=1抽,每日上限80抽
52
+
53
+ 2. **限时兑换**:用水汽兑换限时抽数
54
+ - 指令:WWC限时兑换 [期数] [次数](1-40抽)
55
+ - 示例:WWC限时兑换 2601 15(2601期兑换15抽)
56
+ - 说明:6星限时卡300水汽/抽,7星450水汽/抽
57
+
58
+ 3. **店铺升格**:用热带扰动兑换指定卡片
59
+ - 指令:WWC升格 [卡片编号](可多个编号)
60
+ - 示例:WWC升格 2660 1513(兑换编号2660和1513的卡片)
61
+ - 说明:每日店铺卡片随机刷新,有全服兑换上限
62
+
63
+ 4. **店铺**:查看今日可升格的卡片
64
+ - 指令:WWC店铺
65
+ - 示例:WWC店铺
66
+ - 说明:生成店铺图片,显示卡片价格、兑换指令和剩余次数
67
+
68
+ ### 四、管理员功能(仅主人可用)
69
+ 1. **卡片录入**:添加新卡片到卡池
70
+ - 指令:WWC录入(换行后输入卡片信息)
71
+ - 示例:
72
+ WWC录入
73
+ 2660 春樱吹 6 2026限定
74
+ 1513 苏迪罗 7 台风
75
+ - 说明:每行1张卡,格式:卡片ID 名称 星级 类别
76
+
77
+ 2. **审核昵称**:通过用户的昵称注册申请
78
+ - 指令:WWC审核 [用户QQ]
79
+ - 示例:WWC审核 123456789
80
+ - 说明:审核后用户可显示自定义昵称,否则显示QQ号
81
+
82
+ 3. **限时卡池录入**:添加限时UP卡池
83
+ - 指令:WWC限时录入 [期数] [卡片ID] [名称] [星级] [类别] [开始时间戳] [结束时间戳]
84
+ - 示例:WWC限时录入 2601 2660 春樱吹 6 2026 1775088000000 1775951999000
85
+ - 说明:时间戳需为北京时间(UTC+8)的毫秒数
86
+
87
+ 4. **功能开关**:控制全局/群聊功能开关
88
+ - 指令1:WWC开启全部(全局开启所有功能)
89
+ - 指令2:WWC关闭全部(全局关闭所有功能)
90
+ - 指令3:WWC开启(当前群开启功能)
91
+ - 指令4:WWC关闭(当前群关闭功能)
92
+ - 示例:WWC开启全部
93
+
94
+ 5. **全群广播**:向所有开启功能的群发送消息
95
+ - 指令:WWC广播 [消息内容]
96
+ - 示例:WWC广播 限时卡池「春樱吹」已开放,6星UP概率25%!
97
+ - 说明:仅主人可使用,避免骚扰群聊
98
+
99
+ ### 五、查询功能
100
+ 1. **限时卡池状态**:查看当前开放的限时卡池
101
+ - 指令:WWC限时状态
102
+ - 示例:WWC限时状态
103
+ - 说明:显示期数、卡片名称、星级、开放时间
104
+
105
+ 2. **帮助指令**:查看本说明
106
+ - 指令:WWChelp
107
+ - 示例:WWChelp
108
+ - 说明:随时调用,查看所有指令用法
109
+ ==========================================
110
+ 💡 提示:所有指令前缀为「WWC」,区分大小写;抽卡/兑换次数超出限制会自动提示