feat: 发布咸鱼喵喵全能助手,加入全收集地图与图文说明

This commit is contained in:
Kitasami_Nya
2026-10-24 01:37:15 +08:00
parent 8f6ac4ff04
commit 3823e38b07
39 changed files with 2918 additions and 194 deletions
+94
View File
@@ -0,0 +1,94 @@
// Read-only inventory/location join, evaluated in the game. No progress is modified.
function readEncyclopedia(inventory) {
const maps = DataManager._cacheMapData;
if (!maps || !Object.keys(maps).length) throw new Error('地图资料尚未加载,请先正常读档');
const items = inventory.items.map(item => ({ ...item, locations: [] }));
items.push({ type: 'coin', id: 0, name: '金币', desc: '可用于商店购买物品。', count: inventory.stats.coin.value, max: 99999, locations: [] });
const byKey = new Map(items.map(item => [item.type + ':' + item.id, item]));
const shops = [], locations = [], seen = new Set();
const collectedKeys = new Set();
for (const region of Object.values(typeof $dataRegion === 'undefined' ? {} : $dataRegion)) {
for (const [mapId, eventId] of region?.exploreItem || []) collectedKeys.add(mapId + ':' + eventId);
}
const conditions = cond => {
const result = [];
for (const n of [1, 2]) if (cond?.['switch' + n + 'Valid']) {
const id = cond['switch' + n + 'Id'];
result.push({ label: '开关 ' + ($dataSystem.switches[id] || id), met: !!$gameSwitches.value(id) });
}
if (cond?.variableValid) result.push({ label: '变量 ' + ($dataSystem.variables[cond.variableId] || cond.variableId) + ' ≥ ' + cond.variableValue,
met: $gameVariables.value(cond.variableId) >= cond.variableValue });
if (cond?.actorValid) result.push({ label: '队伍角色 ' + cond.actorId, met: null });
if (cond?.itemValid) result.push({ label: '持有道具 ' + cond.itemId, met: null });
return result;
};
for (const [mapKey, map] of Object.entries(maps)) {
const mapId = Number(mapKey);
if (!map || !Number.isInteger(mapId)) continue;
const regionId = DataManager.mapRegion?.(mapId) || 0;
const region = typeof $dataRegion !== 'undefined' && $dataRegion[regionId];
const area = region?.name || '未分类区域';
for (const event of map.events || []) {
if (!event) continue;
for (const [pageIndex, page] of (event.pages || []).entries()) {
const list = page.list || [];
for (const [commandIndex, command] of list.entries()) {
if (command.code !== 357) continue;
const [, name, , args = {}] = command.parameters || [];
const coinPile = name === 'ai' && Number(args.type) === 60;
if (!coinPile && !['addItem', 'add_coin', 'shop'].includes(name)) continue;
if (name === 'add_coin' && !(Number(args.num) > 0)) continue;
const base = { mapId, mapName: $dataMapInfos?.[mapId]?.name || '地图 ' + mapId, area, regionId,
eventId: event.id, eventName: event.name || '', x: event.x, y: event.y, pageIndex,
conditions: conditions(page.conditions), branch: Number(command.indent) > 0 };
if (page.conditions?.selfSwitchValid) base.conditions.push({ label: '独立开关 ' + page.conditions.selfSwitchCh,
met: !!$gameSelfSwitches.value([mapId, event.id, page.conditions.selfSwitchCh]) });
if (name === 'shop') { shops.push({ ...base, kind: 'shop' }); continue; }
const type = name === 'add_coin' || coinPile ? 'coin' : args.type;
const id = name === 'add_coin' || coinPile ? 0 : Number(args.idx);
const item = byKey.get(type + ':' + id);
if (!item) continue;
const key = `${type}:${id}:${mapId}:${event.id}:${pageIndex}:${commandIndex}`;
if (seen.has(key)) continue;
seen.add(key);
const official = collectedKeys.has(mapId + ':' + event.id);
// A later marker is reliable only for a straight-line pickup. Branch
// rewards may reuse A for another path, so those remain unknown.
const marker = list.slice(commandIndex + 1).find(c => c.code === 123 && Number(c.indent || 0) === Number(command.indent || 0) && c.parameters?.[1] === 0);
const nextPageHides = (event.pages || []).some(pg => pg.conditions?.selfSwitchValid && pg.conditions.selfSwitchCh === marker?.parameters?.[0]);
const reliable = official || (!base.branch && marker && nextPageHides);
const status = reliable ? !!$gameSelfSwitches.value([mapId, event.id, official ? 'A' : marker.parameters[0]]) : null;
const location = { ...base, key, type, id, kind: coinPile ? 'coinpile' : 'pickup', collected: status, official,
sourceNote: coinPile ? '猫币堆:击打 8 次耗尽,掉落数量受配饰影响' : '',
amount: coinPile ? null : name === 'add_coin' ? Number(args.num) : 1, antiCollection: args.antiCollection === 'true' };
item.locations.push(location); locations.push(location);
}
}
}
}
const actor = $gamePlayer.actor();
for (const item of items) {
let definition = null;
if (['consumables','weapons','stones','ornaments'].includes(item.type)) definition = actor['_' + item.type].data()[item.id];
else if (item.type === 'bell') definition = actor._ornaments.bellData();
else if (item.type === 'hpst') definition = actor.hpStrengthData();
else if (item.type === 'skillst') definition = actor.skillStrengthData();
else if (item.type === 'joycoin') definition = actor.joyCoinData();
item.price = definition?.price ?? null;
item.shopCandidate = Number(definition?.price) > 0;
item.openTask = definition?.openTask ?? null;
}
const counted = new Map();
for (const location of locations) if (location.official) counted.set(location.mapId + ':' + location.eventId, location);
const summaries = new Map();
for (const location of counted.values()) {
if (!summaries.has(location.area)) summaries.set(location.area, { area: location.area, total: 0, collected: 0 });
const s = summaries.get(location.area); s.total++; if (location.collected) s.collected++;
}
const regions = [...summaries.values()];
return { ok: true, items, locations, shops, regions, subject: inventory.subject,
stats: { items: items.length, locations: locations.length, shops: shops.length,
total: counted.size, collected: [...counted.values()].filter(l => l.collected).length },
coverage: '来源为当前游戏已加载的地图物品指令、猫币堆、收集点和商店入口。商店候选需结合任务进度在店内确认;脚本动态奖励及普通敌人掉落不在地图索引中。未知状态不会按未收集处理。' };
}
module.exports = { readEncyclopedia };