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
+109 -27
View File
@@ -1,6 +1,8 @@
// Injected into the game. Uses NYA's actor inventory/battle values, not RPG Maker HP/gold.
function installTrainer() {
if (window.__nyaruruTrainer) return window.__nyaruruTrainer;
if (window.__nyaruruTrainer?.version === 3) return window.__nyaruruTrainer;
if (window.__nyaruruTrainer?.dispose) window.__nyaruruTrainer.dispose();
else window.__nyaruruTrainer?.clear();
const fields = { coin: '_coin', hp: '_hp', sp: 'sp', zp: 'zp' };
const locks = new Map();
const ids = new WeakMap();
@@ -54,7 +56,7 @@ function installTrainer() {
}
function touch() { sync(); touched = Date.now(); }
// Only one timer per game page; neither hooks nor lock settings enter save data.
setInterval(() => { try { sync(); } catch { clear(); } }, 100);
const timer = setInterval(() => { try { sync(); } catch { clear(); } }, 100);
const categories = ['consumables', 'weapons', 'stones', 'ornaments'];
const specials = {
@@ -87,7 +89,67 @@ function installTrainer() {
const data = actor[dataMethod]();
items.push({ type, id: 0, name: localized(data.name), desc: localized(data.desc), count: actor[countMethod](), max: 99999, equipped: false });
}
return { ok: true, subject, role: $gamePlayer.isLilyMode() ? '莉莉娅' : actor.name(), stats, items };
if (actor._ornaments.bellData) {
const data = actor._ornaments.bellData(), fromMap = actor._ornaments.eventBellGainNum();
items.push({ type: 'bell', id: 0, name: localized(data.name), desc: localized(data.desc) + ' 默认保留地图铃铛;允许重置收集点时可减少到 0。',
count: actor.bellNum(), min: fromMap, max: 25, equipped: false });
}
const capacity = actor._ornaments.maxEquip ? { used: actor._ornaments.hasEquipNum(), max: actor._ornaments.maxEquip(),
mapBells: actor._ornaments.eventBellGainNum(), extraBells: actor._ornaments._hasBuyBellNum } : null;
return { ok: true, subject, role: $gamePlayer.isLilyMode() ? '莉莉娅' : actor.name(), stats, items, capacity };
}
function validateItem(actor, body) {
const { type } = body, itemId = integer(body.id, 99999);
if (type === 'bell') {
if (itemId !== 0 || !actor._ornaments.bellData) throw new Error('铃铛数据不可用');
const count = integer(body.count, 25), minimum = actor._ornaments.eventBellGainNum();
if (count < minimum) {
if (body.adjustMapBells !== true) throw new Error(`已从地图取得 ${minimum} 个铃铛;勾选允许重置地图铃铛后可继续减少`);
const keys = typeof DataManager !== 'undefined' && DataManager._cacheBell;
if (!Array.isArray(keys) || keys.filter(key => $gameSelfSwitches.value(key)).length !== minimum) throw new Error('地图铃铛记录不完整,不能减少');
}
return;
}
if (Object.prototype.hasOwnProperty.call(specials, type)) {
if (itemId !== 0) throw new Error('物品编号无效');
integer(body.count, 99999); return;
}
if (!categories.includes(type)) throw new Error('物品分类无效');
const container = actor['_' + type], data = container.data();
if (!Object.prototype.hasOwnProperty.call(data, itemId) || !data[itemId]) throw new Error('物品不存在');
integer(body.count, type === 'consumables' ? (body.overCap === true ? 99999 : container.maxNum(itemId)) : 1);
}
function applyItem(actor, body) {
const { type, id: itemId, count } = body;
if (type === 'bell') {
const o = actor._ornaments;
if (count < o.eventBellGainNum()) {
const acquired = DataManager._cacheBell.filter(key => $gameSelfSwitches.value(key));
// Remove the highest map/event keys first for deterministic results.
const ordered = [...acquired].sort((a,b)=>a[0]-b[0] || a[1]-b[1]);
for (const key of ordered.slice(count)) $gameSelfSwitches.setValue(key, false);
}
o._hasBuyBellNum = count - o.eventBellGainNum();
while (o.hasEquipNum() > o.maxEquip()) {
const removable = [...o._equip].reverse().find(id => id !== 20);
if (removable === undefined) break;
o.unequip(removable);
}
} else if (Object.prototype.hasOwnProperty.call(specials, type)) actor[specials[type][2]] = count;
else {
const container = actor['_' + type];
if (type === 'consumables') container.add(itemId, count - container.itemNum(itemId), body.overCap === true);
else if (count) container.unlock(itemId);
else if (type === 'weapons') {
const remaining = container._unlock.filter(n => n !== itemId);
if (container.currentWeapon() === itemId) { actor.changeWeapon(remaining[0]); $gamePlayer.setWeaponChange(); }
container._unlock = remaining;
} else if (type === 'ornaments') container.remove(itemId);
else {
if (container.isEquip(itemId)) container.equip(itemId);
container._unlock = container._unlock.filter(n => n !== itemId);
}
}
}
function change(body) {
touch();
@@ -114,35 +176,55 @@ function installTrainer() {
locks.set(field, { field, target: value, object: null });
try { sync(); } catch (e) { clear(); throw e; }
}
} else if (body.action === 'item') {
const { type } = body;
const itemId = integer(body.id, 99999);
if (Object.prototype.hasOwnProperty.call(specials, type)) {
if (itemId !== 0) throw new Error('物品编号无效');
actor[specials[type][2]] = integer(body.count, 99999);
} else {
if (!categories.includes(type)) throw new Error('物品分类无效');
const container = actor['_' + type], data = container.data();
if (!Object.prototype.hasOwnProperty.call(data, itemId) || !data[itemId]) throw new Error('物品不存在');
const max = type === 'consumables' ? (body.overCap === true ? 99999 : container.maxNum(itemId)) : 1;
const count = integer(body.count, max);
if (type === 'consumables') container.add(itemId, count - container.itemNum(itemId), body.overCap === true);
else if (count) container.unlock(itemId);
else if (type === 'weapons') {
const remaining = container._unlock.filter(n => n !== itemId);
if (!remaining.length) throw new Error('至少需要保留一件武器,避免游戏攻击时找不到武器');
if (container.currentWeapon() === itemId) { actor.changeWeapon(remaining[0]); $gamePlayer.setWeaponChange(); }
container._unlock = remaining;
} else if (type === 'ornaments') container.remove(itemId);
else {
if (container.isEquip(itemId)) container.equip(itemId);
container._unlock = container._unlock.filter(n => n !== itemId);
} else if (body.action === 'equip') {
const { type, id: itemId } = body;
if (!['weapons','stones','ornaments'].includes(type) || typeof body.equipped !== 'boolean') throw new Error('装备参数无效');
integer(itemId, 99999);
const container = actor['_' + type];
if (!Object.hasOwn(container.data(), itemId) || !container.isUnlock(itemId)) throw new Error('需要先获得该物品');
if (type === 'weapons') { if (!body.equipped) throw new Error('当前武器不能卸为空'); actor.changeWeapon(itemId); $gamePlayer.setWeaponChange(); }
else if (type === 'stones') { if (container.isEquip(itemId) !== body.equipped) container.equip(itemId); }
else if (!body.equipped) container.unequip(itemId);
else if (!container.isEquip(itemId) && !container.equip(itemId)) throw new Error('配饰栏位不足,请增加铃铛或先卸下其他配饰');
} else if (body.action === 'batch' || body.action === 'item') {
const changes = body.action === 'item' ? [body] : body.changes;
if (!Array.isArray(changes) || !changes.length || changes.length > 100) throw new Error('批量物品列表无效');
const seen = new Set();
for (const item of changes) {
if (!item || typeof item !== 'object') throw new Error('物品参数无效');
validateItem(actor, item);
const key = item.type + ':' + item.id;
if (seen.has(key)) throw new Error('批量列表含重复物品');
seen.add(key);
}
const weapons = new Set(actor._weapons._unlock);
for (const item of changes) if (item.type === 'weapons') { if (item.count) weapons.add(item.id); else weapons.delete(item.id); }
if (!weapons.size) throw new Error('至少需要保留一件武器,避免游戏攻击时找不到武器');
const objects = categories.map(type => actor['_' + type]);
const snapshots = objects.map(object => Object.fromEntries(Reflect.ownKeys(object).map(key => {
const d = Object.getOwnPropertyDescriptor(object, key);
if ('value' in d && d.value && typeof d.value === 'object') d.value = Array.isArray(d.value) ? [...d.value] : { ...d.value };
return [key, d];
})));
const materialKeys = Object.values(specials).map(v => v[2]);
const materials = materialKeys.map(key => actor[key]);
const bellFlags = changes.some(i=>i.type==='bell' && i.adjustMapBells === true) ? DataManager._cacheBell.map(key=>[key,$gameSelfSwitches.value(key)]) : [];
try {
const ordered = [...changes].sort((a,b) => Number(b.type === 'weapons' && b.count === 1) - Number(a.type === 'weapons' && a.count === 1));
for (const item of ordered) applyItem(actor, item);
} catch (e) {
for (let i = 0; i < objects.length; i++) {
for (const key of Reflect.ownKeys(objects[i])) if (!Object.hasOwn(snapshots[i], key)) delete objects[i][key];
Object.defineProperties(objects[i], snapshots[i]);
}
materialKeys.forEach((key, i) => { actor[key] = materials[i]; });
for (const [key,value] of bellFlags) $gameSelfSwitches.setValue(key,value);
throw e;
}
} else throw new Error('未知修改操作');
return read();
}
return window.__nyaruruTrainer = { read, change, touch, clear };
return window.__nyaruruTrainer = { version: 3, read, change, touch, clear, dispose() { clear(); if (typeof clearInterval === 'function') clearInterval(timer); } };
}
module.exports = { installTrainer };