koishi-plugin-deerpipe-qbot
Version:
炉管新插件,支持大乱斗!
366 lines (361 loc) • 17 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
var import_dayjs = __toESM(require("dayjs"));
var name = "deerpipe-qbot";
var inject = {
required: ["database"]
// 声明插件依赖 database 服务
};
var Config = import_koishi.Schema.object({
specialDates: import_koishi.Schema.array(import_koishi.Schema.string()).default([
"07-08",
"07-21",
"09-01"
]),
isDevelopment: import_koishi.Schema.boolean().default(false)
});
function apply(ctx, config) {
ctx.model.extend("users", {
userId: "string",
// 主键
username: "string",
// 用户名,不允许为空(唯一性由注册逻辑保证)
lastCheckIn: "string",
// 存储日期字符串 'MM-DD'
points: "integer",
lv: "integer",
experience: "integer",
streak: "integer"
}, {
primary: "userId"
// 声明 userId 为主键
});
const matchingLobby = /* @__PURE__ */ new Map();
const pendingGames = /* @__PURE__ */ new Map();
const activeGames = /* @__PURE__ */ new Map();
const DEPOSIT = 2;
const LOBBY_TIMEOUT_MS = 60 * 1e3;
const BATTLE_LOG_INTERVAL_MS = 2 * 1e3;
const BATTLE_LOG_COUNT = 5;
const RANK_TOP_N = 10;
const WINNER_PHRASES = ["占据上风", "猛烈攻击", "步步紧逼", "势如破竹", "占据优势", "压制对手"];
const LOSER_PHRASES = ["节节败退", "苦苦支撑", "勉力抵挡", "露出破绽", "处于劣势", "险象环生"];
if (config.isDevelopment) {
const randomUserId = generateRandomUserId();
const randomUserData = { ...generateRandomUserData(), userId: randomUserId, username: `随机用户${Math.floor(Math.random() * 1e5)}` };
ctx.database.create("users", randomUserData).then(() => console.log(`开发模式:已生成随机用户 ${randomUserId},等级 ${randomUserData.lv},用户名 ${randomUserData.username}`)).catch((err) => console.error(`开发模式:生成随机用户失败: ${err}`));
}
ctx.command("牛子对对碰", "牛子对对碰游戏指令集").action(() => {
return `✨ 牛子对对碰游戏指令集 ✨
以下是你可以使用的指令:
- 注册 <用户名>:首次游玩请先注册你的专属用户名。
例如:注册 otto
------------------------------------------
- 炉管打卡:每日打卡获取积分和经验。
------------------------------------------
- 对对碰匹配 -b <积分>:发起或加入牛子对对碰比赛,需下注积分。
例如:对对碰匹配 -b 100
------------------------------------------
- 开始对对碰:在匹配成功后,由发起方输入此指令开始比赛。
------------------------------------------
- 查询积分:查看你的当前积分、等级、经验和连续打卡天数。
------------------------------------------
- 积分榜单:查看积分排行榜。
祝你玩得开心!
`;
});
ctx.command("注册 <username:string>", "注册你的游戏用户名").action(async ({ session }, username) => {
const userId = session.userId;
if (!username || username.trim() === "") {
return "请输入一个有效的用户名。";
}
if (username.length > 15 || username.length < 2) {
return "用户名长度需在2到15个字符之间。";
}
const [existingUserWithSameName] = await ctx.database.get("users", { username });
if (existingUserWithSameName && existingUserWithSameName.userId !== userId) {
return `用户名“${username}”已被占用,请尝试其他用户名。`;
}
let [user] = await ctx.database.get("users", { userId });
if (user) {
if (user.username === username) {
return `你已经注册过用户名:${user.username},无需重复注册。`;
} else {
await ctx.database.set("users", { userId }, { username });
return `你的用户名已更新为:${username}。`;
}
} else {
await ctx.database.create("users", {
userId,
username,
// 使用用户提供的用户名
lastCheckIn: null,
points: 0,
lv: 0,
experience: 0,
streak: 0
});
return `恭喜你,${username}!你已成功注册牛子对对碰。`;
}
});
ctx.command("炉管打卡", "打卡并获得积分").action(async ({ session }) => {
const userId = session.userId;
let [user] = await ctx.database.get("users", { userId });
if (!user) {
user = await ctx.database.create("users", {
userId,
username: session.username,
// 默认使用会话用户名
lastCheckIn: null,
points: 0,
lv: 0,
experience: 0,
streak: 0
});
return `你尚未注册用户名,已为你创建临时账号(用户名:${session.username})。请使用“注册 <用户名>”设置你的专属用户名。
首次打卡不获得积分,请先注册!`;
}
const today = (0, import_dayjs.default)().format("MM-DD");
const isSpecialDate = config.specialDates.includes(today);
const isDevMode = config.isDevelopment;
if (user.lastCheckIn === today) {
return "今天你已经打过卡了!";
}
const pointsGained = Math.floor(Math.random() * 25) + 1;
user.experience += 10;
const requiredExpForNextLv = (user.lv + 1) * 20;
if (user.experience >= requiredExpForNextLv) {
user.lv += 1;
session.send(`恭喜你,${user.username}!你的等级提升到 Lv.${user.lv}!`);
}
user.streak += 1;
if (user.streak % 7 === 0) {
const weekNumber = user.streak / 7;
session.send(`这是你打卡的第${weekNumber}周!`);
}
if (isDevMode && Math.random() < 0.5 || isSpecialDate && Math.random() < 0.2) {
return "炉管失败!若今日是特殊日期,炉管失败时正常的!";
}
user.points += pointsGained;
user.lastCheckIn = today;
await ctx.database.set("users", { userId }, {
points: user.points,
lv: user.lv,
experience: user.experience,
streak: user.streak,
lastCheckIn: user.lastCheckIn
});
return `打卡成功!你获得了 ${pointsGained} 积分,当前积分:${user.points},Lv:${user.lv},经验:${user.experience}`;
});
ctx.command("对对碰匹配", "参与牛子对对碰比赛,下注积分").option("bet", "-b <points:number> 下注积分").action(async ({ session, options }) => {
const userId = session.userId;
let [user] = await ctx.database.get("users", { userId });
if (!user) {
return "你尚未注册牛子对对碰账号,请先使用“注册 <用户名>”命令进行注册。";
}
const { bet } = options;
if (typeof bet !== "number" || bet <= 0) {
return "请输入有效的下注积分 (大于0的数字)。例如:对对碰匹配 -b 100";
}
const totalCost = bet + DEPOSIT;
if (user.points < totalCost) {
return `积分不足!你需要 ${totalCost} 积分(${bet}下注 + ${DEPOSIT}押金),你当前有 ${user.points} 积分。`;
}
if (matchingLobby.has(userId) || pendingGames.has(userId) || activeGames.has(userId) || Array.from(pendingGames.values()).some((g) => g.player2Id === userId) || Array.from(activeGames.values()).some((g) => g.player2Id === userId)) {
return "你已经在匹配或游戏中了,请勿重复操作。";
}
await ctx.database.set("users", { userId }, { points: user.points - totalCost });
user.points -= totalCost;
const [opponentId, opponentLobbyData] = Array.from(matchingLobby.entries())[0] || [];
if (opponentId && opponentLobbyData) {
const [opponent] = await ctx.database.get("users", { userId: opponentId });
if (!opponent) {
matchingLobby.delete(opponentId);
await ctx.database.set("users", { userId }, { points: user.points + totalCost });
return "匹配失败:对手数据异常,已退还你的积分。";
}
clearTimeout(opponentLobbyData.timeoutId);
matchingLobby.delete(opponentId);
const [player1Data] = await ctx.database.get("users", { userId: opponentId });
const [player2Data] = await ctx.database.get("users", { userId });
const lvDifference = player1Data.lv - player2Data.lv;
const player1WinChance = 0.5 + lvDifference * 0.05;
const winnerId = Math.random() < player1WinChance ? opponentId : userId;
const game = {
player1Id: opponentId,
player2Id: userId,
player1Session: opponentLobbyData.session,
player2Session: session,
player1Bet: opponentLobbyData.bet,
player2Bet: bet,
winnerId
};
pendingGames.set(opponentId, game);
opponentLobbyData.session.send(`匹配成功!你将与 ${player2Data.username} 对战。请在${LOBBY_TIMEOUT_MS / 1e3}秒内输入 "开始对对碰" 命令开始游戏。`);
session.send(`匹配成功!你将与 ${player1Data.username} 对战。等待对方输入 "开始对对碰" 命令开始游戏。`);
return "匹配成功,等待对方开始游戏。";
} else {
const timeoutId = setTimeout(async () => {
if (matchingLobby.has(userId)) {
matchingLobby.delete(userId);
await ctx.database.set("users", { userId }, { points: user.points + totalCost });
await session.send(`60秒内未找到对手,你已退出匹配大厅。已退还 ${totalCost} 积分。当前积分:${user.points + totalCost}`);
}
}, LOBBY_TIMEOUT_MS);
matchingLobby.set(userId, { bet, session, timeoutId });
return `你已进入牛子对对碰匹配大厅,等待其他玩家加入(${LOBBY_TIMEOUT_MS / 1e3}秒内)。已扣除 ${bet} 积分和 ${DEPOSIT} 押金。当前积分:${user.points}`;
}
});
ctx.command("开始对对碰", "开始牛子对对碰比赛").action(async ({ session }) => {
const userId = session.userId;
const game = pendingGames.get(userId);
if (!game || game.player1Id !== userId) {
return "你当前没有等待开始的牛子对对碰比赛,或者你不是发起者。";
}
pendingGames.delete(userId);
const activeGame = {
...game,
logCount: 0,
intervalId: setTimeout(() => {
}, 0)
// 占位符,将被 setInterval 替换
};
activeGames.set(userId, activeGame);
await game.player1Session.send("牛子对对碰比赛开始!");
await game.player2Session.send("牛子对对碰比赛开始!");
await sendBattleLog(activeGame);
activeGame.intervalId = setInterval(() => sendBattleLog(activeGame), BATTLE_LOG_INTERVAL_MS);
return "游戏已开始,战况播报中...";
});
ctx.command("查询积分", "查询自己的积分和等级信息").action(async ({ session }) => {
const userId = session.userId;
const [user] = await ctx.database.get("users", { userId });
if (!user) {
return "你还没有任何数据,请先打卡或参与游戏。";
}
return `你的当前信息:
用户名:${user.username}
积分:${user.points}
等级:Lv.${user.lv}
经验:${user.experience}
连续打卡:${user.streak}天`;
});
ctx.command("积分榜单", "查询积分排行榜").action(async ({ session }) => {
const allUsers = await ctx.database.select("users").orderBy("points", "desc").execute();
if (allUsers.length === 0) {
return "目前还没有用户数据,榜单为空。";
}
let rankMessage = "✨ 积分排行榜 ✨\n";
rankMessage += "--------------------\n";
for (let i = 0; i < Math.min(RANK_TOP_N, allUsers.length); i++) {
const user = allUsers[i];
rankMessage += `${i + 1}. ${user.username} (Lv.${user.lv}) - ${user.points} 积分
`;
}
const currentUserIndex = allUsers.findIndex((u) => u.userId === session.userId);
if (currentUserIndex !== -1) {
const currentUserData = allUsers[currentUserIndex];
if (currentUserIndex >= RANK_TOP_N) {
rankMessage += `--------------------
`;
rankMessage += `你的排名:${currentUserIndex + 1}. ${currentUserData.username} (Lv.${currentUserData.lv}) - ${currentUserData.points} 积分`;
}
} else {
rankMessage += `--------------------
`;
rankMessage += `你还没有数据,无法显示排名。`;
}
return rankMessage;
});
async function sendBattleLog(game) {
game.logCount++;
const [winnerUserData] = await ctx.database.get("users", { userId: game.winnerId });
const [loserUserData] = await ctx.database.get("users", { userId: game.winnerId === game.player1Id ? game.player2Id : game.player1Id });
const winnerPhrase = WINNER_PHRASES[Math.floor(Math.random() * WINNER_PHRASES.length)];
const loserPhrase = LOSER_PHRASES[Math.floor(Math.random() * LOSER_PHRASES.length)];
const logMessage = `战况播报 (${game.logCount}/${BATTLE_LOG_COUNT}):${winnerUserData.lv}级 ${winnerUserData.username} ${winnerPhrase},${loserUserData.lv}级 ${loserUserData.username} ${loserPhrase}!`;
await game.player1Session.send(logMessage);
await game.player2Session.send(logMessage);
if (game.logCount >= BATTLE_LOG_COUNT) {
clearInterval(game.intervalId);
activeGames.delete(game.player1Id);
const [player1] = await ctx.database.get("users", { userId: game.player1Id });
const [player2] = await ctx.database.get("users", { userId: game.player2Id });
let resultMessage1 = "";
let resultMessage2 = "";
if (game.winnerId === game.player1Id) {
player1.points += game.player2Bet + DEPOSIT;
resultMessage1 = `恭喜你,${player1.username}!你赢得了这场牛子对对碰!获得了对方的 ${game.player2Bet} 积分,并返还了 ${DEPOSIT} 押金。你当前积分:${player1.points}`;
resultMessage2 = `很遗憾,${player2.username},你输掉了这场牛子对对碰!失去了 ${game.player2Bet} 积分和 ${DEPOSIT} 押金。你当前积分:${player2.points}`;
} else {
player2.points += game.player1Bet + DEPOSIT;
resultMessage1 = `很遗憾,${player1.username},你输掉了这场牛子对对碰!失去了 ${game.player1Bet} 积分和 ${DEPOSIT} 押金。你当前积分:${player1.points}`;
resultMessage2 = `恭喜你,${player2.username}!你赢得了这场牛子对对碰!获得了对方的 ${game.player1Bet} 积分,并返还了 ${DEPOSIT} 押金。你当前积分:${player2.points}`;
}
await ctx.database.set("users", { userId: game.player1Id }, { points: player1.points });
await ctx.database.set("users", { userId: game.player2Id }, { points: player2.points });
await game.player1Session.send(resultMessage1);
await game.player2Session.send(resultMessage2);
}
}
__name(sendBattleLog, "sendBattleLog");
function generateRandomUserId() {
return `user_${Math.floor(Math.random() * 1e4)}`;
}
__name(generateRandomUserId, "generateRandomUserId");
function generateRandomUserData() {
const lv = Math.floor(Math.random() * 5) + 1;
return {
lastCheckIn: null,
points: Math.floor(Math.random() * 100) + 10,
lv,
experience: lv * 20,
streak: Math.floor(Math.random() * 7)
};
}
__name(generateRandomUserData, "generateRandomUserData");
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name
});