// ==UserScript== // @author Xelminoe // @id VRBB-l8-maximizer@Xelminoe // @name VRBB L8 Maximizer // @category Info // @version 1.0.1 // @description Assist planning L8 resonator max-out using VRBB — e.g. 3 agents from the same side build an L8 portal in 15 minutes. // @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/Xelminoe/VRBB-l8-maximizer.user.js // @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/Xelminoe/VRBB-l8-maximizer.meta.js // @homePage https://xelminoe.github.io/VRBB-L8-Maximizer/ // @homepageURL https://xelminoe.github.io/VRBB-L8-Maximizer/ // @issueTracker https://github.com/Xelminoe/VRBB-L8-Maximizer/issues // @match https://intel.ingress.com/* // @grant none // ==/UserScript== function wrapper(plugin_info) { if (typeof window.plugin !== 'function') window.plugin = () => {}; window.plugin.vrbbL8Maximizer = {}; const plugin = window.plugin.vrbbL8Maximizer; plugin.strategyLibrary = [ { name: "Strategy-NullOrVRBB-P0-Flip", conditions: { isPortalTeamNullOrVRBBTeam: true, }, stages: [ { stage: "before VRBB", action: [ "XReso", "VRBB" ], }, { stage: "Phase 1", action: [ "Wait" ], }, { stage: "Phase 2", action: [ "YReso", "Wait" ], }, { stage: "Phase 3", action: [ "PseudoFlip", "XFlip", "YReso", "Wait" ] }, { stage: "Phase 4", action: [ "XReso", "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-NullOrVRBB-P0-NoFlip", conditions: { isPortalTeamNullOrVRBBTeam: true, }, stages: [ { stage: "before VRBB", action: [ "XReso", "VRBB" ], }, { stage: "Phase 1", action: [ "Wait" ], }, { stage: "Phase 2", action: [ "YReso", "Wait" ], }, { stage: "Phase 3", action: [ "PseudoFlip", "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Null-YActive-P1-Flip", conditions: { isPortalTeamNull: true, isYTeamActive: true }, stages: [ { stage: "before VRBB", action: [ "NoReso", "VRBB" ], }, { stage: "Phase 1", action: [ "XReso", "Wait" ], }, { stage: "Phase 2", action: [ "YReso", "YFlip", "XReso", "Wait" ], }, { stage: "Phase 3", action: [ "YReso", "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Null-YActive-P2-Flip", conditions: { isPortalTeamNull: true, isYTeamActive: true }, stages: [ { stage: "before VRBB", action: [ "NoReso", "VRBB" ], }, { stage: "Phase 1", action: [ "NoReso" ], }, { stage: "Phase 2", action: [ "XReso", "Wait" ], }, { stage: "Phase 3", action: [ "YReso", "YFlip", "XReso", "Wait" ] }, { stage: "Phase 4", action: [ "YReso", "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Null-YInactive-P1-Flip", conditions: { isPortalTeamNull: true, isYTeamActive: false }, stages: [ { stage: "before VRBB", action: [ "NoReso", "VRBB" ], }, { stage: "Phase 1", action: [ "XReso", "XFlip", "Wait" ], }, { stage: "Phase 2", action: [ "XReso", "Done" ], }, { stage: "Phase 3", action: [ "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Null-YInactive-P2-Flip", conditions: { isPortalTeamNull: true, isYTeamActive: false }, stages: [ { stage: "before VRBB", action: [ "NoReso", "VRBB" ], }, { stage: "Phase 1", action: [ "NoReso" ], }, { stage: "Phase 2", action: [ "XReso", "XFlip", "Wait" ], }, { stage: "Phase 3", action: [ "XReso", "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Other-Flip", conditions: { isPortalTeamNullOrVRBBTeam: false, }, stages: [ { stage: "before VRBB", action: [ "YReso", "VRBB" ], }, { stage: "Phase 1", action: [ "Wait" ], }, { stage: "Phase 2", action: [ "PseudoFlip", "XFlip", "YReso", "Wait" ], }, { stage: "Phase 3", action: [ "XReso", "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] }, { name: "Strategy-Other-NoFlip", conditions: { isPortalTeamNullOrVRBBTeam: false, }, stages: [ { stage: "before VRBB", action: [ "YReso", "VRBB" ], }, { stage: "Phase 1", action: [ "Wait" ], }, { stage: "Phase 2", action: [ "PseudoFlip", "Done" ], }, { stage: "Phase 3", action: [ "Done" ] }, { stage: "Phase 4", action: [ "Done" ] }, { stage: "Phase 5", action: [ "Done" ] }, { stage: "After VRBB", action: [ "Done" ] }, ] } ]; plugin.defaultFaction = Math.random() < 0.5 ? 'R' : 'E'; plugin.initializeStrategyPointer = function(strategy) { return { strategyName: strategy.name, stageIndex: 0, actionIndex: 0, lastAction: null, currentAction: strategy.stages[0]?.action[0] || null, done: false }; }; plugin.advanceStrategyPointer = function(pointer, strategy) { const last = pointer.currentAction; let sIdx = pointer.stageIndex; let aIdx = pointer.actionIndex; const stageList = strategy.stages; if (aIdx + 1 < stageList[sIdx].action.length) { aIdx += 1; } else if (sIdx + 1 < stageList.length) { sIdx += 1; aIdx = 0; } else { return { ...pointer, lastAction: last, currentAction: null, done: true }; } return { ...pointer, stageIndex: sIdx, actionIndex: aIdx, lastAction: last, currentAction: stageList[sIdx].action[aIdx], done: false }; }; plugin.isPortalTeamNullOrVRBBTeam = function (portalStatus0, agentList) { const team = portalStatus0?.team; if (!team) return true; const vrbbAgent = agentList.find(a => a.useVrbb); if (!vrbbAgent || !vrbbAgent.team) return false; return team === vrbbAgent.team; }; plugin.isPortalTeamNull = function (portalStatus0) { const team = portalStatus0?.team; return !team; }; plugin.selectValidStrategies = function(portalStatus, agentList, strategyLibrary) { const isNull = plugin.isPortalTeamNull(portalStatus); const isNullOrSame = plugin.isPortalTeamNullOrVRBBTeam(portalStatus, agentList); const vrbbAgent = agentList.find(a => a.useVrbb); const XTeam = vrbbAgent?.team; const YTeam = (XTeam === 'R') ? 'E' : 'R'; const hasY = agentList.some(a => a.active && a.team === YTeam); return strategyLibrary.filter(strategy => { const cond = strategy.conditions || {}; if ('isPortalTeamNullOrVRBBTeam' in cond && cond.isPortalTeamNullOrVRBBTeam !== isNullOrSame) { return false; } if ('isPortalTeamNull' in cond && cond.isPortalTeamNull !== isNull) { return false; } if ('isYTeamActive' in cond && cond.isYTeamActive !== hasY) { return false; } return true; }); }; plugin.phaseChange = function(previousStatus) { const validPhases = new Set(["Phase 1", "Phase 2", "Phase 3", "Phase 4"]); if (!validPhases.has(previousStatus.stage)) { return { ...previousStatus }; } const prevTeam = previousStatus.team; if (prevTeam === 'E') { return { ...previousStatus, team: 'R', resonators: previousStatus.resonators.map(r => ({ ...r, holder: r.resOwner })) }; } else if (prevTeam === 'R') { return { ...previousStatus, team: 'E', resonators: previousStatus.resonators.map(r => ({ ...r, holder: r.enlOwner })) }; } else { return { ...previousStatus }; } }; plugin.applyVRBB = function(previousStatus, agentList) { const team = previousStatus.team; if (team !== 'R' && team !== 'E') { return { ...previousStatus }; } const vrbbAgent = agentList.find(a => a.useVrbb); if (!vrbbAgent || !vrbbAgent.team) { return { ...previousStatus }; } const vrbbName = vrbbAgent.name; const vrbbTeam = vrbbAgent.team; const newResonators = previousStatus.resonators.map(r => { if (!r.holder) return { ...r }; if (vrbbTeam === 'R') { return { ...r, resOwner: vrbbName }; } else if (vrbbTeam === 'E') { return { ...r, enlOwner: vrbbName }; } else { return { ...r }; } }); return { ...previousStatus, resonators: newResonators }; }; plugin.tryDeploy = function(previousStatus, lastAction, agentList, doubleResoEvent) { const resSystemAgent = "__ADA__"; const enlSystemAgent = "__JARVIS__"; const vrbbAgent = agentList.find(a => a.useVrbb); if (!vrbbAgent || !vrbbAgent.team) { console.warn("VRBB agent not found or has no team"); return { ...previousStatus }; } const XTeam = vrbbAgent.team; const YTeam = (XTeam === 'R') ? 'E' : 'R'; const currentTeam = previousStatus.team; const resonators = [...previousStatus.resonators]; // clone array const emptySlots = resonators .map((r, idx) => ({ ...r, idx })) .filter(r => !r.holder); function countByHolder(agentName) { return resonators.filter(r => r.holder === agentName).length; } function tryAssign(teamKey, systemAgent, setResOwner, setEnlOwner) { const agents = agentList.filter(a => a.active && a.team === teamKey); for (const agent of agents) { while (countByHolder(agent.name) < doubleResoEvent) { const slot = emptySlots.shift(); if (!slot) return; // no available slots resonators[slot.idx] = { holder: agent.name, resOwner: setResOwner ? agent.name : systemAgent, enlOwner: setEnlOwner ? agent.name : systemAgent }; } } } if (lastAction === "XReso" || lastAction === "PseudoFlip") { if (currentTeam !== XTeam && currentTeam !== null) { console.warn("Invalid team for XReso / PseudoFlip"); return { ...previousStatus }; } const isXResTeam = (XTeam === 'R'); const systemAgent = (YTeam === 'R') ? resSystemAgent : enlSystemAgent; tryAssign(XTeam, systemAgent, isXResTeam, !isXResTeam); } else if (lastAction === "YReso") { if (currentTeam !== YTeam && currentTeam !== null) { console.warn("Invalid team for YReso"); return { ...previousStatus }; } const isYResTeam = (YTeam === 'R'); const systemAgent = (XTeam === 'R') ? resSystemAgent : enlSystemAgent; tryAssign(YTeam, systemAgent, isYResTeam, !isYResTeam); } return { ...previousStatus, team: lastAction === "YReso" ? YTeam : XTeam, resonators: resonators }; }; plugin.flipCard = function(previousStatus) { const resSystemAgent = "__ADA__"; const enlSystemAgent = "__JARVIS__"; const prevTeam = previousStatus.team; let newTeam = null; let newResonators = [...previousStatus.resonators]; if (prevTeam === 'R') { newTeam = 'E'; newResonators = newResonators.map(r => r.holder ? { ...r, holder: enlSystemAgent, resOwner: resSystemAgent, enlOwner: enlSystemAgent } : { ...r } ); } else if (prevTeam === 'E') { newTeam = 'R'; newResonators = newResonators.map(r => r.holder ? { ...r, holder: resSystemAgent, resOwner: resSystemAgent, enlOwner: enlSystemAgent } : { ...r } ); } else { return { ...previousStatus }; } return { ...previousStatus, team: newTeam, resonators: newResonators }; }; plugin.computeCurrentPortalStatus = function(previousStatus, lastAction, currentAction, stage, agentList, doubleResoEvent) { if (!lastAction) { return { ...previousStatus, stage: stage, action: currentAction }; } const vrbbAgent = agentList.find(a => a.useVrbb); const XTeam = vrbbAgent?.team; const YTeam = (XTeam === 'R') ? 'E' : 'R'; const hasY = agentList.some(a => a.active && a.team === YTeam); switch (lastAction) { case "Wait": case "Done": case "NoReso": return {...plugin.phaseChange(previousStatus), stage: stage, action: currentAction }; case "VRBB": return {...plugin.applyVRBB(previousStatus, agentList), stage: stage, action: currentAction }; case "XReso": case "PseudoFlip": return {...plugin.tryDeploy(previousStatus, lastAction, agentList, doubleResoEvent), stage: stage, action: currentAction }; case "YReso": { if (hasY) { return { ...plugin.tryDeploy(previousStatus, lastAction, agentList, doubleResoEvent), stage: stage, action: currentAction }; } else { return null; } } case "XFlip": return {...plugin.flipCard(previousStatus), stage: stage, action: currentAction }; case "YFlip": { if (hasY) { return { ...plugin.flipCard(previousStatus, lastAction, agentList, doubleResoEvent), stage: stage, action: currentAction }; } else { console.warn("YFlip failed: no valid Y team agent found."); return { ...previousStatus, stage: stage, action: currentAction, flipFailed: true }; } } default: console.warn("Unknown action type:", lastAction); return { ...previousStatus }; } }; plugin.stepStrategyExecution = function(statusHistory, agentList, strategy, pointer, doubleResoEvent) { if (pointer.done || !pointer.currentAction) return { pointer, statusHistory }; const prev = statusHistory.at(-1); const currentStatus = plugin.computeCurrentPortalStatus(prev, pointer.lastAction, pointer.currentAction, strategy.stages[pointer.stageIndex].stage, agentList, doubleResoEvent); const newPointer = plugin.advanceStrategyPointer(pointer, strategy); if (currentStatus) { statusHistory.push({ ...currentStatus }); } else { statusHistory.at(-1).action =pointer.currentAction; } return { pointer: newPointer, statusHistory }; }; plugin.evaluateAndDisplayStrategies = function(portalStatus0, agentList, doubleResoEvent, strategyLibrary) { const results = []; const validStrategies = plugin.selectValidStrategies(portalStatus0, agentList, strategyLibrary); for (const strategy of validStrategies) { let pointer = plugin.initializeStrategyPointer(strategy); const statusHistory = [{ ...portalStatus0, }]; while (!pointer.done) { const result = plugin.stepStrategyExecution( statusHistory, agentList, strategy, pointer, doubleResoEvent ); pointer = result.pointer; } const finalState = statusHistory.at(-1); const hasXFlip = statusHistory.some(s => s.action === "XFlip"); const hasYFlip = statusHistory.some(s => s.action === "YFlip"); const validResonatorCount = finalState.resonators.filter(r => r.holder).length; const finalTeam = finalState.team; results.push({ strategyName: strategy.name, statusHistory, summary: { validResonatorCount, hasXFlip, hasYFlip, finalTeam } }); } // Step 1: keep validResonatorCount maxmizer const maxValidCount = Math.max(...results.map(r => r.summary.validResonatorCount)); let filtered = results.filter(r => r.summary.validResonatorCount === maxValidCount); // Step 2: prefer no flip card const EResults = filtered.filter(r => r.summary.finalTeam === 'E'); const E_NoFlip = EResults.filter(r => !r.summary.hasXFlip && !r.summary.hasYFlip); if (E_NoFlip.length > 0) { filtered = filtered.filter(r => r.summary.finalTeam !== 'E' || (!r.summary.hasXFlip && !r.summary.hasYFlip)); } const RResults = filtered.filter(r => r.summary.finalTeam === 'R'); const R_NoFlip = RResults.filter(r => !r.summary.hasXFlip && !r.summary.hasYFlip); if (R_NoFlip.length > 0) { filtered = filtered.filter(r => r.summary.finalTeam !== 'R' || (!r.summary.hasXFlip && !r.summary.hasYFlip)); } plugin.renderStrategyPanel(filtered, agentList); }; // ========== Utility ========== plugin.getSelectedPortalResonators = function () { const guid = window.selectedPortal; if (!guid) return []; const portal = window.portals[guid]; const portalTeam = portal?._details?.team; if (portalTeam !== 'R' && portalTeam !== 'E') return []; const resoArray = portal?.options?.data?.resonators; if (!Array.isArray(resoArray)) return []; return resoArray .map((r, i) => { if (!r || !r.owner || r.level !== 8) return null; return { owner: r.owner, team: portalTeam, slot: i }; }) .filter(r => r); }; plugin.buildInitialAgentListFromResonators = function () { const resos = plugin.getSelectedPortalResonators(); const draftAgentList = []; const seen = new Set(); const systemAgents = new Set(["__JARVIS__", "__ADA__"]); plugin._resonatorDraft = Array(8).fill(null); // reset resos.forEach((r, idx) => { const owner = r.owner || 'Unknown'; plugin._resonatorDraft[idx] = owner; if (!seen.has(owner)) { seen.add(owner); draftAgentList.push({ name: owner, team: r.team || null, active: !systemAgents.has(owner), // ❗ false for system agents useVrbb: false }); } }); return draftAgentList; }; // ========== UI ========== plugin.renderAgentListEditor = function (draftList) { const container = $('#vrbb-agent-table'); container.empty(); draftList.forEach((agent, idx) => { const row = $(`