您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
QoL enhancement for IQRPG Labyrinth
// ==UserScript== // @name IQRPG Labyrinth Companion // @namespace https://www.iqrpg.com/ // @version 1.1.0 // @author Tempest // @description QoL enhancement for IQRPG Labyrinth // @homepage https://slyboots.studio/iqrpg-labyrinth-companion/ // @source https://github.com/SlybootsStudio/iqrpg-labyrinth-companion // @match https://*.iqrpg.com/* // @require http://code.jquery.com/jquery-latest.js // @license unlicense // @grant none // ==/UserScript== /* global $ */ const SHOW_ROOM_NUMBER = 1; const SHOW_ROOM_TIER = 1; const SHOW_ROOM_CHANCE = 1; const SHOW_ROOM_ESTIMATE = 1; const SHOW_ROOM_TOTAL = 1; const MODIFY_NAVIGATION = 1; // 0 - Don't modify nav button, 1 - modify nav button (default) const NAVITEM_LABY = 7; // Which nav item to style const RENDER_DELAY = 200; // milliseconds after view loads to render companion const SKILL_BOX_INDEX = 3; // Will be 2, or 3, depending on Land status. const MAX_ACTIONS = 100; // This may change in the future if the developer allows an increase. // If that happens, we'll want to read this from the payload data, and not hard code it. const CACHE_LAB = "cache_lab"; // Cache for all the const CACHE_LAB_NAV = "cache_lab_nav"; //----------------------------------------------------------------------- //----------------------------------------------------------------------- // // CACHE // We are caching the daily labyrinth, // which is updated each time to labyrinth page is visited. // The cached data is used when the labyrinth is being run. // function writeCache( key, data ) { localStorage[key] = JSON.stringify(data); } function readCache( key ) { return JSON.parse(localStorage[key] || null) || localStorage[key]; } //----------------------------------------------------------------------- //----------------------------------------------------------------------- function renderLaby(data, skills) { // Create 3rd column let wrapper = $('.two-columns'); let col3 = $('.two-columns__column', wrapper).eq(1).clone(); $(wrapper).append(col3); // Cache data data = JSON.parse(data); writeCache(CACHE_LAB, data); // On the Laby page, 'currentRoom' refers to the completed rooms. // On Laby Run, currentRoom is the target room you're TRYING to pass. data.currentRoom += 1; // Render table renderLabyTable(data, col3, skills); writeNavCache(data.turns, data.maxTurns, data.rewardObtained); } function renderLabyFromCache(data, skills) { let col = $('.labytable'); // Check to make sure a Laby table doesn't currently exist. // If it does, we'll update it. // If it doesn't, we'll create one. if( !col.length ) { let wrapper = $('.main-game-section'); $(wrapper).css('position', 'relative'); col = $('.main-section__body', wrapper).clone(); $(col).css('position', 'absolute'); $(col).css('top', '30px'); $(col).css('right', '0'); $(col).addClass('labytable'); $(wrapper).append(col); } data = JSON.parse(data); let cached = readCache(CACHE_LAB); // Use the cached data for the Laby table, but update the `currentRoom` based // on our most up to date information about player progress. cached.currentRoom = data?.data?.currentRoom || 0; renderLabyTable(cached, col, skills); writeNavCache(data.data.turns, data.data.maxTurns, false); } /** * Lookup a skill name by ID. * * Labyrinth rooms use an ID to reference which * skill is used. * * @param {Number} id * * @return {String} */ function getSkillNameById(id) { let name = ""; switch(id) { case 1: name = "Battling"; break; case 2: name = "Dungeon"; break; case 3: name = "Mining"; break; case 4: name = "Wood"; break; case 5: name = "Quarry"; break; case 6: name = "Rune"; break; case 7: name = "Jewelry"; break; case 8: name = "Alchemy"; break; } return name; } /** * Lookup a skill name by ID. * * Labyrinth rooms use an ID to reference which * skill is used. * * @param {Number} roomId * @param {Number} currentRoomId * * @return {String} style */ function getStyleByRoomState(roomId, currentRoomId) { let style = ""; if(currentRoomId == roomId ) { // Active Rooms are Yellow Background // with Black Text style = "background-color: #cc0; color: #000;"; } // Incomplete Rooms are Red Background else if(currentRoomId < roomId ) { style = "background-color: #600;"; } // Completed Rooms are Green Background else if(currentRoomId > roomId ) { style = "background-color: #006400;"; } return style; } /** * Get the percent chance you have to progress * through a room, based on the tier of the room * and skill level. * * @param {Number} tier * @param {Number} skill * * @return {Float} chance */ function getChanceBySkill(tier, skill) { let base = 1; switch(tier) { case 1: base = 700; break; case 2: base = 1000; break; case 3: base = 1400; break; case 4: base = 1900; break; case 5: base = 2500; break; } let chance = 1000 / base * skill; chance = parseFloat(chance / 10).toFixed(2); return chance; } /** * Render the labyrinth table. * * @param {Object} data * @param {domElement} ele * @param {Array} skills */ function renderLabyTable(data, ele, skills) { let remaining = data.maxTurns; const trStyle = 'border:1px solid black;'; const tdStyle = 'padding:3px;border-top:1px solid black;'; const showNumber = SHOW_ROOM_NUMBER === 1 ? "" : "display:none;"; const showTier = SHOW_ROOM_TIER === 1 ? "" : "display:none;"; const showChance = SHOW_ROOM_CHANCE === 1 ? "" : "display:none;"; const showEstimate = SHOW_ROOM_ESTIMATE === 1 ? "" : "display:none;"; const showTotal = SHOW_ROOM_TOTAL === 1 ? "" : "display:none;"; let html = ''; html += '<div style="width:100%;">' html += '<table style="border-spacing:0;float:right;">'; html += `<tr style='${trStyle}'>`; html += `<td style='${tdStyle}'>Room</td>`; html += `<td style='${tdStyle}${showTotal}'>(T)Skill</td>`; html += `<td style='${tdStyle}${showChance}'>Chance</td>`; html += `<td style='${tdStyle}${showEstimate}'>Est</td>`; html += `<td style='${tdStyle}${showTotal}'>Total</td>`; data.data.map( (room, i) => { let skill = room[0]; let tier = room[1]; const style = getStyleByRoomState(i+1, data.currentRoom); const skillName = getSkillNameById(skill); const chance = getChanceBySkill(tier, skills[skill-1]); const estimated = Math.round(100/chance); remaining -= estimated; html += `<tr style='${style};${trStyle}'>`; html += `<td style='${tdStyle}'>${i+1}</td>`; html += `<td style='${tdStyle}${showTotal}'>(${tier})${skillName}</td>`; html += `<td style='${tdStyle}${showChance}'>${chance}%</td>`; html += `<td style='${tdStyle}${showEstimate}'>${estimated}</td>`; html += `<td style='${tdStyle}${showTotal}'>${100 - remaining}</td>`; html += `</tr>`; }); html += "</table></div>"; ele.html(html); } /** * Update the Labyrinth button in the navigation to match the state of progress. * * @param {Object} data * @param {domElement} ele * @param {Array} skills */ function updateNavigation() { // Players can disable modification entirely using this setting. if(!MODIFY_NAVIGATION) { return; } // This is the payload we should be pulling out from cache /* * data : { * turns * maxTurns * rewardObtained * date * } */ let data = readCache(CACHE_LAB_NAV); // We only want to use cached data from today. // Set to New York Timezone, which matches server time. let date = new Date(); let _date = date.toLocaleString('en-GB', { timeZone: 'America/New_York' }).split(',')[0]; if( _date != data?.date) { data = undefined; } //console.log(_date, data?.date); let link = $('.nav a').eq(NAVITEM_LABY); if(!data) { // We lack information until the user clicks on Labyrinth $(link).css('font-weight', 'bold'); $(link).css('color', 'white'); $(link).css('background-color', 'red'); $(link).html("Labyrinth [Need Info]"); } else if (data.turns < data.maxTurns) { // User has turns remaining $(link).css('color', 'yellow'); $(link).css('font-weight', 'bold'); $(link).css('background-color', 'red'); $(link).html("Labyrinth [Unfinished]"); } else if(!data.rewardObtained) { // Labyrinth is complete, but reward unclaimed $(link).css('color', 'red'); $(link).css('font-weight', 'bold'); $(link).css('background-color', 'white'); $(link).html("Labyrinth [CLAIM LOOT]"); } else if(data.rewardObtained) { // Labyrinth is complete, Reward claimed $(link).css('color', '#ccc'); $(link).css('font-weight', 'normal'); $(link).css('background-color', ''); $(link).html("Labyrinth [Complete]"); } } function writeNavCache(turns, maxTurns, rewardObtained) { let date = new Date(); const _date = date.toLocaleString('en-GB', { timeZone: 'America/New_York' }).split(',')[0]; const payload ={ turns : turns ?? 0, maxTurns : maxTurns ?? MAX_ACTIONS, rewardObtained : rewardObtained ?? false, date : _date } //console.log("Cache", payload); writeCache(CACHE_LAB_NAV, payload); updateNavigation(); } /** * Parse the Skill box into an array of your skill levels * * This works whether or not the box is collapsed. * * @return {Array} skills */ function parseSkills() { // New players won't have the 'Land' main-section box, // which appears above 'Skills'. We need to check the text // To make sure we're in the right box. // For new players, this is skills. // For land players, this is land. let text = $('.main-section').eq(SKILL_BOX_INDEX - 1).text(); if(!text.includes('Skills')) { // For new players, this is center content. // For land players, this is skills. text = $('.main-section').eq(SKILL_BOX_INDEX).text(); } // // The html has been converted into a text string. // Now we parse the string into an array. text = text.replace('Skills', ''); text = text.replaceAll('(', ''); let skills = text.split(')'); skills = skills.map( skill => { // "skillname (level)" skill = skill.trim(); skill = skill.split(' '); return skill[1]; }); return skills; } let loadOnce = false; //----------------------------------------------------------------------- //----------------------------------------------------------------------- let send = window.XMLHttpRequest.prototype.send; function sendReplacement(data) { if(this.onreadystatechange) { this._onreadystatechange = this.onreadystatechange; } this.onreadystatechange = onReadyStateChangeReplacement; return send.apply(this, arguments); } function onReadyStateChangeReplacement() { // // This is called anytime there is an action complete, or a view (page) loads. // console.log('Response URL', this.responseURL); // setTimeout( () => { // // Player Loading the Labyrinth View // if(this.responseURL.includes("php/misc.php?mod=loadLabyrinth")) { // LoadOnce is tracked because this function can be triggered // multiple times in a single call. if(this.response && !loadOnce) { loadOnce = true; $('.labytable').remove(); // Remove old table let skills = parseSkills(); // Scans skill table renderLaby(this.response, skills); // Render a new table. } } // // Player is running the Labyrinth // - We're still on the labyrinth page, but the actions are being executed. // else if(this.responseURL.includes("php/actions/labyrinth.php")) { let skills = parseSkills(); renderLabyFromCache(this.response, skills); } // // Player has claimed the Labyrinth reward // else if(this.responseURL.includes("misc.php?mod=getLabyrinthRewards")) { writeNavCache(MAX_ACTIONS, MAX_ACTIONS, true); } // // We're on another page, elsewhere in IQ // else { // Remove The Labyrinth table. $('.labytable').remove(); loadOnce = false; } }, RENDER_DELAY ); // // Update the navigation to make sure we're showing the latest information. // updateNavigation(); /* Avoid errors in console */ if(this._onreadystatechange) { return this._onreadystatechange.apply(this, arguments); } else { return this._onreadystatechange; } } window.XMLHttpRequest.prototype.send = sendReplacement;