import { ComputeBudgetProgram, Connection, Keypair, PublicKey, } from '@solana/web3.js'; import { BN } from '@staratlas/anchor'; import { CargoPod } from '@staratlas/cargo'; import { arrayDeepEquals, createAssociatedTokenAccountIdempotent, getParsedTokenAccountsByOwner, InstructionReturn, ixToIxReturn, keypairToAsyncSigner, mintToTokenAccount, readFromRPCNullable, readFromRPCOrError, stringToByteArray, } from '@staratlas/data-source'; import { Faction } from '@staratlas/profile-faction'; import { AttackFleetInput, divideIntoParts, Fleet, LootRetrievalAccounts, PlanetType, RetrieveLootInput, SagePlayerProfile, Sector, ShipStats, Starbase, StarbasePlayer, } from '../src'; import { createProfile, removeDuplicateKeys, SageGameTest, SageGameTestProfile, } from './helpers'; import { CombatConfig } from '../src/combatConfig'; import { defaultLootInfo, Loot, LootAccount, lootInfoDataEquals, } from '../src/loot'; describe('Fleet To Fleet Combat', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); const originalSector: [BN, BN] = [new BN(0), new BN(0)]; const hostileSector = [new BN(0), new BN(1)] as [BN, BN]; const hostileKeypair = Keypair.generate(); const hostileProfileKeypair = Keypair.generate(); const hostileSigner = keypairToAsyncSigner(hostileKeypair); const hostileProfile = keypairToAsyncSigner(hostileProfileKeypair); const hostileProfileData: SageGameTestProfile = { key: hostileSigner, profile: hostileProfile.publicKey(), index: 0, }; let revengeSector: [BN, BN]; let hostileStarbase: PublicKey; let hostileSageProfile: PublicKey; let hostileStarbasePlayer: PublicKey; let hostileStarbasePlayerPod: PublicKey; interface FleetResult { fleetKey: PublicKey; cargoHoldKey: PublicKey; fuelTankKey: PublicKey; ammoBankKey: PublicKey; } const numFleets = 5; let idleFleet: FleetResult; let subwarpFleet: FleetResult; const hostileFleets: FleetResult[] = []; const miningAsteroid = keypairToAsyncSigner(Keypair.generate()); let mineItemKey: PublicKey; let resourceKey: PublicKey; let planetKey: PublicKey; let combatConfigKey: PublicKey; let firstLootAccountKey: PublicKey; const DEFAULT_LOOT_INFO = defaultLootInfo(); let sageGameTest: SageGameTest; const startWarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, toSector: BN[], ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendManyToChain( [ fuelTokenFrom.instructions, Fleet.warpToCoordinate( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameState, sageGameTest.gameId, sageGameTest.cargoProgram, { keyIndex: theProfile.index, toSector, }, ), ], true, ); return true; } catch { return false; } }; const stopWarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.cargoHoldKey, true, ); try { await sageGameTest.sendManyToChain( [ fuelTokenFrom.instructions, Fleet.moveWarpHandler( sageGameTest.program, sageGameTest.pointsProgram, theProfile.profile, fleetResult.fleetKey, sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.pilotXpCategory, theProfile.profile, ), sageGameTest.pointCategories.pilotXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.pilotXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameId, ), ], true, ); return true; } catch { return false; } }; const startSubwarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, toSector: BN[], ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendManyToChain( [ fuelTokenFrom.instructions, Fleet.startSubwarp( sageGameTest.program, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, toSector, }, ), ], true, ); return true; } catch { return false; } }; const stopSubwarp = async ( theProfile: SageGameTestProfile, fleetResult: FleetResult, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], fleetResult.fuelTankKey, true, ); try { await sageGameTest.sendToChain([ fuelTokenFrom.instructions, Fleet.stopSubwarp( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleetResult.fleetKey, fleetResult.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.pilotXpCategory, theProfile.profile, ), sageGameTest.pointCategories.pilotXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.pilotXpCategory, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sageGameTest.gameId, sageGameTest.gameState, { keyIndex: theProfile.index, }, ), ]); return true; } catch { return false; } }; const attackFleet = async ( theProfile: SageGameTestProfile, defenderProfileKey: PublicKey, attackerFleetResult: FleetResult, defenderFleetResult: FleetResult, sector: PublicKey, input: Omit, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const attackerAmmoTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], attackerFleetResult.ammoBankKey, true, ); const defenderAmmoTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], defenderFleetResult.ammoBankKey, true, ); const lookupTable = await sageGameTest.newLookupTable( removeDuplicateKeys([ sageGameTest.program.programId, sageGameTest.cargoProgram.programId, sageGameTest.pointsProgram.programId, theProfile.key.publicKey(), attackerAmmoTokenFrom.address, defenderAmmoTokenFrom.address, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), attackerFleetResult.fleetKey, defenderFleetResult.fleetKey, attackerFleetResult.ammoBankKey, defenderFleetResult.ammoBankKey, attackerFleetResult.cargoHoldKey, defenderFleetResult.cargoHoldKey, attackerAmmoTokenFrom.address, defenderAmmoTokenFrom.address, sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.AMMO]), sageGameTest.cargoStatsDefinition, sageGameTest.mints[SageGameTest.AMMO], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.combatXpCategory, theProfile.profile, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.combatXpCategory, defenderProfileKey, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, defenderProfileKey, ), sageGameTest.pointCategories.combatXpCategory, sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.combatXpCategory, ), sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), ]), ); await sageGameTest.sendManyToChain([ attackerAmmoTokenFrom.instructions, defenderAmmoTokenFrom.instructions, ]); const result = Fleet.attackFleet( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.pointsProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), attackerFleetResult.fleetKey, defenderFleetResult.fleetKey, attackerFleetResult.ammoBankKey, defenderFleetResult.ammoBankKey, attackerFleetResult.cargoHoldKey, defenderFleetResult.cargoHoldKey, attackerAmmoTokenFrom.address, defenderAmmoTokenFrom.address, sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.AMMO]), sageGameTest.cargoStatsDefinition, sageGameTest.mints[SageGameTest.AMMO], sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.combatXpCategory, theProfile.profile, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, theProfile.profile, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.combatXpCategory, defenderProfileKey, ), sageGameTest.getUserPointsAccountAddress( sageGameTest.pointCategories.councilRankXpCategory, defenderProfileKey, ), sageGameTest.pointCategories.combatXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.combatXpCategory, ), sageGameTest.pointCategories.councilRankXpCategory, sageGameTest.getPointsModifierAddress( sageGameTest.pointCategories.councilRankXpCategory, ), sector, sageGameTest.gameId, { ...input, keyIndex: theProfile.index, }, ); console.log( await sageGameTest.sendManyToChain( [ ixToIxReturn( ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 426 }), ), ixToIxReturn( ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), ), result.instructions, ], true, [lookupTable], ), ); return { status: true, result: result.lootAccountKey }; }; const retrieveLoot = async ( theProfile: SageGameTestProfile, fleet: FleetResult, lootKey: PublicKey, lootAccount: LootAccount, lootPodIndex: 0 | 1 = 0, ) => { if ( !sageGameTest.gameState || !sageGameTest.mints || !sageGameTest.pointCategories || !sageGameTest.cargoStatsDefinition ) { throw 'sageGameTest not set up correctly'; } const instructions: InstructionReturn[] = []; const lootRetrieval: LootRetrievalAccounts[] = []; const podTokenAccounts = await getParsedTokenAccountsByOwner( connection, lootAccount.items[lootPodIndex].loot, ); for (const podTokenAccount of podTokenAccounts) { const lootTokenTo = createAssociatedTokenAccountIdempotent( podTokenAccount.mint, fleet.cargoHoldKey, true, ); instructions.push(lootTokenTo.instructions); lootRetrieval.push({ cargoType: sageGameTest.getCargoTypeAddress(podTokenAccount.mint), tokenFrom: podTokenAccount.address, tokenTo: lootTokenTo.address, tokenMint: podTokenAccount.mint, }); } const sectorKey = Sector.findAddress( sageGameTest.program, sageGameTest.gameId, lootAccount.sector as [BN, BN], )[0]; const lootRetrievalKeys = lootRetrieval .map((entry) => [ entry.cargoType, entry.tokenFrom, entry.tokenMint, entry.tokenTo, ]) .flat(); const lookupTable = await sageGameTest.newLookupTable( removeDuplicateKeys([ ...lootRetrievalKeys, sageGameTest.program.programId, sageGameTest.cargoProgram.programId, theProfile.key.publicKey(), theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleet.fleetKey, lootKey, fleet.cargoHoldKey, lootAccount.items[lootPodIndex].loot, sageGameTest.cargoStatsDefinition, sectorKey, sageGameTest.gameId, ]), ); const input: RetrieveLootInput = { fundsTo: undefined, lootRetrieval, keyIndex: theProfile.index, }; // close loot account if (lootAccount.creator.equals(theProfile.profile)) { input.fundsTo = 'funder'; } const retrieveLootIx = Fleet.retrieveLoot( sageGameTest.program, sageGameTest.cargoProgram, theProfile.key, theProfile.profile, sageGameTest.getProfileFactionAddress(theProfile.profile), fleet.fleetKey, lootKey, fleet.cargoHoldKey, lootAccount.items[lootPodIndex].loot, sageGameTest.cargoStatsDefinition, sectorKey, sageGameTest.gameId, input, ); instructions.push(retrieveLootIx); await sageGameTest.sendManyToChain(instructions, true, [lookupTable]); const lootPod = await readFromRPCNullable( sageGameTest.connection, sageGameTest.cargoProgram, lootAccount.items[lootPodIndex].loot, CargoPod, 'confirmed', ); expect(lootPod).toBe(null); if (lootAccount.creator.equals(theProfile.profile)) { const lootAccRefreshed = await readFromRPCNullable( sageGameTest.connection, sageGameTest.program, lootKey, Loot, 'confirmed', ); expect(lootAccRefreshed).toBe(null); } return true; }; const _createEnemySector = async ( coordinates = hostileSector, faction = Faction.Ustur, starbaseLevel = 6, createAsteroid = true, numCrew = 5, chunkCrewInstructions = 2, ) => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.fleets || !sageGameTest.ships || !sageGameTest.gameState || !sageGameTest.pointCategories || !sageGameTest.crew ) { throw 'test environment not ready'; } const sectorName = coordinates.toString(); const sectorIx = Sector.registerSector( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.profiles.superuser.profile /** discoverer */, sageGameTest.gameId, coordinates, sectorName, sageGameTest.profiles.superuser.index, ); hostileStarbase = Starbase.findAddress( sageGameTest.program, sageGameTest.gameId, coordinates, )[0]; const starbaseSeqId = 0; hostileSageProfile = SagePlayerProfile.findAddress( sageGameTest.program, hostileProfile.publicKey(), sageGameTest.gameId, )[0]; hostileStarbasePlayer = StarbasePlayer.findAddress( sageGameTest.program, hostileStarbase, hostileSageProfile, starbaseSeqId, )[0]; const sbpPodSeeds = Keypair.generate().publicKey.toBuffer(); hostileStarbasePlayerPod = CargoPod.findAddress( sageGameTest.cargoProgram, Buffer.from(sbpPodSeeds), )[0]; const userAmmoToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], hostileSigner.publicKey(), true, ); const sbpAmmoToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], hostileStarbasePlayerPod, true, ); const userToolkitToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], hostileSigner.publicKey(), true, ); const sbpToolkitToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], hostileStarbasePlayerPod, true, ); const userFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], hostileSigner.publicKey(), true, ); const sbpFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], hostileStarbasePlayerPod, true, ); const firstShip = Object.entries(sageGameTest.ships)[0]; const shipMint = new PublicKey(firstShip[0]); const shipKey = firstShip[1][0]; const shipTokenAccount = createAssociatedTokenAccountIdempotent( shipMint, hostileSigner.publicKey(), true, ); const shipEscrowAta = createAssociatedTokenAccountIdempotent( shipMint, hostileSageProfile, true, ); const cargoAmount = 1_000_000_000; const shipAmount = 1_000_000; const instructions = [ sectorIx.instructions, Starbase.registerStarbase( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sectorIx.sectorKey[0], sageGameTest.gameId, sageGameTest.gameState, { name: stringToByteArray(`${sectorName} Starbase`, 64), subCoordinates: [new BN(0), new BN(0)], sectorCoordinates: coordinates, starbaseLevelIndex: starbaseLevel, faction, keyIndex: sageGameTest.profiles.superuser.index, }, ), SagePlayerProfile.registerSagePlayerProfile( sageGameTest.program, hostileProfile.publicKey(), sageGameTest.gameId, sageGameTest.gameState, ), StarbasePlayer.registerStarbasePlayer( sageGameTest.program, sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileSageProfile, hostileStarbase, sageGameTest.gameId, sageGameTest.gameState, starbaseSeqId, ), StarbasePlayer.createCargoPod( sageGameTest.program, sageGameTest.cargoProgram, hostileStarbasePlayer, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileStarbase, sageGameTest.cargoStatsDefinition, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: sageGameTest.profiles.player1.index, podSeeds: Array.from(sbpPodSeeds), }, ), userAmmoToken.instructions, sbpAmmoToken.instructions, userToolkitToken.instructions, sbpToolkitToken.instructions, userFuelToken.instructions, sbpFuelToken.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.AMMO], userAmmoToken.address, cargoAmount, ), mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.REPAIR_KIT], userToolkitToken.address, cargoAmount, ), mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.FUEL], userFuelToken.address, cargoAmount, ), StarbasePlayer.depositCargoToGame( sageGameTest.program, sageGameTest.cargoProgram, hostileStarbasePlayer, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileStarbase, hostileStarbasePlayerPod, sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.AMMO]), sageGameTest.cargoStatsDefinition, userAmmoToken.address, sbpAmmoToken.address, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: 0, }, ), StarbasePlayer.depositCargoToGame( sageGameTest.program, sageGameTest.cargoProgram, hostileStarbasePlayer, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileStarbase, hostileStarbasePlayerPod, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, userToolkitToken.address, sbpToolkitToken.address, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: 0, }, ), StarbasePlayer.depositCargoToGame( sageGameTest.program, sageGameTest.cargoProgram, hostileStarbasePlayer, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileStarbase, hostileStarbasePlayerPod, sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.FUEL]), sageGameTest.cargoStatsDefinition, userFuelToken.address, sbpFuelToken.address, sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(cargoAmount), keyIndex: 0, }, ), shipTokenAccount.instructions, shipEscrowAta.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, shipMint, shipTokenAccount.address, shipAmount, ), SagePlayerProfile.addShipEscrow( sageGameTest.program, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileSageProfile, // escrow authority hostileSigner, // origin token account owner shipTokenAccount.address, shipKey, shipEscrowAta.address, hostileStarbasePlayer, hostileStarbase, sageGameTest.gameId, sageGameTest.gameState, { shipAmount: new BN(shipAmount), index: null, }, ), ]; if (createAsteroid) { const asteroidBeltResult = sageGameTest.registerPlanetInstructions( sectorIx.sectorKey[0], PlanetType.AsteroidBelt, ); const mineItem = sageGameTest.getMineItemAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ); const resourceResult = sageGameTest.registerResourceInstructions( asteroidBeltResult.planetKey, mineItem, ); instructions.push(asteroidBeltResult.instructions); instructions.push(resourceResult.instructions); planetKey = asteroidBeltResult.planetKey; } await sageGameTest.sendManyToChain(instructions, true); if (numCrew) { const crewChunkNum = chunkCrewInstructions == null ? 0 : chunkCrewInstructions; const crewChunks = divideIntoParts(numCrew, crewChunkNum); for (const chunk of crewChunks) { if (chunk) { await sageGameTest.mintAndAddCrewToGame( sageGameTest.crew.crewMerkleTree, hostileSigner.publicKey(), hostileStarbase, hostileStarbasePlayer, chunk, undefined, // startingLeafIndex undefined, // knownLeaves hostileProfileData, ); } } } }; const _createEnemyFleet = async ( fleetName = 'enemy', shipAmount = 1, ammoAmount = 30, fuelAmount = 500, toolkitAmount = 55, ) => { if ( !sageGameTest.mints || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.fleets || !sageGameTest.ships || !sageGameTest.gameState ) { throw 'test environment not ready'; } const fleetLabel = stringToByteArray( `${fleetName}-${new Date().getTime()}`, 32, ); const firstShip = Object.entries(sageGameTest.ships)[0]; const shipKey = firstShip[1][0]; const createFleetIxResult = Fleet.createFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), shipKey, hostileStarbasePlayer, hostileStarbase, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.cargoStatsDefinition, { shipAmount, shipEscrowIndex: 0, fleetLabel, keyIndex: 0, }, ); const ammoTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], createFleetIxResult.ammoBankKey[0], true, ); const ammoTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], hostileStarbasePlayerPod, true, ); const fuelTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], createFleetIxResult.fuelTankKey[0], true, ); const fuelTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], hostileStarbasePlayerPod, true, ); const toolkitTokenTo = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], createFleetIxResult.cargoHoldKey[0], true, ); const toolkitTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], hostileStarbasePlayerPod, true, ); const instructions = [ createFleetIxResult.instructions, ammoTokenFrom.instructions, ammoTokenTo.instructions, fuelTokenFrom.instructions, fuelTokenTo.instructions, toolkitTokenFrom.instructions, toolkitTokenTo.instructions, Fleet.depositCargoToFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), sageGameTest.funder.publicKey(), hostileStarbase, hostileStarbasePlayer, createFleetIxResult.fleetKey[0], hostileStarbasePlayerPod, createFleetIxResult.ammoBankKey[0], sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.AMMO]), sageGameTest.cargoStatsDefinition, ammoTokenFrom.address, ammoTokenTo.address, sageGameTest.mints[SageGameTest.AMMO], sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(ammoAmount), keyIndex: 0, }, ), Fleet.depositCargoToFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), sageGameTest.funder.publicKey(), hostileStarbase, hostileStarbasePlayer, createFleetIxResult.fleetKey[0], hostileStarbasePlayerPod, createFleetIxResult.fuelTankKey[0], sageGameTest.getCargoTypeAddress(sageGameTest.mints[SageGameTest.FUEL]), sageGameTest.cargoStatsDefinition, fuelTokenFrom.address, fuelTokenTo.address, sageGameTest.mints[SageGameTest.FUEL], sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(fuelAmount), keyIndex: 0, }, ), Fleet.depositCargoToFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), sageGameTest.funder.publicKey(), hostileStarbase, hostileStarbasePlayer, createFleetIxResult.fleetKey[0], hostileStarbasePlayerPod, createFleetIxResult.cargoHoldKey[0], sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, toolkitTokenFrom.address, toolkitTokenTo.address, sageGameTest.mints[SageGameTest.REPAIR_KIT], sageGameTest.gameId, sageGameTest.gameState, { amount: new BN(toolkitAmount), keyIndex: 0, }, ), Fleet.loadingBayToIdle( sageGameTest.program, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), createFleetIxResult.fleetKey[0], hostileStarbase, hostileStarbasePlayer, sageGameTest.gameId, sageGameTest.gameState, 0, ), ]; await sageGameTest.sendManyToChain(instructions, true); return createFleetIxResult; }; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); const airdropTx2 = await connection.requestAirdrop( hostileSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); await connection.confirmTransaction(airdropTx2); const riskZones = { mudSecurityZone: { center: [new BN(-10), new BN(-10)], radius: new BN(1), } as never, oniSecurityZone: { center: [new BN(10), new BN(-10)], radius: new BN(1), } as never, usturSecurityZone: { center: [new BN(10), new BN(10)], radius: new BN(1), } as never, highRiskZone: { center: [new BN(5), new BN(5)], radius: new BN(1), } as never, mediumRiskZone: { center: [new BN(0), new BN(0)], radius: new BN(2000), } as never, }; const defaultShipStats = SageGameTest.getDefaultShipStats(); sageGameTest = await SageGameTest.createGame({ funder: walletKeypair, numCrewForPlayer: 20, chunkCrewInstructions: 4, riskZones, fleet: { maxFleetSize: 3_000_000 }, numberOfShipsInFleet: 2, addCargoToFleet: true, fleetToIdle: true, connection, shipStats: { ...defaultShipStats, combatStats: { ...defaultShipStats.combatStats, // hp: 1200, // ap: 120, // apRegenRate: 12.0 * 100, // sp: 200, // shieldRechargeRate: 1.7 * 100, // shieldBreakDelay: 120, // ammoConsumptionRate: 16 * 100, // repairAbility: 1 * 100, // repairEfficiency: 1 * 100, }, }, }); combatConfigKey = CombatConfig.findAddress( sageGameTest.program, sageGameTest.gameId, )[0]; await sageGameTest.createSector({ coordinates: [new BN(3), new BN(3)], starbaseLevel: 6, }); await createProfile( sageGameTest.playerProfileProgram, sageGameTest.profileFactionProgram, hostileProfile, sageGameTest.funder, [], sageGameTest.connection, hostileSigner, undefined, Faction.Ustur, ); await sageGameTest.setupPlayerProgressionForPlayer( hostileProfile.publicKey(), ); await _createEnemySector(); }); it('setup combat config', async () => { const data1 = { lootExclusivityTime: 100, starbaseUpgradeProgressContinuation: 100, crewRespawnTime: 100, rawShipsRespawnTime: 100, }; await sageGameTest.setupCombatConfig(data1); const combatConfigAcc = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, combatConfigKey, CombatConfig, ); expect(combatConfigAcc.data.globalCeasefire).toBe(1); expect(combatConfigAcc.data.lootExclusivityTime).toBe( data1.lootExclusivityTime, ); expect(combatConfigAcc.data.starbaseUpgradeProgressContinuation).toBe( data1.starbaseUpgradeProgressContinuation, ); expect(combatConfigAcc.data.crewRespawnTime).toBe(data1.crewRespawnTime); expect(combatConfigAcc.data.rawShipsRespawnTime).toBe( data1.rawShipsRespawnTime, ); const data2 = { globalCeasefire: false, lootExclusivityTime: 2, starbaseUpgradeProgressContinuation: 50, crewRespawnTime: 75, }; await sageGameTest.updateCombatConfig(data2); const combatConfigAcc1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, combatConfigKey, CombatConfig, ); expect(combatConfigAcc1.data.globalCeasefire).toBe(0); expect(combatConfigAcc1.data.lootExclusivityTime).toBe( data2.lootExclusivityTime, ); expect(combatConfigAcc1.data.starbaseUpgradeProgressContinuation).toBe( data2.starbaseUpgradeProgressContinuation, ); expect(combatConfigAcc1.data.crewRespawnTime).toBe(data2.crewRespawnTime); expect(combatConfigAcc1.data.rawShipsRespawnTime).toBe( data1.rawShipsRespawnTime, ); }); it('warp in hostile fleets', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } hostileFleets.push( ...( await Promise.all( Array.from({ length: numFleets }, () => _createEnemyFleet('a', 1, 5000, 10000, 1000), ), ) ).map((it) => { return { fleetKey: it.fleetKey[0], cargoHoldKey: it.cargoHoldKey[0], fuelTankKey: it.fuelTankKey[0], ammoBankKey: it.ammoBankKey[0], }; }), ); const startFleetActions = await Promise.all( hostileFleets.map((it) => startWarp(hostileProfileData, it, originalSector), ), ); expect(startFleetActions.every((element) => element === true)).toBe(true); const fleetAccount1 = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); if (!fleetAccount1.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } const moveTime = fleetAccount1.state.MoveWarp.warpFinish .sub(fleetAccount1.state.MoveWarp.warpStart) .toNumber(); // wait for movement to finish await new Promise((r) => setTimeout(r, (Math.max(moveTime) + 1) * 1000)); // stop moving const stopMoving = await Promise.all( hostileFleets.map((it) => stopWarp(hostileProfileData, it)), ); expect(stopMoving.every((element) => element === true)).toBe(true); }); it('attack fleet(s)', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } const combatConfig = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, combatConfigKey, CombatConfig, ); const originalSectorKey = Sector.findAddress( sageGameTest.program, sageGameTest.gameId, originalSector, )[0]; idleFleet = await sageGameTest.quickCreateFleet( sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 1 /** numShips */, 0 /** fuelAmtToDeposit */, 1000 /** foodAmtToDeposit */, 5000 /** ammoAmtToDeposit */, undefined /** repairKitAmtToDeposit */, true /** toIdle */, true /** addShipsToGame */, ); await attackFleet( hostileProfileData, sageGameTest.profiles.player1.profile, hostileFleets[0], idleFleet, originalSectorKey, { anyFleetDies: false, }, ); const hostile0Account = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); let idleFleetAccount = await sageGameTest.loadFleet(idleFleet.fleetKey); expect(hostile0Account.data.ap).toBe(0); expect(hostile0Account.data.sp).toBeLessThan( (hostile0Account.data.stats).combatStats.sp, ); expect(hostile0Account.data.hp).toBeLessThan( (hostile0Account.data.stats).combatStats.hp, ); expect(idleFleetAccount.data.ap).toBeLessThan( (idleFleetAccount.data.stats).combatStats.ap, ); expect(idleFleetAccount.data.sp).toBeLessThan( (idleFleetAccount.data.stats).combatStats.sp, ); expect(idleFleetAccount.data.hp).toBeLessThan( (idleFleetAccount.data.stats).combatStats.hp, ); subwarpFleet = await sageGameTest.quickCreateFleet( sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 1 /** numShips */, 19 /** fuelAmtToDeposit */, undefined /** foodAmtToDeposit */, 5000 /** ammoAmtToDeposit */, 1000 /** repairKitAmtToDeposit */, true /** toIdle */, true /** addShipsToGame */, ); const wave2 = await Promise.all([ startSubwarp(sageGameTest.profiles.player1, subwarpFleet, hostileSector), attackFleet( hostileProfileData, sageGameTest.profiles.player1.profile, hostileFleets[1], idleFleet, originalSectorKey, { anyFleetDies: true, }, ), attackFleet( hostileProfileData, sageGameTest.profiles.player1.profile, hostileFleets[2], subwarpFleet, originalSectorKey, { anyFleetDies: false, }, ), ]); idleFleetAccount = await sageGameTest.loadFleet(idleFleet.fleetKey); if (!idleFleetAccount.state.Respawn) { throw 'Fleet should be in Respawn state'; } const lootAccountKey = wave2[1].result; expect(lootAccountKey != null).toBe(true); if (lootAccountKey) { firstLootAccountKey = lootAccountKey; const lootAcc = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, lootAccountKey, Loot, ); expect(lootAcc.data.gameId.equals(idleFleetAccount.data.gameId)).toBe( true, ); expect(lootAcc.data.creator.equals(hostileProfileData.profile)).toBe( true, ); expect( arrayDeepEquals(lootAcc.data.sector, originalSector, (a, b) => a.eq(b)), ).toBe(true); expect(lootInfoDataEquals(lootAcc.data.items[0], DEFAULT_LOOT_INFO)).toBe( true, ); expect( lootAcc.data.items[1].destroyer.equals(hostileProfileData.profile), ).toBe(true); expect(lootAcc.data.items[1].loot.equals(idleFleet.cargoHoldKey)).toBe( true, ); const expectedExclusivityUnlockTime = idleFleetAccount.data.lastCombatUpdate.add( new BN(combatConfig.data.lootExclusivityTime), ); expect( lootAcc.data.items[1].exclusivityUnlockTime.eq( expectedExclusivityUnlockTime, ), ).toBe(true); await new Promise((r) => setTimeout(r, Math.max(combatConfig.data.lootExclusivityTime) * 1000), ); } expect(wave2[2].result == null).toBe(true); }); it('reload', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } await stopSubwarp(sageGameTest.profiles.player1, subwarpFleet); const hostile0AccountBefore = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); // const subwarpFleetAccountBefore = await sageGameTest.loadFleet( // subwarpFleet.fleetKey, // ); await sageGameTest.sendManyToChain( [ Fleet.reloadFleetAbilityPower( sageGameTest.program, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileFleets[0].fleetKey, sageGameTest.gameId, { keyIndex: hostileProfileData.index, }, ), Fleet.reloadFleetAbilityPower( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), subwarpFleet.fleetKey, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const hostile0Account = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); const subwarpFleetAccount = await sageGameTest.loadFleet( subwarpFleet.fleetKey, ); expect(hostile0Account.data.ap).toBe( (hostile0Account.data.stats).combatStats.ap, ); expect(subwarpFleetAccount.data.ap).toBe( (subwarpFleetAccount.data.stats).combatStats.ap, ); expect( hostile0Account.data.apReloadExpiresAt.gt( hostile0AccountBefore.data.apReloadExpiresAt, ), ).toBe(true); // expect( // subwarpFleetAccount.data.apReloadExpiresAt.gt( // subwarpFleetAccountBefore.data.apReloadExpiresAt, // ), // ).toBe(true); }); it('repair idle', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } const hostile0AccountBefore = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); const hostile2AccountBefore = await sageGameTest.loadFleet( hostileFleets[2].fleetKey, ); const repairKitTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], hostileFleets[0].cargoHoldKey, true, ); await sageGameTest.sendManyToChain( [ repairKitTokenFrom.instructions, Fleet.repairIdleFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileFleets[0].fleetKey, hostileFleets[0].fleetKey, hostileFleets[0].cargoHoldKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, repairKitTokenFrom.address, sageGameTest.mints[SageGameTest.REPAIR_KIT], sageGameTest.gameId, { keyIndex: hostileProfileData.index, amount: 250, }, ), Fleet.repairIdleFleet( sageGameTest.program, sageGameTest.cargoProgram, hostileSigner, hostileProfile.publicKey(), sageGameTest.getProfileFactionAddress(hostileProfile.publicKey()), hostileFleets[0].fleetKey, hostileFleets[2].fleetKey, hostileFleets[0].cargoHoldKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, repairKitTokenFrom.address, sageGameTest.mints[SageGameTest.REPAIR_KIT], sageGameTest.gameId, { keyIndex: hostileProfileData.index, }, ), ], true, ); const hostile0Account = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); const hostile2Account = await sageGameTest.loadFleet( hostileFleets[2].fleetKey, ); expect(hostile0Account.data.pendingHp).toBeGreaterThan( hostile0AccountBefore.data.pendingHp, ); expect(hostile2Account.data.pendingHp).toBeGreaterThan( hostile2AccountBefore.data.pendingHp, ); }); it('repair docked', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } const subwarpFleetAccountBefore = await sageGameTest.loadFleet( subwarpFleet.fleetKey, ); const sectorData = sageGameTest.sectors[0]; const starBase = sectorData.starbases[0]; const starBasePlayerData = sectorData.starbasePlayers[0]; const repairKitTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.REPAIR_KIT], starBasePlayerData.cargoPod, true, ); const atlasTokenFrom = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.ATLAS], sageGameTest.profiles.player1.key.publicKey(), true, ); const gameData = await sageGameTest.loadGame(); await sageGameTest.sendManyToChain( [ atlasTokenFrom.instructions, mintToTokenAccount( sageGameTest.profiles.superuser.key, sageGameTest.mints[SageGameTest.ATLAS], atlasTokenFrom.address, 10_000_000, ), ...sageGameTest.addCargoToGameInstructions( starBase, starBasePlayerData.starbasePlayer, sageGameTest.mints[SageGameTest.REPAIR_KIT], 1_000_000, starBasePlayerData.cargoPod, ), Fleet.idleToLoadingBay( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), subwarpFleet.fleetKey, sageGameTest.sectors[0].starbases[0], starBasePlayerData.starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ), Fleet.repairDockedFleet( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), starBase, starBasePlayerData.starbasePlayer, subwarpFleet.fleetKey, starBasePlayerData.cargoPod, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.REPAIR_KIT], ), sageGameTest.cargoStatsDefinition, repairKitTokenFrom.address, sageGameTest.mints[SageGameTest.REPAIR_KIT], atlasTokenFrom.address, gameData.data.vaults.atlas, gameData.data.mints.atlas, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const subwarpFleetAccount = await sageGameTest.loadFleet( subwarpFleet.fleetKey, ); expect(subwarpFleetAccount.data.pendingHp).toBeGreaterThan( subwarpFleetAccountBefore.data.pendingHp, ); }); it('retrieve loot', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } const originalSectorKey = Sector.findAddress( sageGameTest.program, sageGameTest.gameId, originalSector, )[0]; const attacker = sageGameTest.fleets[0]; const attackerFleetAccount0 = await sageGameTest.loadFleet(attacker); const attackerData = { fleetKey: attacker, ammoBankKey: attackerFleetAccount0.data.ammoBank, cargoHoldKey: attackerFleetAccount0.data.cargoHold, fuelTankKey: attackerFleetAccount0.data.fuelTank, }; const attackResult = await attackFleet( sageGameTest.profiles.player1, hostileProfileData.profile, attackerData, hostileFleets[0], originalSectorKey, { anyFleetDies: true, }, ); const hostile0Fleet = await sageGameTest.loadFleet( hostileFleets[0].fleetKey, ); if (!hostile0Fleet.state.Respawn) { throw 'Fleet should be in Respawn state'; } expect(attackResult.result != null).toBe(true); if (attackResult.result) { const lootAcc = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, attackResult.result, Loot, ); expect( lootAcc.data.creator.equals(sageGameTest.profiles.player1.profile), ).toBe(true); expect(lootInfoDataEquals(lootAcc.data.items[0], DEFAULT_LOOT_INFO)).toBe( true, ); expect( lootAcc.data.items[1].destroyer.equals( sageGameTest.profiles.player1.profile, ), ).toBe(true); expect( lootAcc.data.items[1].loot.equals(hostileFleets[0].cargoHoldKey), ).toBe(true); // cannot claim someone else's loot let success = false; try { await retrieveLoot( hostileProfileData, hostileFleets[3], attackResult.result, lootAcc.data, 1, ); success = true; } catch { // do nothing } expect(success).toBe(false); // can claim own loot await retrieveLoot( sageGameTest.profiles.player1, attackerData, attackResult.result, lootAcc.data, 1, ); // can claim someone else's loot after unlock expiry const enemylootAcc = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, firstLootAccountKey, Loot, ); await retrieveLoot( sageGameTest.profiles.player1, attackerData, firstLootAccountKey, enemylootAcc.data, 1, ); // can close empty loot account const enemylootAcc2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, firstLootAccountKey, Loot, ); expect( enemylootAcc2.data.items .map((it) => it.loot.equals(PublicKey.default)) .flat(), ).toStrictEqual([true, true]); await retrieveLoot( hostileProfileData, hostileFleets[3], firstLootAccountKey, enemylootAcc2.data, ); } }); it('can respawn after destruction', async () => { if ( !sageGameTest.mints || !sageGameTest.fleets || !sageGameTest.sectors || !sageGameTest.cargoStatsDefinition || !sageGameTest.craftingDomain || !sageGameTest.pointCategories || !sageGameTest.gameState ) { throw 'test environment not ready'; } const sectorData = sageGameTest.sectors[0]; const starBase = sectorData.starbases[0]; const starBasePlayerData = sectorData.starbasePlayers[0]; const idleFleetAccBefore = await sageGameTest.loadFleet(idleFleet.fleetKey); const fleetAmmoToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.AMMO], idleFleet.ammoBankKey, true, ); const fleetFuelToken = createAssociatedTokenAccountIdempotent( sageGameTest.mints[SageGameTest.FUEL], idleFleet.fuelTankKey, true, ); await sageGameTest.sendManyToChain( [ fleetAmmoToken.instructions, fleetFuelToken.instructions, Fleet.forceDropFleetCargo( sageGameTest.program, sageGameTest.cargoProgram, idleFleet.fleetKey, idleFleet.ammoBankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.AMMO], ), sageGameTest.cargoStatsDefinition, sageGameTest.gameId, fleetAmmoToken.address, sageGameTest.mints[SageGameTest.AMMO], ), Fleet.forceDropFleetCargo( sageGameTest.program, sageGameTest.cargoProgram, idleFleet.fleetKey, idleFleet.fuelTankKey, sageGameTest.getCargoTypeAddress( sageGameTest.mints[SageGameTest.FUEL], ), sageGameTest.cargoStatsDefinition, sageGameTest.gameId, fleetFuelToken.address, sageGameTest.mints[SageGameTest.FUEL], ), /** cargo hold should be already empty */ Fleet.respawnToLoadingBay( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), idleFleet.fleetKey, starBase, starBasePlayerData.starbasePlayer, idleFleetAccBefore.data.cargoHold, idleFleet.fuelTankKey, idleFleet.ammoBankKey, sageGameTest.gameId, sageGameTest.gameState, { keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const idleFleetAccAfter = await sageGameTest.loadFleet(idleFleet.fleetKey); expect(idleFleetAccAfter.data.ap).toBe(0); expect(idleFleetAccAfter.data.hp).toBe(0); expect(idleFleetAccAfter.data.sp).toBe(0); expect( idleFleetAccAfter.state.StarbaseLoadingBay?.starbase.equals(starBase), ).toBe(true); }); });