UNPKG

koishi-plugin-screeps

Version:
754 lines (736 loc) 23.3 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; 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 __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_koishi2 = require("koishi"); // src/commands/types.ts var defineCommand = /* @__PURE__ */ __name((config) => { return config; }, "defineCommand"); // src/commands/queryMarket.ts var queryMarket_default = defineCommand({ name: "market", register(ctx, api2) { ctx.command("market <resource> <shard> <type>", "查询市场资源信息").alias("市场").alias("查市场").action( async (_, resource, targetShard, type) => { if (!resource) { return "请提供想查询的资源名称"; } if (resource.length === 1) { resource = resource.toUpperCase(); } targetShard ||= "shard3"; if (!["shard0", "shard1", "shard2", "shard3"].includes(targetShard)) { return "无效的shard"; } let isGlobalRes = false; if (["pixel", "cpuUnlock", "accessKey"].includes(resource)) { isGlobalRes = true; } type ||= "buy"; if (!["buy", "sell"].includes(type)) { return "无效的查询类型,请选择 'buy' 或 'sell'"; } try { const res = await api2.getOrdersByResourceType( resource, !isGlobalRes ? targetShard : void 0 ); if (!res.ok) { return `查询市场资源信息失败: ${res.error}`; } const maxLen = 10; const orders = res.list.filter((order) => order.type === type); orders.sort((a, b) => { if (type === "buy") { return b.price - a.price; } return a.price - b.price; }); const filteredOrders = orders.slice(0, maxLen); const roomNames = Array.from( new Set(filteredOrders.map((order) => order.roomName)) ).filter(Boolean); const mapRes = await api2.getMapStats( roomNames, targetShard, "owner0" ); if (!mapRes.ok) { return `查询房间信息失败: ${mapRes.error}`; } const getUser = /* @__PURE__ */ __name((roomName) => { return mapRes.users[mapRes.stats[roomName]?.own?.user]?.username || "Invader"; }, "getUser"); return `${targetShard} ${resource} ${type === "buy" ? "买单" : "卖单"} ` + filteredOrders.map((order) => { return `${order.price.toFixed(3).padEnd(10)} ${isGlobalRes ? "未知" : getUser(order.roomName)}`; }).join("\n"); } catch (error) { return `查询市场资源信息失败`; } } ); } }); // src/commands/queryNuke.ts var queryNuke_default = defineCommand({ name: "nuke", register: /* @__PURE__ */ __name((ctx, api2) => { ctx.command("nuke <shard>", "查询核弹信息,默认shard3").alias("核弹", "查核弹", "查询核弹").action(async (_, targetShard) => { const nukeInfo = await api2.getNukes(); if (!nukeInfo.ok) { return `核弹信息查询失败: ${nukeInfo.error}`; } targetShard ||= "shard3"; const resList = []; Object.entries(nukeInfo.nukes).forEach(([shard, nukes]) => { if (targetShard !== "all" && shard !== targetShard) return; const rooms = Array.from( new Set( nukes.map(({ room, launchRoomName }) => [room, launchRoomName]).flat() ) ); resList.push( new Promise(async (resolve, reject) => { const res = await api2.getMapStats( rooms, shard, "owner0" ); if (res.ok) { resolve([shard, res]); return; } reject(res.error); }) ); }); return await Promise.all(resList).then((responses) => { const result = responses.map(([shard, res]) => { const nukes = nukeInfo.nukes[shard] || []; const o = /* @__PURE__ */ __name((room) => { return res.users[res.stats[room].own.user]?.username || "未知"; }, "o"); return `${shard}: ` + nukes.map((nuke) => { return `${o(nuke.launchRoomName)} ${nuke.launchRoomName} -> ${o(nuke.room)} ${nuke.room}`; }).join("\n"); }).join("\n\n"); return result || "没有核弹信息"; }); }); }, "register") }); // src/commands/queryResource.ts var import_canvas = require("canvas"); // src/utils/common.ts var drawText = /* @__PURE__ */ __name((ctx, text, x, y, color) => { ctx.fillStyle = color; ctx.fillText(text, x, y); }, "drawText"); var splitNum = /* @__PURE__ */ __name((num) => { if (num < 1e3) return num.toString(); let str = ""; str += (num % 1e3).toString().padStart(3, "0"); num = Math.floor(num / 1e3); while (num) { if (num < 1e3) { str = num + "," + str; break; } str = (num % 1e3).toString().padStart(3, "0") + "," + str; num = Math.floor(num / 1e3); } return str; }, "splitNum"); // src/utils/calc.ts var calculateGPL = /* @__PURE__ */ __name((power) => { return Math.floor(Math.pow((power || 0) / 1e3, 0.5)); }, "calculateGPL"); var calculateGCL = /* @__PURE__ */ __name((gcl) => { return Math.floor(Math.pow((gcl || 0) / 1e6, 1 / 2.4)) + 1; }, "calculateGCL"); var gent = /* @__PURE__ */ __name((r) => { return [ r + "H", r + "H2O", "X" + r + "H2O", r + "O", r + "HO2", "X" + r + "HO2" ]; }, "gent"); // src/utils/constants.ts var baseRes = ["energy", "U", "L", "K", "Z", "X", "O", "H", "G"]; var powerRes = ["power", "ops"]; var barsRes = [ "battery", "utrium_bar", "lemergium_bar", "keanium_bar", "zynthium_bar", "purifier", "oxidant", "reductant", "ghodium_melt" ]; var c_greyRes = ["composite", "crystal", "liquid"]; var c_blueRes = [ "silicon", "wire", "switch", "transistor", "microchip", "circuit", "device" ]; var c_yellowRes = [ "metal", "alloy", "tube", "fixtures", "frame", "hydraulics", "machine" ]; var c_pinkRes = [ "mist", "condensate", "concentrate", "extract", "spirit", "emanation", "essence" ]; var c_greenRes = [ "biomass", "cell", "phlegm", "tissue", "muscle", "organoid", "organism" ]; var b_greyRes = ["OH", "ZK", "UL", "G"]; var b_blueRes = gent("U"); var b_yellowRes = gent("Z"); var b_pinkRes = gent("K"); var b_greenRes = gent("L"); var b_witheRes = gent("G"); var RES_COLOR_MAP = { empty: "rgba(0,0,0,0)", energy: "rgb(255,242,0)", battery: "rgb(255,242,0)", Z: "rgb(247, 212, 146)", L: "rgb(108, 240, 169)", U: "rgb(76, 167, 229)", K: "rgb(218, 107, 245)", X: "rgb(255, 192, 203)", G: "rgb(255,255,255)", zynthium_bar: "rgb(247, 212, 146)", lemergium_bar: "rgb(108, 240, 169)", utrium_bar: "rgb(76, 167, 229)", keanium_bar: "rgb(218, 107, 245)", purifier: "rgb(255, 192, 203)", ghodium_melt: "rgb(255,255,255)", power: "rgb(224,90,90)", ops: "rgb(224,90,90)", composite: "#ccc", crystal: "#ccc", liquid: "#ccc", device: "rgb(76, 167,229)", circuit: "rgb(76, 167,229)", microchip: "rgb(76, 167,229)", transistor: "rgb(76, 167,229)", switch: "rgb(76, 167,229)", wire: "rgb(76, 167,229)", silicon: "rgb(76, 167,229)", machine: "rgb(247,212,146)", hydraulics: "rgb(247,212,146)", frame: "rgb(247,212,146)", fixtures: "rgb(247,212,146)", tube: "rgb(247,212,146)", alloy: "rgb(247,212,146)", metal: "rgb(247,212,146)", essence: "rgb(218,107,245)", emanation: "rgb(218,107,245)", spirit: "rgb(218,107,245)", extract: "rgb(218,107,245)", concentrate: "rgb(218,107,245)", condensate: "rgb(218,107,245)", mist: "rgb(218,107,245)", organism: "rgb(108,240,169)", organoid: "rgb(108,240,169)", muscle: "rgb(108,240,169)", tissue: "rgb(108,240,169)", phlegm: "rgb(108,240,169)", cell: "rgb(108,240,169)", biomass: "rgb(108,240,169)", OH: "#ccc", ZK: "#ccc", UL: "#ccc", UH: "rgb(76, 167,229)", UH2O: "rgb(76, 167,229)", XUH2O: "rgb(76, 167,229)", UO: "rgb(76, 167,229)", UHO2: "rgb(76, 167,229)", XUHO2: "rgb(76, 167,229)", ZH: "rgb(247,212,146)", ZH2O: "rgb(247,212,146)", XZH2O: "rgb(247,212,146)", ZO: "rgb(247,212,146)", ZHO2: "rgb(247,212,146)", XZHO2: "rgb(247,212,146)", KH: "rgb(218,107,245)", KH2O: "rgb(218,107,245)", XKH2O: "rgb(218,107,245)", KO: "rgb(218,107,245)", KHO2: "rgb(218,107,245)", XKHO2: "rgb(218,107,245)", LH: "rgb(108,240,169)", LH2O: "rgb(108,240,169)", XLH2O: "rgb(108,240,169)", LO: "rgb(108,240,169)", LHO2: "rgb(108,240,169)", XLHO2: "rgb(108,240,169)", GH: "rgb(255,255,255)", GH2O: "rgb(255,255,255)", XGH2O: "rgb(255,255,255)", GO: "rgb(255,255,255)", GHO2: "rgb(255,255,255)", XGHO2: "rgb(255,255,255)", H: "#ccc", O: "#ccc", oxidant: "#ccc", reductant: "#ccc" }; var CONTROLLER_LEVELS = { 1: 200, 2: 45e3, 3: 135e3, 4: 405e3, 5: 1215e3, 6: 3645e3, 7: 10935e3 }; // src/commands/queryResource.ts var import_koishi = require("koishi"); // src/data/updateMarket.ts var marketDataCache = /* @__PURE__ */ new Map(); var getResourcePrice = /* @__PURE__ */ __name(async (ctx, shard, resourceType) => { const id = `${shard}-${resourceType}`; if (marketDataCache.has(id)) { return marketDataCache.get(id); } const marketData = await ctx.database.get("market", id); const price = marketData.length ? marketData[0].price : void 0; if (price) { marketDataCache.set(id, price); } return price; }, "getResourcePrice"); var updateMarketData = /* @__PURE__ */ __name(async (ctx, api2) => { const shards = ["shard0", "shard1", "shard2", "shard3"]; shards.forEach(async (shard) => { const marketInfo = await api2.getMarketOrdersIndex(shard); if (!marketInfo.ok) { return; } const marketData = {}; marketInfo.list.forEach((info) => { if (!marketData[info._id]) { marketData[info._id] = { totalPrice: 0, totalCount: 0 }; } marketData[info._id].totalPrice += info.avgPrice * info.count; marketData[info._id].totalCount += info.count; }); const newDatas = Object.entries(marketData).map( ([type, { totalPrice, totalCount }]) => { const id = `${shard}-${type}`; const data = { id, shard, resourceType: type, price: totalCount ? Number((totalPrice / totalCount).toFixed(3)) : 0 }; marketDataCache.set(id, data.price); return data; } ); ctx.database.upsert("market", newDatas); }); }, "updateMarketData"); // src/commands/queryResource.ts var queryResource_default = defineCommand({ name: "res", register(context, api2) { context.command("res <user> <shard>", "查询资源信息").alias("资源").alias("查资源").alias("查询资源").action(async (_, username, targetShard) => { if (!username) { return "请提供想查询资源的用户名"; } if (targetShard && !["shard0", "shard1", "shard2", "shard3"].includes(targetShard)) { return "无效的shard"; } const userInfo = await api2.getUserInfoByUserName(username); if (!userInfo.ok || !userInfo.user) { return `未找到该玩家:${username} ${userInfo.error}`; } const roomsInfo = await api2.getRooms(userInfo.user._id); if (!roomsInfo.ok) { return "未找到该玩家房间信息"; } const requests = []; const resStats = {}; const roomResStats = {}; for (const shardName in roomsInfo.shards) { const shard = shardName; if (targetShard && shard !== targetShard) continue; for (const room of roomsInfo.shards[shard]) { requests.push( new Promise(async (resolve, reject) => { const res = await api2.getRoomObject(room, shard); if (res.ok) { resolve([room, shard, res]); } else { reject(res.error); } }) ); } } try { return await Promise.all(requests).then(async (responses) => { for (const [room, shard, res] of responses) { const roomRes = {}; for (const object of res.objects) { if (object.type !== "storage" && object.type !== "terminal" && object.type !== "factory") continue; for (const resType in object.store) { const type = resType; const id = `${shard}-${type}`; resStats[type] = (resStats[type] || 0) + object.store[type]; roomRes[id] = (roomRes[id] || 0) + object.store[type]; } } roomResStats[`${shard}-${room}`] = roomRes; } const gap = 100; const width = 9 * gap; const height = 520; const canvas = (0, import_canvas.createCanvas)(width, height); const ctx = canvas.getContext("2d"); ctx.fillStyle = "#2b2b2b"; ctx.fillRect(0, 0, width, height); ctx.font = "14px"; drawText(ctx, "baseRes", 10, 15, "#fff"); baseRes.forEach((type, index) => { drawText( ctx, `${type} ${splitNum(resStats[type] || 0)}`, 30 + index * gap, 30, RES_COLOR_MAP[type] ); }); drawText(ctx, "barsRes", 10, 65, "#fff"); barsRes.forEach((type, index) => { drawText( ctx, `${type} ${splitNum(resStats[type] || 0)}`, 30 + index * gap, 80, RES_COLOR_MAP[type] ); }); drawText(ctx, "powerRes", 10, 115, "#fff"); powerRes.forEach((type, index) => { drawText( ctx, `${type} ${splitNum(resStats[type] || 0)}`, 30 + index * gap, 130, RES_COLOR_MAP[type] ); }); drawText(ctx, "goods", 10, 165, "#fff"); for (const [y, res] of [ c_greyRes, c_blueRes, c_yellowRes, c_pinkRes, c_greenRes ].entries()) { res.forEach((type, index) => { drawText( ctx, `${type} ${splitNum(resStats[type] || 0)}`, 30 + index * gap, 180 + y * 30, RES_COLOR_MAP[type] ); }); } drawText(ctx, "labRes", 10, 335, "#fff"); for (const [y, res] of [ b_greyRes, b_blueRes, b_yellowRes, b_pinkRes, b_greenRes, b_witheRes ].entries()) { res.forEach((type, index) => { drawText( ctx, `${type} ${splitNum(resStats[type] || 0)}`, 30 + index * gap, 350 + y * 30, RES_COLOR_MAP[type] ); }); } drawText(ctx, (/* @__PURE__ */ new Date()).toLocaleString(), 680, 400, "#888"); drawText( ctx, `${username} ${targetShard || "all shard"}`, 680, 420, "#888" ); let totalValue = 0; let maxValueRoom = ""; let maxValue = 0; await Promise.all( Object.entries(roomResStats).map(async ([roomId, stats]) => { const [shard] = roomId.split("-"); let value = 0; for (const [id, count] of Object.entries(stats)) { const [_2, resourceType] = id.split("-"); const price = await getResourcePrice( context, shard, resourceType ); value += count * (price || 0); } totalValue += value; if (value > maxValue) { maxValue = value; maxValueRoom = roomId; } }) ); drawText( ctx, `总价值: ${splitNum(Math.floor(totalValue))}`, 680, 440, "#888" ); drawText( ctx, `最高价值房间: ${maxValueRoom} (${splitNum( Math.floor(maxValue) )})`, 680, 460, "#888" ); const buf = canvas.toBuffer("image/png"); return import_koishi.h.image(buf, "image/png"); }); } catch (error) { return "查询失败,请稍后再试"; } }); } }); // src/commands/queryRoom.ts var queryRoom_default = defineCommand({ name: "room", register(ctx, api2) { ctx.command("room <room> <shard>", "查询房间信息").alias("房间", "查房间", "查询房间").action(async (_, roomName, targetShard) => { if (!roomName) { return "请提供想查询的房间名称"; } roomName = roomName.toUpperCase(); targetShard ||= "shard3"; if (!["shard0", "shard1", "shard2", "shard3"].includes(targetShard)) { return "无效的shard"; } const res = await api2.getRoomObject(roomName, targetShard); if (!res.ok) { return `查询房间信息失败: ${res.error}`; } const controller = res.objects.find((obj) => obj.type === "controller"); if (!controller) { return `房间 ${roomName} 不存在控制器`; } if (!controller.user || !controller.level) { return `房间 ${roomName} 为无主房间`; } const creeps = res.objects.filter((obj) => obj.type === "creep"); const walls = res.objects.filter( (obj) => obj.type === "constructedWall" || obj.type === "rampart" ); const storage = res.objects.find((obj) => obj.type === "storage"); const terminal = res.objects.find((obj) => obj.type === "terminal"); const maxHitWall = Math.max(...walls.map((wall) => wall.hits)); const avgHitWall = walls.reduce((sum, wall) => sum + wall.hits, 0) / walls.length; let result = `${targetShard} ${roomName} 房间信息: `; if (controller) { result += `- 控制器等级: ${controller.level} ${controller.level < 8 ? `进度: ${(controller.progress / CONTROLLER_LEVELS[controller.level] * 100).toFixed(2)}%` : ""} `; } if (creeps.length) { result += `- 爬爬数量: ${creeps.length} `; } if (storage) { const amount = Object.values(storage.store).reduce( (sum, val) => sum + val, 0 ); result += `- 仓库利用率: ${(amount / storage.storeCapacity * 100).toFixed(2)}% `; } if (terminal) { const amount = Object.values(terminal.store).reduce( (sum, val) => sum + val, 0 ); result += `- 终端利用率: ${(amount / terminal.storeCapacity * 100).toFixed(2)}% `; } if (walls.length) { result += `- 墙体数量: ${walls.length},最大血量: ${(maxHitWall / 1e6).toFixed(2)}m,平均血量: ${(avgHitWall / 1e6).toFixed(2)}m `; } return result.trim(); }); } }); // src/commands/queryUser.ts var queryUser_default = defineCommand({ name: "user", register(ctx, api2) { ctx.command("user <username>", "查询玩家信息").option("top", "-t 最高排名").alias("查询玩家", "玩家信息", "玩家").action(async ({ options }, username) => { if (!username) { return "请提供想查询的用户名"; } if (/[\u4e00-\u9fa5]/.test(username)) { return; } const userInfo = await api2.getUserInfoByUserName(username); if (!userInfo.ok || !userInfo.user) { return `未找到该玩家:${username} ${userInfo.error}`; } const gcl = calculateGCL(userInfo.user.gcl); const gpl = calculateGPL(userInfo.user.power); let worldRank = 0; let powerRank = 0; let time = 0; await Promise.all([ api2.getRank(username, "world"), api2.getRank(username, "power") ]).then((responses) => { if (options.top) { responses[0].list?.sort((a, b) => b.rank - a.rank); responses[1].list?.sort((a, b) => b.rank - a.rank); } worldRank = responses[0].ok ? responses[0].list.pop()?.rank + 1 : 0; powerRank = responses[1].ok ? responses[1].list.pop()?.rank + 1 : 0; time = Math.max( responses[0].list.filter((item) => item.score).length, responses[1].list.filter((item) => item.score).length ); }); return `玩家信息${options.top ? "(最高排名)" : "(最近排名)"}: - 名称: ${userInfo.user.username} - 经营时间: ${time}月 - GCL: ${gcl} (世界排名: ${worldRank || "未上榜"}) - GPL: ${gpl} (抛瓦排名: ${powerRank || "未上榜"})`; }); } }); // src/commands/index.ts var commands = [ queryResource_default, queryMarket_default, queryUser_default, queryNuke_default, queryRoom_default // chat, ]; // src/index.ts var import_screeps_simple_api = require("screeps-simple-api"); // src/data/db.ts var defineModel = /* @__PURE__ */ __name((ctx) => { ctx.model.extend("market", { id: "string", shard: "string", resourceType: "string", price: "float" }); }, "defineModel"); // src/index.ts var import_koishi_plugin_cron = require("koishi-plugin-cron"); var name = "screeps"; var inject = { required: ["database", "cron"] }; var Config = import_koishi2.Schema.object({ token: import_koishi2.Schema.string().description("Screeps API Token").default("") }); var api; async function apply(ctx, config) { api = new import_screeps_simple_api.ScreepsApi({ token: config.token }); commands.forEach((cmd) => cmd.register(ctx, api)); ctx.on("message", (session) => { }); defineModel(ctx); ctx.cron("0 0 * * *", async () => { updateMarketData(ctx, api); }); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, inject, name });