diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..955a9be --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "docwriter.progress.trackFunctions": false, + "docwriter.progress.trackMethods": false, + "docwriter.progress.trackClasses": false, + "docwriter.progress.trackTypes": false +} \ No newline at end of file diff --git a/SAGE_Lab_Assistant.user.js b/SAGE_Lab_Assistant.user.js new file mode 100644 index 0000000..da2b952 --- /dev/null +++ b/SAGE_Lab_Assistant.user.js @@ -0,0 +1,2979 @@ +// ==UserScript== +// @name SLY Lab Assistant 1.0 +// @namespace http://tampermonkey.net/ +// @version 0.5.0 +// @description try to take over the world! +// @author SLY w/ Contributions by SkyLove512, anthonyra, niofox +// @match https://labs.staratlas.com/ +// @require https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js +// @require https://raw.githubusercontent.com/ImGroovin/SAGE-Lab-Assistant/main/anchor-browserified.js +// @require https://raw.githubusercontent.com/ImGroovin/SAGE-Lab-Assistant/main/buffer-browserified.js +// @require https://raw.githubusercontent.com/ImGroovin/SAGE-Lab-Assistant/main/bs58-browserified.js +// @icon https://www.google.com/s2/favicons?sz=64&domain=staratlas.com +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_listValues +// ==/UserScript== + +(async function() { + 'use strict'; + + let provider; + let graceBlockWindow = 5; + let enableAssistant = false; + let initComplete = false; + + // in memory storage + let userFleets = []; + let playerProfile; + const GLOBAL_SCALE_DECIMALS_2 = 100; + const GLOBAL_SCALE_DECIMALS_6 = 1000000; + + const solanaConnection = new solanaWeb3.Connection('https://rpc.hellomoon.io/cfd5910f-fb7d-4489-9b32-f97193eceefd', 'confirmed'); + const anchorProvider = new BrowserAnchor.anchor.AnchorProvider(solanaConnection, null, null); + + // load SAGE program and methods + const sageProgramId = new solanaWeb3.PublicKey('SAGEqqFewepDHH6hMDcmWy7yjHPpyKLDnRXKb3Ki8e6'); + const sageIDL = await BrowserAnchor.anchor.Program.fetchIdl(sageProgramId, anchorProvider); + const sageProgram = new BrowserAnchor.anchor.Program(sageIDL, sageProgramId, anchorProvider); + console.debug('sageProgram: ', sageProgram); + const [sageGameAcct] = await sageProgram.account.game.all(); + console.debug('sageGameAcct: ', sageGameAcct); + const [sageSDUTrackerAcct] = await sageProgram.account.surveyDataUnitTracker.all(); + console.debug('sageSDUTrackerAcct: ', sageSDUTrackerAcct); + + // load player profile and methods + const profileProgramId = new solanaWeb3.PublicKey('pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9'); + const profileIDL = await BrowserAnchor.anchor.Program.fetchIdl(profileProgramId, anchorProvider); + const profileProgram = new BrowserAnchor.anchor.Program(profileIDL, profileProgramId, anchorProvider); + console.debug('profileProgram: ', profileProgram); + + // load cargo program and methods + const cargoProgramId = new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'); + const cargoIDL = await BrowserAnchor.anchor.Program.fetchIdl(cargoProgramId, anchorProvider); + const cargoProgram = new BrowserAnchor.anchor.Program(cargoIDL, cargoProgramId, anchorProvider); + const [cargoStatsDefinitionAcct] = await cargoProgram.account.cargoStatsDefinition.all(); + const cargoStatsDefSeqId = cargoStatsDefinitionAcct.account.seqId; + console.debug('cargoProgram', cargoProgram); + console.debug('cargoStatsDefinitionAcct', cargoStatsDefinitionAcct); + + const profileFactionProgramId = new solanaWeb3.PublicKey('pFACSRuobDmvfMKq1bAzwj27t6d2GJhSCHb1VcfnRmq'); + const profileFactionIDL = await BrowserAnchor.anchor.Program.fetchIdl(profileFactionProgramId, anchorProvider); + const profileFactionProgram = new BrowserAnchor.anchor.Program(profileFactionIDL, profileFactionProgramId, anchorProvider); + + // token accounts + const tokenProgram = new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); + const AssociatedTokenProgram = new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); + const RecentSlotHashes = new solanaWeb3.PublicKey('SysvarS1otHashes111111111111111111111111111'); + const InstructionsSysVar = new solanaWeb3.PublicKey('Sysvar1nstructions1111111111111111111111111'); + + 'Arco','Biomass','Carbon','Copper Ore','Diamond','Hydrogen','Iron Ore','Lumanite','Rochinol' + + const ResourceTokens = { + fuel: { + name: 'Fuel', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim') + }, + food: { + name: 'Food', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('foodQJAztMzX1DKpLaiounNe2BDMds5RNuPC6jsNrDG') + }, + ammo: { + name: 'Ammo', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('ammoK8AkX2wnebQb35cDAZtTkvsXQbi82cGeTnUvvfK') + }, + toolkit: { + name: 'Toolkit', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL') + }, + carbon: { + name: 'Carbon', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('CARBWKWvxEuMcq3MqCxYfi7UoFVpL9c4rsQS99tw6i4X') + }, + ironore: { + name: 'Iron Ore', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('FeorejFjRRAfusN9Fg3WjEZ1dRCf74o6xwT5vDt3R34J') + }, + diamond: { + name: 'Diamond', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('DMNDKqygEN3WXKVrAD4ofkYBc4CKNRhFUbXP4VK7a944') + }, + lumanite: { + name: 'Lumanite', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('LUMACqD5LaKjs1AeuJYToybasTXoYQ7YkxJEc4jowNj') + }, + biomass: { + name: 'Biomass', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('MASS9GqtJz6ABisAxcUn3FeR4phMqH1XfG6LPKJePog') + }, + arco: { + name: 'Arco', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('ARCoQ9dndpg6wE2rRexzfwgJR3NoWWhpcww3xQcQLukg') + }, + hydrogen: { + name: 'Hydrogen', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('HYDR4EPHJcDPcaLYUcNCtrXUdt1PnaN4MvE655pevBYp') + }, + copperore: { + name: 'Copper Ore', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('CUore1tNkiubxSwDEtLc3Ybs1xfWLs8uGjyydUYZ25xc') + }, + rochinol: { + name: 'Rochinol', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('RCH1Zhg4zcSSQK8rw2s6rDMVsgBEWa4kiv1oLFndrN5') + }, + sdu: { + name: 'SDU', + type: ['all'], + publicKey: new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM'), + from: new solanaWeb3.PublicKey('8bBi84Yi7vwSWXSYKDbbHmqnFqqAS41MvPkSEdzFtbsk') + }, + getPodToken: async function (input) { + let { name, publicKey, pod } = input; + if (publicKey) name = this.find(item => item.publicKey === publicKey || item.publicKey.toString() === publicKey).name; + if (name) name = name.toLowerCase().trim(); + if (!name || name === '' || !publicKey) throw new Error('Need to provide resource name or publicKey!'); + if (!pod || !this[name].from) throw new Error('Need to provide pod (fleet or starbase) for resource!'); + + const resourcePublicKey = this[name].publicKey; + const podPublicKey = pod || this[name].from; + + const pda = BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( + [ + podPublicKey.toBuffer(), + tokenProgram.toBuffer(), + resourcePublicKey.toBuffer() + ], + AssociatedTokenProgram + ); + + try { + await solanaConnection.getAccountInfo(pda); + } catch { + await createProgramDerivedAccount(pda, podPublicKey, resourcePublicKey); + } + return pda; + }, + names(type='all') { + return Object.values(this) + .filter(value => typeof value === 'object' && value.type.includes(type.toLowerCase())) + .map(value => value.name); + }, + publicKeys(type='all') { + return Object.values(this) + .filter(value => typeof value === 'object' && value.type.includes(type.toLowerCase())) + .map(value => value.publicKey); + }, + exists(resource) { + const names = this.names().map(name => name.toLowerCase().trim()); + const publicKeys = this.publicKeys().map(pbk => pbk.toString()); + + if (typeof resource === 'object') { + resource = resource.toString() || ''; + } + + return names.includes(resource.toLowerCase().trim()) || publicKeys.includes(resource); + }, + getResourceByName(resource) { + if (!resource || resource == '') return ''; + resource = resource.toLowerCase().trim(); + return this.exists(resource) ? this[resource] : ''; + } + } + + // @todo - redo documentation +/** + * The function `getProvider` is an asynchronous function that takes in a `wallets` object and returns + * the provider object associated with the first wallet found in the `walletWindow` object. + * @param wallets - An object that contains different wallet providers. The keys of the object are the + * names of the wallets, and the values are the corresponding wallet provider objects. + * @returns the provider object if it exists and is connected. + */ + async function getProviders () { + /* + const wallets = [ solflare, solana, phantom ]; + const provider = wallets.find(name => typeof name !== 'undefined'); + if (provider) { + if (!provider.isConnected) await provider.connect(); + return provider; + } + */ + let providers = []; + if (solana) { + providers.push(solana); + } + console.log('providers[0]: ', providers[0]); + console.log('isConnected: ', providers[0].isConnected); + providers[0].connect(); + return providers; + } + +/** + * The "wait" function returns a promise that resolves after a specified number of milliseconds. + * @param ms - The "ms" parameter represents the number of milliseconds to wait before resolving the + * promise. + * @returns a Promise object. + */ + function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + } + + const MAX_TRANSACTION_SIZE = 1232; + + function lengthToCompact16size(size) { + if (size > 0x7f) return 2; + return 1; + } + +/** + * The function calculates the size of a transaction in bytes based on the number of instructions and + * the keys involved. + * @param instructions - An array of objects representing instructions for a transaction. Each object + * has the following properties: `instruction` + * @param funder - The `funder` parameter is an object that represents the funder's account. It should + * have a `toBase58()` method that returns the base58 encoded string representation of the account's + * public key. + * @returns the size of a transaction in bytes. + */ + function getTransactionSize(instructions, funder) { + let ixSizes = 0; + const uniqueSigners = new Set(funder.toBase58()); + const uniqueKeys = new Set(funder.toBase58()); + + instructions.forEach(({ instruction }) => { + uniqueKeys.add(instruction.programId.toBase58()); + + instruction.keys.forEach((key) => { + if (key.isSigner) uniqueSigners.add(key.pubkey.toBase58()); + uniqueKeys.add(key.pubkey.toBase58()); + }); + ixSizes += + 1 + // program id index + lengthToCompact16size(instruction.keys.length) + // num accounts + instruction.keys.length + // account indexes + lengthToCompact16size(instruction.data.length) + // num ix bytes + instruction.data.length; // ix bytes + }); + + // console.log({ uniqueSigners, uniqueKeys, ixSizes }); + + return ( + lengthToCompact16size(uniqueSigners.size) + // num sigs + uniqueSigners.size * 64 + // Sigs + 3 + // message header + lengthToCompact16size(uniqueKeys.size) + // num accounts + uniqueKeys.size * 32 + // accounts + 32 + // recent blockhash + lengthToCompact16size(instructions.length) + // num instructions + ixSizes + ); + } + +/** + * The function `createProgramDerivedAccount` creates a derived account using the provided derived + * public key and two other derived from public keys. + * @param derived - The `derived` parameter is the public key of the account that will be created as a + * derived account. + * @param derivedFrom1 - The `derivedFrom1` parameter is a public key that represents the account from + * which the `derived` account is derived. + * @param derivedFrom2 - The parameter `derivedFrom2` is a public key that represents the account from + * which the `derived` account is derived. + * @returns the result of the `signAndSend` function, which is likely a promise that resolves to the + * transaction details. + */ + async function createProgramDerivedAccount(derived, derivedFrom1, derivedFrom2) { + const keys = [{ + pubkey: provider.publicKey, + isSigner: true, + isWritable: true + }, { + pubkey: derived, + isSigner: false, + isWritable: true + }, { + pubkey: derivedFrom1, + isSigner: false, + isWritable: false + }, { + pubkey: derivedFrom2, + isSigner: false, + isWritable: false + }, { + pubkey: solanaWeb3.SystemProgram.programId, + isSigner: false, + isWritable: false + }, { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }]; + + return await signAndSend({ + instruction: new solanaWeb3.TransactionInstruction({ + keys: keys, + programId: AssociatedTokenProgram.toString(), + data: [] + }) + }); + } + + const seqBN = new BrowserAnchor.anchor.BN(cargoStatsDefSeqId); + const seqArr = seqBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "be", 2); + const seq58 = bs58.encode(seqArr); + + // @todo - pull into ResourceTokens with getCargoType function + const [sduCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + { + memcmp: { + offset: 41, + bytes: ResourceTokens.sdu.publicKey.toBase58(), + }, + }, + { + memcmp: { + offset: 75, + bytes: seq58, + }, + }, + ]); + const [repairKitCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + { + memcmp: { + offset: 41, + bytes: ResourceTokens.toolkit.publicKey.toBase58(), + }, + }, + { + memcmp: { + offset: 75, + bytes: seq58, + }, + }, + ]); + const [fuelCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + { + memcmp: { + offset: 41, + bytes: ResourceTokens.fuel.publicKey.toBase58(), + }, + }, + { + memcmp: { + offset: 75, + bytes: seq58, + }, + }, + ]); + const cargoTypes = await cargoProgram.account.cargoType.all([ + { + memcmp: { + offset: 75, + bytes: seq58, + }, + }, + ]); + + // @todo - redo documentation +/** + * The function `getCurrentFleet` takes in fleet account information and returns the fleet state and any + * additional data associated with it. + * @param fleet - The `fleetAccountInfo` parameter is an object that contains information about a + * fleet account. It has a property called `data` which is an array of numbers. + * @returns The function `getCurrentFleet` returns an array with two elements. The first element is the + * fleet state, which can be one of the following values: 'StarbaseLoadingBay', 'Idle', 'MineAsteroid', + * 'MoveWarp', 'MoveSubwarp', or 'Respawn'. The second element is an additional value that provides + * extra information depending on the fleet state. + */ + async function getCurrentFleet(fleet) { + const fleetAccountInfo = await solanaConnection.getAccountInfo(fleet.publicKey); + const fleetData = sageProgram.coder.accounts.decode('Fleet', fleetAccountInfo.data); + const remainingData = fleetAccountInfo.data.slice(414); + + let fleetState = 'Unknown'; + let extra = null; + switch(remainingData[0]) { + case 0: + fleetState = 'StarbaseLoadingBay'; + extra = sageProgram.coder.types.decode('StarbaseLoadingBay', remainingData.slice(1)); + break; + case 1: { + fleetState = 'Idle'; + let sector = sageProgram.coder.types.decode('Idle', remainingData.slice(1)); + extra = [sector.sector[0].toNumber(), sector.sector[1].toNumber()] + break; + } + case 2: + fleetState = 'MineAsteroid'; + extra = sageProgram.coder.types.decode('MineAsteroid', remainingData.slice(1)); + break; + case 3: + fleetState = 'MoveWarp'; + extra = sageProgram.coder.types.decode('MoveWarp', remainingData.slice(1)); + break; + case 4: + fleetState = 'MoveSubwarp'; + extra = sageProgram.coder.types.decode('MoveSubwarp', remainingData.slice(1)); + break; + case 5: + fleetState = 'Respawn'; + break; + } + return { fleetData, fleetState, extra }; + } + + /** + * The `initUser` function retrieves user profiles and fleet accounts, and returns the user profile + * and user profile faction account. + * @returns The function `initUser` returns an object with properties `playerProfile` and + * `playerProfileFactionAcct`. + */ + async function initUser() { + //await selectWalletToggle(); + [provider] = await getProviders(); + console.debug('Provider: ', provider); + + let userProfiles = await solanaConnection.getProgramAccounts(profileProgramId); + + let playerProfiles = []; + for (let [index, userProfile] of userProfiles.entries()) { + let profileData = userProfile.account.data.subarray(30); + while (profileData.length >= 80) { + const profile = profileData.subarray(0, 80); + const decodedProfile = profileProgram.coder.types.decode('ProfileKey', profile); + + if (decodedProfile.key.toString() === provider.publicKey.toString()) { + const [playerNameAcct] = await solanaConnection.getProgramAccounts( + profileProgramId, + { + filters: [ + { + memcmp: { + offset: 9, + bytes: userProfile.pubkey.toString(), + }, + }, + ], + } + ); + const playerName = playerNameAcct ? new TextDecoder().decode(playerNameAcct.account.data.subarray(42)) : ''; + playerProfiles.push({ ...userProfile, name: playerName, index }) + } + profileData = profileData.subarray(80); + } + } + + playerProfile = playerProfiles.length > 1 ? await assistProfileToggle(playerProfiles) : playerProfiles[0]; + console.debug('User Profile: ', playerProfile); + + const [playerProfileFactionAcct] = await profileFactionProgram.account.profileFactionAccount.all([ + { + memcmp: { + offset: 9, + bytes: playerProfile.pubkey.toBase58(), + }, + }, + ]); + playerProfile.faction = playerProfileFactionAcct; + + const userFleetAccts = await sageProgram.account.fleet.all([ + { + memcmp: { + offset: 41, + bytes: playerProfile.pubkey.toBase58(), + }, + }, + ]); + + for (let fleet of userFleetAccts) { + const name = new TextDecoder("utf-8").decode(new Uint8Array(fleet.account.fleetLabel)).replace(/\0/g, ''); + + const fleetDefaultData = { + assignment: null, + origin: { + coords: '', + supplies: { + 'blank1': 0, + 'blank2': 0, + 'blank3': 0, + 'blank4': 0, + } + }, + destination: { + coords: '', + supplies: { + 'blank1': 0, + 'blank2': 0, + 'blank3': 0, + 'blank4': 0, + } + }, + moveType: 'warp', + mineResource: '', + minePlanet: null, + scanMinimumProbability: 10, + scanSkipCnt: 0, + scanSectorStart: 0, + scanSectorEnd: 0, + scanBlock: [], + scanBlockIndex: 0, + scanMove: true, + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + + const { fleetState, extra } = await getCurrentFleet(fleet); + if (fleetState == 'Idle' && extra) { + for (let i = 0; i < fleetDefaultData.scanBlock.length; i++) { + if (extra[0] == fleetDefaultData.scanBlock[i][0] && extra[1] == fleetDefaultData.scanBlock[i][1]) { + fleetDefaultData.scanBlockIndex = i; + break; + } + } + } + + console.log('fleet: ', fleet); + console.log('fleetDefaultData: ', fleetDefaultData); + console.log('fleetSavedData: ', fleetSavedData); + fleet = { name, ...fleet, ...fleetDefaultData, ...fleetSavedData }; + console.log('final: ', fleet); + userFleets.push(fleet); + //await GM.setValue(fleet.publicKey.toString(), JSON.stringify({...fleetDefaultData, ...fleetSavedData})); + } + userFleets.sort(function (a, b) { + return a.name.toUpperCase().localeCompare(b.name.toUpperCase()); + }); + + return { playerProfile, playerProfileFactionAcct }; + } + +/** + * The function `getBalanceChange` calculates the change in balance for a specific account after a + * transaction. + * @param txResult - The `txResult` parameter is an object that represents the result of a transaction. + * It contains information about the transaction, such as the message, metadata, and token balances + * before and after the transaction. + * @param targetAcct - The `targetAcct` parameter is the account address or key that you want to get + * the balance change for. + * @returns an object with two properties: "preBalance" and "postBalance". + */ + function getBalanceChange(txResult, targetAcct) { + const acctIdx = txResult.transaction.message.staticAccountKeys.findIndex(item => item.toString() === targetAcct); + + const preTokenBalances = txResult.meta.preTokenBalances.find(item => item.accountIndex === acctIdx); + const preBalance = preTokenBalances.uiTokenAmount.uiAmount || 0; + const postTokenBalances = txResult.meta.postTokenBalances.find(item => item.accountIndex === acctIdx); + const postBalance = postTokenBalances.uiTokenAmount.uiAmount || 0; + + return { preBalance, postBalance } + } + + /** + * The function calculates the distance between two points in a two-dimensional space. + * @param origin - The origin parameter represents the starting point of movement. It is an array + * containing the x and y coordinates of the origin point. + * @param destination - The destination parameter is the coordinates of the destination point. It is + * an array with two elements, where the first element represents the x-coordinate and the second + * element represents the y-coordinate. + * @returns the distance between the origin and destination points. + */ + function calculateMovementDistance(origin, destination) { + if (typeof origin === 'string') origin = origin.split(',').map(c => c.trim()); + if (typeof destination === 'string') destination = destination.split(',').map(c => c.trim()); + + return Math.sqrt((origin[0] - destination[0]) ** 2 + (origin[1] - destination[1]) ** 2) || 0 + } + +/** + * The function calculates the amount of fuel burned by a fleet traveling a certain distance while warping. + * @param fleet - An object representing a fleet of spaceships. It should have a property called + * "warpFuelConsumptionRate" which represents the rate at which the fleet consumes warp fuel. + * @param distance - The distance is the total distance that the fleet will be traveling. + * @returns the amount of fuel burned by a fleet when traveling a certain distance. + */ + function calculateWarpFuelBurn(fleet, distance) { + return distance * (fleet.account.stats.movementStats.warpFuelConsumptionRate / 100) + } + +/** + * The function calculates the amount of fuel burned by a fleet based on the distance traveled. + * @param fleet - The fleet parameter represents the fleet of ships that will be traveling the + * distance. It is assumed to be an object with properties related to the fleet's subwarp fuel + * consumption rate. + * @param distance - The distance is the total distance that the fleet will be traveling. + * @returns the amount of fuel burned by a fleet when traveling a certain distance. + */ + function calculateSubwarpFuelBurn(fleet, distance) { + return distance * (fleet.account.stats.movementStats.subwarpFuelConsumptionRate / 100) + } + +/** + * The function calculates the duration of mining based on cargo capacity, mining rate, resource + * hardness, and system richness. + * @param cargoCapacity - The cargo capacity refers to the maximum amount of resources that can be + * stored in the mining vessel. + * @param miningRate - The rate at which resources can be extracted via the mining + * operation. It is measured in units per second. + * @param resourceHardness - Resource hardness refers to the difficulty or toughness of the resource + * being mined. It is usually measured on a scale from 0 to 100, where 0 represents a very soft or + * easy-to-mine resource, and 100 represents a very hard or difficult-to-mine resource. + * @param systemRichness - System richness refers to the abundance or concentration of the resource + * being mined in the system. It is usually represented as a percentage, where 100% indicates a high + * concentration of the resource and 0% indicates no concentration or availability of the resource in + * the system. + * @returns the calculated mining duration. + */ + function calculateMiningDuration(cargoCapacity, miningRate, resourceHardness, systemRichness) { + return resourceHardness > 0 ? Math.ceil(cargoCapacity / (((miningRate / 10000) * (systemRichness / 100)) / (resourceHardness / 100))) : 0; + } + +/** + * The function BNToBs58 takes a BigNumber as input, converts it to a byte array, and then encodes it + * using the Base58 encoding scheme. + * @param bignumber - The `bignumber` parameter is a number that you want to convert to a Base58 + * encoded string. + * @returns a Base58 encoded string. + */ + function BNToBs58(bignumber) { + const bn = new BrowserAnchor.anchor.BN(bignumber); + const bnArray = bn.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); + return bs58.encode(bnArray); + } + +/** + * The function `getStarbaseFromCoords` retrieves a starbase using it's coordinates. + * @param x - The `x` parameter represents the x-coordinate of the starbase location. It is used to + * search for a starbase that matches the given x-coordinate. + * @param y - The parameter `y` represents the y-coordinate of a starbase. + * @returns The function `getStarbaseFromCoords` returns the starbase object that matches the given + * coordinates (x, y). + */ + async function getStarbaseFromCoords(x, y) { + const [starbase] = await sageProgram.account.starbase.all([ + { + memcmp: { + offset: 41, + bytes: BNToBs58(x) + } + }, + { + memcmp: { + offset: 49, + bytes: BNToBs58(y) + } + }, + ]); + + return starbase; + } + +/** + * The function `getPlanetsFromCoords` retrieves planets from coordinates (x, y) using the Sage program. + * @param x - The parameter `x` represents the x-coordinate of a location + * @param y - The parameter `y` represents the y-coordinate of a location. + * @returns an array of planets found in the given coordinates. + */ + async function getPlanetsFromCoords(x, y) { + return await sageProgram.account.planet.all([ + { + memcmp: { + offset: 105, + bytes: BNToBs58(x) + } + }, + { + memcmp: { + offset: 113, + bytes: BNToBs58(y) + } + }, + ]); + } + +/** + * The function `getStarbasePlayer` retrieves a starbase player based on the user profile and starbase + * provided. + * @param playerProfile - The `playerProfile` is the public key of the player profile. + * @param starbase - The `starbase` is the public key for the given starbase. + * @returns the `starbasePlayer` public key. + */ + async function getStarbasePlayer(playerProfile, starbase) { + const [starbasePlayer] = await sageProgram.account.starbasePlayer.all([ + { + memcmp: { + offset: 9, + bytes: playerProfile.pubkey.toBase58() + } + }, + { + memcmp: { + offset: 73, + bytes: starbase.toBase58() + } + }, + ]); + return starbasePlayer; + } + + async function getFleetCountAtCoords() { + const gridSize = document.querySelector('#fleetGridSelect').value; + const targetCoords = document.querySelector('#checkFleetCntInput').value; + + const fleetGrid = document.querySelector('#fleetGrid'); + const loadingMessage = document.querySelector('#loadingMessage'); + + if (!targetCoords || targetCoords.trim() === '') { + loadingMessage.innerText = 'Please enter target coordinates for grid center.'; + loadingMessage.style.display = 'block'; + fleetGrid.style.display = 'none'; + return;// Stop further processing since input is empty or idle + } + const [x, y] = targetCoords.split(',').map(coords => parseInt(coords.trim())); + fleetGrid.innerHTML = ''; // Clear previous results + + try { + loadingMessage.innerText = 'Loading...'; + loadingMessage.style.display = 'block'; + fleetGrid.style.display = 'none'; + + for (let i = 0; i < gridSize; i++) { + const row = fleetGrid.insertRow(); + for (let j = 0; j < gridSize; j++) { + const coordX = x + j - Math.floor(gridSize / 2);// Adjusted for column-first population + const coordY = y + (gridSize-1) - i - Math.floor(gridSize / 2);// Adjusted for descending y value + + const fleetAccts = await solanaConnection.getProgramAccounts(sageProgramId, { + filters: [ + { memcmp: { offset: 415, bytes: BNToBs58(x) } }, + { memcmp: { offset: 423, bytes: BNToBs58(y) } }, + ], + }); + + const cell = row.insertCell(j); + // Create a div to hold the content for formatting + const contentDiv = document.createElement('div'); + contentDiv.style.textAlign = 'center'; + + // Set the content of the div (coordinates and fleet count) + contentDiv.innerHTML = `
[${coordX},${coordY}]
${fleetAccts.length}
`; + + // Calculate gradient color based on fleetAccts.length + const gradientColor = calculateGradientColor(fleetAccts.length); + + // Apply background gradient + cell.style.background = gradientColor; + + // Apply border style + cell.style.border = '2px solid rgb(255, 190, 77)'; + cell.style.borderRadius = '8px'; + cell.style.padding = '9px'; // Adjust padding to maintain inner content space + cell.style.position = 'relative'; + + // Append the content div to the cell + cell.appendChild(contentDiv); + + // Add a green Unicode circle if fleet count is below 5 + if (fleetAccts.length < 5) { + const greenCircle = document.createElement('div'); + greenCircle.style.position = 'absolute'; + greenCircle.style.bottom = '-3px'; + greenCircle.style.right = '0px'; + greenCircle.style.fontSize = '20px'; + greenCircle.innerHTML = '●'; // Unicode circle + greenCircle.style.color = 'rgb(0, 255, 0, 1)'; + greenCircle.style.opacity = '1.0'; + cell.appendChild(greenCircle); + } + + // Function to calculate gradient color + function calculateGradientColor(fleetCount) { + const maxFleetCount = 25; // Maximum fleet count for the hottest color + // Map the fleet count to an RGB value (blue to red gradient) + const r = Math.floor((fleetCount / maxFleetCount) * 255); + const g = 0; + const b = Math.floor(((maxFleetCount - fleetCount) / maxFleetCount) * 255); + + // Calculate the gradient direction based on fleet count (0 degrees for cool to 90 degrees for hot) + const gradientDirection = 0 + (fleetCount / maxFleetCount) * 90; + + // Adjust the gradient direction by 180 degrees to place it at the bottom + const adjustedGradientDirection = gradientDirection - 45; + + // Construct the gradient CSS with stops from 0% to 50% for the color and 50% to 100% for transparent + return `linear-gradient(${adjustedGradientDirection}deg, rgba(${r}, ${g}, ${b}, 1) 0%, rgba(${r}, ${g}, ${b}, 0) 50%, rgba(${r}, ${g}, ${b}, 0) 100%)`; + } + } + } + + loadingMessage.style.display = 'none'; + fleetGrid.style.display = 'block'; + } catch (error) { + console.error('Error fetching fleet information:', error); + loadingMessage.innerText = 'Error fetching fleet information'; + } + } + +/** + * The function `httpMonitor` is an asynchronous function that monitors the status of a transaction. + * @param connection - The `connection` parameter is an object that represents the connection to the + * Solana blockchain network. It is used to interact with the network and perform various operations + * such as sending transactions, querying account information, etc. + * @param txHash - The transaction hash of the transaction you want to monitor. + * @param txn - The `txn` parameter represents the serialized transaction that you want to monitor. It is + * used if the transaction needs to be re-submitted. + * @param lastValidBlockHeight - The `lastValidBlockHeight` parameter represents the block height up to + * which the monitoring function should wait for the transaction to be confirmed. If the current block + * height exceeds or equals `lastValidBlockHeight`, the function will return a timeout error. + * @param lastMinAverageBlockSpeed - The `lastMinAverageBlockSpeed` parameter represents the average + * block speed in the last minute. It is used to calculate the waiting time before checking the + * transaction status again. + * @param [count=1] - The `count` parameter is used to keep track of the number of times the + * `httpMonitor` function has been called recursively. It is initially set to 1 and is incremented each + * time the function is called. + * @returns an object with properties `name` and `err` if a timeout occurs. Otherwise, it returns an + * object with properties `txHash` and `confirmation` if the transaction is confirmed. + */ + async function httpMonitor(connection, txHash, txn, lastValidBlockHeight, lastMinAverageBlockSpeed, count = 1) { + const acceptableCommitments = [connection.commitment, 'finalized']; + try { + let { blockHeight } = await connection.getEpochInfo({ commitment: 'confirmed' }); + if (blockHeight >= lastValidBlockHeight) return { name: 'LudicrousTimoutError', err: `Timed out for ${txHash}` }; + const signatureStatus = await connection.getSignatureStatus(txHash); + + if (signatureStatus.err) { + console.log('HTTP error for', txHash, signatureStatus); + return signatureStatus; + } else if (signatureStatus.value === null || !acceptableCommitments.includes(signatureStatus.value.confirmationStatus)) { + console.log('HTTP not confirmed', txHash, signatureStatus); + await wait(lastMinAverageBlockSpeed * graceBlockWindow); + if (count % 7 == 0) { + console.log('---RESENDTXN---'); + await connection.sendRawTransaction(txn, {skipPreflight: true, maxRetries: 0, preflightCommitment: 'confirmed'}); + } + if (count < 30) return httpMonitor(connection, txHash, txn, lastValidBlockHeight, ++count); + } else if (acceptableCommitments.includes(signatureStatus.value.confirmationStatus) ) { + console.log('HTTP confirmed', txHash, signatureStatus); + return { txHash, confirmation: signatureStatus }; + } + } catch (error) { + console.log(`HTTP connection error: ${txHash}`, error); + return error; + } + + return { name: 'LudicrousTimoutError', err: `Timed out for ${txHash}` }; + } + +/** + * The function `sendLudicrousTransaction` sends a transaction using a connection object, logs the + * transaction hash, and then calls the `httpMonitor` function to monitor the transaction's status. + * @param txn - The `txn` parameter is the transaction object that you want to send. It contains all + * the necessary information for the transaction, such as the sender, recipient, amount, and any + * additional data or instructions. + * @param lastValidBlockHeight - The `lastValidBlockHeight` parameter represents the height of the last + * valid block on the blockchain. It is used in the `sendLudicrousTransaction` function to monitor the + * transaction's confirmation status and ensure that it is included in a block after the specified + * height. + * @param connection - The `connection` parameter is an object that represents the connection to the + * Solana blockchain network. It is used to interact with the network and perform various operations + * such as sending transactions, querying account information, etc. + * @returns The function `sendLudicrousTransaction` is returning the result of the `httpMonitor` + * function. + */ + async function sendLudicrousTransaction(txn, lastValidBlockHeight, connection) { + console.log('---SENDTXN---'); + let txHash = await connection.sendRawTransaction(txn, {skipPreflight: true, maxRetries: 0, preflightCommitment: 'confirmed'}); + console.log(txHash); + + const recentPerformanceSamples = await connection.getRecentPerformanceSamples(1); + const { samplePeriodSecs, numSlots } = recentPerformanceSamples[0]; + const lastMinAverageBlockSpeed = Math.floor(samplePeriodSecs * 1000 / numSlots); + + return await httpMonitor(connection, txHash, txn, lastValidBlockHeight, lastMinAverageBlockSpeed); + } + +/** + * The function `batchTransactions` is an asynchronous function that takes an array of instructions + * (`ixs`) and batches them into multiple transactions based on a maximum transaction size. + * @param ixs - An array of instructions (ixs) to be batched together. Each instruction represents + * a specific action to be performed on the Solana blockchain. + * @param [txs] - The `txs` parameter is an array that stores the transactions that have been batched + * so far. It is an optional parameter and is initially an empty array. Each transaction is added to + * this array before recursively calling the `batchTransactions` function again with the remaining + * transactions. + * @returns The function `batchTransactions` returns an array of transactions (`txs`). + */ + async function batchTransactions(ixs, blockhash, lastValidBlockHeight, txs = []) { + const tx = new solanaWeb3.Transaction(); + + for (let index = 0; index < ixs.length; index++) { + let before = ixs.slice(0, index + 1); + const after = ixs.slice(-(ixs.length - (index + 1))); + + if (getTransactionSize(before, provider.publicKey) > MAX_TRANSACTION_SIZE) { + before = ixs.slice(0, index); + } else { + if (before.length != after.length) continue; + } + + before.forEach(ix => tx.add(ix.instruction)); + tx.recentBlockhash = blockhash; + tx.lastValidBlockHeight = lastValidBlockHeight; + tx.feePayer = provider.publicKey; + tx.signer = provider.publicKey; + txs.push(tx); + if (after.length > 0) return batchTransactions(after, blockhash, lastValidBlockHeight, txs); + } + return txs; + } + +/** + * The function `signAndSend` signs and sends a batch of transactions to the Solana blockchain, + * handling errors and retries if necessary. + * @param ixs - The parameter `ixs` is an array of instructions that you want to include in the + * transaction. Each instruction represents an action to be performed on the Solana blockchain. + * @returns an array of transaction results. + */ + async function signAndSend(ixs) { + let { blockhash, lastValidBlockHeight } = await solanaConnection.getLatestBlockhash('confirmed'); + lastValidBlockHeight = lastValidBlockHeight - 150; + + if (ixs.constructor !== Array) ixs = [ixs]; + + console.log('---INSTRUCTION---'); + console.log(ixs); + const txns = await batchTransactions(ixs, blockhash, lastValidBlockHeight); + const signedTxns = await provider.signAllTransactions(txns); + + let txResults = []; + for (const txn of signedTxns) { + ({ txHash, confirmation} = await sendLudicrousTransaction(txn.serialize(), lastValidBlockHeight, solanaConnection)); + console.log('---CONFIRMATION---'); + console.log(confirmation); + if ((confirmation.name == 'TransactionExpiredBlockheightExceededError' || confirmation.name == 'LudicrousTimoutError')) { + console.log('-----RETRY-----'); + return signAndSend(ix); + } + + const txResult = await solanaConnection.getTransaction(txHash, {commitment: 'confirmed', preflightCommitment: 'confirmed', maxSupportedTransactionVersion: 1}); + console.log('txResult: ', txResult); + txResults.push(txResult); + } + return txResults; + } + +/** + * The `execScan` triggers a scan for survey data units. + * @param fleet - The `fleet` parameter represents the fleet object. + * @param playerProfile - The `playerProfile` parameter represents the user's profile information. It + * includes properties such as `index`, `pubkey`, and `faction`. + * @returns the transaction result of the scan. + */ + async function execScan(fleet, playerProfile) { + const tx = { instruction: await sageProgram.methods.scanForSurveyDataUnits({keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index)}).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + surveyDataUnitTracker: sageSDUTrackerAcct.publicKey, + surveyDataUnitTrackerSigner: sageSDUTrackerAcct.account.signer, + cargoHold: fleet.account.cargoHold, + sduCargoType: sduCargoTypeAcct.publicKey, + repairKitCargoType: repairKitCargoTypeAcct.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + sduTokenFrom: await ResourceTokens.getPodToken({ name: 'sdu' }), + sduTokenTo: ResourceTokens.sdu.publicKey, + repairKitTokenFrom: await ResourceTokens.getPodToken({ name: 'toolkit', pod: fleet.account.cargoHold }), + repairKitMint: sageGameAcct.account.mints.repairKit, + cargoProgram: cargoProgramId, + tokenProgram, + recentSlothashes: RecentSlotHashes, + instructionsSysvar: InstructionsSysVar + }).instruction()} + return await signAndSend(tx); + } + +/** + * The `execSubwarp` function calculates the distance between two coordinates, checks if the fleet has + * enough fuel to subwarp, and if it does then initiates a subwarp to the destination coordinates. + * @param fleet - The `fleet` parameter represents a fleet of ships that will be used for the subwarp + * operation. It contains information about the fleet, such as its origin and destination coordinates, + * maximum warp distance, and fuel tank account. + * @param origin - The `origin` parameter is optional, it is the origin point for the subwarp. + * Defaults to the origin in the fleet's config file. It is an array containing the X and Y coordinates. + * @param destination - The `destination` parameter is optional, it is the destination point for the subwarp. + * Defaults to the destination in the fleet's config file. It is an array containing the X and Y coordinates. + * @returns the duration for subwarp and the transaction result from Solana for entering subwarp. + */ + async function execSubwarp(fleet, origin, destination) { + const { subwarpSpeed } = fleet.account.stats.movementStats; + const [destX, destY] = (destination || fleet.destination.coords).split(',').map(item => item.trim()); + const [originX, originY] = (origin || fleet.origin.coords).split(',').map(item => item.trim()); + + const moveDistance = calculateMovementDistance([originX, originY], [destX, destY]); + const duration = subwarpSpeed > 0 ? (moveDistance / subwarpSpeed / GLOBAL_SCALE_DECIMALS_6) : 0; + const subwarpCost = calculateSubwarpFuelBurn(fleet, moveDistance); + + const fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.fuelTank, { programId: tokenProgram }); + let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); + currentFuel = currentFuel.account.data.parsed.info.tokenAmount.uiAmount || 0; + + if (currentFuel < subwarpCost) { + console.log(`[${fleet.label}] Unable to move, lack of fuel`); + return fleet.state = 'ERROR: Not enough fuel'; + } + + const tx = { instruction: await sageProgram.methods.startSubwarp({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index), + toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)] + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + }).instruction()} + const txResult = await signAndSend(tx); + return { duration, txResult }; + } + + /** + * The function `execExitSubwarp` executes the command (submits transaction) to exit subwarp to Solana. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns the transaction result from Solana for exiting subwarp. + */ + async function execExitSubwarp(fleet) { + const tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).remainingAccounts([ + { + pubkey: playerProfile.pubkey, + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + isSigner: false, + isWritable: true + }, + { + pubkey: fuelCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.account.cargo.statsDefinition, + isSigner: false, + isWritable: false + }, + { + pubkey: ResourceTokens.fuel.publicKey, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.fuel, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.gameState, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: cargoProgramId, + isSigner: false, + isWritable: false + }, + { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }, + ]).instruction()} + return await signAndSend(tx); + } + +/** + * The `execWarp` function calculates the distance between two coordinates, checks if the fleet has + * enough fuel to warp, and if it does then initiates a warp to the destination coordinates. + * @param fleet - The fleet parameter represents a fleet of ships that will be used for the warp + * operation. It contains information about the fleet, such as its origin and destination coordinates, + * maximum warp distance, and fuel tank account. + * @param origin - The `origin` parameter is optional, it is the origin point for the warp. + * Defaults to the origin in the fleet's config file. It is an array containing the X and Y coordinates. + * @param destination - The `destination` parameter is optional, it is the destination point for the warp. + * Defaults to the destination in the fleet's config file. It is an array containing the X and Y coordinates. + * @returns the duration for warp and the transaction result from Solana for entering warp. + */ + async function execWarp(fleet, origin, destination) { + const { warpSpeed, maxWarpDistance, warpCoolDown, subwarpSpeed } = fleet.account.stats.movementStats; + const [destX, destY] = (destination || fleet.destination.coords).split(',').map(item => item.trim()); + const [originX, originY] = (origin || fleet.origin.coords).split(',').map(item => item.trim()); + + let moveDistance = calculateMovementDistance([originX, originY], [destX, destY]); + let warpCount = 0; + if (moveDistance > (maxWarpDistance / 100)) { + warpCount = maxWarpDistance > 0 ? (moveDistance / maxWarpDistance / GLOBAL_SCALE_DECIMALS_2) : 1; + const warpX = Math.trunc((destX - originX) / warpCount); + const warpY = Math.trunc((destY - originY) / warpCount); + + destX = originX + warpX; + destY = originY + warpY; + + moveDistance = calculateMovementDistance([originX, originY], [destX,destY]); + } + + const duration = warpSpeed > 0 ? (moveDistance / warpSpeed / GLOBAL_SCALE_DECIMALS_6) : 0; + const warpCost = calculateWarpFuelBurn(fleet, moveDist); + + const fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.fuelTank, { programId: tokenProgram }); + let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); + currentFuel = currentFuel.account.data.parsed.info.tokenAmount.uiAmount || 0; + + if (currentFuel < (warpCost * warpCount)) { + console.log(`[${fleet.label}] Unable to move, lack of fuel`); + return fleet.state = 'ERROR: Not enough fuel'; + } + + const tx = { instruction: await sageProgram.methods.warpToCoordinate({keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index), toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)]}).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + fuelTank: fleet.account.fuelTank, + cargoType: fuelCargoTypeAcct.publicKey, + statsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + tokenMint: sageGameAcct.account.mints.fuel, + cargoProgram: cargoProgramId, + tokenProgram + }).instruction()} + const txResult = await signAndSend(tx); + return { duration, warpCount, txResult }; + } + +/** + * The function `execExitWarp` executes the command (submits transaction) to exit warp to Solana. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns the transaction result from Solana for exiting warp. + */ + async function execExitWarp(fleet) { + const tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).instruction()} + return await signAndSend(tx); + } + +/** + * The `execDock` function takes a fleet and coordinates as input, retrieves the starbase and starbase + * player associated with the coordinates, and then executes a transaction to dock the fleet at the + * starbase. Should be called before {@link handleReturnTrip} function. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @param coords - The `coords` parameter is an optional string representing the coordinates of a starbase. + * Defaults to `fleet.destination.coords`. It is in the format "x,y" where `x` and `y` are the x and y coordinates + * respectively. + * @returns the transaction result from Solana for docking. + */ + async function execDock(fleet, coords) { + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const tx = { instruction: await sageProgram.methods.idleToLoadingBay(new BrowserAnchor.anchor.BN(playerProfile.index)).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + } + }).instruction()} + fleet.state = 'Docking'; + return await signAndSend(tx); + } + +/** + * The `execUndock` function undocks a fleet from a starbase. Should be called after {@link handleReturnTrip} + * function. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @param coords - The `coords` parameter is an optional string representing the coordinates of a starbase. + * Defaults to `fleet.origin.coords`. It is in the format "x,y" where `x` and `y` are the x and y coordinates + * respectively. + * @returns the transaction result from Solana for undocking. + */ + async function execUndock(fleet, coords) { + const [starbaseX, starbaseY] = (coords || fleet.origin.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const tx = { instruction: await sageProgram.methods.loadingBayToIdle(new BrowserAnchor.anchor.BN(playerProfile.index)).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + } + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction()} + fleet.state = 'Undocking'; + return await signAndSend(tx); + } + + // @todo - documentation + async function determineDefaultLocation(fleet, extra, reverse) { + let location = 'destination'; // default location + for (const [key, value] of Object.entries(fleet)) { + if (value.coords && value.coords == `"${extra[0]},${extra[1]}"`){ + location = key; + break; + } + } + + return reverse ? (location == 'destination' ? 'origin' : 'destination') : location; + } + + // @todo - redo documentation + // @todo - implement fuel and ammo logic +/** + * The function `execCargoFromFleetToStarbase` transfers cargo from a fleet to a starbase. + * @param fleet - The `fleet` parameter represents the fleet object that contains information about the + * fleet, such as its account, destination coordinates, and cargo hold. + * @param options - The `options` parameter is an object that contains the following properties: + * `coords`, `resupply`, and `supplies`. `coords` is an optional parameter, defaults to `fleet.destination.coords`, + * in the format "x,y" where `x` and `y` are the x and y coordinates respectively. `resupply` is an optional + * parameter, a boolean if set will default `supplies` to that on the `fleet` object. `supplies` is an optional + * parameter which is an object "{'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim': amount}" + * @returns the transaction result from Solana for transferring cargo from a fleet to a starbase. + */ + async function execCargoFromFleetToStarbase(fleet, options = {}) { + let { coords, supplies, dump } = options; + const { extra } = await getCurrentFleet(fleet); + const location = await determineDefaultLocation(fleet, extra); // needs to be opposite of current location + + supplies = supplies || fleet[location].supplies; + const [starbaseX, starbaseY] = (coords || fleet[location].coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ + { + memcmp: { + offset: 41, + bytes: starbasePlayer.publicKey.toBase58(), + }, + }, + ]); + const starbasePlayerCargoHold = starbasePlayerCargoHolds.find(item => item.account.openTokenAccounts > 0); + let resourcesToUnload = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + if (supplies && !dump) resourcesToUnload.value = resourcesToUnload.value.filter(item => Object.keys(supplies).includes(item.account.data.parsed.info.mint.toString())); + + let ixs = []; + for (let resource of resourcesInCargoHold.value) { + const resourceString = resource.account.mint.toString() + let amount = resource.account.data.parsed.info.tokenAmount.uiAmount || 0; + if (!dump) amount = Math.min(supplies[resourceString] || 0, amount); + if (amount > 0) { + const resourceCargoType = cargoTypes.find(item => item.account.mint.toString() == resourceString); + const ix = await sageProgram.methods.withdrawCargoFromFleet({ + amount: new BrowserAnchor.anchor.BN(amount), + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.account, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + cargoPodFrom: fleet.account.cargoHold, + cargoPodTo: starbasePlayerCargoHold.publicKey, + cargoType: resourceCargoType.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: fleet.account.cargoHold, + tokenTo: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: starbasePlayerCargoHold.publicKey }), + tokenMint: resourceString, + fundsTo: provider.publicKey, + cargoProgram: cargoProgramId, + tokenProgram + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction() + ixs.push({ instruction: ix }) + } + } + fleet.state = 'Unloading'; + if (ixs.length > 0) await signAndSend(ixs); + fleet.state = 'Docked'; + } + + // @todo - redo documentation +/** + * The function `execCargoFromStarbaseToFleet` transfers cargo from a starbase to a fleet in a game, + * based on the provided options. + * @param fleet - The `fleet` parameter represents the fleet object that contains information about the + * fleet, such as its account, destination coordinates, and cargo hold. + * @param options - The `options` parameter is an object that contains the following properties: + * `coords`, `resupply`, and `supplies`. `coords` is an optional parameter, defaults to `fleet.destination.coords`, + * in the format "x,y" where `x` and `y` are the x and y coordinates respectively. `resupply` is an optional + * parameter, a boolean if set will default `supplies` to that on the `fleet` object. `supplies` is an optional + * parameter which is an object "{'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim': amount}" + * @returns the transaction result(s) from Solana for transferring cargo from a starbase to a fleet. + */ + async function execCargoFromStarbaseToFleet(fleet, options = {}) { + let { coords, supplies } = options; + const { extra } = await getCurrentFleet(fleet); + const location = await determineDefaultLocation(fleet, extra, true); + + supplies = supplies || fleet[location].supplies; + const [starbaseX, starbaseY] = (coords || fleet[location].coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ + { + memcmp: { + offset: 41, + bytes: starbasePlayer.publicKey.toBase58(), + }, + }, + ]); + + const starbasePlayerCargoHold = starbasePlayerCargoHolds.find(item => item.account.openTokenAccounts > 0); + let resourcesToLoad = await solanaConnection.getParsedTokenAccountsByOwner(starbasePlayerCargoHold.publicKey, {programId: tokenProgram}); + if (supplies) resourcesToLoad.value = resourcesToLoad.value.filter(item => Object.keys(supplies).includes(item.account.data.parsed.info.mint.toString())); + + let ixs = []; + for (let resource of resourcesInCargoHold.value) { + const resourceString = resource.account.mint.toString() + const amount = Math.min(supplies[resourceString] || 0, resource.account.data.parsed.info.tokenAmount.uiAmount || 0); + if (amount > 0) { + const resourceCargoType = cargoTypes.find(item => item.account.mint.toString() == resourceString); + const ix = { instruction: await sageProgram.methods.depositCargoToFleet({ + amount: new BrowserAnchor.anchor.BN(amount), + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + fundsTo: provider.publicKey, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + cargoPodFrom: starbasePlayerCargoHold.publicKey, + cargoPodTo: fleet.account.cargoHold, + cargoType: resourceCargoType.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: starbasePlayerCargoHold.publicKey }), + tokenTo: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: fleet.account.cargoHold }), + tokenMint: resourceString, + cargoProgram: cargoProgramId, + tokenProgram + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction()} + ixs.push({ instruction: ix }) + } + } + fleet.state = 'Loading'; + if (ixs.length > 0) await signAndSend(ixs); + fleet.state = 'Docked'; + } + +/** + * The `getMiningDetails` function retrieves mining details based on the mine resource and coordinates + * provided. + * @param mineResource - The `mineResource` parameter is the public key that represents the resource you + * want to mine. + * @param coords - The `coords` parameter is the coordinates of a location. It is used to retrieve the + * planets associated with those coordinates. + * @returns The `getMiningDetails` function returns an object with three properties: `mineItem`, + * `planet`, and `sageResource`. + */ + async function getMiningDetails(mineResource, coords) { + const planets = await getPlanetsFromCoords(coords[0], coords[1]); + const [mineItem] = await sageProgram.account.mineItem.all([ + { + memcmp: { + offset: 105, + bytes: mineResource, + }, + }, + ]); + + let planet, sageResource; + for (let planetCheck of planets) { + const resourceCheck = await sageProgram.account.resource.all([ + { + memcmp: { + offset: 41, + bytes: planetCheck.publicKey, + }, + }, + { + memcmp: { + offset: 73, + bytes: mineItem.publicKey, + }, + }, + ]); + if (resourceCheck.length > 0) { + console.log('FOUND: ', resourceCheck); + [sageResource] = resourceCheck; + planet = planetCheck + break; + } + } + return { mineItem, planet, sageResource }; + } + + /** + * The `execStartMining` function starts the mining process for a fleet. + * @param fleet - The `fleet` parameter represents the fleet object, which contains information + * about the fleet such as its account, destination coordinates, and mining stats. + * @param options - The `options` is an object that contains the following optional parameters: + * - `coords` in the format "x,y" where `x` and `y` are the x and y coordinates respectively. + * Defaults to `fleet.destination.coords`. + * - `mineResource` is the name of the resource you want to mine. + * Defaults to `fleet.mineResource`. + * - `amount` is the amount you want to mine. If `amount` > `maxCargoCapacity`, `maxCargoCapacity` + * is used. Defaults to the `maxCargoCapacity`. + * @returns an object containing `duration` and the transaction result from Solana. + */ + async function execStartMining(fleet, options = {}) { + const { starbase, starbasePlayer, planet } = options; + const ix = { instruction: await sageProgram.methods.startMiningAsteroid({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + mineItem : mineItem.publicKey, + resource: sageResource.publicKey, + planet: planet.publicKey, + }).instruction()} + fleet.state = 'Mining'; + return await signAndSend(ix); + } + +/** + * The `execStopMining` function stops a fleet from mining a resource. + * @param fleet - The `fleet` parameter represents the fleet object, which contains information about + * the fleet such as its public key, account details, and cargo hold. + * @param options - The `options` is an object that contains the following optional parameters: + * - `coords` in the format "x,y" where `x` and `y` are the x and y coordinates respectively. + * Defaults to `fleet.destination.coords`. + * - `mineResource` is the name of the resource you want to mine. + * Defaults to `fleet.mineResource`. + * @returns the transaction result from Solana for stopping mining. + */ + async function execStopMining(fleet, options = {}) { + let { coords, mineResource } = options; + mineResource = ResourceTokens[mineResource || fleet.mineResource]; + + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const { mineItem, planet, sageResource } = await getMiningDetails(mineResource.publicKey.toString(), [starbaseX, starbaseY]); + + const foodCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.food); + const ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); + const resourceCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == resourceToken.toString()); + + const tx1 = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).remainingAccounts([ + { + pubkey: playerProfile.faction.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: fleet.account.cargoHold, + isSigner: false, + isWritable: true + }, + { + pubkey: fleet.account.ammoBank, + isSigner: false, + isWritable: true + }, + { + pubkey: mineItem.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageResource.publicKey, + isSigner: false, + isWritable: true + }, + { + pubkey: planet.publicKey, + isSigner: false, + isWritable: true + }, + { + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'food', pod: fleet.account.cargoHold }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'ammo', pod: fleet.account.ammoBank }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ publicKey: mineResource.publicKey, pod: mineItem.publicKey }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ publicKey: mineResource.publicKey, pod: fleet.account.cargoHold }), + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.food, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.ammo, + isSigner: false, + isWritable: true + }, + { + pubkey: foodCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: ammoCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: resourceCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.account.cargo.statsDefinition, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: cargoProgramId, + isSigner: false, + isWritable: false + }, + { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }, + ]).instruction()} + + const tx2 = { instruction: await sageProgram.methods.stopMiningAsteroid({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + resource: sageResource.publicKey, + planet: planet.publicKey, + fuelTank : fleet.account.fuelTank, + cargoType: fuelCargoTypeAcct.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + tokenMint: sageGameAcct.account.mints.fuel, + cargoProgram: cargoProgramId, + tokenProgram + }).instruction()} + fleet.state = 'Mining stopped'; + return await signAndSend([tx1,tx2]); + } + + // @todo - documentation + async function handleResupply(fleet, dump = false) { + await execDock(fleet); + await execCargoFromFleetToStarbase(fleet, { dump }); + await execCargoFromStarbaseToFleet(fleet); + await execUndock(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return Object.assign({}, fleet, fleetSavedData); + } + +/** + * The `handleMovement` function handles the movement of a fleet, checking its current state + * and executing the appropriate actions based on that state. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns The function does not explicitly return anything. + */ + async function handleMovement(fleet, options = {}) { + const { coords } = options; + const [destX, destY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + + const { fleetData, fleetState, extra }= await getCurrentFleet(fleet); + const warpCooldownExpiresAt = (fleetData.warpCooldownExpiresAt.toNumber() || 0) * 1000; + + console.log('extra[0]: ', extra[0]); + console.log('destX: ', destX); + console.log(typeof extra[0]); + console.log(typeof destX); + console.log('extra[1]: ', extra[1]); + console.log('destY: ', destY); + + let warpArrival, subwarpArrival; + switch (fleetState) { + case 'Idle': + if (extra[0] !== Number(destX) || extra[1] !== Number(destY)) { + const currentLocation = [extra[0], extra[1]]; + + if (fleet.moveType == 'warp') { + if (warpCooldownExpiresAt > 0) { + await execSubwarp(fleet, currentLocation); + } else { + await execWarp(fleet, currentLocation); + } + } else { + await execSubwarp(fleet, currentLocation); + } + } else { + fleet.state = "Arrived"; + return; + } + break; + case 'MoveWarp': + warpArrival = extra.warpFinish.toNumber() * 1000; + if (warpArrival > Date.now()) fleet.state = 'Move [' + new Date(warpArrival).toLocaleTimeString() + ']'; + await wait(warpArrival); + await execExitWarp(fleet); + break; + case 'MoveSubwarp': + subwarpArrival = fleet.moveType == 'warp' ? warpCooldownExpiresAt : extra.arrivalTime.toNumber() * 1000; + if (subwarpArrival > Date.now()) fleet.state = 'Move [' + new Date(subwarpArrival).toLocaleTimeString() + ']'; + await wait(subwarpArrival); + await execExitSubwarp(fleet); + break; + default: + console.log('I only handle movement, bro'); + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return handleMovement({ ...fleetData, ...fleetSavedData }, options); + } + + // @todo - documentation + async function prepareToMine(fleet, options = {}) { + let { coords, mineResource, amount } = options; + + const { foodConsumptionRate, ammoConsumptionRate } = fleet.account.stats.cargoStats; + mineResource = ResourceTokens.getResourceByName(mineResource || fleet.mineResource); + //mineResource = ResourceTokens[mineResource || fleet.mineResource]; + + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile, starbase.publicKey); + + const { mineItem, planet, sageResource } = await getMiningDetails(mineResource.publicKey.toString(), [starbaseX, starbaseY]); + const resourceHardness = mineItem.account.resourceHardness; + const { systemRichness } = sageResource.account; + + const { cargoCapacity, miningRate } = fleet.account.stats.cargoStats; + const currentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + const currentCargoCount = currentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxCargoCapacity = cargoCapacity - currentCargoCount; + let mineAmount = amount || maxCargoCapacity; + if (mineAmount > maxCargoCapacity) { + mineAmount = maxCargoCapacity; + console.log(`Can't mine ${mineAmount} adjusted to ${maxCargoCapacity}, rock and STONE!`) + } + + const duration = calculateMiningDuration(mineAmount, miningRate, resourceHardness, systemRichness); + + console.log('fleet: ', fleet); + console.log('origin: ', fleet.origin); + console.log('supplies: ', fleet.origin.supplies); + fleet.origin.supplies[mineResource.name] = mineAmount; + fleet.destination.supplies[ResourceTokens.ammo.name] = Math.floor(duration * ammoConsumptionRate); + fleet.destination.supplies[ResourceTokens.food.name] = Math.floor(duration * foodConsumptionRate); + + return { duration, starbase, starbasePlayer, planet }; + } + + // @todo - documentation + async function prepareForTrip(fleet) { + // get all the most recent information about the fleet + const { fleetData } = await getCurrentFleet(fleet); + //if (fleetData.assignment.toLowerCase() in fleetState.toLowerCase()) return { fleet, skip: true} + // preparing for trip will allows check destination + const { supplies } = fleet.destination; + let fleetCargoHold = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + let resupplyNeeded = false; + + if (supplies) { + console.log('DEBUG'); + console.log('fleetCargoHold: ', fleetCargoHold); + console.log('supplies: ', supplies); + fleetCargoHold.value = fleetCargoHold.value.filter(item => Object.keys(supplies).includes(item.account.data.parsed.info.mint.toString())); + + for (let cargo of fleetCargoHold.value) { + const cargoString = cargo.account.data.parsed.info.mint.toString(); + const amount = cargo.account.data.parsed.info.tokenAmount.uiAmount || 0; + resupplyNeeded = supplies[cargoString] != amount; + } + } + + if (resupplyNeeded || !supplies) { + await handleMovement(fleet, { coords: fleet.origin.coords }); + await handleResupply(fleet, true); + } + + return { fleet, skip: false }; + } + +/** + * The function `handleReturnTrip` handles the return trip of a fleet by flipping the origin and + * destination coordinates and updating the fleet's saved data. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + */ + async function handleReturnTrip(fleet) { + // shallow copy to mutate and flip origin and destination + const { extra } = await getCurrentFleet(fleet); + const { origin, destination } = JSON.parse(JSON.stringify(fleet)); + const [destX, destY] = destination; + + if (fleetState == 'Idle' && (extra[0] === destX || extra[1] === destY)) { + const fleetPK = fleet.publicKey.toString(); + let fleetSavedData = await GM.getValue(fleetPK, '{}'); + let fleetParsedData = JSON.parse(fleetSavedData); + + fleet.origin.coords = destination.coords; + fleet.destination.coords = origin.coords; + + fleetParsedData.origin.coords = fleet.origin.coords; + fleetParsedData.destination.coords = fleet.destination.coords; + + await GM.setValue(fleetPK, JSON.stringify(fleetParsedData)); + await handleMovement(fleet); + } + } + +/** + * The `handleScan` function is responsible for scanning and determining the next action based on the scan + * results and fleet conditions. + * @param fleet - The `fleet` parameter represents an object that contains information about a fleet. + * @param options - The `options` is an object that contains the following optional parameters: + * - `cargoHoldBuffer` the amount of space remaining in cargoHold before returning to origin. + * Defaults to 100 units. + * - `scanDelay` amount of time in seconds after 4 strikes we delay before scanning again. + * Defaults to 600 seconds. + * - `scanSectorAge` the amount of time in seconds that a sector requires to regenerate. + * Defaults to 120 seconds. + * @returns The function does not explicitly return anything. + */ + async function handleScan(fleetObject, options = {}) { + const { fleet } = await prepareForTrip(fleetObject); + await handleMovement(fleet); + + const { cargoHoldBuffer, scanDelay, scanSectorAge } = options; + let currentFleetState = await solanaConnection.getAccountInfo(fleet.publicKey); + currentFleetState = sageProgram.coder.accounts.decode('Fleet', currentFleetState.data); + const { cargoStats, miscStats } = currentFleetState.stats; + const { scanRepairKitAmount, scanCooldown } = miscStats; + + const currentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + const currentCargoCount = currentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const repairKitToken = await ResourceTokens.getPodToken({ name: 'toolkit', pod: fleet.account.cargoHold }); + + const currentToolkit = fleetCurrentCargo.value.find(item => item.pubkey.toString() === repairKitToken.toString()); + const currentToolkitCount = currentToolkit.account.data.parsed.info.delegatedAmount.uiAmount || 0; + + if ((cargoStats.cargoCapacity - currentCargoCount) < (cargoHoldBuffer || 100)) { + await handleReturnTrip(fleet); + return; + } + + if (currentToolkitCount < scanRepairKitAmount) { + await handleReturnTrip(fleet); + return; + } + + if (Date.now() > fleet.scanEnd) { + const scanResult = await execScan(fleet); + console.log('Scan Result: ', scanResult); + + const changesSDU = getBalanceChange(scanResult, ResourceTokens.sdu.publicKey.toString()); + const changesTool = getBalanceChange(scanResult, ResourceTokens.toolkit.publicKey.toString()); + let scanCondition = scanResult.meta.logMessages ? scanResult.meta.logMessages.find(item => item.startsWith("Program log: SDU probability:")) : null; + scanCondition = scanCondition ? (Number(scanCondition.split(' ').pop())*100).toFixed(4) : 0; + + console.log(`[${fleet.label}] ${new Date(Date.now()).toISOString()}`); + console.log(`[${fleet.label}] ${scanCondition}`); + + if (changesSDU.postBalance != changesSDU.preBalance) { + console.log(`[${fleet.label}] FOUND: ${changesSDU.postBalance - changesSDU.preBalance}`); + fleet.scanSkipCnt = 0; + fleet.scanSectorStart = 0; + } else { + console.log(`[${fleet.label}] Whomp whomp`); + } + + if (scanCondition < fleet.scanMin) { + if (fleet.scanSectorStart == 0) fleet.scanSectorStart = Date.now(); + if (Date.now() - fleet.scanSectorStart >= (scanSectorAge || 120) * 1000) { + ++fleet.scanSkipCnt; + if (scanMove) { + const nextMoveIdx = fleet.scanBlockIdx > 2 ? 0 : ++fleet.scanBlockIdx; + fleet.scanBlockIdx = nextMoveIdx; + } + } + } + + console.log(`[${fleet.label}] Tools Remaining: ${changesTool.postBalance}`); + + if (fleet.scanSkipCnt < 4) { + fleet.state = `Scanning [${scanCondition}%]`; + fleet.scanEnd = Date.now() + (scanCooldown * 1000); + } else { + fleet.scanEnd = Date.now() + (scanDelay || 600) * 1000; + fleet.state = `Scanning Paused [${new Date(fleet.scanEnd).toLocaleTimeString()}]`; + fleet.scanSkipCnt = 0; + console.log(`[${fleet.label}] Scanning Paused due to low probability [${new Date(fleet.scanEnd).toLocaleTimeString()}]`); + } + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + async function handleMining(fleetObject) { + console.log('fleet BEFORE: ', fleetObject); + const { + duration: miningDuration, + starbase, + starbasePlayer, + planet + } = await prepareToMine(fleetObject); + console.log('fleet AFTER: ', fleetObject); + const { fleet, skip } = await prepareForTrip(fleetObject); + + if (!skip) { + await handleMovement(fleet); + await execStartMining(fleet, { starbase, starbasePlayer, planet }); + } + + const { foodConsumptionRate, ammoConsumptionRate } = fleet.account.stats.cargoStats; + + const currentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.ammoBank, {programId: tokenProgram}); + const currentAmmoCount = currentAmmoBank.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxAmmoDuration = Math.floor(currentAmmoCount / ammoConsumptionRate); + + const currentFood = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + currentFood.value = currentFood.value.filter(item => item.account.data.parsed.info.mint.toString() == ResourceTokens.food.publicKey.toString()); + const currentFoodCount = currentFood.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxFoodDuration = Math.floor(currentFoodCount / foodConsumptionRate); + + const duration = Math.min(miningDuration, maxAmmoDuration, maxFoodDuration); + fleet.state = 'Mine [' + new Date(Date.now() + (duration * 1000)).toLocaleTimeString() + ']'; + + await wait(duration); + await execStopMining(fleet); + await handleReturnTrip(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + async function handleTransport(fleetObject) { + const { fleet } = await prepareForTrip(fleetObject); + await handleMovement(fleet); + await handleResupply(fleet); + await handleReturnTrip(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + async function selectWalletToggle() { + return new Promise(async resolve => { + let errElem = document.querySelector('#walletModalAssistant .assist-modal-error'); + let targetElem = document.querySelector('#walletModalAssistant'); + if (targetElem.style.display === 'none') { + targetElem.style.display = 'block'; + let selectWalletBtn = document.querySelector('#selectWalletBtn'); + selectWalletBtn.onclick = function() { + let selectWalletBtn = document.querySelector('#walletSelection'); + let errBool = false; + errElem.innerHTML = ''; + if (selectWalletBtn.value.length > 0) { + if (selectWalletBtn.value === 'phantom') { + if (typeof solana === 'undefined') { + errElem.innerHTML = 'ERROR: Phantom wallet not found.'; + errBool = true; + } + } else { + if (typeof solflare === 'undefined') { + errElem.innerHTML = 'ERROR: Solflare wallet not found.'; + errBool = true; + } + } + if (!errBool) { + wallet = selectWalletBtn.value; + selectWalletToggle(); + resolve(); + } + } + } + } else { + targetElem.style.display = 'none'; + resolve(); + } + }); + } + + function addScanOptions(fleet) { + console.log(fleet); + const options = document.createElement('tr'); + options.id = `${fleet.publicKey}-scan-options`; + options.classList.add('assist-scan-row'); + options.style.display = fleet.assignment == 'Scan' ? 'table-row' : 'none'; + fleet.assignment == 'Scan' && options.classList.add('show-top-border'); + + const paddingTd = document.createElement('td'); + options.appendChild(paddingTd); + + // create minimum probability textfield + const minimumProbabilityTd = document.createElement('td'); + const minimumProbabilityField = document.createElement('span'); + minimumProbabilityField.innerHTML = 'Minimum Probability:'; + + const minimumProbability = document.createElement('input'); + minimumProbability.setAttribute('type', 'text'); + minimumProbability.placeholder = '10'; + minimumProbability.style.width = '30px'; + minimumProbability.style.marginRight = '10px'; + minimumProbability.value = fleet.scanMin || ''; + + const minimumProbabilityDiv = document.createElement('div'); + minimumProbabilityDiv.appendChild(minimumProbability); + minimumProbabilityDiv.appendChild(minimumProbability); + + minimumProbabilityTd.setAttribute('colspan', '3'); + minimumProbabilityTd.appendChild(minimumProbabilityDiv); + options.appendChild(minimumProbabilityTd); + + // create should move while scanning input + const moveWhileScanningTd = document.createElement('td'); + const moveWhileScanning = document.createElement('span'); + moveWhileScanning.innerHTML = 'Move While Scanning:'; + + const moveWhileScanningCB = document.createElement('input'); + moveWhileScanningCB.setAttribute('type', 'checkbox'); + moveWhileScanningCB.checked = fleet.scanMove || true; + moveWhileScanningCB.style.marginRight = '10px'; + + const moveWhileScanningDiv = document.createElement('div'); + moveWhileScanningDiv.appendChild(moveWhileScanning); + moveWhileScanningDiv.appendChild(moveWhileScanningCB); + + moveWhileScanningTd.setAttribute('colspan', '4'); + moveWhileScanningTd.appendChild(moveWhileScanningDiv); + options.appendChild(moveWhileScanningTd); + + return options; + } + + function addMineOptions(fleet) { + console.log(fleet); + const mineResource = ResourceTokens.getResourceByName(fleet.mineResource); + const options = document.createElement('tr'); + options.id = `${fleet.publicKey.toString()}-mine-options`; + options.classList.add('assist-mine-row'); + options.style.display = fleet.assignment == 'Mine' ? 'table-row' : 'none'; + fleet.assignment == 'Mine' && options.classList.add('show-top-border'); + + const paddingTd = document.createElement('td'); + options.appendChild(paddingTd); + + const td = document.createElement('td'); + td.setAttribute('colspan', '7'); + + const label = document.createElement('span'); + label.innerHTML = 'Resource to mine:'; + td.appendChild(label); + + const mineResources = ['', ...ResourceTokens.names('R9')] + const resources = document.createElement('select'); + mineResources.forEach((resource, key) => { + resources[key] = new Option(resource, resource, resource == '', resource == mineResource.name) + }); + td.appendChild(resources); + options.appendChild(td); + + return options; + } + + function calculateCargoTotals(id, cargoCapacity) { + const fields = document.querySelectorAll(`input[id*='${id}-']`); + const total = [...fields].map(field => parseInt(field.value)).reduce((a,b) => a + (b || 0), 0); + console.log(fields.length, total, cargoCapacity); + fields.forEach(function(field) { + field.style.border = total > cargoCapacity ? '2px solid red' : null + }); + } + + function createTransportOptions(container, transportSupplies, cargoCapacity) { + const resources = ['', ...ResourceTokens.names('all')]; + const supplies = Object.entries(transportSupplies); + const id = container.id; + + //supplies.forEach(supply => { + for (let i=0; i < 4; i++) { + //const [ transportResource, amount ] = supplies && supplies[i]; + console.log('supplies: ', supplies); + let transportResource, amount; + if (supplies && supplies.length > 0 && supplies[i]) { + transportResource = supplies[i][0]; + amount = supplies[i][1]; + } + const div = document.createElement('div'); + + const selector = document.createElement('select'); + resources.forEach((resource, key) => { + selector[key] = new Option(resource, resource, resource == '', resource == transportResource) + }) + div.appendChild(selector); + + const transportAmount = document.createElement('input'); + transportAmount.setAttribute('type', 'text'); + transportAmount.id = `${id}-${i}`; + transportAmount.placeholder = '0'; + transportAmount.style.width = '60px'; + transportAmount.style.marginRight = '10px'; + transportAmount.value = amount ?? 0; + transportAmount.min = 0; + transportAmount.addEventListener("input", function() { + if (this.value < 0) this.value = Math.abs(this.value); + calculateCargoTotals(id, cargoCapacity) + }) + div.appendChild(transportAmount); + + container.appendChild(div); + } + //}) + + return container; + } + + // @todo - do capacity check magic + function addTransportOptions(fleet) { + const fleetPublicKeyString = fleet.publicKey.toString() + const options = document.createElement('tr'); + options.id = `${fleetPublicKeyString}-transport-options`; + options.classList.add('assist-transport-row'); + options.style.display = fleet.assignment == 'Transport' ? 'table-row' : 'none'; + fleet.assignment == 'Mine' && options.classList.add('show-top-border'); + + const td = document.createElement('td'); + td.setAttribute('colspan', '8'); + + let destinationContainer = document.createElement('div'); + destinationContainer.id = `${fleetPublicKeyString}-to-destination`; + destinationContainer.classList.add('transport-to-target'); + destinationContainer.style.display = 'flex' + destinationContainer.style.flexDirection = 'row'; + destinationContainer.style.justifyContent = 'flex-start'; + + const toDestinationLabel = document.createElement('div'); + toDestinationLabel.innerHTML = 'To Dest:'; + toDestinationLabel.style.width = '84px'; + toDestinationLabel.style.minWidth = '84px'; + destinationContainer.appendChild(toDestinationLabel) + + destinationContainer = createTransportOptions(destinationContainer, fleet.destination.supplies, fleet.account.stats.cargoStats.cargoCapacity); + td.appendChild(destinationContainer); + + let originContainer = document.createElement('div'); + originContainer.id = `${fleetPublicKeyString}-to-origin`; + originContainer.classList.add('transport-to-starbase'); + originContainer.style.display = 'flex' + originContainer.style.flexDirection = 'row'; + originContainer.style.justifyContent = 'flex-start'; + + const toOriginLabel = document.createElement('div'); + toOriginLabel.innerHTML = 'To Origin:'; + toOriginLabel.style.width = '84px'; + toOriginLabel.style.minWidth = '84px'; + originContainer.appendChild(toOriginLabel) + + originContainer = createTransportOptions(originContainer, fleet.destination.supplies, fleet.account.stats.cargoStats.cargoCapacity); + td.appendChild(originContainer); + + options.appendChild(td); + return options; + } + + async function addAssistInput(fleet) { + const fleetPublicKeyString = fleet.publicKey.toString(); + + const fleetRow = document.createElement('tr'); + fleetRow.classList.add('assist-fleet-row'); + fleetRow.id = `${fleetPublicKeyString}-row`; + fleetRow.setAttribute('pk', fleetPublicKeyString); + + // create fleet name + const fleetLabelTd = document.createElement('td'); + const fleetLabel = document.createElement('span'); + fleetLabel.innerHTML = fleet.name; + fleetLabelTd.appendChild(fleetLabel); + fleetRow.appendChild(fleetLabelTd); + + // create assignment selector + const fleetAssignmentTd = document.createElement('td'); + const fleetAssignment = document.createElement('select'); + fleetAssignment.id = `${fleetPublicKeyString}-select`; + + const assistAssignments = ['','Scan','Mine','Transport']; + assistAssignments.forEach((assignment, key) => { + fleetAssignment[key] = new Option(assignment, assignment, assignment == '', assignment == fleet.assignment) + }); + fleetAssignmentTd.appendChild(fleetAssignment); + fleetRow.appendChild(fleetAssignmentTd); + + // create destination textfield + const fleetDestinationTd = document.createElement('td'); + const fleetDestinationField = document.createElement('input'); + fleetDestinationField.setAttribute('type', 'text'); + fleetDestinationField.placeholder = 'x, y'; + fleetDestinationField.style.width = '50px'; + fleetDestinationField.value = fleet.destination.coords || ''; + fleetDestinationTd.appendChild(fleetDestinationField); + fleetRow.appendChild(fleetDestinationTd); + + // create origin textfield + const fleetOriginTd = document.createElement('td'); + const fleetOriginField = document.createElement('input'); + fleetOriginField.setAttribute('type', 'text'); + fleetOriginField.placeholder = 'x, y'; + fleetOriginField.style.width = '50px'; + fleetOriginField.value = fleet.origin.coords || ''; + fleetOriginTd.appendChild(fleetOriginField); + fleetRow.appendChild(fleetOriginTd); + + // create moveType selector + const fleetMoveTypeTd = document.createElement('td'); + const fleetMoveType = document.createElement('select'); + const fleetMoveTypeOptions = ['Subwarp','Warp', 'Hybrid']; + fleetMoveTypeOptions.forEach((type, key) => { + fleetMoveType[key] = new Option(type, type == 'Warp', type == fleet.moveType) + }); + fleetMoveTypeTd.appendChild(fleetMoveType); + fleetRow.appendChild(fleetMoveTypeTd); + + // create cargo label + /* + const fleetCargoTd = document.createElement('td'); + const fleetCargo = document.createElement('span'); + fleetCargo.innerHTML = fleet.account.stats.cargoStats.cargoCapacity; + fleetCargoTd.appendChild(fleetCargo); + fleetRow.appendChild(fleetCargoTd); + */ + + // create fuel tank label + const fleetFuelTankTd = document.createElement('td'); + const fleetFuelTank = document.createElement('span'); + fleetFuelTank.innerHTML = fleet.account.stats.cargoStats.fuelCapacity; + fleetFuelTankTd.appendChild(fleetFuelTank); + fleetRow.appendChild(fleetFuelTankTd); + /* + const fleetFuelTankTd = document.createElement('td'); + const fleetFuelTank = document.createElement('input'); + fleetFuelTank.setAttribute('type', 'text'); + fleetFuelTank.placeholder = fleet.account.stats.cargoStats.fuelCapacity; + fleetFuelTank.setAttribute('size', fleetFuelTank.getAttribute('placeholder').length); + fleetFuelTankTd.appendChild(fleetFuelTank); + fleetRow.appendChild(fleetFuelTankTd); + */ + + // create ammo bank label + const fleetAmmoBankTd = document.createElement('td'); + const fleetAmmoBank = document.createElement('span'); + fleetAmmoBank.innerHTML = fleet.account.stats.cargoStats.ammoCapacity; + fleetAmmoBankTd.appendChild(fleetAmmoBank); + fleetRow.appendChild(fleetAmmoBankTd); + /* + const fleetAmmoBankTd = document.createElement('td'); + const fleetAmmoBank = document.createElement('input'); + fleetAmmoBank.setAttribute('type', 'text'); + fleetAmmoBank.placeholder = fleet.account.stats.cargoStats.ammoCapacity; + fleetAmmoBank.setAttribute('size', fleetAmmoBank.getAttribute('placeholder').length); + fleetAmmoBankTd.appendChild(fleetAmmoBank); + fleetRow.appendChild(fleetAmmoBankTd); + */ + + const fleetContainer = document.querySelector('#assistModal .assist-modal-body table'); + fleetContainer.appendChild(fleetRow); + console.log(fleet); + const scanOptionsRow = addScanOptions(fleet); + fleetContainer.appendChild(scanOptionsRow); + const mineOptionsRow = addMineOptions(fleet); + fleetContainer.appendChild(mineOptionsRow); + const transportOptionsRow = addTransportOptions(fleet); + fleetContainer.appendChild(transportOptionsRow) + + const padRowTd = document.createElement('td'); + padRowTd.setAttribute('colspan', '7'); + padRowTd.style.height = '15px'; + + const paddingRow = document.createElement('tr'); + paddingRow.classList.add('assist-pad-row'); + paddingRow.style.display = fleet.assignment ? 'table-row' : 'none'; + + paddingRow.appendChild(padRowTd); + fleetContainer.appendChild(paddingRow); + + fleetAssignment.onchange = function() { + switch(fleetAssignment.value) { + case 'Scan': + scanOptionsRow.style.display = 'table-row'; + mineOptionsRow.style.display = 'none'; + transportOptionsRow.style.display = 'none'; + paddingRow.style.display = 'table-row'; + fleetRow.classList.add('show-top-border'); + break; + case 'Mine': + mineOptionsRow.style.display = 'table-row'; + scanOptionsRow.style.display = 'none'; + transportOptionsRow.style.display = 'none'; + paddingRow.style.display = 'table-row'; + fleetRow.classList.add('show-top-border'); + break; + case 'Transport': + transportOptionsRow.style.display = 'table-row'; + scanOptionsRow.style.display = 'none'; + mineOptionsRow.style.display = 'none'; + paddingRow.style.display = 'table-row'; + fleetRow.classList.add('show-top-border'); + break; + default: + scanOptionsRow.style.display = 'none'; + mineOptionsRow.style.display = 'none'; + transportOptionsRow.style.display = 'none'; + paddingRow.style.display = 'none'; + fleetRow.classList.remove('show-top-border'); + } + } + } + + function updateAssistStatus(fleet) { + let targetRow = document.querySelectorAll('#assistStatus .assist-fleet-row[pk="' + fleet.publicKey.toString() + '"]'); + + if (targetRow.length > 0) { + targetRow[0].children[1].firstChild.innerHTML = fleet.toolCnt; + targetRow[0].children[2].firstChild.innerHTML = fleet.sduCnt; + targetRow[0].children[3].firstChild.innerHTML = fleet.state; + } else { + let fleetRow = document.createElement('tr'); + fleetRow.classList.add('assist-fleet-row'); + fleetRow.setAttribute('pk', fleet.publicKey.toString()); + let fleetLabel = document.createElement('span'); + fleetLabel.innerHTML = fleet.name; + let fleetLabelTd = document.createElement('td'); + fleetLabelTd.appendChild(fleetLabel); + let fleetTool = document.createElement('span'); + fleetTool.innerHTML = fleet.toolCnt; + let fleetToolTd = document.createElement('td'); + fleetToolTd.appendChild(fleetTool); + let fleetSdu = document.createElement('span'); + fleetSdu.innerHTML = fleet.sduCnt; + let fleetSduTd = document.createElement('td'); + fleetSduTd.appendChild(fleetSdu); + let fleetStatus = document.createElement('span'); + fleetStatus.innerHTML = fleet.state; + let fleetStatusTd = document.createElement('td'); + fleetStatusTd.appendChild(fleetStatus); + fleetRow.appendChild(fleetLabelTd); + fleetRow.appendChild(fleetToolTd); + fleetRow.appendChild(fleetSduTd); + fleetRow.appendChild(fleetStatusTd); + let targetElem = document.querySelector('#assistStatus .assist-modal-body table'); + targetElem.appendChild(fleetRow); + } + } + + async function saveConfig() { + const fleetRows = document.querySelectorAll('#assistModal .assist-fleet-row'); + //const scanRows = document.querySelectorAll('#assistModal .assist-scan-row'); + //const mineRows = document.querySelectorAll('#assistModal .assist-mine-row'); + const transportRows = document.querySelectorAll('#assistModal .assist-transport-row > td'); + let error = false; + + for (let [i, row] of fleetRows.entries()) { + const fleetPK = row.getAttribute('pk'); + const fleetIndex = userFleets.findIndex(item => {return item.publicKey.toString() == fleetPK}); + const fleet = userFleets[fleetIndex]; + const name = row.children[0].firstChild.innerText; + const assignment = row.children[1].firstChild.value; + const moveType = row.children[4].firstChild.value; + + let scanOptions = document.getElementById(`${fleetPK}-scan-options`); + const scanMin = parseInt(scanOptions.children[1].children[0].children[0].value) || 10; + const scanMove = scanOptions.children[2].children[0].children[1].checked; + + let mineOptions = document.getElementById(`${fleetPK}-mine-options`); + let mineResource = mineOptions.children[1].children[1].value; + mineResource = ResourceTokens.getResourceByName(mineResource).name; + + const destination = { + coords: row.children[2].firstChild.value, + supplies: {} + } + + const origin = { + coords: row.children[3].firstChild.value, + supplies: {} + } + + const moveDist = calculateMovementDistance(origin.coords, destination.coords); + const warpCost = calculateWarpFuelBurn(fleet, moveDist); + if (assignment !== '' && (warpCost > fleet.account.stats.cargoStats.fuelCapacity)) { + let subwarpCost = calculateSubwarpFuelBurn(fleet, moveDist); + if (subwarpCost * 2 > fleet.account.stats.cargoStats.fuelCapacity) { + console.log('ERROR: Fleet will not have enough fuel to return to starbase'); + row.children[4].firstChild.style.border = '2px solid red'; + row.children[5].firstChild.style.border = '2px solid red'; + errElem[0].innerHTML = 'ERROR: Distance exceeds fuel capacity'; + errBool = true; + rowErrBool = true; + } else { + moveType = 'subwarp'; + } + } + + const transportToDestination = document.querySelectorAll(`#${fleetPK}-to-destination > div`); + const transportToOrigin = document.querySelectorAll(`#${fleetPK}-to-origin > div`); + const transportDivs = [ ...transportToDestination, ...transportToOrigin]; + + console.log('DEBUG'); + console.log('transportDivs: ', transportDivs); + for (let div of transportDivs) { + if (!div.children[0]) continue; + console.log('div.children[0]: ', div.children[0]); + const resource = ResourceTokens.getResourceByName(div.children[0].value); + console.log('resource: ', resource); + const amount = parseInt(div.children[1].value) || 0; + console.log('amount: ', amount); + const parent = div.parentElement.classList; + console.log('parent: ', parent); + + if (resource.name == '') continue; + switch(resource.name) { + case 'Ammo': + if (amount > fleet.account.stats.cargoStats.ammoCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } + case 'Fuel': + if (amount > fleet.account.stats.cargoStats.fuelCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } + default: + if (amount > fleet.account.stats.cargoStats.cargoCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } + } + + div.children[1].style.border = null; + if (parent.contains('transport-to-target')) destination.supplies[resource.name] = amount; + if (parent.contains('transport-to-starbase')) origin.supplies[resource.name] = amount; + } + + const fleetData = { + name, + assignment, + origin, + destination, + moveType, + mineResource, + scanMin, + scanMove, + } + + if (error) return; + console.log('SAVING'); + userFleets[fleetIndex] = { ...fleet, ...fleetData }; + await GM.setValue(fleet.publicKey.toString(), JSON.stringify(fleetData)); + //assistModalToggle(); + } + } + + // let fleetSavedData = await GM.getValue(fleetPK, '{}'); + // let fleetParsedData = JSON.parse(fleetSavedData); + // let fleetMoveTarget = fleetParsedData.destination || ''; + // destX = Number(destX); + // destY = Number(destY); + // let scanShiftX = destX > 0 ? -1 : 1; + // let scanShiftY = destY > 0 ? -1 : 1; + // let scanBlock = []; + // if (destX !== '' && destY !== '') { + // scanBlock.push([destX, destY]); + // scanBlock.push([destX+scanShiftX, destY]); + // scanBlock.push([destX+scanShiftX, destY+scanShiftY]); + // scanBlock.push([destX, destY+scanShiftY]); + // } + + async function exportConfig() { + const importText = document.querySelector('#importText'); + const exportData = {} + + for (let fleet of userFleets) { + const fleetPublicKeyString = fleet.publicKey.toString(); + const savedFleetData = await GM.getValue(fleetPublicKeyString, false); + if (savedFleetData) exportData[fleetPublicKeyString] = savedFleetData; + } + importText.value = JSON.stringify(exportData); + assistImportExportToggle(); + } + + async function importConfig() { + const importText = document.querySelector('#importText'); + const jsonConfig = JSON.parse(importText.value); + + for (let key in jsonConfig) { + let fleetObj = jsonConfig[key]; + let fleetJson = JSON.stringify(fleetObj); + await GM.setValue(key, fleetJson); + } + assistImportExportToggle(); + } + + async function assistImportExportToggle() { + const importModal = document.querySelector('#importModal'); + if (importModal.style.display === 'none') { + importModal.style.display = 'block'; + assistModalToggle(); + } else { + importModal.style.display = 'none'; + } + } + + function assistModalToggle() { + let targetElem = document.querySelector('#assistModal'); + if (targetElem.style.display === 'none') { + document.querySelectorAll('#assistModal .assist-fleet-row').forEach(e => e.remove()); + document.querySelectorAll('#assistModal .assist-scan-row').forEach(e => e.remove()); + document.querySelectorAll('#assistModal .assist-mine-row').forEach(e => e.remove()); + document.querySelectorAll('#assistModal .assist-pad-row').forEach(e => e.remove()); + document.querySelectorAll('#assistModal .assist-transport-row').forEach(e => e.remove()); + for (let fleet of userFleets) { + addAssistInput(fleet); + } + targetElem.style.display = 'block'; + } else { + targetElem.style.display = 'none'; + } + } + + function assistStatusToggle() { + let targetElem = document.querySelector('#assistStatus'); + if (targetElem.style.display === 'none') { + targetElem.style.display = 'block'; + } else { + targetElem.style.display = 'none'; + } + } + + function assistSurveillanceToggle() { + let targetElem = document.querySelector('#assistSurveillance'); + if (targetElem.style.display === 'none') { + targetElem.style.display = 'block'; + } else { + targetElem.style.display = 'none'; + } + } + + // TODO - need to test to see if it works with the new playerProfile object + async function assistProfileToggle(profiles) { + return new Promise(async resolve => { + let targetElem = document.querySelector('#profileModal'); + if (targetElem.style.display === 'none' && profiles) { + targetElem.style.display = 'block'; + let contentElem = document.querySelector('#profileDiv'); + let transportOptStr = ''; + profiles.forEach( function(profile) {transportOptStr += '';}); + let profileSelect = document.createElement('select'); + profileSelect.size = profiles.length + 1; + profileSelect.style.padding = '2px 10px'; + profileSelect.innerHTML = transportOptStr; + contentElem.append(profileSelect); + profileSelect.onchange = function() { + console.log(profileSelect.value); + let selected = profiles.find(o => o.profile === profileSelect.value); + assistProfileToggle(null); + resolve(selected); + } + } else { + targetElem.style.display = 'none'; + resolve(null); + } + }); + } + + async function assistAddAcctToggle() { + let targetElem = document.querySelector('#addAcctModal'); + if (targetElem.style.display === 'none') { + targetElem.style.display = 'block'; + } else { + targetElem.style.display = 'none'; + } + } + + async function startAssistant() { + if (enableAssistant) { + const fleetQueue = new TaskQueue(userFleets.length); + for (let fleet of userFleets) { + switch(fleet.assignment) { + case 'Mine': + console.log('startAssistant Fleet: ', fleet); + fleetQueue.add(handleMining(fleet)); + break; + case 'Transport': + fleetQueue.add(handleTransport(fleet)); + break; + case 'Scan': + fleetQueue.add(handleScan(fleet)); + break; + default: + console.log(`${ fleet.assignment } is unsupported`); + } + } + } + } + + async function toggleAssistant() { + let autoSpanRef = document.querySelector('#autoScanBtn > span'); + if (enableAssistant === true) { + enableAssistant = false; + autoSpanRef.innerHTML = 'Start'; + } else { + enableAssistant = true; + startAssistant(); + autoSpanRef.innerHTML = 'Stop'; + for (let i=0, n=userFleets.length; i < n; i++) { + let {fleetState, extra} = await getCurrentFleet(userFleets[i]); + let fleetCoords = fleetState == 'Idle' && extra ? extra : []; + //userFleets[i].origin = fleetCoords; + userFleets[i].state = fleetState; + } + } + } + + function makeDraggable(elem) { + const elemHeader = elem.querySelector('.assist-modal-header'); + let originalX, originalY, elementX, elementY; + function dragElem(e) + { + elementY = elementY + e.clientY - originalY; + elementX = elementX + e.clientX - originalX; + originalY = e.clientY; + originalX = e.clientX; + elem.style.top = elementY + "px"; + elem.style.left = elementX + "px"; + elem.style.right = 'auto'; + elem.style.bottom = 'auto'; + } + elemHeader.addEventListener('mousedown', function(e) { + e.preventDefault(); + originalX = e.clientX; + originalY = e.clientY; + elementX = elem.offsetLeft; + elementY = elem.offsetTop; + + window.addEventListener('mousemove', dragElem, false); + + window.addEventListener('mouseup', () => { + window.removeEventListener('mousemove', dragElem, false); + }, false); + + }, false); + } + + let observer = new MutationObserver(waitForLabs); + function waitForLabs(_mutations, observer){ + let elemTrigger = observer ? '#root > div:first-of-type > div:first-of-type > div > header > h1' : 'body'; + if(document.querySelectorAll(elemTrigger).length > 0 && !document.getElementById("assistContainer")) { + document.getElementById("assistContainerIso") && document.getElementById("assistContainerIso").remove(); + observer && observer.disconnect(); + let assistCSS = document.createElement('style'); + assistCSS.innerHTML = '.assist-modal {display: none; position: fixed; z-index: 2; padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);} .assist-modal-content {position: relative; display: flex; flex-direction: column; background-color: rgb(41, 41, 48); margin: auto; padding: 0; border: 1px solid #888; width: 785px; min-width: 450px; max-width: 75%; height: auto; min-height: 50px; max-height: 85%; overflow-y: auto; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); -webkit-animation-name: animatetop; -webkit-animation-duration: 0.4s; animation-name: animatetop; animation-duration: 0.4s;} #assist-modal-error {color: red; margin-left: 5px; margin-right: 5px; font-size: 16px;} .assist-modal-header-right {color: rgb(255, 190, 77); margin-left: auto !important; font-size: 20px;} .assist-btn {background-color: rgb(41, 41, 48); color: rgb(255, 190, 77); margin-left: 2px; margin-right: 2px;} .assist-btn:hover {background-color: rgba(255, 190, 77, 0.2);} .assist-modal-close:hover, .assist-modal-close:focus {font-weight: bold; text-decoration: none; cursor: pointer;} .assist-modal-btn {color: rgb(255, 190, 77); padding: 5px 5px; margin-right: 5px; text-decoration: none; background-color: rgb(41, 41, 48); border: none; cursor: pointer;} .assist-modal-save:hover { background-color: rgba(255, 190, 77, 0.2); } .assist-modal-header {display: flex; align-items: center; padding: 2px 16px; background-color: rgba(255, 190, 77, 0.2); border-bottom: 2px solid rgb(255, 190, 77); color: rgb(255, 190, 77);} .assist-modal-body {padding: 2px 16px; font-size: 12px;} .assist-modal-body > table {width: 100%;} .assist-modal-body th, .assist-modal-body td {padding-right: 5px, padding-left: 5px;} #assistStatus {background-color: rgba(0,0,0,0.4); opacity: 0.75; backdrop-filter: blur(10px); position: absolute; top: 80px; right: 20px; z-index: 1;} #assistSurveillance {background-color: rgba(0,0,0,0.75); backdrop-filter: blur(10px); position: absolute; margin: auto; left: 0; right: 0; top: 100px; width: 650px; min-width: 450px; max-width: 75%; z-index: 1;} .dropdown { position: absolute; display: none; margin-top: 25px; margin-left: 152px; background-color: rgb(41, 41, 48); min-width: 120px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); z-index: 2; } .dropdown.show { display: block; } .assist-btn-alt { color: rgb(255, 190, 77); padding: 12px 16px; text-decoration: none; display: block; background-color: rgb(41, 41, 48); border: none; cursor: pointer; } .assist-btn-alt:hover { background-color: rgba(255, 190, 77, 0.2); } #checkresults { padding: 5px; margin-top: 20px; border: 1px solid grey; border-radius: 8px;} .dropdown button {width: 100%; text-align: left;} #assistModal table {border-collapse: collapse;} .assist-scan-row, .assist-mine-row, .assist-transport-row {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-bottom: 1px solid white} .show-top-border {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-top: 1px solid white;}'; + + let assistModal = document.createElement('div'); + assistModal.classList.add('assist-modal'); + assistModal.id = 'assistModal'; + assistModal.style.display = 'none'; + let assistModalContent = document.createElement('div'); + assistModalContent.classList.add('assist-modal-content'); + let iconStr = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA4CAYAAABNGP5yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfnCwMTJgKRQOBEAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAZdklEQVRoQ91aB3RUx9Vebe/alXa1fVVWXVr1hgoqICEhikQngOjFBmyQKKJ3CBgDQoAwotqAQPQuwKaDsbExGGzAGLCBALYJcQCbkJB8/523u4Cd5JycnPxAMud8mnnzptz73Tt3Zp6W96JSQkKCyemM6hMfF7ekUaOMI5kZjb/OSM++1ahR5u3ExNRzTmfc9ujomCmNGzduXlhYKHF3++9O7du3FyTHxxdGR0duz8zMeFJcXIy2bdqiTWkblJaWIDu7MYqKmhGK0KxZAbKyshAfn0CIv5eYmDg3NzfX3z3Uf18iS+bGOWPPNUpLA1O8VatWKC0p5VBSUsI9k5Jo2rQpKd8M+fn5yMvLIxIaI436xMbGIiIi4rHT6XybnmXuYV/9REqJmPWindF/a0IKFRUVolXLlihp3ZoUL0Xr1iVo2bIVZ/UuXbogNTWVFM/lkJ2TjcysDDRqlIbk5GTmCQgPD0dgYOAZIiTAPcWrm9q1ayeLiXHuZdZr0iQXRc3y0YIR0KIYrRkJrVpg8KD+mDdvJmbMmIw1a9ejU6cOWL58EUaNqkBBfh7SUlOQkpzKEUBxA3FxcYgIC4fFbL5D5Vj3VK9eys7OFkaGh21NSohHXm4OmhEBxYyAwmYoJcUXVL+Fq1c/x63bF7FrVx0GERE1S1fizfJyzJo1CSdP7se9u19j3apqFORkIIFiQQJ5QHxsHOJiYhAVEQmLyUQBM9HunvLVSkFBAWNjnU7kZmehaW42CvIITXLQp3tnnDtzGJ+fOUou3w4KhRxeXl7o2K03ftOjD9as3wA+34tDSEgQFlVPxfUvtuL1bgWIjYxGrDPGDYoJoeEwGU0nGdnuaV+NFBYWEBbiCPpTVnoGsjMzkZuVSXkGendvi6tfHcGk8SMgl0s5xRkEQhHWb9+P5LR07P3gECKjokDDgOd+nxgfgQ/3zEBl30JEhoXBGRmJmCgnh8CAIFit1rGumV+RZDabdiYnJSEjrREyKYBlNEpGmxaZ+PL0JpR1bUPW5XOKaTRaKJVK5OUXo25rA5GiwKA3hmDh4iXcewp2lLvaWo067FwxEH3bJiMsJIyICEd0RDSiwqNgMph/TkpKsrmnf7nJZjNEBQc7/ppCQSuVISkRGSnx2LlqMAYNKOGU4fG8IJZIUDV/AXR6IzZs34dARwgEAgFn9amz3kajjEyMGj0WTnJ11sfLi4cIhxEH3u2O9LhghDpCER4SzsER4IC/zb/aLcLLTQa97xK29hMpYCXGxyIxNgZv9MzF5tqBEIkFbmW80HfAYKxcuwmzq2rQjda+hAgZP2UKfHU6yBVKrFq3ESPHTsDOvfugUquf9ivvkYIZg7MQZA9EcFAIR0RYcBgFRMuPFAukbjFeTqK1KLNbLT+yNRobTQErOgrxzghsWdAFbfLjOAV4FNzMVisOnfgEn1+8gpWr14FPlm/bqSN5wg7MrV7AeUdSSiOcvXAZn5w9hz4DXn9KQKDZG9tm5yEi0IJAIiGIrB8cGAyrycrIaO0W5eUki8VQ7AgIoHUZQdtUBCLp0NIi24mDK3pAp34W9AYPqcC5C19hdd16WIgMk9lM3lBHHjAV2xsaMHb8eGrHR+eu3bD/gwPYe+AQpDK2W/AgJAIXvxmDNlmBsJltsFsDOCICbAEE+xq3KC8n2czmWor+CKdIzbao0GAHKrolYvPs1hCR4Gx9M2tXL6xBQGDQU0JGjBmDhbXLIBSLIZPLUdyiBUJCQ7mdgMWF0nYdEJ+UwrXlE0a2C8SYDkHQ+/rAYjTDbrGzGECwX3dJ8pKSv9VyxRFELulwICstGZX9S1E3KRfLx+dzgjMFTGYTho+sdG1zhHg6/7+3cSMWEQEpqWncDuF554GFrNvuN93chPHRL9+EFYP8MXVoUxQ1SYHRz0DeYIHNYn0SHBz8cm6OYWFhKrvF+igwgNzR3x8GvQ/aFTfG6mmFWFyZ/ZQAutCAjshPlRMKhUhv3JgIqMWJj09h4+YtaEM3RDF5g6eNQuWNsv7P4kDvpkZsrIzA+KFtYfDVwM/XlzzBBCuREBMaGkh9XnwixfyJgCdMeX+7HUa9HxTkzqWNjFg5JoOWgGs/12q1dALs/FQ5D9jSyKHL0vZdu/HD7+/i0qVL3OWHvTOZzMjMzoMXLSM2RkVLEyZ0tEEqFkKj9ubmMhMBFmpHhkiiPi8+hYaGxpAF/urvIcBggFqlQohBgdWVCbD4uIIg84TBfTtAqZBxypHA3GGIlRkWLlqE+w8eYN26dZx3sLri3AQoZa7+UqEXFvY0oiSRDlE0hi8RavTz45Q3G4wIcziaUZ8XnyJCQrKYEIwA5gVWiuw+Gg0UIjHKi23okmPklPdWyjCxXzoGdMzhzvs+Pj6oIfevHD0GzYuLcevObex7fz+dCuWc8vFhBgxpbUNKuJkjIDVIippuGoQTsd5EsI76mygGsLlNRHpIUFCpS6IXnEIdjkKTwUQEUES2+1NAslCU1kAuFSPcT4qpnazQq8XITQ7EtH7xmDM4HX065XFK2shjDhw+gm9v3sTJjz/iDkOs3mHTYVQHO4aVmtC70AGVRIgp7VXokqKBViaDD3mOzlfHKc4IYMEwKCioMyfQi060BPKIgL/Z7TZuCVhIqCCzD5KMUqhEAqQHSDAgX4832oRjUlkEqsqbok+XEk5Rhmg6PR45dgwBdI7w1AXSODOGdsTIUhteKzDitXwzOibKoZOJEGdRI8ziAx8i2Wg0cgQYaCnQ/aEr9X3xKTSIEWBkNzPYbDZySyMdaRXIDNIiTi+GWCBERpgK5W0DMHtYKebMnIHJ02chJ7cJZGRNGuIXkZ8tpUGDBmHBvCrMquyH3vkByApXQEGBL0gjRockM2QSMXx9fH9BgD3Q/tIIKDKS0owAO/MAi5XWtxZysQRRdl8YVDL0amLDvNE9kENXZLbfFxQ2R139JuzYuYc7G7Ro0RJlZd1RSzHh8OHDqKyshJbiiN1ixpTyXujdPA4GOlEm+GuhVUigJoINev3THYARYLFYXg4BdCNrxdag1UoHEvIARgSzjFqtgIRIyItSY2KpGvnRVCeXcAGN3QolEhkyshpjwMDBqFu/ARUVw1Ba2gYGWkKsDbdzULDMjLJiNMWC4ngdREIRVHR11mmfBUAGPyLjpREQHBjYxagnAij4eQgw06lPr9chyKDCuGIFJreQYWyxFuPaBsHf7PtUQRf4mDBhMncrfL5eLpWieYoFA/N9MaxQi+GFGjj0cm6H8ez/7ADEEaAjbzCbu7tFerEpxOEoM+j8/saUZgRwJNAyMPn5IjtMiWS7lCK3EEqREIVOb8x8IwOFTVIhED67Ik8kAsS0rmk47jk2JhzlZWnomEmurZFCTn1DjHJkhWmhJwKY8uwI7CFARydCIqCXS6IXnGj/7eTnq39iNtFWyHYCIoDBavCD3lsNrUoJNQU7KV8ACSln10owsndjrFw6G4XNirhDz1i6/zMPoB0FU6eMwaTRXZEeaYA3kaSkmKGiIKkm19co1TDofLmt1m4lsoloRoAvnQnI83q6Rfr30p/uX239+OGNsscPb/d8RHjy6Lsejz3gnm/3fPzwm94MTx5e7/X4j9d6PfrD5T7b6muXjRwy4K+jhg3EuMo3Mb5yCCaNKiclhmLC6HIO46iucXIsNOTuDCoio1G0A+8SCdu31WPh4sWYMGky1m9Yhya5qdCKRPAlxf2oncVbhQH9emN4xVBUDi/HmMphGDdmOCaOHcFhwphhVDcUWzesWvHo/u/6PH54s5dHB1Z2ye3Go+8Id9m73o8e3enz6OGdvo8f3ej74N75Lrw//3jkx7Mf1uLo3kU4vv8dDsf2EfYy1OBowyIc3rMIh3ZX49CuKhzcOY/LD1F+cOccHCAc2k3lXVS/ey4ON8wjVNF41Th58B1sem8mrCoF9BTY9ESCj4BOh6RkTnIc6jbUo0ePHvCRyqH1EsKP3vtRO52Aj3FDymjeWpJnBU5+sJLDiX0rSKblONJQS3MtxqE9NZQvIhkXk8w1OL5vEb1fSHMvpDYLSM5qkm8Bhw92VhMWYP/2+di3dS7OfrgMP/3w0e95f/79lvstGoeQhQTQEPNaEk5LuYZyF1z1Lni561xtfChnFmNgZRcE8KWxdFTWUdmPcqaUgXIOpBxT1F9Kt8LYGOjo6KzjFHe/59qz8b2gplxDxHCyURs2rw/lTEY2l5bKXM7aERixTEZvrq8LrI61Y23YmCxndT1L0vDohwM3eX++W3+vONPBCa1nwpGFOCEJzBJ64fOgNgQdlT3vuNwNVmaKs74eZYxUNtLNj8FTx2ChA46JwNrrn6vnwPoTPGPqqa8fzcXesbF/Md9z8CX4kJKcIViZdGHP3Bz07ILLyF2Kk/Hwzm4i4Pu6u8WZwZwVWQNuciq7BCC35eARxA0a1GNVl4Vd8Dyz3EjW50Dtnyrvzj1KGCnKP2v/HH5FFteG2rP+T2Xg4JKPKejLPM8NzgsJv5D5OTBiOhcm4sGt7dd5j+8svVtCHuAn5MFIV0+TyAuBagmmDczBuxOb4b2JTbF2WgFWT8zG2slNUDetGOtmtEDPokhqz8eKSZnYWV2E3Us6w18uRk6EHbuq22LTrFJsm9MBr5XEu0lwYerrBdhd0w17l/VG79JsTtniRDP2vZOP3Yta482OqVTn5WpPJDN4jMLaBiolWDMlAHWT9HhvrBbRdPdgBtERcqJN2FPdFHuqaKw5OYQMNFTnYcfsbGx/Ow/zh2eRfuRNIj66FsXjj9c33+D96cZvv2uT4Q+zxAsWqResMi/kRvniq3V6nFsqxZnFIny7LQhfr9Hjymojrq214Hq9AbP60sGHyDryjgNHZslwelkALPRsJhKrB5rx2VIdTi3yxskaK6J9pVx900gTTlRb8eF8MXbPVcFG5wSmVMdGPvhqvQFHZ8tQ2ZH2eCLAQuQymKls4gzD54zTv3UA9owRYdsIAXaPFmB0VwNnCBOhbZIvLi4T4GqdBJffk+Grd+W4Uq/G+eUSnF8px1XSI8RbBJuUjx50zP7xWt0N3sPPut9pn6qHXe5FFiTrK7zQItmEazssOL/WG5c2mVA9KAFzB8SiZng8lk1MR82INLRJs8NGpO2db8TxOVJ8vNhMgY0FNy+EewtweL4Jn9RoCSpUvWZFkEyADROj8P4kAU5UydA8XklLgBQktE1W48u1Ghx7W4qR7QywUZ1dLIA/gStLXPAnHKgNw95xfOyfLMXhKQLsn0uXMZrXLhWgQ4ofztQIcHqtEtO6i7GT2pyYJyVjqHDqHRm+aQhDrEECB+nYq3kk7l2cf4N3ZXPK7Q6JaoSovTiEq/nIjdDg4gYb9k9kLEuwZYgUm4dIsHucGIdmSXFohgiTOhgRrOBjx0wNTi0U4+wKK0JpYAeR6CAvyguW4NQSEmiJD75YpcPSYQk4OFOJw78VYGpvLaxiUl5Mlia0SVLgC7LYp4skGNPRgEAiMZDGCKKxGLgxCW3T1bi2y0okCnFzZyFOzxfhwnIliuM0NDcfHZN12PCmGPUk79rXJVjdV4K6flJsLRcTaQJ8u9eJWL2QDMSni5oF3x7reIN3cLbxVod4GbmpAE5CjK8QKVY51k8PxroRGtS9oaKB5FhRJsfSblIs6SrFnDYilBfqEKYmgt7S4NxKKS7Q0ggj8kKVLoQoieWmCpxZ5kskKGk5aHFgBllluhKhGrIsKcmWnJ28qH2qEhfXynD+XSnGdzGSsjzOSiGEUKUAYTReGI1XP9uCz2q9cewtJVaOTsM3G/xxboEXFr7pB6eGxkn0xvz2IrzTVYZlJO/K7nIs66nAzN+oMaGjL8pyfRDLdPQRolumGkdqw27zNoxQ3eyUKEeyUYxEsxRlBdHoVRiF/i2j8VrrCAztEI214xOxe6Q3tlcosHWoiiaQYkiBllN47zwfXN6gxtWtUYj3o6urUYEksxJJFhWifCVYMESPs7UaHH1bwXlPuxQZAsiVGWzuvHO6El9vVOLaZl9M6+WAUydDvEGBZJMKqTROOBGd6S/CDwfN+GiOHDummpHlL8X1nak4v5iPMyt0SDEK0DnFBzVdxFjSTYJawvIyKdb0Jg+okOHwTBU+q49DoknMfa9on6LA+lGGW7y64crbyQ45BHRel9Aef2FrEU7XGPExKfbhbDWOkdsenChDwwhyI1oO+8fJ8V4/Cfo11YJPl5id8yjgLVPg0toAXN6Ujqvbc3FlWzaubc/CrPIUut3xcbjagB1jxNgySgxvhZDrx+DlzgsztTi3SkmepMXljXG4siWHxskjZOPGnkIYdUqM6aPFN/VanJyrQO9WVvC9eJg9zB9fLBfj/DIxOhTqkR2rwye0LL5YK8Xet4SYSWTUl7MlK8HBGUJc2h4OjUrM6ZoVIUf9KP0N3taJ6ttpEUruNiag7eb08kTsHcHHnmF87GYYzscuyndVCLCzQoitQ0Soe02MQc003C1u4zR/bB8pwvZRQjRMEOHQdDGOzhTixFt8TO9j49qsG2fEyv58rH5DToS4vvw+j6LGJrxPAWtrJc0xVoT3p4jJYiIcnyXAZwu0cFi88cV6C869o8KZpYHQeUu5fjFhany9yYizS4TYPNeC3DiKN0sFuLlHgbNr5Ng4leLKSjJOvRIX6tS4siMYGtri2cfaokQVGqYYLvI+ejf6w4q+GRDQPstwcWMuPq0y4VSVgeCHTxf4Eat6nHzbF8dn+uDQFAp6JNTk3q4PmbuqwnG8So+jc3UU3fX4eKEfTi82kCBGzB1k4drs/K2D1r8vPqw2wVv57DOYBy3zrLRb+OEYjXF0nh4n2JyLmGJGXH7XjOE9nbi1w4nL9fHYXFXAeQ7rJ5WIaFdogkvrQ3CtIRoV1O7GFieub44jb4nBtfoI3NiZQPElElc2JuDMxqbc/xaYsQd3DsHROfodvFPrS1LufVX7YOq4oRCSB/j5qWGzajiYzd6wEPtWgp2e7TYNXXkJVPZWu6wQEapHUpwFiXFmJMU+Q6LTjECrD9fG4a9FZLAOoYE+7h9L/JIAb5WU+pvceG6cGAuSnBaEBFmQHBdOiIDZqH9KAFMk0GZGsjMCKbGRCLAZEWAlWEw0twFBDFTHyjaTHhKxiNyfj9f7dcTNa2sfHFrVKp7G4fE+WJiU/v2F+fdmTh8NMcUBNvCvwU3ohmf9egTx4Klgz9Vxz/9kvH8FjCzPXB6w9c/V/2qs58f/R2AeXv5mGa6cXfJwQ21xDvV5lnZXpUTe+XL6tVVLZ0ImldBEvxTy2UAuIZ5/97QN9eHeUTvec1bu164xgm06NE2xIs1pRFEjK1rnRiE/1YKmqcEoSAtFekI4UsnCpcW56NKuCC0Ksp6N61GWG98jxzN42j2Fuz37Bun53RFTftSwMlz6dPG9VbNLMqnd36dNcxJMvzs78tymuirEOiMRFRGGmKhIKkchLsaJhFhCnBPxsdH0THXUJjY6gtqEwxkVxiGWyux9dHgoJ2xcVAgq+5egoiwPi0cXEnKwaWY+5lVkYvPMAlR0jcfwrsmYOaQFlkztj9JmGZg3rQL9y0o5wc1GHSLDAhEdFUzjh8AZHUoguaLZnOGIjgzjEMVA8kbRvFHhIdz8zggXYujd5PEDce3sgpvb5rWMcav7j9OWubGaW5+P3vHg1vpHP33X8ONP3zc8vP/9np9+vLP7wR9u77r/w42tj298Vf+3K+frcemz9bjw6Xp8+ck6XDhVh4un1uDCx6tx/vgqzJ8+nGNdTGd0hVQElYy2QLkESsqVMpZLuGetSg6dRkGRXQ6NQgQZnQvEdPxln8zYbwV6dS3B8b3L8OmR93DuBM310QZ88XE9LnyyAZdOb8TVc5uf3Ly05S/fX9vx8x9uNjz446199+/fef/+/e/3P3h494P7D+8eePjwh/2P7l5b8n7D8tb/+g+rwBzJlVjuAW8Cj8cvLAyWpKf7qtQSSaFYIPxZLpRAxhdCSgFGQpB6CVxlgRDr5zbBpCHpsNGFSEFBVk63Ng9kHCiac/CClC4+ajoiN08zY2L3EO4/TDLyAjkDkcnA+kgZsTS2SCCYQyKJCRSWnsr7d4mtBXfxP58EPEGBSCh8IBeJSFASkISV0RpkH0JZxC3MMuNsQzYuH2mLXgUOpJn4yDTzkWXhI8PihUZmL6TRczqhZaQS+xbk07aWiTdKrZyyShpHRWBfd1TsmbxCSnMJBF4raHqm+CuR4mkL/VpKO4iYhBQy5Z8LWAaNCHNG2nH9aAmm9XeidbQAraL5aBXFpzIfpbFCVLQy0C2uFfbMjkMKHXAEXq79WsiR6fEQGl8g/AttpeNpzv8/q/6bSUVHzGpau0/INbnTFslISrh+KCEi92/XRI3TW9Iwf3gssgIlyHIIkR0ixMhOdlK+AJN7BcGHDkqePh6wE6qQyCXFj9P4L+cHEf9qEvF4sUI+f59YSG5Ka/V5RZhHBJjpkjLJjiWjwpFglWFMWRD2VEWiSTzdLaiNi7RnYAcnwkUKiG1o+FfO6v80kcCFhM9ZJGeKUJWbBAqOZM3+bfxwalUc5g/xh5UC5K+t7lb8G0If6ku8/ncmcgJ+Gf355tfewKxt08u4n9Pxn7O6W/EfCOXU/+X+EvQ/mKS0LMrdiv2CCHr3VHHylodUnkx1aq7X/2DSkpKzCD97iHAr/hcqL6P3Flez//1kIaVrCN+S4ivpOcJV/aITj/d/AtCBMSY54ZcAAAAASUVORK5CYII='; + assistModalContent.innerHTML = '
SLY Lab Assistant
x
FleetAssignmentTargetStarbaseFuelAmmoDrive
'; + assistModal.append(assistModalContent); + + let importModal = document.createElement('div'); + importModal.classList.add('assist-modal'); + importModal.id = 'importModal'; + importModal.style.display = 'none'; + importModal.style.zIndex = 3; + let importModalContent = document.createElement('div'); + importModalContent.classList.add('assist-modal-content'); + importModalContent.innerHTML = '
Config Import/Export
x
Copy the text below to save your raw Lab Assistant configuration. To restore your previous configuration, enter configuration text in the text box below then click the Import Config button.
'; + importModal.append(importModalContent); + + let walletModal = document.createElement('div'); + walletModal.classList.add('assist-modal'); + walletModal.id = 'walletModalAssistant'; + walletModal.style.display = 'none'; + walletModal.style.zIndex = 3; + let walletModalContent = document.createElement('div'); + walletModalContent.classList.add('assist-modal-content'); + walletModalContent.style.width = '300px'; + walletModalContent.style.minWidth = '300px'; + walletModalContent.innerHTML = '
Connect your "primary" wallet.
x
'; + walletModal.append(walletModalContent); + + let profileModal = document.createElement('div'); + profileModal.classList.add('assist-modal'); + profileModal.id = 'profileModal'; + profileModal.style.display = 'none'; + profileModal.style.zIndex = 3; + let profileModalContent = document.createElement('div'); + profileModalContent.classList.add('assist-modal-content'); + profileModalContent.innerHTML = '
Profile Selection
x
Select a profile to connect to Lab Assistant.
'; + profileModal.append(profileModalContent); + + let addAcctModal = document.createElement('div'); + addAcctModal.classList.add('assist-modal'); + addAcctModal.id = 'addAcctModal'; + addAcctModal.style.display = 'none'; + addAcctModal.style.zIndex = 3; + let addAcctModalContent = document.createElement('div'); + addAcctModalContent.classList.add('assist-modal-content'); + addAcctModalContent.innerHTML = '
Add Restricted Account
x
Grant restricted access to interact with this account\'s SAGE instance from another account. Enter the public key of the restricted account below.
'; + addAcctModal.append(addAcctModalContent); + + let assistStatus = document.createElement('div'); + assistStatus.id = 'assistStatus'; + assistStatus.style.display = 'none'; + let assistStatusContent = document.createElement('div'); + assistStatusContent.classList.add('assist-status-content'); + assistStatusContent.innerHTML = '
Status
x
FleetToolsSDUsState
' + assistStatus.append(assistStatusContent); + + let assistSurveillance = document.createElement('div') + assistSurveillance.id = 'assistSurveillance' + assistSurveillance.style.display = 'none' + let assistSurveillanceContent = document.createElement('div') + assistSurveillanceContent.innerHTML = '
Fleet Surveillance
x
'; + assistSurveillance.append(assistSurveillanceContent) + + let autoContainer = document.createElement('div'); + autoContainer.style.display = 'flex'; + autoContainer.style.flexDirection = 'row'; + let autoTitle = document.createElement('span'); + autoTitle.innerHTML = 'Lab Assistant'; + autoTitle.style.fontSize = '14px'; + let autoButton = document.createElement('button'); + autoButton.id = 'autoScanBtn'; + autoButton.classList.add('assist-btn'); + autoButton.addEventListener('click', function(e) {toggleAssistant();}); + let autoBtnSpan = document.createElement('span'); + autoBtnSpan.innerText = initComplete == true ? 'Start' : 'Wait...'; + autoBtnSpan.style.fontSize = '14px'; + autoButton.appendChild(autoBtnSpan); + + let dropdown = document.createElement('div'); + dropdown.classList.add('dropdown'); + let dropdownBtn = document.createElement('button'); + dropdownBtn.classList.add('assist-btn'); + dropdownBtn.innerText = 'Tools'; + dropdownBtn.addEventListener('click', function () { + dropdown.classList.toggle('show'); + }) + dropdown.addEventListener('click', function() { + dropdown.classList.remove('show'); + }); + + let assistConfigButton = document.createElement('button'); + assistConfigButton.id = 'assistConfigBtn'; + assistConfigButton.classList.add('assist-btn','assist-btn-alt'); + assistConfigButton.addEventListener('click', function(e) {assistModalToggle();}); + let assistConfigSpan = document.createElement('span'); + assistConfigSpan.innerText = 'Config'; + assistConfigSpan.style.fontSize = '14px'; + assistConfigButton.appendChild(assistConfigSpan); + + let assistSurveillanceButton = document.createElement('button'); + assistSurveillanceButton.id = 'assistSurveillanceBtn'; + assistSurveillanceButton.classList.add('assist-btn','assist-btn-alt'); + assistSurveillanceButton.addEventListener('click', function(e) {assistSurveillanceToggle();}); + let assistSurveillanceSpan = document.createElement('span'); + assistSurveillanceSpan.innerText = 'Surveillance'; + assistSurveillanceSpan.style.fontSize = '14px'; + assistSurveillanceButton.appendChild(assistSurveillanceSpan); + + let assistStatusButton = document.createElement('button'); + assistStatusButton.id = 'assistStatusBtn'; + assistStatusButton.classList.add('assist-btn','assist-btn-alt'); + assistStatusButton.addEventListener('click', function(e) {assistStatusToggle();}); + let assistStatusSpan = document.createElement('span'); + assistStatusSpan.innerText = 'Status'; + assistStatusSpan.style.fontSize = '14px'; + assistStatusButton.appendChild(assistStatusSpan); + autoContainer.appendChild(autoTitle); + autoContainer.appendChild(autoButton); + autoContainer.appendChild(dropdownBtn); + autoContainer.appendChild(dropdown); + + dropdown.appendChild(assistStatusButton); + dropdown.appendChild(assistSurveillanceButton); + dropdown.appendChild(assistConfigButton); + + let targetElem = document.querySelector('body'); + if (observer) { + autoContainer.id = 'assistContainer'; + targetElem = document.querySelector('#root > div:first-of-type > div:first-of-type > div > header > h1'); + targetElem.style.fontSize = '18px'; + targetElem.append(assistCSS); + targetElem.append(autoContainer); + } else { + autoContainer.id = 'assistContainerIso'; + targetElem.prepend(autoContainer); + targetElem.prepend(assistCSS); + } + targetElem.append(assistModal); + targetElem.append(assistStatus); + targetElem.append(assistSurveillance); + targetElem.append(importModal); + targetElem.append(walletModal); + targetElem.append(profileModal); + targetElem.append(addAcctModal); + + // wallet modal buttons + let walletModalClose = document.querySelector('#walletModalAssistant .assist-modal-close'); + walletModalClose.addEventListener('click', function(e) {selectWalletToggle();}); + + // profile modal butttons + let profileModalClose = document.querySelector('#profileModal .assist-modal-close'); + profileModalClose.addEventListener('click', function(e) {assistProfileToggle(null);}); + + // status modal buttons + let assistStatusClose = document.querySelector('#assistStatus .assist-modal-close'); + assistStatusClose.addEventListener('click', function(e) {assistStatusToggle();}); + + // surveillance modal buttons + let assistSurveillanceFleetBtn = document.querySelector('#checkFleetBtn'); + assistSurveillanceFleetBtn.addEventListener('click', function(e) {getFleetCountAtCoords();}); + let assistSurveillanceClose = document.querySelector('#assistSurveillance .assist-modal-close'); + assistSurveillanceClose.addEventListener('click', function(e) {assistSurveillanceToggle();}); + + // config modal buttons + let addAcctOpen = document.querySelector('#addAcctOpen'); + addAcctOpen.addEventListener('click', function(e) {assistAddAcctToggle();}); + let configImportExport = document.querySelector('#configImportExportBtn'); + configImportExport.addEventListener('click', function(e) {exportConfig();}); + let assistModalSave = document.querySelector('#assistModal .assist-modal-save'); + assistModalSave.addEventListener('click', function(e) {saveConfig();}); + let assistModalClose = document.querySelector('#assistModal .assist-modal-close'); + assistModalClose.addEventListener('click', function(e) {assistModalToggle();}); + + // import / export modal buttons + let configImport = document.querySelector('#importConfigBtn'); + configImport.addEventListener('click', function(e) {importConfig();}); + let configImportClose = document.querySelector('#importModal .assist-modal-close'); + configImportClose.addEventListener('click', function(e) {assistImportExportToggle();}); + + // restricted profile buttons + let addAcctBtn = document.querySelector('#addAcctBtn'); + addAcctBtn.addEventListener('click', function(e) {addKeyToProfile(document.querySelector('#addAcctDiv').value);}); + let removeAcctBtn = document.querySelector('#removeAcctBtn'); + removeAcctBtn.addEventListener('click', function(e) {removeKeyFromProfile();}); + let addAcctClose = document.querySelector('#addAcctModal .assist-modal-close'); + addAcctClose.addEventListener('click', function(e) {assistAddAcctToggle();}); + + makeDraggable(assistSurveillance); + makeDraggable(assistStatus); + } + } + observer.observe(document, {childList: true, subtree: true}); + waitForLabs(null, null); + + + function TaskQueue(concurrentCount = 1) { + this.tasks = []; + this.total = this.tasks.length; + this.running = []; + this.complete = []; + this.count = concurrentCount; + } + + TaskQueue.prototype.runNext = function(){ + return ((this.running.length < this.count) && this.task.length); + } + + TaskQueue.prototype.add = function(task){ + this.tasks.push(task); + } + + // @todo - cancel task + // @todo - should there be a timeout for each task? + + TaskQueue.prototype.run = function () { + while (this.runNext()) { + const promise = this.tasks.shift(); + promise.then((fleet) => { + switch(fleet.assignment) { + case 'Mine': + this.add(handleMining(fleet)); + break; + case 'Transport': + this.add(handleTransport(fleet)); + break; + case 'Scan': + this.add(handleScan(fleet)); + break; + default: + console.log(`${ fleet.assignment } is unsupported`); + } + this.run(); + }); + this.running.push({fleet: fleet.account.publicKey, promise }); + } + } + + await initUser(); + let autoSpanRef = document.querySelector('#autoScanBtn > span'); + autoSpanRef ? autoSpanRef.innerHTML = 'Start' : null; + console.log('init complete'); + console.log('Fleets: ', userFleets); +})(); \ No newline at end of file diff --git a/SLY_Lab_Assistant.user.js b/SLY_Lab_Assistant.user.js index 02a4175..66ab055 100644 --- a/SLY_Lab_Assistant.user.js +++ b/SLY_Lab_Assistant.user.js @@ -1,7 +1,7 @@ // ==UserScript== -// @name SLY Lab Assistant +// @name SLY Lab Assistant 1.0 // @namespace http://tampermonkey.net/ -// @version 0.3.7 +// @version 0.5.0 // @description try to take over the world! // @author SLY w/ Contributions by SkyLove512, anthonyra, niofox // @match https://labs.staratlas.com/ @@ -18,53 +18,320 @@ (async function() { 'use strict'; + let provider; + const wallets = [ solflare, solana, phantom ]; + let graceBlockWindow = 5; let enableAssistant = false; let initComplete = false; - const solanaConnection = new solanaWeb3.Connection('https://solana-api.syndica.io/access-token/WPoEqWQ2auQQY1zHRNGJyRBkvfOLqw58FqYucdYtmy8q9Z84MBWwqtfVf8jKhcFh/rpc', 'confirmed'); + // in memory storage + let userFleets = []; + let playerProfile; + const GLOBAL_SCALE_DECIMALS_2 = 100; + const GLOBAL_SCALE_DECIMALS_6 = 1000000; + + const solanaConnection = new solanaWeb3.Connection('https://rpc.hellomoon.io/cfd5910f-fb7d-4489-9b32-f97193eceefd', 'confirmed'); const anchorProvider = new BrowserAnchor.anchor.AnchorProvider(solanaConnection, null, null); - const sageIDL = JSON.parse('{"version":"0.1.0","name":"sage","instructions":[{"name":"activateGameState","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":true,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":true,"isSigner":false,"docs":["The [`GameState`] account"]}],"args":[{"name":"input","type":{"defined":"ManageGameInput"}}]},{"name":"addConnection","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for adding the connection"]},{"name":"sector1","isMut":true,"isSigner":false,"docs":["The first connected sector"]},{"name":"sector2","isMut":true,"isSigner":false,"docs":["The second connected sector"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"subCoordinates1","type":{"array":["i64",2]}},{"name":"flags1","type":"u8"},{"name":"subCoordinates2","type":{"array":["i64",2]}},{"name":"flags2","type":"u8"},{"name":"keyIndex","type":"u16"}]},{"name":"addRental","accounts":[{"name":"ownerProfile","isMut":false,"isSigner":false,"docs":["The fleet owners profile."]},{"name":"ownerKey","isMut":false,"isSigner":true,"docs":["The key on the owner profile with renting permissions."]},{"name":"invalidator","isMut":false,"isSigner":true,"docs":["This is a signer to help make sure the fleet wont be locked."]},{"name":"subProfile","isMut":false,"isSigner":false,"docs":["The profile to rent to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet to rent out."]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[{"name":"ownerKeyIndex","type":"u16"}]},{"name":"addShipEscrow","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"sagePlayerProfile","isMut":true,"isSigner":false,"docs":["The [`SagePlayerProfile`] account"]},{"name":"originTokenAccount","isMut":true,"isSigner":false,"docs":["The Origin Token Account"]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account"]},{"name":"shipEscrowTokenAccount","isMut":true,"isSigner":false,"docs":["The Escrow Token Account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Token Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"AddShipEscrowInput"}}]},{"name":"addShipToFleet","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new `Fleet`"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"AddShipToFleetInput"}}]},{"name":"cancelCraftingProcess","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":true,"isSigner":false,"docs":["The [`CraftingInstance`] account to cancel"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account"]},{"name":"craftingFacility","isMut":true,"isSigner":false,"docs":["The `CraftingFacility` account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]}],"args":[{"name":"input","type":{"defined":"StarbaseCancelCraftingProcessInput"}}]},{"name":"changeRental","accounts":[{"name":"subProfileInvalidator","isMut":false,"isSigner":true,"docs":["This is a signer to help make sure the fleet wont be locked."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet to rent out."]},{"name":"newSubProfile","isMut":false,"isSigner":false,"docs":["The new sub profile"]}],"args":[]},{"name":"claimCraftingNonConsumables","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":false,"isSigner":false,"docs":["The [`CraftingInstance`] account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting recipe"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source account of the tokens - owner should be `crafting_process`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination account of the tokens - owner should be `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The token mint"]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"StarbaseClaimCraftingNonConsumablesInput"}}]},{"name":"claimCraftingOutputs","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":false,"isSigner":false,"docs":["The [`CraftingInstance`] account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The [`CraftingProcess`] account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting [`Recipe`]"]},{"name":"craftableItem","isMut":false,"isSigner":false,"docs":["The craftable item"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source account of the tokens - owner should be `craftable_item`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination account of the tokens - owner should be `cargo_pod_to`"]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"StarbaseClaimCraftingOutputInput"}}]},{"name":"closeCraftingProcess","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":true,"isSigner":false,"docs":["The [`CraftingInstance`] account to close"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting recipe account"]},{"name":"craftingFacility","isMut":true,"isSigner":false,"docs":["The `CraftingFacility` account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]}],"args":[{"name":"input","type":{"defined":"StarbaseCloseCraftingProcessInput"}}]},{"name":"closeDisbandedFleet","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the player profile."]},{"name":"playerProfile","isMut":false,"isSigner":false,"docs":["The player profile."]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"disbandedFleet","isMut":true,"isSigner":false,"docs":["The [`DisbandedFleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]}],"args":[{"name":"input","type":{"defined":"CloseDisbandedFleetInput"}}]},{"name":"closeFleetCargoPodTokenAccount","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The cargo pod, owned by the fleet"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"token","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"closeStarbaseCargoTokenAccount","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The new cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"token","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"copyGameState","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new `GameState`"]},{"name":"oldGameState","isMut":false,"isSigner":false,"docs":["The old [`GameState`] account"]},{"name":"newGameState","isMut":true,"isSigner":false,"docs":["The [`GameState`] account","This will and should fail if there already exists a `GameState`for the desired `update_id`"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"ManageGameInput"}}]},{"name":"createCargoPod","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new crafting process"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The new cargo pod"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"StarbaseCreateCargoPodInput"}}]},{"name":"createCraftingProcess","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new crafting process"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":true,"isSigner":false,"docs":["The [`CraftingInstance`] account to initialize"]},{"name":"craftingFacility","isMut":true,"isSigner":false,"docs":["The `CraftingFacility` account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account (NOT initialized)"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting recipe"]},{"name":"craftingDomain","isMut":false,"isSigner":false,"docs":["The crafting domain"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["Solana System program"]}],"args":[{"name":"input","type":{"defined":"StarbaseCreateCraftingProcessInput"}}]},{"name":"createFleet","accounts":[{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new `Fleet`"]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The [`Fleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"cargoHold","isMut":true,"isSigner":false,"docs":["The new fleet `cargo_hold` cargo pod (not initialized)"]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The new fleet `fuel_tank` cargo pod (not initialized)"]},{"name":"ammoBank","isMut":true,"isSigner":false,"docs":["The new fleet `ammo_bank` cargo pod (not initialized)"]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account - represents the first ship in the new fleet"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"CreateFleetInput"}}]},{"name":"depositCargoToFleet","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPodFrom","isMut":true,"isSigner":false,"docs":["The origin cargo pod"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod_from`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"DepositCargoToFleetInput"}}]},{"name":"depositCargoToGame","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The new cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `key`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod`"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"CargoToGameInput"}}]},{"name":"depositCraftingIngredient","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":false,"isSigner":false,"docs":["The [`CraftingInstance`] account"]},{"name":"craftingFacility","isMut":false,"isSigner":false,"docs":["The [`CraftingFacility`](crafting::CraftingFacility) account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account"]},{"name":"cargoPodFrom","isMut":true,"isSigner":false,"docs":["The source cargo pod account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting recipe"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source account of the tokens - owner should be `cargo_pod_from`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination account of the tokens - owner should be `crafting_process`"]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"StarbaseDepositCraftingIngredientInput"}}]},{"name":"deregisterMineItem","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing funds go."]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"deregisterResource","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing funds go."]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]},{"name":"location","isMut":true,"isSigner":false,"docs":["The Location address"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"deregisterStarbase","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing funds go."]},{"name":"starbase","isMut":true,"isSigner":false,"docs":["The [`Starbase`] account"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"deregisterSurveyDataUnitTracker","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing funds go."]},{"name":"surveyDataUnitTracker","isMut":true,"isSigner":false,"docs":["The [`SurveyDataUnitTracker`] account"]}],"args":[{"name":"input","type":{"defined":"DeregisterSurveyDataUnitTrackerInput"}}]},{"name":"disbandFleet","accounts":[{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"disbandedFleet","isMut":true,"isSigner":false,"docs":["The [`DisbandedFleet`] account"]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The [`Fleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"cargoHold","isMut":true,"isSigner":false,"docs":["The fleet `cargo_hold` cargo pod"]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The fleet `fuel_tank` cargo pod"]},{"name":"ammoBank","isMut":true,"isSigner":false,"docs":["The fleet `ammo_bank` cargo pod"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"DisbandFleetInput"}}]},{"name":"disbandedFleetToEscrow","accounts":[{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"disbandedFleet","isMut":true,"isSigner":false,"docs":["The [`DisbandedFleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"DisbandedFleetToEscrowInput"}}]},{"name":"drainMineItemBank","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing funds go."]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The mine item token bank to drain"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["Where to send tokens from the bank"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"drainSurveyDataUnitsBank","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the closing rent refunds go."]},{"name":"surveyDataUnitTracker","isMut":false,"isSigner":false,"docs":["The [`SurveyDataUnitTracker`] account"]},{"name":"surveyDataUnitTrackerSigner","isMut":false,"isSigner":false,"docs":["The `SurveyDataUnitTracker` signer"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The SDU token bank to drain"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["Where to send tokens from the bank"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"KeyIndexInput"}}]},{"name":"fleetStateHandler","accounts":[{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}],"args":[]},{"name":"forceDisbandFleet","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"disbandedFleet","isMut":true,"isSigner":false,"docs":["The new [`DisbandedFleet`] account"]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The [`Fleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"cargoHold","isMut":true,"isSigner":false,"docs":["The fleet `cargo_hold` cargo pod"]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The fleet `fuel_tank` cargo pod"]},{"name":"ammoBank","isMut":true,"isSigner":false,"docs":["The fleet `ammo_bank` cargo pod"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`]","Must provide at least one ship that is invalid for this instruction"]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"ForcedDisbandFleetInput"}}]},{"name":"forceDropFleetCargo","accounts":[{"name":"fleet","isMut":true,"isSigner":false,"docs":["The `Fleet` Account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The origin cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The `cargo_type` for the token"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token account"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[]},{"name":"idleToLoadingBay","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"idleToRespawn","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"atlasTokenFrom","isMut":true,"isSigner":false,"docs":["Source Token account for ATLAS, owned by the player"]},{"name":"atlasTokenTo","isMut":true,"isSigner":false,"docs":["Vault Token account for ATLAS"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Solana Token Program"]}],"args":[{"name":"input","type":{"defined":"IdleToRespawnInput"}}]},{"name":"initGame","accounts":[{"name":"signer","isMut":true,"isSigner":true,"docs":["The entity calling this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The sector permissions [`Profile`]"]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new game"]},{"name":"gameId","isMut":true,"isSigner":true,"docs":["The [`Game`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[]},{"name":"initGameState","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new `GameState`"]},{"name":"gameState","isMut":true,"isSigner":false,"docs":["The [`GameState`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"InitGameStateInput"}}]},{"name":"invalidateRental","accounts":[{"name":"subProfileInvalidator","isMut":false,"isSigner":true,"docs":["This is a signer to help make sure the fleet wont be locked."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet to rent out."]}],"args":[]},{"name":"invalidateShip","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":true,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"ship","isMut":true,"isSigner":false,"docs":["The current [`Ship`] account"]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"loadingBayToIdle","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"mineAsteroidToRespawn","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]},{"name":"planet","isMut":true,"isSigner":false,"docs":["The [`Planet`] account"]},{"name":"fuelTank","isMut":false,"isSigner":false,"docs":["The fuel tank cargo pod"]},{"name":"fuelTokenFrom","isMut":false,"isSigner":false,"docs":["The source token account for fuel - owned by the `fuel_tank`"]},{"name":"atlasTokenFrom","isMut":true,"isSigner":false,"docs":["Source Token account for ATLAS, owned by the player"]},{"name":"atlasTokenTo","isMut":true,"isSigner":false,"docs":["Vault Token account for ATLAS"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Solana Token Program"]}],"args":[{"name":"input","type":{"defined":"MineAsteroidToRespawnInput"}}]},{"name":"registerFleetsPointModifier","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for the cpi points instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The points permissions [`Profile`](player_profile::state::Profile)"]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"pointsCategory","isMut":false,"isSigner":false,"docs":["The `PointCategory`"]},{"name":"pointsModifierAccount","isMut":true,"isSigner":false,"docs":["The `PointsModifier` account to be inited in Points CPI"]},{"name":"pointsProgram","isMut":false,"isSigner":false,"docs":["The points program"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]},{"name":"game","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterFleetsPointsModifierInput"}}]},{"name":"registerMineItem","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new mine item"]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"mint","isMut":true,"isSigner":false,"docs":["The mint address representing the mine item"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterMineItemInput"}}]},{"name":"registerPlanet","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new game"]},{"name":"planet","isMut":true,"isSigner":true,"docs":["The [`Planet`] account"]},{"name":"sector","isMut":true,"isSigner":false,"docs":["The [`Sector`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterPlanetInput"}}]},{"name":"registerResource","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new resource"]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]},{"name":"location","isMut":true,"isSigner":false,"docs":["The Location address"]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterResourceInput"}}]},{"name":"registerSagePlayerProfile","accounts":[{"name":"profile","isMut":false,"isSigner":false,"docs":["The player permissions [`Profile`]"]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new profile"]},{"name":"sagePlayerProfile","isMut":true,"isSigner":false,"docs":["The `SagePlayerProfile` account"]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[]},{"name":"registerSector","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new sector"]},{"name":"discoverer","isMut":false,"isSigner":false,"docs":["The discoverer of this sector"]},{"name":"sector","isMut":true,"isSigner":false,"docs":["The [`Sector`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"coordinates","type":{"array":["i64",2]}},{"name":"name","type":{"array":["u8",64]}},{"name":"keyIndex","type":"u16"}]},{"name":"registerShip","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new game"]},{"name":"ship","isMut":true,"isSigner":true,"docs":["The [`Ship`] account"]},{"name":"mint","isMut":false,"isSigner":false,"docs":["The mint address representing the [`Ship`]"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterShipInput"}}]},{"name":"registerStar","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder - pays for account rent"]},{"name":"star","isMut":true,"isSigner":true,"docs":["The [`Star`] account"]},{"name":"sector","isMut":true,"isSigner":false,"docs":["The [`Sector`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterStarInput"}}]},{"name":"registerStarbase","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new star base"]},{"name":"starbase","isMut":true,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"sector","isMut":false,"isSigner":false,"docs":["The [`Sector`] account"]},{"name":"gameStateAndProfile","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterStarbaseInputUnpacked"}}]},{"name":"registerStarbasePlayer","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder -- pays account rent"]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"sagePlayerProfile","isMut":false,"isSigner":false,"docs":["The [`SagePlayerProfile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the player belongs to."]},{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] account to initialize"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[]},{"name":"registerSurveyDataUnitTracker","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new mine item"]},{"name":"surveyDataUnitTracker","isMut":true,"isSigner":false,"docs":["The [`SurveyDataUnitTracker`] account"]},{"name":"mint","isMut":false,"isSigner":false,"docs":["The mint for the new `SurveyDataUnitTracker`"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RegisterSurveyDataUnitTrackerInput"}}]},{"name":"removeCargoPod","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The cargo pod (should be empty)"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"StarbaseRemoveCargoPodInput"}}]},{"name":"removeConnection","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["Where the rent refund funds from the connections go to."]},{"name":"sector1","isMut":true,"isSigner":false,"docs":["The first sector to remove from"]},{"name":"sector2","isMut":true,"isSigner":false,"docs":["The second sector to remove from"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"sector1Index","type":"u16"},{"name":"sector2Index","type":"u16"},{"name":"keyIndex","type":"u16"}]},{"name":"removeInvalidShipEscrow","accounts":[{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"sagePlayerProfile","isMut":true,"isSigner":false,"docs":["The [`SagePlayerProfile`] account"]},{"name":"destinationTokenAccount","isMut":true,"isSigner":false,"docs":["The Destination Token Account"]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account"]},{"name":"shipEscrowTokenAccount","isMut":true,"isSigner":false,"docs":["The Escrow Token Account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Token Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RemoveShipEscrowInput"}}]},{"name":"removeShipEscrow","accounts":[{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"sagePlayerProfile","isMut":true,"isSigner":false,"docs":["The [`SagePlayerProfile`] account"]},{"name":"destinationTokenAccount","isMut":true,"isSigner":false,"docs":["The Destination Token Account"]},{"name":"ship","isMut":false,"isSigner":false,"docs":["The [`Ship`] Account"]},{"name":"shipEscrowTokenAccount","isMut":true,"isSigner":false,"docs":["The Escrow Token Account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Token Program"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"RemoveShipEscrowInput"}}]},{"name":"respawnToLoadingBay","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoHold","isMut":false,"isSigner":false,"docs":["The fleet `cargo_hold` cargo pod"]},{"name":"fuelTank","isMut":false,"isSigner":false,"docs":["The fleet `fuel_tank` cargo pod"]},{"name":"ammoBank","isMut":false,"isSigner":false,"docs":["The fleet `ammo_bank` cargo pod"]}],"args":[{"name":"input","type":{"defined":"RespawnToLoadingBayInput"}}]},{"name":"scanForSurveyDataUnits","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"surveyDataUnitTracker","isMut":true,"isSigner":false,"docs":["The [`SurveyDataUnitTracker`] account"]},{"name":"surveyDataUnitTrackerSigner","isMut":false,"isSigner":false,"docs":["The `SurveyDataUnitTracker` signer"]},{"name":"cargoHold","isMut":true,"isSigner":false,"docs":["The general cargo hold cargo pod for the fleet"]},{"name":"sduTokenFrom","isMut":true,"isSigner":false,"docs":["Source token account for the SDU, owned by `survey_data_unit_tracker_signer`"]},{"name":"sduTokenTo","isMut":true,"isSigner":false,"docs":["Destination token account for the SDU, owned by cargo_hold"]},{"name":"repairKitTokenFrom","isMut":true,"isSigner":false,"docs":["Token account for repair kit, owned by fleet"]},{"name":"repairKitMint","isMut":true,"isSigner":false,"docs":["The food token mint"]},{"name":"sduCargoType","isMut":false,"isSigner":false,"docs":["The cargo type of the SDU"]},{"name":"repairKitCargoType","isMut":false,"isSigner":false,"docs":["The cargo type of Repair Kits"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The cargo program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The token program"]},{"name":"instructionsSysvar","isMut":false,"isSigner":false,"docs":["Solana Instructions Sysvar"]},{"name":"recentSlothashes","isMut":false,"isSigner":false,"docs":["Solana recent slothashes"]}],"args":[{"name":"input","type":{"defined":"ScanForSurveyDataUnitsInput"}}]},{"name":"setNextShip","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"ship","isMut":true,"isSigner":false,"docs":["The current [`Ship`] account"]},{"name":"nextShip","isMut":true,"isSigner":false,"docs":["The next [`Ship`] account"]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"startCraftingProcess","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":false,"isSigner":false,"docs":["The [`CraftingInstance`] account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The [`CraftingProcess`] account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting [`Recipe`]"]},{"name":"craftingFacility","isMut":false,"isSigner":false,"docs":["The `CraftingFacility` account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]}],"args":[{"name":"input","type":{"defined":"StarbaseStartCraftingProcessInput"}}]},{"name":"startMiningAsteroid","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"mineItem","isMut":false,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]},{"name":"planet","isMut":true,"isSigner":false,"docs":["The [`Planet`] account"]}],"args":[{"name":"input","type":{"defined":"StartMiningAsteroidInput"}}]},{"name":"startSubwarp","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[{"name":"input","type":{"defined":"StartSubwarpInput"}}]},{"name":"stopMiningAsteroid","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]},{"name":"planet","isMut":true,"isSigner":false,"docs":["The [`Planet`] account"]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The fuel tank cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account for fuel"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `fuel_tank`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token account"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"StopMiningAsteroidInput"}}]},{"name":"stopSubwarp","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[{"name":"input","type":{"defined":"StopSubwarpInput"}}]},{"name":"syncResource","accounts":[{"name":"mineItem","isMut":false,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]}],"args":[]},{"name":"syncStarbasePlayer","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[]},{"name":"transferCargoAtStarbase","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPodFrom","isMut":true,"isSigner":false,"docs":["The origin cargo pod"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod_from`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"StarbaseTransferCargoInput"}}]},{"name":"transferCargoWithinFleet","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoPodFrom","isMut":true,"isSigner":false,"docs":["The origin cargo pod"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod_from`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"TransferCargoWithinFleetInput"}}]},{"name":"updateGame","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":true,"isSigner":false,"docs":["The [`Game`] account"]}]}],"args":[{"name":"input","type":{"defined":"UpdateGameInput"}}]},{"name":"updateGameState","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":true,"isSigner":false,"docs":["The [`GameState`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateGameStateInput"}}]},{"name":"updateMineItem","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"mineItem","isMut":true,"isSigner":false,"docs":["The [`MineItem`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateMineItemInput"}}]},{"name":"updatePlanet","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"planet","isMut":true,"isSigner":false,"docs":["The [`Planet`] account"]}],"args":[{"name":"input","type":{"defined":"UpdatePlanetInput"}}]},{"name":"updateResource","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"mineItem","isMut":false,"isSigner":false,"docs":["The [`MineItem`] account"]},{"name":"resource","isMut":true,"isSigner":false,"docs":["The [`Resource`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateResourceInput"}}]},{"name":"updateShip","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"ship","isMut":true,"isSigner":false,"docs":["The [`Ship`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateShipInput"}}]},{"name":"updateShipEscrow","accounts":[{"name":"oldShip","isMut":false,"isSigner":false,"docs":["The old [`Ship`] Account"]},{"name":"next","isMut":false,"isSigner":false,"docs":["The address indicated as `next` in the `old_ship` account"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":true,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[{"name":"input","type":{"defined":"UpdateShipEscrowInput"}}]},{"name":"updateShipInFleet","accounts":[{"name":"fleet","isMut":true,"isSigner":false,"docs":["The [`Fleet`] account"]},{"name":"fleetShips","isMut":true,"isSigner":false,"docs":["The [`FleetShips`] account"]},{"name":"oldShip","isMut":false,"isSigner":false,"docs":["The old [`Ship`] Account"]},{"name":"next","isMut":false,"isSigner":false,"docs":["The address indicated as `next` in the `old_ship` account"]},{"name":"gameAccounts","accounts":[{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]}],"args":[{"name":"input","type":{"defined":"UpdateShipFleetInput"}}]},{"name":"updateStar","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"star","isMut":true,"isSigner":false,"docs":["The [`Star`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateStarInput"}}]},{"name":"updateStarbase","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new game"]},{"name":"starbase","isMut":true,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The Solana System program"]}],"args":[{"name":"input","type":{"defined":"UpdateStarbaseInput"}}]},{"name":"updateSurveyDataUnitTracker","accounts":[{"name":"gameAndProfile","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"surveyDataUnitTracker","isMut":true,"isSigner":false,"docs":["The [`SurveyDataUnitTracker`] account"]}],"args":[{"name":"input","type":{"defined":"UpdateSurveyDataUnitTrackerInput"}}]},{"name":"warpLane","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"fromStarbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"toStarbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"fromSector","isMut":false,"isSigner":false,"docs":["The Sector account representing the fleet`s current sector"]},{"name":"toSector","isMut":false,"isSigner":false,"docs":["The Sector account that `Fleet` will move to"]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The fuel tank cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The `Cargo Type` Account"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The `CargoStatsDefinition` for the cargo type"]},{"name":"fuelTokenFrom","isMut":true,"isSigner":false,"docs":["The fuel source token account - owned by the `fuel_tank`"]},{"name":"fuelMint","isMut":true,"isSigner":false,"docs":["Token Mint - The fuel mint"]},{"name":"feeTokenFrom","isMut":true,"isSigner":false,"docs":["The fee source token account"]},{"name":"feeTokenTo","isMut":true,"isSigner":false,"docs":["The fee destination token account"]},{"name":"feeMint","isMut":true,"isSigner":false,"docs":["Fee Token Mint"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"WarpLaneInput"}}]},{"name":"warpToCoordinate","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"fuelTank","isMut":true,"isSigner":false,"docs":["The fuel tank cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account for fuel"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `fuel_tank`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["Token Mint - The fuel mint"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"WarpToCoordinateInput"}}]},{"name":"withdrawCargoFromFleet","accounts":[{"name":"gameAccountsFleetAndOwner","accounts":[{"name":"gameFleetAndOwner","accounts":[{"name":"fleetAndOwner","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key on the profile."]},{"name":"owningProfile","isMut":false,"isSigner":false,"docs":["The profile that owns the fleet."]},{"name":"owningProfileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"fleet","isMut":true,"isSigner":false,"docs":["The fleet."]}]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"cargoPodFrom","isMut":true,"isSigner":false,"docs":["The origin cargo pod, owned by the fleet"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod, owned by the Starbase player"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod_from`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"WithdrawCargoFromFleetInput"}}]},{"name":"withdrawCargoFromGame","accounts":[{"name":"fundsTo","isMut":true,"isSigner":false,"docs":["The funds_to - receives rent refund"]},{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The new cargo pod"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The cargo type account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source token account - owned by the `cargo_pod`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination token account - owned by the `key`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The mint of the token accounts"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"input","type":{"defined":"CargoToGameInput"}}]},{"name":"withdrawCraftingIngredient","accounts":[{"name":"starbaseAndStarbasePlayer","accounts":[{"name":"starbase","isMut":false,"isSigner":false,"docs":["The [`Starbase`] account"]},{"name":"starbasePlayer","isMut":false,"isSigner":false,"docs":["The [`StarbasePlayer`] Account"]}]},{"name":"craftingInstance","isMut":false,"isSigner":false,"docs":["The [`CraftingInstance`] account"]},{"name":"craftingFacility","isMut":false,"isSigner":false,"docs":["The [`CraftingFacility`](crafting::CraftingFacility) account"]},{"name":"craftingProcess","isMut":true,"isSigner":false,"docs":["The crafting process account"]},{"name":"cargoPodTo","isMut":true,"isSigner":false,"docs":["The destination cargo pod account"]},{"name":"craftingRecipe","isMut":false,"isSigner":false,"docs":["The crafting recipe"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoStatsDefinition","isMut":false,"isSigner":false,"docs":["The cargo stats definition account"]},{"name":"gameAccountsAndProfile","accounts":[{"name":"gameAndProfileAndFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The [`Profile`] account"]},{"name":"profileFaction","isMut":false,"isSigner":false,"docs":["The faction that the profile belongs to."]},{"name":"gameId","isMut":false,"isSigner":false,"docs":["The [`Game`] account"]}]},{"name":"gameState","isMut":false,"isSigner":false,"docs":["The [`GameState`] account"]}]},{"name":"tokenFrom","isMut":true,"isSigner":false,"docs":["The source account of the tokens - owner should be `crafting_process`"]},{"name":"tokenTo","isMut":true,"isSigner":false,"docs":["The destination account of the tokens - owner should be `cargo_pod_to`"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The token mint"]},{"name":"craftingProgram","isMut":false,"isSigner":false,"docs":["The Crafting Program"]},{"name":"cargoProgram","isMut":false,"isSigner":false,"docs":["The Cargo Program"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The [Token] program"]}],"args":[{"name":"input","type":{"defined":"StarbaseWithdrawCraftingIngredientInput"}}]}],"accounts":[{"name":"CraftingInstance","docs":["This account is used to store relevant information for a crafting process instance"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account"],"type":"u8"},{"name":"seqId","docs":["The sequence id for the `Starbase`"],"type":"u16"},{"name":"numCrew","docs":["The number of crew taking part in the crafting process"],"type":"u64"},{"name":"starbasePlayer","docs":["The `StarbasePlayer` account address"],"type":"publicKey"},{"name":"craftingProcess","docs":["The `CraftingProcess` account address"],"type":"publicKey"},{"name":"bump","docs":["Bump of Account PDA"],"type":"u8"}]}},{"name":"DisbandedFleet","docs":["Keeps track of a fleet while it is disbanded"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["The game id this belongs to."],"type":"publicKey"},{"name":"ownerProfile","docs":["The owners profile."],"type":"publicKey"},{"name":"starbase","docs":["The `Starbase` at which the original `Fleet` was disbanded."],"type":"publicKey"},{"name":"fleetLabel","docs":["The label or name of the disbanded fleet."],"type":{"array":["u8",32]}},{"name":"fleetShips","docs":["The `FleetShips` account belonging to the original `Fleet` that was disbanded."],"type":"publicKey"},{"name":"bump","docs":["The disbanded fleets bump."],"type":"u8"}]}},{"name":"Fleet","docs":["A `SAGE` fleet."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["The game id this belongs to."],"type":"publicKey"},{"name":"ownerProfile","docs":["The owners profile."],"type":"publicKey"},{"name":"fleetShips","docs":["Fleet Ships Key"],"type":"publicKey"},{"name":"subProfile","docs":["The fleets sub-authority.","If [`Some`] will have the exclusive ability to interact with this fleet."],"type":{"defined":"OptionalNonSystemPubkey"}},{"name":"subProfileInvalidator","docs":["The authority for revoking a sun-authority."],"type":"publicKey"},{"name":"fleetLabel","docs":["The label or name of the fleet."],"type":{"array":["u8",32]}},{"name":"shipCounts","docs":["The number of ships in the fleet."],"type":{"defined":"ShipCounts"}},{"name":"warpCooldownExpiresAt","docs":["The time at which the warp cooldown expires"],"type":"i64"},{"name":"scanCooldownExpiresAt","docs":["The time at which the scan cooldown expires"],"type":"i64"},{"name":"stats","docs":["The fleets stats."],"type":{"defined":"ShipStats"}},{"name":"cargoHold","docs":["The Cargo pod representing the fleets cargo hold"],"type":"publicKey"},{"name":"fuelTank","docs":["The Cargo pod representing the fleets fuel tank"],"type":"publicKey"},{"name":"ammoBank","docs":["The Cargo pod representing the fleets ammo bank"],"type":"publicKey"},{"name":"updateId","docs":["The update id for the `Fleet`"],"type":"u64"},{"name":"bump","docs":["The fleets bump."],"type":"u8"}]}},{"name":"FleetShips","docs":["Keeps track of a the individual ships that make up a fleet"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"fleet","docs":["The `Fleet` account this belongs to"],"type":"publicKey"},{"name":"fleetShipsInfoCount","docs":["List length of `RemainingData`"],"type":"u32"},{"name":"bump","docs":["The disbanded fleets bump."],"type":"u8"}]}},{"name":"Game","docs":["Global Game Configuration variables"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"updateId","docs":["The sequence id for updates."],"type":"u64"},{"name":"profile","docs":["The [`Profile`](player_profile::state::Profile) that handles the sector program permissions"],"type":"publicKey"},{"name":"gameState","docs":["The associated `GameState` account."],"type":"publicKey"},{"name":"points","docs":["Points setting"],"type":{"defined":"Points"}},{"name":"cargo","docs":["Cargo settings"],"type":{"defined":"Cargo"}},{"name":"crafting","docs":["Crafting settings"],"type":{"defined":"Crafting"}},{"name":"mints","docs":["mint related settings"],"type":{"defined":"Mints"}},{"name":"vaults","docs":["vault related settings"],"type":{"defined":"Vaults"}},{"name":"riskZones","docs":["Data for risk zones"],"type":{"defined":"RiskZonesData"}}]}},{"name":"GameState","docs":["Keeps track of variables that may change frequently during a `Game` session"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account"],"type":"u8"},{"name":"updateId","docs":["The sequence id for updates"],"type":"u64"},{"name":"gameId","docs":["The `Game` that this belongs to"],"type":"publicKey"},{"name":"fleet","docs":["Fleet settings"],"type":{"defined":"FleetInfo"}},{"name":"levers","docs":["Global levers"],"type":{"defined":"Levers"}},{"name":"misc","docs":["Miscellaneous settings"],"type":{"defined":"MiscVariables"}},{"name":"bump","docs":["PDA bump"],"type":"u8"}]}},{"name":"MineItem","docs":["Represents a token registered as an item that can be mined"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["the game_id account this item is registered with"],"type":"publicKey"},{"name":"name","docs":["The name of the `MineItem`"],"type":{"array":["u8",64]}},{"name":"mint","docs":["the mint representing the items mined"],"type":"publicKey"},{"name":"mineItemUpdateId","docs":["The `MineItem` update id"],"type":"u64"},{"name":"resourceHardness","docs":["How hard it is to mine this item -> Ranges from 1-10"],"type":"u16"},{"name":"numResourceAccounts","docs":["The number of resource accounts for this mine item"],"type":"u64"},{"name":"bump","docs":["bump for PDA"],"type":"u8"}]}},{"name":"Planet","docs":["Planet"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"name","docs":["The name of this `Planet`"],"type":{"array":["u8",64]}},{"name":"gameId","docs":["the `Game` that this belongs to"],"type":"publicKey"},{"name":"sector","docs":["the sector that this belongs to"],"type":{"array":["i64",2]}},{"name":"subCoordinates","docs":["sub_coordinates as [x, y]"],"type":{"array":["i64",2]}},{"name":"planetType","docs":["the planet type"],"type":"u8"},{"name":"position","docs":["the planet position"],"type":"u8"},{"name":"size","docs":["size"],"type":"u64"},{"name":"maxHp","docs":["maximum health"],"type":"u64"},{"name":"currentHealth","docs":["The current health of the `Planet`."],"type":"u64"},{"name":"amountMined","docs":["the cumulative amount mined from this `Asteroid`"],"type":"u64"},{"name":"numResources","docs":["the number of resources at this `Asteroid`"],"type":"u8"},{"name":"numMiners","docs":["the number of entities currently mining at this `Asteroid`"],"type":"u64"}]}},{"name":"Resource","docs":["Represents a mine-able item existing at a particular location (e.g. a planet)"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["the game_id pubkey"],"type":"publicKey"},{"name":"location","docs":["the locations pubkey"],"type":"publicKey"},{"name":"mineItem","docs":["the mine item pubkey"],"type":"publicKey"},{"name":"locationType","docs":["the location type"],"type":"u8"},{"name":"systemRichness","docs":["How abundant the resource is at the location -> Ranges from 1-5"],"type":"u16"},{"name":"amountMined","docs":["the cumulative amount mined from this resource"],"type":"u64"},{"name":"numMiners","docs":["the number of entities currently mining this resource"],"type":"u64"},{"name":"mineItemUpdateId","docs":["The `MineItem` update id"],"type":"u64"},{"name":"resourceUpdateId","docs":["The `Resource` update id"],"type":"u64"},{"name":"bump","docs":["bump for PDA"],"type":"u8"}]}},{"name":"SagePlayerProfile","docs":["A `SAGE` players profile."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account"],"type":"u8"},{"name":"playerProfile","docs":["The `Profile` key"],"type":"publicKey"},{"name":"gameId","docs":["The id of the `Game`"],"type":"publicKey"},{"name":"bump","docs":["Bump of Account PDA"],"type":"u8"}]}},{"name":"Sector","docs":["Sector"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["the game_id that this belongs to"],"type":"publicKey"},{"name":"coordinates","docs":["coordinates as [x, y]"],"type":{"array":["i64",2]}},{"name":"discoverer","docs":["The discoverer of this sector"],"type":"publicKey"},{"name":"name","docs":["The name of this sector"],"type":{"array":["u8",64]}},{"name":"numStars","docs":["the number of stars in this system"],"type":"u16"},{"name":"numPlanets","docs":["the number of planets in this system"],"type":"u16"},{"name":"numMoons","docs":["the number of moons in this system"],"type":"u16"},{"name":"numAsteroidBelts","docs":["the number of num_asteroid belts in this system"],"type":"u16"},{"name":"numConnections","docs":["the number of connections in this system"],"type":"u16"},{"name":"bump","docs":["PDA bump"],"type":"u8"}]}},{"name":"Ship","docs":["This account represents a Ship"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["the game_id account this Ship is registered with"],"type":"publicKey"},{"name":"mint","docs":["the mint representing the Ship"],"type":"publicKey"},{"name":"name","docs":["The name of this `Ship`"],"type":{"array":["u8",64]}},{"name":"sizeClass","docs":["the ships size class"],"type":"u8"},{"name":"stats","docs":["The ships stats"],"type":{"defined":"ShipStats"}},{"name":"updateId","docs":["The `update_id` for the `Ship`"],"type":"u64"},{"name":"maxUpdateId","docs":["The max `Game` `update_id` that the `Ship` is valid for"],"type":"u64"},{"name":"next","docs":["the next `Ship` account to use when this `Ship` is updated"],"type":{"defined":"OptionalNonSystemPubkey"}}]}},{"name":"Star","docs":["`Star` account"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"name","docs":["The name of this `Star`"],"type":{"array":["u8",64]}},{"name":"gameId","docs":["the game_id that this belongs to"],"type":"publicKey"},{"name":"sector","docs":["the sector that this belongs to"],"type":{"array":["i64",2]}},{"name":"size","docs":["size"],"type":"u64"},{"name":"subCoordinates","docs":["sub_coordinates as [x, y]"],"type":{"array":["i64",2]}},{"name":"starType","docs":["the star type"],"type":"u8"}]}},{"name":"Starbase","docs":["Starbase"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this `Starbase` account."],"type":"u8"},{"name":"gameId","docs":["the game_id that this `Starbase` belongs to"],"type":"publicKey"},{"name":"sector","docs":["the sector that this `Starbase` belongs to"],"type":{"array":["i64",2]}},{"name":"craftingFacility","docs":["the [`CraftingFacility`] to use for crafting at this `Starbase`"],"type":"publicKey"},{"name":"name","docs":["The name of this `Starbase`"],"type":{"array":["u8",64]}},{"name":"subCoordinates","docs":["coordinates as [x, y]"],"type":{"array":["i64",2]}},{"name":"faction","docs":["The faction of the `Starbase`."],"type":"u8"},{"name":"bump","docs":["bump for PDA"],"type":"u8"},{"name":"seqId","docs":["The sequence id for the `Starbase`"],"type":"u16"},{"name":"state","docs":["The state of the `Starbase`. Is a [`StarbaseState`]."],"type":"u8"},{"name":"level","docs":["The level of the `Starbase`."],"type":"u8"},{"name":"hp","docs":["The `Starbase` health points."],"type":"u64"},{"name":"sp","docs":["The `Starbase` shield points."],"type":"u64"},{"name":"sectorRingAvailable","docs":["The planet position (`sector::state::Ring`) available for this `Starbase`"],"type":"u8"},{"name":"upgradeState","docs":["The `Starbase` upgrade state using `StarbaseUpgradeLevelState`"],"type":"i64"},{"name":"builtDestroyedTimestamp","docs":["The last time the starbase was built or destroyed"],"type":"i64"},{"name":"numUpgradingFleets","docs":["The number of fleets currently upgrading the `Starbase`"],"type":"u64"},{"name":"totalUpgradeRate","docs":["The total rate at which the SB is currently upgrading"],"type":"u64"},{"name":"receivedUpgradeMaterials","docs":["The total received amount of material for upgrading the `Starbase` until the `last_updated_rate_timestamp`"],"type":"u64"},{"name":"requiredUpgradeMaterials","docs":["The total required amount of material for upgrading the `Starbase`"],"type":"u64"},{"name":"lastUpdatedRateTimestamp","docs":["The last time the SB total upgrade rate was updated"],"type":"i64"}]}},{"name":"StarbasePlayer","docs":["The `SAGE` player info within a `Starbase`"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account"],"type":"u8"},{"name":"playerProfile","docs":["The `Profile` key"],"type":"publicKey"},{"name":"gameId","docs":["The id of the `Game`"],"type":"publicKey"},{"name":"starbase","docs":["The `Starbase` key"],"type":"publicKey"},{"name":"sagePlayerProfile","docs":["The `SagePlayerProfile` key"],"type":"publicKey"},{"name":"bump","docs":["Bump of Account PDA"],"type":"u8"},{"name":"shipEscrowCount","docs":["List length of `RemainingData`"],"type":"u32"},{"name":"totalCrew","docs":["The total crew members from the players fleets at the `Starbase`"],"type":"u64"},{"name":"busyCrew","docs":["The number of crew members that is engaged/busy and not available"],"type":"u64"},{"name":"updateId","docs":["The `Game` update id"],"type":"u64"},{"name":"updatedShipEscrowCount","docs":["Number of updated items in `RemainingData` list","This will be `ship_escrow_count` when all ships in escrow are up-to-date"],"type":"u32"}]}},{"name":"SurveyDataUnitTracker","docs":["Survey Data Unit (SDU) Tracker"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"gameId","docs":["The game_id that this belongs to"],"type":"publicKey"},{"name":"mint","docs":["The Survey Data Unit Mint"],"type":"publicKey"},{"name":"signer","docs":["The signer for this account"],"type":"publicKey"},{"name":"signerBump","docs":["The signer for this account"],"type":"u8"},{"name":"surveyDataUnitBySecond","docs":["Survey Data Units found in the last `MAX_SECONDS` seconds","This is structured such that the 0th index represents SDUs found `MAX_SECONDS` seconds ago,","and the last index represents SDUs found in the most recent second","NB: the only reason why this is `[u32; MAX_SECONDS]` and not `[u16; MAX_SECONDS]` is to prevent overflows"],"type":{"array":["u32",60]}},{"name":"limit","docs":["The global limit on how many SDUs can be found in a `MAX_SECONDS` second period"],"type":"u32"},{"name":"scanCooldown","docs":["The amount of time that must go by before someone can scan a sector again"],"type":"u16"},{"name":"probability","docs":["The chance that a player gets an SDU on a legitimate scan, this is meant to be a percentage","10,000 == 100%, 100 = 1%, etc."],"type":"u16"},{"name":"max","docs":["The max number of SDUs that can be found while scanning"],"type":"u16"},{"name":"numSectors","docs":["The number of Sectors that can be scanned"],"type":"u16"},{"name":"lastUpdate","docs":["The last time the `SurveyDataUnitTracker` was updated"],"type":"i64"}]}}],"types":[{"name":"AddShipEscrowInput","docs":["Struct for data input for `AddShipEscrow`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Amount of `Ship` tokens to transfer to escrow"],"type":"u64"},{"name":"index","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`","Some index `WrappedShipEscrow`, or None for new `WrappedShipEscrow`"],"type":{"option":"u32"}}]}},{"name":"AddShipToFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Number of ships to add to the fleet"],"type":"u8"},{"name":"shipEscrowIndex","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`"],"type":"u32"},{"name":"fleetShipInfoIndex","docs":["Index of `FleetShipsInfo` in remaining data of `FleetShips`"],"type":{"option":"u32"}},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"BaseEmissionsBySizeUtil","docs":["Type containing derived sub-levers used in `calculate_base_emissions()` discriminated by ship size"],"type":{"kind":"struct","fields":[{"name":"xxSmall","docs":["xx_small"],"type":"u64"},{"name":"xSmall","docs":["x_small"],"type":"u64"},{"name":"small","docs":["small"],"type":"u64"},{"name":"medium","docs":["medium"],"type":"u64"},{"name":"large","docs":["large"],"type":"u64"},{"name":"capital","docs":["capital"],"type":"u64"},{"name":"commander","docs":["commander"],"type":"u64"},{"name":"titan","docs":["titan"],"type":"u64"}]}},{"name":"BaseEmissionsBySizeUtilInput","docs":["Struct for data input to Update `base_emissions_by_size_util`"],"type":{"kind":"struct","fields":[{"name":"xxSmall","docs":["xx_small"],"type":"u64"},{"name":"xSmall","docs":["x_small"],"type":"u64"},{"name":"small","docs":["small"],"type":"u64"},{"name":"medium","docs":["medium"],"type":"u64"},{"name":"large","docs":["large"],"type":"u64"},{"name":"capital","docs":["capital"],"type":"u64"},{"name":"commander","docs":["commander"],"type":"u64"},{"name":"titan","docs":["titan"],"type":"u64"}]}},{"name":"Cargo","docs":["Variables for the Cargo program"],"type":{"kind":"struct","fields":[{"name":"statsDefinition","docs":["The cargo stats definition account"],"type":"publicKey"}]}},{"name":"CargoStats","docs":["A ships cargo stats"],"type":{"kind":"struct","fields":[{"name":"cargoCapacity","docs":["the capacity of the ships cargo hold"],"type":"u32"},{"name":"fuelCapacity","docs":["the capacity of the ships fuel tank"],"type":"u32"},{"name":"ammoCapacity","docs":["the capacity of the ships ammo bank"],"type":"u32"},{"name":"ammoConsumptionRate","docs":["the amount of ammo consumed per second by the ship when doing non-combat activities e.g. mining"],"type":"u32"},{"name":"foodConsumptionRate","docs":["the amount of food consumed per second by the ship when doing non-combat activities e.g. mining"],"type":"u32"},{"name":"miningRate","docs":["the amount of resources that can be mined by a ship per second"],"type":"u32"},{"name":"upgradeRate","docs":["the amount of upgrade material that is consumed by a ship per second while upgrading a Starbase"],"type":"u32"}]}},{"name":"CargoStatsUnpacked","docs":["Unpacked version of [`CargoStats`]"],"type":{"kind":"struct","fields":[{"name":"cargoCapacity","docs":["the capacity of the ships cargo hold"],"type":"u32"},{"name":"fuelCapacity","docs":["the capacity of the ships fuel tank"],"type":"u32"},{"name":"ammoCapacity","docs":["the capacity of the ships ammo bank"],"type":"u32"},{"name":"ammoConsumptionRate","docs":["the amount of ammo consumed per second by the ship when doing non-combat activities e.g. mining"],"type":"u32"},{"name":"foodConsumptionRate","docs":["the amount of food consumed per second by the ship when doing non-combat activities e.g. mining"],"type":"u32"},{"name":"miningRate","docs":["the amount of resources that can be mined by a ship per second"],"type":"u32"},{"name":"upgradeRate","docs":["the amount of upgrade material that is consumed by a ship per second while upgrading a Starbase"],"type":"u32"}]}},{"name":"CargoToGameInput","docs":["Struct for data input to `DepositCargoToGame`"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["cargo amount"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"CloseDisbandedFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"Crafting","docs":["Variables for the Crafting program"],"type":{"kind":"struct","fields":[{"name":"domain","docs":["The crafting domain account"],"type":"publicKey"}]}},{"name":"CreateFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Number of ships to add to the fleet"],"type":"u8"},{"name":"fleetLabel","docs":["the fleet label"],"type":{"array":["u8",32]}},{"name":"shipEscrowIndex","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`"],"type":"u32"},{"name":"cargoHoldSeeds","docs":["cargo hold seeds"],"type":{"array":["u8",32]}},{"name":"fuelTankSeeds","docs":["fuel tank seeds"],"type":{"array":["u8",32]}},{"name":"ammoBankSeeds","docs":["ammo bank seeds"],"type":{"array":["u8",32]}},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"DepositCargoToFleetInput","docs":["Struct for data input to `DepositCargoToFleet`"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["cargo amount"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"DeregisterSurveyDataUnitTrackerInput","docs":["Struct for data input that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"DisbandFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"DisbandedFleetToEscrowInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Number of ships to add to the fleet"],"type":"u16"},{"name":"shipEscrowIndex","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`"],"type":{"option":"u32"}},{"name":"fleetShipInfoIndex","docs":["Index of `FleetShipsInfo` in remaining data of `FleetShips`"],"type":"u32"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"FactionsStarbaseLevelInfo","docs":["`Starbase` levels discriminated by faction"],"type":{"kind":"struct","fields":[{"name":"mud","docs":["Mud Starbase Levels Info"],"type":{"array":[{"defined":"StarbaseLevelInfo"},7]}},{"name":"oni","docs":["Oni Starbase Levels Info"],"type":{"array":[{"defined":"StarbaseLevelInfo"},7]}},{"name":"ustur","docs":["Ustur Starbase Levels Info"],"type":{"array":[{"defined":"StarbaseLevelInfo"},7]}}]}},{"name":"FleetInfo","docs":["Variables for the Fleet program"],"type":{"kind":"struct","fields":[{"name":"starbaseLevels","docs":["`Starbase` levels discriminated by faction"],"type":{"defined":"FactionsStarbaseLevelInfo"}},{"name":"fleetsLpModifier","docs":["The fleets account registered as a modifier for LP in the Points program"],"type":{"defined":"FleetsPointModifier"}},{"name":"fleetsXpModifier","docs":["The fleets account registered as a modifier for XP in the Points program"],"type":{"defined":"FleetsPointModifier"}},{"name":"maxFleetSize","docs":["Maximum `Fleet` size allowed"],"type":"u32"}]}},{"name":"FleetInput","docs":["Struct for data input to Update fleet settings"],"type":{"kind":"struct","fields":[{"name":"starbaseLevelInfoArray","docs":["`Starbase` Level Info array"],"type":{"option":{"vec":{"defined":"StarbaseLevelInfoArrayInput"}}}},{"name":"maxFleetSize","docs":["Maximum `Fleet` size allowed"],"type":{"option":"u32"}},{"name":"fleetsLpModifierBump","docs":["The bump for the `FleetsLPModifier` account"],"type":{"option":"u8"}},{"name":"fleetsXpModifierBump","docs":["The bump for the `FleetsXPModifier` account"],"type":{"option":"u8"}}]}},{"name":"FleetShipsInfo","docs":["Struct that represents info on a single ship type in a fleet"],"type":{"kind":"struct","fields":[{"name":"ship","docs":["The `Ship` account address"],"type":"publicKey"},{"name":"amount","docs":["The `Ship` token amount in escrow"],"type":"u64"},{"name":"updateId","docs":["The update id for the `Ship`"],"type":"u64"}]}},{"name":"FleetStarbaseUpgradeState","docs":["The upgrade start state for a fleet"],"type":{"kind":"enum","variants":[{"name":"NotFullyFilled"},{"name":"Started"},{"name":"Burning"}]}},{"name":"FleetsPointModifier","docs":["The fleets account registered as a modifier in the Points program"],"type":{"kind":"struct","fields":[{"name":"pubkey","docs":["`FleetsPointModifier` Pubkey"],"type":"publicKey"},{"name":"bump","docs":["`FleetsPointModifier` bump"],"type":"u8"}]}},{"name":"ForcedDisbandFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"fleetShipInfoIndex","docs":["Index of `FleetShipsInfo` in remaining data of `FleetShips`"],"type":"u32"}]}},{"name":"Idle","docs":["The data for the [`FleetStateData::Idle`](crate::state_machine::FleetStateData::Idle) state"],"type":{"kind":"struct","fields":[{"name":"sector","docs":["The star system the fleet is in"],"type":{"array":["i64",2]}}]}},{"name":"IdleToRespawnInput","docs":["Struct for data input to initialize an `IdleToRespawn` Ix"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["index of the key in the player profile"],"type":"u16"}]}},{"name":"InitGameStateInput","docs":["Struct for data input to `InitGameState`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"KeyIndexInput","docs":["Struct for data input that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"Levers","docs":["Global levers"],"type":{"kind":"struct","fields":[{"name":"l0ResourcesScalarMultiplication","docs":["global lever that scales the quantity of resources. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l1EmissionsMainBreaker","docs":["global lever which impacts the distribution of effective emissions. Units are 10000ths. Valid output values >= 0."],"type":"u64"},{"name":"l2SystemRichnessEmissions","docs":["global lever which impacts the effect that system richness has on resource emissions. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l3ShipSizeWeight","docs":["global lever which impacts the effect that ship size has on resource emissions. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l4ResourceHardness","docs":["global lever which impacts the effect that resource hardness has on resource emissions. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l5FuelWarpBreaker","docs":["Module wide lever that directly impacts the nominal cost of warping. Units are 10000ths. Valid output values > 1."],"type":"u64"},{"name":"l6FuelPlanetBreaker","docs":["Module wide lever that directly impacts the nominal cost of planet exit. Units are 10000ths. Valid output values > 1."],"type":"u64"},{"name":"l7FuelRefinementEfficiency","docs":["Module wide lever that impacts refinement from R20 (hydrogen) to R4 (fuel). Units are 10000ths. Valid output values > 1."],"type":"u64"},{"name":"l8MiningFoodBreaker","docs":["Module wide lever that directly impacts the nominal cost of mining. Units are 10000ths. Valid output values between 0 and 1."],"type":"u64"},{"name":"l10FoodRefinementEfficiency","docs":["Module wide lever that impacts refinement from R20 (organics) to R4 (food). Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l11OrganicsScalarMultiplication","docs":["Module specific lever that scales the quantity of organics in the economy."],"type":"u64"},{"name":"l16FuelCombatBreaker","docs":["Module wide lever that directly impacts the nominal cost of combat. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"l21FuelSubwarpBreaker","docs":["Module wide lever that directly impacts the nominal cost of subwarp mvmt. Units are 10000ths. Valid output values > 0."],"type":"u64"},{"name":"baseEmissionsBySizeUtil","docs":["Set of derived sub-levers used in `calculate_base_emissions()`.","Math formula: `l1_emissions_main_breaker.powf(l3_ship_size_weight * ship_size)`"],"type":{"defined":"BaseEmissionsBySizeUtil"}}]}},{"name":"LeversInput","docs":["Struct for data input to Update levers"],"type":{"kind":"struct","fields":[{"name":"l0ResourcesScalarMultiplication","docs":["global lever that scales the quantity of resources."],"type":{"option":"u64"}},{"name":"l1EmissionsMainBreaker","docs":["global lever which impacts the distribution of effective emissions."],"type":{"option":"u64"}},{"name":"l2SystemRichnessEmissions","docs":["global lever which impacts the effect that system richness has on resource emissions."],"type":{"option":"u64"}},{"name":"l3ShipSizeWeight","docs":["global lever which impacts the effect that ship size has on resource emissions."],"type":{"option":"u64"}},{"name":"l4ResourceHardness","docs":["global lever which impacts the effect that resource hardness has on resource emissions."],"type":{"option":"u64"}},{"name":"l5FuelWarpBreaker","docs":["Module wide lever that directly impacts the nominal cost of warping"],"type":{"option":"u64"}},{"name":"l6FuelPlanetBreaker","docs":["Module wide lever that directly impacts the nominal cost of planet exit"],"type":{"option":"u64"}},{"name":"l7FuelRefinementEfficiency","docs":["Module wide lever that impacts refinement from R20 (hydrogen) to R4 (fuel)"],"type":{"option":"u64"}},{"name":"l8MiningFoodBreaker","docs":["Module wide lever that directly impacts the nominal cost of mining. (Value between 0 and 1)"],"type":{"option":"u64"}},{"name":"l10FoodRefinementEfficiency","docs":["Module wide lever that impacts refinement from R20 (organics) to R4 (food)"],"type":{"option":"u64"}},{"name":"l11OrganicsScalarMultiplication","docs":["Module specific lever that scales the quantity of organics in the economy."],"type":{"option":"u64"}},{"name":"l16FuelCombatBreaker","docs":["Module wide lever that directly impacts the nominal cost of combat. Units are 10000ths. Valid output values > 0."],"type":{"option":"u64"}},{"name":"l21FuelSubwarpBreaker","docs":["Module wide lever that directly impacts the nominal cost of subwarp mvmt. Units are 10000ths. Valid output values > 0."],"type":{"option":"u64"}}]}},{"name":"LocationType","docs":["Represents different types of locations that a `Resource` might be found"],"type":{"kind":"enum","variants":[{"name":"Planet"}]}},{"name":"ManageGameInput","docs":["Struct for data input to managing Game accounts"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"MineAsteroid","docs":["The data for the [`FleetStateData::MineAsteroid`](crate::state_machine::FleetStateData::MineAsteroid) state"],"type":{"kind":"struct","fields":[{"name":"asteroid","docs":["The `Asteroid` the `Fleet` is mining (Must be an asteroid belt)"],"type":"publicKey"},{"name":"resource","docs":["The `Resource` being mined on the `Asteroid`"],"type":"publicKey"},{"name":"start","docs":["The timestamp at which mining activity started"],"type":"i64"},{"name":"end","docs":["The timestamp at which mining activity stops"],"type":"i64"},{"name":"lastUpdate","docs":["The last time the `Fleet` was updated"],"type":"i64"}]}},{"name":"MineAsteroidToRespawnInput","docs":["Struct for data input for `MineAsteroidToRespawnInput`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"Mints","docs":["Token mints"],"type":{"kind":"struct","fields":[{"name":"atlas","docs":["ATLAS token mint"],"type":"publicKey"},{"name":"polis","docs":["POLIS token mint"],"type":"publicKey"},{"name":"ammo","docs":["ammunition"],"type":"publicKey"},{"name":"food","docs":["food"],"type":"publicKey"},{"name":"fuel","docs":["fuel"],"type":"publicKey"},{"name":"repairKit","docs":["repair kit"],"type":"publicKey"}]}},{"name":"MiscStats","docs":["A ships miscellaneous stats"],"type":{"kind":"struct","fields":[{"name":"crew","docs":["Number of crew in the ship"],"type":"u64"},{"name":"respawnTime","docs":["the time it takes the ship to respawn"],"type":"u16"},{"name":"scanCoolDown","docs":["the time it takes the ship to be able to scan again after scanning"],"type":"u16"},{"name":"scanRepairKitAmount","docs":["the amount of food required to do a scan"],"type":"u32"}]}},{"name":"MiscStatsUnpacked","docs":["Unpacked version of [`MiscStats`]"],"type":{"kind":"struct","fields":[{"name":"crew","docs":["Number of crew in the ship"],"type":"u64"},{"name":"respawnTime","docs":["the time it takes the ship to respawn"],"type":"u16"},{"name":"scanCoolDown","docs":["the time it takes the ship to be able to scan again after scanning"],"type":"u16"},{"name":"scanRepairKitAmount","docs":["the amount of food required to do a scan"],"type":"u32"}]}},{"name":"MiscVariables","docs":["Miscellaneous game state variables"],"type":{"kind":"struct","fields":[{"name":"warpLaneFuelCostReduction","docs":["Percentage by which the warp lane movement type reduces warp fuel cost"],"type":"i32"},{"name":"respawnFee","docs":["Respawn fee; You cannot enter into the respawning state without paying this fee","Since ATLAS has 8 decimal places, units are in the smallest value of ATLAS possible."],"type":"u64"}]}},{"name":"MiscVariablesInput","docs":["Struct for data input to update miscellaneous settings"],"type":{"kind":"struct","fields":[{"name":"warpLaneFuelCostReduction","docs":["Percentage by which the warp lane movement type reduces warp fuel cost"],"type":{"option":"i32"}},{"name":"respawnFee","docs":["Respawn fee, charged in ATLAS"],"type":{"option":"u64"}}]}},{"name":"MoveSubwarp","docs":["The data for the [`FleetStateData::MoveSubwarp`] state"],"type":{"kind":"struct","fields":[{"name":"fromSector","docs":["The sector the fleet is coming from"],"type":{"array":["i64",2]}},{"name":"toSector","docs":["The sector the fleet is going to"],"type":{"array":["i64",2]}},{"name":"currentSector","docs":["The sector the fleet is currently in"],"type":{"array":["i64",2]}},{"name":"departureTime","docs":["When the fleet started subwarp"],"type":"i64"},{"name":"arrivalTime","docs":["When the fleet will finish subwarp"],"type":"i64"},{"name":"fuelExpenditure","docs":["The fuel cost of the subwarp"],"type":"u64"},{"name":"lastUpdate","docs":["The last update time"],"type":"i64"}]}},{"name":"MoveWarp","docs":["The data for the [`FleetStateData::MoveWarp`] state"],"type":{"kind":"struct","fields":[{"name":"fromSector","docs":["The star system the fleet is coming from"],"type":{"array":["i64",2]}},{"name":"toSector","docs":["The star system the fleet is going to"],"type":{"array":["i64",2]}},{"name":"warpStart","docs":["When the fleet started warping"],"type":"i64"},{"name":"warpFinish","docs":["When the warp will end"],"type":"i64"}]}},{"name":"MovementStats","docs":["A ships movement stats"],"type":{"kind":"struct","fields":[{"name":"subwarpSpeed","docs":["the amount of distance that the ship can cover in one second while sub-warping"],"type":"u32"},{"name":"warpSpeed","docs":["the amount of distance that the ship can cover in one second while warping"],"type":"u32"},{"name":"maxWarpDistance","docs":["the max distance that the ship can warp"],"type":"u16"},{"name":"warpCoolDown","docs":["the time it takes the ship to be able to warp again after a warp"],"type":"u16"},{"name":"subwarpFuelConsumptionRate","docs":["the amount of fuel consumed by the ship when sub-warp moving"],"type":"u32"},{"name":"warpFuelConsumptionRate","docs":["the amount of fuel consumed by the ship when warp moving"],"type":"u32"},{"name":"planetExitFuelAmount","docs":["the amount of fuel required to exit a planet"],"type":"u32"}]}},{"name":"MovementStatsUnpacked","docs":["Unpacked version of [`MovementStats`]"],"type":{"kind":"struct","fields":[{"name":"subwarpSpeed","docs":["the amount of distance that the ship can cover in one second while sub-warping"],"type":"u32"},{"name":"warpSpeed","docs":["the amount of distance that the ship can cover in one second while warping"],"type":"u32"},{"name":"maxWarpDistance","docs":["the max distance that the ship can warp"],"type":"u16"},{"name":"warpCoolDown","docs":["the time it takes the ship to be able to warp again after a warp"],"type":"u16"},{"name":"subwarpFuelConsumptionRate","docs":["the amount of fuel consumed by the ship when sub-warp moving"],"type":"u32"},{"name":"warpFuelConsumptionRate","docs":["the amount of fuel consumed by the ship when warp moving"],"type":"u32"},{"name":"planetExitFuelAmount","docs":["the amount of fuel required to exit a planet"],"type":"u32"}]}},{"name":"OptionalNonSystemPubkey","docs":["A pubkey sized option that is none if set to the system program."],"type":{"kind":"struct","fields":[{"name":"key","type":"publicKey"}]}},{"name":"PlanetType","docs":["Represents different types a `Planet` could be"],"type":{"kind":"enum","variants":[{"name":"Terrestrial"},{"name":"Volcanic"},{"name":"Barren"},{"name":"AsteroidBelt"},{"name":"GasGiant"},{"name":"IceGiant"},{"name":"Dark"}]}},{"name":"Points","docs":["Variables for the Points program"],"type":{"kind":"struct","fields":[{"name":"xpPointsCategory","docs":["Represents the points category to use for XP (experience points)"],"type":"publicKey"},{"name":"lpPointsCategory","docs":["Represents the points category to use for LP (loyalty points)"],"type":"publicKey"}]}},{"name":"RegisterFleetsPointsModifierInput","docs":["Struct for data input to register a points modifier for fleets program"],"type":{"kind":"struct","fields":[{"name":"canIncrement","docs":["The modifier can increment points"],"type":"bool"},{"name":"canDecrement","docs":["The modifier can decrement points"],"type":"bool"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"},{"name":"pointsCategory","docs":["The points category of the modifier (XP/LP)"],"type":"u8"}]}},{"name":"RegisterMineItemInput","docs":["Struct for data input to Register a Resource"],"type":{"kind":"struct","fields":[{"name":"name","docs":["The name of the `MineItem`"],"type":{"array":["u8",64]}},{"name":"resourceHardness","docs":["How hard it is to mine this item"],"type":"u16"},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"RegisterPlanetInput","docs":["Struct for data input to Register Planet"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Planet` name"],"type":{"array":["u8",64]}},{"name":"size","docs":["`Planet` size"],"type":"u64"},{"name":"maxHp","docs":["`Planet` max health"],"type":"u64"},{"name":"subCoordinates","docs":["`Planet` sub_coordinates"],"type":{"array":["i64",2]}},{"name":"planetType","docs":["`Planet` type"],"type":"u8"},{"name":"position","docs":["`Planet` position"],"type":"u8"},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"RegisterResourceInput","docs":["Struct for data input to Register a Resource"],"type":{"kind":"struct","fields":[{"name":"locationType","docs":["`Resource` location type"],"type":"u8"},{"name":"systemRichness","docs":["`Resource` `system_richness`"],"type":"u16"},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"RegisterShipInput","docs":["Struct for data input to Register Ship"],"type":{"kind":"struct","fields":[{"name":"name","docs":["The `Ship` name/label"],"type":{"array":["u8",64]}},{"name":"sizeClass","docs":["the ships size class"],"type":{"defined":"SizeClass"}},{"name":"stats","docs":["The stats for the ship"],"type":{"defined":"ShipStatsUnpacked"}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"},{"name":"isActive","docs":["Whether the ship is initialized to active (`update_id == current_update_id`)"],"type":"bool"}]}},{"name":"RegisterStarInput","docs":["Struct for data input to Register Star"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Star` name"],"type":{"array":["u8",64]}},{"name":"size","docs":["`Star` size"],"type":"u64"},{"name":"subCoordinates","docs":["`Star` sub_coordinates"],"type":{"array":["i64",2]}},{"name":"starType","docs":["`Star` type"],"type":"u8"},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"RegisterStarbaseInput","docs":["Struct for data input to Register `Starbase`"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Starbase` name"],"type":{"array":["u8",64]}},{"name":"subCoordinates","docs":["`Starbase` coordinates"],"type":{"array":["i64",2]}},{"name":"starbaseLevelIndex","docs":["The index representing the level of the `Starbase` in the game variables."],"type":"u8"},{"name":"faction","docs":["The `Starbase` faction"],"type":"u8"},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"RegisterStarbaseInputUnpacked","docs":["Unpacked version of [`RegisterStarbaseInput`]"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Starbase` name"],"type":{"array":["u8",64]}},{"name":"subCoordinates","docs":["`Starbase` coordinates"],"type":{"array":["i64",2]}},{"name":"starbaseLevelIndex","docs":["The index representing the level of the `Starbase` in the game variables."],"type":"u8"},{"name":"faction","docs":["The `Starbase` faction"],"type":"u8"},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"RegisterSurveyDataUnitTrackerInput","docs":["Struct for data input to Register SurveyDataUnitTracker"],"type":{"kind":"struct","fields":[{"name":"limit","docs":["The global limit on how many SDUs can be found in a `MAX_SECONDS` second period"],"type":"u32"},{"name":"scanCooldown","docs":["The amount of time that must go by before someone can scan a sector again"],"type":"u16"},{"name":"probability","docs":["The chance that a player gets an SDU on a legitimate scan, this is meant to be a percentage"],"type":"u16"},{"name":"max","docs":["The max number of SDUs that can be found while scanning"],"type":"u16"},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"RemoveShipEscrowInput","docs":["Struct for data input for `RemoveShipEscrow`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Amount of `Ship` tokens to transfer from escrow"],"type":"u64"},{"name":"permissionKeyIndex","docs":["the index of the `ProfileKey` in `Profile` with required permissions"],"type":"u16"},{"name":"shipEscrowIndex","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`"],"type":"u32"}]}},{"name":"Respawn","docs":["The data for the [`FleetStateData::Respawn`](crate::state_machine::FleetStateData::Respawn) state"],"type":{"kind":"struct","fields":[{"name":"sector","docs":["The star system the fleet was in when it entered the `Respawn` state"],"type":{"array":["i64",2]}},{"name":"start","docs":["The time `Respawn` started"],"type":"i64"}]}},{"name":"RespawnToLoadingBayInput","docs":["Struct for data input to `RespawnToLoadingBay`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"RiskZoneData","docs":["`RiskZone` center and radius"],"type":{"kind":"struct","fields":[{"name":"center","docs":["Risk zone center"],"type":{"array":["i64",2]}},{"name":"radius","docs":["Risk zone radius"],"type":"u64"}]}},{"name":"RiskZoneDataUnpacked","docs":["Unpacked version of [`RiskZoneData`]"],"type":{"kind":"struct","fields":[{"name":"center","docs":["Risk zone center"],"type":{"array":["i64",2]}},{"name":"radius","docs":["Risk zone radius"],"type":"u64"}]}},{"name":"RiskZonesData","docs":["[`RiskZoneData`] for [`RiskZones`]"],"type":{"kind":"struct","fields":[{"name":"mudSecurityZone","docs":["Mud security zone"],"type":{"defined":"RiskZoneData"}},{"name":"oniSecurityZone","docs":["Oni security zone"],"type":{"defined":"RiskZoneData"}},{"name":"usturSecurityZone","docs":["Ustur security zone"],"type":{"defined":"RiskZoneData"}},{"name":"highRiskZone","docs":["High risk zone"],"type":{"defined":"RiskZoneData"}},{"name":"mediumRiskZone","docs":["Medium risk zone"],"type":{"defined":"RiskZoneData"}}]}},{"name":"RiskZonesDataUnpacked","docs":["Unpacked version of [`RiskZonesData`]"],"type":{"kind":"struct","fields":[{"name":"mudSecurityZone","docs":["Mud security zone"],"type":{"defined":"RiskZoneData"}},{"name":"oniSecurityZone","docs":["Oni security zone"],"type":{"defined":"RiskZoneData"}},{"name":"usturSecurityZone","docs":["Ustur security zone"],"type":{"defined":"RiskZoneData"}},{"name":"highRiskZone","docs":["High risk zone"],"type":{"defined":"RiskZoneData"}},{"name":"mediumRiskZone","docs":["Medium risk zone"],"type":{"defined":"RiskZoneData"}}]}},{"name":"ScanForSurveyDataUnitsInput","docs":["Struct for data input to Scan For Survey Data Units"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["The index of the key in the player profile"],"type":"u16"}]}},{"name":"SectorConnection","docs":["Connection between sectors"],"type":{"kind":"struct","fields":[{"name":"connectionSector","docs":["The sector connected to"],"type":"publicKey"},{"name":"subCoordinates","docs":["The location of the connection"],"type":{"array":["i64",2]}},{"name":"flags","docs":["Connection flags"],"type":"u8"}]}},{"name":"SectorRing","docs":["Represents the orbital position of a `Planet` in the `Sector`"],"type":{"kind":"enum","variants":[{"name":"Inner"},{"name":"Mid"},{"name":"Outer"}]}},{"name":"ShipCounts","docs":["Ship counts for a fleet."],"type":{"kind":"struct","fields":[{"name":"total","docs":["The total number of ships in the fleet."],"type":"u32"},{"name":"updated","docs":["Used when updating a fleet.","Value is 0 when fleet update is in progress"],"type":"u32"},{"name":"xxSmall","docs":["The number of xx small ships in the fleet."],"type":"u16"},{"name":"xSmall","docs":["The number of x small ships in the fleet."],"type":"u16"},{"name":"small","docs":["The number of small ships in the fleet."],"type":"u16"},{"name":"medium","docs":["The number of medium ships in the fleet."],"type":"u16"},{"name":"large","docs":["The number of large ships in the fleet."],"type":"u16"},{"name":"capital","docs":["The number of capital ships in the fleet."],"type":"u16"},{"name":"commander","docs":["The number of commander ships in the fleet."],"type":"u16"},{"name":"titan","docs":["The number of titan ships in the fleet."],"type":"u16"}]}},{"name":"ShipCountsUnpacked","docs":["Unpacked version of [`ShipCounts`]"],"type":{"kind":"struct","fields":[{"name":"total","docs":["The total number of ships in the fleet."],"type":"u32"},{"name":"updated","docs":["Used when updating a fleet.","Value is 0 when fleet update is in progress"],"type":"u32"},{"name":"xxSmall","docs":["The number of xx small ships in the fleet."],"type":"u16"},{"name":"xSmall","docs":["The number of x small ships in the fleet."],"type":"u16"},{"name":"small","docs":["The number of small ships in the fleet."],"type":"u16"},{"name":"medium","docs":["The number of medium ships in the fleet."],"type":"u16"},{"name":"large","docs":["The number of large ships in the fleet."],"type":"u16"},{"name":"capital","docs":["The number of capital ships in the fleet."],"type":"u16"},{"name":"commander","docs":["The number of commander ships in the fleet."],"type":"u16"},{"name":"titan","docs":["The number of titan ships in the fleet."],"type":"u16"}]}},{"name":"ShipSizes","docs":["Ship sizes."],"type":{"kind":"struct","fields":[{"name":"xxSmall","docs":["The size of xx small ships"],"type":"u8"},{"name":"xSmall","docs":["The size of x small ships"],"type":"u8"},{"name":"small","docs":["The size of small ships"],"type":"u8"},{"name":"medium","docs":["The size of medium ships"],"type":"u8"},{"name":"large","docs":["The size of large ships"],"type":"u8"},{"name":"capital","docs":["The size of capital ships"],"type":"u8"},{"name":"commander","docs":["The size of commander ships"],"type":"u8"},{"name":"titan","docs":["The size of titan ships"],"type":"u8"}]}},{"name":"ShipStats","docs":["A ships stats"],"type":{"kind":"struct","fields":[{"name":"movementStats","docs":["Movement stats for the ship"],"type":{"defined":"MovementStats"}},{"name":"cargoStats","docs":["Cargo stats for the ship"],"type":{"defined":"CargoStats"}},{"name":"miscStats","docs":["Miscellaneous stats for the ship"],"type":{"defined":"MiscStats"}}]}},{"name":"ShipStatsUnpacked","docs":["Unpacked version of [`ShipStats`]"],"type":{"kind":"struct","fields":[{"name":"movementStats","docs":["Movement stats for the ship"],"type":{"defined":"MovementStats"}},{"name":"cargoStats","docs":["Cargo stats for the ship"],"type":{"defined":"CargoStats"}},{"name":"miscStats","docs":["Miscellaneous stats for the ship"],"type":{"defined":"MiscStats"}}]}},{"name":"SizeClass","docs":["Represents different types of Ships"],"type":{"kind":"enum","variants":[{"name":"XxSmall"},{"name":"XSmall"},{"name":"Small"},{"name":"Medium"},{"name":"Large"},{"name":"Capital"},{"name":"Commander"},{"name":"Titan"}]}},{"name":"StarType","docs":["Represents different types of Stars"],"type":{"kind":"enum","variants":[{"name":"WhiteDwarf"},{"name":"RedDwarf"},{"name":"Solar"},{"name":"HotBlue"},{"name":"RedGiant"}]}},{"name":"StarbaseCancelCraftingProcessInput","docs":["Struct for data input to cancel a `CraftingProcess`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseClaimCraftingNonConsumablesInput","docs":["Struct for data input to Claim Crafting Process Non-consumable inputs"],"type":{"kind":"struct","fields":[{"name":"ingredientIndex","docs":["the index of the recipe output"],"type":"u16"}]}},{"name":"StarbaseClaimCraftingOutputInput","docs":["Struct for data input to close a `CraftingProcess`"],"type":{"kind":"struct","fields":[{"name":"ingredientIndex","docs":["the index of the recipe output"],"type":"u16"}]}},{"name":"StarbaseCloseCraftingProcessInput","docs":["Struct for data input to close a `CraftingProcess`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseCreateCargoPodInput","docs":["Struct for data input to `StarbaseCreateCargoPod`"],"type":{"kind":"struct","fields":[{"name":"podSeeds","docs":["cargo pod seeds"],"type":{"array":["u8",32]}},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseCreateCraftingProcessInput","docs":["Struct for data input to create a `CraftingProcess`"],"type":{"kind":"struct","fields":[{"name":"craftingId","docs":["crafting id"],"type":"u64"},{"name":"recipeCategoryIndex","docs":["the index of the recipes category"],"type":"u16"},{"name":"quantity","docs":["quantity of outputs to craft"],"type":"u64"},{"name":"numCrew","docs":["number of crew members to use for this crafting process"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseDepositCraftingIngredientInput","docs":["Struct for data input to deposit an ingredient"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["the amount of ingredient to deposit"],"type":"u64"},{"name":"ingredientIndex","docs":["the index of the recipe ingredient"],"type":"u16"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseLevelInfo","docs":["Information associated with `Starbase` levels"],"type":{"kind":"struct","fields":[{"name":"recipeForUpgrade","docs":["The crafting recipe required to upgrade a `Starbase` to this level"],"type":"publicKey"},{"name":"recipeCategoryForLevel","docs":["The crafting recipe category enabled for crafting at a `Starbase` of this level."],"type":"publicKey"},{"name":"hp","docs":["The `Starbase` health points for this level."],"type":"u64"},{"name":"sp","docs":["The `Starbase` shield points for this level."],"type":"u64"},{"name":"sectorRingAvailable","docs":["The planet position `Ring` available for this level"],"type":"u8"},{"name":"warpLaneMovementFee","docs":["Fee charged for the warp lane movement type which is meant to be charged in ATLAS","Since ATLAS has 8 decimal places, units are in the smallest value of ATLAS possible."],"type":"u64"}]}},{"name":"StarbaseLevelInfoArrayInput","docs":["Struct for data input to Update Starbase Level Settings"],"type":{"kind":"struct","fields":[{"name":"level","docs":["The level of the `Starbase`."],"type":"u8"},{"name":"faction","docs":["The `Starbase` faction."],"type":"u8"},{"name":"hp","docs":["The `Starbase` health points for this level."],"type":"u64"},{"name":"sp","docs":["The `Starbase` shield points for this level."],"type":"u64"},{"name":"sectorRingAvailable","docs":["The planet position `Ring` available for this level"],"type":{"defined":"SectorRing"}},{"name":"warpLaneMovementFee","docs":["Fee charged for the warp lane movement type which is meant to be charged in ATLAS"],"type":"u64"}]}},{"name":"StarbaseLoadingBay","docs":["The data for the [`FleetStateData::StarbaseLoadingBay`] state"],"type":{"kind":"struct","fields":[{"name":"starbase","docs":["The `Starbase` is in the loading bay of"],"type":"publicKey"},{"name":"lastUpdate","docs":["The last time this fleet was updated"],"type":"i64"}]}},{"name":"StarbaseRemoveCargoPodInput","docs":["Struct for data input to `StarbaseRemoveCargoPod`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseStartCraftingProcessInput","docs":["Struct for data input to start a crafting process"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseState","docs":["The state of a `Starbase`."],"type":{"kind":"enum","variants":[{"name":"Active"},{"name":"Destroyed"}]}},{"name":"StarbaseTransferCargoInput","docs":["Struct for data input to `DepositCargoToGame`"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["cargo amount"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StarbaseUpgrade","docs":["The data for the `StarbaseUpgrade` state"],"type":{"kind":"struct","fields":[{"name":"starbase","docs":["The `Starbase` being upgraded"],"type":"publicKey"},{"name":"upgradeState","docs":["[`FleetStarbaseUpgradeState`]"],"type":"u8"},{"name":"startUpgrade","docs":["When the fleet started participation in the `Starbase` upgrade."],"type":"i64"},{"name":"endUpgrade","docs":["When the fleet completes participation in the `Starbase` upgrade. This is a potential end time, constrained by max resources or user decision.","If `upgrade_state` is set to `NotFullyFilled`, this is set to the max duration in upgrading state with the partial ingredients deposited."],"type":"i64"},{"name":"checksum","docs":["used to check if expected inputs have been supplied"],"type":{"array":["u8",16]}}]}},{"name":"StarbaseUpgradeLevelState","docs":["The upgrade state for one SB lvl"],"type":{"kind":"enum","variants":[{"name":"NotStarted"},{"name":"Started"},{"name":"Finished","fields":[{"name":"timestamp","docs":["Timestamp of the upgrade completion"],"type":"i64"}]}]}},{"name":"StarbaseUpgradeTask","docs":["`Starbase` upgrade task item"],"type":{"kind":"struct","fields":[{"name":"fleet","docs":["The `Fleet` Pubkey"],"type":"publicKey"},{"name":"completionTime","docs":["The timestamp at which the fleet completes its contribution to the upgrade"],"type":"i64"}]}},{"name":"StarbaseWithdrawCraftingIngredientInput","docs":["Struct for data input to withdraw an ingredient"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["the amount of ingredient to withdraw"],"type":"u64"},{"name":"ingredientIndex","docs":["the index of the recipe ingredient"],"type":"u16"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StartMiningAsteroidInput","docs":["Struct for data input for `StartMiningAsteroid`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StartSubwarpInput","docs":["Struct for data input to initialize an `SubwarpMovement`"],"type":{"kind":"struct","fields":[{"name":"toSector","docs":["The destination coordinates"],"type":{"array":["i64",2]}},{"name":"keyIndex","docs":["The index of the key in the player profile"],"type":"u16"}]}},{"name":"StopMiningAsteroidInput","docs":["Struct for data input for `StopMiningAsteroidInput`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"StopSubwarpInput","docs":["Struct for data input to stop an `SubwarpMovement`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["The index of the key in the player profile"],"type":"u16"}]}},{"name":"TransferCargoWithinFleetInput","docs":["Struct for data input to `TransferCargoWithinFleet`"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["cargo amount"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"UpdateGameInput","docs":["Struct for data input to Update instruction"],"type":{"kind":"struct","fields":[{"name":"cargo","docs":["Cargo settings"],"type":"u8"},{"name":"crafting","docs":["Crafting settings"],"type":"u8"},{"name":"mints","docs":["Mints"],"type":"u8"},{"name":"vaults","docs":["Vaults"],"type":"u8"},{"name":"points","docs":["Points settings"],"type":"u8"},{"name":"riskZones","docs":["Data for risk zones"],"type":{"option":{"defined":"RiskZonesDataUnpacked"}}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateGameStateInput","docs":["Struct for data input to Update instruction"],"type":{"kind":"struct","fields":[{"name":"fleet","docs":["Fleet settings"],"type":{"option":{"defined":"FleetInput"}}},{"name":"levers","docs":["Levers"],"type":{"option":{"defined":"LeversInput"}}},{"name":"baseEmissionsBySizeUtil","docs":["Set of derived sub-levers used in `calculate_base_emissions()`."],"type":{"option":{"defined":"BaseEmissionsBySizeUtilInput"}}},{"name":"misc","docs":["Miscellaneous settings"],"type":{"option":{"defined":"MiscVariablesInput"}}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateMineItemInput","docs":["Struct for data input to Register a Resource"],"type":{"kind":"struct","fields":[{"name":"name","docs":["The name of the `MineItem`"],"type":{"option":{"array":["u8",64]}}},{"name":"resourceHardness","docs":["How hard it is to mine this item"],"type":{"option":"u16"}},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"UpdatePlanetInput","docs":["Struct for data input to Update Planet"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Planet` name"],"type":{"option":{"array":["u8",64]}}},{"name":"size","docs":["`Planet` size"],"type":{"option":"u64"}},{"name":"maxHp","docs":["`Planet` max_hp"],"type":{"option":"u64"}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateResourceInput","docs":["Struct for data input to Update Resource"],"type":{"kind":"struct","fields":[{"name":"systemRichness","docs":["`Resource` richness"],"type":{"option":"u16"}},{"name":"keyIndex","docs":["the index of the key in the fleet permissions profile"],"type":"u16"}]}},{"name":"UpdateShipEscrowInput","docs":["Struct for data input for `UpdateShipEscrow`"],"type":{"kind":"struct","fields":[{"name":"shipEscrowIndex","docs":["Index of `WrappedShipEscrow` in remaining data of `StarbasePlayer`"],"type":"u32"}]}},{"name":"UpdateShipFleetInput","docs":["Struct for data input for that has `key_index`"],"type":{"kind":"struct","fields":[{"name":"shipAmount","docs":["Number of ships to add to the fleet"],"type":"u16"},{"name":"fleetShipInfoIndex","docs":["Index of `FleetShipsInfo` in remaining data of `FleetShips`"],"type":"u32"}]}},{"name":"UpdateShipInput","docs":["Struct for data input to Update Ship"],"type":{"kind":"struct","fields":[{"name":"name","docs":["The `Ship` name/label"],"type":{"array":["u8",64]}},{"name":"sizeClass","docs":["the ships size class"],"type":{"defined":"SizeClass"}},{"name":"stats","docs":["The stats for the ship"],"type":{"defined":"ShipStatsUnpacked"}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateStarInput","docs":["Struct for data input to Update Star"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Star` name"],"type":{"option":{"array":["u8",64]}}},{"name":"size","docs":["`Star` size"],"type":{"option":"u64"}},{"name":"starType","docs":["`Star` type"],"type":{"option":"u8"}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateStarbaseInput","docs":["Struct for data input to Update `Starbase`"],"type":{"kind":"struct","fields":[{"name":"name","docs":["`Starbase` name"],"type":{"option":{"array":["u8",64]}}},{"name":"subCoordinates","docs":["`Starbase` coordinates"],"type":{"option":{"array":["i64",2]}}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"UpdateSurveyDataUnitTrackerInput","docs":["Struct for data input to Update SurveyDataUnitTracker"],"type":{"kind":"struct","fields":[{"name":"limit","docs":["The global limit on how many SDUs can be found in a `MAX_SECONDS` second period"],"type":{"option":"u32"}},{"name":"scanCooldown","docs":["The amount of time that must go by before someone can scan a sector again"],"type":{"option":"u16"}},{"name":"probability","docs":["The chance that a player gets an SDU on a legitimate scan, this is meant to be a percentage"],"type":{"option":"u16"}},{"name":"max","docs":["The max number of SDUs that can be found while scanning"],"type":{"option":"u16"}},{"name":"keyIndex","docs":["the index of the key in the sector permissions profile"],"type":"u16"}]}},{"name":"Vaults","docs":["Token vaults"],"type":{"kind":"struct","fields":[{"name":"atlas","docs":["ATLAS token mint"],"type":"publicKey"},{"name":"polis","docs":["POLIS token mint"],"type":"publicKey"}]}},{"name":"WarpLaneInput","docs":["Struct for data input to initialize a `WarpLane`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["The index of the key in the player profile"],"type":"u16"},{"name":"toSectorIndex","docs":["Index of the to_sector in `SectorConnections` of the from_sector"],"type":"u16"},{"name":"fromSectorIndex","docs":["Index of the from_sector in `SectorConnections` of the to_sector"],"type":"u16"}]}},{"name":"WarpToCoordinateInput","docs":["Struct for data input to initialize a `WarpToCoordinate`"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["The index of the key in the player profile"],"type":"u16"},{"name":"toSector","docs":["The destination coordinates"],"type":{"array":["i64",2]}}]}},{"name":"WithdrawCargoFromFleetInput","docs":["Struct for data input to `WithdrawCargoFromFleet`"],"type":{"kind":"struct","fields":[{"name":"amount","docs":["cargo amount"],"type":"u64"},{"name":"keyIndex","docs":["the index of the key in the player profile"],"type":"u16"}]}},{"name":"WrappedShipEscrow","docs":["Wrapped `Ship` escrow info"],"type":{"kind":"struct","fields":[{"name":"ship","docs":["The `Ship` account address"],"type":"publicKey"},{"name":"amount","docs":["The `Ship` token amount in escrow"],"type":"u64"},{"name":"updateId","docs":["The update id for the `Ship`"],"type":"u64"}]}}],"errors":[{"code":6000,"name":"IncorrectAdminAddress","msg":"Incorrect admin address."},{"code":6001,"name":"MissingRemainingAccount","msg":"An expected remaining account is missing."},{"code":6002,"name":"NoStargateConnectionsAvailable","msg":"No Stargate connections available."},{"code":6003,"name":"StargatesNotConnected","msg":"The provided Stargates are not connected."},{"code":6004,"name":"InvalidPlanetType","msg":"Invalid Planet Type."},{"code":6005,"name":"InvalidRingType","msg":"Invalid Ring Type."},{"code":6006,"name":"InvalidStarType","msg":"Invalid Star Type."},{"code":6007,"name":"InvalidOrInactiveGame","msg":"Invalid Or Inactive Game"},{"code":6008,"name":"InvalidShipSizeClass","msg":"Invalid Ship Size Class."},{"code":6009,"name":"IncorrectAccountSize","msg":"Incorrect Account Size."},{"code":6010,"name":"UpdateIdMismatch","msg":"The update_id is mismatched."},{"code":6011,"name":"AlreadyActive","msg":"The account is already active."},{"code":6012,"name":"InactiveAccount","msg":"The account is inactive."},{"code":6013,"name":"InvalidGame","msg":"The game account is invalid."},{"code":6014,"name":"InvalidGameState","msg":"The game state account is invalid."},{"code":6015,"name":"InvalidSector","msg":"The sector account is invalid."},{"code":6016,"name":"IncorrectVarsAccountAddress","msg":"Incorrect sage game_id account address."},{"code":6017,"name":"InsufficientFuel","msg":"Insufficient Fuel to complete movement"},{"code":6018,"name":"DistanceGreaterThanMax","msg":"Distance of movement is greater than the allowed maximum"},{"code":6019,"name":"NumericOverflow","msg":"Numeric overflow"},{"code":6020,"name":"InvalidLocationType","msg":"Invalid Location Type."},{"code":6021,"name":"LocationTypeNotSupported","msg":"The provided location type is not supported."},{"code":6022,"name":"IncorrectMineItem","msg":"Incorrect mine item address."},{"code":6023,"name":"IncorrectAuthorityAddress","msg":"Incorrect authority address."},{"code":6024,"name":"IncorrectResourceAddress","msg":"Incorrect resource address."},{"code":6025,"name":"IncorrectMintAuthority","msg":"Incorrect mint authority."},{"code":6026,"name":"MintAuthorityIsNone","msg":"The mint authority should exist."},{"code":6027,"name":"InvalidCurrentFleetState","msg":"The current fleet state is not valid."},{"code":6028,"name":"InvalidCurrentStarbaseState","msg":"The current starbase state is not valid."},{"code":6029,"name":"AuthorityMismatch","msg":"Authority mismatch"},{"code":6030,"name":"MintMismatch","msg":"Mint mismatch"},{"code":6031,"name":"TokenMismatch","msg":"Incorrect token address."},{"code":6032,"name":"OwnerMismatch","msg":"Owner mismatch"},{"code":6033,"name":"GameMismatch","msg":"Game ID mismatch"},{"code":6034,"name":"ProfileMismatch","msg":"Profile mismatch"},{"code":6035,"name":"SagePlayerProfileMismatch","msg":"SagePlayerProfile mismatch"},{"code":6036,"name":"StarbaseMismatch","msg":"Starbase mismatch"},{"code":6037,"name":"FactionMismatch","msg":"Faction mismatch"},{"code":6038,"name":"SeqIdMismatch","msg":"Sequence id mismatch"},{"code":6039,"name":"ShipMismatch","msg":"Ship mismatch"},{"code":6040,"name":"CargoPodMismatch","msg":"Cargo Pod mismatch"},{"code":6041,"name":"PlanetMismatch","msg":"Planet mismatch"},{"code":6042,"name":"MineItemMismatch","msg":"MineItem mismatch"},{"code":6043,"name":"LocationMismatch","msg":"Location mismatch"},{"code":6044,"name":"InvalidEscrowKey","msg":"Escrow key not found in remaining data"},{"code":6045,"name":"InvalidShipAmount","msg":"Insufficient Ship token amount"},{"code":6046,"name":"InvalidShipHangarSpaceAmount","msg":"Insufficient Ship hangar space amount"},{"code":6047,"name":"InvalidCrewAmount","msg":"Invalid crew amount"},{"code":6048,"name":"InvalidState","msg":"Invalid state"},{"code":6049,"name":"InvalidDistance","msg":"Invalid distance"},{"code":6050,"name":"NotAtCentralSpaceStation","msg":"Not at central space station"},{"code":6051,"name":"ShipNotExpected","msg":"The instruction does not expect a ship account"},{"code":6052,"name":"AddressMismatch","msg":"Address mismatch"},{"code":6053,"name":"InvalidSectorConnection","msg":"Invalid sector connection"},{"code":6054,"name":"InvalidStarbaseLevel","msg":"Invalid Starbase level"},{"code":6055,"name":"InvalidStarbaseUpgradeRecipeCategory","msg":"Invalid Starbase upgrade recipe category"},{"code":6056,"name":"HangarUpgradeNotPossible","msg":"Hangar upgrade not Possible"},{"code":6057,"name":"DisbandedFleetNotEmpty","msg":"Disbanded fleet not empty"},{"code":6058,"name":"FaultyMovement","msg":"Faulty movement"},{"code":6059,"name":"IncorrectHandleRawAccount","msg":"Incorrect Account Type for Handle Raw"},{"code":6060,"name":"InsufficientShipCargoCapacity","msg":"Insufficient Ship Cargo Capacity"},{"code":6061,"name":"FleetDoesNotNeedUpdate","msg":"Fleet does not need update"},{"code":6062,"name":"MustDisbandFleet","msg":"Must disband fleet"},{"code":6063,"name":"CannotForceDisbandFleet","msg":"Cannot force-disband fleet"},{"code":6064,"name":"ShipMismatchOrAlreadyUpdated","msg":"Ship mismatch or already updated"},{"code":6065,"name":"ShipAlreadyUpdated","msg":"Ship already updated"},{"code":6066,"name":"InvalidNextShipAddress","msg":"Invalid next ship address"},{"code":6067,"name":"InvalidShipForForcedDisband","msg":"Ship is not valid for forced disband of fleet"},{"code":6068,"name":"InvalidWarpRange","msg":"Warp range exceeded"},{"code":6069,"name":"InvalidIngredient","msg":"Invalid Ingredient"},{"code":6070,"name":"StarbaseUpgradeNotInProgress","msg":"Starbase Upgrade Not in progress"},{"code":6071,"name":"FleetNotInQueue","msg":"Fleet Not in queue"},{"code":6072,"name":"NeedCleanStarbaseUpgradeQueue","msg":"Need to clean Starbase upgrade queue"},{"code":6073,"name":"PlanetNotReachable","msg":"Planet Not Reachable"},{"code":6074,"name":"RespawnNotPossible","msg":"Respawn Not Possible"},{"code":6075,"name":"InvalidMovement","msg":"Cannot enter enemy factions Security Zone"},{"code":6076,"name":"CargoAmountAboveZero","msg":"The Cargo Pod contains a non-zero amount of the Cargo Type"},{"code":6077,"name":"InvalidCargoPod","msg":"The Cargo Pod is invalid"},{"code":6078,"name":"InvalidZoneCoordinates","msg":"Invalid Zone Coordinates"},{"code":6079,"name":"RespawnTimeNotElapsed","msg":"Respawn time not elapsed"},{"code":6080,"name":"ActiveAccount","msg":"The Account is Active"},{"code":6081,"name":"StarbasePlayerMismatch","msg":"Starbase Player mismatch"},{"code":6082,"name":"AlreadyProcessed","msg":"The account has already been processed"},{"code":6083,"name":"InvalidAmount","msg":"The amount is invalid"},{"code":6084,"name":"WarpIsOnCooldown","msg":"Warp is on cooldown"},{"code":6085,"name":"ProgramMismatch","msg":"Program Mismatch"},{"code":6086,"name":"MustBeOnlyInstruction","msg":"Current Instruction Is Not Only Instruction"},{"code":6087,"name":"InvalidTime","msg":"Invalid Time"},{"code":6088,"name":"ScanIsOnCooldown","msg":"Scanning is on cooldown"},{"code":6089,"name":"InvalidFleetSize","msg":"Invalid Fleet Size"},{"code":6090,"name":"InactiveFeature","msg":"The feature is inactive"},{"code":6091,"name":"ZeroShipsAdded","msg":"Zero ships added to fleet"},{"code":6092,"name":"GenericInvalid","msg":"Generic invalid data"}]}'); + // load SAGE program and methods const sageProgramId = new solanaWeb3.PublicKey('SAGEqqFewepDHH6hMDcmWy7yjHPpyKLDnRXKb3Ki8e6'); - const profileIDL = JSON.parse('{"version":"0.7.0","name":"player_profile","instructions":[{"name":"acceptRoleInvitation","accounts":[{"name":"newMember","isMut":false,"isSigner":false,"docs":["The new member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is joining"]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the new member"]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"keyIndexInRoleAccount","type":"u16"},{"name":"keyIndexInMembershipAccount","type":"u16"}]},{"name":"addExistingMemberToRole","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for reallocation."]},{"name":"newMember","isMut":false,"isSigner":false,"docs":["The profile of the member to be added to the role"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which the role belongs to."]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the new member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is joining"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"keyIndexInMembershipAccount","type":"u16"}]},{"name":"addKeys","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the profile."]},{"name":"key","isMut":false,"isSigner":true,"docs":["Key with [`ProfilePermissions::ADD_KEYS`] permission to add keys."]},{"name":"profile","isMut":true,"isSigner":false,"docs":["The profile to add to"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyAddIndex","type":"u16"},{"name":"keyPermissionsIndex","type":"u16"},{"name":"keysToAdd","type":{"vec":{"defined":"AddKeyInput"}}}]},{"name":"adjustAuth","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the profile."]},{"name":"profile","isMut":true,"isSigner":false,"docs":["The profile to create"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"authIndexes","type":{"vec":"u16"}},{"name":"newKeyPermissions","type":{"vec":{"defined":"AddKeyInput"}}},{"name":"removeRange","type":{"array":["u16",2]}},{"name":"newKeyThreshold","type":"u8"}]},{"name":"createProfile","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new profile."]},{"name":"profile","isMut":true,"isSigner":true,"docs":["The profile to create"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyPermissions","type":{"vec":{"defined":"AddKeyInput"}}},{"name":"keyThreshold","type":"u8"}]},{"name":"createRole","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the transaction"]},{"name":"profile","isMut":true,"isSigner":false,"docs":["The [`Profile`] account that the role is being created for"]},{"name":"newRoleAccount","isMut":true,"isSigner":false,"docs":["The role account being created"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"inviteMemberToRole","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new profile."]},{"name":"newMember","isMut":false,"isSigner":false,"docs":["The profile of the user to be added to the role"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which the role belongs to."]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the new member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is joining"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"joinRole","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new profile."]},{"name":"newMember","isMut":false,"isSigner":false,"docs":["The new member joining the role"]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the new member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is joining"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"leaveRole","accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The funder to receive the rent allocation."]},{"name":"member","isMut":false,"isSigner":false,"docs":["The member leaving the role"]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is leaving"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"keyIndexInRoleAccount","type":"u16"},{"name":"keyIndexInMembershipAccount","type":"u16"}]},{"name":"removeKeys","accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The funder for the profile."]},{"name":"key","isMut":false,"isSigner":true,"docs":["Key with [`ProfilePermissions::REMOVE_KEYS`] permission to add keys."]},{"name":"profile","isMut":true,"isSigner":false,"docs":["The profile to remove from"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"keysToRemove","type":{"array":["u16",2]}}]},{"name":"removeMemberFromRole","accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The funder to receive the rent allocation"]},{"name":"member","isMut":false,"isSigner":false,"docs":["The profile of the user to be added to the role"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which the role belongs to."]},{"name":"roleMembershipAccount","isMut":true,"isSigner":false,"docs":["The role membership account for the member"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role which the player is being removed from"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"keyIndexInRoleAccount","type":"u16"},{"name":"keyIndexInMembershipAccount","type":"u16"}]},{"name":"removeRole","accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The funder for the transaction"]},{"name":"profile","isMut":true,"isSigner":false,"docs":["The Profile that the role is being removed from"]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role being removed"]},{"name":"roleNameAccount","isMut":true,"isSigner":false,"docs":["The role name account (if it exists)"]}],"args":[{"name":"roleNameBump","type":"u8"},{"name":"keyIndex","type":"u16"}]},{"name":"setName","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized to change the name."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the name size change."]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile to set the name for."]},{"name":"name","isMut":true,"isSigner":false,"docs":["The name account."]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program."]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"name","type":"bytes"}]},{"name":"setRoleAcceptingMembers","accounts":[{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which owns the role being modified."]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role account to set as accepting members."]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"setRoleAuthorizer","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the name size change."]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile to set the name for."]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role account to set the authorizer for."]},{"name":"authorizer","isMut":false,"isSigner":false,"docs":["The authorizer account to set."]}],"args":[{"name":"keyIndex","type":"u16"}]},{"name":"setRoleName","accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the name size change."]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which the role belongs to"]},{"name":"role","isMut":false,"isSigner":false,"docs":["The role to set the name for."]},{"name":"name","isMut":true,"isSigner":false,"docs":["The name account."]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program."]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"name","type":"bytes"}]},{"name":"setRoleNotAcceptingMembers","accounts":[{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile which owns the role being modified."]},{"name":"roleAccount","isMut":true,"isSigner":false,"docs":["The role account to set as not accepting members."]}],"args":[{"name":"keyIndex","type":"u16"}]}],"accounts":[{"name":"PlayerName","docs":["Stores a players name on-chain."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"profile","docs":["The profile this name is for."],"type":"publicKey"},{"name":"bump","docs":["The bump for this account."],"type":"u8"}]}},{"name":"Profile","docs":["A player profile."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"authKeyCount","docs":["The number of auth keys on the account"],"type":"u16"},{"name":"keyThreshold","docs":["The number of auth keys needed to update the profile."],"type":"u8"},{"name":"nextSeqId","docs":["The next sequence number for a new role."],"type":"u64"},{"name":"createdAt","docs":["When the profile was created."],"type":"i64"}]}},{"name":"ProfileRoleMembership","docs":["A players roles for a given profile","Remaining data contains an unordered list of [`RoleMembership`](RoleMembership) structs"],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"profile","docs":["The Profile this belongs to"],"type":"publicKey"},{"name":"member","docs":["The members profile pubkey"],"type":"publicKey"},{"name":"bump","docs":["PDA bump"],"type":"u8"}]}},{"name":"Role","docs":["A Role associated with a Profile. A Role contains an unordered list of Role Members in its","remaining data which lists all of the members who carry this role."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"profile","docs":["Profile that this role belongs to"],"type":"publicKey"},{"name":"authorizer","docs":["Origin authority of the account"],"type":"publicKey"},{"name":"roleSeqId","docs":["Roles seq_id"],"type":"u64"},{"name":"acceptingNewMembers","docs":["Is role accepting new members"],"type":"u8"},{"name":"bump","docs":["The name of the rank","TODO: Add instruction to use `player-name` as the label","PDA bump"],"type":"u8"}]}}],"types":[{"name":"AddKeyInput","docs":["Struct for adding a key"],"type":{"kind":"struct","fields":[{"name":"scope","docs":["The block of permissions"],"type":"publicKey"},{"name":"expireTime","docs":["The expire time of the key to add"],"type":"i64"},{"name":"permissions","docs":["The permissions for the key"],"type":{"array":["u8",8]}}]}},{"name":"MemberStatus","docs":["Represents potential membership statuses for a player with a role"],"type":{"kind":"enum","variants":[{"name":"Inactive"},{"name":"Active"}]}},{"name":"ProfileKey","docs":["A key on a profile."],"type":{"kind":"struct","fields":[{"name":"key","docs":["The key."],"type":"publicKey"},{"name":"scope","docs":["The key for the permissions."],"type":"publicKey"},{"name":"expireTime","docs":["The expire time for this key.","If `<0` does not expire."],"type":"i64"},{"name":"permissions","docs":["The permissions for the key."],"type":{"array":["u8",8]}}]}},{"name":"RoleMembership","docs":["Represents a members status in a role"],"type":{"kind":"struct","fields":[{"name":"key","docs":["The member or role key associated with this membership"],"type":"publicKey"},{"name":"status","docs":["The members role status"],"type":"u8"}]}}],"errors":[{"code":6000,"name":"KeyIndexOutOfBounds","msg":"Key index out of bounds"},{"code":6001,"name":"ProfileMismatch","msg":"Profile did not match profile key"},{"code":6002,"name":"KeyMismatch","msg":"Key did not match profile key"},{"code":6003,"name":"ScopeMismatch","msg":"Scope did not match profile scope"},{"code":6004,"name":"KeyExpired","msg":"Key expired"},{"code":6005,"name":"KeyMissingPermissions","msg":"Key is missing permissions"},{"code":6006,"name":"PermissionsMismatch","msg":"Permissions dont match available"},{"code":6007,"name":"AuthKeyCannotExpire","msg":"Auth keys cannot expire"},{"code":6008,"name":"AuthKeyMustSign","msg":"New auth keys must be signers"},{"code":6009,"name":"DuplicateAuthKey","msg":"Duplicate key when adjusting auth keys"},{"code":6010,"name":"RoleAuthorityAlreadySet","msg":"Role authority has already been set"},{"code":6011,"name":"RoleNotAcceptingMembers","msg":"Role is not accepting new members"},{"code":6012,"name":"RoleMembershipMismatch","msg":"Role membership is not as expected"},{"code":6013,"name":"RoleLimitExceeded","msg":"Role limit exceeded"},{"code":6014,"name":"RoleHasMembers","msg":"Cannot remove role with members"},{"code":6015,"name":"FeatureNotImplemented","msg":"This feature is not yet support"}]}'); + const sageIDL = await BrowserAnchor.anchor.Program.fetchIdl(sageProgramId, anchorProvider); + const sageProgram = new BrowserAnchor.anchor.Program(sageIDL, sageProgramId, anchorProvider); + console.debug('sageProgram: ', sageProgram); + const [sageGameAcct] = await sageProgram.account.game.all(); + console.debug('sageGameAcct: ', sageGameAcct); + const [sageSDUTrackerAcct] = await sageProgram.account.surveyDataUnitTracker.all(); + console.debug('sageSDUTrackerAcct: ', sageSDUTrackerAcct); + + // load player profile and methods const profileProgramId = new solanaWeb3.PublicKey('pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9'); - const cargoIDL = JSON.parse('{"version":"0.1.0","name":"cargo","docs":["The `cargo` program"],"instructions":[{"name":"addCargo","docs":["Adds cargo to a [`CargoPod`](state::CargoPod).","Requires the authority to sign."],"accounts":[{"name":"authority","isMut":false,"isSigner":true,"docs":["Authority for the cargo pod"]},{"name":"signerOriginAccount","isMut":false,"isSigner":true,"docs":["Signer for Cargo Token Transfer"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"originTokenAccount","isMut":true,"isSigner":false,"docs":["The Origin Token Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"cargoAmount","type":"u64"}]},{"name":"closeCargoPod","docs":["Closes the [`CargoPod`](state::CargoPod) if it has no open token accounts.","Requires the authority to sign."],"accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The account to return the rent"]},{"name":"authority","isMut":false,"isSigner":true,"docs":["The authority for the pod account"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system Program"]}],"args":[]},{"name":"closeTokenAccount","docs":["Closes and burns any excess tokens in a given token account within a [`CargoPod`](state::CargoPod).","Requires the authority to sign."],"accounts":[{"name":"funder","isMut":true,"isSigner":false,"docs":["The account to return the rent"]},{"name":"authority","isMut":false,"isSigner":true,"docs":["The authority for [CargoPod] account"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"mint","isMut":true,"isSigner":false,"docs":["The Token Mint"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["The Token Program"]}],"args":[]},{"name":"consumeCargo","docs":["Consumes cargo from a [`CargoPod`](state::CargoPod), burning the amount.","Requires the authority to sign."],"accounts":[{"name":"authority","isMut":false,"isSigner":true,"docs":["Authority for the cargo pod"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["Token Mint"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"cargoAmount","type":"u64"}]},{"name":"initCargoPod","docs":["Inits a new [`CargoPod`](state::CargoPod) account for the given [`CargoStatsDefinition`](state::CargoStatsDefinition) and authority."],"accounts":[{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new cargo pod"]},{"name":"authority","isMut":false,"isSigner":true,"docs":["The authority for the new cargo pod"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The new cargo pod"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The definition of tracked stats"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"podSeeds","type":{"array":["u8",32]}}]},{"name":"initCargoType","docs":["Inits a new [`CargoType`](state::CargoType) account for the given [`CargoStatsDefinition`](state::CargoStatsDefinition)."],"accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The crafting permissions [`Profile`].","Is going to act as the authority for the new definition."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the cargo type"]},{"name":"mint","isMut":false,"isSigner":false,"docs":["The mint for the new cargo type"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The definition for the cargo type"]},{"name":"cargoType","isMut":true,"isSigner":false,"docs":["The cargo type to init"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"InitCargoTypeInput"}}]},{"name":"initCargoTypeForNextSeqId","docs":["Creates a new cargo type for the next `seq_id`."],"accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The cargo permissions [`Profile`].","Is going to act as the authority for the new definition."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the cargo type"]},{"name":"mint","isMut":false,"isSigner":false,"docs":["The mint for the new cargo type"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The definition for the cargo type"]},{"name":"cargoType","isMut":true,"isSigner":false,"docs":["The cargo type to init"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"InitCargoTypeInput"}}]},{"name":"initCargoTypeFromOldCargoType","docs":["Creates a new cargo type for the next `seq_id` from a given cargo type."],"accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The cargo permissions [`Profile`].","Is going to act as the authority for the new definition."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the cargo type"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The definition for the cargo type"]},{"name":"oldCargoType","isMut":false,"isSigner":false,"docs":["The old Cargo Type Account"]},{"name":"cargoType","isMut":true,"isSigner":false,"docs":["The cargo type to init"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"InitCargoTypeFromOldCargoTypeInput"}}]},{"name":"initDefinition","docs":["Inits a [`CargoStatsDefinition`](state::CargoStatsDefinition) account."],"accounts":[{"name":"profile","isMut":false,"isSigner":false,"docs":["The cargo permissions [`Profile`](player_profile::state::Profile).","Is going to act as the authority for the new definition."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the new definition"]},{"name":"statsDefinition","isMut":true,"isSigner":true,"docs":["The new definition"]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program"]}],"args":[{"name":"input","type":{"defined":"InitDefinitionInput"}}]},{"name":"legitimizeCargo","docs":["Legitimizes cargo in a [`CargoPod`](state::CargoPod) that was added outside of [`add_cargo`] or other cargo ix.","Requires the authority to sign."],"accounts":[{"name":"authority","isMut":false,"isSigner":true,"docs":["Authority for the cargo pod"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"cargoAmount","type":"u64"}]},{"name":"mintTo","docs":["Mints tokens directly to a [`CargoPod`](state::CargoPod).","Requires the authority to sign."],"accounts":[{"name":"authority","isMut":false,"isSigner":true,"docs":["Authority for the [`CargoPod`] Account"]},{"name":"mintAuthority","isMut":false,"isSigner":true,"docs":["The mint Authority"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [`CargoPod`] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The [`CargoType`] Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The Cargo token mint"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"mintAmount","type":"u64"}]},{"name":"removeCargo","docs":["Removes cargo from a [`CargoPod`](state::CargoPod) to a given token account.","Requires the authority to sign."],"accounts":[{"name":"authority","isMut":false,"isSigner":true,"docs":["Authority for the cargo pod"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"destinationTokenAccount","isMut":true,"isSigner":false,"docs":["The Destination Token Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["Cargo Token Account"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"cargoAmount","type":"u64"}]},{"name":"transferAuthority","docs":["Transfers authority of a [`CargoPod`](state::CargoPod) to a new authority.","Requires both authorities to sign."],"accounts":[{"name":"originPodAuthority","isMut":false,"isSigner":true,"docs":["Authority for the cargo pod"]},{"name":"newPodAuthority","isMut":false,"isSigner":true,"docs":["New authority for the cargo pod"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]}],"args":[]},{"name":"transferCargo","docs":["Transfers cargo between [`CargoPod`](state::CargoPod)s.","Requires both authorities to sign."],"accounts":[{"name":"originPodAuthority","isMut":false,"isSigner":true,"docs":["Authority for the origin cargo pod"]},{"name":"destinationPodAuthority","isMut":false,"isSigner":true,"docs":["Authority for the destination cargo pod"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"originCargoPod","isMut":true,"isSigner":false,"docs":["The Origin [CargoPod] Account"]},{"name":"destinationCargoPod","isMut":true,"isSigner":false,"docs":["The Destination [CargoPod] Account"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The Cargo Type Account"]},{"name":"originTokenAccount","isMut":true,"isSigner":false,"docs":["The Origin Token Account"]},{"name":"destinationTokenAccount","isMut":true,"isSigner":false,"docs":["The Destination Token Account"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[{"name":"cargoAmount","type":"u64"}]},{"name":"updateCargoPod","docs":["Updates a [`CargoPod`](state::CargoPod) account to have the newest sequence id from the [`CargoDefinition`](state::CargoStatsDefinition).","This is the first step to update a [`CargoPod`](state::CargoPod) to a new [`CargoStatsDefinition`](state::CargoStatsDefinition).","Permissionless function."],"accounts":[{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The cargo pod to update"]},{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The definition of tracked stats"]}],"args":[]},{"name":"updateDefinition","docs":["Updates a [`CargoStatsDefinition`](state::CargoStatsDefinition) account.","Will advance the `seq_id` unless `rollback` is set to true."],"accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key authorized for this instruction"]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The cargo permissions [`Profile`](player_profile::state::Profile).","Is going to act as the authority for the new definition."]},{"name":"statsDefinition","isMut":true,"isSigner":false,"docs":["The [CargoStatsDefinition]"]}],"args":[{"name":"input","type":{"defined":"UpdateDefinitionInput"}}]},{"name":"updatePodTokenAccount","docs":["Updates a [`CargoPod`](state::CargoPod)s token account to have the same sequence id as the [`CargoPod`](state::CargoPod).","This must be called after [`update_cargo_pod`].","Permissionless function."],"accounts":[{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"oldCargoType","isMut":false,"isSigner":false,"docs":["The previous version(`seq_id`) Cargo Type"]},{"name":"cargoType","isMut":false,"isSigner":false,"docs":["The updated Cargo Type Account"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[]},{"name":"updateTokenAccountForInvalidType","docs":["Removes a pods token account if it no longer has a cargo type by burning all the invalid cargo.","This must be called after [`update_cargo_pod`].","Permissionless function."],"accounts":[{"name":"statsDefinition","isMut":false,"isSigner":false,"docs":["The [CargoStatsDefinition] for the cargo type"]},{"name":"cargoPod","isMut":true,"isSigner":false,"docs":["The [CargoPod] Account"]},{"name":"oldCargoType","isMut":false,"isSigner":false,"docs":["The previous version(`seq_id`) Cargo Type"]},{"name":"cargoTokenAccount","isMut":true,"isSigner":false,"docs":["The Cargo Token Account"]},{"name":"tokenMint","isMut":true,"isSigner":false,"docs":["The Cargo token mint"]},{"name":"tokenProgram","isMut":false,"isSigner":false,"docs":["Token Program"]}],"args":[]}],"accounts":[{"name":"CargoPod","docs":["A pod that can store any number of resources and tracks stats given a definition."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"statsDefinition","docs":["The definition of tracked stats."],"type":"publicKey"},{"name":"authority","docs":["The authority for this pod."],"type":"publicKey"},{"name":"openTokenAccounts","docs":["The number of open token accounts in this pod."],"type":"u8"},{"name":"podSeeds","docs":["The seeds of the signer for this pod."],"type":{"array":["u8",32]}},{"name":"podBump","docs":["The bump of the signer for this pod."],"type":"u8"},{"name":"seqId","docs":["The sequence id for the definition"],"type":"u16"},{"name":"unupdatedTokenAccounts","docs":["The number of unupdated token accounts in this pod. If this is greater than zero means the pod is frozen and only can withdraw cargo but not deposit."],"type":"u8"}]}},{"name":"CargoStatsDefinition","docs":["A definition of cargo stats.","Remaining data is the stats."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"authority","docs":["The authority for this definition."],"type":"publicKey"},{"name":"defaultCargoType","docs":["The default cargo type. System program (all 0s) if none."],"type":"publicKey"},{"name":"statsCount","docs":["The number of stats in this definition."],"type":"u16"},{"name":"seqId","docs":["The sequence id for the definition"],"type":"u16"}]}},{"name":"CargoType","docs":["The stats for a given cargo type (token mint)."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"statsDefinition","docs":["The definition this follows"],"type":"publicKey"},{"name":"mint","docs":["The mint the cargo type is for"],"type":"publicKey"},{"name":"bump","docs":["The bump for this account"],"type":"u8"},{"name":"statsCount","docs":["The number of stats in this definition."],"type":"u16"},{"name":"seqId","docs":["The sequence id for the definition"],"type":"u16"}]}}],"types":[{"name":"InitCargoTypeFromOldCargoTypeInput","docs":["Struct for data input for this IX"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the crafting permissions profile"],"type":"u16"},{"name":"newValues","docs":["vector with values for all stats tracked by the definition"],"type":{"option":{"vec":"u64"}}}]}},{"name":"InitCargoTypeInput","docs":["Struct for data input for this IX"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the crafting permissions profile"],"type":"u16"},{"name":"values","docs":["vector with values for all stats tracked by the definition"],"type":{"vec":"u64"}}]}},{"name":"InitDefinitionInput","docs":["Struct for data input for [`InitDefinition`]"],"type":{"kind":"struct","fields":[{"name":"cargoStats","docs":["the count of stats the definition has"],"type":"u16"}]}},{"name":"UpdateDefinitionInput","docs":["Struct for data input for this IX"],"type":{"kind":"struct","fields":[{"name":"keyIndex","docs":["the index of the key in the crafting permissions profile"],"type":"u16"},{"name":"rollback","docs":["flag that if present means we need to decrease the definition seq_id"],"type":{"option":"bool"}}]}}],"errors":[{"code":6000,"name":"StatOutOfBounds","msg":"A given stat was out of bounds"},{"code":6001,"name":"TooManyStats","msg":"There are too many stats"},{"code":6002,"name":"InvalidRentFunder","msg":"Rent funder was not owned by the system program or this program"},{"code":6003,"name":"TooFewStats","msg":"Popped a stat when there are no stats left"},{"code":6004,"name":"MissingSystemProgram","msg":"System program is missing when needed"},{"code":6005,"name":"InvalidCargoStat","msg":"Cargo stat data was invalid"},{"code":6006,"name":"InvalidCargoStatSize","msg":"Cargo stat size data was invalid"},{"code":6007,"name":"InvalidCargoType","msg":"Cargo type is invalid"},{"code":6008,"name":"WrongNumberOfDefinitions","msg":"Wrong number of definitions provided to init a cargo type"},{"code":6009,"name":"InvalidValueForStat","msg":"Invalid value provided for stat"},{"code":6010,"name":"NumericOverflow","msg":"Math overflow"},{"code":6011,"name":"AuthorityMismatch","msg":"Authority mismatch"},{"code":6012,"name":"StatsDefinitionMismatch","msg":"Stats definition mismatch"},{"code":6013,"name":"MintMismatch","msg":"Mint mismatch"},{"code":6014,"name":"OwnerMismatch","msg":"Owner mismatch"},{"code":6015,"name":"InvalidDelegation","msg":"Delegated amount is invalid"},{"code":6016,"name":"FrozenPod","msg":"The pod is frozen"},{"code":6017,"name":"UnupdatedCargoPodAccount","msg":"Unupdated CargoPod Account"},{"code":6018,"name":"InvalidSeqId","msg":"Invalid seq_id"},{"code":6019,"name":"UnupdatedTokenAccount","msg":"Unupdated token account"},{"code":6020,"name":"OpenTokenAccounts","msg":"Cargo Pod has token accounts open"},{"code":6021,"name":"NonZeroDelegation","msg":"Non Zero Delegated Amount"},{"code":6022,"name":"InvalidPreviousType","msg":"Invalid previous cargo_type account"},{"code":6023,"name":"InsufficientCargoAmount","msg":"Insufficient cargo amount"},{"code":6024,"name":"InsufficientTokenAmount","msg":"Insufficient token amount"},{"code":6025,"name":"PodTokenAccountAlreadyUpdated","msg":"Pod Token Account Already Updated"}]}'); + const profileIDL = await BrowserAnchor.anchor.Program.fetchIdl(profileProgramId, anchorProvider); + const profileProgram = new BrowserAnchor.anchor.Program(profileIDL, profileProgramId, anchorProvider); + console.debug('profileProgram: ', profileProgram); + + // load cargo program and methods const cargoProgramId = new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'); - const profileFactionIDL = JSON.parse('{"version":"0.7.0","name":"profile_faction","instructions":[{"name":"chooseFaction","accounts":[{"name":"key","isMut":false,"isSigner":true,"docs":["The key with auth permissions."]},{"name":"funder","isMut":true,"isSigner":true,"docs":["The funder for the transaction."]},{"name":"profile","isMut":false,"isSigner":false,"docs":["The profile to change faction for."]},{"name":"faction","isMut":true,"isSigner":false,"docs":["The faction to change to."]},{"name":"systemProgram","isMut":false,"isSigner":false,"docs":["The system program."]}],"args":[{"name":"keyIndex","type":"u16"},{"name":"faction","type":{"defined":"Faction"}}]}],"accounts":[{"name":"ProfileFactionAccount","docs":["Stores a profiles enlisted faction on-chain."],"type":{"kind":"struct","fields":[{"name":"version","docs":["The data version of this account."],"type":"u8"},{"name":"profile","docs":["The profile this faction enlistment is for."],"type":"publicKey"},{"name":"faction","docs":["The faction of the profile."],"type":"u8"},{"name":"bump","docs":["The bump for this account."],"type":"u8"}]}}],"types":[{"name":"Faction","docs":["A faction that a player can belong to."],"type":{"kind":"enum","variants":[{"name":"Unaligned"},{"name":"MUD"},{"name":"ONI"},{"name":"Ustur"}]}}]}'); + const cargoIDL = await BrowserAnchor.anchor.Program.fetchIdl(cargoProgramId, anchorProvider); + const cargoProgram = new BrowserAnchor.anchor.Program(cargoIDL, cargoProgramId, anchorProvider); + const [cargoStatsDefinitionAcct] = await cargoProgram.account.cargoStatsDefinition.all(); + const cargoStatsDefSeqId = cargoStatsDefinitionAcct.account.seqId; + console.debug('cargoProgram', cargoProgram); + console.debug('cargoStatsDefinitionAcct', cargoStatsDefinitionAcct); + const profileFactionProgramId = new solanaWeb3.PublicKey('pFACSRuobDmvfMKq1bAzwj27t6d2GJhSCHb1VcfnRmq'); - const resourceTokens = [{name: 'Carbon', token: 'CARBWKWvxEuMcq3MqCxYfi7UoFVpL9c4rsQS99tw6i4X'},{name: 'Iron Ore', token: 'FeorejFjRRAfusN9Fg3WjEZ1dRCf74o6xwT5vDt3R34J'},{name: 'Diamond', token: 'DMNDKqygEN3WXKVrAD4ofkYBc4CKNRhFUbXP4VK7a944'},{name: 'Lumanite', token: 'LUMACqD5LaKjs1AeuJYToybasTXoYQ7YkxJEc4jowNj'},{name: 'Biomass', token: 'MASS9GqtJz6ABisAxcUn3FeR4phMqH1XfG6LPKJePog'},{name: 'Arco', token: 'ARCoQ9dndpg6wE2rRexzfwgJR3NoWWhpcww3xQcQLukg'},{name: 'Hydrogen', token: 'HYDR4EPHJcDPcaLYUcNCtrXUdt1PnaN4MvE655pevBYp'},{name: 'Copper Ore', token: 'CUore1tNkiubxSwDEtLc3Ybs1xfWLs8uGjyydUYZ25xc'},{name: 'Rochinol', token: 'RCH1Zhg4zcSSQK8rw2s6rDMVsgBEWa4kiv1oLFndrN5'}] - const r4Tokens = [{name: 'Fuel', token: 'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim'},{name: 'Food', token: 'foodQJAztMzX1DKpLaiounNe2BDMds5RNuPC6jsNrDG'},{name: 'Ammo', token: 'ammoK8AkX2wnebQb35cDAZtTkvsXQbi82cGeTnUvvfK'},{name: 'Toolkit', token: 'tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL'},{name: 'SDU', token: 'SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM'}] - - let userPublicKey = null; - let userProfileAcct = null; - let userProfileKeyIdx = 0; - let userProfileFactionAcct = null; - let userFleetAccts = null; - let userFleets = []; + const profileFactionIDL = await BrowserAnchor.anchor.Program.fetchIdl(profileFactionProgramId, anchorProvider); + const profileFactionProgram = new BrowserAnchor.anchor.Program(profileFactionIDL, profileFactionProgramId, anchorProvider); + + // token accounts + const tokenProgram = new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); + const AssociatedTokenProgram = new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); + const RecentSlotHashes = new solanaWeb3.PublicKey('SysvarS1otHashes111111111111111111111111111'); + const InstructionsSysVar = new solanaWeb3.PublicKey('Sysvar1nstructions1111111111111111111111111'); + + 'Arco','Biomass','Carbon','Copper Ore','Diamond','Hydrogen','Iron Ore','Lumanite','Rochinol' + + const ResourceTokens = { + fuel: { + name: 'Fuel', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim') + }, + food: { + name: 'Food', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('foodQJAztMzX1DKpLaiounNe2BDMds5RNuPC6jsNrDG') + }, + ammo: { + name: 'Ammo', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('ammoK8AkX2wnebQb35cDAZtTkvsXQbi82cGeTnUvvfK') + }, + toolkit: { + name: 'Toolkit', + type: ['all', 'r4'], + publicKey: new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL') + }, + carbon: { + name: 'Carbon', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('CARBWKWvxEuMcq3MqCxYfi7UoFVpL9c4rsQS99tw6i4X') + }, + ironore: { + name: 'Iron Ore', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('FeorejFjRRAfusN9Fg3WjEZ1dRCf74o6xwT5vDt3R34J') + }, + diamond: { + name: 'Diamond', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('DMNDKqygEN3WXKVrAD4ofkYBc4CKNRhFUbXP4VK7a944') + }, + lumanite: { + name: 'Lumanite', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('LUMACqD5LaKjs1AeuJYToybasTXoYQ7YkxJEc4jowNj') + }, + biomass: { + name: 'Biomass', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('MASS9GqtJz6ABisAxcUn3FeR4phMqH1XfG6LPKJePog') + }, + arco: { + name: 'Arco', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('ARCoQ9dndpg6wE2rRexzfwgJR3NoWWhpcww3xQcQLukg') + }, + hydrogen: { + name: 'Hydrogen', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('HYDR4EPHJcDPcaLYUcNCtrXUdt1PnaN4MvE655pevBYp') + }, + copperore: { + name: 'Copper Ore', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('CUore1tNkiubxSwDEtLc3Ybs1xfWLs8uGjyydUYZ25xc') + }, + rochinol: { + name: 'Rochinol', + type: ['all', 'r9'], + publicKey: new solanaWeb3.PublicKey('RCH1Zhg4zcSSQK8rw2s6rDMVsgBEWa4kiv1oLFndrN5') + }, + sdu: { + name: 'SDU', + type: ['all'], + publicKey: new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM'), + from: new solanaWeb3.PublicKey('8bBi84Yi7vwSWXSYKDbbHmqnFqqAS41MvPkSEdzFtbsk') + }, + getPodToken: async function (input) { + let { name, publicKey, pod } = input; + if (publicKey) name = this.find(item => item.publicKey === publicKey || item.publicKey.toString() === publicKey).name; + if (name) name = name.toLowerCase().trim(); + if (!name || name === '' || !publicKey) throw new Error('Need to provide resource name or publicKey!'); + if (!pod || !this[name].from) throw new Error('Need to provide pod (fleet or starbase) for resource!'); + + const resourcePublicKey = this[name].publicKey; + const podPublicKey = pod || this[name].from; + + const pda = BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( + [ + podPublicKey.toBuffer(), + tokenProgram.toBuffer(), + resourcePublicKey.toBuffer() + ], + AssociatedTokenProgram + ); + + try { + await solanaConnection.getAccountInfo(pda); + } catch { + await createProgramDerivedAccount(pda, podPublicKey, resourcePublicKey); + } + return pda; + }, + names(type='all') { + return Object.values(this) + .filter(value => typeof value === 'object' && value.type.includes(type.toLowerCase())) + .map(value => value.name); + }, + publicKeys(type='all') { + return Object.values(this) + .filter(value => typeof value === 'object' && value.type.includes(type.toLowerCase())) + .map(value => value.publicKey); + }, + exists(resource) { + const names = this.names().map(name => name.toLowerCase().trim()); + const publicKeys = this.publicKeys().map(pbk => pbk.toString()); + + if (typeof resource === 'object') { + resource = resource.toString() || ''; + } - let sageProgram = new BrowserAnchor.anchor.Program(sageIDL, sageProgramId, anchorProvider); - console.log('sageProgram: ', sageProgram); - let [sageGameAcct] = await sageProgram.account.game.all(); - console.log('sageGameAcct: ', sageGameAcct); - let [sageSDUTrackerAcct] = await sageProgram.account.surveyDataUnitTracker.all(); - console.log('sageSDUTrackerAcct: ', sageSDUTrackerAcct); - - let profileProgram = new BrowserAnchor.anchor.Program(profileIDL, profileProgramId, anchorProvider); - console.log('profileProgram: ', profileProgram); - - let cargoProgram = new BrowserAnchor.anchor.Program(cargoIDL, cargoProgramId, anchorProvider); - let [cargoStatsDefinitionAcct] = await cargoProgram.account.cargoStatsDefinition.all(); - let cargoStatsDefSeqId = cargoStatsDefinitionAcct.account.seqId; - console.log('cargoProgram', cargoProgram); - console.log('cargoStatsDefinitionAcct', cargoStatsDefinitionAcct); - let seqBN = new BrowserAnchor.anchor.BN(cargoStatsDefSeqId); - let seqArr = seqBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "be", 2); - let seq58 = bs58.encode(seqArr); - let [sduCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + return names.includes(resource.toLowerCase().trim()) || publicKeys.includes(resource); + }, + findName(resource) { + if (!resource || resource == '') return ''; + resource = resource.toLowerCase().trim(); + return this.exists(resource) ? this[resource].name : ''; + } + } + + // @todo - redo documentation +/** + * The function `getProvider` is an asynchronous function that takes in a `wallets` object and returns + * the provider object associated with the first wallet found in the `walletWindow` object. + * @param wallets - An object that contains different wallet providers. The keys of the object are the + * names of the wallets, and the values are the corresponding wallet provider objects. + * @returns the provider object if it exists and is connected. + */ + async function getProvider () { + const provider = wallets.find(name => typeof name !== 'undefined'); + if (provider) { + if (!provider.isConnected) await provider.connect(); + return provider; + } + } + +/** + * The "wait" function returns a promise that resolves after a specified number of milliseconds. + * @param ms - The "ms" parameter represents the number of milliseconds to wait before resolving the + * promise. + * @returns a Promise object. + */ + function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + } + + const MAX_TRANSACTION_SIZE = 1232; + + function lengthToCompact16size(size) { + if (size > 0x7f) return 2; + return 1; + } + +/** + * The function calculates the size of a transaction in bytes based on the number of instructions and + * the keys involved. + * @param instructions - An array of objects representing instructions for a transaction. Each object + * has the following properties: `instruction` + * @param funder - The `funder` parameter is an object that represents the funder's account. It should + * have a `toBase58()` method that returns the base58 encoded string representation of the account's + * public key. + * @returns the size of a transaction in bytes. + */ + function getTransactionSize(instructions, funder) { + let ixSizes = 0; + const uniqueSigners = new Set(funder.toBase58()); + const uniqueKeys = new Set(funder.toBase58()); + + instructions.forEach(({ instruction }) => { + uniqueKeys.add(instruction.programId.toBase58()); + + instruction.keys.forEach((key) => { + if (key.isSigner) uniqueSigners.add(key.pubkey.toBase58()); + uniqueKeys.add(key.pubkey.toBase58()); + }); + ixSizes += + 1 + // program id index + lengthToCompact16size(instruction.keys.length) + // num accounts + instruction.keys.length + // account indexes + lengthToCompact16size(instruction.data.length) + // num ix bytes + instruction.data.length; // ix bytes + }); + + // console.log({ uniqueSigners, uniqueKeys, ixSizes }); + + return ( + lengthToCompact16size(uniqueSigners.size) + // num sigs + uniqueSigners.size * 64 + // Sigs + 3 + // message header + lengthToCompact16size(uniqueKeys.size) + // num accounts + uniqueKeys.size * 32 + // accounts + 32 + // recent blockhash + lengthToCompact16size(instructions.length) + // num instructions + ixSizes + ); + } + +/** + * The function `createProgramDerivedAccount` creates a derived account using the provided derived + * public key and two other derived from public keys. + * @param derived - The `derived` parameter is the public key of the account that will be created as a + * derived account. + * @param derivedFrom1 - The `derivedFrom1` parameter is a public key that represents the account from + * which the `derived` account is derived. + * @param derivedFrom2 - The parameter `derivedFrom2` is a public key that represents the account from + * which the `derived` account is derived. + * @returns the result of the `signAndSend` function, which is likely a promise that resolves to the + * transaction details. + */ + async function createProgramDerivedAccount(derived, derivedFrom1, derivedFrom2) { + const keys = [{ + pubkey: provider.publicKey, + isSigner: true, + isWritable: true + }, { + pubkey: derived, + isSigner: false, + isWritable: true + }, { + pubkey: derivedFrom1, + isSigner: false, + isWritable: false + }, { + pubkey: derivedFrom2, + isSigner: false, + isWritable: false + }, { + pubkey: solanaWeb3.SystemProgram.programId, + isSigner: false, + isWritable: false + }, { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }]; + + return await signAndSend({ + instruction: new solanaWeb3.TransactionInstruction({ + keys: keys, + programId: AssociatedTokenProgram.toString(), + data: [] + }) + }); + } + + const seqBN = new BrowserAnchor.anchor.BN(cargoStatsDefSeqId); + const seqArr = seqBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "be", 2); + const seq58 = bs58.encode(seqArr); + + // @todo - pull into ResourceTokens with getCargoType function + const [sduCargoTypeAcct] = await cargoProgram.account.cargoType.all([ { memcmp: { offset: 41, - bytes: new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM').toBase58(), + bytes: ResourceTokens.sdu.publicKey.toBase58(), }, }, { @@ -74,11 +341,11 @@ }, }, ]); - let [repairKitCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + const [repairKitCargoTypeAcct] = await cargoProgram.account.cargoType.all([ { memcmp: { offset: 41, - bytes: new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL').toBase58(), + bytes: ResourceTokens.toolkit.publicKey.toBase58(), }, }, { @@ -88,11 +355,11 @@ }, }, ]); - let [fuelCargoTypeAcct] = await cargoProgram.account.cargoType.all([ + const [fuelCargoTypeAcct] = await cargoProgram.account.cargoType.all([ { memcmp: { offset: 41, - bytes: new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim').toBase58(), + bytes: ResourceTokens.fuel.publicKey.toBase58(), }, }, { @@ -102,7 +369,7 @@ }, }, ]); - let cargoTypes = await cargoProgram.account.cargoType.all([ + const cargoTypes = await cargoProgram.account.cargoType.all([ { memcmp: { offset: 75, @@ -110,398 +377,356 @@ }, }, ]); - let [sduTokenFrom] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - new solanaWeb3.PublicKey('8bBi84Yi7vwSWXSYKDbbHmqnFqqAS41MvPkSEdzFtbsk').toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - - function createProgramDerivedAccount(derived, derivedFrom1, derivedFrom2) { - return new Promise(async resolve => { - const keys = [{ - pubkey: userPublicKey, - isSigner: true, - isWritable: true - }, { - pubkey: derived, - isSigner: false, - isWritable: true - }, { - pubkey: derivedFrom1, - isSigner: false, - isWritable: false - }, { - pubkey: derivedFrom2, - isSigner: false, - isWritable: false - }, { - pubkey: solanaWeb3.SystemProgram.programId, - isSigner: false, - isWritable: false - }, { - pubkey: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - isSigner: false, - isWritable: false - }]; - let tx = {instruction: new solanaWeb3.TransactionInstruction({ - keys: keys, - programId: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', - data: [] - })} - let txResult = await txSignAndSend(tx); - resolve(txResult); - }); - } - function getFleetState(fleetAcctInfo) { - let remainingData = fleetAcctInfo.data.subarray(414); + // @todo - redo documentation +/** + * The function `getCurrentFleet` takes in fleet account information and returns the fleet state and any + * additional data associated with it. + * @param fleet - The `fleetAccountInfo` parameter is an object that contains information about a + * fleet account. It has a property called `data` which is an array of numbers. + * @returns The function `getCurrentFleet` returns an array with two elements. The first element is the + * fleet state, which can be one of the following values: 'StarbaseLoadingBay', 'Idle', 'MineAsteroid', + * 'MoveWarp', 'MoveSubwarp', or 'Respawn'. The second element is an additional value that provides + * extra information depending on the fleet state. + */ + async function getCurrentFleet(fleet) { + const fleetAccountInfo = await solanaConnection.getAccountInfo(fleet.publicKey); + const fleetData = sageProgram.coder.accounts.decode('Fleet', fleetAccountInfo.data); + const remainingData = fleetAccountInfo.data.slice(414); + let fleetState = 'Unknown'; let extra = null; switch(remainingData[0]) { case 0: fleetState = 'StarbaseLoadingBay'; - extra = sageProgram.coder.types.decode('StarbaseLoadingBay', remainingData.subarray(1)); + extra = sageProgram.coder.types.decode('StarbaseLoadingBay', remainingData.slice(1)); break; case 1: { fleetState = 'Idle'; - let sector = sageProgram.coder.types.decode('Idle', remainingData.subarray(1)); + let sector = sageProgram.coder.types.decode('Idle', remainingData.slice(1)); extra = [sector.sector[0].toNumber(), sector.sector[1].toNumber()] break; } case 2: fleetState = 'MineAsteroid'; - extra = sageProgram.coder.types.decode('MineAsteroid', remainingData.subarray(1)); + extra = sageProgram.coder.types.decode('MineAsteroid', remainingData.slice(1)); break; case 3: fleetState = 'MoveWarp'; - extra = sageProgram.coder.types.decode('MoveWarp', remainingData.subarray(1)); + extra = sageProgram.coder.types.decode('MoveWarp', remainingData.slice(1)); break; case 4: fleetState = 'MoveSubwarp'; - extra = sageProgram.coder.types.decode('MoveSubwarp', remainingData.subarray(1)); + extra = sageProgram.coder.types.decode('MoveSubwarp', remainingData.slice(1)); break; case 5: fleetState = 'Respawn'; break; } - return [fleetState, extra]; + return { fleetData, fleetState, extra }; } - function initUser() { - return new Promise(async resolve => { - if (typeof solflare === 'undefined') { - let walletConn = await solana.connect(); - userPublicKey = walletConn.publicKey; - } else { - await solflare.connect(); - userPublicKey = solflare.publicKey; - } - - let userProfiles = await solanaConnection.getProgramAccounts(profileProgramId); - - let foundProf = []; - console.log(userProfiles[0]); - for (let userProf of userProfiles) { - let userProfData = userProf.account.data.subarray(30); - let iter = 0; - while (userProfData.length >= 80) { - let currProf = userProfData.subarray(0, 80); - let profDecoded = profileProgram.coder.types.decode('ProfileKey', currProf); - if (profDecoded.key.toString() === userPublicKey.toString()) { - let [playerNameAcct] = await solanaConnection.getProgramAccounts( - profileProgramId, - { - filters: [ - { - memcmp: { - offset: 9, - bytes: userProf.pubkey.toString(), - }, + /** + * The `initUser` function retrieves user profiles and fleet accounts, and returns the user profile + * and user profile faction account. + * @returns The function `initUser` returns an object with properties `playerProfile` and + * `playerProfileFactionAcct`. + */ + async function initUser() { + provider = await getProvider(wallets); + console.debug('Provider: ', provider); + + let userProfiles = await solanaConnection.getProgramAccounts(profileProgramId); + + let playerProfiles = []; + for (let [index, userProfile] of userProfiles.entries()) { + let profileData = userProfile.account.data.subarray(30); + while (profileData.length >= 80) { + const profile = profileData.subarray(0, 80); + const decodedProfile = profileProgram.coder.types.decode('ProfileKey', profile); + + if (decodedProfile.key.toString() === provider.publicKey.toString()) { + const [playerNameAcct] = await solanaConnection.getProgramAccounts( + profileProgramId, + { + filters: [ + { + memcmp: { + offset: 9, + bytes: userProfile.pubkey.toString(), }, - ], - } - ); - let playerName = playerNameAcct ? new TextDecoder().decode(playerNameAcct.account.data.subarray(42)) : ''; - foundProf.push({profile: userProf.pubkey.toString(), name: playerName, idx: iter}) - } - userProfData = userProfData.subarray(80); - //iter > 0 && foundProf.push({profile: userProf, key: profDecoded, idx: iter}); - iter += 1; + }, + ], + } + ); + const playerName = playerNameAcct ? new TextDecoder().decode(playerNameAcct.account.data.subarray(42)) : ''; + playerProfiles.push({ ...userProfile, name: playerName, index }) } + profileData = profileData.subarray(80); } + } - let userProfile = foundProf.length > 1 ? await assistProfileToggle(foundProf) : foundProf[0]; - userProfileAcct = new solanaWeb3.PublicKey(userProfile.profile); - userProfileKeyIdx = userProfile.idx; - - /* - function getUserProfileAcct(procId, roomId, sessionId) { - return new Promise((resolve) => { - let autoWS = new WebSocket(`wss://starcomm-mp.staratlas.com/${procId}/${roomId}?sessionId=${sessionId}`); - autoWS.binaryType = 'arraybuffer'; - autoWS.onmessage = (event) => { - let tempArr = Array.from(new Uint8Array(event.data)) - if (tempArr[0] === 14) { - let tempSlice = new Uint8Array(tempArr.slice(9,52)); - userProfileAcct = new solanaWeb3.PublicKey(new TextDecoder().decode(tempSlice)); - autoWS.close(); - resolve(); - } - }; - autoWS.onopen = (event) => { - let initData = new Uint8Array(1); - initData[0] = 10; - autoWS.send(initData); - }; - }); - } + playerProfile = playerProfiles.length > 1 ? await assistProfileToggle(playerProfiles) : playerProfiles[0]; + console.debug('User Profile: ', playerProfile); - let sageRoom = await fetch("https://starcomm-mp.staratlas.com/matchmake/joinOrCreate/Sage_Account_Room", { - "headers": { - "accept": "application/json", - "accept-language": "en-US,en;q=0.9", - "content-type": "application/json", + const [playerProfileFactionAcct] = await profileFactionProgram.account.profileFactionAccount.all([ + { + memcmp: { + offset: 9, + bytes: playerProfile.pubkey.toBase58(), }, - "referrerPolicy": "no-referrer", - "body": `{"playerPublicKey":"${userPublicKey.toBase58()}"}`, - "method": "POST", - "mode": "cors", - "credentials": "omit" - }); - let sageRoomJson = await sageRoom.json(); - await getUserProfileAcct(sageRoomJson.room.processId, sageRoomJson.room.roomId, sageRoomJson.sessionId); - */ + }, + ]); + playerProfile.faction = playerProfileFactionAcct; - let profileFactionProgram = new BrowserAnchor.anchor.Program(profileFactionIDL, profileFactionProgramId, anchorProvider); - [userProfileFactionAcct] = await profileFactionProgram.account.profileFactionAccount.all([ - { - memcmp: { - offset: 9, - bytes: userProfileAcct.toBase58(), - }, + const userFleetAccts = await sageProgram.account.fleet.all([ + { + memcmp: { + offset: 41, + bytes: playerProfile.pubkey.toBase58(), }, - ]); + }, + ]); - userFleetAccts = await sageProgram.account.fleet.all([ - { - memcmp: { - offset: 41, - bytes: userProfileAcct.toBase58(), - }, + for (let fleet of userFleetAccts) { + const name = new TextDecoder("utf-8").decode(new Uint8Array(fleet.account.fleetLabel)).replace(/\0/g, ''); + + const fleetDefaultData = { + origin: { + coords: '', + supplies: { + 'blank1': 0, + 'blank2': 0, + 'blank3': 0, + 'blank4': 0, + } }, - ]); - console.log(userFleetAccts); - - for (let fleet of userFleetAccts) { - let fleetLabel = new TextDecoder("utf-8").decode(new Uint8Array(fleet.account.fleetLabel)); - let [fleetRepairKitToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.account.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetSduToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.account.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetFuelToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.account.fuelTank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let fleetSavedData = await GM.getValue(fleet.publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetDest = fleetParsedData && fleetParsedData.dest ? fleetParsedData.dest : ''; - let fleetScanBlock = fleetParsedData && fleetParsedData.scanBlock ? fleetParsedData.scanBlock : []; - let fleetScanMin = fleetParsedData && fleetParsedData.scanMin ? fleetParsedData.scanMin : 10; - let fleetScanMove = fleetParsedData && fleetParsedData.scanMove == 'false' ? 'false' : 'true'; - let fleetMineResource = fleetParsedData && fleetParsedData.mineResource ? fleetParsedData.mineResource : ''; - let fleetStarbase = fleetParsedData && fleetParsedData.starbase ? fleetParsedData.starbase : ''; - let fleetMoveType = fleetParsedData && fleetParsedData.moveType ? fleetParsedData.moveType : 'warp'; - let fleetMoveTarget = fleetParsedData && fleetParsedData.moveTarget ? fleetParsedData.moveTarget : ''; - await solanaConnection.getAccountInfo(fleetSduToken) || await createProgramDerivedAccount(fleetSduToken, fleet.account.cargoHold, new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM')); - await solanaConnection.getAccountInfo(fleetRepairKitToken) || await createProgramDerivedAccount(fleetRepairKitToken, fleet.account.cargoHold, new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL')); - await solanaConnection.getAccountInfo(fleetFuelToken) || await createProgramDerivedAccount(fleetFuelToken, fleet.account.fuelTank, new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim')); - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentToolCnt = fleetCurrentCargo.value.find(item => item.pubkey.toString() === fleetRepairKitToken.toString()); - let fleetCurrentFuel = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuelCnt = fleetCurrentFuel.value.find(item => item.pubkey.toString() === fleetFuelToken.toString()); - let fleetAcctInfo = await solanaConnection.getAccountInfo(fleet.publicKey); - let [fleetState, extra] = getFleetState(fleetAcctInfo); - let fleetCoords = fleetState == 'Idle' && extra ? extra : []; - let fleetScanBlockIdx = 0; - for (let i = 0; i < fleetScanBlock.length; i++) { - if (fleetCoords[0] == fleetScanBlock[i][0] && fleetCoords[1] == fleetScanBlock[i][1]) { - fleetScanBlockIdx = i; + destination: { + coords: '', + supplies: { + 'blank1': 0, + 'blank2': 0, + 'blank3': 0, + 'blank4': 0, + } + }, + moveType: 'warp', + mineResource: '', + minePlanet: null, + scanMinimumProbability: 10, + scanSkipCnt: 0, + scanSectorStart: 0, + scanSectorEnd: 0, + scanBlock: [], + scanBlockIndex: 0, + scanMove: true, + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + + const { fleetState, extra } = await getCurrentFleet(fleet); + if (fleetState == 'Idle' && extra) { + for (let i = 0; i < fleetDefaultData.scanBlock.length; i++) { + if (extra[0] == fleetDefaultData.scanBlock[i][0] && extra[1] == fleetDefaultData.scanBlock[i][1]) { + fleetDefaultData.scanBlockIndex = i; break; } } - userFleets.push({publicKey: fleet.publicKey, label: fleetLabel.replace(/\0/g, ''), state: fleetState, moveTarget: fleetMoveTarget, startingCoords: fleetCoords, cargoHold: fleet.account.cargoHold, fuelTank: fleet.account.fuelTank, ammoBank: fleet.account.ammoBank, repairKitToken: fleetRepairKitToken, sduToken: fleetSduToken, fuelToken: fleetFuelToken, warpFuelConsumptionRate: fleet.account.stats.movementStats.warpFuelConsumptionRate, warpSpeed: fleet.account.stats.movementStats.warpSpeed, maxWarpDistance: fleet.account.stats.movementStats.maxWarpDistance, subwarpFuelConsumptionRate: fleet.account.stats.movementStats.subwarpFuelConsumptionRate, subwarpSpeed: fleet.account.stats.movementStats.subwarpSpeed, cargoCapacity: fleet.account.stats.cargoStats.cargoCapacity, fuelCapacity: fleet.account.stats.cargoStats.fuelCapacity, ammoCapacity: fleet.account.stats.cargoStats.ammoCapacity, scanCost: fleet.account.stats.miscStats.scanRepairKitAmount, scanCooldown: fleet.account.stats.miscStats.scanCoolDown, warpCooldown: fleet.account.stats.movementStats.warpCoolDown, miningRate: fleet.account.stats.cargoStats.miningRate, foodConsumptionRate: fleet.account.stats.cargoStats.foodConsumptionRate, ammoConsumptionRate: fleet.account.stats.cargoStats.ammoConsumptionRate, planetExitFuelAmount: fleet.account.stats.movementStats.planetExitFuelAmount, destCoord: fleetDest, starbaseCoord: fleetStarbase, scanBlock: fleetScanBlock, scanBlockIdx: fleetScanBlockIdx, scanEnd: 0, scanSkipCnt: 0, scanSectorStart: 0, scanMin: fleetScanMin, scanMove: fleetScanMove, toolCnt: currentToolCnt.account.data.parsed.info.tokenAmount.uiAmount, sduCnt: 0, fuelCnt: currentFuelCnt.account.data.parsed.info.tokenAmount.uiAmount, moveType: fleetMoveType, mineResource: fleetMineResource, minePlanet: null}); } - userFleets.sort(function (a, b) { - return a.label.toUpperCase().localeCompare(b.label.toUpperCase()); - }); - initComplete = true; - resolve(); - }); - } - function wait(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms); + fleet = { name, ...fleet, ...fleetDefaultData, ...fleetSavedData }; + userFleets.push(fleet); + await GM.setValue(fleet.publicKey.toString(), JSON.stringify(fleet)); + } + userFleets.sort(function (a, b) { + return a.name.toUpperCase().localeCompare(b.name.toUpperCase()); }); + + return { playerProfile, playerProfileFactionAcct }; } +/** + * The function `getBalanceChange` calculates the change in balance for a specific account after a + * transaction. + * @param txResult - The `txResult` parameter is an object that represents the result of a transaction. + * It contains information about the transaction, such as the message, metadata, and token balances + * before and after the transaction. + * @param targetAcct - The `targetAcct` parameter is the account address or key that you want to get + * the balance change for. + * @returns an object with two properties: "preBalance" and "postBalance". + */ function getBalanceChange(txResult, targetAcct) { - let acctIdx = txResult.transaction.message.staticAccountKeys.findIndex(item => item.toString() === targetAcct); - let preBalanceObj = txResult.meta.preTokenBalances.find(item => item.accountIndex === acctIdx); - let preBalance = preBalanceObj && preBalanceObj.uiTokenAmount && preBalanceObj.uiTokenAmount.uiAmount ? preBalanceObj.uiTokenAmount.uiAmount : 0; - let postBalanceObj = txResult.meta.postTokenBalances.find(item => item.accountIndex === acctIdx); - let postBalance = postBalanceObj && postBalanceObj.uiTokenAmount && postBalanceObj.uiTokenAmount.uiAmount ? postBalanceObj.uiTokenAmount.uiAmount : 0; - return {preBalance: preBalance, postBalance: postBalance} - } - - // Extracted from SAGE Labs, keep for future reference - function getTokenBalanceChanges(br, targetAcct) { - const gr = {}; - if (br.meta && br.meta.preTokenBalances && br.meta.postTokenBalances) { - for (const _r of br.meta.preTokenBalances) { - const vr = br.transaction.message.staticAccountKeys[_r.accountIndex] - , Sr = _r.uiTokenAmount.uiAmount ?? 0; - gr[vr.toString()] = Sr - } - for (const _r of br.meta.postTokenBalances) { - const vr = br.transaction.message.staticAccountKeys[_r.accountIndex] - , wr = (_r.uiTokenAmount.uiAmount ?? 0) - gr[vr.toString()]; - gr[vr.toString()] = wr - } - } - return gr - } + const acctIdx = txResult.transaction.message.staticAccountKeys.findIndex(item => item.toString() === targetAcct); - function calculateMovementDistance(orig, dest) { - return dest ? Math.sqrt((orig[0] - dest[0]) ** 2 + (orig[1] - dest[1]) ** 2) : 0 - } + const preTokenBalances = txResult.meta.preTokenBalances.find(item => item.accountIndex === acctIdx); + const preBalance = preTokenBalances.uiTokenAmount.uiAmount || 0; + const postTokenBalances = txResult.meta.postTokenBalances.find(item => item.accountIndex === acctIdx); + const postBalance = postTokenBalances.uiTokenAmount.uiAmount || 0; - function calculateWarpTime(fleet, distance) { - return fleet.warpSpeed > 0 ? distance / (fleet.warpSpeed / 1e6) : 0 + return { preBalance, postBalance } } - function calculateWarpFuelBurn(fleet, distance) { - return distance * (fleet.warpFuelConsumptionRate / 100) + /** + * The function calculates the distance between two points in a two-dimensional space. + * @param origin - The origin parameter represents the starting point of movement. It is an array + * containing the x and y coordinates of the origin point. + * @param destination - The destination parameter is the coordinates of the destination point. It is + * an array with two elements, where the first element represents the x-coordinate and the second + * element represents the y-coordinate. + * @returns the distance between the origin and destination points. + */ + function calculateMovementDistance(origin, destination) { + if (typeof origin === 'string') origin = origin.split(',').map(c => c.trim()); + if (typeof destination === 'string') destination = destination.split(',').map(c => c.trim()); + + return Math.sqrt((origin[0] - destination[0]) ** 2 + (origin[1] - destination[1]) ** 2) || 0 } - function calculateSubwarpTime(fleet, distance) { - return fleet.subwarpSpeed > 0 ? distance / (fleet.subwarpSpeed / 1e6) : 0 +/** + * The function calculates the amount of fuel burned by a fleet traveling a certain distance while warping. + * @param fleet - An object representing a fleet of spaceships. It should have a property called + * "warpFuelConsumptionRate" which represents the rate at which the fleet consumes warp fuel. + * @param distance - The distance is the total distance that the fleet will be traveling. + * @returns the amount of fuel burned by a fleet when traveling a certain distance. + */ + function calculateWarpFuelBurn(fleet, distance) { + return distance * (fleet.account.stats.movementStats.warpFuelConsumptionRate / 100) } +/** + * The function calculates the amount of fuel burned by a fleet based on the distance traveled. + * @param fleet - The fleet parameter represents the fleet of ships that will be traveling the + * distance. It is assumed to be an object with properties related to the fleet's subwarp fuel + * consumption rate. + * @param distance - The distance is the total distance that the fleet will be traveling. + * @returns the amount of fuel burned by a fleet when traveling a certain distance. + */ function calculateSubwarpFuelBurn(fleet, distance) { - return distance * (fleet.subwarpFuelConsumptionRate / 100) + return distance * (fleet.account.stats.movementStats.subwarpFuelConsumptionRate / 100) } +/** + * The function calculates the duration of mining based on cargo capacity, mining rate, resource + * hardness, and system richness. + * @param cargoCapacity - The cargo capacity refers to the maximum amount of resources that can be + * stored in the mining vessel. + * @param miningRate - The rate at which resources can be extracted via the mining + * operation. It is measured in units per second. + * @param resourceHardness - Resource hardness refers to the difficulty or toughness of the resource + * being mined. It is usually measured on a scale from 0 to 100, where 0 represents a very soft or + * easy-to-mine resource, and 100 represents a very hard or difficult-to-mine resource. + * @param systemRichness - System richness refers to the abundance or concentration of the resource + * being mined in the system. It is usually represented as a percentage, where 100% indicates a high + * concentration of the resource and 0% indicates no concentration or availability of the resource in + * the system. + * @returns the calculated mining duration. + */ function calculateMiningDuration(cargoCapacity, miningRate, resourceHardness, systemRichness) { return resourceHardness > 0 ? Math.ceil(cargoCapacity / (((miningRate / 10000) * (systemRichness / 100)) / (resourceHardness / 100))) : 0; } +/** + * The function BNToBs58 takes a BigNumber as input, converts it to a byte array, and then encodes it + * using the Base58 encoding scheme. + * @param bignumber - The `bignumber` parameter is a number that you want to convert to a Base58 + * encoded string. + * @returns a Base58 encoded string. + */ + function BNToBs58(bignumber) { + const bn = new BrowserAnchor.anchor.BN(bignumber); + const bnArray = bn.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); + return bs58.encode(bnArray); + } + +/** + * The function `getStarbaseFromCoords` retrieves a starbase using it's coordinates. + * @param x - The `x` parameter represents the x-coordinate of the starbase location. It is used to + * search for a starbase that matches the given x-coordinate. + * @param y - The parameter `y` represents the y-coordinate of a starbase. + * @returns The function `getStarbaseFromCoords` returns the starbase object that matches the given + * coordinates (x, y). + */ async function getStarbaseFromCoords(x, y) { - return new Promise(async resolve => { - let xBN = new BrowserAnchor.anchor.BN(x); - let xArr = xBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); - let x58 = bs58.encode(xArr); - let yBN = new BrowserAnchor.anchor.BN(y); - let yArr = yBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); - let y58 = bs58.encode(yArr); - let [starbase] = await sageProgram.account.starbase.all([ - { - memcmp: { - offset: 41, - bytes: x58 - } - }, - { - memcmp: { - offset: 49, - bytes: y58 - } - }, - ]); - resolve(starbase); - }); + const [starbase] = await sageProgram.account.starbase.all([ + { + memcmp: { + offset: 41, + bytes: BNToBs58(x) + } + }, + { + memcmp: { + offset: 49, + bytes: BNToBs58(y) + } + }, + ]); + + return starbase; } +/** + * The function `getPlanetsFromCoords` retrieves planets from coordinates (x, y) using the Sage program. + * @param x - The parameter `x` represents the x-coordinate of a location + * @param y - The parameter `y` represents the y-coordinate of a location. + * @returns an array of planets found in the given coordinates. + */ async function getPlanetsFromCoords(x, y) { - return new Promise(async resolve => { - let xBN = new BrowserAnchor.anchor.BN(x); - let xArr = xBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); - let x58 = bs58.encode(xArr); - let yBN = new BrowserAnchor.anchor.BN(y); - let yArr = yBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, "le", 8); - let y58 = bs58.encode(yArr); - let planets = await sageProgram.account.planet.all([ - { - memcmp: { - offset: 105, - bytes: x58 - } - }, - { - memcmp: { - offset: 113, - bytes: y58 - } - }, - ]); - resolve(planets); - }); + return await sageProgram.account.planet.all([ + { + memcmp: { + offset: 105, + bytes: BNToBs58(x) + } + }, + { + memcmp: { + offset: 113, + bytes: BNToBs58(y) + } + }, + ]); } - async function getStarbasePlayer(userProfile, starbase) { - return new Promise(async resolve => { - let [starbasePlayer] = await sageProgram.account.starbasePlayer.all([ - { - memcmp: { - offset: 9, - bytes: userProfile.toBase58() - } - }, - { - memcmp: { - offset: 73, - bytes: starbase.toBase58() - } - }, - ]); - resolve(starbasePlayer); - }); +/** + * The function `getStarbasePlayer` retrieves a starbase player based on the user profile and starbase + * provided. + * @param playerProfile - The `playerProfile` is the public key of the player profile. + * @param starbase - The `starbase` is the public key for the given starbase. + * @returns the `starbasePlayer` public key. + */ + async function getStarbasePlayer(playerProfile, starbase) { + const [starbasePlayer] = await sageProgram.account.starbasePlayer.all([ + { + memcmp: { + offset: 9, + bytes: playerProfile.pubkey.toBase58() + } + }, + { + memcmp: { + offset: 73, + bytes: starbase.toBase58() + } + }, + ]); + return starbasePlayer; } - async function getFleetCntAtCoords() { - let gridSizeElem = document.querySelector('#fleetGridSelect'); - let gridSize = gridSizeElem.value; - let targetCoordsElem = document.querySelector('#checkFleetCntInput'); - let targetCoords = targetCoordsElem.value; - let fleetGrid = document.querySelector('#fleetGrid'); - let loadingMessage = document.querySelector('#loadingMessage'); + async function getFleetCountAtCoords() { + const gridSize = document.querySelector('#fleetGridSelect').value; + const targetCoords = document.querySelector('#checkFleetCntInput').value; + + const fleetGrid = document.querySelector('#fleetGrid'); + const loadingMessage = document.querySelector('#loadingMessage'); + if (!targetCoords || targetCoords.trim() === '') { loadingMessage.innerText = 'Please enter target coordinates for grid center.'; loadingMessage.style.display = 'block'; fleetGrid.style.display = 'none'; return;// Stop further processing since input is empty or idle } - let [x, y] = targetCoords.split(',').map(coord => parseInt(coord.trim())); - + const [x, y] = targetCoords.split(',').map(coords => parseInt(coords.trim())); fleetGrid.innerHTML = ''; // Clear previous results try { @@ -510,29 +735,21 @@ fleetGrid.style.display = 'none'; for (let i = 0; i < gridSize; i++) { - let row = fleetGrid.insertRow(); + const row = fleetGrid.insertRow(); for (let j = 0; j < gridSize; j++) { - let coordX = x + j - Math.floor(gridSize / 2);// Adjusted for column-first population - let coordY = y + (gridSize-1) - i - Math.floor(gridSize / 2);// Adjusted for descending y value - - let xBN = new BrowserAnchor.anchor.BN(coordX); - let xArr = xBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, 'le', 8); - let x58 = bs58.encode(xArr); + const coordX = x + j - Math.floor(gridSize / 2);// Adjusted for column-first population + const coordY = y + (gridSize-1) - i - Math.floor(gridSize / 2);// Adjusted for descending y value - let yBN = new BrowserAnchor.anchor.BN(coordY); - let yArr = yBN.toTwos(64).toArrayLike(BrowserBuffer.Buffer.Buffer, 'le', 8); - let y58 = bs58.encode(yArr); - - let fleetAccts = await solanaConnection.getProgramAccounts(sageProgramId, { + const fleetAccts = await solanaConnection.getProgramAccounts(sageProgramId, { filters: [ - { memcmp: { offset: 415, bytes: x58 } }, - { memcmp: { offset: 423, bytes: y58 } }, + { memcmp: { offset: 415, bytes: BNToBs58(x) } }, + { memcmp: { offset: 423, bytes: BNToBs58(y) } }, ], }); - let cell = row.insertCell(j); + const cell = row.insertCell(j); // Create a div to hold the content for formatting - let contentDiv = document.createElement('div'); + const contentDiv = document.createElement('div'); contentDiv.style.textAlign = 'center'; // Set the content of the div (coordinates and fleet count) @@ -569,8 +786,6 @@ // Function to calculate gradient color function calculateGradientColor(fleetCount) { const maxFleetCount = 25; // Maximum fleet count for the hottest color - const minFleetCount = 0; // Minimum fleet count for the coolest color - // Map the fleet count to an RGB value (blue to red gradient) const r = Math.floor((fleetCount / maxFleetCount) * 255); const g = 0; @@ -590,1254 +805,1441 @@ loadingMessage.style.display = 'none'; fleetGrid.style.display = 'block'; - //resultDiv.appendChild(fleetGrid); - } catch (error) { console.error('Error fetching fleet information:', error); loadingMessage.innerText = 'Error fetching fleet information'; } } - function waitForTxConfirmation(txHash) { - return new Promise(async resolve => { - let response = null; - try { - let latestBH = await solanaConnection.getLatestBlockhash('confirmed'); - let confirmation = await solanaConnection.confirmTransaction({ - blockhash: latestBH.blockhash, - lastValidBlockHeight: latestBH.lastValidBlockHeight, - signature: txHash - }, 'confirmed'); - response = confirmation; - /* - console.log(confirmation); - let curBlockHeight = await solanaConnection.getEpochInfo().blockHeight; - while (curBlockHeight < latestBH.lastValidBlockHeight && response !== 'confirmed') { - console.log('Fallback confirmation check'); - response = 'confirmed'; - await wait(1000); +/** + * The function `httpMonitor` is an asynchronous function that monitors the status of a transaction. + * @param connection - The `connection` parameter is an object that represents the connection to the + * Solana blockchain network. It is used to interact with the network and perform various operations + * such as sending transactions, querying account information, etc. + * @param txHash - The transaction hash of the transaction you want to monitor. + * @param txn - The `txn` parameter represents the serialized transaction that you want to monitor. It is + * used if the transaction needs to be re-submitted. + * @param lastValidBlockHeight - The `lastValidBlockHeight` parameter represents the block height up to + * which the monitoring function should wait for the transaction to be confirmed. If the current block + * height exceeds or equals `lastValidBlockHeight`, the function will return a timeout error. + * @param lastMinAverageBlockSpeed - The `lastMinAverageBlockSpeed` parameter represents the average + * block speed in the last minute. It is used to calculate the waiting time before checking the + * transaction status again. + * @param [count=1] - The `count` parameter is used to keep track of the number of times the + * `httpMonitor` function has been called recursively. It is initially set to 1 and is incremented each + * time the function is called. + * @returns an object with properties `name` and `err` if a timeout occurs. Otherwise, it returns an + * object with properties `txHash` and `confirmation` if the transaction is confirmed. + */ + async function httpMonitor(connection, txHash, txn, lastValidBlockHeight, lastMinAverageBlockSpeed, count = 1) { + const acceptableCommitments = [connection.commitment, 'finalized']; + try { + let { blockHeight } = await connection.getEpochInfo({ commitment: 'confirmed' }); + if (blockHeight >= lastValidBlockHeight) return { name: 'LudicrousTimoutError', err: `Timed out for ${txHash}` }; + const signatureStatus = await connection.getSignatureStatus(txHash); + + if (signatureStatus.err) { + console.log('HTTP error for', txHash, signatureStatus); + return signatureStatus; + } else if (signatureStatus.value === null || !acceptableCommitments.includes(signatureStatus.value.confirmationStatus)) { + console.log('HTTP not confirmed', txHash, signatureStatus); + await wait(lastMinAverageBlockSpeed * graceBlockWindow); + if (count % 7 == 0) { + console.log('---RESENDTXN---'); + await connection.sendRawTransaction(txn, {skipPreflight: true, maxRetries: 0, preflightCommitment: 'confirmed'}); } - */ - } catch (err) { - console.log('ERROR: ', err); - console.log('ERROR NAME: ', err.name); - response = err; + if (count < 30) return httpMonitor(connection, txHash, txn, lastValidBlockHeight, ++count); + } else if (acceptableCommitments.includes(signatureStatus.value.confirmationStatus) ) { + console.log('HTTP confirmed', txHash, signatureStatus); + return { txHash, confirmation: signatureStatus }; } - resolve(response); - }); + } catch (error) { + console.log(`HTTP connection error: ${txHash}`, error); + return error; + } + + return { name: 'LudicrousTimoutError', err: `Timed out for ${txHash}` }; } - function txSignAndSend(ix) { - return new Promise(async resolve => { - let tx = new solanaWeb3.Transaction(); - console.log('---INSTRUCTION---'); - console.log(ix); - if (ix.constructor === Array) { - ix.forEach(item => tx.add(item.instruction)) - } else { - tx.add(ix.instruction); - } - let latestBH = await solanaConnection.getLatestBlockhash('confirmed'); - tx.recentBlockhash = latestBH.blockhash; - tx.lastValidBlockHeight = latestBH.lastValidBlockHeight; - tx.feePayer = userPublicKey; - tx.signer = userPublicKey; - let txSigned = null; - if (typeof solflare === 'undefined') { - txSigned = await solana.signAllTransactions([tx]); +/** + * The function `sendLudicrousTransaction` sends a transaction using a connection object, logs the + * transaction hash, and then calls the `httpMonitor` function to monitor the transaction's status. + * @param txn - The `txn` parameter is the transaction object that you want to send. It contains all + * the necessary information for the transaction, such as the sender, recipient, amount, and any + * additional data or instructions. + * @param lastValidBlockHeight - The `lastValidBlockHeight` parameter represents the height of the last + * valid block on the blockchain. It is used in the `sendLudicrousTransaction` function to monitor the + * transaction's confirmation status and ensure that it is included in a block after the specified + * height. + * @param connection - The `connection` parameter is an object that represents the connection to the + * Solana blockchain network. It is used to interact with the network and perform various operations + * such as sending transactions, querying account information, etc. + * @returns The function `sendLudicrousTransaction` is returning the result of the `httpMonitor` + * function. + */ + async function sendLudicrousTransaction(txn, lastValidBlockHeight, connection) { + console.log('---SENDTXN---'); + let txHash = await connection.sendRawTransaction(txn, {skipPreflight: true, maxRetries: 0, preflightCommitment: 'confirmed'}); + console.log(txHash); + + const recentPerformanceSamples = await connection.getRecentPerformanceSamples(1); + const { samplePeriodSecs, numSlots } = recentPerformanceSamples[0]; + const lastMinAverageBlockSpeed = Math.floor(samplePeriodSecs * 1000 / numSlots); + + return await httpMonitor(connection, txHash, txn, lastValidBlockHeight, lastMinAverageBlockSpeed); + } + +/** + * The function `batchTransactions` is an asynchronous function that takes an array of instructions + * (`ixs`) and batches them into multiple transactions based on a maximum transaction size. + * @param ixs - An array of instructions (ixs) to be batched together. Each instruction represents + * a specific action to be performed on the Solana blockchain. + * @param [txs] - The `txs` parameter is an array that stores the transactions that have been batched + * so far. It is an optional parameter and is initially an empty array. Each transaction is added to + * this array before recursively calling the `batchTransactions` function again with the remaining + * transactions. + * @returns The function `batchTransactions` returns an array of transactions (`txs`). + */ + async function batchTransactions(ixs, blockhash, lastValidBlockHeight, txs = []) { + const tx = new solanaWeb3.Transaction(); + + for (let index = 0; index < ixs.length; index++) { + let before = ixs.slice(0, index + 1); + const after = ixs.slice(-(ixs.length - (index + 1))); + + if (getTransactionSize(before, provider.publicKey) > MAX_TRANSACTION_SIZE) { + before = ixs.slice(0, index); } else { - txSigned = await solflare.signAllTransactions([tx]); + if (before.length != after.length) continue; } - let txSerialized = txSigned[0].serialize(); - let txHash = await solanaConnection.sendRawTransaction(txSerialized, {skipPreflight: true, preflightCommitment: 'confirmed'}); - console.log('---TXHASH---'); - console.log(txHash); - let confirmation = await waitForTxConfirmation(txHash); + + before.forEach(ix => tx.add(ix.instruction)); + tx.recentBlockhash = blockhash; + tx.lastValidBlockHeight = lastValidBlockHeight; + tx.feePayer = provider.publicKey; + tx.signer = provider.publicKey; + txs.push(tx); + if (after.length > 0) return batchTransactions(after, blockhash, lastValidBlockHeight, txs); + } + return txs; + } + +/** + * The function `signAndSend` signs and sends a batch of transactions to the Solana blockchain, + * handling errors and retries if necessary. + * @param ixs - The parameter `ixs` is an array of instructions that you want to include in the + * transaction. Each instruction represents an action to be performed on the Solana blockchain. + * @returns an array of transaction results. + */ + async function signAndSend(ixs) { + let { blockhash, lastValidBlockHeight } = await solanaConnection.getLatestBlockhash('confirmed'); + lastValidBlockHeight = lastValidBlockHeight - 150; + + if (ixs.constructor !== Array) ixs = [ixs]; + + console.log('---INSTRUCTION---'); + console.log(ixs); + const txns = await batchTransactions(ixs, blockhash, lastValidBlockHeight); + const signedTxns = await provider.signAllTransactions(txns); + + let txResults = []; + for (const txn of signedTxns) { + ({ txHash, confirmation} = await sendLudicrousTransaction(txn.serialize(), lastValidBlockHeight, solanaConnection)); console.log('---CONFIRMATION---'); console.log(confirmation); - let txResult = await solanaConnection.getTransaction(txHash, {commitment: 'confirmed', preflightCommitment: 'confirmed', maxSupportedTransactionVersion: 1}); - if (confirmation.name == 'TransactionExpiredBlockheightExceededError' && !txResult) { + if ((confirmation.name == 'TransactionExpiredBlockheightExceededError' || confirmation.name == 'LudicrousTimoutError')) { console.log('-----RETRY-----'); - txResult = await txSignAndSend(ix); - } - if (!confirmation.name) { - while (!txResult) { - await wait(2000); - txResult = await solanaConnection.getTransaction(txHash, {commitment: 'confirmed', preflightCommitment: 'confirmed', maxSupportedTransactionVersion: 1}); - } + return signAndSend(ix); } + + const txResult = await solanaConnection.getTransaction(txHash, {commitment: 'confirmed', preflightCommitment: 'confirmed', maxSupportedTransactionVersion: 1}); console.log('txResult: ', txResult); - resolve(txResult); - }); + txResults.push(txResult); + } + return txResults; } - //bitshift taken from @staratlas/sage permissions.ts - function buildPermissions(input) { - const out = [0,0,0,0,0,0,0,0]; - out[0] = new BrowserAnchor.anchor.BN( - (input[0][0] ? 1 << 0 : 0) | - (input[0][1] ? 1 << 1 : 0) | - (input[0][2] ? 1 << 2 : 0) | - (input[0][3] ? 1 << 3 : 0) | - (input[0][4] ? 1 << 4 : 0) | - (input[0][5] ? 1 << 5 : 0) | - (input[0][6] ? 1 << 6 : 0) | - (input[0][7] ? 1 << 7 : 0)); - out[1] = new BrowserAnchor.anchor.BN( - (input[1][0] ? 1 << 0 : 0) | - (input[1][1] ? 1 << 1 : 0) | - (input[1][2] ? 1 << 2 : 0) | - (input[1][3] ? 1 << 3 : 0) | - (input[1][4] ? 1 << 4 : 0) | - (input[1][5] ? 1 << 5 : 0) | - (input[1][6] ? 1 << 6 : 0) | - (input[1][7] ? 1 << 7 : 0)); - out[2] = new BrowserAnchor.anchor.BN( - (input[2][0] ? 1 << 0 : 0) | - (input[2][1] ? 1 << 1 : 0) | - (input[2][2] ? 1 << 2 : 0) | - (input[2][3] ? 1 << 3 : 0) | - (input[2][4] ? 1 << 4 : 0) | - (input[2][5] ? 1 << 5 : 0) | - (input[2][6] ? 1 << 6 : 0) | - (input[2][7] ? 1 << 7 : 0)); - console.log('out: ', out); - return out; - } - - async function addKeyToProfile(newKey) { - return new Promise(async resolve => { - let permissions = buildPermissions([[true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true],[true,true,true,true,true,true,true,true]]); - //let permissions = buildPermissions([[true,false,false,false,false,false,false,false],[false,false,true,true,true,false,false,true],[true,false,true,true,true,false,true,true]]); - /* - let keys = [{key: 'newKey', expireTime: new BrowserAnchor.anchor.BN(-1), permissions: permissions}]; - let tempKeys = keys.map( - (key) => ({ - sageProgramId, - expireTime: - key.expireTime, - permissions: key.permissions, - }), - ) - */ - let txResult = {}; - if (newKey.length > 0) { - let tx = { instruction: await profileProgram.methods.addKeys(0, 0, [{ - scope: sageProgramId, - expireTime: new BrowserAnchor.anchor.BN(-1), - permissions: permissions - }]).accountsStrict({ - funder: userPublicKey, - profile: userProfileAcct, - key: userPublicKey, - systemProgram: solanaWeb3.SystemProgram.programId - }).remainingAccounts([{ - pubkey: new solanaWeb3.PublicKey(newKey), - isSigner: false, - isWritable: false - }]).instruction()} - txResult = await txSignAndSend(tx); - } else { - txResult = {name: "InputNeeded"}; - } - resolve(txResult); - }); +/** + * The `execScan` triggers a scan for survey data units. + * @param fleet - The `fleet` parameter represents the fleet object. + * @param playerProfile - The `playerProfile` parameter represents the user's profile information. It + * includes properties such as `index`, `pubkey`, and `faction`. + * @returns the transaction result of the scan. + */ + async function execScan(fleet, playerProfile) { + const tx = { instruction: await sageProgram.methods.scanForSurveyDataUnits({keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index)}).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey + }, + gameState: sageGameAcct.account.gameState + }, + surveyDataUnitTracker: sageSDUTrackerAcct.publicKey, + surveyDataUnitTrackerSigner: sageSDUTrackerAcct.account.signer, + cargoHold: fleet.account.cargoHold, + sduCargoType: sduCargoTypeAcct.publicKey, + repairKitCargoType: repairKitCargoTypeAcct.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + sduTokenFrom: await ResourceTokens.getPodToken({ name: 'sdu' }), + sduTokenTo: ResourceTokens.sdu.publicKey, + repairKitTokenFrom: await ResourceTokens.getPodToken({ name: 'toolkit', pod: fleet.account.cargoHold }), + repairKitMint: sageGameAcct.account.mints.repairKit, + cargoProgram: cargoProgramId, + tokenProgram, + recentSlothashes: RecentSlotHashes, + instructionsSysvar: InstructionsSysVar + }).instruction()} + return await signAndSend(tx); } - async function removeKeyFromProfile() { - return new Promise(async resolve => { - let tx = { instruction: await profileProgram.methods.removeKeys(0, [new BrowserAnchor.anchor.BN(1), new BrowserAnchor.anchor.BN(2)]).accountsStrict({ - funder: userPublicKey, - profile: userProfileAcct, - key: userPublicKey, - systemProgram: solanaWeb3.SystemProgram.programId - }).instruction()} - let txResult = await txSignAndSend(tx); - resolve(txResult); - }); - } +/** + * The `execSubwarp` function calculates the distance between two coordinates, checks if the fleet has + * enough fuel to subwarp, and if it does then initiates a subwarp to the destination coordinates. + * @param fleet - The `fleet` parameter represents a fleet of ships that will be used for the subwarp + * operation. It contains information about the fleet, such as its origin and destination coordinates, + * maximum warp distance, and fuel tank account. + * @param origin - The `origin` parameter is optional, it is the origin point for the subwarp. + * Defaults to the origin in the fleet's config file. It is an array containing the X and Y coordinates. + * @param destination - The `destination` parameter is optional, it is the destination point for the subwarp. + * Defaults to the destination in the fleet's config file. It is an array containing the X and Y coordinates. + * @returns the duration for subwarp and the transaction result from Solana for entering subwarp. + */ + async function execSubwarp(fleet, origin, destination) { + const { subwarpSpeed } = fleet.account.stats.movementStats; + const [destX, destY] = (destination || fleet.destination.coords).split(',').map(item => item.trim()); + const [originX, originY] = (origin || fleet.origin.coords).split(',').map(item => item.trim()); + + const moveDistance = calculateMovementDistance([originX, originY], [destX, destY]); + const duration = subwarpSpeed > 0 ? (moveDistance / subwarpSpeed / GLOBAL_SCALE_DECIMALS_6) : 0; + const subwarpCost = calculateSubwarpFuelBurn(fleet, moveDistance); + + const fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.fuelTank, { programId: tokenProgram }); + let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); + currentFuel = currentFuel.account.data.parsed.info.tokenAmount.uiAmount || 0; - async function execScan(fleet) { - return new Promise(async resolve => { - // FIX: need to figure out how to initialize fleet.sduToken - // look for await gr.getAccountInfo(Br) || (Rr.push(srcExports$2.createAssociatedTokenAccount(qr, Qr, !0).instructions) - let tx = { instruction: await sageProgram.methods.scanForSurveyDataUnits({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx)}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, // sageProgram.fleet.publicKey - owningProfile: userProfileAcct, // from API - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey // wallet.publicKey - }, - gameId: sageGameAcct.publicKey // sageProgram.game.publicKey + if (currentFuel < subwarpCost) { + console.log(`[${fleet.label}] Unable to move, lack of fuel`); + return fleet.state = 'ERROR: Not enough fuel'; + } + + const tx = { instruction: await sageProgram.methods.startSubwarp({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index), + toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)] + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey }, - gameState: sageGameAcct.account.gameState // sageProgram.game.gameState + gameId: sageGameAcct.publicKey }, - surveyDataUnitTracker: sageSDUTrackerAcct.publicKey, // sageProgram.SurveyDataUnitTracker.publicKey - surveyDataUnitTrackerSigner: sageSDUTrackerAcct.account.signer, // sageProgram.SurveyDataUnitTracker.signer - cargoHold: fleet.cargoHold, // sageProgram.fleet.cargoHold - sduCargoType: sduCargoTypeAcct.publicKey, // cargoProgram.cargoType - memcmp offset 41 'SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM' - repairKitCargoType: repairKitCargoTypeAcct.publicKey, // cargoProgram.cargoType - memcmp offset 41 'tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL' - cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, // sageProgram.game.cargo - sduTokenFrom: sduTokenFrom, // calculated - sduTokenTo: fleet.sduToken, - repairKitTokenFrom: fleet.repairKitToken, - repairKitMint: sageGameAcct.account.mints.repairKit, // sageProgram.game.repairKit - cargoProgram: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), // static - tokenProgram: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), // static - recentSlothashes: new solanaWeb3.PublicKey('SysvarS1otHashes111111111111111111111111111'), // static - instructionsSysvar: new solanaWeb3.PublicKey('Sysvar1nstructions1111111111111111111111111') // static - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + gameState: sageGameAcct.account.gameState + }, + }).instruction()} + const txResult = await signAndSend(tx); + return { duration, txResult }; } - async function execSubwarp(fleet, destX, destY) { - return new Promise(async resolve => { - let tx = { instruction: await sageProgram.methods.startSubwarp({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx), toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)]}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey - }, - gameState: sageGameAcct.account.gameState - }, - })/*.remainingAccounts([ - { - pubkey: userProfileAcct, - isSigner: false, - isWritable: true - }, - { - pubkey: fleet.fuelTank, - isSigner: false, - isWritable: true - }, - { - pubkey: fuelCargoTypeAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.account.cargo.statsDefinition, - isSigner: false, - isWritable: false - }, - { - pubkey: fleet.fuelToken, - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.mints.fuel, - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.gameState, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), - isSigner: false, - isWritable: false - }, - { - pubkey: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - isSigner: false, - isWritable: false - }, - ])*/.instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + /** + * The function `execExitSubwarp` executes the command (submits transaction) to exit subwarp to Solana. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns the transaction result from Solana for exiting subwarp. + */ + async function execExitSubwarp(fleet) { + const tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).remainingAccounts([ + { + pubkey: playerProfile.pubkey, + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + isSigner: false, + isWritable: true + }, + { + pubkey: fuelCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.account.cargo.statsDefinition, + isSigner: false, + isWritable: false + }, + { + pubkey: ResourceTokens.fuel.publicKey, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.fuel, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.gameState, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: cargoProgramId, + isSigner: false, + isWritable: false + }, + { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }, + ]).instruction()} + return await signAndSend(tx); } - async function execExitSubwarp(fleet) { - return new Promise(async resolve => { - let tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ - fleet: fleet.publicKey - }).remainingAccounts([ - { - pubkey: userProfileAcct, - isSigner: false, - isWritable: true - }, - { - pubkey: fleet.fuelTank, - isSigner: false, - isWritable: true - }, - { - pubkey: fuelCargoTypeAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.account.cargo.statsDefinition, - isSigner: false, - isWritable: false - }, - { - pubkey: fleet.fuelToken, - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.mints.fuel, - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.gameState, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), - isSigner: false, - isWritable: false +/** + * The `execWarp` function calculates the distance between two coordinates, checks if the fleet has + * enough fuel to warp, and if it does then initiates a warp to the destination coordinates. + * @param fleet - The fleet parameter represents a fleet of ships that will be used for the warp + * operation. It contains information about the fleet, such as its origin and destination coordinates, + * maximum warp distance, and fuel tank account. + * @param origin - The `origin` parameter is optional, it is the origin point for the warp. + * Defaults to the origin in the fleet's config file. It is an array containing the X and Y coordinates. + * @param destination - The `destination` parameter is optional, it is the destination point for the warp. + * Defaults to the destination in the fleet's config file. It is an array containing the X and Y coordinates. + * @returns the duration for warp and the transaction result from Solana for entering warp. + */ + async function execWarp(fleet, origin, destination) { + const { warpSpeed, maxWarpDistance, warpCoolDown, subwarpSpeed } = fleet.account.stats.movementStats; + const [destX, destY] = (destination || fleet.destination.coords).split(',').map(item => item.trim()); + const [originX, originY] = (origin || fleet.origin.coords).split(',').map(item => item.trim()); + + let moveDistance = calculateMovementDistance([originX, originY], [destX, destY]); + let warpCount = 0; + if (moveDistance > (maxWarpDistance / 100)) { + warpCount = maxWarpDistance > 0 ? (moveDistance / maxWarpDistance / GLOBAL_SCALE_DECIMALS_2) : 1; + const warpX = Math.trunc((destX - originX) / warpCount); + const warpY = Math.trunc((destY - originY) / warpCount); + + destX = originX + warpX; + destY = originY + warpY; + + moveDistance = calculateMovementDistance([originX, originY], [destX,destY]); + } + + const duration = warpSpeed > 0 ? (moveDistance / warpSpeed / GLOBAL_SCALE_DECIMALS_6) : 0; + const warpCost = calculateWarpFuelBurn(fleet, moveDist); + + const fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.fuelTank, { programId: tokenProgram }); + let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); + currentFuel = currentFuel.account.data.parsed.info.tokenAmount.uiAmount || 0; + + if (currentFuel < (warpCost * warpCount)) { + console.log(`[${fleet.label}] Unable to move, lack of fuel`); + return fleet.state = 'ERROR: Not enough fuel'; + } + + const tx = { instruction: await sageProgram.methods.warpToCoordinate({keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index), toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)]}).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey }, - { - pubkey: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - isSigner: false, - isWritable: false + gameState: sageGameAcct.account.gameState + }, + fuelTank: fleet.account.fuelTank, + cargoType: fuelCargoTypeAcct.publicKey, + statsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + tokenMint: sageGameAcct.account.mints.fuel, + cargoProgram: cargoProgramId, + tokenProgram + }).instruction()} + const txResult = await signAndSend(tx); + return { duration, warpCount, txResult }; + } + +/** + * The function `execExitWarp` executes the command (submits transaction) to exit warp to Solana. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns the transaction result from Solana for exiting warp. + */ + async function execExitWarp(fleet) { + const tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).instruction()} + return await signAndSend(tx); + } + +/** + * The `execDock` function takes a fleet and coordinates as input, retrieves the starbase and starbase + * player associated with the coordinates, and then executes a transaction to dock the fleet at the + * starbase. Should be called before {@link handleReturnTrip} function. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @param coords - The `coords` parameter is an optional string representing the coordinates of a starbase. + * Defaults to `fleet.destination.coords`. It is in the format "x,y" where `x` and `y` are the x and y coordinates + * respectively. + * @returns the transaction result from Solana for docking. + */ + async function execDock(fleet, coords) { + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const tx = { instruction: await sageProgram.methods.idleToLoadingBay(new BrowserAnchor.anchor.BN(playerProfile.index)).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey }, - ]).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + } + }).instruction()} + fleet.state = 'Docking'; + return await signAndSend(tx); } - async function execWarp(fleet, destX, destY) { - return new Promise(async resolve => { - let tx = { instruction: await sageProgram.methods.warpToCoordinate({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx), toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)]}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey +/** + * The `execUndock` function undocks a fleet from a starbase. Should be called after {@link handleReturnTrip} + * function. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @param coords - The `coords` parameter is an optional string representing the coordinates of a starbase. + * Defaults to `fleet.origin.coords`. It is in the format "x,y" where `x` and `y` are the x and y coordinates + * respectively. + * @returns the transaction result from Solana for undocking. + */ + async function execUndock(fleet, coords) { + const [starbaseX, starbaseY] = (coords || fleet.origin.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const tx = { instruction: await sageProgram.methods.loadingBayToIdle(new BrowserAnchor.anchor.BN(playerProfile.index)).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey }, - gameState: sageGameAcct.account.gameState + gameId: sageGameAcct.publicKey }, - fuelTank: fleet.fuelTank, - cargoType: fuelCargoTypeAcct.publicKey, - statsDefinition: sageGameAcct.account.cargo.statsDefinition, - tokenFrom: fleet.fuelToken, - tokenMint: sageGameAcct.account.mints.fuel, - cargoProgram: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), - tokenProgram: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + } + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction()} + fleet.state = 'Undocking'; + return await signAndSend(tx); } - async function execExitWarp(fleet) { - return new Promise(async resolve => { - let tx = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ - fleet: fleet.publicKey - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + // @todo - documentation + async function determineDefaultLocation(fleet, extra, reverse) { + let location = 'destination'; // default location + for (const [key, value] of Object.entries(fleet)) { + if (value.coords && value.coords == `"${extra[0]},${extra[1]}"`){ + location = key; + break; + } + } + + return reverse ? (location == 'destination' ? 'origin' : 'destination') : location; } - async function execDock(fleet, dockCoords) { - return new Promise(async resolve => { - let starbaseX = dockCoords.split(',')[0].trim(); - let starbaseY = dockCoords.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(starbaseX, starbaseY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); - console.log('-----DEBUG-----'); - console.log(userProfileKeyIdx); - console.log(new BrowserAnchor.anchor.BN(userProfileKeyIdx)); - let tx = { instruction: await sageProgram.methods.idleToLoadingBay(new BrowserAnchor.anchor.BN(userProfileKeyIdx)).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey + // @todo - redo documentation + // @todo - implement fuel and ammo logic +/** + * The function `execCargoFromFleetToStarbase` transfers cargo from a fleet to a starbase. + * @param fleet - The `fleet` parameter represents the fleet object that contains information about the + * fleet, such as its account, destination coordinates, and cargo hold. + * @param options - The `options` parameter is an object that contains the following properties: + * `coords`, `resupply`, and `supplies`. `coords` is an optional parameter, defaults to `fleet.destination.coords`, + * in the format "x,y" where `x` and `y` are the x and y coordinates respectively. `resupply` is an optional + * parameter, a boolean if set will default `supplies` to that on the `fleet` object. `supplies` is an optional + * parameter which is an object "{'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim': amount}" + * @returns the transaction result from Solana for transferring cargo from a fleet to a starbase. + */ + async function execCargoFromFleetToStarbase(fleet, options) { + let { coords, supplies, dump } = options; + const { extra } = await getCurrentFleet(fleet); + const location = await determineDefaultLocation(fleet, extra); // needs to be opposite of current location + + supplies = supplies || fleet[location].supplies; + const [starbaseX, starbaseY] = (coords || fleet[location].coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ + { + memcmp: { + offset: 41, + bytes: starbasePlayer.publicKey.toBase58(), + }, + }, + ]); + const starbasePlayerCargoHold = starbasePlayerCargoHolds.find(item => item.account.openTokenAccounts > 0); + let resourcesToUnload = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + if (supplies && !dump) resourcesToUnload.value = resourcesToUnload.value.filter(item => Object.keys(supplies).includes(item.account.mint.toString())); + + let ixs = []; + for (let resource of resourcesInCargoHold.value) { + const resourceString = resource.account.mint.toString() + let amount = resource.account.data.parsed.info.tokenAmount.uiAmount || 0; + if (!dump) amount = Math.min(supplies[resourceString] || 0, amount); + if (amount > 0) { + const resourceCargoType = cargoTypes.find(item => item.account.mint.toString() == resourceString); + const ix = await sageProgram.methods.withdrawCargoFromFleet({ + amount: new BrowserAnchor.anchor.BN(amount), + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.account, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey }, - gameId: sageGameAcct.publicKey + gameState: sageGameAcct.account.gameState }, - gameState: sageGameAcct.account.gameState - }, - starbaseAndStarbasePlayer: { - starbase: starbase.publicKey, - starbasePlayer: starbasePlayer.publicKey - } - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + cargoPodFrom: fleet.account.cargoHold, + cargoPodTo: starbasePlayerCargoHold.publicKey, + cargoType: resourceCargoType.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: fleet.account.cargoHold, + tokenTo: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: starbasePlayerCargoHold.publicKey }), + tokenMint: resourceString, + fundsTo: provider.publicKey, + cargoProgram: cargoProgramId, + tokenProgram + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction() + ixs.push({ instruction: ix }) + } + } + fleet.state = 'Unloading'; + if (ixs.length > 0) await signAndSend(ixs); + fleet.state = 'Docked'; } - async function execUndock(fleet, dockCoords) { - return new Promise(async resolve => { - let starbaseX = dockCoords.split(',')[0].trim(); - let starbaseY = dockCoords.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(starbaseX, starbaseY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); - let tx = { instruction: await sageProgram.methods.loadingBayToIdle(new BrowserAnchor.anchor.BN(userProfileKeyIdx)).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey + // @todo - redo documentation +/** + * The function `execCargoFromStarbaseToFleet` transfers cargo from a starbase to a fleet in a game, + * based on the provided options. + * @param fleet - The `fleet` parameter represents the fleet object that contains information about the + * fleet, such as its account, destination coordinates, and cargo hold. + * @param options - The `options` parameter is an object that contains the following properties: + * `coords`, `resupply`, and `supplies`. `coords` is an optional parameter, defaults to `fleet.destination.coords`, + * in the format "x,y" where `x` and `y` are the x and y coordinates respectively. `resupply` is an optional + * parameter, a boolean if set will default `supplies` to that on the `fleet` object. `supplies` is an optional + * parameter which is an object "{'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim': amount}" + * @returns the transaction result(s) from Solana for transferring cargo from a starbase to a fleet. + */ + async function execCargoFromStarbaseToFleet(fleet, options) { + let { coords, supplies } = options; + const { extra } = await getCurrentFleet(fleet); + const location = await determineDefaultLocation(fleet, extra, true); + + supplies = supplies || fleet[location].supplies; + const [starbaseX, starbaseY] = (coords || fleet[location].coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + const starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ + { + memcmp: { + offset: 41, + bytes: starbasePlayer.publicKey.toBase58(), + }, + }, + ]); + + const starbasePlayerCargoHold = starbasePlayerCargoHolds.find(item => item.account.openTokenAccounts > 0); + let resourcesToLoad = await solanaConnection.getParsedTokenAccountsByOwner(starbasePlayerCargoHold.publicKey, {programId: tokenProgram}); + if (supplies) resourcesToLoad.value = resourcesToLoad.value.filter(item => Object.keys(supplies).includes(item.account.mint.toString())); + + let ixs = []; + for (let resource of resourcesInCargoHold.value) { + const resourceString = resource.account.mint.toString() + const amount = Math.min(supplies[resourceString] || 0, resource.account.data.parsed.info.tokenAmount.uiAmount || 0); + if (amount > 0) { + const resourceCargoType = cargoTypes.find(item => item.account.mint.toString() == resourceString); + const ix = { instruction: await sageProgram.methods.depositCargoToFleet({ + amount: new BrowserAnchor.anchor.BN(amount), + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey }, - gameId: sageGameAcct.publicKey + gameState: sageGameAcct.account.gameState }, - gameState: sageGameAcct.account.gameState - }, - starbaseAndStarbasePlayer: { - starbase: starbase.publicKey, - starbasePlayer: starbasePlayer.publicKey - } - }).remainingAccounts([{ - pubkey: starbase.publicKey, - isSigner: false, - isWritable: false - }]).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); + fundsTo: provider.publicKey, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + cargoPodFrom: starbasePlayerCargoHold.publicKey, + cargoPodTo: fleet.account.cargoHold, + cargoType: resourceCargoType.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: starbasePlayerCargoHold.publicKey }), + tokenTo: await ResourceTokens.getPodToken({ publicKey: resource.publicKey, pod: fleet.account.cargoHold }), + tokenMint: resourceString, + cargoProgram: cargoProgramId, + tokenProgram + }).remainingAccounts([{ + pubkey: starbase.publicKey, + isSigner: false, + isWritable: false + }]).instruction()} + ixs.push({ instruction: ix }) + } + } + fleet.state = 'Loading'; + if (ixs.length > 0) await signAndSend(ixs); + fleet.state = 'Docked'; } - async function execCargoFromFleetToStarbase(fleet, fleetCargoPod, tokenMint, dockCoords, amount) { - return new Promise(async resolve => { - let starbaseX = dockCoords.split(',')[0].trim(); - let starbaseY = dockCoords.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(starbaseX, starbaseY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); - let starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ +/** + * The `getMiningDetails` function retrieves mining details based on the mine resource and coordinates + * provided. + * @param mineResource - The `mineResource` parameter is the public key that represents the resource you + * want to mine. + * @param coords - The `coords` parameter is the coordinates of a location. It is used to retrieve the + * planets associated with those coordinates. + * @returns The `getMiningDetails` function returns an object with three properties: `mineItem`, + * `planet`, and `sageResource`. + */ + async function getMiningDetails(mineResource, coords) { + const planets = await getPlanetsFromCoords(coords); + const [mineItem] = await sageProgram.account.mineItem.all([ + { + memcmp: { + offset: 105, + bytes: mineResource, + }, + }, + ]); + + let planet, sageResource; + for (let planetCheck of planets) { + const resourceCheck = await sageProgram.account.resource.all([ { memcmp: { offset: 41, - bytes: starbasePlayer.publicKey.toBase58(), + bytes: planetCheck.publicKey, }, }, - ]); - let starbasePlayerCargoHold = starbasePlayerCargoHolds.find(item => item.account.openTokenAccounts > 0); - let [starbaseCargoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - starbasePlayerCargoHold.publicKey.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(tokenMint).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleetCargoPod.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(tokenMint).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let fleetCurrentPod = await solanaConnection.getParsedTokenAccountsByOwner(fleetCargoPod, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentResource = fleetCurrentPod.value.find(item => item.account.data.parsed.info.mint === tokenMint); - let fleetResourceAcct = currentResource ? currentResource.pubkey : fleetResourceToken; - let resourceCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == tokenMint); - await solanaConnection.getAccountInfo(starbaseCargoToken) || await createProgramDerivedAccount(starbaseCargoToken, starbasePlayerCargoHold.publicKey, new solanaWeb3.PublicKey(tokenMint)); - let tx = { instruction: await sageProgram.methods.withdrawCargoFromFleet({ amount: new BrowserAnchor.anchor.BN(amount), keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx) }).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey + { + memcmp: { + offset: 73, + bytes: mineItem.publicKey, }, - gameState: sageGameAcct.account.gameState }, - starbaseAndStarbasePlayer: { - starbase: starbase.publicKey, - starbasePlayer: starbasePlayer.publicKey + ]); + if (resourceCheck.length > 0) { + [sageResource] = resourceCheck; + planet = planetCheck + break; + } + } + return { mineItem, planet, sageResource }; + } + + /** + * The `execStartMining` function starts the mining process for a fleet. + * @param fleet - The `fleet` parameter represents the fleet object, which contains information + * about the fleet such as its account, destination coordinates, and mining stats. + * @param options - The `options` is an object that contains the following optional parameters: + * - `coords` in the format "x,y" where `x` and `y` are the x and y coordinates respectively. + * Defaults to `fleet.destination.coords`. + * - `mineResource` is the name of the resource you want to mine. + * Defaults to `fleet.mineResource`. + * - `amount` is the amount you want to mine. If `amount` > `maxCargoCapacity`, `maxCargoCapacity` + * is used. Defaults to the `maxCargoCapacity`. + * @returns an object containing `duration` and the transaction result from Solana. + */ + async function execStartMining(fleet, options) { + const { starbase, starbasePlayer, planet } = options; + const ix = { instruction: await sageProgram.methods.startMiningAsteroid({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey + }, + gameId: sageGameAcct.publicKey }, - cargoPodFrom: fleetCargoPod, // fleet.cargoHold, - cargoPodTo: starbasePlayerCargoHold.publicKey, - cargoType: resourceCargoTypeAcct.publicKey, - cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, - tokenFrom: fleetResourceAcct, - tokenTo: starbaseCargoToken, - tokenMint: tokenMint, - fundsTo: userPublicKey, - cargoProgram: cargoProgramId, - tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - }).remainingAccounts([{ + gameState: sageGameAcct.account.gameState + }, + starbaseAndStarbasePlayer: { + starbase: starbase.publicKey, + starbasePlayer: starbasePlayer.publicKey + }, + mineItem : mineItem.publicKey, + resource: sageResource.publicKey, + planet: planet.publicKey, + }).instruction()} + fleet.state = 'Mining'; + return await signAndSend(ix); + } + +/** + * The `execStopMining` function stops a fleet from mining a resource. + * @param fleet - The `fleet` parameter represents the fleet object, which contains information about + * the fleet such as its public key, account details, and cargo hold. + * @param options - The `options` is an object that contains the following optional parameters: + * - `coords` in the format "x,y" where `x` and `y` are the x and y coordinates respectively. + * Defaults to `fleet.destination.coords`. + * - `mineResource` is the name of the resource you want to mine. + * Defaults to `fleet.mineResource`. + * @returns the transaction result from Solana for stopping mining. + */ + async function execStopMining(fleet, options) { + let { coords, mineResource } = options; + mineResource = ResourceTokens[mineResource || fleet.mineResource]; + + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const { mineItem, planet, sageResource } = await getMiningDetails(mineResource.publicKey, [starbaseX, starbaseY]); + + const foodCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.food); + const ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); + const resourceCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == resourceToken.toString()); + + const tx1 = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ + fleet: fleet.publicKey + }).remainingAccounts([ + { + pubkey: playerProfile.faction.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: fleet.account.cargoHold, + isSigner: false, + isWritable: true + }, + { + pubkey: fleet.account.ammoBank, + isSigner: false, + isWritable: true + }, + { + pubkey: mineItem.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageResource.publicKey, + isSigner: false, + isWritable: true + }, + { + pubkey: planet.publicKey, + isSigner: false, + isWritable: true + }, + { pubkey: starbase.publicKey, isSigner: false, isWritable: false - }]).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); - }); - } - - async function execCargoFromStarbaseToFleet(fleet, cargoPodTo, tokenTo, tokenMint, cargoType, dockCoords, amount) { - return new Promise(async resolve => { - await solanaConnection.getAccountInfo(tokenTo) || await createProgramDerivedAccount(tokenTo, cargoPodTo, new solanaWeb3.PublicKey(tokenMint)); - let starbaseX = dockCoords.split(',')[0].trim(); - let starbaseY = dockCoords.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(starbaseX, starbaseY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); - let starbasePlayerCargoHolds = await cargoProgram.account.cargoPod.all([ - { - memcmp: { - offset: 41, - bytes: starbasePlayer.publicKey.toBase58(), + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'food', pod: fleet.account.cargoHold }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ name: 'ammo', pod: fleet.account.ammoBank }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ publicKey: mineResource.publicKey, pod: mineItem.publicKey }), + isSigner: false, + isWritable: true + }, + { + pubkey: await ResourceTokens.getPodToken({ publicKey: mineResource.publicKey, pod: fleet.account.cargoHold }), + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.food, + isSigner: false, + isWritable: true + }, + { + pubkey: sageGameAcct.account.mints.ammo, + isSigner: false, + isWritable: true + }, + { + pubkey: foodCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: ammoCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: resourceCargoTypeAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.account.cargo.statsDefinition, + isSigner: false, + isWritable: false + }, + { + pubkey: sageGameAcct.publicKey, + isSigner: false, + isWritable: false + }, + { + pubkey: cargoProgramId, + isSigner: false, + isWritable: false + }, + { + pubkey: tokenProgram, + isSigner: false, + isWritable: false + }, + ]).instruction()} + + const tx2 = { instruction: await sageProgram.methods.stopMiningAsteroid({ + keyIndex: new BrowserAnchor.anchor.BN(playerProfile.index) + }).accountsStrict({ + gameAccountsFleetAndOwner: { + gameFleetAndOwner: { + fleetAndOwner: { + fleet: fleet.publicKey, + owningProfile: playerProfile.pubkey, + owningProfileFaction: playerProfile.faction.publicKey, + key: provider.publicKey }, + gameId: sageGameAcct.publicKey }, - ]); - let starbasePlayerCargoHold = starbasePlayerCargoHolds[0]; - let mostFound = 0; - for (let cargoHold of starbasePlayerCargoHolds) { - if (cargoHold.account && cargoHold.account.openTokenAccounts > 0) { - let cargoHoldTokens = await solanaConnection.getParsedTokenAccountsByOwner(cargoHold.publicKey, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let cargoHoldFound = cargoHoldTokens.value.find(item => item.account.data.parsed.info.mint === tokenMint && item.account.data.parsed.info.tokenAmount.uiAmount >= amount); - if (cargoHoldFound) { - starbasePlayerCargoHold = cargoHold; - mostFound = cargoHoldFound.account.data.parsed.info.tokenAmount.uiAmount; - break; - } else { - let cargoHoldFound = cargoHoldTokens.value.find(item => item.account.data.parsed.info.mint === tokenMint && item.account.data.parsed.info.tokenAmount.uiAmount >= mostFound); - if (cargoHoldFound) { - starbasePlayerCargoHold = cargoHold; - mostFound = cargoHoldFound.account.data.parsed.info.tokenAmount.uiAmount; + gameState: sageGameAcct.account.gameState + }, + resource: sageResource.publicKey, + planet: planet.publicKey, + fuelTank : fleet.account.fuelTank, + cargoType: fuelCargoTypeAcct.publicKey, + cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, + tokenFrom: await ResourceTokens.getPodToken({ name: 'fuel', pod: fleet.account.fuelTank }), + tokenMint: sageGameAcct.account.mints.fuel, + cargoProgram: cargoProgramId, + tokenProgram + }).instruction()} + fleet.state = 'Mining stopped'; + return await signAndSend([tx1,tx2]); + } + + // @todo - documentation + async function handleResupply(fleet, dump = false) { + await execDock(fleet); + await execCargoFromFleetToStarbase(fleet, { dump }); + await execCargoFromStarbaseToFleet(fleet); + await execUndock(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return Object.assign({}, fleet, fleetSavedData); + } + +/** + * The `handleMovement` function handles the movement of a fleet, checking its current state + * and executing the appropriate actions based on that state. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + * @returns The function does not explicitly return anything. + */ + async function handleMovement(fleet, options) { + const { coords } = options; + const [destX, destY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + + const { fleetData, fleetState, extra }= getCurrentFleet(fleet); + const warpCooldownExpiresAt = (fleetData.warpCooldownExpiresAt.toNumber() || 0) * 1000; + + let warpArrival, subwarpArrival; + switch (fleetState) { + case 'Idle': + if (extra[0] !== destX || extra[1] !== destY) { + const currentLocation = [extra[0], extra[1]]; + + if (fleet.moveType == 'warp') { + if (warpCooldownExpiresAt > 0) { + await execSubwarp(fleet, currentLocation); + } else { + await execWarp(fleet, currentLocation); } + } else { + await execSubwarp(fleet, currentLocation); + } + } else { + fleet.state = "Arrived"; + return; + } + break; + case 'MoveWarp': + warpArrival = extra.warpFinish.toNumber() * 1000; + if (warpArrival > Date.now()) fleet.state = 'Move [' + new Date(warpArrival).toLocaleTimeString() + ']'; + await wait(warpArrival); + await execExitWarp(fleet); + break; + case 'MoveSubwarp': + subwarpArrival = fleet.moveType == 'warp' ? warpCooldownExpiresAt : extra.arrivalTime.toNumber() * 1000; + if (subwarpArrival > Date.now()) fleet.state = 'Move [' + new Date(subwarpArrival).toLocaleTimeString() + ']'; + await wait(subwarpArrival); + await execExitSubwarp(fleet); + break; + default: + console.log('I only handle movement, bro'); + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return handleMovement({ ...fleetData, ...fleetSavedData }, options); + } + + // @todo - documentation + async function prepareToMine(fleetObject, options) { + let { coords, mineResource, amount } = options; + + const { foodConsumptionRate, ammoConsumptionRate } = fleet.account.stats.cargoStats; + mineResource = ResourceTokens[mineResource || fleet.mineResource]; + + const [starbaseX, starbaseY] = (coords || fleet.destination.coords).split(',').map(item => item.trim()); + const starbase = await getStarbaseFromCoords(starbaseX, starbaseY); + const starbasePlayer = await getStarbasePlayer(playerProfile.pubkey, starbase.publicKey); + + const { mineItem, planet, sageResource } = await getMiningDetails(mineResource.publicKey, [starbaseX, starbaseY]); + const resourceHardness = mineItem.account.resourceHardness; + const { systemRichness } = sageResource.account; + + const { cargoCapacity, miningRate } = fleet.account.stats.cargoStats; + const currentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + const currentCargoCount = currentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxCargoCapacity = cargoCapacity - currentCargoCount; + let mineAmount = amount || maxCargoCapacity; + if (mineAmount > maxCargoCapacity) { + mineAmount = maxCargoCapacity; + console.log(`Can't mine ${mineAmount} adjusted to ${maxCargoCapacity}, rock and STONE!`) + } + + const duration = calculateMiningDuration(mineAmount, miningRate, resourceHardness, systemRichness); + + fleet.origin.supplies[mineResource.publicKey.toString()] = mineAmount; + fleet.destination.supplies[ResourceTokens.ammo.publicKey.toString()] = Math.floor(duration * ammoConsumptionRate); + fleet.destination.supplies[ResourceTokens.food.publicKey.toString()] = Math.floor(duration * foodConsumptionRate); + + return { duration, starbase, starbasePlayer, planet }; + } + + // @todo - documentation + async function prepareForTrip(fleet) { + // get all the most recent information about the fleet + const { fleetData } = await getCurrentFleet(fleet); + if (fleetData.assignment.lower() in fleetState.lower()) return { fleet, skip: true} + // preparing for trip will allows check destination + const { supplies } = fleet.destination; + let fleetCargoHold = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + let resupplyNeeded = false; + + if (supplies) { + fleetCargoHold.value = fleetCargoHold.value.filter(item => Object.keys(supplies).includes(item.account.mint.toString())); + + for (let cargo of fleetCargoHold.value) { + const cargoString = cargo.account.mint.toString() + const amount = cargo.account.data.parsed.info.tokenAmount.uiAmount || 0; + resupplyNeeded = supplies[cargoString] != amount + } + } + + if (resupplyNeeded || !supplies) { + await handleMovement(fleet, { coords: fleet.origin.coords }); + await handleResupply(fleet, true); + } + + return { fleet, skip: false }; + } + +/** + * The function `handleReturnTrip` handles the return trip of a fleet by flipping the origin and + * destination coordinates and updating the fleet's saved data. + * @param fleet - The `fleet` parameter is an object that represents a fleet. + */ + async function handleReturnTrip(fleet) { + // shallow copy to mutate and flip origin and destination + const { extra } = getCurrentFleet(fleet); + const { origin, destination } = JSON.parse(JSON.stringify(fleet)); + const [destX, destY] = destination; + + if (fleetState == 'Idle' && (extra[0] === destX || extra[1] === destY)) { + const fleetPK = fleet.publicKey.toString(); + let fleetSavedData = await GM.getValue(fleetPK, '{}'); + let fleetParsedData = JSON.parse(fleetSavedData); + + fleet.origin.coords = destination.coords; + fleet.destination.coords = origin.coords; + + fleetParsedData.origin.coords = fleet.origin.coords; + fleetParsedData.destination.coords = fleet.destination.coords; + + await GM.setValue(fleetPK, JSON.stringify(fleetParsedData)); + await handleMovement(fleet); + } + } + +/** + * The `handleScan` function is responsible for scanning and determining the next action based on the scan + * results and fleet conditions. + * @param fleet - The `fleet` parameter represents an object that contains information about a fleet. + * @param options - The `options` is an object that contains the following optional parameters: + * - `cargoHoldBuffer` the amount of space remaining in cargoHold before returning to origin. + * Defaults to 100 units. + * - `scanDelay` amount of time in seconds after 4 strikes we delay before scanning again. + * Defaults to 600 seconds. + * - `scanSectorAge` the amount of time in seconds that a sector requires to regenerate. + * Defaults to 120 seconds. + * @returns The function does not explicitly return anything. + */ + async function handleScan(fleetObject, options) { + const { fleet } = await prepareForTrip(fleetObject); + await handleMovement(fleet); + + const { cargoHoldBuffer, scanDelay, scanSectorAge } = options; + let currentFleetState = await solanaConnection.getAccountInfo(fleet.publicKey); + currentFleetState = sageProgram.coder.accounts.decode('Fleet', currentFleetState.data); + const { cargoStats, miscStats } = currentFleetState.stats; + const { scanRepairKitAmount, scanCooldown } = miscStats; + + const currentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + const currentCargoCount = currentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const repairKitToken = await ResourceTokens.getPodToken({ name: 'toolkit', pod: fleet.account.cargoHold }); + + const currentToolkit = fleetCurrentCargo.value.find(item => item.pubkey.toString() === repairKitToken.toString()); + const currentToolkitCount = currentToolkit.account.data.parsed.info.delegatedAmount.uiAmount || 0; + + if ((cargoStats.cargoCapacity - currentCargoCount) < (cargoHoldBuffer || 100)) { + await handleReturnTrip(fleet); + return; + } + + if (currentToolkitCount < scanRepairKitAmount) { + await handleReturnTrip(fleet); + return; + } + + if (Date.now() > fleet.scanEnd) { + const scanResult = await execScan(fleet); + console.log('Scan Result: ', scanResult); + + const changesSDU = getBalanceChange(scanResult, ResourceTokens.sdu.publicKey.toString()); + const changesTool = getBalanceChange(scanResult, ResourceTokens.toolkit.publicKey.toString()); + let scanCondition = scanResult.meta.logMessages ? scanResult.meta.logMessages.find(item => item.startsWith("Program log: SDU probability:")) : null; + scanCondition = scanCondition ? (Number(scanCondition.split(' ').pop())*100).toFixed(4) : 0; + + console.log(`[${fleet.label}] ${new Date(Date.now()).toISOString()}`); + console.log(`[${fleet.label}] ${scanCondition}`); + + if (changesSDU.postBalance != changesSDU.preBalance) { + console.log(`[${fleet.label}] FOUND: ${changesSDU.postBalance - changesSDU.preBalance}`); + fleet.scanSkipCnt = 0; + fleet.scanSectorStart = 0; + } else { + console.log(`[${fleet.label}] Whomp whomp`); + } + + if (scanCondition < fleet.scanMin) { + if (fleet.scanSectorStart == 0) fleet.scanSectorStart = Date.now(); + if (Date.now() - fleet.scanSectorStart >= (scanSectorAge || 120) * 1000) { + ++fleet.scanSkipCnt; + if (scanMove) { + const nextMoveIdx = fleet.scanBlockIdx > 2 ? 0 : ++fleet.scanBlockIdx; + fleet.scanBlockIdx = nextMoveIdx; } } } - amount = amount > mostFound ? mostFound : amount; - let [starbaseCargoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - starbasePlayerCargoHold.publicKey.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(tokenMint).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - await solanaConnection.getAccountInfo(starbaseCargoToken) || await createProgramDerivedAccount(starbaseCargoToken, starbasePlayerCargoHold.publicKey, new solanaWeb3.PublicKey(tokenMint)); - let tx = { instruction: await sageProgram.methods.depositCargoToFleet({ amount: new BrowserAnchor.anchor.BN(amount), keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx) }).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey - }, - gameState: sageGameAcct.account.gameState - }, - fundsTo: userPublicKey, - starbaseAndStarbasePlayer: { - starbase: starbase.publicKey, - starbasePlayer: starbasePlayer.publicKey - }, - cargoPodFrom: starbasePlayerCargoHold.publicKey, - cargoPodTo: cargoPodTo, - cargoType: cargoType.publicKey, - cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, - tokenFrom: starbaseCargoToken, - tokenTo: tokenTo, - tokenMint: tokenMint, - cargoProgram: cargoProgramId, - tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - }).remainingAccounts([{ - pubkey: starbase.publicKey, - isSigner: false, - isWritable: false - }]).instruction()} - let txResult = {}; - if (amount > 0) { - fleet.busy = true; - txResult = await txSignAndSend(tx); - fleet.busy = false; + + console.log(`[${fleet.label}] Tools Remaining: ${changesTool.postBalance}`); + + if (fleet.scanSkipCnt < 4) { + fleet.state = `Scanning [${scanCondition}%]`; + fleet.scanEnd = Date.now() + (scanCooldown * 1000); } else { - txResult = {name: "NotEnoughResource"}; + fleet.scanEnd = Date.now() + (scanDelay || 600) * 1000; + fleet.state = `Scanning Paused [${new Date(fleet.scanEnd).toLocaleTimeString()}]`; + fleet.scanSkipCnt = 0; + console.log(`[${fleet.label}] Scanning Paused due to low probability [${new Date(fleet.scanEnd).toLocaleTimeString()}]`); } - let [fleetRepairKitToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetSduToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetFuelToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.fuelTank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - await solanaConnection.getAccountInfo(fleetSduToken) || await createProgramDerivedAccount(fleetSduToken, fleet.cargoHold, new solanaWeb3.PublicKey('SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM')); - await solanaConnection.getAccountInfo(fleetRepairKitToken) || await createProgramDerivedAccount(fleetRepairKitToken, fleet.cargoHold, new solanaWeb3.PublicKey('tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL')); - await solanaConnection.getAccountInfo(fleetFuelToken) || await createProgramDerivedAccount(fleetFuelToken, fleet.fuelTank, new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim')); - var userFleetIndex = userFleets.findIndex(item => {return item.publicKey === fleet.publicKey}); - userFleets[userFleetIndex].sduToken = fleetSduToken; - userFleets[userFleetIndex].repairKitToken = fleetRepairKitToken; - userFleets[userFleetIndex].fuelToken = fleetFuelToken; - resolve(txResult); + } + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + async function handleMining(fleetObject) { + const { + duration: miningDuration, + starbase, + starbasePlayer, + planet + } = prepareToMine(fleetObject, options); + const { fleet, skip } = await prepareForTrip(fleetObject); + + if (!skip) { + await handleMovement(fleet); + await execStartMining(fleet, { starbase, starbasePlayer, planet }); + } + + const { foodConsumptionRate, ammoConsumptionRate } = fleet.account.stats.cargoStats; + + const currentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.ammoBank, {programId: tokenProgram}); + const currentAmmoCount = currentAmmoBank.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxAmmoDuration = Math.floor(currentAmmoCount / ammoConsumptionRate); + + const currentFood = await solanaConnection.getParsedTokenAccountsByOwner(fleet.account.cargoHold, {programId: tokenProgram}); + currentFood.value = currentFood.value.filter(item => item.account.mint.toString() == ResourceTokens.food.publicKey.toString()); + const currentFoodCount = currentFood.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount || 0, 0); + const maxFoodDuration = Math.floor(currentFoodCount / foodConsumptionRate); + + const duration = Math.min(miningDuration, maxAmmoDuration, maxFoodDuration); + fleet.state = 'Mine [' + new Date(Date.now() + (duration * 1000)).toLocaleTimeString() + ']'; + + await wait(duration); + await execStopMining(fleet); + await handleReturnTrip(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + async function handleTransport(fleetObject) { + const { fleet } = await prepareForTrip(fleetObject); + await handleMovement(fleet); + await handleResupply(fleet); + await handleReturnTrip(fleet); + + const fleetSavedData = JSON.parse(await GM.getValue(fleet.publicKey.toString(), '{}')); + return { ...fleet, ...fleetSavedData }; + } + + function addScanOptions(fleetParsedData) { + console.log(fleetParsedData); + const options = document.createElement('tr'); + options.id = `${fleetParsedData.publicKey}-scan-options` + options.style.display = 'table-row'; + + const paddingTd = document.createElement('td'); + options.appendChild(paddingTd); + + // create minimum probability textfield + const minimumProbabilityTd = document.createElement('td'); + const minimumProbabilityField = document.createElement('span'); + minimumProbabilityField.innerHTML = 'Minimum Probability:'; + + const minimumProbability = document.createElement('input'); + minimumProbability.setAttribute('type', 'text'); + minimumProbability.placeholder = '10'; + minimumProbability.style.width = '30px'; + minimumProbability.style.marginRight = '10px'; + minimumProbability.value = fleetParsedData.scanMin || ''; + + const minimumProbabilityDiv = document.createElement('div'); + minimumProbabilityDiv.appendChild(minimumProbability); + minimumProbabilityDiv.appendChild(minimumProbability); + + minimumProbabilityTd.setAttribute('colspan', '3'); + minimumProbabilityTd.appendChild(minimumProbabilityDiv); + options.appendChild(minimumProbabilityTd); + + // create should move while scanning input + const moveWhileScanningTd = document.createElement('td'); + const moveWhileScanning = document.createElement('span'); + moveWhileScanning.innerHTML = 'Move While Scanning:'; + + const moveWhileScanningCB = document.createElement('input'); + moveWhileScanningCB.setAttribute('type', 'checkbox'); + moveWhileScanningCB.checked = fleetParsedData.scanMove || true; + moveWhileScanningCB.style.marginRight = '10px'; + + const moveWhileScanningDiv = document.createElement('div'); + moveWhileScanningDiv.appendChild(moveWhileScanning); + moveWhileScanningDiv.appendChild(moveWhileScanningCB); + + moveWhileScanningTd.setAttribute('colspan', '4'); + moveWhileScanningTd.appendChild(moveWhileScanningDiv); + options.appendChild(moveWhileScanningTd); + + return options; + } + + function addMineOptions(fleet) { + console.log(fleet); + const mineResource = ResourceTokens.findName(fleet.mineResource); + const options = document.createElement('tr'); + options.id = `${fleet.publicKey.toString()}-mine-options` + options.style.display = 'table-row'; + + const paddingTd = document.createElement('td'); + options.appendChild(paddingTd); + + const td = document.createElement('td'); + td.setAttribute('colspan', '7'); + + const label = document.createElement('span'); + label.innerHTML = 'Resource to mine:'; + td.appendChild(label); + + const mineResources = ['', ...ResourceTokens.names('R9')] + const resources = document.createElement('select'); + mineResources.forEach((resource, key) => { + resources[key] = new Option(resource, resource, resource == '', resource == mineResource) }); + td.appendChild(resources); + options.appendChild(td); + + return options; } - async function execStartMining(fleet, mineItem, sageResource, planet) { - return new Promise(async resolve => { - let resourceToken = fleet.mineResource; - let targetX = fleet.destCoord.split(',')[0].trim(); - let targetY = fleet.destCoord.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(targetX, targetY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); - let tx = { instruction: await sageProgram.methods.startMiningAsteroid({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx)}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey - }, - gameState: sageGameAcct.account.gameState - }, - starbaseAndStarbasePlayer: { - starbase: starbase.publicKey, - starbasePlayer: starbasePlayer.publicKey - }, - mineItem : mineItem.publicKey, - resource: sageResource.publicKey, - planet: planet.publicKey, - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend(tx); - fleet.busy = false; - resolve(txResult); + function calculateCargoTotals(id, cargoCapacity) { + const fields = document.querySelectorAll(`input[id*='${id}-']`); + const total = [...fields].map(field => parseInt(field.value)).reduce((a,b) => a + (b || 0), 0); + console.log(fields.length, total, cargoCapacity); + fields.forEach(function(field) { + field.style.border = total > cargoCapacity ? '2px solid red' : null }); } - async function execStopMining(fleet, sageResource, sageResourceAcctInfo, mineItem, resourceToken) { - return new Promise(async resolve => { - let planet = sageResourceAcctInfo.location; - let targetX = fleet.destCoord.split(',')[0].trim(); - let targetY = fleet.destCoord.split(',')[1].trim(); - let starbase = await getStarbaseFromCoords(targetX, targetY); - let starbasePlayer = await getStarbasePlayer(userProfileAcct,starbase.publicKey); + function createTransportOptions(container, transportSupplies, cargoCapacity) { + const resources = ['', ...ResourceTokens.names('all')]; + const supplies = Object.entries(transportSupplies); + const id = container.id; + + supplies.forEach(supply => { + const [ transportResource, amount ] = supply; + const div = document.createElement('div'); + + const transportAmount = document.createElement('input'); + transportAmount.setAttribute('type', 'text'); + transportAmount.id = `${id}-${transportResource}`; + transportAmount.placeholder = '0'; + transportAmount.style.width = '60px'; + transportAmount.style.marginRight = '10px'; + transportAmount.value = amount; + transportAmount.min = 0; + transportAmount.addEventListener("input", function() { + if (this.value < 0) this.value = Math.abs(this.value); + calculateCargoTotals(id, cargoCapacity) + }) + div.appendChild(transportAmount); - let [planetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - mineItem.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - resourceToken.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - resourceToken.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetFoodToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.food.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetAmmoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - fleet.ammoBank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.ammo.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(fleet.cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFood = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.food.toString()); - let fleetFoodAcct = currentFood ? currentFood.pubkey : fleetFoodToken; - - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(fleet.ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let fleetAmmoAcct = currentAmmo ? currentAmmo.pubkey : fleetAmmoToken; - - await solanaConnection.getAccountInfo(fleetResourceToken) || await createProgramDerivedAccount(fleetResourceToken, fleet.cargoHold, resourceToken); - let foodCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.food); - let ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); - let resourceCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == resourceToken.toString()); - let tx1 = { instruction: await sageProgram.methods.fleetStateHandler().accountsStrict({ - fleet: fleet.publicKey - }).remainingAccounts([ - { - pubkey: userProfileFactionAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: fleet.cargoHold, - isSigner: false, - isWritable: true - }, - { - pubkey: fleet.ammoBank, - isSigner: false, - isWritable: true - }, - { - pubkey: mineItem, - isSigner: false, - isWritable: false - }, - { - pubkey: sageResource, //Account5 - isSigner: false, - isWritable: true - }, - { - pubkey: planet, - isSigner: false, - isWritable: true - }, - { - pubkey: starbase.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: fleetFoodAcct, //foodTokenFrom - isSigner: false, - isWritable: true - }, - { - pubkey: fleetAmmoAcct, //ammoTokenFrom - isSigner: false, - isWritable: true - }, - { - pubkey: planetResourceToken, //resourceTokenFrom - isSigner: false, - isWritable: true - }, - { - pubkey: fleetResourceToken, //resourceTokenTo - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.mints.food, - isSigner: false, - isWritable: true - }, - { - pubkey: sageGameAcct.account.mints.ammo, - isSigner: false, - isWritable: true - }, - { - pubkey: foodCargoTypeAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: ammoCargoTypeAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: resourceCargoTypeAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.account.cargo.statsDefinition, - isSigner: false, - isWritable: false - }, - { - pubkey: sageGameAcct.publicKey, - isSigner: false, - isWritable: false - }, - { - pubkey: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), - isSigner: false, - isWritable: false - }, - { - pubkey: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - isSigner: false, - isWritable: false - }, - ]).instruction()} - - let tx2 = { instruction: await sageProgram.methods.stopMiningAsteroid({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx)}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey - }, - gameState: sageGameAcct.account.gameState - }, - resource: sageResource, - planet: planet, - fuelTank : fleet.fuelTank, - cargoType: fuelCargoTypeAcct.publicKey, - cargoStatsDefinition: sageGameAcct.account.cargo.statsDefinition, - tokenFrom: fleet.fuelToken, - tokenMint: sageGameAcct.account.mints.fuel, - cargoProgram: new solanaWeb3.PublicKey('Cargo8a1e6NkGyrjy4BQEW4ASGKs9KSyDyUrXMfpJoiH'), - tokenProgram: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - }).instruction()} - fleet.busy = true; - let txResult = await txSignAndSend([tx1,tx2]); - fleet.busy = false; - console.log('---STOP MINE---'); - console.log(txResult); - resolve(txResult); - }); + const selector = document.createElement('select'); + resources.forEach((resource, key) => { + selector[key] = new Option(resource, resource, resource == '', resource == transportResource) + }) + div.appendChild(selector); + + container.appendChild(div); + }) + + return container; + } + + // @todo - do capacity check magic + function addTransportOptions(fleet) { + const fleetPublicKeyString = fleet.publicKey.toString() + const options = document.createElement('tr'); + options.id = `${fleetPublicKeyString}-transport-options` + options.style.display = 'table-row'; + + const td = document.createElement('td'); + td.setAttribute('colspan', '8'); + + let destinationContainer = document.createElement('div'); + destinationContainer.id = `${fleetPublicKeyString}-to-destination`; + destinationContainer.style.display = 'flex' + destinationContainer.style.flexDirection = 'row'; + destinationContainer.style.justifyContent = 'flex-start'; + + const toDestinationLabel = document.createElement('div'); + toDestinationLabel.innerHTML = 'To Dest:'; + toDestinationLabel.style.width = '84px'; + toDestinationLabel.style.minWidth = '84px'; + destinationContainer.appendChild(toDestinationLabel) + + destinationContainer = createTransportOptions(destinationContainer, fleet.destination.supplies, fleet.account.stats.cargoStats.cargoCapacity); + td.appendChild(destinationContainer); + + let originContainer = document.createElement('div'); + originContainer.id = `${fleetPublicKeyString}-to-origin`; + originContainer.style.display = 'flex' + originContainer.style.flexDirection = 'row'; + originContainer.style.justifyContent = 'flex-start'; + + const toOriginLabel = document.createElement('div'); + toOriginLabel.innerHTML = 'To Origin:'; + toOriginLabel.style.width = '84px'; + toOriginLabel.style.minWidth = '84px'; + originContainer.appendChild(toOriginLabel) + + originContainer = createTransportOptions(originContainer, fleet.destination.supplies, fleet.account.stats.cargoStats.cargoCapacity); + td.appendChild(originContainer); + + options.appendChild(td); + return options; } async function addAssistInput(fleet) { - let fleetSavedData = await GM.getValue(fleet.publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetRow = document.createElement('tr'); + const fleetPublicKeyString = fleet.publicKey.toString(); + + const fleetRow = document.createElement('tr'); fleetRow.classList.add('assist-fleet-row'); - fleetRow.setAttribute('pk', fleet.publicKey.toString()); + fleetRow.id = `${fleetPublicKeyString}-row`; - let fleetLabel = document.createElement('span'); - fleetLabel.innerHTML = fleet.label; - let fleetLabelTd = document.createElement('td'); + // create fleet name + const fleetLabelTd = document.createElement('td'); + const fleetLabel = document.createElement('span'); + fleetLabel.innerHTML = fleet.name; fleetLabelTd.appendChild(fleetLabel); + fleetRow.appendChild(fleetLabelTd); - let assistAssignments = ['','Scan','Mine','Transport']; - let assignmentOptionsStr = ''; - let fleetAssignment = document.createElement('select'); - assistAssignments.forEach( function(assignment) {assignmentOptionsStr += '';}); - fleetAssignment.innerHTML = assignmentOptionsStr; - fleetAssignment.value = fleetParsedData && fleetParsedData.assignment ? fleetParsedData.assignment : ''; - let fleetAssignmentTd = document.createElement('td'); - fleetAssignmentTd.appendChild(fleetAssignment); -/* - let fleetResupply = document.createElement('input'); - fleetResupply.setAttribute('type', 'checkbox'); - if (fleetAssignment.value !== 'Transport') fleetResupply.setAttribute('disabled', ''); - fleetResupply.checked = fleetParsedData && fleetParsedData.resupply && fleetParsedData.resupply == 'true' ? true : false; - let fleetResupplyTd = document.createElement('td'); - fleetResupplyTd.appendChild(fleetResupply); - fleetAssignment.onchange = function() { - fleetAssignment.value == 'Transport' ? fleetResupply.removeAttribute('disabled') : fleetResupply.setAttribute('disabled', ''); - }; -*/ -/* - let assistResources = ['','Arco','Biomass','Carbon','Copper Ore','Diamond','Hydrogen','Iron Ore','Lumanite','Rochinol'] - let optionsStr = ''; - let fleetMineRes = document.createElement('select'); - assistResources.forEach( function(resource) {optionsStr += '';}); - fleetMineRes.innerHTML = optionsStr; - let resourceToken = fleetParsedData && fleetParsedData.mineResource && fleetParsedData.mineResource !== '' ? resourceTokens.find(r => r.token == fleetParsedData.mineResource) : ''; - fleetMineRes.value = resourceToken && resourceToken.name ? resourceToken.name : ''; - let fleetMineResTd = document.createElement('td'); - fleetMineResTd.appendChild(fleetMineRes); -*/ - let fleetDestCoord = document.createElement('input'); - fleetDestCoord.setAttribute('type', 'text'); - fleetDestCoord.placeholder = 'x, y'; - fleetDestCoord.style.width = '50px'; - fleetDestCoord.value = fleetParsedData && fleetParsedData.dest ? fleetParsedData.dest : ''; - let fleetDestCoordTd = document.createElement('td'); - fleetDestCoordTd.appendChild(fleetDestCoord); - - let fleetStarbaseCoord = document.createElement('input'); - fleetStarbaseCoord.setAttribute('type', 'text'); - fleetStarbaseCoord.placeholder = 'x, y'; - fleetStarbaseCoord.style.width = '50px'; - fleetStarbaseCoord.value = fleetParsedData && fleetParsedData.starbase ? fleetParsedData.starbase : ''; - let fleetStarbaseCoordTd = document.createElement('td'); - fleetStarbaseCoordTd.appendChild(fleetStarbaseCoord); - - let fleetSubwarpPref = document.createElement('input'); - fleetSubwarpPref.setAttribute('type', 'checkbox'); - fleetSubwarpPref.checked = fleetParsedData && fleetParsedData.subwarpPref && fleetParsedData.subwarpPref == 'true' ? true : false; - let fleetSubwarpPrefTd = document.createElement('td'); - fleetSubwarpPrefTd.appendChild(fleetSubwarpPref); - - let fleetCargoCapacity = document.createElement('span'); - fleetCargoCapacity.innerHTML = fleet.cargoCapacity; - let fleetCargoCapacityTd = document.createElement('td'); - fleetCargoCapacityTd.appendChild(fleetCargoCapacity); - - let fleetAmmoCapacity = document.createElement('span'); - fleetAmmoCapacity.innerHTML = fleet.ammoCapacity; - let fleetAmmoCapacityTd = document.createElement('td'); - fleetAmmoCapacityTd.appendChild(fleetAmmoCapacity); - - let fleetFuelCapacity = document.createElement('span'); - fleetFuelCapacity.innerHTML = fleet.fuelCapacity; - let fleetFuelCapacityTd = document.createElement('td'); - fleetFuelCapacityTd.appendChild(fleetFuelCapacity); + // create assignment selector + const fleetAssignmentTd = document.createElement('td'); + const fleetAssignment = document.createElement('select'); + fleetAssignment.id = `${fleetPublicKeyString}-select`; - fleetRow.appendChild(fleetLabelTd); + const assistAssignments = ['','Scan','Mine','Transport']; + assistAssignments.forEach((assignment, key) => { + fleetAssignment[key] = new Option(assignment, assignment, assignment == '', assignment == fleet.assignment) + }); + fleetAssignmentTd.appendChild(fleetAssignment); fleetRow.appendChild(fleetAssignmentTd); - fleetRow.appendChild(fleetDestCoordTd); - fleetRow.appendChild(fleetStarbaseCoordTd); - fleetRow.appendChild(fleetSubwarpPrefTd); - fleetRow.appendChild(fleetCargoCapacityTd); - fleetRow.appendChild(fleetAmmoCapacityTd); - fleetRow.appendChild(fleetFuelCapacityTd); - let targetElem = document.querySelector('#assistModal .assist-modal-body table'); - targetElem.appendChild(fleetRow); - - let scanRow = document.createElement('tr'); - scanRow.classList.add('assist-scan-row'); - scanRow.style.display = fleetParsedData && fleetParsedData.assignment == 'Scan' ? 'table-row' : 'none'; - fleetParsedData && fleetParsedData.assignment == 'Scan' && fleetRow.classList.add('show-top-border'); - targetElem.appendChild(scanRow); - - let scanPadTd = document.createElement('td'); - scanRow.appendChild(scanPadTd); - - let scanMinLabel = document.createElement('span'); - scanMinLabel.innerHTML = 'Minimum Probability:'; - let scanMin = document.createElement('input'); - scanMin.setAttribute('type', 'text'); - scanMin.placeholder = '10'; - scanMin.style.width = '30px'; - scanMin.style.marginRight = '10px'; - scanMin.value = fleetParsedData && fleetParsedData.scanMin ? fleetParsedData.scanMin : ''; - let scanMinDiv = document.createElement('div'); - scanMinDiv.appendChild(scanMinLabel); - scanMinDiv.appendChild(scanMin); - let scanMinTd = document.createElement('td'); - scanMinTd.setAttribute('colspan', '3'); - scanMinTd.appendChild(scanMinDiv); - scanRow.appendChild(scanMinTd); - - let scanMoveLabel = document.createElement('span'); - scanMoveLabel.innerHTML = 'Move While Scanning:'; - let scanMove = document.createElement('input'); - scanMove.setAttribute('type', 'checkbox'); - scanMove.checked = fleetParsedData && fleetParsedData.scanMove && fleetParsedData.scanMove == 'false' ? false : true; - scanMove.style.marginRight = '10px'; - let scanMoveDiv = document.createElement('div'); - scanMoveDiv.appendChild(scanMoveLabel); - scanMoveDiv.appendChild(scanMove); - let scanMoveTd = document.createElement('td'); - scanMoveTd.setAttribute('colspan', '4'); - scanMoveTd.appendChild(scanMoveDiv); - scanRow.appendChild(scanMoveTd); -/* - let scanTd = document.createElement('td'); - scanTd.setAttribute('colspan', '8'); - let scanWrapper = document.createElement('div'); - scanWrapper.classList.add('scan-wrapper'); - scanWrapper.style.display = 'flex' - scanWrapper.style.flexDirection = 'row'; - scanWrapper.style.justifyContent = 'flex-start'; - scanWrapper.appendChild(scanMinDiv); - scanWrapper.appendChild(scanMoveDiv); - //scanWrapper.appendChild(transportSBResource2Div); - //scanWrapper.appendChild(transportSBResource3Div); - //scanWrapper.appendChild(transportSBResource4Div); - scanTd.appendChild(scanWrapper); - scanRow.appendChild(scanTd); -*/ - targetElem.appendChild(scanRow); - - let mineRow = document.createElement('tr'); - mineRow.classList.add('assist-mine-row'); - mineRow.style.display = fleetParsedData && fleetParsedData.assignment == 'Mine' ? 'table-row' : 'none'; - fleetParsedData && fleetParsedData.assignment == 'Mine' && fleetRow.classList.add('show-top-border'); - targetElem.appendChild(mineRow); - - let minePadTd = document.createElement('td'); - mineRow.appendChild(minePadTd); - - let mineResLabel = document.createElement('span'); - mineResLabel.innerHTML = 'Resource to mine:'; - let assistResources = ['','Arco','Biomass','Carbon','Copper Ore','Diamond','Hydrogen','Iron Ore','Lumanite','Rochinol'] - let optionsStr = ''; - let fleetMineRes = document.createElement('select'); - assistResources.forEach( function(resource) {optionsStr += '';}); - fleetMineRes.innerHTML = optionsStr; - let resourceToken = fleetParsedData && fleetParsedData.mineResource && fleetParsedData.mineResource !== '' ? resourceTokens.find(r => r.token == fleetParsedData.mineResource) : ''; - fleetMineRes.value = resourceToken && resourceToken.name ? resourceToken.name : ''; - let fleetMineResTd = document.createElement('td'); - fleetMineResTd.setAttribute('colspan', '7'); - fleetMineResTd.appendChild(mineResLabel); - fleetMineResTd.appendChild(fleetMineRes); - mineRow.appendChild(fleetMineResTd); - targetElem.appendChild(mineRow); - - let transportRow = document.createElement('tr'); - transportRow.classList.add('assist-transport-row'); - transportRow.style.display = fleetParsedData && fleetParsedData.assignment == 'Transport' ? 'table-row' : 'none'; - fleetParsedData && fleetParsedData.assignment == 'Transport' && fleetRow.classList.add('show-top-border'); - targetElem.appendChild(transportRow); - - let transportLabel1 = document.createElement('div'); - transportLabel1.innerHTML = 'To Target:'; - transportLabel1.style.width = '84px'; - transportLabel1.style.minWidth = '84px'; - - let transportResources = ['','Ammo','Food','Fuel','SDU','Toolkit','Arco','Biomass','Carbon','Copper Ore','Diamond','Hydrogen','Iron Ore','Lumanite','Rochinol'] - let transportOptStr = ''; - transportResources.forEach( function(resource) {transportOptStr += '';}); - let transportResource1 = document.createElement('select'); - transportResource1.innerHTML = transportOptStr; - let transportResource1Token = fleetParsedData && fleetParsedData.transportResource1 && fleetParsedData.transportResource1 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportResource1) : ''; - transportResource1.value = transportResource1Token && transportResource1Token.name ? transportResource1Token.name : ''; - let transportResource1Perc = document.createElement('input'); - transportResource1Perc.setAttribute('type', 'text'); - transportResource1Perc.placeholder = '0'; - transportResource1Perc.style.width = '60px'; - transportResource1Perc.style.marginRight = '10px'; - transportResource1Perc.value = fleetParsedData && fleetParsedData.transportResource1Perc ? fleetParsedData.transportResource1Perc : ''; - let transportResource1Div = document.createElement('div'); - transportResource1Div.appendChild(transportResource1); - transportResource1Div.appendChild(transportResource1Perc); - - let transportResource2 = document.createElement('select'); - transportResource2.innerHTML = transportOptStr; - let transportResource2Token = fleetParsedData && fleetParsedData.transportResource2 && fleetParsedData.transportResource2 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportResource2) : ''; - transportResource2.value = transportResource2Token && transportResource2Token.name ? transportResource2Token.name : ''; - let transportResource2Perc = document.createElement('input'); - transportResource2Perc.setAttribute('type', 'text'); - transportResource2Perc.placeholder = '0'; - transportResource2Perc.style.width = '60px'; - transportResource2Perc.style.marginRight = '10px'; - transportResource2Perc.value = fleetParsedData && fleetParsedData.transportResource2Perc ? fleetParsedData.transportResource2Perc : ''; - let transportResource2Div = document.createElement('div'); - transportResource2Div.appendChild(transportResource2); - transportResource2Div.appendChild(transportResource2Perc); - - let transportResource3 = document.createElement('select'); - transportResource3.innerHTML = transportOptStr; - let transportResource3Token = fleetParsedData && fleetParsedData.transportResource3 && fleetParsedData.transportResource3 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportResource3) : ''; - transportResource3.value = transportResource3Token && transportResource3Token.name ? transportResource3Token.name : ''; - let transportResource3Perc = document.createElement('input'); - transportResource3Perc.setAttribute('type', 'text'); - transportResource3Perc.placeholder = '0'; - transportResource3Perc.style.width = '60px'; - transportResource3Perc.style.marginRight = '10px'; - transportResource3Perc.value = fleetParsedData && fleetParsedData.transportResource3Perc ? fleetParsedData.transportResource3Perc : ''; - let transportResource3Div = document.createElement('div'); - transportResource3Div.appendChild(transportResource3); - transportResource3Div.appendChild(transportResource3Perc); - - let transportResource4 = document.createElement('select'); - transportResource4.innerHTML = transportOptStr; - let transportResource4Token = fleetParsedData && fleetParsedData.transportResource4 && fleetParsedData.transportResource4 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportResource4) : ''; - transportResource4.value = transportResource4Token && transportResource4Token.name ? transportResource4Token.name : ''; - let transportResource4Perc = document.createElement('input'); - transportResource4Perc.setAttribute('type', 'text'); - transportResource4Perc.placeholder = '0'; - transportResource4Perc.style.width = '60px'; - transportResource4Perc.value = fleetParsedData && fleetParsedData.transportResource4Perc ? fleetParsedData.transportResource4Perc : ''; - let transportResource4Div = document.createElement('div'); - transportResource4Div.appendChild(transportResource4); - transportResource4Div.appendChild(transportResource4Perc); - - let transportLabel2 = document.createElement('div'); - transportLabel2.innerHTML = 'To Starbase:'; - transportLabel2.style.width = '84px'; - transportLabel2.style.minWidth = '84px'; - - let transportSBResource1 = document.createElement('select'); - transportSBResource1.innerHTML = transportOptStr; - let transportSBResource1Token = fleetParsedData && fleetParsedData.transportSBResource1 && fleetParsedData.transportSBResource1 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportSBResource1) : ''; - transportSBResource1.value = transportSBResource1Token && transportSBResource1Token.name ? transportSBResource1Token.name : ''; - let transportSBResource1Perc = document.createElement('input'); - transportSBResource1Perc.setAttribute('type', 'text'); - transportSBResource1Perc.placeholder = '0'; - transportSBResource1Perc.style.width = '60px'; - transportSBResource1Perc.style.marginRight = '10px'; - transportSBResource1Perc.value = fleetParsedData && fleetParsedData.transportSBResource1Perc ? fleetParsedData.transportSBResource1Perc : ''; - let transportSBResource1Div = document.createElement('div'); - transportSBResource1Div.appendChild(transportSBResource1); - transportSBResource1Div.appendChild(transportSBResource1Perc); - - let transportSBResource2 = document.createElement('select'); - transportSBResource2.innerHTML = transportOptStr; - let transportSBResource2Token = fleetParsedData && fleetParsedData.transportSBResource2 && fleetParsedData.transportSBResource2 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportSBResource2) : ''; - transportSBResource2.value = transportSBResource2Token && transportSBResource2Token.name ? transportSBResource2Token.name : ''; - let transportSBResource2Perc = document.createElement('input'); - transportSBResource2Perc.setAttribute('type', 'text'); - transportSBResource2Perc.placeholder = '0'; - transportSBResource2Perc.style.width = '60px'; - transportSBResource2Perc.style.marginRight = '10px'; - transportSBResource2Perc.value = fleetParsedData && fleetParsedData.transportSBResource2Perc ? fleetParsedData.transportSBResource2Perc : ''; - let transportSBResource2Div = document.createElement('div'); - transportSBResource2Div.appendChild(transportSBResource2); - transportSBResource2Div.appendChild(transportSBResource2Perc); - - let transportSBResource3 = document.createElement('select'); - transportSBResource3.innerHTML = transportOptStr; - let transportSBResource3Token = fleetParsedData && fleetParsedData.transportSBResource3 && fleetParsedData.transportSBResource3 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportSBResource3) : ''; - transportSBResource3.value = transportSBResource3Token && transportSBResource3Token.name ? transportSBResource3Token.name : ''; - let transportSBResource3Perc = document.createElement('input'); - transportSBResource3Perc.setAttribute('type', 'text'); - transportSBResource3Perc.placeholder = '0'; - transportSBResource3Perc.style.width = '60px'; - transportSBResource3Perc.style.marginRight = '10px'; - transportSBResource3Perc.value = fleetParsedData && fleetParsedData.transportSBResource3Perc ? fleetParsedData.transportSBResource3Perc : ''; - let transportSBResource3Div = document.createElement('div'); - transportSBResource3Div.appendChild(transportSBResource3); - transportSBResource3Div.appendChild(transportSBResource3Perc); - - let transportSBResource4 = document.createElement('select'); - transportSBResource4.innerHTML = transportOptStr; - let transportSBResource4Token = fleetParsedData && fleetParsedData.transportSBResource4 && fleetParsedData.transportSBResource4 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.token == fleetParsedData.transportSBResource4) : ''; - transportSBResource4.value = transportSBResource4Token && transportSBResource4Token.name ? transportSBResource4Token.name : ''; - let transportSBResource4Perc = document.createElement('input'); - transportSBResource4Perc.setAttribute('type', 'text'); - transportSBResource4Perc.placeholder = '0'; - transportSBResource4Perc.style.width = '60px'; - transportSBResource4Perc.style.marginRight = '10px'; - transportSBResource4Perc.value = fleetParsedData && fleetParsedData.transportSBResource4Perc ? fleetParsedData.transportSBResource4Perc : ''; - let transportSBResource4Div = document.createElement('div'); - transportSBResource4Div.appendChild(transportSBResource4); - transportSBResource4Div.appendChild(transportSBResource4Perc); - - let transportTd = document.createElement('td'); - transportTd.setAttribute('colspan', '8'); - let transportTargettWrapper = document.createElement('div'); - transportTargettWrapper.classList.add('transport-to-target'); - transportTargettWrapper.style.display = 'flex' - transportTargettWrapper.style.flexDirection = 'row'; - transportTargettWrapper.style.justifyContent = 'flex-start'; - transportTargettWrapper.appendChild(transportLabel1); - transportTargettWrapper.appendChild(transportResource1Div); - transportTargettWrapper.appendChild(transportResource2Div); - transportTargettWrapper.appendChild(transportResource3Div); - transportTargettWrapper.appendChild(transportResource4Div); - let transportStarbaseWrapper = document.createElement('div'); - transportStarbaseWrapper.classList.add('transport-to-starbase'); - transportStarbaseWrapper.style.display = 'flex' - transportStarbaseWrapper.style.flexDirection = 'row'; - transportStarbaseWrapper.style.justifyContent = 'flex-start'; - transportStarbaseWrapper.appendChild(transportLabel2); - transportStarbaseWrapper.appendChild(transportSBResource1Div); - transportStarbaseWrapper.appendChild(transportSBResource2Div); - transportStarbaseWrapper.appendChild(transportSBResource3Div); - transportStarbaseWrapper.appendChild(transportSBResource4Div); - transportTd.appendChild(transportTargettWrapper); - transportTd.appendChild(transportStarbaseWrapper); - transportRow.appendChild(transportTd); - targetElem.appendChild(transportRow); - - let padRow = document.createElement('tr'); - padRow.classList.add('assist-pad-row'); - padRow.style.display = fleetParsedData && fleetParsedData.assignment ? 'table-row' : 'none'; - let padRowTd = document.createElement('td'); + + // create origin textfield + const fleetOriginTd = document.createElement('td'); + const fleetOriginField = document.createElement('input'); + fleetOriginField.setAttribute('type', 'text'); + fleetOriginField.placeholder = 'x, y'; + fleetOriginField.style.width = '50px'; + fleetOriginField.value = fleet.origin.coords || ''; + fleetOriginTd.appendChild(fleetOriginField); + fleetRow.appendChild(fleetOriginTd); + + // create destination textfield + const fleetDestinationTd = document.createElement('td'); + const fleetDestinationField = document.createElement('input'); + fleetDestinationField.setAttribute('type', 'text'); + fleetDestinationField.placeholder = 'x, y'; + fleetDestinationField.style.width = '50px'; + fleetDestinationField.value = fleet.destination.coords || ''; + fleetDestinationTd.appendChild(fleetDestinationField); + fleetRow.appendChild(fleetDestinationTd); + + // create fuel tank textfield + const fleetFuelTankTd = document.createElement('td'); + const fleetFuelTank = document.createElement('input'); + fleetFuelTank.setAttribute('type', 'text'); + fleetFuelTank.placeholder = fleet.account.stats.cargoStats.fuelCapacity; + fleetFuelTank.setAttribute('size', fleetFuelTank.getAttribute('placeholder').length); + fleetFuelTankTd.appendChild(fleetFuelTank); + fleetRow.appendChild(fleetFuelTankTd); + + // create ammo bank textfield + const fleetAmmoBankTd = document.createElement('td'); + const fleetAmmoBank = document.createElement('input'); + fleetAmmoBank.setAttribute('type', 'text'); + fleetAmmoBank.placeholder = fleet.account.stats.cargoStats.ammoCapacity; + fleetAmmoBank.setAttribute('size', fleetAmmoBank.getAttribute('placeholder').length); + fleetAmmoBankTd.appendChild(fleetAmmoBank); + fleetRow.appendChild(fleetAmmoBankTd); + + // create moveType selector + const fleetMoveTypeTd = document.createElement('td'); + const fleetMoveType = document.createElement('select'); + const fleetMoveTypeOptions = ['Subwarp','Warp', 'Hybrid']; + fleetMoveTypeOptions.forEach((type, key) => { + fleetMoveType[key] = new Option(type, type == 'Warp', type == fleet.moveType) + }); + fleetMoveTypeTd.appendChild(fleetMoveType); + fleetRow.appendChild(fleetMoveTypeTd); + + const fleetContainer = document.querySelector('#assistModal .assist-modal-body table'); + fleetContainer.appendChild(fleetRow); + console.log(fleet); + fleetContainer.appendChild(addScanOptions(fleet)) + fleetContainer.appendChild(addMineOptions(fleet)) + fleetContainer.appendChild(addTransportOptions(fleet)) + + const padRowTd = document.createElement('td'); padRowTd.setAttribute('colspan', '7'); padRowTd.style.height = '15px'; - padRow.appendChild(padRowTd); - targetElem.appendChild(padRow); + + const paddingRow = document.createElement('tr'); + paddingRow.classList.add('assist-pad-row'); + paddingRow.style.display = 'table-row'; + + paddingRow.appendChild(padRowTd); + fleetContainer.appendChild(paddingRow); fleetAssignment.onchange = function() { - if (fleetAssignment.value == 'Scan') { - scanRow.style.display = 'table-row'; - mineRow.style.display = 'none'; - transportRow.style.display = 'none'; - padRow.style.display = 'table-row'; - fleetRow.classList.add('show-top-border'); - } else if (fleetAssignment.value == 'Mine') { - mineRow.style.display = 'table-row'; - scanRow.style.display = 'none'; - transportRow.style.display = 'none'; - padRow.style.display = 'table-row'; - fleetRow.classList.add('show-top-border'); - } else if (fleetAssignment.value == 'Transport') { - transportRow.style.display = 'table-row'; - scanRow.style.display = 'none'; - mineRow.style.display = 'none'; - padRow.style.display = 'table-row'; - fleetRow.classList.add('show-top-border'); - } else { - scanRow.style.display = 'none'; - mineRow.style.display = 'none'; - transportRow.style.display = 'none'; - padRow.style.display = 'none'; - fleetRow.classList.remove('show-top-border'); - } - }; - /* - fleetAssignment.onchange = function(event) { - console.log(event.currentTarget); - if (event.currentTarget.checked) { - transportRow.style.display = 'table-row'; - fleetRow.classList.add('show-top-border'); - } else { - transportRow.style.display = 'none'; - fleetRow.classList.remove('show-top-border'); - } - }; - */ + console.log(this.value); + } } function updateAssistStatus(fleet) { @@ -1852,7 +2254,7 @@ fleetRow.classList.add('assist-fleet-row'); fleetRow.setAttribute('pk', fleet.publicKey.toString()); let fleetLabel = document.createElement('span'); - fleetLabel.innerHTML = fleet.label; + fleetLabel.innerHTML = fleet.name; let fleetLabelTd = document.createElement('td'); fleetLabelTd.appendChild(fleetLabel); let fleetTool = document.createElement('span'); @@ -1876,36 +2278,41 @@ } } - async function saveAssistInput() { - let fleetRows = document.querySelectorAll('#assistModal .assist-fleet-row'); - let scanRows = document.querySelectorAll('#assistModal .assist-scan-row'); - let mineRows = document.querySelectorAll('#assistModal .assist-mine-row'); - let transportRows = document.querySelectorAll('#assistModal .assist-transport-row > td'); - let errElem = document.querySelectorAll('#assist-modal-error'); - let errBool = false; - for (let [i, row] of fleetRows.entries()) { - let rowErrBool = false; - let fleetPK = row.getAttribute('pk'); - let fleetName = row.children[0].firstChild.innerText; - let fleetAssignment = row.children[1].firstChild.value; - //let fleetResupply = row.children[2].firstChild.checked; - let fleetDestCoord = row.children[2].firstChild.value; - let fleetStarbaseCoord = row.children[3].firstChild.value; - let subwarpPref = row.children[4].firstChild.checked; - let destX = fleetDestCoord.split(',').length > 1 ? fleetDestCoord.split(',')[0].trim() : ''; - let destY = fleetDestCoord.split(',').length > 1 ? fleetDestCoord.split(',')[1].trim() : ''; - let starbaseX = fleetStarbaseCoord.split(',').length > 1 ? fleetStarbaseCoord.split(',')[0].trim() : ''; - let starbaseY = fleetStarbaseCoord.split(',').length > 1 ? fleetStarbaseCoord.split(',')[1].trim() : ''; - let userFleetIndex = userFleets.findIndex(item => {return item.publicKey == fleetPK}); - let moveType = subwarpPref == true ? 'subwarp' : 'warp'; - let moveDist = calculateMovementDistance([starbaseX,starbaseY], [destX,destY]); - //let maxWarpDist = userFleets[i].maxWarpDistance / 100; - //let warpCnt = Math.ceil(moveDist / maxWarpDist); - let warpCost = calculateWarpFuelBurn(userFleets[userFleetIndex], moveDist); - //if (fleetAssignment !== '' && (moveDist > userFleets[userFleetIndex].maxWarpDistance / 100)) { - if (fleetAssignment !== '' && (warpCost > userFleets[userFleetIndex].fuelCapacity)) { - let subwarpCost = calculateSubwarpFuelBurn(userFleets[userFleetIndex], moveDist); - if (subwarpCost * 2 > userFleets[userFleetIndex].fuelCapacity) { + async function saveConfig() { + const fleetRows = document.querySelectorAll('#assistModal .assist-fleet-row'); + const scanRows = document.querySelectorAll('#assistModal .assist-scan-row'); + const mineRows = document.querySelectorAll('#assistModal .assist-mine-row'); + const transportRows = document.querySelectorAll('#assistModal .assist-transport-row > td'); + let error = false; + + for (let [i, row] of fleetRows.entries()) { + const fleetPK = row.getAttribute('pk'); + const fleetIndex = userFleets.findIndex(item => {return item.publicKey.toString() == fleetPK}); + const fleet = userFleets[fleetIndex]; + const name = row.children[0].firstChild.innerText; + const assignment = row.children[1].firstChild.value; + const moveType = row.children[4].firstChild.checked ? 'subwarp' : 'warp'; + const scanMin = parseInt(scanRows[i].children[1].children[0].children[1].value) || 0; + const scanMove = scanRows[i].children[2].children[0].children[1].checked; + + let mineResource = mineRows[i].children[1].children[1].value; + mineResource = ResourceTokens.findName(mineResource); + + const destination = { + coords: row.children[2].firstChild.value, + supplies: {} + } + + const origin = { + coords: row.children[3].firstChild.value, + supplies: {} + } + + const moveDist = calculateMovementDistance(origin.coords, destination.coords); + const warpCost = calculateWarpFuelBurn(fleet, moveDist); + if (assignment !== '' && (warpCost > fleet.account.stats.cargoStats.fuelCapacity)) { + let subwarpCost = calculateSubwarpFuelBurn(fleet, moveDist); + if (subwarpCost * 2 > fleet.account.stats.cargoStats.fuelCapacity) { console.log('ERROR: Fleet will not have enough fuel to return to starbase'); row.children[4].firstChild.style.border = '2px solid red'; row.children[5].firstChild.style.border = '2px solid red'; @@ -1917,148 +2324,108 @@ } } - let scanMin = parseInt(scanRows[i].children[1].children[0].children[1].value) || 0; - let scanMove = scanRows[i].children[2].children[0].children[1].checked; - - let fleetMineResource = mineRows[i].children[1].children[1].value; - fleetMineResource = fleetMineResource !== '' ? resourceTokens.find(r => r.name == fleetMineResource).token : ''; - - let transportToTarget = transportRows[i].querySelectorAll(':scope > .transport-to-target > div'); - let transportResource1 = transportToTarget[1].children[0].value; - transportResource1 = transportResource1 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportResource1).token : ''; - let transportResource1Perc = parseInt(transportToTarget[1].children[1].value) || 0; - let transportResource2 = transportToTarget[2].children[0].value; - transportResource2 = transportResource2 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportResource2).token : ''; - let transportResource2Perc = parseInt(transportToTarget[2].children[1].value) || 0; - let transportResource3 = transportToTarget[3].children[0].value; - transportResource3 = transportResource3 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportResource3).token : ''; - let transportResource3Perc = parseInt(transportToTarget[3].children[1].value) || 0; - let transportResource4 = transportToTarget[4].children[0].value; - transportResource4 = transportResource4 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportResource4).token : ''; - let transportResource4Perc = parseInt(transportToTarget[4].children[1].value) || 0; - let ammoWanted = 0; - let fuelWanted = 0; - let cargoWanted = 0; - for (let i=1; i < 5; i++) { - if (transportToTarget[i].children[0].value == 'Ammo') { - ammoWanted += parseInt(transportToTarget[i].children[1].value); - } else if (transportToTarget[i].children[0].value == 'Fuel') { - fuelWanted += parseInt(transportToTarget[i].children[1].value); - } else { - cargoWanted += parseInt(transportToTarget[i].children[1].value); + const transportToDestination = transportRows[i].querySelectorAll(':scope > .transport-to-target > div'); + const transportToOrigin = transportRows[i].querySelectorAll(':scope > .transport-to-starbase > div'); + const transportDivs = [ ...transportToDestination, ...transportToOrigin]; + + for (let div of transportDivs) { + if (!div.children[0]) continue; + const name = ResourceTokens.findName(div.children[0].value); + const amount = parseInt(div.children[1].value) || 0; + const parent = div.parentElement.className; + + if (name == '') continue; + switch(name) { + case 'Ammo': + if (amount > fleet.account.stats.cargoStats.ammoCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } + case 'Fuel': + if (amount > fleet.account.stats.cargoStats.fuelCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } + default: + if (amount > fleet.account.stats.cargoStats.cargoCapacity) { + div.children[1].style.border = '2px solid red'; + error = true; + continue; + } } - } - if (ammoWanted > userFleets[userFleetIndex].ammoCapacity) cargoWanted += ammoWanted - userFleets[userFleetIndex].ammoCapacity; - if (fuelWanted > userFleets[userFleetIndex].fuelCapacity) cargoWanted += fuelWanted - userFleets[userFleetIndex].fuelCapacity; - if (cargoWanted > userFleets[userFleetIndex].cargoCapacity) { - console.log('ERROR'); - transportToTarget[1].children[1].style.border = '2px solid red'; - transportToTarget[2].children[1].style.border = '2px solid red'; - transportToTarget[3].children[1].style.border = '2px solid red'; - transportToTarget[4].children[1].style.border = '2px solid red'; - errElem[0].innerHTML = 'ERROR: Total cannot exceed Max Capacity'; - errBool = true; - rowErrBool = true; - } - let transportToStarbase = transportRows[i].querySelectorAll(':scope > .transport-to-starbase > div'); - let transportSBResource1 = transportToStarbase[1].children[0].value; - transportSBResource1 = transportSBResource1 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportSBResource1).token : ''; - let transportSBResource1Perc = parseInt(transportToStarbase[1].children[1].value) || 0; - let transportSBResource2 = transportToStarbase[2].children[0].value; - transportSBResource2 = transportSBResource2 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportSBResource2).token : ''; - let transportSBResource2Perc = parseInt(transportToStarbase[2].children[1].value) || 0; - let transportSBResource3 = transportToStarbase[3].children[0].value; - transportSBResource3 = transportSBResource3 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportSBResource3).token : ''; - let transportSBResource3Perc = parseInt(transportToStarbase[3].children[1].value) || 0; - let transportSBResource4 = transportToStarbase[4].children[0].value; - transportSBResource4 = transportSBResource4 !== '' ? resourceTokens.concat(r4Tokens).find(r => r.name == transportSBResource4).token : ''; - let transportSBResource4Perc = parseInt(transportToStarbase[4].children[1].value) || 0; - let ammoSBWanted = 0; - let fuelSBWanted = 0; - let cargoSBWanted = 0; - for (let i=1; i < 5; i++) { - if (transportToStarbase[i].children[0].value == 'Ammo') { - ammoSBWanted += parseInt(transportToStarbase[i].children[1].value); - } else if (transportToStarbase[i].children[0].value == 'Fuel') { - fuelSBWanted += parseInt(transportToStarbase[i].children[1].value); - } else { - cargoSBWanted += parseInt(transportToStarbase[i].children[1].value); - } - } - if (ammoSBWanted > userFleets[userFleetIndex].ammoCapacity) cargoSBWanted += ammoSBWanted - userFleets[userFleetIndex].ammoCapacity; - if (fuelSBWanted > userFleets[userFleetIndex].fuelCapacity) cargoSBWanted += fuelSBWanted - userFleets[userFleetIndex].fuelCapacity; - if (cargoSBWanted > userFleets[userFleetIndex].cargoCapacity) { - console.log('ERROR'); - transportToStarbase[1].children[1].style.border = '2px solid red'; - transportToStarbase[2].children[1].style.border = '2px solid red'; - transportToStarbase[3].children[1].style.border = '2px solid red'; - transportToStarbase[4].children[1].style.border = '2px solid red'; - errElem[0].innerHTML = 'ERROR: Total cannot exceed Max Capacity'; - errBool = true; - rowErrBool = true; + div.children[1].style.border = null; + if (parent == 'transport-to-target') destination.supplies[name] = amount; + if (parent == 'transport-to-starbase') origin.supplies[name] = amount; } - if (rowErrBool === false) { - let fleetSavedData = await GM.getValue(fleetPK, '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetMoveTarget = fleetParsedData && fleetParsedData.moveTarget ? fleetParsedData.moveTarget : ''; - destX = Number(destX); - destY = Number(destY); - let scanShiftX = destX > 0 ? -1 : 1; - let scanShiftY = destY > 0 ? -1 : 1; - let scanBlock = []; - if (destX !== '' && destY !== '') { - scanBlock.push([destX, destY]); - scanBlock.push([destX+scanShiftX, destY]); - scanBlock.push([destX+scanShiftX, destY+scanShiftY]); - scanBlock.push([destX, destY+scanShiftY]); - } - await GM.setValue(fleetPK, `{\"name\": \"${fleetName}\", \"assignment\": \"${fleetAssignment}\", \"mineResource\": \"${fleetMineResource}\", \"dest\": \"${fleetDestCoord}\", \"starbase\": \"${fleetStarbaseCoord}\", \"moveType\": \"${moveType}\", \"subwarpPref\": \"${subwarpPref}\", \"moveTarget\": \"${fleetMoveTarget}\", \"transportResource1\": \"${transportResource1}\", \"transportResource1Perc\": ${transportResource1Perc}, \"transportResource2\": \"${transportResource2}\", \"transportResource2Perc\": ${transportResource2Perc}, \"transportResource3\": \"${transportResource3}\", \"transportResource3Perc\": ${transportResource3Perc}, \"transportResource4\": \"${transportResource4}\", \"transportResource4Perc\": ${transportResource4Perc}, \"transportSBResource1\": \"${transportSBResource1}\", \"transportSBResource1Perc\": ${transportSBResource1Perc}, \"transportSBResource2\": \"${transportSBResource2}\", \"transportSBResource2Perc\": ${transportSBResource2Perc}, \"transportSBResource3\": \"${transportSBResource3}\", \"transportSBResource3Perc\": ${transportSBResource3Perc}, \"transportSBResource4\": \"${transportSBResource4}\", \"transportSBResource4Perc\": ${transportSBResource4Perc}, \"scanBlock\": ${JSON.stringify(scanBlock)}, \"scanMin\": ${scanMin}, \"scanMove\": \"${scanMove}\"}`); - userFleets[userFleetIndex].mineResource = fleetMineResource; - userFleets[userFleetIndex].destCoord = fleetDestCoord; - userFleets[userFleetIndex].starbaseCoord = fleetStarbaseCoord; - userFleets[userFleetIndex].moveType = moveType; - userFleets[userFleetIndex].scanBlock = scanBlock; - userFleets[userFleetIndex].scanMin = scanMin; - userFleets[userFleetIndex].scanMove = scanMove ? 'true' : 'false'; + + const fleetData = { + name, + assignment, + mineResource, + destination, + origin, + moveType, + scanMin, + scanMove, } - } - if (errBool === false) { - errElem[0].innerHTML = ''; - assistModalToggle(); + + if (error) return; + userFleets[fleetIndex] = { ...fleet, ...fleetData }; + await GM.setValue(fleet.publicKey.toString(), JSON.stringify(fleet)); } } - async function assistImportToggle() { - let targetElem = document.querySelector('#importModal'); - if (targetElem.style.display === 'none') { - targetElem.style.display = 'block'; - let importText = document.querySelector('#importText'); - importText.value = '{'; - let fleetKeys = GM_listValues(); - console.log(fleetKeys); - for (let i in fleetKeys) { - let fleetSavedData = await GM.getValue(fleetKeys[i], '{}'); - //let fleetParsedData = JSON.parse(fleetSavedData); - importText.value += '"' + fleetKeys[i] + '":' + fleetSavedData; - if (i < fleetKeys.length - 1) importText.value += ','; - } - importText.value += '}'; - assistModalToggle(); - } else { - targetElem.style.display = 'none'; + // let fleetSavedData = await GM.getValue(fleetPK, '{}'); + // let fleetParsedData = JSON.parse(fleetSavedData); + // let fleetMoveTarget = fleetParsedData.destination || ''; + // destX = Number(destX); + // destY = Number(destY); + // let scanShiftX = destX > 0 ? -1 : 1; + // let scanShiftY = destY > 0 ? -1 : 1; + // let scanBlock = []; + // if (destX !== '' && destY !== '') { + // scanBlock.push([destX, destY]); + // scanBlock.push([destX+scanShiftX, destY]); + // scanBlock.push([destX+scanShiftX, destY+scanShiftY]); + // scanBlock.push([destX, destY+scanShiftY]); + // } + + async function exportConfig() { + const importText = document.querySelector('#importText'); + const exportData = {} + + for (let fleet of userFleets) { + const fleetPublicKeyString = fleet.publicKey.toString(); + const savedFleetData = await GM.getValue(fleetPublicKeyString, false); + if (savedFleetData) exportData[fleetPublicKeyString] = savedFleetData; } + importText.value = JSON.stringify(exportData); + assistImportExportToggle(); } - async function saveConfigImport() { - let importText = document.querySelector('#importText'); - let jsonConfig = JSON.parse(importText.value); + async function importConfig() { + const importText = document.querySelector('#importText'); + const jsonConfig = JSON.parse(importText.value); + for (let key in jsonConfig) { let fleetObj = jsonConfig[key]; let fleetJson = JSON.stringify(fleetObj); await GM.setValue(key, fleetJson); } - assistImportToggle(); + assistImportExportToggle(); + } + + async function assistImportExportToggle() { + const importModal = document.querySelector('#importModal'); + if (importModal.style.display === 'none') { + importModal.style.display = 'block'; + assistModalToggle(); + } else { + importModal.style.display = 'none'; + } } function assistModalToggle() { @@ -2087,8 +2454,8 @@ } } - function assistCheckToggle() { - let targetElem = document.querySelector('#assistCheck'); + function assistSurveillanceToggle() { + let targetElem = document.querySelector('#assistSurveillance'); if (targetElem.style.display === 'none') { targetElem.style.display = 'block'; } else { @@ -2096,6 +2463,7 @@ } } + // TODO - need to test to see if it works with the new playerProfile object async function assistProfileToggle(profiles) { return new Promise(async resolve => { let targetElem = document.querySelector('#profileModal'); @@ -2103,7 +2471,7 @@ targetElem.style.display = 'block'; let contentElem = document.querySelector('#profileDiv'); let transportOptStr = ''; - profiles.forEach( function(profile) {transportOptStr += '';}); + profiles.forEach( function(profile) {transportOptStr += '';}); let profileSelect = document.createElement('select'); profileSelect.size = profiles.length + 1; profileSelect.style.padding = '2px 10px'; @@ -2131,929 +2499,25 @@ } } - async function handleMovement(i, moveDist, moveX, moveY) { - return new Promise(async resolve => { - let moveTime = 1; - //let moveCost = 0; - let warpCooldownFinished = 0; - let fleetAcctInfo = await solanaConnection.getAccountInfo(userFleets[i].publicKey); - let [fleetState, extra] = getFleetState(fleetAcctInfo); - if (fleetState == 'Idle' && extra.length > 1 && moveDist && moveX !== null && moveX !== '' && moveY != null && moveY !== '') { - if (extra[0] !== moveX || extra[1] !== moveY) { - let warpCost = calculateWarpFuelBurn(userFleets[i], moveDist); - let subwarpCost = calculateSubwarpFuelBurn(userFleets[i], moveDist); - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentCargoFuel = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentCargoFuelCnt = currentCargoFuel ? currentCargoFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - if (userFleets[i].moveType == 'warp' && (currentFuelCnt + currentCargoFuelCnt) >= warpCost) { - let fleetAcctData = sageProgram.coder.accounts.decode('Fleet', fleetAcctInfo.data); - let warpCooldownExpiresAt = fleetAcctData.warpCooldownExpiresAt.toNumber() * 1000; - while (Date.now() < warpCooldownExpiresAt) { - if (userFleets[i].state.slice(0, 13) !== 'Warp Cooldown') { - console.log(`[${userFleets[i].label}] Waiting for warp cooldown`); - userFleets[i].state = 'Warp Cooldown [' + new Date(warpCooldownExpiresAt).toLocaleTimeString() + ']'; - updateAssistStatus(userFleets[i]); - } - await wait(5000); - } - await wait(2000); - if (moveDist > userFleets[i].maxWarpDistance / 100) { - let warpCnt = userFleets[i].maxWarpDistance > 0 ? moveDist / (userFleets[i].maxWarpDistance / 100) : 1; - let distXRaw = (moveX - extra[0]) / warpCnt; - let distYRaw = (moveY - extra[1]) / warpCnt; - let distX = distXRaw > 0 ? Math.floor(distXRaw) : Math.ceil(distXRaw); - let distY = distYRaw > 0 ? Math.floor(distYRaw) : Math.ceil(distYRaw); - moveX = extra[0] + distX; - moveY = extra[1] + distY; - let fleetSavedData = await GM.getValue(userFleets[i].publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetPK = userFleets[i].publicKey.toString(); - fleetParsedData.moveTarget = userFleets[i].moveTarget; - await GM.setValue(fleetPK, JSON.stringify(fleetParsedData)); - moveDist = calculateMovementDistance(extra, [moveX,moveY]); - } - console.log(`[${userFleets[i].label}] Warping to [${moveX},${moveY}]`); - moveTime = calculateWarpTime(userFleets[i], moveDist); - //moveCost = calculateWarpFuelBurn(userFleets[i], moveDist); - userFleets[i].state = 'Warp [' + new Date(Date.now()+(moveTime * 1000 + 10000)).toLocaleTimeString() + ']'; - let warpResult = await execWarp(userFleets[i], moveX, moveY); - console.log('Warp Result: ', warpResult); - warpCooldownFinished = Date.now() + userFleets[i].warpCooldown*1000 + 2000; - } else if (currentFuelCnt + currentCargoFuelCnt >= subwarpCost) { - console.log(`[${userFleets[i].label}] Subwarping to [${moveX},${moveY}]`); - moveTime = calculateSubwarpTime(userFleets[i], moveDist); - //moveCost = calculateSubwarpFuelBurn(userFleets[i], moveDist); - userFleets[i].state = 'Subwarp [' + new Date(Date.now()+(moveTime * 1000 + 10000)).toLocaleTimeString() + ']'; - let subwarpResult = await execSubwarp(userFleets[i], moveX, moveY); - console.log('Subwarp Result: ', subwarpResult); - } else { - console.log(`[${userFleets[i].label}] Unable to move, lack of fuel`); - userFleets[i].state = 'ERROR: Not enough fuel'; - } - updateAssistStatus(userFleets[i]); - } - } - await wait(moveTime * 1000); - fleetAcctInfo = await solanaConnection.getAccountInfo(userFleets[i].publicKey); - [fleetState, extra] = getFleetState(fleetAcctInfo); - let warpFinish = fleetState == 'MoveWarp' ? extra.warpFinish.toNumber() * 1000 : 0; - let subwarpFinish = fleetState == 'MoveSubwarp' ? extra.arrivalTime.toNumber() * 1000 : 0; - let endTime = warpFinish > subwarpFinish ? warpFinish : subwarpFinish; - while (endTime > Date.now()) { - userFleets[i].state = 'Move [' + new Date(endTime).toLocaleTimeString() + ']'; - await wait(10000); - } - await wait(2000); - console.log(`[${userFleets[i].label}] Exiting Warp/Subwarp`); - if (fleetState == 'MoveWarp') { - await execExitWarp(userFleets[i]); - console.log(`[${userFleets[i].label}] Idle`); - userFleets[i].state = 'Idle'; - } else if (fleetState == 'MoveSubwarp'){ - await execExitSubwarp(userFleets[i]); - console.log(`[${userFleets[i].label}] Idle`); - userFleets[i].state = 'Idle'; - } - updateAssistStatus(userFleets[i]); - fleetAcctInfo = await solanaConnection.getAccountInfo(userFleets[i].publicKey); - [fleetState, extra] = getFleetState(fleetAcctInfo); - if (fleetState == 'Idle' && extra) { - let targetX = userFleets[i].moveTarget != '' && userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[0].trim() : ''; - let targetY = userFleets[i].moveTarget != '' && userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[1].trim() : ''; - if (extra[0] == targetX && extra[1] == targetY) { - userFleets[i].moveTarget = []; - let fleetSavedData = await GM.getValue(userFleets[i].publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetPK = userFleets[i].publicKey.toString(); - fleetParsedData.moveTarget = userFleets[i].moveTarget; - await GM.setValue(fleetPK, JSON.stringify(fleetParsedData)); - } - } - await wait(2000); - resolve(warpCooldownFinished); - }); - } - - async function handleScan(i, fleetCoords, destCoords) { - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let cargoCnt = fleetCurrentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount, 0); - let currentToolAcct = fleetCurrentCargo.value.find(item => item.pubkey.toString() === userFleets[i].repairKitToken.toString()); - let currentToolCnt = currentToolAcct.account.data.parsed.info.delegatedAmount ? currentToolAcct.account.data.parsed.info.delegatedAmount.uiAmount : 0; - let readyToScan = true; - - if (userFleets[i].scanCost == 0) { - if (userFleets[i].cargoCapacity - cargoCnt < 100) { - readyToScan = false; - } - } else { - if (currentToolCnt < userFleets[i].scanCost) { - readyToScan = false; - } - } - - if (readyToScan) { - if ((fleetCoords[0] !== destCoords[0] || fleetCoords[1] !== destCoords[1])) { - if (userFleets[i].state.slice(0, 13) !== 'Warp Cooldown') { - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let starbaseX = userFleets[i].starbaseCoord.split(',')[0].trim(); - let starbaseY = userFleets[i].starbaseCoord.split(',')[1].trim(); - let furthestPoint = userFleets[i].scanBlock.reduce((max, val) => calculateMovementDistance(max, [starbaseX,starbaseY]) > calculateMovementDistance(val, [starbaseX,starbaseY]) ? max : val); - let distToStarbase = Math.max(calculateMovementDistance(fleetCoords, [starbaseX,starbaseY]), calculateMovementDistance(furthestPoint, [starbaseX,starbaseY])); - console.log(`[${userFleets[i].label}] DEBUG`); - console.log(`[${userFleets[i].label}] distToStarbase: ${distToStarbase}`); - let fuelNeeded = 0; - let exactFuelNeeded = 0; - if (userFleets[i].moveType == 'warp') { - fuelNeeded = calculateWarpFuelBurn(userFleets[i], distToStarbase) + calculateWarpFuelBurn(userFleets[i], 2); - exactFuelNeeded = calculateWarpFuelBurn(userFleets[i], distToStarbase); - } else { - fuelNeeded = calculateSubwarpFuelBurn(userFleets[i], distToStarbase) + calculateSubwarpFuelBurn(userFleets[i], 2); - exactFuelNeeded = calculateSubwarpFuelBurn(userFleets[i], distToStarbase); - } - console.log(`[${userFleets[i].label}] currentFuelCnt: ${currentFuelCnt}`); - console.log(`[${userFleets[i].label}] fuelNeeded: ${exactFuelNeeded}`); - if (currentFuelCnt > fuelNeeded) { - let moveDist = calculateMovementDistance(fleetCoords, destCoords); - if (moveDist > 0) { - let warpCooldownFinished = await handleMovement(i, moveDist, destCoords[0], destCoords[1]); - console.log(`[${userFleets[i].label}] Movement finished`); - userFleets[i].scanSectorStart = Date.now() - } else { - console.log(`[${userFleets[i].label}] Skipping movement`); - } - } - } - } - if (Date.now() > userFleets[i].scanEnd) { - userFleets[i].state = 'Scanning'; - updateAssistStatus(userFleets[i]); - userFleets[i].scanEnd = Date.now() + (userFleets[i].scanCooldown * 1000 + 600000); // failsafe to avoid duplicate scans in case Solana is lagging - let scanResult = await execScan(userFleets[i]); - console.log('Scan Result: ', scanResult); - userFleets[i].scanSectorStart = userFleets[i].scanSectorStart == 0 ? Date.now() : userFleets[i].scanSectorStart; - let changesSDU = scanResult ? getBalanceChange(scanResult, userFleets[i].sduToken.toString()) : {postBalance: userFleets[i].sduCnt, preBalance: userFleets[i].sduCnt}; - let changesTool = scanResult ? getBalanceChange(scanResult, userFleets[i].repairKitToken.toString()) : {postBalance: userFleets[i].toolCnt - userFleets[i].scanCost, preBalance: userFleets[i].toolCnt}; - let scanConditionLog = scanResult && scanResult.meta.logMessages ? scanResult.meta.logMessages.find(item => item.startsWith("Program log: SDU probability:")) : null; - let scanCondition = scanConditionLog ? (Number(scanConditionLog.split(' ').pop())*100).toFixed(4) : 0; - console.log(`[${userFleets[i].label}] ${new Date(Date.now()).toISOString()}`); - console.log(`[${userFleets[i].label}] ${scanCondition}`); - if (changesSDU.postBalance != changesSDU.preBalance) { - console.log(`[${userFleets[i].label}] FOUND: ${changesSDU.postBalance - changesSDU.preBalance}`); - userFleets[i].scanSectorStart = Date.now(); - userFleets[i].scanSkipCnt = 0; - //scanTimer = userFleets[i].scanCost > 0 ? 180 : userFleets[i].scanCooldown; - } else { - console.log(`[${userFleets[i].label}] Whomp whomp`); - } - console.log(`[${userFleets[i].label}] Date.now(): ${Date.now()}`); - console.log(`[${userFleets[i].label}] userFleets[i].scanSectorStart: ${userFleets[i].scanSectorStart}`); - console.log(`[${userFleets[i].label}] diff: ${Date.now() - userFleets[i].scanSectorStart}`); - let strike = scanCondition < userFleets[i].scanMin && (Date.now() - userFleets[i].scanSectorStart) >= 120000 ? true : false; - console.log(`[${userFleets[i].label}] strike: ${strike}`); - userFleets[i].scanSkipCnt = strike ? userFleets[i].scanSkipCnt + 1 : 0; - let nextMoveIdx = userFleets[i].scanBlockIdx > 2 ? 0 : userFleets[i].scanBlockIdx+1; - userFleets[i].scanBlockIdx = strike && userFleets[i].scanMove == 'true' ? nextMoveIdx : userFleets[i].scanBlockIdx; - console.log(`[${userFleets[i].label}] Tools Remaining: ${changesTool.postBalance}`); - userFleets[i].toolCnt = changesTool.postBalance; - userFleets[i].sduCnt = changesSDU.postBalance; - if (userFleets[i].scanSkipCnt < 4) { - userFleets[i].state = `Scanning [${scanCondition}%]`; - userFleets[i].scanEnd = Date.now() + (userFleets[i].scanCooldown * 1000 + 2000); - } else { - userFleets[i].scanEnd = Date.now() + 600000; - userFleets[i].state = `Scanning Paused [${new Date(userFleets[i].scanEnd).toLocaleTimeString()}]`; - console.log(`[${userFleets[i].label}] Scanning Paused due to low probability [${new Date(userFleets[i].scanEnd).toLocaleTimeString()}]`); - userFleets[i].scanSectorStart = 0; - userFleets[i].scanSkipCnt = 0; - } - updateAssistStatus(userFleets[i]); - } - /* - setTimeout(() => { - userFleets[i].state = 'Idle'; - updateAssistStatus(userFleets[i]); - }, scanTimer * 1000 + 2000); - */ - } else { - handleResupply(i, fleetCoords) - } - } - - async function handleResupply(i, fleetCoords) { - let starbaseX = userFleets[i].starbaseCoord.split(',')[0].trim(); - let starbaseY = userFleets[i].starbaseCoord.split(',')[1].trim(); - - if (fleetCoords[0] == starbaseX && fleetCoords[1] == starbaseY) { - console.log(`[${userFleets[i].label}] Docking`); - userFleets[i].state = 'Docking'; - updateAssistStatus(userFleets[i]); - await execDock(userFleets[i], userFleets[i].starbaseCoord); - await wait(2000); - console.log(`[${userFleets[i].label}] Unloading`); - userFleets[i].state = 'Unloading'; - updateAssistStatus(userFleets[i]); - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentSduCnt = fleetCurrentCargo.value.find(item => item.pubkey.toString() === userFleets[i].sduToken.toString()) - if (currentSduCnt && currentSduCnt.account.data.parsed.info.tokenAmount.uiAmount > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].cargoHold, 'SDUsgfSZaDhhZ76U3ZgvtFiXsfnHbf2VrzYxjBZ5YbM', userFleets[i].starbaseCoord, currentSduCnt.account.data.parsed.info.tokenAmount.uiAmount); - userFleets[i].sduCnt = 0; - await wait(2000); - } - console.log(`[${userFleets[i].label}] Loading`); - userFleets[i].state = 'Loading'; - updateAssistStatus(userFleets[i]); - let currentToolCnt = fleetCurrentCargo.value.find(item => item.pubkey.toString() === userFleets[i].repairKitToken.toString()) - let fleetCurrentFuel = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuelCnt = fleetCurrentFuel.value.find(item => item.pubkey.toString() === userFleets[i].fuelToken.toString()) - if (userFleets[i].scanCost > 0) { - await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].cargoHold, userFleets[i].repairKitToken, 'tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL', repairKitCargoTypeAcct, userFleets[i].starbaseCoord, userFleets[i].cargoCapacity - currentToolCnt.account.data.parsed.info.tokenAmount.uiAmount); - } - fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentTool = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === 'tooLsNYLiVqzg8o4m3L2Uetbn62mvMWRqkog6PQeYKL'); - userFleets[i].toolCnt = currentTool ? currentTool.account.data.parsed.info.tokenAmount.uiAmount : 0; - await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].fuelTank, userFleets[i].fuelToken, 'fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim', fuelCargoTypeAcct, userFleets[i].starbaseCoord, userFleets[i].fuelCapacity - currentFuelCnt.account.data.parsed.info.tokenAmount.uiAmount); - userFleets[i].fuelCnt = userFleets[i].fuelCapacity; - await wait(2000); - console.log(`[${userFleets[i].label}] Undocking`); - userFleets[i].state = 'Undocking'; - updateAssistStatus(userFleets[i]); - await execUndock(userFleets[i], userFleets[i].starbaseCoord); - await wait(2000); - userFleets[i].state = 'Idle'; - updateAssistStatus(userFleets[i]); - } else { - let moveDist = calculateMovementDistance(fleetCoords, [starbaseX,starbaseY]); - if (moveDist > 0 && userFleets[i].state.slice(0, 13) !== 'Warp Cooldown') { - userFleets[i].state = 'Moving'; - updateAssistStatus(userFleets[i]); - let warpCooldownFinished = await handleMovement(i, moveDist, starbaseX, starbaseY); - console.log(`[${userFleets[i].label}] Movement finished`); - } else { - console.log(`[${userFleets[i].label}] Skipping movement`); - } - } - } - - async function handleMining(i, fleetState, fleetCoords, fleetMining) { - let destX = userFleets[i].destCoord.split(',')[0].trim(); - let destY = userFleets[i].destCoord.split(',')[1].trim(); - let starbaseX = userFleets[i].starbaseCoord.split(',')[0].trim(); - let starbaseY = userFleets[i].starbaseCoord.split(',')[1].trim(); - let [mineItem] = await sageProgram.account.mineItem.all([ - { - memcmp: { - offset: 105, - bytes: userFleets[i].mineResource, - }, - }, - ]); - let resourceHardness = mineItem.account.resourceHardness; - let planets = await getPlanetsFromCoords(destX, destY); - let sageResource = null; - let planet = null; - for (let planetCheck of planets) { - let resourceCheck = await sageProgram.account.resource.all([ - { - memcmp: { - offset: 41, - bytes: planetCheck.publicKey, - }, - }, - { - memcmp: { - offset: 73, - bytes: mineItem.publicKey, - }, - }, - ]); - if (sageResource === null && resourceCheck.length > 0) { - [sageResource] = resourceCheck; - planet = planetCheck - } - } - let systemRichness = null; - if (sageResource && sageResource.account) { - systemRichness = sageResource.account.systemRichness; - } else { - let resShort = resourceTokens.concat(r4Tokens).find(r => r.token == userFleets[i].mineResource).name; - console.log(`[${userFleets[i].label}] ERROR: ${resShort} not found at mining location`); - userFleets[i].state = `ERROR: ${resShort} not found at mining location`; - updateAssistStatus(userFleets[i]); - } - - // fleet PDA - let [fleetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(userFleets[i].mineResource).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetFoodToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.food.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetAmmoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].ammoBank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.ammo.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetCargoAmmoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.ammo.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetFuelToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].fuelTank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let fleetFuelAcct = currentFuel ? currentFuel.pubkey : fleetFuelToken; - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let cargoCnt = fleetCurrentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount, 0); - let currentFood = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.food.toString()); - let fleetFoodAcct = currentFood ? currentFood.pubkey : fleetFoodToken; - let currentFoodCnt = currentFood ? currentFood.account.data.parsed.info.tokenAmount.uiAmount : 0; - let currentResource = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === userFleets[i].mineResource); - let fleetResourceAcct = currentResource ? currentResource.pubkey : fleetResourceToken; - let currentResourceCnt = currentResource ? currentResource.account.data.parsed.info.tokenAmount.uiAmount : 0; - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let fleetAmmoAcct = currentAmmo ? currentAmmo.pubkey : fleetAmmoToken; - let currentAmmoCnt = currentAmmo ? currentAmmo.account.data.parsed.info.tokenAmount.uiAmount : 0; - - let miningDuration = calculateMiningDuration(userFleets[i].cargoCapacity - cargoCnt, userFleets[i].miningRate, resourceHardness, systemRichness); - let foodForDuration = Math.ceil((miningDuration-10) * (userFleets[i].foodConsumptionRate / 10000)); // Adding less food to ensure that food is completely consumed - let ammoForDuration = Math.ceil(miningDuration * (userFleets[i].ammoConsumptionRate / 10000)); - ammoForDuration = Math.min(userFleets[i].ammoCapacity, ammoForDuration); - - let distToTarget = calculateMovementDistance(fleetCoords, [destX,destY]); - let distReturn = calculateMovementDistance([destX,destY], [starbaseX,starbaseY]); - let fuelNeeded = userFleets[i].planetExitFuelAmount; - let warpCost = calculateWarpFuelBurn(userFleets[i], distToTarget) + calculateWarpFuelBurn(userFleets[i], distReturn) + userFleets[i].planetExitFuelAmount; - let halfWarpCost = calculateWarpFuelBurn(userFleets[i], distToTarget) + calculateSubwarpFuelBurn(userFleets[i], distReturn) + userFleets[i].planetExitFuelAmount; - let subwarpCost = calculateSubwarpFuelBurn(userFleets[i], distToTarget) + calculateSubwarpFuelBurn(userFleets[i], distReturn) + userFleets[i].planetExitFuelAmount; - if (userFleets[i].moveType == 'warp') { - fuelNeeded = userFleets[i].planetExitFuelAmount + (userFleets[i].fuelCapacity < warpCost ? userFleets[i].fuelCapacity < halfWarpCost ? subwarpCost : halfWarpCost : warpCost); - } else { - fuelNeeded = userFleets[i].planetExitFuelAmount + subwarpCost; - } - - async function handleMineMovement() { - if (userFleets[i].moveTarget && userFleets[i].moveTarget !== '') { - let targetX = userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[0].trim() : ''; - let targetY = userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[1].trim() : ''; - let moveDist = calculateMovementDistance(fleetCoords, [targetX,targetY]); - if (moveDist > 0) { - let warpCooldownFinished = await handleMovement(i, moveDist, targetX, targetY); - } else { - console.log(`[${userFleets[i].label}] Idle`); - userFleets[i].state = 'Idle'; - updateAssistStatus(userFleets[i]); - } - } else { - console.log(`[${userFleets[i].label}] Mining - ERROR: Fleet must start at Target or Starbase`); - userFleets[i].state = 'ERROR: Fleet must start at Target or Starbase'; - updateAssistStatus(userFleets[i]); - } - } - - if (fleetState === 'Idle') { - console.log(`[${userFleets[i].label}] Mining Status Check`); - let errorResource = []; - let needSupplies = false; - - if (currentFuelCnt < fuelNeeded || currentAmmoCnt < ammoForDuration || currentFoodCnt < foodForDuration || cargoCnt > userFleets[i].cargoCapacity * 0.95) { - needSupplies = true; - } - - if (needSupplies) { - if (fleetCoords[0] == starbaseX && fleetCoords[1] == starbaseY) { - console.log(`[${userFleets[i].label}] Docking`); - userFleets[i].state = 'Docking'; - updateAssistStatus(userFleets[i]); - await execDock(userFleets[i], userFleets[i].starbaseCoord); - await wait(2000); - console.log(`[${userFleets[i].label}] Unloading`); - userFleets[i].state = 'Unloading'; - updateAssistStatus(userFleets[i]); - if (currentResourceCnt > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].cargoHold, userFleets[i].mineResource, userFleets[i].starbaseCoord, currentResourceCnt); - await wait(2000); - } - console.log(`[${userFleets[i].label}] Loading`); - userFleets[i].state = 'Loading'; - updateAssistStatus(userFleets[i]); - if (currentFuelCnt < userFleets[i].fuelCapacity) { - console.log(`[${userFleets[i].label}] Loading fuel`); - let fuelResp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].fuelTank, fleetFuelAcct, sageGameAcct.account.mints.fuel.toString(), fuelCargoTypeAcct, userFleets[i].starbaseCoord, userFleets[i].fuelCapacity - currentFuelCnt); - if (fuelResp && fuelResp.name == 'NotEnoughResource') { - console.log(`[${userFleets[i].label}] ERROR: Not enough fuel`); - errorResource.push('fuel'); - } - await wait(2000); - } - if (currentAmmoCnt < ammoForDuration) { - console.log(`[${userFleets[i].label}] Loading ammo`); - let ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); - let ammoResp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].ammoBank, fleetAmmoAcct, sageGameAcct.account.mints.ammo.toString(), ammoCargoTypeAcct, userFleets[i].starbaseCoord, userFleets[i].ammoCapacity - currentAmmoCnt); - if (ammoResp && ammoResp.name == 'NotEnoughResource') { - console.log(`[${userFleets[i].label}] ERROR: Not enough ammo`); - errorResource.push('ammo'); - } - await wait(2000); - } - fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - cargoCnt = fleetCurrentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount, 0); - miningDuration = calculateMiningDuration(userFleets[i].cargoCapacity - cargoCnt, userFleets[i].miningRate, resourceHardness, systemRichness); - foodForDuration = Math.ceil(miningDuration * (userFleets[i].foodConsumptionRate / 10000)); - if (currentFoodCnt < foodForDuration) { - console.log(`[${userFleets[i].label}] Loading food`); - let foodCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.food); - let foodResp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].cargoHold, fleetFoodAcct, sageGameAcct.account.mints.food.toString(), foodCargoTypeAcct, userFleets[i].starbaseCoord, foodForDuration - currentFoodCnt); - if (foodResp && foodResp.name == 'NotEnoughResource') { - console.log(`[${userFleets[i].label}] ERROR: Not enough food`); - errorResource.push('food'); - } - await wait(2000); - } - if (errorResource.length > 0) { - userFleets[i].state = `ERROR: Not enough ${errorResource.toString()}`; - } else { - console.log(`[${userFleets[i].label}] Undocking`); - await execUndock(userFleets[i], userFleets[i].starbaseCoord); - userFleets[i].state = 'Idle'; - } - updateAssistStatus(userFleets[i]); - //await wait(2000); - //userFleets[i].moveTarget = userFleets[i].destCoord; - } else { - userFleets[i].moveTarget = userFleets[i].starbaseCoord; - handleMineMovement(); - } - } else if (fleetCoords[0] == destX && fleetCoords[1] == destY) { - console.log(`[${userFleets[i].label}] Mining Start`); - userFleets[i].state = 'Mine [' + new Date(Date.now()+(miningDuration * 1000)).toLocaleTimeString() + ']'; - updateAssistStatus(userFleets[i]); - await execStartMining(userFleets[i], mineItem, sageResource, planet); - } else { - userFleets[i].moveTarget = userFleets[i].destCoord; - handleMineMovement(); - } - } else if (userFleets[i].state.slice(0, 4) === 'Mine') { - let mineEnd = (fleetMining.start.toNumber() + miningDuration) * 1000 + 10; // Adding a 10 second buffer to avoid stopping too early - userFleets[i].state = 'Mine [' + new Date(mineEnd).toLocaleTimeString() + ']'; - updateAssistStatus(userFleets[i]); - let sageResourceAcctInfo = await sageProgram.account.resource.fetch(fleetMining.resource); - let mineItem = await sageProgram.account.mineItem.fetch(sageResourceAcctInfo.mineItem); - if (Date.now() > mineEnd) { - console.log(`[${userFleets[i].label}] Mining Stop`); - userFleets[i].state = 'Mining Stop'; - updateAssistStatus(userFleets[i]); - await execStopMining(userFleets[i], fleetMining.resource, sageResourceAcctInfo, sageResourceAcctInfo.mineItem, mineItem.mint); - await wait(2000); - console.log(`[${userFleets[i].label}] Idle`); - userFleets[i].state = 'Idle'; - updateAssistStatus(userFleets[i]); - } - //userFleets[i].moveTarget = userFleets[i].starbaseCoord; - } - } - - async function handleTransport(i, fleetState, fleetCoords, fleetResupply) { - let destX = userFleets[i].destCoord.split(',')[0].trim(); - let destY = userFleets[i].destCoord.split(',')[1].trim(); - let starbaseX = userFleets[i].starbaseCoord.split(',')[0].trim(); - let starbaseY = userFleets[i].starbaseCoord.split(',')[1].trim(); - let moveDist = calculateMovementDistance([starbaseX,starbaseY], [destX,destY]); - let moveTarget = userFleets[i].moveTarget; - let fleetSavedData = await GM.getValue(userFleets[i].publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let resource1 = fleetParsedData.transportResource1; - let resource2 = fleetParsedData.transportResource2; - let resource3 = fleetParsedData.transportResource3; - let resource4 = fleetParsedData.transportResource4; - let targetResources = [fleetParsedData.transportResource1, fleetParsedData.transportResource2, fleetParsedData.transportResource3, fleetParsedData.transportResource4]; - let resource1Perc = fleetParsedData.transportResource1Perc; - let resource2Perc = fleetParsedData.transportResource2Perc; - let resource3Perc = fleetParsedData.transportResource3Perc; - let resource4Perc = fleetParsedData.transportResource4Perc; - let targetResourceAmounts = [fleetParsedData.transportResource1Perc, fleetParsedData.transportResource2Perc, fleetParsedData.transportResource3Perc, fleetParsedData.transportResource4Perc]; - let starbaseResources = [fleetParsedData.transportSBResource1, fleetParsedData.transportSBResource2, fleetParsedData.transportSBResource3, fleetParsedData.transportSBResource4]; - let starbaseResourceAmounts = [fleetParsedData.transportSBResource1Perc, fleetParsedData.transportSBResource2Perc, fleetParsedData.transportSBResource3Perc, fleetParsedData.transportSBResource4Perc]; - - // fleet PDA - let [fleetFuelToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].fuelTank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('fueL3hBZjLLLJHiFH9cqZoozTG3XQZ53diwFPwbzNim').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetAmmoToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].ammoBank.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.ammo.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [fleetCargoFuelToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - sageGameAcct.account.mints.fuel.toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - - if (fleetState === 'Idle') { - console.log(`[${userFleets[i].label}] Transporting`); - let fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let errorResource = []; - if (fleetCoords[0] == starbaseX && fleetCoords[1] == starbaseY) { // Fleet at starbase? - console.log(`[${userFleets[i].label}] Docking`); - userFleets[i].state = 'Docking'; - updateAssistStatus(userFleets[i]); - await execDock(userFleets[i], userFleets[i].starbaseCoord); - await wait(2000); - if (starbaseResourceAmounts[0] > 0 || starbaseResourceAmounts[1] > 0 || starbaseResourceAmounts[2] > 0 || starbaseResourceAmounts[3] > 0) { - console.log(`[${userFleets[i].label}] Unloading`); - userFleets[i].state = 'Unloading'; - updateAssistStatus(userFleets[i]); - - let extraFuel = 0; - let extraAmmo = 0; - for (let [j, resource] of starbaseResources.entries()) { - let resourceAmount = starbaseResourceAmounts[j]; - if (resource !== '' && resourceAmount > 0) { - console.log(`[${userFleets[i].label}] Unloading ${resource}`); - let currentRes = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === resource); - let currentResCnt = currentRes ? currentRes.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmt = resourceAmount; - let resMax = Math.min(currentResCnt, resAmt); - if (resMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].cargoHold, resource, userFleets[i].starbaseCoord, resMax); - await wait(2000); - } - if (resource == sageGameAcct.account.mints.fuel.toString() && resMax < resAmt) extraFuel = resAmt - resMax; - if (resource == sageGameAcct.account.mints.ammo.toString() && resMax < resAmt) extraAmmo = resAmt - resMax; - } - } - - if (extraFuel > 0) { - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resFuelMax = Math.min(currentFuelCnt, extraFuel); - if (resFuelMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].fuelTank, sageGameAcct.account.mints.fuel.toString(), userFleets[i].starbaseCoord, resFuelMax); - await wait(2000); - } - } - - if (extraAmmo > 0) { - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let currentAmmoCnt = currentAmmo ? currentAmmo.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmmoMax = Math.min(currentAmmoCnt, extraAmmo); - if (resAmmoMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].ammoBank, sageGameAcct.account.mints.ammo.toString(), userFleets[i].starbaseCoord, resAmmoMax); - await wait(2000); - } - } - } - console.log(`[${userFleets[i].label}] Refueling`); - userFleets[i].state = 'Refueling'; - updateAssistStatus(userFleets[i]); - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let fleetFuelAcct = currentFuel ? currentFuel.pubkey : fleetFuelToken; - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - if (currentFuelCnt < userFleets[i].fuelCapacity) { - let fuelResp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].fuelTank, fleetFuelAcct, sageGameAcct.account.mints.fuel.toString(), fuelCargoTypeAcct, userFleets[i].starbaseCoord, userFleets[i].fuelCapacity - currentFuelCnt); - if (fuelResp && fuelResp.name == 'NotEnoughResource') { - console.log(`[${userFleets[i].label}] ERROR: Not enough fuel`); - errorResource.push('fuel'); - } - await wait(2000); - } - let fuelNeeded = 0; - if (userFleets[i].moveType == 'warp') { - fuelNeeded = calculateWarpFuelBurn(userFleets[i], moveDist) * 2; - } else { - fuelNeeded = calculateSubwarpFuelBurn(userFleets[i], moveDist) * 2; - } - fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let cargoCnt = fleetCurrentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount, 0); - let cargoSpace = userFleets[i].cargoCapacity - cargoCnt; - if (fuelNeeded > userFleets[i].fuelCapacity) { - let currentFuel = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let fleetCargoFuelAcct = currentFuel ? currentFuel.pubkey : fleetCargoFuelToken; - let cargoFuelAmt = calculateSubwarpFuelBurn(userFleets[i], moveDist) - currentFuelCnt; - if (cargoFuelAmt > 0 && cargoSpace > cargoFuelAmt) { - cargoSpace -= cargoFuelAmt; - cargoCnt += cargoFuelAmt; - let fuelResp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].cargoHold, fleetCargoFuelAcct, sageGameAcct.account.mints.fuel.toString(), fuelCargoTypeAcct, userFleets[i].starbaseCoord, cargoFuelAmt); - if (fuelResp && fuelResp.name == 'NotEnoughResource') { - console.log(`[${userFleets[i].label}] ERROR: Not enough fuel`); - errorResource.push('fuel'); - } - } else { - errorResource.push('fuel'); - } - } - if (errorResource.length == 0) { - userFleets[i].state = 'Loading'; - updateAssistStatus(userFleets[i]); - - let resAmmo = targetResources.indexOf(sageGameAcct.account.mints.ammo.toString()); - let extraAmmo = 0; - if (resAmmo > -1) { - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); - let fleetAmmoAcct = currentAmmo ? currentAmmo.pubkey : fleetAmmoToken; - let currentAmmoCnt = currentAmmo ? currentAmmo.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmmoAmt = targetResourceAmounts[resAmmo]; - let resAmmoMax = Math.min(userFleets[i].ammoCapacity, resAmmoAmt); - if (currentAmmoCnt < resAmmoMax) { - await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].ammoBank, fleetAmmoAcct, sageGameAcct.account.mints.ammo.toString(), ammoCargoTypeAcct, userFleets[i].starbaseCoord, resAmmoMax - currentAmmoCnt); - await wait(2000); - } - if (resAmmoMax < resAmmoAmt) extraAmmo = resAmmoAmt - resAmmoMax; - } - - for (let [j, resource] of targetResources.entries()) { - let resourceAmount = targetResourceAmounts[j]; - if (resource !== '' && resourceAmount > 0) { - console.log(`[${userFleets[i].label}] Loading ${resource}`); - let [fleetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(resource).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let currentRes = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === resource); - let fleetResAcct = currentRes ? currentRes.pubkey : fleetResourceToken; - let resCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == resource); - //let res1Amt = Math.ceil((userFleets[i].cargoCapacity - cargoCnt) * (resource1Perc / 100)); - let resAmt = resource == sageGameAcct.account.mints.ammo ? extraAmmo : resourceAmount; - let resMax = Math.min(cargoSpace, resAmt); - if (resMax > 0) { - cargoSpace -= resMax; - let resp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].cargoHold, fleetResAcct, resource, resCargoTypeAcct, userFleets[i].starbaseCoord, resMax); - if (resp && resp.name == 'NotEnoughResource') { - let resShort = resourceTokens.concat(r4Tokens).find(r => r.token == resource).name; - console.log(`[${userFleets[i].label}] ERROR: Not enough ${resShort}`); - errorResource.push(resShort); - } - await wait(2000); - } - } - } - } - if (errorResource.length > 0) { - userFleets[i].state = `ERROR: Not enough ${errorResource.toString()}`; - } else { - console.log(`[${userFleets[i].label}] Undocking`); - userFleets[i].state = 'Undocking'; - await execUndock(userFleets[i], userFleets[i].starbaseCoord); - } - updateAssistStatus(userFleets[i]); - await wait(2000); - userFleets[i].moveTarget = userFleets[i].destCoord; - } - if (fleetCoords[0] == destX && fleetCoords[1] == destY) { - console.log(`[${userFleets[i].label}] Docking`); - userFleets[i].state = 'Docking'; - updateAssistStatus(userFleets[i]); - await execDock(userFleets[i], userFleets[i].destCoord); - await wait(2000); - if (targetResourceAmounts[0] > 0 || targetResourceAmounts[1] > 0 || targetResourceAmounts[2] > 0 || targetResourceAmounts[3] > 0) { - userFleets[i].state = 'Unloading'; - updateAssistStatus(userFleets[i]); - - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentCargoFuel = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let currentCargoFuelCnt = currentCargoFuel ? currentCargoFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - - let warpCost = calculateWarpFuelBurn(userFleets[i], moveDist); - let subwarpCost = calculateSubwarpFuelBurn(userFleets[i], moveDist); - let extraFuel = (currentFuelCnt + currentCargoFuelCnt) - Math.ceil(subwarpCost); - if (userFleets[i].moveType == 'warp' && currentFuelCnt + currentCargoFuelCnt > warpCost) { - extraFuel = (currentFuelCnt + currentCargoFuelCnt) - Math.ceil(warpCost); - } - console.log('Current Fuel: ', currentFuelCnt); - console.log('Current Cargo Fuel: ', currentCargoFuelCnt); - console.log('Warp Cost: ', warpCost); - console.log('Subwarp Cost: ', subwarpCost); - console.log('Extra Fuel: ', extraFuel); - - let extraAmmo = 0; - for (let [j, resource] of targetResources.entries()) { - let resourceAmount = targetResourceAmounts[j]; - if (resource == sageGameAcct.account.mints.fuel.toString()) resourceAmount = Math.min(extraFuel, targetResourceAmounts[j]); - if (resource !== '' && resourceAmount > 0) { - console.log(`[${userFleets[i].label}] Unloading ${resource}`); - let currentRes = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === resource); - let currentResCnt = currentRes ? currentRes.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmt = resourceAmount; - let resMax = Math.min(currentResCnt, resAmt); - if (resMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].cargoHold, resource, userFleets[i].destCoord, resMax); - await wait(2000); - } - if (resource == sageGameAcct.account.mints.fuel.toString()) extraFuel = resAmt - resMax; - if (resource == sageGameAcct.account.mints.ammo.toString()) extraAmmo = resAmt - resMax; - } - } - - if (extraFuel > 0) { - let fleetCurrentFuelTank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].fuelTank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentFuel = fleetCurrentFuelTank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.fuel.toString()); - let currentFuelCnt = currentFuel ? currentFuel.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resFuelMax = Math.min(currentFuelCnt, extraFuel); - if (resFuelMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].fuelTank, sageGameAcct.account.mints.fuel.toString(), userFleets[i].destCoord, resFuelMax); - await wait(2000); - } - } - - if (extraAmmo > 0) { - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let currentAmmoCnt = currentAmmo ? currentAmmo.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmmoMax = Math.min(currentAmmoCnt, extraAmmo); - if (resAmmoMax > 0) { - await execCargoFromFleetToStarbase(userFleets[i], userFleets[i].ammoBank, sageGameAcct.account.mints.ammo.toString(), userFleets[i].destCoord, resAmmoMax); - await wait(2000); - } - } - } - userFleets[i].state = 'Loading'; - updateAssistStatus(userFleets[i]); - - let resAmmo = starbaseResources.indexOf(sageGameAcct.account.mints.ammo.toString()); - let extraAmmo = 0; - if (resAmmo > -1) { - let fleetCurrentAmmoBank = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].ammoBank, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let currentAmmo = fleetCurrentAmmoBank.value.find(item => item.account.data.parsed.info.mint === sageGameAcct.account.mints.ammo.toString()); - let ammoCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == sageGameAcct.account.mints.ammo); - let fleetAmmoAcct = currentAmmo ? currentAmmo.pubkey : fleetAmmoToken; - let currentAmmoCnt = currentAmmo ? currentAmmo.account.data.parsed.info.tokenAmount.uiAmount : 0; - let resAmmoAmt = starbaseResourceAmounts[resAmmo]; - let resAmmoMax = Math.min(userFleets[i].ammoCapacity, resAmmoAmt); - if (currentAmmoCnt < resAmmoMax) { - console.log(`[${userFleets[i].label}] Loading Ammo`); - await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].ammoBank, fleetAmmoAcct, sageGameAcct.account.mints.ammo.toString(), ammoCargoTypeAcct, userFleets[i].destCoord, resAmmoMax - currentAmmoCnt); - await wait(2000); - } - if (resAmmoMax < resAmmoAmt) extraAmmo = resAmmoAmt - resAmmoMax; - } - - fleetCurrentCargo = await solanaConnection.getParsedTokenAccountsByOwner(userFleets[i].cargoHold, {programId: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')}); - let cargoCnt = fleetCurrentCargo.value.reduce((n, {account}) => n + account.data.parsed.info.tokenAmount.uiAmount, 0); - let cargoSpace = userFleets[i].cargoCapacity - cargoCnt; - - for (let [j, resource] of starbaseResources.entries()) { - let resourceAmount = starbaseResourceAmounts[j]; - if (resource !== '' && resourceAmount > 0) { - console.log(`[${userFleets[i].label}] Loading ${resource}`); - let [fleetResourceToken] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - userFleets[i].cargoHold.toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey(resource).toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let currentRes = fleetCurrentCargo.value.find(item => item.account.data.parsed.info.mint === resource); - let fleetResAcct = currentRes ? currentRes.pubkey : fleetResourceToken; - let resCargoTypeAcct = cargoTypes.find(item => item.account.mint.toString() == resource); - //let res1Amt = Math.ceil((userFleets[i].cargoCapacity - cargoCnt) * (resource1Perc / 100)); - let resAmt = resource == sageGameAcct.account.mints.ammo.toString() ? extraAmmo : resourceAmount; - let resMax = Math.min(cargoSpace, resAmt); - if (resMax > 0) { - cargoSpace -= resMax; - let resp = await execCargoFromStarbaseToFleet(userFleets[i], userFleets[i].cargoHold, fleetResAcct, resource, resCargoTypeAcct, userFleets[i].destCoord, resMax); - if (resp && resp.name == 'NotEnoughResource') { - let resShort = resourceTokens.concat(r4Tokens).find(r => r.token == resource).name; - console.log(`[${userFleets[i].label}] ERROR: Not enough ${resShort}`); - errorResource.push(resShort); - } - await wait(2000); - } - } - } - if (errorResource.length > 0) { - userFleets[i].state = `ERROR: Not enough ${errorResource.toString()}`; - } else { - console.log(`[${userFleets[i].label}] Undocking`); - userFleets[i].state = 'Undocking'; - await execUndock(userFleets[i], userFleets[i].destCoord); - await wait(2000); - } - updateAssistStatus(userFleets[i]); - userFleets[i].moveTarget = userFleets[i].starbaseCoord; - } - if (errorResource.length > 0) { - userFleets[i].state = `ERROR: Not enough ${errorResource.toString()}`; - } else { - if (userFleets[i].moveTarget !== '') { - let targetX = userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[0].trim() : ''; - let targetY = userFleets[i].moveTarget.split(',').length > 1 ? userFleets[i].moveTarget.split(',')[1].trim() : ''; - moveDist = calculateMovementDistance(fleetCoords, [targetX,targetY]); - let warpCooldownFinished = await handleMovement(i, moveDist, targetX, targetY); - } else { - console.log(`[${userFleets[i].label}] Transporting - ERROR: Fleet must start at Target or Starbase`); - userFleets[i].state = 'ERROR: Fleet must start at Target or Starbase'; - updateAssistStatus(userFleets[i]); - } - } - updateAssistStatus(userFleets[i]); - } - } - - let iterCnt = 1; async function startAssistant() { if (enableAssistant) { - for (let i=0, n=userFleets.length; i < n; i++) { - if(userFleets[i].busy) continue; - try { - let fleetSavedData = await GM.getValue(userFleets[i].publicKey.toString(), '{}'); - let fleetParsedData = JSON.parse(fleetSavedData); - let fleetAcctInfo = await solanaConnection.getAccountInfo(userFleets[i].publicKey); - let [fleetState, extra] = getFleetState(fleetAcctInfo); - let fleetCoords = fleetState == 'Idle' ? extra : []; - let fleetMining = fleetState == 'MineAsteroid' ? extra : []; - userFleets[i].startingCoords = fleetCoords; - if (userFleets[i].state == 'MoveWarp' || userFleets[i].state == 'MoveSubwarp') { - handleMovement(i, null, null, null); - } - /* - if (fleetParsedData.assignment == 'Scan' && readyToScan && userFleets[i].state === 'Idle') { // change to fleetState == 'Idle' - console.log(`[${userFleets[i].label}] Scanning`); - let destCoords = userFleets[i].scanBlock[userFleets[i].scanBlockIdx]; - handleScan(i, fleetCoords, destCoords); - } else if (fleetParsedData.assignment == 'Scan' && userFleets[i].state === 'Idle') { - console.log(`[${userFleets[i].label}] Resupplying`); - handleResupply(i); - */ - if (fleetParsedData.assignment == 'Scan' && fleetState == 'Idle') { - console.log(`[${userFleets[i].label}] Scanning`); - let destCoords = userFleets[i].scanBlock[userFleets[i].scanBlockIdx]; - handleScan(i, fleetCoords, destCoords); - } else if (fleetParsedData.assignment == 'Mine') { - if (userFleets[i].state.slice(0, 5) !== 'ERROR') { - handleMining(i, userFleets[i].state, fleetCoords, fleetMining); - } - } else if (fleetParsedData.assignment == 'Transport') { - if (userFleets[i].state.slice(0, 5) !== 'ERROR') { - handleTransport(i, userFleets[i].state, fleetCoords, fleetParsedData.resupply); - } - } - } catch (err) { - console.log('ERROR: ', err); + const fleetQueue = new TaskQueue(userFleets.length); + for (let fleet of userFleets) { + switch(fleet.assignment) { + case 'Mine': + fleetQueue.add(handleMining(fleet)); + break; + case 'Transport': + fleetQueue.add(handleTransport(fleet)); + break; + case 'Scan': + fleetQueue.add(handleScan(fleet)); + break; + default: + console.log(`${ fleet.assignment } is unsupported`); } - await wait(100); - updateAssistStatus(userFleets[i]); } - //console.log('Iter: ', iterCnt); - setTimeout(startAssistant, 20000); - iterCnt++; - }; + } } async function toggleAssistant() { @@ -3066,10 +2530,9 @@ startAssistant(); autoSpanRef.innerHTML = 'Stop'; for (let i=0, n=userFleets.length; i < n; i++) { - let fleetAcctInfo = await solanaConnection.getAccountInfo(userFleets[i].publicKey); - let [fleetState, extra] = getFleetState(fleetAcctInfo); + let {fleetState, extra} = await getCurrentFleet(userFleets[i]); let fleetCoords = fleetState == 'Idle' && extra ? extra : []; - userFleets[i].startingCoords = fleetCoords; + userFleets[i].origin = fleetCoords; userFleets[i].state = fleetState; } } @@ -3106,15 +2569,13 @@ } let observer = new MutationObserver(waitForLabs); - function waitForLabs(mutations, observer){ + function waitForLabs(_mutations, observer){ let elemTrigger = observer ? '#root > div:first-of-type > div:first-of-type > div > header > h1' : 'body'; - //if(document.querySelectorAll('#root > div:first-of-type > div:first-of-type > div > header > h1').length > 0 && !document.getElementById("autoScanBtn")) { - //if(document.querySelectorAll('body').length > 0 && !document.getElementById("autoScanBtn")) { if(document.querySelectorAll(elemTrigger).length > 0 && !document.getElementById("assistContainer")) { document.getElementById("assistContainerIso") && document.getElementById("assistContainerIso").remove(); observer && observer.disconnect(); let assistCSS = document.createElement('style'); - assistCSS.innerHTML = '.assist-modal {display: none; position: fixed; z-index: 2; padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);} .assist-modal-content {position: relative; display: flex; flex-direction: column; background-color: rgb(41, 41, 48); margin: auto; padding: 0; border: 1px solid #888; width: 785px; min-width: 450px; max-width: 75%; height: auto; min-height: 50px; max-height: 85%; overflow-y: auto; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); -webkit-animation-name: animatetop; -webkit-animation-duration: 0.4s; animation-name: animatetop; animation-duration: 0.4s;} #assist-modal-error {color: red; margin-left: 5px; margin-right: 5px; font-size: 16px;} .assist-modal-header-right {color: rgb(255, 190, 77); margin-left: auto !important; font-size: 20px;} .assist-btn {background-color: rgb(41, 41, 48); color: rgb(255, 190, 77); margin-left: 2px; margin-right: 2px;} .assist-btn:hover {background-color: rgba(255, 190, 77, 0.2);} .assist-modal-close:hover, .assist-modal-close:focus {font-weight: bold; text-decoration: none; cursor: pointer;} .assist-modal-btn {color: rgb(255, 190, 77); padding: 5px 5px; margin-right: 5px; text-decoration: none; background-color: rgb(41, 41, 48); border: none; cursor: pointer;} .assist-modal-save:hover { background-color: rgba(255, 190, 77, 0.2); } .assist-modal-header {display: flex; align-items: center; padding: 2px 16px; background-color: rgba(255, 190, 77, 0.2); border-bottom: 2px solid rgb(255, 190, 77); color: rgb(255, 190, 77);} .assist-modal-body {padding: 2px 16px; font-size: 12px;} .assist-modal-body > table {width: 100%;} .assist-modal-body th, .assist-modal-body td {padding-right: 5px, padding-left: 5px;} #assistStatus {background-color: rgba(0,0,0,0.4); opacity: 0.75; backdrop-filter: blur(10px); position: absolute; top: 80px; right: 20px; z-index: 1;} #assistCheck {background-color: rgba(0,0,0,0.75); backdrop-filter: blur(10px); position: absolute; margin: auto; left: 0; right: 0; top: 100px; width: 650px; min-width: 450px; max-width: 75%; z-index: 1;} .dropdown { position: absolute; display: none; margin-top: 25px; margin-left: 152px; background-color: rgb(41, 41, 48); min-width: 120px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); z-index: 2; } .dropdown.show { display: block; } .assist-btn-alt { color: rgb(255, 190, 77); padding: 12px 16px; text-decoration: none; display: block; background-color: rgb(41, 41, 48); border: none; cursor: pointer; } .assist-btn-alt:hover { background-color: rgba(255, 190, 77, 0.2); } #checkresults { padding: 5px; margin-top: 20px; border: 1px solid grey; border-radius: 8px;} .dropdown button {width: 100%; text-align: left;} #assistModal table {border-collapse: collapse;} .assist-scan-row, .assist-mine-row, .assist-transport-row {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-bottom: 1px solid white} .show-top-border {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-top: 1px solid white;}'; + assistCSS.innerHTML = '.assist-modal {display: none; position: fixed; z-index: 2; padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);} .assist-modal-content {position: relative; display: flex; flex-direction: column; background-color: rgb(41, 41, 48); margin: auto; padding: 0; border: 1px solid #888; width: 785px; min-width: 450px; max-width: 75%; height: auto; min-height: 50px; max-height: 85%; overflow-y: auto; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); -webkit-animation-name: animatetop; -webkit-animation-duration: 0.4s; animation-name: animatetop; animation-duration: 0.4s;} #assist-modal-error {color: red; margin-left: 5px; margin-right: 5px; font-size: 16px;} .assist-modal-header-right {color: rgb(255, 190, 77); margin-left: auto !important; font-size: 20px;} .assist-btn {background-color: rgb(41, 41, 48); color: rgb(255, 190, 77); margin-left: 2px; margin-right: 2px;} .assist-btn:hover {background-color: rgba(255, 190, 77, 0.2);} .assist-modal-close:hover, .assist-modal-close:focus {font-weight: bold; text-decoration: none; cursor: pointer;} .assist-modal-btn {color: rgb(255, 190, 77); padding: 5px 5px; margin-right: 5px; text-decoration: none; background-color: rgb(41, 41, 48); border: none; cursor: pointer;} .assist-modal-save:hover { background-color: rgba(255, 190, 77, 0.2); } .assist-modal-header {display: flex; align-items: center; padding: 2px 16px; background-color: rgba(255, 190, 77, 0.2); border-bottom: 2px solid rgb(255, 190, 77); color: rgb(255, 190, 77);} .assist-modal-body {padding: 2px 16px; font-size: 12px;} .assist-modal-body > table {width: 100%;} .assist-modal-body th, .assist-modal-body td {padding-right: 5px, padding-left: 5px;} #assistStatus {background-color: rgba(0,0,0,0.4); opacity: 0.75; backdrop-filter: blur(10px); position: absolute; top: 80px; right: 20px; z-index: 1;} #assistSurveillance {background-color: rgba(0,0,0,0.75); backdrop-filter: blur(10px); position: absolute; margin: auto; left: 0; right: 0; top: 100px; width: 650px; min-width: 450px; max-width: 75%; z-index: 1;} .dropdown { position: absolute; display: none; margin-top: 25px; margin-left: 152px; background-color: rgb(41, 41, 48); min-width: 120px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); z-index: 2; } .dropdown.show { display: block; } .assist-btn-alt { color: rgb(255, 190, 77); padding: 12px 16px; text-decoration: none; display: block; background-color: rgb(41, 41, 48); border: none; cursor: pointer; } .assist-btn-alt:hover { background-color: rgba(255, 190, 77, 0.2); } #checkresults { padding: 5px; margin-top: 20px; border: 1px solid grey; border-radius: 8px;} .dropdown button {width: 100%; text-align: left;} #assistModal table {border-collapse: collapse;} .assist-scan-row, .assist-mine-row, .assist-transport-row {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-bottom: 1px solid white} .show-top-border {background-color: rgba(255, 190, 77, 0.1); border-left: 1px solid white; border-right: 1px solid white; border-top: 1px solid white;}'; let assistModal = document.createElement('div'); assistModal.classList.add('assist-modal'); @@ -3123,7 +2584,7 @@ let assistModalContent = document.createElement('div'); assistModalContent.classList.add('assist-modal-content'); let iconStr = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA4CAYAAABNGP5yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfnCwMTJgKRQOBEAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAZdklEQVRoQ91aB3RUx9Vebe/alXa1fVVWXVr1hgoqICEhikQngOjFBmyQKKJ3CBgDQoAwotqAQPQuwKaDsbExGGzAGLCBALYJcQCbkJB8/523u4Cd5JycnPxAMud8mnnzptz73Tt3Zp6W96JSQkKCyemM6hMfF7ekUaOMI5kZjb/OSM++1ahR5u3ExNRzTmfc9ujomCmNGzduXlhYKHF3++9O7du3FyTHxxdGR0duz8zMeFJcXIy2bdqiTWkblJaWIDu7MYqKmhGK0KxZAbKyshAfn0CIv5eYmDg3NzfX3z3Uf18iS+bGOWPPNUpLA1O8VatWKC0p5VBSUsI9k5Jo2rQpKd8M+fn5yMvLIxIaI436xMbGIiIi4rHT6XybnmXuYV/9REqJmPWindF/a0IKFRUVolXLlihp3ZoUL0Xr1iVo2bIVZ/UuXbogNTWVFM/lkJ2TjcysDDRqlIbk5GTmCQgPD0dgYOAZIiTAPcWrm9q1ayeLiXHuZdZr0iQXRc3y0YIR0KIYrRkJrVpg8KD+mDdvJmbMmIw1a9ejU6cOWL58EUaNqkBBfh7SUlOQkpzKEUBxA3FxcYgIC4fFbL5D5Vj3VK9eys7OFkaGh21NSohHXm4OmhEBxYyAwmYoJcUXVL+Fq1c/x63bF7FrVx0GERE1S1fizfJyzJo1CSdP7se9u19j3apqFORkIIFiQQJ5QHxsHOJiYhAVEQmLyUQBM9HunvLVSkFBAWNjnU7kZmehaW42CvIITXLQp3tnnDtzGJ+fOUou3w4KhRxeXl7o2K03ftOjD9as3wA+34tDSEgQFlVPxfUvtuL1bgWIjYxGrDPGDYoJoeEwGU0nGdnuaV+NFBYWEBbiCPpTVnoGsjMzkZuVSXkGendvi6tfHcGk8SMgl0s5xRkEQhHWb9+P5LR07P3gECKjokDDgOd+nxgfgQ/3zEBl30JEhoXBGRmJmCgnh8CAIFit1rGumV+RZDabdiYnJSEjrREyKYBlNEpGmxaZ+PL0JpR1bUPW5XOKaTRaKJVK5OUXo25rA5GiwKA3hmDh4iXcewp2lLvaWo067FwxEH3bJiMsJIyICEd0RDSiwqNgMph/TkpKsrmnf7nJZjNEBQc7/ppCQSuVISkRGSnx2LlqMAYNKOGU4fG8IJZIUDV/AXR6IzZs34dARwgEAgFn9amz3kajjEyMGj0WTnJ11sfLi4cIhxEH3u2O9LhghDpCER4SzsER4IC/zb/aLcLLTQa97xK29hMpYCXGxyIxNgZv9MzF5tqBEIkFbmW80HfAYKxcuwmzq2rQjda+hAgZP2UKfHU6yBVKrFq3ESPHTsDOvfugUquf9ivvkYIZg7MQZA9EcFAIR0RYcBgFRMuPFAukbjFeTqK1KLNbLT+yNRobTQErOgrxzghsWdAFbfLjOAV4FNzMVisOnfgEn1+8gpWr14FPlm/bqSN5wg7MrV7AeUdSSiOcvXAZn5w9hz4DXn9KQKDZG9tm5yEi0IJAIiGIrB8cGAyrycrIaO0W5eUki8VQ7AgIoHUZQdtUBCLp0NIi24mDK3pAp34W9AYPqcC5C19hdd16WIgMk9lM3lBHHjAV2xsaMHb8eGrHR+eu3bD/gwPYe+AQpDK2W/AgJAIXvxmDNlmBsJltsFsDOCICbAEE+xq3KC8n2czmWor+CKdIzbao0GAHKrolYvPs1hCR4Gx9M2tXL6xBQGDQU0JGjBmDhbXLIBSLIZPLUdyiBUJCQ7mdgMWF0nYdEJ+UwrXlE0a2C8SYDkHQ+/rAYjTDbrGzGECwX3dJ8pKSv9VyxRFELulwICstGZX9S1E3KRfLx+dzgjMFTGYTho+sdG1zhHg6/7+3cSMWEQEpqWncDuF554GFrNvuN93chPHRL9+EFYP8MXVoUxQ1SYHRz0DeYIHNYn0SHBz8cm6OYWFhKrvF+igwgNzR3x8GvQ/aFTfG6mmFWFyZ/ZQAutCAjshPlRMKhUhv3JgIqMWJj09h4+YtaEM3RDF5g6eNQuWNsv7P4kDvpkZsrIzA+KFtYfDVwM/XlzzBBCuREBMaGkh9XnwixfyJgCdMeX+7HUa9HxTkzqWNjFg5JoOWgGs/12q1dALs/FQ5D9jSyKHL0vZdu/HD7+/i0qVL3OWHvTOZzMjMzoMXLSM2RkVLEyZ0tEEqFkKj9ubmMhMBFmpHhkiiPi8+hYaGxpAF/urvIcBggFqlQohBgdWVCbD4uIIg84TBfTtAqZBxypHA3GGIlRkWLlqE+w8eYN26dZx3sLri3AQoZa7+UqEXFvY0oiSRDlE0hi8RavTz45Q3G4wIcziaUZ8XnyJCQrKYEIwA5gVWiuw+Gg0UIjHKi23okmPklPdWyjCxXzoGdMzhzvs+Pj6oIfevHD0GzYuLcevObex7fz+dCuWc8vFhBgxpbUNKuJkjIDVIippuGoQTsd5EsI76mygGsLlNRHpIUFCpS6IXnEIdjkKTwUQEUES2+1NAslCU1kAuFSPcT4qpnazQq8XITQ7EtH7xmDM4HX065XFK2shjDhw+gm9v3sTJjz/iDkOs3mHTYVQHO4aVmtC70AGVRIgp7VXokqKBViaDD3mOzlfHKc4IYMEwKCioMyfQi060BPKIgL/Z7TZuCVhIqCCzD5KMUqhEAqQHSDAgX4832oRjUlkEqsqbok+XEk5Rhmg6PR45dgwBdI7w1AXSODOGdsTIUhteKzDitXwzOibKoZOJEGdRI8ziAx8i2Wg0cgQYaCnQ/aEr9X3xKTSIEWBkNzPYbDZySyMdaRXIDNIiTi+GWCBERpgK5W0DMHtYKebMnIHJ02chJ7cJZGRNGuIXkZ8tpUGDBmHBvCrMquyH3vkByApXQEGBL0gjRockM2QSMXx9fH9BgD3Q/tIIKDKS0owAO/MAi5XWtxZysQRRdl8YVDL0amLDvNE9kENXZLbfFxQ2R139JuzYuYc7G7Ro0RJlZd1RSzHh8OHDqKyshJbiiN1ixpTyXujdPA4GOlEm+GuhVUigJoINev3THYARYLFYXg4BdCNrxdag1UoHEvIARgSzjFqtgIRIyItSY2KpGvnRVCeXcAGN3QolEhkyshpjwMDBqFu/ARUVw1Ba2gYGWkKsDbdzULDMjLJiNMWC4ngdREIRVHR11mmfBUAGPyLjpREQHBjYxagnAij4eQgw06lPr9chyKDCuGIFJreQYWyxFuPaBsHf7PtUQRf4mDBhMncrfL5eLpWieYoFA/N9MaxQi+GFGjj0cm6H8ez/7ADEEaAjbzCbu7tFerEpxOEoM+j8/saUZgRwJNAyMPn5IjtMiWS7lCK3EEqREIVOb8x8IwOFTVIhED67Ik8kAsS0rmk47jk2JhzlZWnomEmurZFCTn1DjHJkhWmhJwKY8uwI7CFARydCIqCXS6IXnGj/7eTnq39iNtFWyHYCIoDBavCD3lsNrUoJNQU7KV8ACSln10owsndjrFw6G4XNirhDz1i6/zMPoB0FU6eMwaTRXZEeaYA3kaSkmKGiIKkm19co1TDofLmt1m4lsoloRoAvnQnI83q6Rfr30p/uX239+OGNsscPb/d8RHjy6Lsejz3gnm/3fPzwm94MTx5e7/X4j9d6PfrD5T7b6muXjRwy4K+jhg3EuMo3Mb5yCCaNKiclhmLC6HIO46iucXIsNOTuDCoio1G0A+8SCdu31WPh4sWYMGky1m9Yhya5qdCKRPAlxf2oncVbhQH9emN4xVBUDi/HmMphGDdmOCaOHcFhwphhVDcUWzesWvHo/u/6PH54s5dHB1Z2ye3Go+8Id9m73o8e3enz6OGdvo8f3ej74N75Lrw//3jkx7Mf1uLo3kU4vv8dDsf2EfYy1OBowyIc3rMIh3ZX49CuKhzcOY/LD1F+cOccHCAc2k3lXVS/ey4ON8wjVNF41Th58B1sem8mrCoF9BTY9ESCj4BOh6RkTnIc6jbUo0ePHvCRyqH1EsKP3vtRO52Aj3FDymjeWpJnBU5+sJLDiX0rSKblONJQS3MtxqE9NZQvIhkXk8w1OL5vEb1fSHMvpDYLSM5qkm8Bhw92VhMWYP/2+di3dS7OfrgMP/3w0e95f/79lvstGoeQhQTQEPNaEk5LuYZyF1z1Lni561xtfChnFmNgZRcE8KWxdFTWUdmPcqaUgXIOpBxT1F9Kt8LYGOjo6KzjFHe/59qz8b2gplxDxHCyURs2rw/lTEY2l5bKXM7aERixTEZvrq8LrI61Y23YmCxndT1L0vDohwM3eX++W3+vONPBCa1nwpGFOCEJzBJ64fOgNgQdlT3vuNwNVmaKs74eZYxUNtLNj8FTx2ChA46JwNrrn6vnwPoTPGPqqa8fzcXesbF/Md9z8CX4kJKcIViZdGHP3Bz07ILLyF2Kk/Hwzm4i4Pu6u8WZwZwVWQNuciq7BCC35eARxA0a1GNVl4Vd8Dyz3EjW50Dtnyrvzj1KGCnKP2v/HH5FFteG2rP+T2Xg4JKPKejLPM8NzgsJv5D5OTBiOhcm4sGt7dd5j+8svVtCHuAn5MFIV0+TyAuBagmmDczBuxOb4b2JTbF2WgFWT8zG2slNUDetGOtmtEDPokhqz8eKSZnYWV2E3Us6w18uRk6EHbuq22LTrFJsm9MBr5XEu0lwYerrBdhd0w17l/VG79JsTtniRDP2vZOP3Yta482OqVTn5WpPJDN4jMLaBiolWDMlAHWT9HhvrBbRdPdgBtERcqJN2FPdFHuqaKw5OYQMNFTnYcfsbGx/Ow/zh2eRfuRNIj66FsXjj9c33+D96cZvv2uT4Q+zxAsWqResMi/kRvniq3V6nFsqxZnFIny7LQhfr9Hjymojrq214Hq9AbP60sGHyDryjgNHZslwelkALPRsJhKrB5rx2VIdTi3yxskaK6J9pVx900gTTlRb8eF8MXbPVcFG5wSmVMdGPvhqvQFHZ8tQ2ZH2eCLAQuQymKls4gzD54zTv3UA9owRYdsIAXaPFmB0VwNnCBOhbZIvLi4T4GqdBJffk+Grd+W4Uq/G+eUSnF8px1XSI8RbBJuUjx50zP7xWt0N3sPPut9pn6qHXe5FFiTrK7zQItmEazssOL/WG5c2mVA9KAFzB8SiZng8lk1MR82INLRJs8NGpO2db8TxOVJ8vNhMgY0FNy+EewtweL4Jn9RoCSpUvWZFkEyADROj8P4kAU5UydA8XklLgBQktE1W48u1Ghx7W4qR7QywUZ1dLIA/gStLXPAnHKgNw95xfOyfLMXhKQLsn0uXMZrXLhWgQ4ofztQIcHqtEtO6i7GT2pyYJyVjqHDqHRm+aQhDrEECB+nYq3kk7l2cf4N3ZXPK7Q6JaoSovTiEq/nIjdDg4gYb9k9kLEuwZYgUm4dIsHucGIdmSXFohgiTOhgRrOBjx0wNTi0U4+wKK0JpYAeR6CAvyguW4NQSEmiJD75YpcPSYQk4OFOJw78VYGpvLaxiUl5Mlia0SVLgC7LYp4skGNPRgEAiMZDGCKKxGLgxCW3T1bi2y0okCnFzZyFOzxfhwnIliuM0NDcfHZN12PCmGPUk79rXJVjdV4K6flJsLRcTaQJ8u9eJWL2QDMSni5oF3x7reIN3cLbxVod4GbmpAE5CjK8QKVY51k8PxroRGtS9oaKB5FhRJsfSblIs6SrFnDYilBfqEKYmgt7S4NxKKS7Q0ggj8kKVLoQoieWmCpxZ5kskKGk5aHFgBllluhKhGrIsKcmWnJ28qH2qEhfXynD+XSnGdzGSsjzOSiGEUKUAYTReGI1XP9uCz2q9cewtJVaOTsM3G/xxboEXFr7pB6eGxkn0xvz2IrzTVYZlJO/K7nIs66nAzN+oMaGjL8pyfRDLdPQRolumGkdqw27zNoxQ3eyUKEeyUYxEsxRlBdHoVRiF/i2j8VrrCAztEI214xOxe6Q3tlcosHWoiiaQYkiBllN47zwfXN6gxtWtUYj3o6urUYEksxJJFhWifCVYMESPs7UaHH1bwXlPuxQZAsiVGWzuvHO6El9vVOLaZl9M6+WAUydDvEGBZJMKqTROOBGd6S/CDwfN+GiOHDummpHlL8X1nak4v5iPMyt0SDEK0DnFBzVdxFjSTYJawvIyKdb0Jg+okOHwTBU+q49DoknMfa9on6LA+lGGW7y64crbyQ45BHRel9Aef2FrEU7XGPExKfbhbDWOkdsenChDwwhyI1oO+8fJ8V4/Cfo11YJPl5id8yjgLVPg0toAXN6Ujqvbc3FlWzaubc/CrPIUut3xcbjagB1jxNgySgxvhZDrx+DlzgsztTi3SkmepMXljXG4siWHxskjZOPGnkIYdUqM6aPFN/VanJyrQO9WVvC9eJg9zB9fLBfj/DIxOhTqkR2rwye0LL5YK8Xet4SYSWTUl7MlK8HBGUJc2h4OjUrM6ZoVIUf9KP0N3taJ6ttpEUruNiag7eb08kTsHcHHnmF87GYYzscuyndVCLCzQoitQ0Soe02MQc003C1u4zR/bB8pwvZRQjRMEOHQdDGOzhTixFt8TO9j49qsG2fEyv58rH5DToS4vvw+j6LGJrxPAWtrJc0xVoT3p4jJYiIcnyXAZwu0cFi88cV6C869o8KZpYHQeUu5fjFhany9yYizS4TYPNeC3DiKN0sFuLlHgbNr5Ng4leLKSjJOvRIX6tS4siMYGtri2cfaokQVGqYYLvI+ejf6w4q+GRDQPstwcWMuPq0y4VSVgeCHTxf4Eat6nHzbF8dn+uDQFAp6JNTk3q4PmbuqwnG8So+jc3UU3fX4eKEfTi82kCBGzB1k4drs/K2D1r8vPqw2wVv57DOYBy3zrLRb+OEYjXF0nh4n2JyLmGJGXH7XjOE9nbi1w4nL9fHYXFXAeQ7rJ5WIaFdogkvrQ3CtIRoV1O7GFieub44jb4nBtfoI3NiZQPElElc2JuDMxqbc/xaYsQd3DsHROfodvFPrS1LufVX7YOq4oRCSB/j5qWGzajiYzd6wEPtWgp2e7TYNXXkJVPZWu6wQEapHUpwFiXFmJMU+Q6LTjECrD9fG4a9FZLAOoYE+7h9L/JIAb5WU+pvceG6cGAuSnBaEBFmQHBdOiIDZqH9KAFMk0GZGsjMCKbGRCLAZEWAlWEw0twFBDFTHyjaTHhKxiNyfj9f7dcTNa2sfHFrVKp7G4fE+WJiU/v2F+fdmTh8NMcUBNvCvwU3ohmf9egTx4Klgz9Vxz/9kvH8FjCzPXB6w9c/V/2qs58f/R2AeXv5mGa6cXfJwQ21xDvV5lnZXpUTe+XL6tVVLZ0ImldBEvxTy2UAuIZ5/97QN9eHeUTvec1bu164xgm06NE2xIs1pRFEjK1rnRiE/1YKmqcEoSAtFekI4UsnCpcW56NKuCC0Ksp6N61GWG98jxzN42j2Fuz37Bun53RFTftSwMlz6dPG9VbNLMqnd36dNcxJMvzs78tymuirEOiMRFRGGmKhIKkchLsaJhFhCnBPxsdH0THXUJjY6gtqEwxkVxiGWyux9dHgoJ2xcVAgq+5egoiwPi0cXEnKwaWY+5lVkYvPMAlR0jcfwrsmYOaQFlkztj9JmGZg3rQL9y0o5wc1GHSLDAhEdFUzjh8AZHUoguaLZnOGIjgzjEMVA8kbRvFHhIdz8zggXYujd5PEDce3sgpvb5rWMcav7j9OWubGaW5+P3vHg1vpHP33X8ONP3zc8vP/9np9+vLP7wR9u77r/w42tj298Vf+3K+frcemz9bjw6Xp8+ck6XDhVh4un1uDCx6tx/vgqzJ8+nGNdTGd0hVQElYy2QLkESsqVMpZLuGetSg6dRkGRXQ6NQgQZnQvEdPxln8zYbwV6dS3B8b3L8OmR93DuBM310QZ88XE9LnyyAZdOb8TVc5uf3Ly05S/fX9vx8x9uNjz446199+/fef/+/e/3P3h494P7D+8eePjwh/2P7l5b8n7D8tb/+g+rwBzJlVjuAW8Cj8cvLAyWpKf7qtQSSaFYIPxZLpRAxhdCSgFGQpB6CVxlgRDr5zbBpCHpsNGFSEFBVk63Ng9kHCiac/CClC4+ajoiN08zY2L3EO4/TDLyAjkDkcnA+kgZsTS2SCCYQyKJCRSWnsr7d4mtBXfxP58EPEGBSCh8IBeJSFASkISV0RpkH0JZxC3MMuNsQzYuH2mLXgUOpJn4yDTzkWXhI8PihUZmL6TRczqhZaQS+xbk07aWiTdKrZyyShpHRWBfd1TsmbxCSnMJBF4raHqm+CuR4mkL/VpKO4iYhBQy5Z8LWAaNCHNG2nH9aAmm9XeidbQAraL5aBXFpzIfpbFCVLQy0C2uFfbMjkMKHXAEXq79WsiR6fEQGl8g/AttpeNpzv8/q/6bSUVHzGpau0/INbnTFslISrh+KCEi92/XRI3TW9Iwf3gssgIlyHIIkR0ixMhOdlK+AJN7BcGHDkqePh6wE6qQyCXFj9P4L+cHEf9qEvF4sUI+f59YSG5Ka/V5RZhHBJjpkjLJjiWjwpFglWFMWRD2VEWiSTzdLaiNi7RnYAcnwkUKiG1o+FfO6v80kcCFhM9ZJGeKUJWbBAqOZM3+bfxwalUc5g/xh5UC5K+t7lb8G0If6ku8/ncmcgJ+Gf355tfewKxt08u4n9Pxn7O6W/EfCOXU/+X+EvQ/mKS0LMrdiv2CCHr3VHHylodUnkx1aq7X/2DSkpKzCD97iHAr/hcqL6P3Flez//1kIaVrCN+S4ivpOcJV/aITj/d/AtCBMSY54ZcAAAAASUVORK5CYII='; - assistModalContent.innerHTML = '
SLY Lab Assistant
x
FleetAssignmentTargetStarbaseSubwarpMax CargoMax AmmoMax Fuel
'; + assistModalContent.innerHTML = '
SLY Lab Assistant
x
FleetAssignmentTargetStarbaseFuelAmmoDrive
'; assistModal.append(assistModalContent); let importModal = document.createElement('div'); @@ -3164,13 +2625,12 @@ assistStatusContent.innerHTML = '
Status
x
FleetToolsSDUsState
' assistStatus.append(assistStatusContent); - let assistCheck = document.createElement('div') - assistCheck.id = 'assistCheck' - assistCheck.style.display = 'none' - let assistCheckContent = document.createElement('div') - //assistCheckContent.classList.add('assist-check-content'); - assistCheckContent.innerHTML = '
Fleet Surveillance
x
'; - assistCheck.append(assistCheckContent) + let assistSurveillance = document.createElement('div') + assistSurveillance.id = 'assistSurveillance' + assistSurveillance.style.display = 'none' + let assistSurveillanceContent = document.createElement('div') + assistSurveillanceContent.innerHTML = '
Fleet Surveillance
x
'; + assistSurveillance.append(assistSurveillanceContent) let autoContainer = document.createElement('div'); autoContainer.style.display = 'flex'; @@ -3181,9 +2641,6 @@ let autoButton = document.createElement('button'); autoButton.id = 'autoScanBtn'; autoButton.classList.add('assist-btn'); - //autoButton.style.position = 'absolute'; - //autoButton.style.left = '50%'; - //autoButton.style.transform = 'translate(-50%, 0)'; autoButton.addEventListener('click', function(e) {toggleAssistant();}); let autoBtnSpan = document.createElement('span'); autoBtnSpan.innerText = initComplete == true ? 'Start' : 'Wait...'; @@ -3211,14 +2668,14 @@ assistConfigSpan.style.fontSize = '14px'; assistConfigButton.appendChild(assistConfigSpan); - let assistCheckButton = document.createElement('button'); - assistCheckButton.id = 'assistCheckBtn'; - assistCheckButton.classList.add('assist-btn','assist-btn-alt'); - assistCheckButton.addEventListener('click', function(e) {assistCheckToggle();}); - let assistCheckSpan = document.createElement('span'); - assistCheckSpan.innerText = 'Surveillance'; - assistCheckSpan.style.fontSize = '14px'; - assistCheckButton.appendChild(assistCheckSpan); + let assistSurveillanceButton = document.createElement('button'); + assistSurveillanceButton.id = 'assistSurveillanceBtn'; + assistSurveillanceButton.classList.add('assist-btn','assist-btn-alt'); + assistSurveillanceButton.addEventListener('click', function(e) {assistSurveillanceToggle();}); + let assistSurveillanceSpan = document.createElement('span'); + assistSurveillanceSpan.innerText = 'Surveillance'; + assistSurveillanceSpan.style.fontSize = '14px'; + assistSurveillanceButton.appendChild(assistSurveillanceSpan); let assistStatusButton = document.createElement('button'); assistStatusButton.id = 'assistStatusBtn'; @@ -3234,7 +2691,7 @@ autoContainer.appendChild(dropdown); dropdown.appendChild(assistStatusButton); - dropdown.appendChild(assistCheckButton); + dropdown.appendChild(assistSurveillanceButton); dropdown.appendChild(assistConfigButton); let targetElem = document.querySelector('body'); @@ -3251,120 +2708,102 @@ } targetElem.append(assistModal); targetElem.append(assistStatus); - targetElem.append(assistCheck); + targetElem.append(assistSurveillance); targetElem.append(importModal); targetElem.append(profileModal); targetElem.append(addAcctModal); - let assistModalClose = document.querySelector('#assistModal .assist-modal-close'); - assistModalClose.addEventListener('click', function(e) {assistModalToggle();}); - let assistModalSave = document.querySelector('#assistModal .assist-modal-save'); - assistModalSave.addEventListener('click', function(e) {saveAssistInput();}); + + // profile modal butttons + let profileModalClose = document.querySelector('#profileModal .assist-modal-close'); + profileModalClose.addEventListener('click', function(e) {assistProfileToggle(null);}); + + // status modal buttons let assistStatusClose = document.querySelector('#assistStatus .assist-modal-close'); assistStatusClose.addEventListener('click', function(e) {assistStatusToggle();}); - let assistCheckClose = document.querySelector('#assistCheck .assist-modal-close'); - assistCheckClose.addEventListener('click', function(e) {assistCheckToggle();}); - let assistCheckFleetBtn = document.querySelector('#checkFleetBtn'); - assistCheckFleetBtn.addEventListener('click', function(e) {getFleetCntAtCoords();}); - let configImportExport = document.querySelector('#configImportExport'); - configImportExport.addEventListener('click', function(e) {assistImportToggle();}); - let configImport = document.querySelector('#importConfigBtn'); - configImport.addEventListener('click', function(e) {saveConfigImport();}); + + // surveillance modal buttons + let assistSurveillanceFleetBtn = document.querySelector('#checkFleetBtn'); + assistSurveillanceFleetBtn.addEventListener('click', function(e) {getFleetCountAtCoords();}); + let assistSurveillanceClose = document.querySelector('#assistSurveillance .assist-modal-close'); + assistSurveillanceClose.addEventListener('click', function(e) {assistSurveillanceToggle();}); + + // config modal buttons let addAcctOpen = document.querySelector('#addAcctOpen'); addAcctOpen.addEventListener('click', function(e) {assistAddAcctToggle();}); + let configImportExport = document.querySelector('#configImportExportBtn'); + configImportExport.addEventListener('click', function(e) {exportConfig();}); + let assistModalSave = document.querySelector('#assistModal .assist-modal-save'); + assistModalSave.addEventListener('click', function(e) {saveConfig();}); + let assistModalClose = document.querySelector('#assistModal .assist-modal-close'); + assistModalClose.addEventListener('click', function(e) {assistModalToggle();}); + + // import / export modal buttons + let configImport = document.querySelector('#importConfigBtn'); + configImport.addEventListener('click', function(e) {importConfig();}); + let configImportClose = document.querySelector('#importModal .assist-modal-close'); + configImportClose.addEventListener('click', function(e) {assistImportExportToggle();}); + + // restricted profile buttons let addAcctBtn = document.querySelector('#addAcctBtn'); addAcctBtn.addEventListener('click', function(e) {addKeyToProfile(document.querySelector('#addAcctDiv').value);}); let removeAcctBtn = document.querySelector('#removeAcctBtn'); removeAcctBtn.addEventListener('click', function(e) {removeKeyFromProfile();}); - let configImportClose = document.querySelector('#importModal .assist-modal-close'); - configImportClose.addEventListener('click', function(e) {assistImportToggle();}); - let profileModalClose = document.querySelector('#profileModal .assist-modal-close'); - profileModalClose.addEventListener('click', function(e) {assistProfileToggle(null);}); let addAcctClose = document.querySelector('#addAcctModal .assist-modal-close'); addAcctClose.addEventListener('click', function(e) {assistAddAcctToggle();}); - makeDraggable(assistCheck); + makeDraggable(assistSurveillance); makeDraggable(assistStatus); } } observer.observe(document, {childList: true, subtree: true}); waitForLabs(null, null); - await initUser(); - let autoSpanRef = document.querySelector('#autoScanBtn > span'); - autoSpanRef ? autoSpanRef.innerHTML = 'Start' : null; - console.log('init complete'); - console.log('Fleets: ', userFleets); - // DEBUG - use if mining fleet gets stuck - /* - async function miningSelfDestruct(fleet, destX, destY) { - let [playerAtlasTokenAcct] = await BrowserAnchor.anchor.web3.PublicKey.findProgramAddressSync( - [ - new solanaWeb3.PublicKey(userPublicKey).toBuffer(), - new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA').toBuffer(), - new solanaWeb3.PublicKey('ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx').toBuffer() - ], - new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') - ); - let [mineItem] = await sageProgram.account.mineItem.all([ - { - memcmp: { - offset: 105, - bytes: fleet.mineResource, - }, - }, - ]); - let planets = await getPlanetsFromCoords(destX, destY); - let sageResource = null; - let planet = null; - for (let planetCheck of planets) { - let resourceCheck = await sageProgram.account.resource.all([ - { - memcmp: { - offset: 41, - bytes: planetCheck.publicKey, - }, - }, - { - memcmp: { - offset: 73, - bytes: mineItem.publicKey, - }, - }, - ]); - if (sageResource === null && resourceCheck.length > 0) { - [sageResource] = resourceCheck; - planet = planetCheck + function TaskQueue(concurrentCount = 1) { + this.tasks = []; + this.total = this.tasks.length; + this.running = []; + this.complete = []; + this.count = concurrentCount; + } + + TaskQueue.prototype.runNext = function(){ + return ((this.running.length < this.count) && this.task.length); + } + + TaskQueue.prototype.add = function(task){ + this.tasks.push(task); + } + + // @todo - cancel task + // @todo - should there be a timeout for each task? + + TaskQueue.prototype.run = function () { + while (this.runNext()) { + const promise = this.tasks.shift(); + promise.then((fleet) => { + switch(fleet.assignment) { + case 'Mine': + this.add(handleMining(fleet)); + break; + case 'Transport': + this.add(handleTransport(fleet)); + break; + case 'Scan': + this.add(handleScan(fleet)); + break; + default: + console.log(`${ fleet.assignment } is unsupported`); } + this.run(); + }); + this.running.push({fleet: fleet.account.publicKey, promise }); } - return new Promise(async resolve => { - let tx = { instruction: await sageProgram.methods.mineAsteroidToRespawn({keyIndex: new BrowserAnchor.anchor.BN(userProfileKeyIdx), toSector: [new BrowserAnchor.anchor.BN(destX), new BrowserAnchor.anchor.BN(destY)]}).accountsStrict({ - gameAccountsFleetAndOwner: { - gameFleetAndOwner: { - fleetAndOwner: { - fleet: fleet.publicKey, - owningProfile: userProfileAcct, - owningProfileFaction: userProfileFactionAcct.publicKey, - key: userPublicKey - }, - gameId: sageGameAcct.publicKey - }, - gameState: sageGameAcct.account.gameState - }, - resource: sageResource.publicKey, - planet: planet.publicKey, - fuelTank: fleet.fuelTank, - fuelTokenFrom: fleet.fuelToken, - atlasTokenFrom: playerAtlasTokenAcct, - atlasTokenTo: 'FdHkzP8eWeFpNSreMiZCWzJYrcZG2GJAPSyb3gENL8fS', - tokenProgram: new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') - }).instruction()} - let txResult = await txSignAndSend(tx); - console.log(txResult); - resolve(txResult); - }); } - await miningSelfDestruct(userFleets[2], -30, 30); - */ + await initUser(); + let autoSpanRef = document.querySelector('#autoScanBtn > span'); + autoSpanRef ? autoSpanRef.innerHTML = 'Start' : null; + console.log('init complete'); + console.log('Fleets: ', userFleets); })(); \ No newline at end of file