IdlePixel TCG Dex (Lux Fork)

Organizational script for the Criptoe Trading Card Game

目前为 2025-02-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name IdlePixel TCG Dex (Lux Fork)
  3. // @namespace luxferre.dev
  4. // @version 1.5.0
  5. // @description Organizational script for the Criptoe Trading Card Game
  6. // @author GodofNades & Lux-Ferre
  7. // @match *://idle-pixel.com/login/play*
  8. // @grant none
  9. // @license MIT
  10. // @require https://gf.qytechs.cn/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
  11. // @require https://gf.qytechs.cn/scripts/527481/code/IdlePixel%20Dialogue%20Handler.js?anticache=20250219b
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. let playername = "";
  18.  
  19. class tcgDex extends IdlePixelPlusPlugin {
  20. constructor() {
  21. super("tcgDex", {
  22. about: {
  23. name: GM_info.script.name + " (ver: " + GM_info.script.version + ")",
  24. version: GM_info.script.version,
  25. author: GM_info.script.author,
  26. description: GM_info.script.description,
  27. },
  28. config: [
  29. {
  30. label:
  31. "------------------------------------------------<br/>Notification<br/>------------------------------------------------",
  32. type: "label"
  33. },
  34. {
  35. id: "tcgNotification",
  36. label:
  37. "Enable TCG Card Buying Available Notification<br/>(Default: Enabled)",
  38. type: "boolean",
  39. default: true
  40. },
  41. {
  42. id: "newCardTimer",
  43. label:
  44. "New Card Timer<br/>(How long do you want a card to show as new, in minutes.)",
  45. type: "int",
  46. default: 15
  47. },
  48. {
  49. id: "enableSend",
  50. label: "Enable auto send of duplicate cards to the player in the next option.",
  51. type: "boolean",
  52. default: false
  53. },
  54. {
  55. id: "sendTo",
  56. label: "Player to send duplicate cards to automatically.",
  57. type: "string",
  58. default: null
  59. }
  60. ]
  61. })
  62. this.login_loaded = false
  63. this.dupe_sending = false
  64. this.pause_processing = false
  65. this.newest_card_ids = new Map()
  66. this.categoriesTCG = []
  67. this.row_bg_colours = {
  68. "ORE": "#734d26",
  69. "BAR": "#3d3d29",
  70. "SEED": "#1a3300",
  71. "WOOD": "#663300",
  72. "LEAF": "#669900",
  73. "GEM": "#990099",
  74. "FISH": "#3333cc",
  75. "MONSTER": "#000000",
  76. "GEAR": "#800000",
  77. "LEGENDARY": "#ffffff",
  78. "BREEDING": "#ffb31a",
  79. "LIMITED": "#ffffe6",
  80. }
  81. this.row_text_colours = {
  82. "ORE": "white",
  83. "BAR": "white",
  84. "SEED": "white",
  85. "WOOD": "white",
  86. "LEAF": "white",
  87. "GEM": "white",
  88. "FISH": "white",
  89. "MONSTER": "white",
  90. "GEAR": "white",
  91. "LEGENDARY": "black",
  92. "BREEDING": "black",
  93. "LIMITED": "black",
  94. }
  95.  
  96. this.cards_received = []
  97.  
  98. this.load_fa()
  99. }
  100.  
  101. load_fa(){
  102. const fontAwesomeLink = document.createElement('link')
  103. fontAwesomeLink.rel = 'stylesheet'
  104. fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css'
  105. document.head.appendChild(fontAwesomeLink)
  106. }
  107.  
  108. getCategoryData() {
  109. let uniqueDescriptionTitles = [];
  110. const descriptionTitlesSet = new Set();
  111. let i = 1;
  112.  
  113. Object.values(CardData.data).forEach((card) => {
  114. const descriptionTitle = card["description_title"];
  115. const properTitles =
  116. descriptionTitle.charAt(0).toUpperCase() +
  117. descriptionTitle.slice(1).toLowerCase();
  118.  
  119. if (!descriptionTitlesSet.has(descriptionTitle)) {
  120. descriptionTitlesSet.add(descriptionTitle);
  121.  
  122. uniqueDescriptionTitles.push({
  123. id: `[${descriptionTitle}]`,
  124. desc: descriptionTitle,
  125. label: `${properTitles}`,
  126. });
  127. i++;
  128. }
  129. })
  130.  
  131. // Sort categories alphabetically with legendaries first
  132. uniqueDescriptionTitles.sort((a, b) => {
  133. if (a.desc === "LEGENDARY") return -1
  134. if (b.desc === "LEGENDARY") return 1
  135. return a.desc.localeCompare(b.desc)
  136. })
  137.  
  138. return uniqueDescriptionTitles
  139. }
  140.  
  141. ensureNewSettingExists() {
  142. const settings = JSON.parse(
  143. localStorage.getItem(`${playername}.tcgSettings`)
  144. );
  145. if (settings && typeof settings.new === "undefined") {
  146. settings.new = true;
  147. localStorage.setItem(
  148. `${playername}.tcgSettings`,
  149. JSON.stringify(settings)
  150. );
  151. }
  152. }
  153.  
  154. initializeDatabase() {
  155. const dbName = `IdlePixel_TCG_DB.${playername}`;
  156. const version = 2;
  157. const request = indexedDB.open(dbName, version);
  158.  
  159. request.onerror = (event) => {
  160. console.error("Database error: ", event.target.error);
  161. };
  162.  
  163. request.onupgradeneeded = (event) => {
  164. const db = event.target.result;
  165. const objectStoreName = `current_cards`;
  166. if (!db.objectStoreNames.contains(objectStoreName)) {
  167. db.createObjectStore(objectStoreName, {keyPath: "cardNum"})
  168. } else {
  169. db.deleteObjectStore(objectStoreName)
  170. db.createObjectStore(objectStoreName, {keyPath: "cardNum"})
  171. }
  172. };
  173.  
  174. request.onsuccess = (event) => {
  175. this.db = event.target.result;
  176. };
  177. }
  178.  
  179. card_counts(currentCards){
  180. if(this.categoriesTCG.length === 0){console.log("Cards counted without categories!");return;}
  181. const counts = {}
  182.  
  183. this.categoriesTCG.forEach((category) => {
  184. counts[category.desc] = {
  185. uniHolo: 0,
  186. ttlHolo: 0,
  187. uniNormal: 0,
  188. ttlNormal: 0,
  189. possHolo: 0,
  190. possNormal: 0,
  191. possUniHolo: 0,
  192. possUniNormal: 0,
  193. };
  194. });
  195.  
  196. const overall_counts = {
  197. overallUniHolo: 0,
  198. overallHolo: 0,
  199. overallTTL: 0,
  200. overallUniNormal: 0,
  201. overallNormal: 0,
  202. };
  203.  
  204. Object.values(CardData.data).forEach((card) => {
  205. const category = this.categoriesTCG.find(
  206. (c) => c.id === `[${card.description_title}]`
  207. );
  208. if (category) {
  209. counts[category.desc].uniHolo++;
  210. counts[category.desc].uniNormal++;
  211. overall_counts.overallTTL++;
  212. }
  213. });
  214.  
  215. const uniHoloSetOverall = new Set();
  216. const uniNormalSetOverall = new Set();
  217.  
  218. currentCards.forEach((card) => {
  219. const new_id = `${card.id}${card.holo? "_h":""}`
  220. if (!this.newest_card_ids[new_id]) {
  221. this.newest_card_ids.set(new_id, card.cardNum);
  222. }
  223.  
  224. const category = Object.entries(CardData.data).find(
  225. (c) => c[0] === card.id
  226. );
  227. if (category) {
  228. if (card.holo) {
  229. counts[category[1].description_title].possHolo++;
  230. counts[category[1].description_title].ttlHolo++;
  231. overall_counts.overallHolo++;
  232. if (!uniHoloSetOverall.has(card.id)) {
  233. uniHoloSetOverall.add(card.id);
  234. overall_counts.overallUniHolo++;
  235. counts[category[1].description_title].possUniHolo++;
  236. }
  237. } else {
  238. counts[category[1].description_title].possNormal++;
  239. counts[category[1].description_title].ttlNormal++;
  240. overall_counts.overallNormal++;
  241. if (!uniNormalSetOverall.has(card.id)) {
  242. uniNormalSetOverall.add(card.id);
  243. overall_counts.overallUniNormal++;
  244. counts[category[1].description_title].possUniNormal++;
  245. }
  246. }
  247. }
  248. });
  249. return {counts, overall_counts};
  250. }
  251.  
  252. async identifyAndRemoveAbsentCards(db, objectStoreName, currentCards) {
  253. try {
  254. const dbCards = await this.fetchAllCardsFromDB(db, objectStoreName);
  255.  
  256. const current_card_nums = currentCards.map(card => card.cardNum)
  257.  
  258. dbCards.forEach((dbCard) => {
  259. const card_num = dbCard.cardNum
  260. if (!current_card_nums.includes(card_num)) {
  261. //console.log(`Card not found in current cards, removing: ${dbCardKey}`);
  262. this.removeCardFromDB(db, objectStoreName, card_num);
  263. }
  264. });
  265. } catch (error) {
  266. console.error('Error in identifyAndRemoveAbsentCards:', error);
  267. }
  268. }
  269.  
  270. removeCardFromDB(db, objectStoreName, cardKey) {
  271. const transaction = db.transaction([objectStoreName], "readwrite");
  272. const objectStore = transaction.objectStore(objectStoreName);
  273. const request = objectStore.delete(cardKey);
  274. request.onerror = (event) => {
  275. console.error("Error removing card from DB:", event.target.error);
  276. };
  277. request.onsuccess = () => {
  278. //console.log(`Card removed from DB: ${cardKey}`);
  279. };
  280. }
  281.  
  282. updateTcgSettings(categoryId, state) {
  283. const settings = JSON.parse(
  284. localStorage.getItem(`${playername}.tcgSettings`)
  285. );
  286. settings[categoryId] = state;
  287. localStorage.setItem(
  288. `${playername}.tcgSettings`,
  289. JSON.stringify(settings)
  290. );
  291. }
  292.  
  293. getTcgSetting(categoryId) {
  294. const settings = JSON.parse(
  295. localStorage.getItem(`${playername}.tcgSettings`)
  296. );
  297. return settings[categoryId];
  298. }
  299.  
  300. tcgBuyerNotifications() {
  301. let tcgTimerCheck = IdlePixelPlus.getVarOrDefault("tcg_timer", 0, "int");
  302. let tcgUnlocked = IdlePixelPlus.getVarOrDefault("tcg_active", 0, "int");
  303. const notifDiv = document.createElement("div");
  304. notifDiv.id = `notification-tcg-timer`;
  305. notifDiv.onclick = function () {
  306. switch_panels("panel-criptoe-tcg");
  307. Modals.open_buy_tcg();
  308. };
  309. notifDiv.className = "notification hover";
  310. notifDiv.style = "margin-right: 4px; margin-bottom: 4px; display: none";
  311. notifDiv.style.display = "inline-block";
  312.  
  313. let elem = document.createElement("img");
  314. elem.setAttribute("src", window.get_image("images/ash_50.png"))
  315. const notifIcon = elem;
  316. notifIcon.className = "w20";
  317.  
  318. const notifDivLabel = document.createElement("span");
  319. notifDivLabel.id = `notification-tcg-timer-label`;
  320. notifDivLabel.innerText = " Loading...";
  321. notifDivLabel.className = "color-white";
  322.  
  323. notifDiv.append(notifIcon, notifDivLabel);
  324. document.querySelector("#notifications-area").prepend(notifDiv);
  325. if (tcgUnlocked == 0 || !this.getConfig("tcgNotification")) {
  326. document.querySelector("#notification-tcg-timer").style.display =
  327. "none";
  328. }
  329. }
  330.  
  331. updateTCGNotification() {
  332. let tcgTimerCheck = IdlePixelPlus.getVarOrDefault("tcg_timer", 0, "int");
  333. let tcgUnlocked = IdlePixelPlus.getVarOrDefault("tcg_active", 0, "int");
  334. if (this.getConfig("tcgNotification") && tcgUnlocked != 0) {
  335. document.getElementById("notification-tcg-timer").style.display =
  336. "inline-block";
  337. if (tcgTimerCheck > 0) {
  338. let timerLabel = format_time(tcgTimerCheck);
  339. document.getElementById(
  340. "notification-tcg-timer-label"
  341. ).innerText = ` ${timerLabel}`;
  342. } else {
  343. document.getElementById(
  344. "notification-tcg-timer-label"
  345. ).innerText = ` Time to buy cards!`;
  346. }
  347. } else {
  348. document.getElementById("notification-tcg-timer").style.display =
  349. "none";
  350. }
  351. }
  352.  
  353. async checkForAndHandleDuplicates() {
  354. const sendTo = IdlePixelPlus.plugins.tcgDex.getConfig("sendTo");
  355. const enableSend = IdlePixelPlus.plugins.tcgDex.getConfig("enableSend");
  356. const cards = await this.fetchAllCardsFromDB(this.db, 'current_cards');
  357. const cardOccurrences = new Map();
  358.  
  359. if (!this.dupe_sending && sendTo !== playername) {
  360. this.dupe_sending = true;
  361. cards.forEach((card) => {
  362. const key = `${card.id}-${card.holo}`;
  363. if (cardOccurrences.has(key)) {
  364. cardOccurrences.get(key).push(card);
  365. } else {
  366. cardOccurrences.set(key, [card]);
  367. }
  368. });
  369.  
  370. cardOccurrences.forEach((occurrences, key) => {
  371. if (occurrences.length > 1) {
  372. occurrences.sort((a, b) => b.cardNum - a.cardNum);
  373.  
  374. for (let i = 0; i < (occurrences.length - 1); i++) {
  375. const duplicate = occurrences[i];
  376.  
  377. if (enableSend && sendTo) {
  378. websocket.send(`GIVE_TCG_CARD=${sendTo}~${duplicate.cardNum}`);
  379. }
  380. }
  381. }
  382. });
  383.  
  384. setTimeout(function () {
  385. CardData.fetchData();
  386. setTimeout(function () {
  387. this.dupe_sending = false;
  388. }.bind(this), 10000);
  389. }.bind(this), 20000);
  390. }
  391. }
  392.  
  393. async fetchAllCardsFromDB(db, objectStoreName) {
  394. return new Promise((resolve, reject) => {
  395. const transaction = db.transaction([objectStoreName], "readonly");
  396. const objectStore = transaction.objectStore(objectStoreName);
  397. const request = objectStore.getAll();
  398.  
  399. request.onerror = (event) => {
  400. console.error("Error fetching cards from DB:", event.target.error);
  401. reject(event.target.error);
  402. };
  403.  
  404. request.onsuccess = () => {
  405. resolve(request.result);
  406. };
  407. });
  408. }
  409.  
  410. add_open_multi_button(){
  411. const new_button = `<table data-bs-dismiss="modal" onclick="IdlePixelPlus.plugins.tcgDex.multi_open()" class="modal-table-button hover">
  412. <tbody><tr>
  413. <td>
  414. <img alt="" src="${window.get_image("images/tcg_back_50.png")}" class="w50" title="tcg_back_50">
  415. </td>
  416. <td>
  417. REVEAL MULTIPLE
  418. <hr>
  419. <span class="color-grey font-small">Reveal many cards for yourself.</span>
  420. </td>
  421. </tr>
  422. </tbody></table>`
  423.  
  424. let inner = document.getElementById("modal-open-tcg-unknown").querySelector("center").innerHTML
  425. inner += new_button
  426. document.getElementById("modal-open-tcg-unknown").querySelector("center").innerHTML = inner
  427.  
  428. const modal_string = `
  429. <div class="modal fade" id="tcg_open_multi_modal" tabindex="-1" data-bs-theme="dark">
  430. <div class="modal-dialog">
  431. <div class="modal-content justify-content-around d-flex flex-column" style="color: white; min-height:200px;">
  432. <div class="row"><div class="col d-flex justify-content-center">
  433. <img alt="" src="${window.get_image("images/tcg_back_50.png")}">
  434. </div></div>
  435. <div class="row"><div class="col d-flex justify-content-center">
  436. How many do you want to open?
  437. </div></div>
  438. <div class="row"><div class="col d-flex justify-content-center">
  439. <input type="number" id="tcg_multi_open_count">
  440. </div></div>
  441. <div class="row"><div class="col d-flex justify-content-center">
  442. <button id="tcg_multi_button" data-bs-dismiss="modal" class="bg-info-subtle"><span class="font-pixel hover">Open</span></button>
  443. </div></div>
  444. </div>
  445. </div>
  446. </div>`
  447. document.body.insertAdjacentHTML("beforeend", modal_string);
  448.  
  449. document.getElementById("tcg_multi_button").addEventListener("click", (e) => {
  450. let input = document.getElementById("tcg_multi_open_count").value
  451. if (input === "") {
  452. return
  453. } else {
  454. input = parseInt(input)
  455. }
  456. if (input > parseInt(window.var_tcg_unknown)){
  457. input = parseInt(window.var_tcg_unknown)
  458. }
  459.  
  460. IdlePixelPlus.plugins.tcgDex.pause_processing = true;
  461. setTimeout(function () {
  462. IdlePixelPlus.plugins.tcgDex.pause_processing = false;
  463. websocket.send("RFRESH_TCG_CLIENT")
  464. }, 2000)
  465. for (let i = 0; i < input; i++) {
  466. websocket.send("REVEAL_TCG_CARD")
  467. }
  468. })
  469. }
  470.  
  471. multi_open(){
  472. $("#tcg_open_multi_modal").modal("show")
  473. }
  474.  
  475. cardStyling() {
  476. const style = document.createElement("style");
  477. style.id = "styles-tcg-dex";
  478. style.textContent = `
  479. .tcg-card-inner {
  480. text-align: center;
  481. margin: 5px 18px;
  482. border: 2px solid black;
  483. background-color: #FEFEFE;
  484. box-shadow: 1px 1px 5px;
  485. padding: 25px 25px;
  486. }
  487.  
  488. .tcg-card {
  489. width: 200px;
  490. height: 300px;
  491. display: inline-block;
  492. border-radius: 10pt;
  493. box-shadow: 1px 1px 5px;
  494. margin: 5px;
  495. color: black;
  496. }
  497.  
  498. .tcg-card-title {
  499. font-weight: bold;
  500. font-size: 12pt;
  501. margin-left: 18px;
  502. margin-top: 4px;
  503. }
  504.  
  505. .tcg-card-inner-text {
  506. margin: 0px 18px;
  507. border: 1px solid black;
  508. border-radius: 5pt;
  509. background-color: #FEFEFE;
  510. padding: 5px 5px;
  511. font-size: 8pt;
  512. margin-top: 10px;
  513. margin-bottom: 4px;
  514. }
  515.  
  516. .tcg-card-rarity {
  517. font-weight: bold;
  518. font-size: 12pt;
  519. margin-right: 4px;
  520. text-align: right;
  521. font-style: italic;
  522. }
  523.  
  524. .tcg-card-type {
  525. font-weight: bold;
  526. font-size: 12pt;
  527. margin-left: 4px;
  528. text-align: left;
  529. }
  530.  
  531. .tcg-category-text {
  532. font-weight: bold;
  533. font-size: 12px;
  534. color: black;
  535. }
  536.  
  537. .tcgDex-card-container-open {
  538. margin-bottom: 20px;
  539. }
  540.  
  541. .tcgDex-card-container-closed {
  542. margin-bottom: 5px;
  543. }
  544. .tcgdex_category_label_row {
  545. display: inline-flex;
  546. width: 100%;
  547. height: 30px;
  548. font-weight: bolder;
  549. user-select: none;
  550. }
  551. .tcgdex_category_label_button {
  552. flex: 0 0 5%;
  553. align-content: center;
  554. text-align: center;
  555. }
  556. .tcgdex_category_label_cat {
  557. flex: 0 0 35%;
  558. align-content: center;
  559. }
  560. .tcgdex_category_label_total {
  561. flex: 0 0 10%;
  562. align-content: center;
  563. padding-left: 5px;
  564. }
  565. .tcgdex_category_label_counts {
  566. flex: 0 0 33%;
  567. align-content: center;
  568. }
  569. .tcgdex_category_label_counts_outer {
  570. flex: 0 0 25%;
  571. display: inline-flex;
  572. }
  573. .tcgdex_category_label_counts_label {
  574. padding-left: 5px;
  575. flex: 0 0 34%;
  576. align-content: center;
  577. }
  578. .tcgdex_category_new_label {
  579. flex: 0 0 55%;
  580. align-content: center;
  581. }
  582. .tcgdex_category_new_total {
  583. flex: 0 0 10%;
  584. align-content: center;
  585. }
  586. .tcgdex_category_new_values_holo {
  587. flex: 0 0 15%;
  588. align-content: center;
  589. }
  590. .tcgdex_category_new_values {
  591. flex: 0 0 15%;
  592. align-content: center;
  593. }
  594. `;
  595. document.head.appendChild(style);
  596. }
  597.  
  598. create_totals_bar_frag(overall_counts) {
  599. const template = document.getElementById("tcg_category_total_template")
  600. let clone = template.content.cloneNode(true)
  601.  
  602. clone.querySelector(`.ttl-cards-label`).textContent = `Total: ${overall_counts.overallHolo + overall_counts.overallNormal} `;
  603. clone.querySelector(`.uni-holo-label`).textContent = `U: ${overall_counts.overallUniHolo}/${overall_counts.overallTTL}`;
  604. clone.querySelector(`.ttl-holo-label`).textContent = `T: ${overall_counts.overallHolo}`;
  605. clone.querySelector(`.uni-normal-label`).textContent = `U: ${overall_counts.overallUniNormal}/${overall_counts.overallTTL}`;
  606. clone.querySelector(`.ttl-normal-label`).textContent = `T: ${overall_counts.overallNormal}`;
  607.  
  608. return clone;
  609. }
  610.  
  611. create_new_bar_frag() {
  612. const template = document.getElementById("tcg_category_new_template")
  613. let clone = template.content.cloneNode(true)
  614.  
  615. let loadVis = JSON.parse(localStorage.getItem(`${playername}.tcgSettings`))['new']
  616.  
  617. let category_div = clone.getElementById("tcgDex-New_Card-Container")
  618.  
  619. category_div.classList.add(loadVis ? "tcgDex-card-container-open" : "tcgDex-card-container-closed")
  620.  
  621. clone.querySelector(".tcg_category_container_inner").setAttribute("style", IdlePixelPlus.plugins.tcgDex.getTcgSetting("new") ? "" : "display: none;")
  622.  
  623. clone.querySelector(".tcg_new_timer_label").textContent = `New Cards (last ${this.new_card_timer} mins)`;
  624.  
  625. clone.querySelector(".fas").classList.add(IdlePixelPlus.plugins.tcgDex.getTcgSetting("new") ? "fa-eye-slash" : "fa-eye")
  626.  
  627. category_div.addEventListener("click", (event) => {
  628. if (event.target.closest(".tcg_category_container_inner")) {return;}
  629. const ele = event.currentTarget
  630. const category_inner = ele.querySelector(".tcg_category_container_inner")
  631. const isVisible = getComputedStyle(category_inner).display !== "none"
  632.  
  633. if (isVisible) {
  634. category_inner.style.display = "none"
  635. ele.querySelector(".fas").classList.remove("fa-eye-slash")
  636. ele.querySelector(".fas").classList.add("fa-eye")
  637. ele.classList.add("tcgDex-card-container-closed")
  638. ele.classList.remove("tcgDex-card-container-open")
  639. } else {
  640. category_inner.style.display = ""
  641. ele.querySelector(".fas").classList.add("fa-eye-slash")
  642. ele.querySelector(".fas").classList.remove("fa-eye")
  643. ele.classList.remove("tcgDex-card-container-closed")
  644. ele.classList.add("tcgDex-card-container-open")
  645. }
  646.  
  647. IdlePixelPlus.plugins.tcgDex.updateTcgSettings("new", !isVisible);
  648. });
  649.  
  650. return clone;
  651. }
  652.  
  653. draw_card_categories(card_container_frag, all_counts) {
  654. this.categoriesTCG.forEach((category) => {
  655. const template = document.getElementById("tcg_category_template");
  656. let row_frag = this.create_row_fragment(template, category);
  657.  
  658. card_container_frag.appendChild(row_frag);
  659. });
  660.  
  661. for (const [cat, counts] of Object.entries(all_counts)) {
  662. card_container_frag.querySelector(`#tcgDex-${cat}-Container .ttl-cards-label`).textContent = `Total: ${counts.possHolo + counts.possNormal}`;
  663. card_container_frag.querySelector(`#tcgDex-${cat}-Container .uni-holo-label`).textContent = `U: ${counts.possUniHolo}/${counts.uniHolo}`;
  664. card_container_frag.querySelector(`#tcgDex-${cat}-Container .ttl-holo-label`).textContent = `T: ${counts.ttlHolo}`;
  665. card_container_frag.querySelector(`#tcgDex-${cat}-Container .uni-normal-label`).textContent = `U: ${counts.possUniNormal}/${counts.uniNormal}`;
  666. card_container_frag.querySelector(`#tcgDex-${cat}-Container .ttl-normal-label`).textContent = `T: ${counts.ttlNormal}`;
  667. }
  668. }
  669.  
  670. create_card_template(){
  671. const card_template_str = `
  672. <template id="tcg_card_template">
  673. <div id="" onclick="IdlePixelPlus.plugins.tcgDex.send_card(this.getAttribute('data-card-id'), this.getAttribute('id'))" style="" class='tcg-card hover'>
  674. <div class='row d-flex justify-content-around w-100'>
  675. <div class='col text-start' style="max-width:80%; margin-right:0; padding-right:1px; padding-left:0">
  676. <div class='tcg-card-title' style="white-space:nowrap; text-overflow:clip; overflow:hidden;"></div>
  677. </div>
  678. <div class='col-auto text-end' style="margin-top:4px; padding: 0; max-width:19%">
  679. <span class="dupe_count" style="font-weight: bolder;"></span>
  680. </div>
  681. </div>
  682. <div class='tcg-card-inner'>
  683. <img src="" class='w50'>
  684. </div>
  685. <div class='tcg-card-inner-text'>
  686. <span class='tcg-category-text'></span>
  687. <br>
  688. <br>
  689. <span class='tcg_card_zalgo'>𓀚𓁁𓂧𓃢𓃴𓄜𓈤𓈤𓊙𓐈𓀚𓁁𓂧𓃢𓃴<br>𓄜𓈤𓈤𓊙𓐈𓀚𓁁𓂧𓃢𓃴𓄜𓈤𓈤𓊙𓐈<br>𓀚𓁁𓂧𓃢𓃴𓄜𓈤𓈤𓊙𓐈𓀚𓁁𓂧𓃢𓃴<br>𓄜𓈤𓈤𓊙𓐈𓀚𓁁𓂧𓃢𓃴𓄜𓈤𓈤𓊙𓐈</span>
  690. </div>
  691. <div class="row" style="display: flex; flex-wrap:nowrap">
  692. <div class="col" style="flex: 0 0 50%; padding-right:0px;">
  693. <span class="tcg-card-type"></span>
  694. </div>
  695. <div class="col" style="flex: 0 0 50%; text-align: end; padding-left: 0px;flex-wrap:nowrap;">
  696. <span class="tcg-card-rarity"></span>
  697. </div>
  698. </div>
  699. </div>
  700. </template>
  701. `
  702. $("body").append($(card_template_str))
  703. }
  704.  
  705. create_row_template(){
  706. const row_template_str = `
  707. <template id="tcg_category_template">
  708. <div class="tcgdex_card_container">
  709. <div class="tcgdex_category_label_row">
  710. <div class="col tcgdex_category_label_button">
  711. <i class="fas"></i>
  712. </div>
  713. <div class="col tcgdex_category_label_cat">
  714. <span class="labelSpan"></span>
  715. </div>
  716. <div class="col tcgdex_category_label_total">
  717. <span class="ttl-cards-label"></span>
  718. </div>
  719. <div class="col tcgdex_category_label_counts_outer">
  720. <div class="tcgdex_category_label_counts_label">Holo:</div>
  721. <div class="tcgdex_category_label_counts">
  722. <span class="ttl-holo-label"></span>
  723. </div>
  724. <div class="tcgdex_category_label_counts">
  725. <span class="uni-holo-label"></span>
  726. </div>
  727. </div>
  728. <div class="col tcgdex_category_label_counts_outer">
  729. <div class="tcgdex_category_label_counts_label">Normal:</div>
  730. <div class="tcgdex_category_label_counts">
  731. <span class="ttl-normal-label"></span>
  732. </div>
  733. <div class="tcgdex_category_label_counts">
  734. <span class="uni-normal-label"></span>
  735. </div>
  736. </div>
  737. </div>
  738. <br>
  739. <div class="tcg_category_container_inner" id="tcgDex-LIMITED-Container-Inner" style="display: none;"></div>
  740. </div>
  741. </template>
  742. `
  743. $("body").append($(row_template_str))
  744. }
  745.  
  746. create_total_row_template(){
  747. const row_template_str = `
  748. <template id="tcg_category_total_template">
  749. <div class="tcgdex_category_label_row" style="background-color:cyan; color:black;">
  750. <div class="col tcgdex_category_label_button"></div>
  751. <div class="col tcgdex_category_label_cat">T = Total &amp; U = Unique</div>
  752. <div class="col tcgdex_category_label_total" style="border-left: 1px solid black;">
  753. <span class="ttl-cards-label"></span>
  754. </div>
  755. <div class="col tcgdex_category_label_counts_outer" style="border-left: 1px solid black;">
  756. <div class="tcgdex_category_label_counts_label">Holo:</div>
  757. <div class="tcgdex_category_label_counts">
  758. <span class="ttl-holo-label"></span>
  759. </div>
  760. <div class="tcgdex_category_label_counts">
  761. <span class="uni-holo-label"></span>
  762. </div>
  763. </div>
  764. <div class="col tcgdex_category_label_counts_outer" style="border-left: 1px solid black;">
  765. <div class="tcgdex_category_label_counts_label">Normal:</div>
  766. <div class="tcgdex_category_label_counts">
  767. <span class="ttl-normal-label"></span>
  768. </div>
  769. <div class="tcgdex_category_label_counts">
  770. <span class="uni-normal-label"></span>
  771. </div>
  772. </div>
  773. </div>
  774. </template>
  775. `
  776. $("body").append($(row_template_str))
  777. }
  778.  
  779. create_new_row_template(){
  780. const row_template_str = `
  781. <template id="tcg_category_new_template">
  782. <div id="tcgDex-New_Card-Container">
  783. <div class="tcgdex_category_label_row" style="background-color:gray; color:black;">
  784. <div class="col tcgdex_category_label_button">
  785. <i class="fas"></i>
  786. </div>
  787. <div class="col tcgdex_category_new_label">
  788. <span class="tcg_new_timer_label"></span>
  789. </div>
  790. <div class="col tcgdex_category_new_total"></div>
  791. <div class="col tcgdex_category_new_values_holo"></div>
  792. <div class="col tcgdex_category_new_values"></div>
  793. </div>
  794. <br>
  795. <div class="tcg_category_container_inner"></div>
  796. </div>
  797. </template>
  798. `
  799. $("body").append($(row_template_str))
  800. }
  801.  
  802. add_link_to_collection(){
  803. const url = `https://luxferre.dev/idlepixel/tcg?user=${window.var_username}`
  804.  
  805. const button = `
  806. <div id="tcg_collection_button" class="itembox-rings hover" onclick="window.open('${url}', '_blank')">
  807. <div class="center mt-1"><img alt="" src="https://cdn.idle-pixel.com/images/book_template.png" title="Collection Viewer"></div>
  808. <div class="center mt-2"><span>CLXN VIEW</span></div>
  809. </div>
  810. `
  811.  
  812. const boxes = document.getElementById("panel-criptoe-tcg").querySelectorAll(".itembox-rings")
  813. const stats = boxes[boxes.length-1].parentNode
  814. stats.insertAdjacentHTML("afterend", button)
  815. }
  816.  
  817. add_link_to_received(){
  818. const button = `
  819. <div id="received_card_button" class="itembox-rings hover">
  820. <div class="center mt-1"><img alt="" height="50px" width="50px" src="https://cdn.idle-pixel.com/images/castle_chest.png" title="Received Cards"></div>
  821. <div class="center mt-2"><span>Received</span></div>
  822. </div>
  823. `
  824.  
  825. document.getElementById("tcg_collection_button").insertAdjacentHTML("beforebegin", button)
  826.  
  827. document.getElementById("received_card_button").addEventListener("click", function(){
  828. document.getElementById("received_card_button").classList.remove("animation-glow")
  829. let inner_html = ""
  830. IdlePixelPlus.plugins.tcgDex.cards_received.forEach(card=>{
  831. inner_html += `<li>${card}</li>`
  832. })
  833. document.getElementById("received_card_list").innerHTML = inner_html
  834. $("#tcg_received_list_modal").modal("show")
  835. })
  836. }
  837.  
  838. create_received_list_modal() {
  839. const modal_string = `
  840. <div class="modal fade" id="tcg_received_list_modal" tabindex="-1" data-bs-theme="dark">
  841. <div class="modal-dialog">
  842. <div class="modal-content" style="color: white;">
  843. <ul style="list-style-type: circle; margin-top: 5px;" id="received_card_list"></ul>
  844. </div>
  845. </div>
  846. </div>`
  847. document.body.insertAdjacentHTML("beforeend", modal_string);
  848. }
  849.  
  850. initialisation(){
  851. if(!CardData.data){
  852. setTimeout(() => {
  853. IdlePixelPlus.plugins.tcgDex.initialisation()
  854. }, 1000)
  855. return
  856. }
  857.  
  858. this.create_card_template()
  859. this.create_row_template()
  860. this.create_total_row_template()
  861. this.create_new_row_template()
  862. this.cardStyling()
  863. this.create_received_list_modal()
  864. this.add_link_to_collection()
  865. this.add_link_to_received()
  866. this.add_open_multi_button()
  867.  
  868. this.categoriesTCG = this.getCategoryData();
  869.  
  870. playername = IdlePixelPlus.getVarOrDefault("username", "", "string")
  871. if (!localStorage.getItem(`${playername}.tcgSettings`)) {
  872. let defaultSettings = this.categoriesTCG.reduce((settings, category) => {
  873. settings[category.desc] = true;
  874. return settings;
  875. }, {});
  876. defaultSettings.new = true;
  877. localStorage.setItem(
  878. `${playername}.tcgSettings`,
  879. JSON.stringify(defaultSettings)
  880. );
  881. } else {
  882. IdlePixelPlus.plugins.tcgDex.ensureNewSettingExists();
  883. }
  884.  
  885. this.initializeDatabase()
  886. this.tcgBuyerNotifications()
  887. this.updateTCGNotification()
  888.  
  889. const popup_handler = function(title, image_path, message, primary_button_text, secondary_button_text, command, force_unclosable){
  890. let name, card
  891. if (message.includes("unrevealed")){
  892. name = message.split("from ")[1]
  893. card = "Unrevealed"
  894. } else {
  895. name = message.split("<")[0].slice(27)
  896. card = message.split("<")[3].split(">")[1]
  897. }
  898.  
  899. let time = new Date()
  900. time = time.toLocaleString()
  901.  
  902. IdlePixelPlus.plugins.tcgDex.cards_received.push(`${card}(${name}) [${time}]`)
  903. document.getElementById("received_card_button").classList.add("animation-glow")
  904. return [title, image_path, message, primary_button_text, secondary_button_text, command, force_unclosable]
  905. }
  906.  
  907. dialoguer.register_handler("TRADE COMPLETED", popup_handler, false)
  908.  
  909. this.card_order = new Map()
  910. let order = 1
  911. Object.keys(CardData.data).forEach((card_name) => {
  912. this.card_order.set(`${card_name}_h`, order++);
  913. this.card_order.set(`${card_name}`, order++);
  914. })
  915. this.login_loaded = true
  916. }
  917.  
  918. onLogin() {
  919. this.new_card_timer = IdlePixelPlus.plugins.tcgDex.getConfig("newCardTimer")
  920. CToe.loadCards = function () {}
  921. if (!CardData.data) {
  922. CardData.fetchData();
  923. }
  924.  
  925. this.initialisation()
  926. }
  927.  
  928. onVariableSet(key, valueBefore, valueAfter) {
  929. if (this.login_loaded) {
  930. if (key.startsWith("tcg") && valueBefore != valueAfter) {
  931. IdlePixelPlus.plugins.tcgDex.updateTCGNotification();
  932. }
  933. }
  934. }
  935.  
  936. onConfigChange() {
  937. if (this.login_loaded) {
  938. IdlePixelPlus.plugins.tcgDex.updateTCGNotification();
  939. this.new_card_timer = IdlePixelPlus.plugins.tcgDex.getConfig("newCardTimer")
  940. }
  941. }
  942.  
  943. send_card(card_id, card_name){
  944. Modals.open_tcg_give_card(null, card_id)
  945. card_name = card_name.replace("_Normal", "").replace("_Holo", "_h")
  946. IdlePixelPlus.plugins.tcgDex.newest_card_ids.delete(card_name)
  947. }
  948.  
  949. create_card_fragment(template, card){
  950. const id = card.cardNum
  951. const holo = card.holo
  952. const card_data = CardData.data[card.id]
  953.  
  954. const rarity_map = {
  955. common: "Common",
  956. uncommon: "Uncommon",
  957. rare: "Rare",
  958. very_rare: "Very Rare",
  959. legendary: "Legendary"
  960. }
  961.  
  962. let clone = template.content.cloneNode(true)
  963.  
  964. let tcg_outer = clone.querySelector(".tcg-card")
  965. tcg_outer.setAttribute("data-card-id", id)
  966. tcg_outer.setAttribute("data-card-cat", card_data.description_title)
  967.  
  968. const styles = `${card_data.border_css}${card_data.background_css}`
  969. tcg_outer.setAttribute("style", styles)
  970.  
  971. const label = card_data.label.replaceAll('MOONSTONE', 'M. STONE').replaceAll('PROMETHIUM', 'PROM.').replaceAll('WOODEN ARROWS', 'WOOD ARROWS').replaceAll('STINGER ', 'STING ')
  972.  
  973. clone.querySelector(".tcg-card-title").innerText = label
  974. clone.querySelector(".tcg-card-rarity").innerText = `(${rarity_map[card_data.rarity]})`
  975. clone.querySelector("img").setAttribute("src", `https://cdn.idle-pixel.com/images/${card_data.image}`)
  976. clone.querySelector(".tcg-category-text").innerText = `[${card_data.description_title}]`
  977.  
  978. if (card.count){
  979. clone.querySelector(".dupe_count").innerText = `x${card.count}`
  980. }
  981.  
  982.  
  983. if(holo){
  984. tcg_outer.id = `${card.id}_Holo`
  985. clone.querySelector(".tcg-card-type").innerText = " Holo"
  986. clone.querySelector(".tcg-card-inner").classList.add("holo")
  987. clone.querySelector(".tcg_card_zalgo").classList.add("shine")
  988. } else {
  989. tcg_outer.id = `${card.id}_Normal`
  990. clone.querySelector(".tcg-card-type").innerText = " Normal"
  991. clone.querySelector(".tcg_card_zalgo").classList.add("color-red")
  992. }
  993.  
  994. return clone;
  995. }
  996.  
  997. create_row_fragment(template, category){
  998. let loadVis = IdlePixelPlus.plugins.tcgDex.getTcgSetting(category.desc);
  999. let rowBGColor = this.row_bg_colours[category.desc];
  1000. let rowTextColor = this.row_text_colours[category.desc];
  1001.  
  1002. let clone = template.content.cloneNode(true)
  1003.  
  1004. let category_div = clone.querySelector(".tcgdex_card_container")
  1005. category_div.id = `tcgDex-${category.desc}-Container`
  1006. category_div.classList.add(loadVis ? "tcgDex-card-container-open" : "tcgDex-card-container-closed")
  1007.  
  1008. let category_inner = clone.querySelector(".tcg_category_container_inner")
  1009. category_inner.id = `tcgDex-${category.desc}-Container-Inner`;
  1010. category_inner.setAttribute("style", IdlePixelPlus.plugins.tcgDex.getTcgSetting(category.desc) ? "" : "display: none;")
  1011.  
  1012. clone.querySelector(".tcgdex_category_label_row").setAttribute("style", `background-color: ${rowBGColor}; color: ${rowTextColor};`)
  1013.  
  1014. clone.querySelector(".fas").classList.add(IdlePixelPlus.plugins.tcgDex.getTcgSetting(category.desc) ? "fa-eye-slash" : "fa-eye")
  1015.  
  1016. clone.querySelector(".tcgdex_category_label_counts_outer").setAttribute("style", `border-left: 1px solid ${rowTextColor};`)
  1017. clone.querySelector(".tcgdex_category_label_total").setAttribute("style", `border-left: 1px solid ${rowTextColor};`)
  1018.  
  1019. clone.querySelector(".labelSpan").innerHTML = category.label
  1020.  
  1021. category_div.addEventListener("click", (event) => {
  1022. if (event.target.closest(".tcg_category_container_inner")) {return;}
  1023. const ele = event.currentTarget
  1024. const category_inner = ele.querySelector(".tcg_category_container_inner")
  1025. const isVisible = getComputedStyle(category_inner).display !== "none"
  1026.  
  1027. if (isVisible) {
  1028. category_inner.style.display = "none"
  1029. ele.querySelector(".fas").classList.remove("fa-eye-slash")
  1030. ele.querySelector(".fas").classList.add("fa-eye")
  1031. ele.classList.add("tcgDex-card-container-closed")
  1032. ele.classList.remove("tcgDex-card-container-open")
  1033. } else {
  1034. category_inner.style.display = ""
  1035. ele.querySelector(".fas").classList.add("fa-eye-slash")
  1036. ele.querySelector(".fas").classList.remove("fa-eye")
  1037. ele.classList.remove("tcgDex-card-container-closed")
  1038. ele.classList.add("tcgDex-card-container-open")
  1039. }
  1040.  
  1041. IdlePixelPlus.plugins.tcgDex.updateTcgSettings(
  1042. category.desc,
  1043. !isVisible
  1044. );
  1045. });
  1046.  
  1047. return clone;
  1048. }
  1049.  
  1050. draw_cards(currentCards, card_type_count){
  1051. document.getElementById("tcg-area-context").innerHTML = ""
  1052.  
  1053. const template = document.getElementById("tcg_card_template")
  1054. let card_container_frag = document.createDocumentFragment()
  1055.  
  1056. const {counts, overall_counts} = this.card_counts(currentCards)
  1057.  
  1058. card_container_frag.appendChild(this.create_totals_bar_frag(overall_counts))
  1059. card_container_frag.appendChild(this.create_new_bar_frag())
  1060. IdlePixelPlus.plugins['tcgDex'].draw_card_categories(card_container_frag, counts);
  1061.  
  1062. document.getElementById("tcg-area-context").appendChild(card_container_frag)
  1063.  
  1064. const card_sorter = {}
  1065. this.categoriesTCG.forEach((category) => {
  1066. card_sorter[category.desc] = []
  1067. })
  1068.  
  1069. for (const [card, count] of Object.entries(card_type_count)) {
  1070. const split_idx = card.lastIndexOf("_")
  1071. const card_id = card.slice(0, split_idx)
  1072. const holo = card.slice(split_idx + 1) === "Holo"
  1073.  
  1074. const order_key = holo? `${card_id}_h`: card_id
  1075.  
  1076. const card_data = {
  1077. id: card_id,
  1078. holo: holo,
  1079. cardNum: this.newest_card_ids.get(order_key),
  1080. count: count,
  1081. }
  1082.  
  1083. const card_fragment = this.create_card_fragment(template, card_data)
  1084. const card_category = card_fragment.querySelector(".tcg-card").getAttribute("data-card-cat")
  1085.  
  1086. const card_position = this.card_order.get(order_key)
  1087. card_sorter[card_category][card_position] = card_fragment
  1088. }
  1089.  
  1090. for (const [cat, cards] of Object.entries(card_sorter)) {
  1091. let cat_frag = document.createDocumentFragment()
  1092. cards.forEach(card_frag => {
  1093. cat_frag.appendChild(card_frag)
  1094. })
  1095. document.getElementById(`tcgDex-${cat}-Container-Inner`).appendChild(cat_frag)
  1096. }
  1097. }
  1098.  
  1099. handle_new_cards(newCards){
  1100. newCards.sort((a, b) => b.received_datetime - a.received_datetime);
  1101. const new_card_container = document.getElementById("tcgDex-New_Card-Container").querySelector(".tcg_category_container_inner")
  1102. new_card_container.innerHTML = "";
  1103. const template = document.getElementById("tcg_card_template")
  1104. let card_container_frag = document.createDocumentFragment()
  1105.  
  1106. for (const card of Object.values(newCards)) {
  1107. card.count = false
  1108. const card_fragment = this.create_card_fragment(template, card);
  1109.  
  1110. card_container_frag.appendChild(card_fragment)
  1111. }
  1112.  
  1113. new_card_container.appendChild(card_container_frag)
  1114. card_container_frag.innerHTML = ""
  1115. }
  1116.  
  1117. find_new_cards(currentCards){
  1118. const new_cards = []
  1119. const objectStoreName = `current_cards`;
  1120. const transaction = this.db.transaction([objectStoreName], "readwrite");
  1121. const objectStore = transaction.objectStore(objectStoreName);
  1122.  
  1123. currentCards.forEach((card) => {
  1124. const key = card.cardNum
  1125. const getRequest = objectStore.get(key);
  1126. getRequest.onsuccess = (event) => {
  1127. let result = event.target.result;
  1128. if (result) {
  1129. let now = new Date();
  1130. let timeBefore = new Date(now.getTime() - this.new_card_timer * 60 * 1000);
  1131.  
  1132. let receivedDateTime = result.received_datetime;
  1133. if (receivedDateTime > timeBefore) {
  1134. new_cards.push({
  1135. cardNum: result.cardNum,
  1136. id: result.id,
  1137. holo: result.holo,
  1138. received_datetime: receivedDateTime,
  1139. });
  1140. }
  1141. } else {
  1142. const cardData = {
  1143. cardNum: card.cardNum,
  1144. id: card.id,
  1145. holo: card.holo,
  1146. received_datetime: new Date(),
  1147. };
  1148. const addRequest = objectStore.add(cardData);
  1149. addRequest.onerror = (event) => {
  1150. console.error("Error adding new card:", event.target.error);
  1151. };
  1152. addRequest.onsuccess = (event) => {
  1153. // console.log("Successfully adding new card:", event.target.result);
  1154. };
  1155. new_cards.push({
  1156. cardNum: card.cardNum,
  1157. id: card.id,
  1158. holo: card.holo,
  1159. received_datetime: new Date(),
  1160. });
  1161. }
  1162. };
  1163. getRequest.onerror = (event) => {
  1164. console.error("Error fetching card record:", event.target.error);
  1165. };
  1166. });
  1167. return new_cards;
  1168. }
  1169.  
  1170. parse_card_stream(parts){
  1171. const current_cards = []
  1172. const card_type_count = {};
  1173.  
  1174. for (let i = 0; i < parts.length; i += 3) {
  1175. const cardNum = parts[i];
  1176. const cardKey = parts[i + 1];
  1177. const isHolo = parts[i + 2] === "true";
  1178. const idHolo = isHolo ? "Holo" : "Normal";
  1179. const countKey = `${cardKey}_${idHolo}`;
  1180.  
  1181. current_cards.push({
  1182. id: cardKey,
  1183. cardNum: parseInt(cardNum),
  1184. holo: isHolo,
  1185. })
  1186.  
  1187. // Increment or initialise count for card type
  1188. card_type_count[countKey] = (card_type_count[countKey] ?? 0) + 1
  1189. }
  1190.  
  1191. return {current_cards, card_type_count};
  1192. }
  1193.  
  1194. onMessageReceived(data) {
  1195. if (data.startsWith("REFRESH_TCG")) {
  1196. if(this.pause_processing){return;}
  1197. const parts = data.replace("REFRESH_TCG=", "").split("~")
  1198.  
  1199. const {current_cards, card_type_count} = this.parse_card_stream(parts)
  1200.  
  1201. const new_cards = this.find_new_cards(current_cards);
  1202.  
  1203. void this.identifyAndRemoveAbsentCards(this.db, `current_cards`, current_cards)
  1204.  
  1205. this.draw_cards(current_cards, card_type_count);
  1206.  
  1207. setTimeout(() => this.handle_new_cards(new_cards), 2000)
  1208.  
  1209. void this.checkForAndHandleDuplicates()
  1210. }
  1211. }
  1212. }
  1213.  
  1214. const plugin = new tcgDex();
  1215. IdlePixelPlus.registerPlugin(plugin);
  1216. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址