IdlePixel+ New Card Interface

Improved interface for opening new cards, receiving cards in trade & trading cards

  1. // ==UserScript==
  2. // @name IdlePixel+ New Card Interface
  3. // @namespace lbtechnology.info
  4. // @version 1.2.3
  5. // @description Improved interface for opening new cards, receiving cards in trade & trading cards
  6. // @author Zlef
  7. // @license MIT
  8. // @match *://idle-pixel.com/login/play*
  9. // @grant none
  10. // @icon https://d1xsc8x7nc5q8t.cloudfront.net/images/tcg_back_50.png
  11. // @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. class TCGRevamp extends IdlePixelPlusPlugin {
  18. constructor() {
  19. super("TCG Interface Changes", {
  20. about: {
  21. name: GM_info.script.name,
  22. version: GM_info.script.version,
  23. author: GM_info.script.author,
  24. description: GM_info.script.description
  25. }
  26. });
  27.  
  28. this.showPopup = false;
  29. this.messageStart = "You got a"
  30. this.trade = false;
  31. this.card = "";
  32. this.refreshTCG = "";
  33. this.currentPopup = null;
  34. this.savedUsernamesTCG = null;
  35. this.previousTradeUsername = null;
  36. this.inCombat = false;
  37. this.inCombatTrade = false;
  38. this.loadUsernames();
  39.  
  40. this.overlay = document.createElement('div');
  41. this.overlay.id = 'newCardOverlay';
  42. this.overlay.style.position = 'fixed';
  43. this.overlay.style.top = '0';
  44. this.overlay.style.left = '0';
  45. this.overlay.style.width = '100%';
  46. this.overlay.style.height = '100%';
  47. this.overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  48. this.overlay.style.zIndex = '1000';
  49. this.overlay.style.display = 'flex';
  50. this.overlay.style.justifyContent = 'center';
  51. this.overlay.style.alignItems = 'center';
  52. this.overlay.addEventListener('click', (event) => {
  53. if (event.target === this.overlay) {
  54. this.closePopup();
  55. }
  56. });
  57.  
  58. window.addEventListener('resize', this.adjustPopupPosition.bind(this));
  59. }
  60.  
  61. onLogin() {
  62. this.originalTcgGiveCard = Modals.open_tcg_give_card;
  63.  
  64. Modals.open_tcg_give_card = (modal_id, card) => {
  65. if (!modal_id) {
  66. this.newTradePopup(card);
  67. } else {
  68. this.originalTcgGiveCard(modal_id, card);
  69. }
  70. };
  71.  
  72. if (!CardData.data) {
  73. CardData.fetchData();
  74. }
  75. this.monitorRevealTCG();
  76. }
  77.  
  78. saveUsernames(){
  79. const saveData = JSON.stringify({usernames: this.savedUsernamesTCG});
  80. localStorage.setItem(`savedUsernamesTCG`, saveData);
  81. }
  82.  
  83. loadUsernames(){
  84. const savedUsernamesTCG = localStorage.getItem(`savedUsernamesTCG`);
  85.  
  86. if (savedUsernamesTCG){
  87. this.savedUsernamesTCG = JSON.parse(savedUsernamesTCG).usernames;
  88. } else {
  89. this.savedUsernamesTCG = [];
  90. }
  91. }
  92.  
  93. monitorRevealTCG() {
  94. const originalWebSocketSend = WebSocket.prototype.send;
  95. const self = this;
  96. WebSocket.prototype.send = function(data) {
  97. try {
  98. originalWebSocketSend.call(this, data);
  99. if (data === 'REVEAL_TCG_CARD') {
  100. self.showPopup = true;
  101. }
  102. } catch (error) {
  103. console.error('Error in overridden WebSocket send:', error);
  104. }
  105. };
  106. }
  107.  
  108. onMessageReceived(data){
  109. const originalOpenImageModal = Modals.open_image_modal;
  110. const self = this;
  111.  
  112. if (data.includes("OPEN_DIALOGUE")){
  113. console.log("Open dialogue message received");
  114. Modals.open_image_modal = function(title, imgUrl, description, footerText, closeBtnText, anotherBtnText, isModalDismissible) {
  115. if (description.includes("You were given a card from")) {
  116. console.log("Opening custom dialogue");
  117. const usernameRegex = /You were given a card from (.*?)<br \/>/;
  118. const cardRegex = /<span class='color-grey'>(.*?)<\/span>/;
  119.  
  120. const usernameMatch = description.match(usernameRegex);
  121. const cardMatch = description.match(cardRegex);
  122.  
  123. let username = "";
  124. let card = "";
  125.  
  126. if (usernameMatch && usernameMatch.length > 1) {
  127. username = usernameMatch[1];
  128. }
  129.  
  130. if (cardMatch && cardMatch.length > 1) {
  131. self.card = cardMatch[1];
  132. }
  133.  
  134. self.messageStart = `${username} sent you a`;
  135. self.trade = true;
  136. self.showPopup = true;
  137. } else {
  138. console.log("Opening original dialogue");
  139. originalOpenImageModal.call(this, title, imgUrl, description, footerText, closeBtnText, anotherBtnText, isModalDismissible);
  140. }
  141. };
  142. }
  143.  
  144. if (data.includes("REFRESH_TCG")){
  145. this.refreshTCG = data;
  146. // console.log("In REFRESH_TCG, checking for inCombat");
  147. if (this.trade && this.inCombat){
  148. this.showPopup = false;
  149. this.inCombatTrade = true;
  150. // console.log("inCombatTrade set to True");
  151. }
  152. if (this.showPopup){
  153. if (this.trade){
  154. this.getCardInfo();
  155. this.trade = false;
  156. this.card = "";
  157. } else {
  158. this.getCardInfo();
  159. }
  160.  
  161. this.showPopup = false;
  162. this.messageStart = "You got a";
  163. }
  164. }
  165. if (data.includes('START_RAID')){
  166. this.inCombat = true;
  167. // console.log("In raid");
  168. }
  169. if (data.includes('RAIDS_TEAM_INFO_DESTROYED') || data.includes('RAIDS REWARD')){
  170. // console.log("Exited raid");
  171. this.inCombat = false;
  172. if (this.inCombatTrade){
  173. // console.log("inCombatTrade is true, opening modal");
  174. this.inCombatTrade = false;
  175. this.openSimplePopup("Trading Card Game", "You received card(s) during in combat!");
  176. }
  177. }
  178. }
  179.  
  180. onCombatStart(){
  181. this.inCombat = true;
  182. }
  183.  
  184. onCombatEnd(){
  185. this.inCombat = false;
  186. if (this.inCombatTrade){
  187. this.inCombatTrade = false;
  188. this.openSimplePopup("Trading Card Game", "You received card(s) during in combat!");
  189. }
  190. }
  191.  
  192. getCardInfoTrade(cardid) {
  193. const cardData = this.refreshTCG.replace('REFRESH_TCG=', '').split('~');
  194. const index = cardData.indexOf(cardid);
  195. const isThisYourCard = [cardData[index], cardData[index + 1], cardData[index + 2]];
  196.  
  197. const isHolo = isThisYourCard[2] === 'true';
  198. const cardHTML = CardData.getCardHTML(isThisYourCard[0], isThisYourCard[1], isHolo);
  199. return cardHTML;
  200. }
  201.  
  202. getCardInfo() {
  203. const cardData = this.refreshTCG.replace('REFRESH_TCG=', '').split('~');
  204. let isHolo = 'false';
  205.  
  206. if (this.trade) {
  207. const cardName = this.card.replace(/ \(holo\)$/, '');
  208. const index = cardData.indexOf(cardName);
  209. if (index !== -1) {
  210. const id = cardData[index - 1];
  211. const nameKey = cardData[index];
  212. const holo = cardData[index + 1];
  213. isHolo = this.card.includes("(holo)") ? 'true' : holo;
  214. this.displayNewCard(id, nameKey, isHolo);
  215. }
  216. } else {
  217. if (cardData.length >= 3) {
  218. const id = cardData[0];
  219. const nameKey = cardData[1];
  220. isHolo = cardData[2];
  221. this.displayNewCard(id, nameKey, isHolo);
  222. }
  223. }
  224. }
  225.  
  226. getCardInfoUnified(cardPart, identifier) {
  227. const cardData = this.refreshTCG.replace('REFRESH_TCG=', '').split('~');
  228. const index = cardData.indexOf(identifier);
  229. let id, nameKey, holo, isHolo;
  230.  
  231. if (identifier === 'card_id') {
  232. id = cardPart;
  233. nameKey = cardData[index + 1];
  234. holo = cardData[index + 2];
  235. } else if (identifier === 'card_key') {
  236. id = cardData[index - 1];
  237. nameKey = cardPart;
  238. holo = cardData[index + 1];
  239.  
  240. if (this.isTrade) {
  241. nameKey = this.refactorCardKey(nameKey);
  242. holo = this.card.includes("(holo)") ? 'true' : holo;
  243. }
  244. } else {
  245. console.error('Invalid card target type');
  246. return;
  247. }
  248.  
  249. isHolo = (holo === 'true');
  250. this.displayNewCard(id, nameKey, isHolo);
  251. }
  252.  
  253. displayNewCard(cardId, cardNameKey, holo) {
  254. const cardName = cardNameKey.replace('tcg_', '').replace(/_/g, ' ').replace(" icon", "");
  255. const isHolo = holo === 'true';
  256. let bloodyVowels = "";
  257.  
  258. const vowels = ['a', 'e', 'i', 'o', 'u'];
  259. if (vowels.some(vowel => cardName.toLowerCase().startsWith(vowel))) {
  260. bloodyVowels = "n";
  261. }
  262.  
  263. const message = isHolo ? `${this.messageStart} holo ${cardName} card!` : `${this.messageStart}${bloodyVowels} ${cardName} card!`;
  264.  
  265. const cardHTML = CardData.getCardHTML(cardId, cardNameKey, isHolo);
  266.  
  267. this.newCardOverlay(message, cardHTML);
  268. }
  269.  
  270. updateUserListDisplay() {
  271. this.userListContainer.innerHTML = '';
  272.  
  273. const table = document.createElement('table');
  274. table.className = 'table table-hover';
  275.  
  276. const thead = document.createElement('thead');
  277. thead.innerHTML = '<tr><th scope="col">Saved users</th><th scope="col"></th></tr>';
  278. table.appendChild(thead);
  279.  
  280. const tbody = document.createElement('tbody');
  281. table.appendChild(tbody);
  282.  
  283. const usernames = this.savedUsernamesTCG;
  284. const minimumRows = 0;
  285. const rowsToCreate = Math.max(minimumRows, usernames.length);
  286.  
  287. for (let i = 0; rowsToCreate > i; i++) {
  288. const tr = document.createElement('tr');
  289. const usernameCell = document.createElement('td');
  290.  
  291. usernameCell.style.cursor = 'pointer';
  292. usernameCell.style.verticalAlign = 'middle';
  293. usernameCell.style.fontSize = '1.2em';
  294. usernameCell.addEventListener('click', () => {
  295. const usernameInput = document.getElementById('recipientUsernameInput');
  296. if (usernameInput) {
  297. usernameInput.value = usernames[i];
  298. }
  299. });
  300.  
  301. const actionCell = document.createElement('td');
  302. actionCell.align = "right";
  303. actionCell.style.width = '80px';
  304.  
  305. if (i < usernames.length) {
  306. usernameCell.textContent = usernames[i];
  307. const deleteButton = document.createElement('button');
  308. deleteButton.textContent = 'Delete';
  309. deleteButton.className = 'btn btn-danger btn-sm';
  310. deleteButton.style.padding = '5px 10px';
  311. deleteButton.style.height = 'auto';
  312. deleteButton.onclick = () => this.deleteUsername(usernames[i]);
  313. actionCell.appendChild(deleteButton);
  314. }
  315.  
  316. tr.appendChild(usernameCell);
  317. tr.appendChild(actionCell);
  318. tbody.appendChild(tr);
  319. }
  320.  
  321. this.userListContainer.appendChild(table);
  322. }
  323.  
  324. deleteUsername(username) {
  325. const index = this.savedUsernamesTCG.indexOf(username);
  326. if (index !== -1) {
  327. this.savedUsernamesTCG.splice(index, 1);
  328. this.saveUsernames();
  329. this.updateUserListDisplay();
  330. }
  331. }
  332.  
  333. newTradePopup(card) {
  334. const cardHTML = this.getCardInfoTrade(card).replace(/onclick='[^']+'/g, '');
  335.  
  336. const tradePopupStyles = `
  337. <style>
  338. #tradePopup {
  339. display: flex;
  340. flex-direction: column;
  341. align-items: center;
  342. width: 100%;
  343. max-width: 800px;
  344. margin: 0 auto;
  345. background-color: #fff;
  346. border-radius: 8px;
  347. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  348. padding: 20px;
  349. box-sizing: border-box;
  350. }
  351. #tradePopup .tradePopup-row {
  352. display: flex;
  353. width: 100%;
  354. }
  355. #tradePopup .tradePopup-col {
  356. flex: 1;
  357. box-sizing: border-box;
  358. }
  359. #tradePopup .tradePopup-card-container {
  360. flex: none;
  361. width: 35%;
  362. }
  363. #tradePopup .tradePopup-button-container {
  364. display: flex;
  365. justify-content: right;
  366. width: 100%;
  367. }
  368. #tradePopup button {
  369. padding: 10px 20px;
  370. cursor: pointer;
  371. margin-right: 10px;
  372. }
  373. #userListContainer {
  374. max-height: 230px;
  375. overflow-y: auto;
  376. }
  377. </style>
  378. `;
  379.  
  380. document.head.insertAdjacentHTML('beforeend', tradePopupStyles);
  381.  
  382. const popupBox = document.createElement('div');
  383. popupBox.id = 'tradePopup';
  384.  
  385. const rowDiv = document.createElement('div');
  386. rowDiv.className = 'tradePopup-row';
  387. popupBox.appendChild(rowDiv);
  388.  
  389. const cardColDiv = document.createElement('div');
  390. cardColDiv.className = 'tradePopup-col tradePopup-card-container';
  391. rowDiv.appendChild(cardColDiv);
  392.  
  393. const formColDiv = document.createElement('div');
  394. formColDiv.className = 'tradePopup-col';
  395. rowDiv.appendChild(formColDiv);
  396.  
  397. const cardContainer = document.createElement('div');
  398. cardContainer.innerHTML = cardHTML;
  399. cardColDiv.appendChild(cardContainer);
  400.  
  401. const title = document.createElement('h5');
  402. title.textContent = "Who do you want to send this card to?";
  403. title.className = "modal-title";
  404. formColDiv.appendChild(title);
  405.  
  406. const inputGroup = document.createElement('div');
  407. inputGroup.className = 'input-group mb-3';
  408.  
  409. const usernameInput = document.createElement('input');
  410. usernameInput.type = 'text';
  411. usernameInput.className = 'form-control';
  412. usernameInput.id = 'recipientUsernameInput';
  413. usernameInput.placeholder = 'Enter username';
  414. if (this.previousTradeUsername){
  415. usernameInput.value = this.previousTradeUsername;
  416. }
  417.  
  418. inputGroup.appendChild(usernameInput);
  419.  
  420. const addUserButton = document.createElement('button');
  421. addUserButton.textContent = 'SAVE USER';
  422. addUserButton.className = 'btn btn-secondary';
  423. addUserButton.type = 'button';
  424.  
  425. inputGroup.appendChild(addUserButton);
  426. formColDiv.appendChild(inputGroup);
  427.  
  428. this.userListContainer = document.createElement('div');
  429. this.userListContainer.id = 'userListContainer';
  430. formColDiv.appendChild(this.userListContainer);
  431. this.updateUserListDisplay();
  432.  
  433. const sendCardButton = document.createElement('button');
  434. sendCardButton.textContent = 'SEND CARD';
  435. sendCardButton.className = 'btn btn-primary';
  436. sendCardButton.type = 'button';
  437.  
  438. const closeButton = document.createElement('button');
  439. closeButton.textContent = 'CLOSE';
  440. closeButton.className = 'btn btn-secondary';
  441. closeButton.type = 'button';
  442.  
  443. const buttonContainer = document.createElement('div');
  444. buttonContainer.className = 'tradePopup-button-container';
  445. buttonContainer.appendChild(sendCardButton);
  446. buttonContainer.appendChild(closeButton);
  447. popupBox.appendChild(buttonContainer);
  448.  
  449. const actions = [
  450. {
  451. button: sendCardButton,
  452. handler: () => {
  453. const recipientUsername = usernameInput.value.trim();
  454. this.previousTradeUsername = recipientUsername;
  455. IdlePixelPlus.sendMessage(`GIVE_TCG_CARD=${recipientUsername}~${card}`);
  456. }
  457. },
  458. {
  459. button: closeButton,
  460. handler: () => {
  461. this.closePopup();
  462. },
  463. closeOnAction: true
  464. },
  465. {
  466. button: addUserButton,
  467. handler: () => {
  468. const username = usernameInput.value;
  469. if (username && !this.savedUsernamesTCG.includes(username)) {
  470. this.savedUsernamesTCG.push(username);
  471. this.saveUsernames();
  472. this.updateUserListDisplay();
  473. }
  474. },
  475. closeOnAction: false
  476. }
  477. ];
  478.  
  479. this.launchPopup(popupBox, actions);
  480. }
  481.  
  482. newCardOverlay(message, cardHTML) {
  483. const popupBox = document.createElement('div');
  484. popupBox.id = 'newCardPopupBox';
  485. popupBox.style.width = '300px';
  486. popupBox.style.margin = '0 auto';
  487. popupBox.style.backgroundColor = '#fff';
  488. popupBox.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
  489. popupBox.style.borderRadius = '8px';
  490. popupBox.style.padding = '20px';
  491. popupBox.style.textAlign = 'center';
  492.  
  493. const messageP = document.createElement('p');
  494. messageP.textContent = message;
  495. messageP.style.fontSize = '18px';
  496. messageP.style.fontWeight = 'bold';
  497.  
  498. const cardContainer = document.createElement('div');
  499. cardContainer.innerHTML = cardHTML;
  500. cardContainer.firstChild.style.marginTop = '0px';
  501.  
  502. const cardTitle = cardContainer.querySelector('.tcg-card-title');
  503. const cardInnerText = cardContainer.querySelector('.tcg-card-inner-text');
  504. if (cardTitle) {
  505. cardTitle.style.textAlign = 'left';
  506. }
  507. if (cardInnerText) {
  508. cardInnerText.style.textAlign = 'left';
  509. }
  510.  
  511. const openAnotherButton = document.createElement('button');
  512. openAnotherButton.textContent = 'OPEN ANOTHER';
  513. openAnotherButton.style.padding = '10px 20px';
  514. openAnotherButton.style.fontSize = '16px';
  515. openAnotherButton.style.cursor = 'pointer';
  516. openAnotherButton.style.marginRight = '10px';
  517.  
  518. const tcg_unknown = IdlePixelPlus.getVarOrDefault("tcg_unknown", 0, "int");
  519. openAnotherButton.disabled = tcg_unknown == 1;
  520.  
  521. const closeButton = document.createElement('button');
  522. closeButton.textContent = 'CLOSE';
  523. closeButton.style.padding = '10px 20px';
  524. closeButton.style.fontSize = '16px';
  525. closeButton.style.cursor = 'pointer';
  526.  
  527. const actions = [
  528. {
  529. button: openAnotherButton,
  530. handler: () => {
  531. IdlePixelPlus.sendMessage("REVEAL_TCG_CARD");
  532. }
  533. },
  534. {
  535. button: closeButton,
  536. handler: () => {
  537. this.closePopup();
  538. },
  539. closeOnAction: true
  540. }
  541. ];
  542.  
  543. popupBox.appendChild(messageP);
  544. popupBox.appendChild(cardContainer);
  545. if (!this.trade) {
  546. popupBox.appendChild(openAnotherButton);
  547. }
  548. popupBox.appendChild(closeButton);
  549. this.trade = false;
  550.  
  551. this.launchPopup(popupBox, actions);
  552. }
  553.  
  554. openSimplePopup(message, footer) {
  555. const popupBox = document.createElement('div');
  556. popupBox.style.width = '300px';
  557. popupBox.style.margin = '0 auto';
  558. popupBox.style.backgroundColor = '#fff';
  559. popupBox.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
  560. popupBox.style.borderRadius = '8px';
  561. popupBox.style.padding = '20px';
  562. popupBox.style.textAlign = 'center';
  563.  
  564. const messageP = document.createElement('p');
  565. messageP.textContent = message;
  566. messageP.style.fontSize = '18px';
  567. messageP.style.fontWeight = 'bold';
  568.  
  569. const footerP = document.createElement('p');
  570. footerP.textContent = footer;
  571. footerP.style.fontSize = '16px';
  572.  
  573. const closeButton = document.createElement('button');
  574. closeButton.textContent = 'CLOSE';
  575. closeButton.style.padding = '10px 20px';
  576. closeButton.style.fontSize = '16px';
  577. closeButton.style.cursor = 'pointer';
  578.  
  579. closeButton.addEventListener('click', () => {
  580. this.closePopup();
  581. });
  582.  
  583. popupBox.appendChild(messageP);
  584. popupBox.appendChild(footerP);
  585. popupBox.appendChild(closeButton);
  586.  
  587. this.launchPopup(popupBox, []);
  588. }
  589.  
  590. launchPopup(popup, actions) {
  591. if (this.currentPopup) {
  592. if (this.overlay.contains(this.currentPopup)) {
  593. this.overlay.removeChild(this.currentPopup);
  594. }
  595. this.currentPopup = null;
  596. }
  597.  
  598. this.currentPopup = popup;
  599.  
  600. this.overlay.appendChild(popup);
  601. document.body.appendChild(this.overlay);
  602.  
  603. this.adjustPopupPosition();
  604.  
  605. actions.forEach(action => {
  606. const button = action.button;
  607. button.addEventListener('click', () => {
  608. action.handler();
  609. if (action.closeOnAction !== false) {
  610. this.closePopup();
  611. }
  612. });
  613. });
  614. }
  615.  
  616. adjustPopupPosition() {
  617. if (!this.currentPopup) return;
  618.  
  619. const viewportHeight = window.innerHeight;
  620. const popupHeight = this.currentPopup.offsetHeight;
  621. const scrollOffset = window.pageYOffset || document.documentElement.scrollTop;
  622. const topPosition = (viewportHeight - popupHeight) / 2 + scrollOffset;
  623. this.currentPopup.style.position = 'absolute';
  624. this.currentPopup.style.top = `${topPosition > 0 ? topPosition : 0}px`;
  625. }
  626.  
  627. closePopup() {
  628. if (this.overlay.contains(this.currentPopup)) {
  629. this.overlay.removeChild(this.currentPopup);
  630. }
  631. document.body.removeChild(this.overlay);
  632. this.currentPopup = null;
  633. }
  634. }
  635.  
  636. const plugin = new TCGRevamp();
  637. IdlePixelPlus.registerPlugin(plugin);
  638.  
  639. })();