feat: 发布咸鱼喵喵全能助手,加入全收集地图与图文说明
This commit is contained in:
@@ -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 };
|
||||
Reference in New Issue
Block a user