DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import type { TreeOption } from "naive-ui";
|
||||
import type { ExportPreviewFile } from "../../bindings/pvfine/services/models";
|
||||
|
||||
export function exportFileKey(path: string): string {
|
||||
return `file:${path}`;
|
||||
}
|
||||
|
||||
export function buildExportTree(files: ExportPreviewFile[]): TreeOption[] {
|
||||
const roots: TreeOption[] = [];
|
||||
const directories = new Map<string, TreeOption>();
|
||||
for (const file of files) {
|
||||
const parts = file.path.split("/");
|
||||
let children = roots;
|
||||
let parent = "";
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
parent = parent ? `${parent}/${part}` : part;
|
||||
let directory = directories.get(parent);
|
||||
if (!directory) {
|
||||
directory = { key: `dir:${parent}`, label: part, children: [], isLeaf: false };
|
||||
directories.set(parent, directory);
|
||||
children.push(directory);
|
||||
}
|
||||
children = directory.children!;
|
||||
}
|
||||
children.push({
|
||||
key: exportFileKey(file.path),
|
||||
label: parts[parts.length - 1],
|
||||
isLeaf: true,
|
||||
checkboxDisabled: file.required,
|
||||
path: file.path,
|
||||
required: file.required,
|
||||
});
|
||||
}
|
||||
function sort(nodes: TreeOption[]): void {
|
||||
nodes.sort((a, b) => {
|
||||
if (a.isLeaf !== b.isLeaf) return a.isLeaf ? 1 : -1;
|
||||
const sa = String(a.label);
|
||||
const sb = String(b.label);
|
||||
return sa < sb ? -1 : (sa > sb ? 1 : 0);
|
||||
});
|
||||
for (const node of nodes) if (node.children) sort(node.children);
|
||||
}
|
||||
sort(roots);
|
||||
return roots;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { ImageBatchResult, ImageData, ImageReference } from "../../bindings/pvfine/services/models";
|
||||
|
||||
const batchSize = 128;
|
||||
const maxConcurrentBatches = 2;
|
||||
const batchDelayMs = 8;
|
||||
const cacheEntries = 512;
|
||||
|
||||
type ImageJob = {
|
||||
key: string;
|
||||
generation: number;
|
||||
epoch: number;
|
||||
reference: ImageReference;
|
||||
promise: Promise<ImageData | null>;
|
||||
resolve: (data: ImageData | null) => void;
|
||||
};
|
||||
|
||||
// Coalesce only requested images, including visible tree rows and previews.
|
||||
// Reset settles old callers immediately and prevents old responses filling the cache.
|
||||
export function createImageLoader(
|
||||
getGeneration: () => number,
|
||||
loadBatch: (references: ImageReference[], generation: number) => Promise<ImageBatchResult[]>,
|
||||
onError: (error: unknown) => void = () => {},
|
||||
) {
|
||||
const pending = new Map<string, ImageJob>();
|
||||
const resolved = new Map<string, ImageData | null>();
|
||||
let queue: ImageJob[] = [];
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let active = 0;
|
||||
let epoch = 0;
|
||||
|
||||
function schedule() {
|
||||
if (timer !== null || queue.length === 0 || active >= maxConcurrentBatches) return;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
pump();
|
||||
}, batchDelayMs);
|
||||
}
|
||||
|
||||
function pump() {
|
||||
while (queue.length > 0 && active < maxConcurrentBatches) {
|
||||
const jobs = queue.splice(0, batchSize);
|
||||
active++;
|
||||
void run(jobs);
|
||||
}
|
||||
}
|
||||
|
||||
async function run(jobs: ImageJob[]) {
|
||||
try {
|
||||
const results = await loadBatch(jobs.map((job) => job.reference), jobs[0]!.generation);
|
||||
for (let index = 0; index < jobs.length; index++) {
|
||||
const job = jobs[index]!;
|
||||
if (job.epoch !== epoch || job.generation !== getGeneration()) continue;
|
||||
const result = results[index];
|
||||
const data = result?.data ?? null;
|
||||
if (result?.error) {
|
||||
onError(result.error);
|
||||
} else if (result) {
|
||||
resolved.delete(job.key);
|
||||
resolved.set(job.key, data);
|
||||
while (resolved.size > cacheEntries) resolved.delete(resolved.keys().next().value!);
|
||||
}
|
||||
job.resolve(data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (jobs[0]!.epoch === epoch) onError(error);
|
||||
} finally {
|
||||
for (const job of jobs) {
|
||||
job.resolve(null); // also settles errors or stale/incomplete responses
|
||||
if (pending.get(job.key) === job) pending.delete(job.key);
|
||||
}
|
||||
active--;
|
||||
pump();
|
||||
}
|
||||
}
|
||||
|
||||
function load(reference: ImageReference | null | undefined): Promise<ImageData | null> {
|
||||
if (!reference?.path || !Number.isInteger(reference.index) || reference.index < 0) return Promise.resolve(null);
|
||||
const path = reference.path.replaceAll("\\", "/").replace(/^\/+|\/+$/g, "").toLowerCase();
|
||||
if (!path) return Promise.resolve(null);
|
||||
const generation = getGeneration();
|
||||
const key = `${generation}\u0000${path}\u0000${reference.index}`;
|
||||
if (resolved.has(key)) return Promise.resolve(resolved.get(key) ?? null);
|
||||
const existing = pending.get(key);
|
||||
if (existing) return existing.promise;
|
||||
let resolve!: ImageJob["resolve"];
|
||||
const promise = new Promise<ImageData | null>((done) => { resolve = done; });
|
||||
const job: ImageJob = { key, generation, epoch, reference: { path, index: reference.index }, promise, resolve };
|
||||
pending.set(key, job);
|
||||
queue.push(job);
|
||||
schedule();
|
||||
return promise;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
epoch++;
|
||||
if (timer !== null) clearTimeout(timer);
|
||||
timer = null;
|
||||
queue = [];
|
||||
for (const job of pending.values()) job.resolve(null);
|
||||
pending.clear();
|
||||
resolved.clear();
|
||||
}
|
||||
|
||||
return { load, reset };
|
||||
}
|
||||
Reference in new issue
Block a user