// ==UserScript==
// @name Torn圣诞小镇掉落物品坐标显示
// @namespace WH
// @version 0.2.2
// @description 在地图界面上方显示附近的宝箱、物品、钥匙坐标,兼容手机APP Torn PDA及Alook
// @author Woohoo[2687093]
// @match https://www.torn.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const ___window___ = window || window.unsafeWindow;
if (___window___.WHLOOTPOS||___window___.WHTRANS) return;
___window___.WHLOOTPOS = true;
const $ = ___window___.jQuery;
if (/christmas_town\.php/.test(window.location.href)) {
const $root = document.querySelector('#christmastownroot');
const chestTypeDict = {'1': '金', '2': '银', '3': '铜',};
const chestTypeColorDict = {'1': 'gold', '2': 'silver', '3': 'sandybrown',};
const lootTypeDict = {'chests': '钥匙箱', 'gifts': '礼物', 'combinationChest': '密码箱', 'keys': '钥匙',};
const keyTypeDict = {'b': '铜', 's': '银', 'g': '金',};
let dropHist = localStorage.getItem('wh-loot-store')
? JSON.parse(localStorage.getItem('wh-loot-store'))
: {};
const alertSettings = localStorage.getItem('wh-loot-setting')
? JSON.parse(localStorage.getItem('wh-loot-setting'))
: {blink: 'y', sound: 'y', chest: 'y'};
let $ct_wrap;
let soundLoopFlag = false;
const getDOMOb = new MutationObserver(() => {
$ct_wrap = $root.querySelector('#ct-wrap');
if ($ct_wrap) {
getDOMOb.disconnect();
const insert_html = `
`;
$($ct_wrap).before(insert_html);
const $wh_loot_container = $root.querySelector('#wh-loot-container');
const $btn = $wh_loot_container.querySelector('#wh-loot-btn button');
const $clear_btn = $wh_loot_container.querySelector('#wh-hist-clear button');
const $ex = $wh_loot_container.querySelector('#wh-loot-container-ex');
const $tbody = $wh_loot_container.querySelector('tbody');
const $blink = $wh_loot_container.querySelector('#wh-loot-setting-blink');
const $sound = $wh_loot_container.querySelector('#wh-loot-setting-sound');
const $chest = $wh_loot_container.querySelector('#wh-loot-setting-chest');
const $audio = $wh_loot_container.querySelector('audio');
$btn.onclick = e => {
e.target.innerText = e.target.innerText === '设置' ? '收起' : '设置';
$($ex).toggleClass('wh-hide');
e.target.blur();
};
$clear_btn.onclick = e => {
e.target.blur();
dropHist = {};
$tbody.innerHTML = '';
localStorage.setItem('wh-loot-store', JSON.stringify(dropHist));
};
$blink.onchange = e => {
if (e.target.checked) {
alertSettings.blink = 'y';
if ($wh_loot_container.querySelector('#wh-loot-item-count').innerText !== '(0)') {
$wh_loot_container.querySelector('#wh-loot-container-main').style.animation = 'lootFoundAlert 2s infinite';
}
} else {
alertSettings.blink = 'n';
$wh_loot_container.querySelector('#wh-loot-container-main').style.animation = '';
}
localStorage.setItem('wh-loot-setting', JSON.stringify(alertSettings));
};
$sound.onchange = e => {
if (e.target.checked) {
alertSettings.sound = 'y';
if ($wh_loot_container.querySelector('#wh-loot-item-count').innerText !== '(0)') {
soundLoopFlag = true;
}
} else {
alertSettings.sound = 'n';
soundLoopFlag = false;
}
localStorage.setItem('wh-loot-setting', JSON.stringify(alertSettings));
};
$chest.onchange = e => {
alertSettings.chest = e.target.checked ? 'y' : 'n';
localStorage.setItem('wh-loot-setting', JSON.stringify(alertSettings));
};
const soundIntervalID = window.setInterval(() => {
if (soundLoopFlag) $audio.play().then();
}, 1200);
ob.observe($ct_wrap, {childList: true, subtree: true});
}
});
const ob = new MutationObserver(() => {
const $ct_title = $ct_wrap.querySelector('.status-title');
const $pos = $ct_wrap.querySelector('.map-title span[class^="position___"]') || $ct_wrap.querySelector('.status-title span[class^="position___"]');
if (!$pos) return;
const $pos_spl = $pos.innerText.trim().split(',');
const player_position = {};
player_position.x = parseInt($pos_spl[0]);
player_position.y = parseInt($pos_spl[1]);
const $wh_loot_container = $root.querySelector('#wh-loot-container');
if (!$wh_loot_container) {
console.error('掉落助手未找到DOM容器');
return;
}
const $blink = $wh_loot_container.querySelector('#wh-loot-setting-blink');
const $sound = $wh_loot_container.querySelector('#wh-loot-setting-sound');
const $chest = $wh_loot_container.querySelector('#wh-loot-setting-chest');
const $tbody = $wh_loot_container.querySelector('tbody');
const nearby_arr = [];
const items = $root.querySelectorAll('div.grid-layer div.items-layer div.ct-item');
// 附近的所有物品
items.forEach(el => {
const item_props = {x: 0, y: 0, name: '', type: '', url: '',};
item_props.x = parseInt(el.style.left.replaceAll('px', '')) / 30;
item_props.y = -parseInt(el.style.top.replaceAll('px', '')) / 30;
item_props.url = el.firstElementChild.src;
const srcSpl = item_props.url.trim().split('/');
item_props.name = srcSpl[6];
item_props.type = srcSpl[8].slice(0, 1);
nearby_arr.push(item_props);
});
const $wh_loot_container_items = $wh_loot_container.querySelector('#wh-loot-container-items');
const $wh_loot_container_chests = $wh_loot_container.querySelector('#wh-loot-container-chests');
let item_count = 0, chest_count = 0;
$wh_loot_container_items.innerHTML = '';
$wh_loot_container_chests.innerHTML = '';
nearby_arr.forEach(nearby_item => {
let path = '=';
if (nearby_item.x < player_position.x && nearby_item.y < player_position.y) path = '↙';
else if (nearby_item.x < player_position.x && nearby_item.y === player_position.y) path = '←';
else if (nearby_item.x < player_position.x && nearby_item.y > player_position.y) path = '↖';
else if (nearby_item.x === player_position.x && nearby_item.y > player_position.y) path = '↑';
else if (nearby_item.x > player_position.x && nearby_item.y > player_position.y) path = '↗';
else if (nearby_item.x > player_position.x && nearby_item.y === player_position.y) path = '→';
else if (nearby_item.x > player_position.x && nearby_item.y < player_position.y) path = '↘';
else if (nearby_item.x === player_position.x && nearby_item.y < player_position.y) path = '↓';
let item_name;
if (nearby_item.name === 'chests') {
chest_count++;
item_name = chestTypeDict[nearby_item.type] + lootTypeDict[nearby_item.name];
$wh_loot_container_chests.innerHTML += `${path}[${nearby_item.x},${nearby_item.y}] ${item_name}
`
} else {
item_count++;
item_name = (nearby_item.name === 'keys' ? keyTypeDict[nearby_item.type] || '' : '') + lootTypeDict[nearby_item.name] || nearby_item.name;
$wh_loot_container_items.innerHTML += `${path}[${nearby_item.x},${nearby_item.y}] ${item_name}
`
}
// 确认地图坐标存在
if ($ct_title) {
const hist_key = `[${nearby_item.x},${nearby_item.y}]"${$ct_title.firstChild.nodeValue.trim()}"${item_name}`;
const el = dropHist[hist_key];
if (el) {
if (path === '=' && (nearby_item.name === 'keys' || nearby_item.name === 'gifts')) {
el.isPassed = true;
}
} else {
if (!(nearby_item.name === 'chests' && $chest.checked)) {
const now = new Date();
dropHist[hist_key] = {
pos: `[${nearby_item.x},${nearby_item.y}]`,
map: $ct_title.firstChild.nodeValue.trim(),
last: `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`,
name: item_name,
id: Object.keys(dropHist).length,
};
}
}
}
});
$wh_loot_container.querySelector('#wh-loot-item-count').innerText = `(${item_count})`;
if (item_count === 0) {
$wh_loot_container_items.innerText = '暂无';
$wh_loot_container.querySelector('#wh-loot-container-main').style.animation = '';
soundLoopFlag = false;
} else {
if ($blink.checked) $wh_loot_container.querySelector('#wh-loot-container-main').style.animation = 'lootFoundAlert 2s infinite';
if ($sound.checked) soundLoopFlag = true;
}
$wh_loot_container.querySelector('#wh-loot-chest-count').innerText = `(${chest_count})`;
if (chest_count === 0) $wh_loot_container_chests.innerText = '暂无';
const history = Object.keys(dropHist).map(key => dropHist[key]).sort((a, b) => a.id - b.id);
let table_html = '';
history.forEach(e => {
table_html += `| ${e.pos} | ${e.map} | ${e.name} | ${e.last} | ${e.isPassed ? '已取得' : '不确定'} |
`;
});
$tbody.innerHTML = table_html;
localStorage.setItem('wh-loot-store', JSON.stringify(dropHist));
});
getDOMOb.observe($root, {childList: true, subtree: true});
}
}());