koishi-plugin-screeps
Version:
screeps plugin for koishi
754 lines (736 loc) • 23.3 kB
JavaScript
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
});