import { fetchMerkleTree, getCurrentRoot, getMerkleProof, hashLeaf, verifyLeaf, } from '@metaplex-foundation/mpl-bubblegum'; import { publicKey } from '@metaplex-foundation/umi'; import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { BN } from '@staratlas/anchor'; import { keypairToAsyncSigner, readFromRPCOrError, } from '@staratlas/data-source'; import { DisbandedFleet, Fleet, FleetShips, GameState, SagePlayerProfile, Ship, ShipStats, StarbasePlayer, } from '../src'; import { CREW_TREE_MAX_DEPTH, SageGameTest, mintAndPrepareCrew, setupUmi, } from './helpers'; describe('Crew', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); let sageGameTest: SageGameTest; let sagePlayerProfile: PublicKey; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); sageGameTest = await SageGameTest.createGame({ numberOfShipsInFleet: 1, funder: walletKeypair, connection, }); sagePlayerProfile = SagePlayerProfile.findAddress( sageGameTest.program, sageGameTest.profiles.player1.profile, sageGameTest.gameId, )[0]; if (!sageGameTest.crew || !sageGameTest.sectors) { throw 'game not setup correctly'; } await sageGameTest.addShipToGame( sageGameTest.getShips()[0], sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 1000, ); }); it('can import and export crew into the game after "pre-add" period', async () => { if (!sageGameTest.crew || !sageGameTest.sectors) { throw 'game not setup correctly'; } const starbasePlayerAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // mint and add 2 crew const umi = await setupUmi(sageGameTest.funder, sageGameTest.connection, [ sageGameTest.profiles.superuser.key, ]); let merkleTreeAccount = await fetchMerkleTree( umi, fromWeb3JsPublicKey(sageGameTest.crew.crewMerkleTree), ); const numCrew = 2; const mintPrepareResult = await mintAndPrepareCrew( sageGameTest.crew.crewMerkleTree, sageGameTest.profiles.player1.key.publicKey(), umi, [...sageGameTest.crew.knownLeaves], numCrew, ); // call `addCrewToGame` await sageGameTest.sendManyToChain( SagePlayerProfile.addCrewToGame( sageGameTest.program, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), sageGameTest.profiles.player1.key, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.crew.crewProgramConfig, sageGameTest.gameId, { items: mintPrepareResult.items, }, ), true, ); const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect(starbasePlayerAccount1.totalCrew()).toBe( starbasePlayerAccount0.totalCrew() + numCrew, ); // verify crew now owned by `sagePlayerProfile` const leavesUpdatedAfterAdd = mintPrepareResult.items.map((it) => hashLeaf(umi, { merkleTree: merkleTreeAccount.publicKey, owner: fromWeb3JsPublicKey(sagePlayerProfile), leafIndex: it.leafIndex, metadata: it.metadata, }), ); const sageGameTestCrew = sageGameTest.crew; const proofsUpdatedAfterAdd = mintPrepareResult.items.map((it, idx) => { const thisUpdatedLeaf = leavesUpdatedAfterAdd[idx]; return getMerkleProof( [ ...sageGameTestCrew.knownLeaves, ...leavesUpdatedAfterAdd.map((it) => publicKey(it)), ], CREW_TREE_MAX_DEPTH, publicKey(thisUpdatedLeaf), it.leafIndex, ); }); merkleTreeAccount = await fetchMerkleTree( umi, fromWeb3JsPublicKey(sageGameTest.crew.crewMerkleTree), ); for (let idx = 0; idx < proofsUpdatedAfterAdd.length; idx++) { const thisProof = proofsUpdatedAfterAdd[idx]; const thisLeaf = leavesUpdatedAfterAdd[idx]; const thisItem = mintPrepareResult.items[idx]; await verifyLeaf(umi, { merkleTree: fromWeb3JsPublicKey(sageGameTest.crew.crewMerkleTree), root: getCurrentRoot(merkleTreeAccount.tree), leaf: thisLeaf, index: thisItem.leafIndex, proof: thisProof, }).sendAndConfirm(umi); } // can export crew from the game after "pre-add" period' await sageGameTest.sendManyToChain( SagePlayerProfile.removeCrewFromGame( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), sageGameTest.profiles.superuser.key.publicKey() /** new owner */, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.crew.crewProgramConfig, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, items: mintPrepareResult.items, }, ), true, ); const starbasePlayerAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect(starbasePlayerAccount1.totalCrew()).toBe( starbasePlayerAccount2.totalCrew() + numCrew, ); // verify crew now owned by `superuser` key const leavesUpdatedAfterRemove = mintPrepareResult.items.map((it) => hashLeaf(umi, { merkleTree: merkleTreeAccount.publicKey, owner: fromWeb3JsPublicKey( sageGameTest.profiles.superuser.key.publicKey(), ), leafIndex: it.leafIndex, metadata: it.metadata, }), ); const proofsUpdatedAfterRemove = mintPrepareResult.items.map((it, idx) => { const thisUpdatedLeaf = leavesUpdatedAfterRemove[idx]; return getMerkleProof( [ ...sageGameTestCrew.knownLeaves, ...leavesUpdatedAfterRemove.map((it) => publicKey(it)), ], CREW_TREE_MAX_DEPTH, publicKey(thisUpdatedLeaf), it.leafIndex, ); }); merkleTreeAccount = await fetchMerkleTree( umi, fromWeb3JsPublicKey(sageGameTest.crew.crewMerkleTree), ); for (let idx = 0; idx < proofsUpdatedAfterRemove.length; idx++) { const thisProof = proofsUpdatedAfterRemove[idx]; const thisLeaf = leavesUpdatedAfterRemove[idx]; const thisItem = mintPrepareResult.items[idx]; await verifyLeaf(umi, { merkleTree: fromWeb3JsPublicKey(sageGameTest.crew.crewMerkleTree), root: getCurrentRoot(merkleTreeAccount.tree), leaf: thisLeaf, index: thisItem.leafIndex, proof: thisProof, }).sendAndConfirm(umi); } sageGameTest.crew.knownLeaves.push( ...leavesUpdatedAfterRemove.map((it) => publicKey(it)), ); }); it('import more crew', async () => { if (!sageGameTest.crew || !sageGameTest.sectors) { throw 'game not setup correctly'; } const starbasePlayerAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // mint and add crew const numCrew = 7; const mintAndAddResults = await sageGameTest.mintAndAddCrewToGame( sageGameTest.crew.crewMerkleTree, sageGameTest.profiles.player1.key.publicKey(), sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, numCrew, ); const mintAndAddResult = mintAndAddResults[0]; const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect(starbasePlayerAccount1.totalCrew()).toBe( starbasePlayerAccount0.totalCrew() + numCrew, ); const umi = await setupUmi(sageGameTest.funder, sageGameTest.connection, [ sageGameTest.profiles.superuser.key, ]); const merkleTreeAccountKey = fromWeb3JsPublicKey( sageGameTest.crew.crewMerkleTree, ); sageGameTest.crew.knownLeaves.push( ...mintAndAddResult.items.map((it) => publicKey( hashLeaf(umi, { merkleTree: merkleTreeAccountKey, owner: fromWeb3JsPublicKey(sagePlayerProfile), leafIndex: it.leafIndex, metadata: it.metadata, }), ), ), ); }); it('can load/unload required fleet crew', async () => { if ( !sageGameTest.crew || !sageGameTest.sectors || !sageGameTest.gameState ) { throw 'game not setup correctly'; } const fleet = await sageGameTest.quickCreateFleet( sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 2 /** numShips */, undefined /** fuelAmtToDeposit */, undefined /** foodAmtToDeposit */, undefined /** ammoAmtToDeposit */, undefined /** repairKitAmtToDeposit */, false /** toIdle */, false /** addShipsToGame */, ); const fleetAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect((fleetAccount1.data.stats).miscStats.requiredCrew).toBe( (fleetAccount1.data.stats).miscStats.crewCount, ); // unload required crew const count = 1; await sageGameTest.sendManyToChain( [ Fleet.unloadFleetCrew( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, count, }, ), ], true, ); // cannot undock with insufficient required crew let success = true; try { await sageGameTest.sendToChain( Fleet.loadingBayToIdle( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ), ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('FleetCrewNotNormalized'); success = false; } expect(success).toBe(false); const fleetAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); const starbasePlayerAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was incremented expect(starbasePlayerAccount1.totalCrew() + count).toBe( starbasePlayerAccount2.totalCrew(), ); // fleet crew count was decremented expect( (fleetAccount2.data.stats).miscStats.crewCount + count, ).toBe((fleetAccount1.data.stats).miscStats.crewCount); // load required crew and undock await sageGameTest.sendManyToChain( [ Fleet.loadFleetCrew( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, count: count, }, ), Fleet.loadingBayToIdle( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ), ], true, ); const fleetAccount3 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); const starbasePlayerAccount3 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was decremented expect(starbasePlayerAccount3.totalCrew() + count).toBe( starbasePlayerAccount2.totalCrew(), ); // fleet crew count was incremented expect( (fleetAccount2.data.stats).miscStats.crewCount + count, ).toBe((fleetAccount3.data.stats).miscStats.crewCount); }); it('can load/unload passengers', async () => { if ( !sageGameTest.crew || !sageGameTest.sectors || !sageGameTest.gameState ) { throw 'game not setup correctly'; } const starbasePlayerAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); const fleet = await sageGameTest.quickCreateFleet( sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 1 /** numShips */, undefined /** fuelAmtToDeposit */, undefined /** foodAmtToDeposit */, undefined /** ammoAmtToDeposit */, undefined /** repairKitAmtToDeposit */, false /** toIdle */, false /** addShipsToGame */, ); const fleetAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); expect((fleetAccount1.data.stats).miscStats.requiredCrew).toBe( (fleetAccount1.data.stats).miscStats.crewCount, ); const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // requiredCrew was deducted from Starbase expect(starbasePlayerAccount0.totalCrew()).toBe( starbasePlayerAccount1.totalCrew() + (fleetAccount1.data.stats).miscStats.requiredCrew, ); // add ship to fleet const ship = sageGameTest.getShips()[0]; await sageGameTest.sendManyToChain( [ Fleet.addShipToFleet( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, ship, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.gameId, sageGameTest.gameState, { shipAmount: 1, shipEscrowIndex: 0, fleetShipInfoIndex: 0, keyIndex: sageGameTest.profiles.player1.index, }, ), ], true, ); const fleetAccount1b = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); expect( (fleetAccount1b.data.stats).miscStats.crewCount, ).toBeGreaterThan( (fleetAccount1.data.stats).miscStats.crewCount, ); expect((fleetAccount1b.data.stats).miscStats.requiredCrew).toBe( (fleetAccount1b.data.stats).miscStats.crewCount, ); const starbasePlayerAccount1b = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // requiredCrew was deducted from Starbase expect(starbasePlayerAccount0.totalCrew()).toBe( starbasePlayerAccount1b.totalCrew() + (fleetAccount1b.data.stats).miscStats.requiredCrew, ); // load passengers const loadCount = 1; await sageGameTest.sendManyToChain( [ Fleet.loadFleetCrew( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, count: loadCount, }, ), ], true, ); const fleetAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); const starbasePlayerAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was decremented expect(starbasePlayerAccount1b.totalCrew()).toBe( starbasePlayerAccount2.totalCrew() + loadCount, ); // fleet passenger count was incremented expect((fleetAccount2.data.stats).miscStats.crewCount).toBe( (fleetAccount1b.data.stats).miscStats.crewCount + loadCount, ); // undock, dock, unload passengers const unloadCount = 1; await sageGameTest.sendManyToChain( [ Fleet.loadingBayToIdle( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ) /** proves that passengers can move with fleet */, Fleet.idleToLoadingBay( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, sageGameTest.gameState, sageGameTest.profiles.player1.index, ) /** proves that passengers can move with fleet */, Fleet.unloadFleetCrew( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleet.fleetKey, sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.gameId, { keyIndex: sageGameTest.profiles.player1.index, count: unloadCount, }, ), ], true, ); const fleetAccount3 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, fleet.fleetKey, Fleet, 'confirmed', ); const starbasePlayerAccount3 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was incremented expect(starbasePlayerAccount3.totalCrew()).toBe( starbasePlayerAccount2.totalCrew() + unloadCount, ); // fleet passenger count was decremented expect((fleetAccount2.data.stats).miscStats.crewCount).toBe( (fleetAccount3.data.stats).miscStats.crewCount + unloadCount, ); // disband fleet const disbandFleetResult = Fleet.disbandFleet( sageGameTest.program, sageGameTest.cargoProgram, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), fleetAccount3, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.gameId, sageGameTest.gameState, { keyIndex: sageGameTest.profiles.player1.index }, ); await sageGameTest.sendManyToChain(disbandFleetResult.instructions, true); const starbasePlayerAccount4 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was incremented expect(starbasePlayerAccount4.totalCrew()).toBe( starbasePlayerAccount3.totalCrew() + (fleetAccount3.data.stats).miscStats.crewCount, ); }); it('check add/remove ships does not affect crew after "pre-add" period', async () => { if (!sageGameTest.crew || !sageGameTest.sectors || !sageGameTest.fleets) { throw 'game not setup correctly'; } const starbasePlayerAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); await sageGameTest.addShipToGame( sageGameTest.getShips()[0], sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 13, ); const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect(starbasePlayerAccount1.totalCrew()).toBe( starbasePlayerAccount0.totalCrew(), ); await sageGameTest.removeShipFromGame( sageGameTest.getShips()[0], sageGameTest.sectors[0].starbases[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, 13, ); const starbasePlayerAccount2 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); expect(starbasePlayerAccount2.totalCrew()).toBe( starbasePlayerAccount1.totalCrew(), ); expect(starbasePlayerAccount2.totalCrew()).toBe( starbasePlayerAccount0.totalCrew(), ); }); it('can add crew back to starbase with forced disband after "pre-add" period', async () => { if ( !sageGameTest.crew || !sageGameTest.sectors || !sageGameTest.fleets || !sageGameTest.gameState ) { throw 'game not setup correctly'; } const fleetAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.fleets[0], Fleet, 'confirmed', ); const starbasePlayerAccount0 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); await sageGameTest.sendManyToChain( [ Ship.invalidateShip( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.getShips()[0], sageGameTest.gameId, sageGameTest.profiles.superuser.index, ), ], true, ); const gameAccount = await sageGameTest.loadGame(); const oldGameStateId = GameState.findAddress( sageGameTest.program, sageGameTest.gameId, gameAccount.data.updateId, ); const newGameStateId = GameState.findAddress( sageGameTest.program, sageGameTest.gameId, gameAccount.data.updateId.add(new BN(1)), ); const disbandFleetResult = Fleet.forceDisbandFleet( sageGameTest.program, sageGameTest.cargoProgram, fleetAccount0, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.getShips()[0], sageGameTest.gameId, newGameStateId[0], { fleetShipInfoIndex: 0 }, ); await sageGameTest.sendManyToChain( [ GameState.copyGameState( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.gameId, oldGameStateId[0], newGameStateId[0], { keyIndex: sageGameTest.profiles.superuser.index, }, ), GameState.activateGameState( sageGameTest.program, sageGameTest.profiles.superuser.key, sageGameTest.profiles.superuser.profile, sageGameTest.gameId, newGameStateId[0], { keyIndex: sageGameTest.profiles.superuser.index, }, ), SagePlayerProfile.updateShipEscrow( sageGameTest.program, sageGameTest.getShips()[0], PublicKey.default, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.gameId, newGameStateId[0], { shipEscrowIndex: 0 }, ) /** should not affect crew numbers */, disbandFleetResult.instructions, DisbandedFleet.disbandedFleetToEscrow( sageGameTest.program, sageGameTest.profiles.player1.key, sageGameTest.profiles.player1.profile, sageGameTest.getProfileFactionAddress( sageGameTest.profiles.player1.profile, ), disbandFleetResult.disbandedFleetKey[0], FleetShips.findAddress( sageGameTest.program, sageGameTest.fleets[0], )[0], sageGameTest.getShips()[0], sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, sageGameTest.sectors[0].starbases[0], sageGameTest.gameId, newGameStateId[0], { fleetShipInfoIndex: 0, shipAmount: SageGameTest.default_ships_in_fleet, shipEscrowIndex: null, keyIndex: sageGameTest.profiles.player1.index, }, ) /** crew should be moved to starbase */, ], true, ); sageGameTest.gameState = newGameStateId[0]; const starbasePlayerAccount1 = await readFromRPCOrError( sageGameTest.connection, sageGameTest.program, sageGameTest.sectors[0].starbasePlayers[0].starbasePlayer, StarbasePlayer, 'confirmed', ); // starbase crew was incremented expect(starbasePlayerAccount1.totalCrew()).toBe( starbasePlayerAccount0.totalCrew() + (fleetAccount0.data.stats).miscStats.crewCount, ); }); });