import { getAccount, getAssociatedTokenAddress } from '@solana/spl-token'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { AnchorProvider, BN, Program, Wallet } from '@staratlas/anchor'; import { CARGO_IDL, CargoPermissions, CargoPod, CargoStatsDefinition, CargoType, } from '@staratlas/cargo'; import { CREW_IDL } from '@staratlas/crew'; import { FixedSizeArray, arrayDeepEquals, buildSendAndCheck, createAssociatedTokenAccount, createMint, createTokenAccount, keypairToAsyncSigner, mintToTokenAccount, readFromRPCOrError, stringToByteArray, } from '@staratlas/data-source'; import { PLAYER_PROFILE_IDL } from '@staratlas/player-profile'; import { POINTS_IDL, UserPoints } from '@staratlas/points'; import { Faction, PROFILE_FACTION_IDL, ProfileFactionAccount, } from '@staratlas/profile-faction'; import { FactionsStarbaseLevelInfo, Fleet, GLOBAL_SCALE_DECIMALS_2, Game, GameState, MiscStats, ProgressionItemType, RegisterShipInput, SAGE_IDL, SagePermissions, SagePlayerProfile, Sector, SectorRing, Ship, ShipStats, SizeClass, Starbase, StarbasePlayer, WarpLaneInput, WarpToCoordinateInput, calculateDistance, getSizeClassAnchorEnum, } from '../src'; import { CREW_PROGRAM_ID, SAGE_PROGRAM_ID, SageGameTest, checkUserPoints, createAndFundTokenAccounts, createCargoTypes, createManyMints, createProfile, getRandomInt, mintAndImportCrewToGame, sendManyToChain, setupCrewConfigInstructions, setupPointModifierAccounts, setupPointsAccounts, setupProgression, setupUmi, } from './helpers'; interface FleetResult { ammoBankKey: [PublicKey, number]; cargoHoldKey: [PublicKey, number]; fuelTankKey: [PublicKey, number]; fleetKey: [PublicKey, number]; fleetData: Fleet; } describe('Fleet Movement', () => { const connection = new Connection('http://localhost:8899', 'confirmed'); const walletKeypair = Keypair.generate(); const walletSigner = keypairToAsyncSigner(walletKeypair); const provider = new AnchorProvider( connection, new Wallet(walletKeypair), AnchorProvider.defaultOptions(), ); const program = new Program(SAGE_IDL, SAGE_PROGRAM_ID, provider); const playerProfileProgram = new Program( PLAYER_PROFILE_IDL, new PublicKey('PprofUW1pURCnMW2si88GWPXEEK3Bvh9Tksy8WtnoYJ'), provider, ); const profileFactionProgram = new Program( PROFILE_FACTION_IDL, new PublicKey('pFACzkX2eSpAjDyEohD6i3VRJvREtH9ynbtM1DwVFsj'), provider, ); const cargoProgram = new Program( CARGO_IDL, new PublicKey('CArGoi989iv3VL3xArrJXmYYDNhjwCX5ey5sY5KKwMG'), provider, ); const crewProgram = new Program(CREW_IDL, CREW_PROGRAM_ID, provider); const pointsProgram = new Program( POINTS_IDL, new PublicKey('PointJfvuHi8DgGsPCy97EaZkQ6NvpghAAVkuquLf3w'), provider, ); const funder = keypairToAsyncSigner(Keypair.generate()); const authority = keypairToAsyncSigner(Keypair.generate()); const playerProfile = keypairToAsyncSigner(Keypair.generate()); const _fleetRespawnSigner = keypairToAsyncSigner(Keypair.generate()); // fleets stuff const _sageSigner = keypairToAsyncSigner(Keypair.generate()); let _sagePlayerProfileKey: PublicKey; let _starbaseKey: PublicKey; let _starbase2Key: PublicKey; let _starbase4Key: PublicKey; let _starbase5Key: PublicKey; let _starbase99Key: PublicKey; let _starbasePlayerKey: PublicKey; const _starbasePodSeeds = Keypair.generate().publicKey.toBuffer(); const _starbaseCargoPod = CargoPod.findAddress( cargoProgram, _starbasePodSeeds, )[0]; const _moveFleetKey = keypairToAsyncSigner(Keypair.generate()); const _toLoadingBayFleetKey = keypairToAsyncSigner(Keypair.generate()); const _toIdleFleetKey = keypairToAsyncSigner(Keypair.generate()); let fleet1: PublicKey; let fleet1FuelTank: PublicKey; let fleet2Result: FleetResult; // `Ship` const ship = keypairToAsyncSigner(Keypair.generate()); const ship2 = keypairToAsyncSigner(Keypair.generate()); const ship3 = keypairToAsyncSigner(Keypair.generate()); // ship mint const shipMint = keypairToAsyncSigner(Keypair.generate()); let _shipOriginTokenAccount: PublicKey; let _shipEscrowTokenAccount: PublicKey; // Sector stuff const _game = keypairToAsyncSigner(Keypair.generate()); let _gameState: PublicKey; let _sectorKey: PublicKey; let _sector2Key: PublicKey; let _sector3Key: PublicKey; let _sector4Key: PublicKey; let _sector5Key: PublicKey; let _sector99Key: PublicKey; const _ammo = Keypair.generate(); const _food = Keypair.generate(); const _fuel = Keypair.generate(); const _repairKit = Keypair.generate(); const _atlas = Keypair.generate(); const _fleetCargoMints = [_fuel, _ammo, _repairKit, _food]; let _atlasVault: PublicKey; // cargo stuff const _cargoStatsDefinitionSigner = keypairToAsyncSigner(Keypair.generate()); const _cargoSigner = keypairToAsyncSigner(Keypair.generate()); // Points let _miningXpCategory: PublicKey; let _pilotXpCategory: PublicKey; let _councilRankXpCategory: PublicKey; let _userPilotXp: PublicKey; let _pilotXpModifier: PublicKey; let _userCouncilRankXp: PublicKey; let _councilRankXpModifier: PublicKey; // key indices const CARGO_MANAGER = 1; const SAGE_MANAGER = 2; const LOADING_BAY_TO_IDLE = 3; const IDLE_TO_LOADING_BAY = 4; const MOVE_FLEET = 5; const CAN_RESPAWN_FLEET = 6; let warpCooldownTime = 0; const _createCargoStatsDefinition = async ( statsDefinitionSigner = _cargoStatsDefinitionSigner, ) => { await buildSendAndCheck( CargoStatsDefinition.initDefinition({ program: cargoProgram, profile: playerProfile.publicKey(), statsDefinition: statsDefinitionSigner, input: { cargoStats: 1, }, }), walletSigner, provider.connection, ); }; const _createGame = async () => { await buildSendAndCheck( Game.initGame(program, authority, playerProfile.publicKey(), _game), walletSigner, provider.connection, ); const game = await readFromRPCOrError( provider.connection, program, _game.publicKey(), Game, 'confirmed', ); const gameStateId = GameState.findAddress( program, _game.publicKey(), game.data.updateId.add(new BN(1)), ); _gameState = gameStateId[0]; const createGameStateIx = GameState.initGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, }, ); const updateLeversFeesMisc = GameState.updateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, SAGE_MANAGER, { maxFleetSize: 64, } /** fleet */, { warpLaneFuelCostReduction: 75, upkeepMiningEmissionsPenalty: 50, respawnFee: new BN(25), } /** misc */, ); // update levers await buildSendAndCheck( [createGameStateIx, updateLeversFeesMisc].flat(), walletSigner, provider.connection, ); const _updatedRiskZones = { mudSecurityZone: { center: [new BN(0), new BN(0)], radius: new BN(150), } as never, oniSecurityZone: { center: [new BN(160), new BN(0)], radius: new BN(1), } as never, usturSecurityZone: { center: [new BN(155), new BN(0)], radius: new BN(1), } as never, highRiskZone: { center: [new BN(-160), new BN(0)], radius: new BN(1), } as never, mediumRiskZone: { center: [new BN(0), new BN(0)], radius: new BN(200), } as never, }; const updateGameIx = Game.updateGame( _game.publicKey(), program, _sageSigner, playerProfile.publicKey(), SAGE_MANAGER, undefined /** points */, { ammo: _ammo.publicKey, atlas: _atlas.publicKey, food: _food.publicKey, fuel: _fuel.publicKey, repairKit: _repairKit.publicKey, } /** mints */, { atlas: _atlasVault } /** vaults */, undefined /** crafting */, { statsDefinition: _cargoStatsDefinitionSigner.publicKey() } /** cargo */, _updatedRiskZones /** risk zones */, ); const updateGameIx2 = Game.updateGame( _game.publicKey(), program, _sageSigner, playerProfile.publicKey(), SAGE_MANAGER, { pilotXpCategory: _pilotXpCategory, miningXpCategory: _miningXpCategory, councilRankXpCategory: _councilRankXpCategory, } /** points */, ); const updateGameStateIx = GameState.updateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, SAGE_MANAGER, { starbaseLevels: [ { starbaseLevelIndex: 3, faction: Faction.MUD as number, hp: new BN(337), level: 3, sectorRingAvailable: SectorRing.Inner, recipeCategoryForLevel: PublicKey.default, oldRecipeForUpgrade: PublicKey.default, newRecipeForUpgrade: PublicKey.default, sp: new BN(331), warpLaneMovementFee: new BN(337), repairFee: new BN(1337), repairEfficiency: 150, shieldRechargeRate: 120, shieldBreakDelay: 130, }, { starbaseLevelIndex: 6, faction: Faction.MUD as number, hp: new BN(1337), level: 6, sectorRingAvailable: SectorRing.Inner, recipeCategoryForLevel: PublicKey.default, oldRecipeForUpgrade: PublicKey.default, newRecipeForUpgrade: PublicKey.default, sp: new BN(7331), warpLaneMovementFee: new BN(7337), repairFee: new BN(337), repairEfficiency: 10, shieldRechargeRate: 20, shieldBreakDelay: 30, }, ], } /** fleet */, ); const activateGameIx = GameState.activateGameState( program, _sageSigner, playerProfile.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, }, ); const sectorIx = Sector.registerSector( program, _sageSigner, playerProfile.publicKey(), playerProfile.publicKey() /** discoverer */, _game.publicKey(), [new BN(1), new BN(1)], 'Super Sector', SAGE_MANAGER, ); _sectorKey = sectorIx.sectorKey[0]; await sendManyToChain( [ updateGameIx, updateGameIx2, updateGameStateIx, activateGameIx, sectorIx.instructions, ], walletSigner, provider.connection, ); }; const _createSector = async ( coordinates: [BN, BN], sectorName: string, ): Promise => { const { sectorKey, instructions } = Sector.registerSector( program, _sageSigner, // auth key playerProfile.publicKey(), playerProfile.publicKey(), // discoverer _game.publicKey(), coordinates, // coordinates sectorName, // sector name SAGE_MANAGER, // auth key index (defined in _createProfile) ); await buildSendAndCheck(instructions, walletSigner, provider.connection); return sectorKey[0]; }; // init Sector const _createStarbase = async ( name: string, isCSS: boolean, theSector: PublicKey, ) => { const sectorAcc = await readFromRPCOrError( provider.connection, program, theSector, Sector, 'confirmed', ); await buildSendAndCheck( Starbase.registerStarbase( program, _sageSigner, playerProfile.publicKey(), theSector, _game.publicKey(), _gameState, { name: stringToByteArray(name, 64), subCoordinates: [new BN(1), new BN(1)], sectorCoordinates: sectorAcc.data.coordinates as [BN, BN], starbaseLevelIndex: isCSS ? 6 : 3, faction: Faction.MUD, keyIndex: SAGE_MANAGER, }, ), walletSigner, provider.connection, ); return Starbase.findAddress( program, _game.publicKey(), sectorAcc.data.coordinates as [BN, BN], )[0]; }; const _createSagePlayerProfile = async () => { await buildSendAndCheck( SagePlayerProfile.registerSagePlayerProfile( program, playerProfile.publicKey(), _game.publicKey(), _gameState, ), walletSigner, provider.connection, ); _sagePlayerProfileKey = SagePlayerProfile.findAddress( program, playerProfile.publicKey(), _game.publicKey(), )[0]; }; const _addCargoToStarbasePlayer = async ( starbasePlayerKey: PublicKey, amount: BN = new BN(100_000_000_000), ) => { // create token accounts for _starbaseCargoPod await Promise.all([ createAndFundTokenAccounts( authority, _fleetCargoMints.map((it) => [it.publicKey, 0]), _starbaseCargoPod, walletSigner, walletSigner, provider.connection, ), ]); // create starbasePlayer cargo pod await buildSendAndCheck( StarbasePlayer.createCargoPod( program, cargoProgram, starbasePlayerKey, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbaseKey, _cargoStatsDefinitionSigner.publicKey(), _game.publicKey(), _gameState, { keyIndex: SAGE_MANAGER, podSeeds: Array.from(_starbasePodSeeds), }, ), walletSigner, provider.connection, ); // add cargo to starbasePlayer for (let index = 0; index < _fleetCargoMints.length; index++) { const mint = _fleetCargoMints[index]; if (mint) { const tokenFrom = await getAssociatedTokenAddress( mint.publicKey, _sageSigner.publicKey(), true, ); const tokenTo = await getAssociatedTokenAddress( mint.publicKey, _starbaseCargoPod, true, ); const ix = StarbasePlayer.depositCargoToGame( program, cargoProgram, starbasePlayerKey, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbaseKey, _starbaseCargoPod, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), mint.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, tokenTo, _game.publicKey(), _gameState, { amount, keyIndex: SAGE_MANAGER, }, ); await buildSendAndCheck(ix, walletSigner, provider.connection); } } }; const _createStarbasePlayer = async (theStarbase: PublicKey) => { const starbaseSeqId = 0; await buildSendAndCheck( StarbasePlayer.registerStarbasePlayer( program, ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, theStarbase, _game.publicKey(), _gameState, starbaseSeqId, ), walletSigner, provider.connection, ); const address = StarbasePlayer.findAddress( program, theStarbase, _sagePlayerProfileKey, starbaseSeqId, )[0]; return address; }; const _createShipMint = async (mint = shipMint) => { // mint `Ship` to walletSigner token account const initMintIx = createMint(mint, 0, authority.publicKey(), null); // simulate transaction await buildSendAndCheck(initMintIx, walletSigner, provider.connection); }; const _createShipTokens = async ( mint = shipMint, originAuthority = walletSigner.publicKey(), escrowAuthority = _sagePlayerProfileKey, amount = 1, ): Promise<{ shipOriginTokenAccount: PublicKey; shipEscrowTokenAccount: PublicKey; }> => { // walletSigner's token account that will own Ship const shipOriginTokenAccount = keypairToAsyncSigner(Keypair.generate()); const initTokenAccountIx = createTokenAccount( shipOriginTokenAccount, originAuthority, mint.publicKey(), ); const mintToIx = mintToTokenAccount( authority, mint.publicKey(), shipOriginTokenAccount.publicKey(), amount, ); const shipEscrowTokenAccount = keypairToAsyncSigner(Keypair.generate()); const initShipEscrowIx = createTokenAccount( shipEscrowTokenAccount, escrowAuthority, mint.publicKey(), ); await buildSendAndCheck( [initTokenAccountIx, mintToIx, initShipEscrowIx], walletSigner, provider.connection, ); return { shipOriginTokenAccount: shipOriginTokenAccount.publicKey(), shipEscrowTokenAccount: shipEscrowTokenAccount.publicKey(), }; }; // _createShipTokens const _createShip = async ( shipKey = ship, mint = shipMint.publicKey(), sizeClass = getRandomInt(SizeClass.Medium), crew = 0, activate = true, ) => { const stats: ShipStats = SageGameTest.getDefaultShipStats(crew); const input: RegisterShipInput = { name: stringToByteArray('💀❤️🤖', 64) as FixedSizeArray, sizeClass: getSizeClassAnchorEnum(sizeClass), stats: stats, isActive: activate, keyIndex: SAGE_MANAGER, }; await buildSendAndCheck( Ship.registerShip( program, _sageSigner, playerProfile.publicKey(), shipKey, mint, _game.publicKey(), input, ), walletSigner, provider.connection, ); }; // init Ship const _depositCargoToFleet = async ( fleetAddress: PublicKey, fleetCargoPod: PublicKey, cargoMint: PublicKey, amount: BN, ) => { const tokenToResult = createAssociatedTokenAccount( cargoMint, fleetCargoPod, true, ); return Promise.all([ tokenToResult.instructions, Fleet.depositCargoToFleet( program, cargoProgram, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], walletSigner.publicKey(), _starbaseKey, _starbasePlayerKey, fleetAddress, _starbaseCargoPod, fleetCargoPod, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), cargoMint, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), await getAssociatedTokenAddress(cargoMint, _starbaseCargoPod, true), await getAssociatedTokenAddress(cargoMint, fleetCargoPod, true), cargoMint, _game.publicKey(), _gameState, { amount, keyIndex: SAGE_MANAGER, }, ), ]); }; const _createFleet = async ( fleetName = 'mosh', shipsToUse = [ship], shipAmounts = [1], shipEscrowIndices = [0], fuelAmtToDeposit: number | undefined = undefined, foodAmtToDeposit: number | undefined = undefined, fillCargoHold = false, ) => { const fleetLabel = stringToByteArray(fleetName, 32); const firstShip = shipsToUse.shift(); const firstShipAmt = shipAmounts.shift(); const firstShipIdx = shipEscrowIndices.shift(); if (!firstShip || !firstShipAmt || firstShipIdx == null) { throw 'Cant find ship'; } const createFleetIxResult = Fleet.createFleet( program, cargoProgram, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], firstShip.publicKey(), _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, _cargoStatsDefinitionSigner.publicKey(), { shipAmount: firstShipAmt, shipEscrowIndex: firstShipIdx, fleetLabel, keyIndex: SAGE_MANAGER, }, ); const ixs = await Promise.all( shipsToUse.map((ship, idx) => { const shipAmt = shipAmounts[idx]; const shipIdx = shipEscrowIndices[idx]; if (!shipAmt || shipIdx == null) { throw 'Cant find ship inner'; } return Fleet.addShipToFleet( program, _sageSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], createFleetIxResult.fleetKey[0], ship.publicKey(), _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, { shipAmount: shipAmt, shipEscrowIndex: shipIdx, fleetShipInfoIndex: null, keyIndex: SAGE_MANAGER, }, ); }), ); ixs.unshift(createFleetIxResult.instructions); await buildSendAndCheck(ixs.flat(), walletSigner, provider.connection); // deposit food and fuel const fleetAccount = await readFromRPCOrError( provider.connection, program, createFleetIxResult.fleetKey[0], Fleet, 'confirmed', ); const fuelTankIx = _depositCargoToFleet( createFleetIxResult.fleetKey[0], fleetAccount.data.fuelTank, _fuel.publicKey, fuelAmtToDeposit == null ? new BN((fleetAccount.data.stats.cargoStats as any).fuelCapacity) : new BN(fuelAmtToDeposit), ); const cargoHoldCapacity = new BN( Math.floor((fleetAccount.data.stats.cargoStats as any).cargoCapacity), ); const actualFoodAmount = foodAmtToDeposit == null ? cargoHoldCapacity.div(new BN(2)) : foodAmtToDeposit === -1 // set to FULL cargoCapacity if -1 ? cargoHoldCapacity : new BN(foodAmtToDeposit); const foodIx = _depositCargoToFleet( createFleetIxResult.fleetKey[0], fleetAccount.data.cargoHold, _food.publicKey, actualFoodAmount, ); await buildSendAndCheck( (await fuelTankIx).flat().concat((await foodIx).flat()), walletSigner, provider.connection, ); if (fillCargoHold && !actualFoodAmount.eq(cargoHoldCapacity)) { const fuelInCargoHoldIx = await _depositCargoToFleet( createFleetIxResult.fleetKey[0], fleetAccount.data.cargoHold, _fuel.publicKey, cargoHoldCapacity.sub(actualFoodAmount), ); await buildSendAndCheck( fuelInCargoHoldIx.flat(), walletSigner, provider.connection, ); } return { ammoBankKey: createFleetIxResult.ammoBankKey, cargoHoldKey: createFleetIxResult.cargoHoldKey, fuelTankKey: createFleetIxResult.fuelTankKey, fleetKey: createFleetIxResult.fleetKey, fleetData: fleetAccount, }; }; const quickCreateFleet = async ( name = 'Fleet X', fuelAmount: number | undefined = undefined, foodAmount: number | undefined = undefined, toIdle = true, ) => { const fleetResult = await _createFleet( name, [ship], [1], [0], fuelAmount, foodAmount, ); if (toIdle) { await buildSendAndCheck( Fleet.loadingBayToIdle( program, _toIdleFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetResult.fleetKey[0], _starbaseKey, _starbasePlayerKey, _game.publicKey(), _gameState, LOADING_BAY_TO_IDLE, ), walletSigner, provider.connection, ); } return fleetResult; }; beforeAll(async () => { const airdropTx = await connection.requestAirdrop( walletSigner.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx); const airdropTx2 = await connection.requestAirdrop( funder.publicKey(), 500_000_000_000, ); await connection.confirmTransaction(airdropTx2); // create mints await createManyMints( _fleetCargoMints, authority.publicKey(), walletSigner, provider.connection, ); await createManyMints( [_atlas], authority.publicKey(), walletSigner, provider.connection, ); await createAndFundTokenAccounts( authority, _fleetCargoMints.map((it) => [it.publicKey, 999_999_999_999]), _sageSigner.publicKey(), funder, walletSigner, provider.connection, ); await createAndFundTokenAccounts( authority, [_atlas].map((it) => [it.publicKey, 0]), _sageSigner.publicKey(), funder, walletSigner, provider.connection, ); _atlasVault = await getAssociatedTokenAddress( _atlas.publicKey, _sageSigner.publicKey(), true, ); // create player profile await createProfile( playerProfileProgram, profileFactionProgram, playerProfile, funder, [ // Cargo Superuser // 1 { key: _cargoSigner.publicKey(), expireTime: null, scope: cargoProgram.programId, permissions: CargoPermissions.all(), }, // Fleet Superuser // 2 { key: _sageSigner.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.all(), }, // SagePermissions::TRANSITION_FROM_IDLE // 3 { key: _toIdleFleetKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.transitionFromLoadingBayPermissions(), }, // SagePermissions::TRANSITION_FROM_LOADING_BAY // 4 { key: _toLoadingBayFleetKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.transitionFromIdlePermissions(), }, // SagePermissions::MOVE_FLEET // 5 { key: _moveFleetKey.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.permissionsBuilder([ 'moveFleet', 'respawn', ]), }, { key: _fleetRespawnSigner.publicKey(), expireTime: null, scope: program.programId, permissions: SagePermissions.respawnPermissions(), }, ], provider.connection, authority, walletSigner, Faction.MUD, ); await _createCargoStatsDefinition(); await createCargoTypes( _fleetCargoMints.map((it) => it.publicKey), _cargoStatsDefinitionSigner.publicKey(), _cargoSigner, playerProfile.publicKey(), walletSigner, 0, { keyIndex: CARGO_MANAGER, values: [new BN(1)], }, provider.connection, cargoProgram, ); const pointsResult = await setupPointsAccounts( authority, walletSigner, playerProfile.publicKey(), playerProfile.publicKey(), pointsProgram, connection, ); _miningXpCategory = pointsResult.miningXpCategory; _pilotXpCategory = pointsResult.pilotXpCategory; _councilRankXpCategory = pointsResult.councilRankXpCategory; await _createGame(); _sector2Key = await _createSector([new BN(2), new BN(3)], 'sector 2'); _sector3Key = await _createSector([new BN(2), new BN(2)], 'sector 3'); _sector4Key = await _createSector([new BN(6), new BN(7)], 'sector 4'); _sector5Key = await _createSector([new BN(1), new BN(2)], 'sector 5'); _sector99Key = await _createSector([new BN(99), new BN(99)], 'sector 99'); _starbaseKey = await _createStarbase('A', true, _sectorKey); _starbase2Key = await _createStarbase('B', false, _sector2Key); _starbase4Key = await _createStarbase('D', false, _sector4Key); _starbase5Key = await _createStarbase('E', false, _sector5Key); _starbase99Key = await _createStarbase('99', false, _sector99Key); // setup crew const crewConfigResult = await setupCrewConfigInstructions( authority, playerProfile.publicKey(), 0, _game.publicKey(), program, crewProgram, await setupUmi(walletSigner, provider.connection), provider.connection, ); if (crewConfigResult.instructions.length > 0) { await sendManyToChain( crewConfigResult.instructions, walletSigner, provider.connection, 'confirmed', true, ); } await _createSagePlayerProfile(); _starbasePlayerKey = await _createStarbasePlayer(_starbaseKey); await _addCargoToStarbasePlayer(_starbasePlayerKey); await _createShipMint(); await _createShip(ship, shipMint.publicKey(), SizeClass.XxSmall, 1); await _createShip(ship2, shipMint.publicKey(), SizeClass.Medium, 20); await _createShip(ship3, shipMint.publicKey(), SizeClass.Large, 30); await setupPointModifierAccounts( { miningXpCategory: _miningXpCategory, pilotXpCategory: _pilotXpCategory, councilRankXpCategory: _councilRankXpCategory, }, authority, walletSigner, playerProfile.publicKey(), _game.publicKey(), program, pointsProgram, connection, ); await setupProgression( authority, walletSigner, playerProfile.publicKey(), _game.publicKey(), program, connection, ); _userPilotXp = UserPoints.findAddress( pointsProgram, _pilotXpCategory, playerProfile.publicKey(), )[0]; _pilotXpModifier = Game.findPointsModifierAddress( program, _game.publicKey(), _pilotXpCategory, )[0]; _userCouncilRankXp = UserPoints.findAddress( pointsProgram, _councilRankXpCategory, playerProfile.publicKey(), )[0]; _councilRankXpModifier = Game.findPointsModifierAddress( program, _game.publicKey(), _councilRankXpCategory, )[0]; // import crew to game await mintAndImportCrewToGame( crewConfigResult.merkleTree, authority, crewConfigResult.crewProgramConfig, await setupUmi(walletSigner, provider.connection), program, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _starbasePlayerKey, _starbaseKey, _game.publicKey(), funder, connection, [], // knownLeaves 20, // numCrew ); }); it('prepare Fleet', async () => { // first add ships to escrow const shipTokenResult = await _createShipTokens( shipMint, undefined, undefined, 100, ); _shipOriginTokenAccount = shipTokenResult.shipOriginTokenAccount; _shipEscrowTokenAccount = shipTokenResult.shipEscrowTokenAccount; const ixs = ( await Promise.all( [ship, ship2, ship3].map((it) => SagePlayerProfile.addShipEscrow( program, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], _sagePlayerProfileKey, // escrow authority walletSigner, // origin token account owner _shipOriginTokenAccount, it.publicKey(), _shipEscrowTokenAccount, _starbasePlayerKey, _starbaseKey, _game.publicKey(), _gameState, { shipAmount: new BN(16), index: null, }, ), ), ) ).flat(); await buildSendAndCheck(ixs, walletSigner, provider.connection); // then create fleet const fleetResult = await _createFleet('The Black Pearl', [ship], [2], [0]); fleet1 = fleetResult.fleetKey[0]; fleet1FuelTank = fleetResult.fuelTankKey[0]; }); it('change Fleet state from StarbaseLoadingBay to Idle', async () => { await buildSendAndCheck( Fleet.loadingBayToIdle( program, _toIdleFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, _starbaseKey, _starbasePlayerKey, _game.publicKey(), _gameState, LOADING_BAY_TO_IDLE, ), walletSigner, provider.connection, ); // Check that the FleetState data is correct const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.Idle) { throw 'Fleet should be in idle state'; } const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); expect( arrayDeepEquals( fleetData.state.Idle.sector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); }); it('start Warp To Coordinate movement', async () => { const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); const sector2Acc = await readFromRPCOrError( provider.connection, program, _sector2Key, Sector, 'confirmed', ); const input: WarpToCoordinateInput = { keyIndex: MOVE_FLEET, toSector: sector2Acc.data.coordinates, }; // Find Fleet const fleet = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const tokenFromBefore = await getAccount( program.provider.connection, tokenFrom, 'processed', ); const moveIx = Fleet.warpToCoordinate( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet.key, fleet1FuelTank, fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _gameState, _game.publicKey(), cargoProgram, input, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } expect( arrayDeepEquals( fleetData.state.MoveWarp.fromSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveWarp.toSector, sector2Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const expectedTravelTime = Math.floor( Fleet.calculateWarpTimeWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ); expect( fleetData.state.MoveWarp.warpFinish .sub(fleetData.state.MoveWarp.warpStart) .toNumber(), ).toBe(expectedTravelTime); const tokenFromAfter = await getAccount( program.provider.connection, tokenFrom, 'processed', ); const expectedFuelCost = Math.ceil( Fleet.calculateWarpFuelBurnWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ); expect(Number(tokenFromBefore.amount) - Number(tokenFromAfter.amount)).toBe( expectedFuelCost, ); // Sleep to allow the Warp movement to complete await new Promise((f) => setTimeout(f, expectedTravelTime * 1000)); warpCooldownTime = (expectedTravelTime + (fleetData.data.stats).movementStats.warpCoolDown) * 1000; }); it('movement handler after Warp To Coordinate movement', async () => { const fleet = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleet.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } await checkUserPoints(_userPilotXp, 0, pointsProgram, connection); await checkUserPoints(_userCouncilRankXp, 0, pointsProgram, connection); const fleetIx = Fleet.moveWarpHandler( program, pointsProgram, playerProfile.publicKey(), fleet.key, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); const expectedPoints = Math.floor( calculateDistance( fleet.state.MoveWarp.fromSector as [BN, BN], fleet.state.MoveWarp.toSector as [BN, BN], ) * Fleet.getFleetSize(fleet.data.shipCounts) * (ProgressionItemType.Warp + 1), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.Idle) { throw 'Fleet should be in idle state'; } await checkUserPoints( _userPilotXp, expectedPoints, pointsProgram, connection, ); await checkUserPoints( _userCouncilRankXp, expectedPoints, pointsProgram, connection, ); const sector2Acc = await readFromRPCOrError( provider.connection, program, _sector2Key, Sector, 'confirmed', ); expect( arrayDeepEquals( fleetData.state.Idle.sector, sector2Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); }); it('cannot warp during warp cooldown', async () => { const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); // Try move fleet let success = true; try { await buildSendAndCheck( Fleet.warpToCoordinate( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, fleet1FuelTank, fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _gameState, _game.publicKey(), cargoProgram, { keyIndex: MOVE_FLEET, toSector: sectorAcc.data.coordinates, }, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('WarpIsOnCooldown'); success = false; } expect(success).toBe(false); }); it('cannot warp to rival faction security zone', async () => { // Sleep to allow the warp cooldown to expire await new Promise((f) => setTimeout(f, warpCooldownTime)); const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); // Try move fleet let success = true; try { await buildSendAndCheck( Fleet.warpToCoordinate( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, fleet1FuelTank, fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _gameState, _game.publicKey(), cargoProgram, { keyIndex: MOVE_FLEET, toSector: [ new BN(155), new BN(0), ] /** this is within the Ustur security zone */, }, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InvalidMovement'); success = false; } expect(success).toBe(false); }); it('start Subwarp movement', async () => { // load sectors const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); const sector2Acc = await readFromRPCOrError( provider.connection, program, _sector2Key, Sector, 'confirmed', ); // Find Fleet const fleet = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); const moveIx = Fleet.startSubwarp( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet.key, _game.publicKey(), _gameState, { toSector: sectorAcc.data.coordinates, keyIndex: MOVE_FLEET, }, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.MoveSubwarp) { throw 'Fleet should be in MoveSubwarp state'; } expect( arrayDeepEquals( fleetData.state.MoveSubwarp.fromSector, sector2Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveSubwarp.toSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveSubwarp.currentSector, sector2Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const expectedFuelCost = Math.floor( Fleet.calculateSubwarpFuelBurnWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ); const expectedDuration = Math.floor( Fleet.calculateSubwarpTimeWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ); expect( Number( fleetData.state.MoveSubwarp.arrivalTime.sub( fleetData.state.MoveSubwarp.departureTime, ), ), ).toBe(expectedDuration); expect(Number(fleetData.state.MoveSubwarp.fuelExpenditure)).toBe( expectedFuelCost, ); }); it('cannot change state during Subwarp movement', async () => { const fleetDataBefore = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); const fleetAccountInfo = await provider.connection.getAccountInfo( fleetDataBefore.key, ); const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const tokenFromBefore = await getAccount( program.provider.connection, tokenFrom, 'processed', ); if (fleetAccountInfo) { const fleetIx = Fleet.movementSubwarpHandler( program, cargoProgram, pointsProgram, playerProfile.publicKey(), fleet1, fleet1FuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); const fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if ( !fleetDataBefore.state.MoveSubwarp || !fleetDataAfter.state.MoveSubwarp ) { throw 'Fleet should be in MoveSubwarp state'; } expect( arrayDeepEquals( fleetDataBefore.state.MoveSubwarp.fromSector, fleetDataAfter.state.MoveSubwarp.fromSector, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetDataBefore.state.MoveSubwarp.toSector, fleetDataAfter.state.MoveSubwarp.toSector, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetDataAfter.state.MoveSubwarp.currentSector, fleetDataBefore.state.MoveSubwarp.fromSector, (a, b) => a.eq(b), ), ).toBe(true); const tokenFromAfter = await getAccount( program.provider.connection, tokenFrom, 'processed', ); expect(Number(tokenFromBefore.amount)).toBeGreaterThanOrEqual( Number(tokenFromAfter.amount), ); } else { throw 'Fleet account info does not exist'; } }); it('stop Subwarp movement', async () => { // Find Fleet const fleetDataBefore = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetDataBefore.state.MoveSubwarp) { throw 'Fleet should be in MoveSubwarp state'; } const pilotXpBefore = ( await readFromRPCOrError( connection, pointsProgram, _userPilotXp, UserPoints, ) ).data.earnedPoints.toNumber(); const councilRankXpBefore = ( await readFromRPCOrError( connection, pointsProgram, _userCouncilRankXp, UserPoints, ) ).data.earnedPoints.toNumber(); const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const tokenFromBefore = await getAccount( program.provider.connection, tokenFrom, 'processed', ); await new Promise((f) => setTimeout(f, 2050), ) /** got this by trial and error */; const stopSubwarpIx = Fleet.stopSubwarp( program, cargoProgram, pointsProgram, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, fleet1FuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), _gameState, { keyIndex: MOVE_FLEET }, ); await buildSendAndCheck(stopSubwarpIx, walletSigner, provider.connection); const fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetDataAfter.state.Idle) { throw 'Fleet should be in idle state'; } const tokenFromAfter = await getAccount( program.provider.connection, tokenFrom, 'processed', ); const actualDistance = calculateDistance( fleetDataBefore.state.MoveSubwarp.fromSector as [BN, BN], fleetDataAfter.state.Idle.sector as [BN, BN], ); expect(Number(tokenFromBefore.amount)).toBeGreaterThan( Number(tokenFromAfter.amount), ); const earnedPoints = Math.floor( actualDistance * Fleet.getFleetSize(fleetDataBefore.data.shipCounts) * (ProgressionItemType.Subwarp + 1), ); await checkUserPoints( _userPilotXp, pilotXpBefore + earnedPoints, pointsProgram, connection, ); await checkUserPoints( _userCouncilRankXp, councilRankXpBefore + earnedPoints, pointsProgram, connection, ); }); it('continue Subwarp movement after stopping', async () => { // load sectors const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); // Find Fleet const fleet = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); const moveIx = Fleet.startSubwarp( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet.key, _game.publicKey(), _gameState, { toSector: sectorAcc.data.coordinates, keyIndex: MOVE_FLEET, }, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.MoveSubwarp) { throw 'Fleet should be in MoveSubwarp state'; } expect( arrayDeepEquals( fleetData.state.MoveSubwarp.toSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); // sleep until movement ends const expectedDuration = Math.floor( Fleet.calculateSubwarpTimeWithCoords( fleetData.data.stats, fleetData.state.MoveSubwarp.fromSector as [BN, BN], fleetData.state.MoveSubwarp.toSector as [BN, BN], ), ); await new Promise((f) => setTimeout(f, expectedDuration * 1000 + 250)); }); it('movement handler after Subwarp movement', async () => { const fleetDataBefore = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetDataBefore.state.MoveSubwarp) { throw 'Fleet should be in MoveSubwarp state'; } const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet1FuelTank, true, ); const tokenFromBefore = await getAccount( program.provider.connection, tokenFrom, 'processed', ); const pilotXpBefore = ( await readFromRPCOrError( connection, pointsProgram, _userPilotXp, UserPoints, ) ).data.earnedPoints.toNumber(); const councilRankXpBefore = ( await readFromRPCOrError( connection, pointsProgram, _userCouncilRankXp, UserPoints, ) ).data.earnedPoints.toNumber(); const fleetIx = Fleet.movementSubwarpHandler( program, cargoProgram, pointsProgram, playerProfile.publicKey(), fleet1, fleet1FuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); const fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetDataAfter.state.Idle) { throw 'Fleet should be in idle state'; } const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); expect( arrayDeepEquals( fleetDataAfter.state.Idle.sector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const tokenFromAfter = await getAccount( program.provider.connection, tokenFrom, 'processed', ); const actualDistance = calculateDistance( fleetDataBefore.state.MoveSubwarp.fromSector as [BN, BN], fleetDataAfter.state.Idle.sector as [BN, BN], ); const expectedFuelCost = Math.floor( Fleet.calculateSubwarpFuelBurnWithDistance( fleetDataBefore.data.stats, actualDistance, ), ); expect(Number(tokenFromBefore.amount) - Number(tokenFromAfter.amount)).toBe( expectedFuelCost, ); expect(fleetDataBefore.state.MoveSubwarp.fuelExpenditure.toNumber()).toBe( expectedFuelCost, ); const earnedPoints = Math.floor( actualDistance * Fleet.getFleetSize(fleetDataAfter.data.shipCounts) * (ProgressionItemType.Subwarp + 1), ); await checkUserPoints( _userPilotXp, pilotXpBefore + earnedPoints, pointsProgram, connection, ); await checkUserPoints( _userCouncilRankXp, councilRankXpBefore + earnedPoints, pointsProgram, connection, ); }); it('subwarp movement to rival faction security zone fails', async () => { // Try move fleet let success = true; try { await buildSendAndCheck( Fleet.startSubwarp( program, _moveFleetKey, playerProfile.publicKey() /** this is a MUD player profile */, ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, _game.publicKey(), _gameState, { toSector: [ new BN(155), new BN(0), ] /** this is within the Ustur security zone */, keyIndex: MOVE_FLEET, }, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InvalidMovement'); success = false; } expect(success).toBe(false); }); it('subwarp movement works okay when distance is 0', async () => { // create fleet and set it to Idle const thisFleet = await quickCreateFleet('OxZero', 1337); const fleetData = await readFromRPCOrError( provider.connection, program, thisFleet.fleetKey[0], Fleet, 'confirmed', ); if (!fleetData.state.Idle) { throw 'Fleet should be in Idle state'; } const tokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleetData.data.fuelTank, true, ); await buildSendAndCheck( [ Fleet.startSubwarp( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], thisFleet.fleetKey[0], _game.publicKey(), _gameState, { toSector: fleetData.state.Idle.sector, keyIndex: MOVE_FLEET, }, ), Fleet.movementSubwarpHandler( program, cargoProgram, pointsProgram, playerProfile.publicKey(), thisFleet.fleetKey[0], fleetData.data.fuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), tokenFrom, _fuel.publicKey, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ), ], walletSigner, provider.connection, ); const fleetDataAfter = await readFromRPCOrError( provider.connection, program, thisFleet.fleetKey[0], Fleet, 'confirmed', ); if (!fleetDataAfter.state.Idle) { throw 'Fleet should be in Idle state'; } expect( arrayDeepEquals( fleetData.state.Idle.sector, fleetDataAfter.state.Idle.sector, (a, b) => a.eq(b), ), ).toBe(true); }); it('change Fleet state from Idle to StarbaseLoadingBay', async () => { await buildSendAndCheck( Fleet.idleToLoadingBay( program, _toLoadingBayFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet1, _starbaseKey, _starbasePlayerKey, _game.publicKey(), _gameState, IDLE_TO_LOADING_BAY, ), walletSigner, provider.connection, ); // Check that the FleetState data is correct const fleetData = await readFromRPCOrError( provider.connection, program, fleet1, Fleet, 'confirmed', ); if (!fleetData.state.StarbaseLoadingBay) { throw 'Fleet should be in idle state'; } expect(fleetData.state.StarbaseLoadingBay.starbase.toBase58()).toBe( _starbaseKey.toBase58(), ); }); it('start Warp Lane movement', async () => { const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); const sector4Acc = await readFromRPCOrError( provider.connection, program, _sector4Key, Sector, 'confirmed', ); // add warp lane connection await buildSendAndCheck( Sector.addConnection( program, _sageSigner, playerProfile.publicKey(), _sectorKey, _sector4Key, _game.publicKey(), sectorAcc.data.coordinates as [BN, BN], { isJumpPoint: true, }, sector4Acc.data.coordinates as [BN, BN], { isJumpPoint: true, }, SAGE_MANAGER, ), walletSigner, provider.connection, ); // create and fund atlas token account await createAndFundTokenAccounts( authority, [_atlas].map((it) => [it.publicKey, 999_999]), _moveFleetKey.publicKey(), funder, walletSigner, provider.connection, ); // create fleet and set it to Idle fleet2Result = await quickCreateFleet('Warp Master 3000', 10000); const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const fuelTokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet2Result.fuelTankKey[0], true, ); const fuelTokenFromBefore = await getAccount( program.provider.connection, fuelTokenFrom, 'processed', ); const feeTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); const feeTokenFromBefore = await getAccount( program.provider.connection, feeTokenFrom, 'processed', ); const feeTokenToBefore = await getAccount( program.provider.connection, _atlasVault, 'processed', ); const input: WarpLaneInput = { keyIndex: MOVE_FLEET, fromSectorIndex: 0, toSectorIndex: 0, }; const moveIx = Fleet.warpLane( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet2Result.fleetKey[0], _starbaseKey, _starbase4Key, _sectorKey, _sector4Key, fleet2Result.fuelTankKey[0], fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), fuelTokenFrom, _fuel.publicKey, feeTokenFrom, _atlasVault, _atlas.publicKey, _gameState, _game.publicKey(), cargoProgram, input, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet2Result.fleetKey[0], Fleet, 'confirmed', ); if (!fleetData.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } expect( arrayDeepEquals( fleetData.state.MoveWarp.fromSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveWarp.toSector, sector4Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const fuelTokenFromAfter = await getAccount( program.provider.connection, fuelTokenFrom, 'processed', ); const feeTokenFromAfter = await getAccount( program.provider.connection, feeTokenFrom, 'processed', ); const feeTokenToAfter = await getAccount( program.provider.connection, _atlasVault, 'processed', ); const gameStateAcc = await readFromRPCOrError( provider.connection, program, _gameState, GameState, 'confirmed', ); const factionStarbaseLevelInfos = gameStateAcc.data.fleet .starbaseLevels as FactionsStarbaseLevelInfo; const expectedFuelCost = Math.ceil( Fleet.calculateWarpFuelBurnWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector4Acc.data.coordinates as [BN, BN], ) * (1 - gameStateAcc.data.misc.warpLaneFuelCostReduction / GLOBAL_SCALE_DECIMALS_2), ); const expectedDuration = Math.floor( Fleet.calculateWarpTimeWithCoords( fleetData.data.stats, sectorAcc.data.coordinates as [BN, BN], sector4Acc.data.coordinates as [BN, BN], ), ); expect( Number( fleetData.state.MoveWarp.warpFinish.sub( fleetData.state.MoveWarp.warpStart, ), ), ).toBe(expectedDuration); expect( Number(fuelTokenFromBefore.amount) - Number(fuelTokenFromAfter.amount), ).toBe(expectedFuelCost); expect( Number(feeTokenFromBefore.amount) - Number(feeTokenFromAfter.amount), ).toBe(factionStarbaseLevelInfos.mud[6].warpLaneMovementFee.toNumber()); expect( Number(feeTokenToAfter.amount) - Number(feeTokenToBefore.amount), ).toBe(factionStarbaseLevelInfos.mud[6].warpLaneMovementFee.toNumber()); }); it('cannot change state during Warp Lane movement', async () => { const fleetDataBefore = await readFromRPCOrError( provider.connection, program, fleet2Result.fleetKey[0], Fleet, 'confirmed', ); const fleetAccountInfo = await provider.connection.getAccountInfo( fleetDataBefore.key, ); if (fleetAccountInfo) { const fleetIx = Fleet.moveWarpHandler( program, pointsProgram, playerProfile.publicKey(), fleet2Result.fleetKey[0], _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); const fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleet2Result.fleetKey[0], Fleet, 'confirmed', ); if (!fleetDataBefore.state.MoveWarp || !fleetDataAfter.state.MoveWarp) { throw 'Fleet should be in MoveWarp state'; } expect( arrayDeepEquals( fleetDataBefore.state.MoveWarp.fromSector, fleetDataAfter.state.MoveWarp.fromSector, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetDataBefore.state.MoveWarp.toSector, fleetDataAfter.state.MoveWarp.toSector, (a, b) => a.eq(b), ), ).toBe(true); } else { throw 'Fleet account info does not exist'; } }); it('movement handler after Warp Lane movement', async () => { // Sleep for 7 second to allow the Warp movement to complete await new Promise((f) => setTimeout(f, 7000)); const fleet = await readFromRPCOrError( provider.connection, program, fleet2Result.fleetKey[0], Fleet, 'confirmed', ); const fleetAccountInfo = await provider.connection.getAccountInfo( fleet.key, ); if (fleetAccountInfo) { const fleetIx = Fleet.moveWarpHandler( program, pointsProgram, playerProfile.publicKey(), fleet.key, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleet2Result.fleetKey[0], Fleet, 'confirmed', ); if (!fleetData.state.Idle) { throw 'Fleet should be in idle state'; } const sector4Acc = await readFromRPCOrError( provider.connection, program, _sector4Key, Sector, 'confirmed', ); expect( arrayDeepEquals( fleetData.state.Idle.sector, sector4Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); } else { throw 'Fleet account info does not exist'; } }); it('warp lane movement to unconnected Sector fails', async () => { const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const fuelTokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet2Result.fuelTankKey[0], true, ); const feeTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); const input: WarpLaneInput = { keyIndex: MOVE_FLEET, fromSectorIndex: 0, toSectorIndex: 0, }; let success = true; try { await buildSendAndCheck( Fleet.warpLane( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet2Result.fleetKey[0], _starbase4Key, _starbase2Key, _sector4Key, _sector2Key /** not connected */, fleet2Result.fuelTankKey[0], fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), fuelTokenFrom, _fuel.publicKey, feeTokenFrom, _atlasVault, _atlas.publicKey, _gameState, _game.publicKey(), cargoProgram, input, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InvalidSectorConnection'); success = false; } expect(success).toBe(false); }); it('warp lane movement to out-of-range Sector fails', async () => { // first add connection await buildSendAndCheck( Sector.addConnection( program, _sageSigner, playerProfile.publicKey(), _sector99Key, _sector4Key, _game.publicKey(), [new BN(0), new BN(0)], { isJumpPoint: true, }, [new BN(0), new BN(0)], { isJumpPoint: true, }, SAGE_MANAGER, ), walletSigner, provider.connection, ); // then test const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const fuelTokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, fleet2Result.fuelTankKey[0], true, ); const feeTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); const input: WarpLaneInput = { keyIndex: MOVE_FLEET, fromSectorIndex: 0, toSectorIndex: 1, }; let success = true; try { await buildSendAndCheck( Fleet.warpLane( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleet2Result.fleetKey[0], _starbase4Key, _starbase99Key, _sector4Key, _sector99Key /** out of range */, fleet2Result.fuelTankKey[0], fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), fuelTokenFrom, _fuel.publicKey, feeTokenFrom, _atlasVault, _atlas.publicKey, _gameState, _game.publicKey(), cargoProgram, input, ), walletSigner, provider.connection, ); } catch (err) { expect(JSON.stringify(err as Error)).toContain('InvalidWarpRange'); success = false; } expect(success).toBe(false); }); it('change Fleet state from Idle to Respawn', async () => { // create fleet with 0 fuel and set state to `Idle` const fleetA = await quickCreateFleet('AAA', 0, -1); const atlasTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); const atlasVaultBefore = await getAccount( program.provider.connection, _atlasVault, 'processed', ); // change state to respawn await buildSendAndCheck( Fleet.idleToRespawn( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetA.fleetKey[0], atlasTokenFrom, _atlasVault, _gameState, _game.publicKey(), { keyIndex: MOVE_FLEET, }, ), walletSigner, provider.connection, ); // Check that the FleetState data is correct const fleetData = await readFromRPCOrError( provider.connection, program, fleetA.fleetKey[0], Fleet, 'confirmed', ); if (!fleetData.state.Respawn) { throw 'Fleet should be in idle state'; } const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); expect( arrayDeepEquals( fleetData.state.Respawn.sector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const atlasVaultAfter = await getAccount( program.provider.connection, _atlasVault, 'processed', ); const gameStateAcc = await readFromRPCOrError( provider.connection, program, _gameState, GameState, 'confirmed', ); expect( Number(atlasVaultAfter.amount) - Number(atlasVaultBefore.amount), ).toBe(gameStateAcc.data.misc.respawnFee.toNumber()); }); it('transition from move subwarp to respawn', async function () { // load sectors const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); const sector2Acc = await readFromRPCOrError( provider.connection, program, _sector2Key, Sector, 'confirmed', ); // create fleet, passing in amount of fuel to start with const shipAcc = await readFromRPCOrError( provider.connection, program, ship.publicKey(), Ship, 'confirmed', ); const numShips = 2; const neededFuel = Math.floor( Fleet.calculateSubwarpFuelBurnWithCoords( Fleet.multiplyShipStats(shipAcc.data.stats, numShips), sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ) / 2; const fleetAddress = await _createFleet( 'big cat', [ship], [numShips], [0], neededFuel, ); const fleetKey = fleetAddress.fleetKey[0]; await buildSendAndCheck( Fleet.loadingBayToIdle( program, _toIdleFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, _starbaseKey, _starbasePlayerKey, _game.publicKey(), _gameState, LOADING_BAY_TO_IDLE, ), walletSigner, provider.connection, ); // Check that the FleetState data is correct const fleet = await readFromRPCOrError( provider.connection, program, fleetKey, Fleet, 'confirmed', ); if (!fleet.state.Idle) { throw 'Fleet should be in idle state'; } const moveIx = Fleet.startSubwarp( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, _game.publicKey(), _gameState, { toSector: sector2Acc.data.coordinates, keyIndex: MOVE_FLEET, }, ); await buildSendAndCheck(moveIx, walletSigner, provider.connection); const fleetData = await readFromRPCOrError( provider.connection, program, fleetKey, Fleet, 'confirmed', ); if (!fleetData.state.MoveSubwarp) { throw 'Fleet should be in MoveSubwarp state'; } const expectedDuration = Math.floor( Fleet.calculateSubwarpTimeWithCoords( fleetData.data.stats, fleetData.state.MoveSubwarp.fromSector as [BN, BN], fleetData.state.MoveSubwarp.toSector as [BN, BN], ), ); expect( arrayDeepEquals( fleetData.state.MoveSubwarp.fromSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveSubwarp.toSector, sector2Acc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); expect( arrayDeepEquals( fleetData.state.MoveSubwarp.currentSector, sectorAcc.data.coordinates, (a, b) => a.eq(b), ), ).toBe(true); const fleetFoodToken = await getAssociatedTokenAddress( _food.publicKey, fleet.data.cargoHold, true, ); const fleetFoodTokenAccountInfo = await provider.connection.getAccountInfo( fleetFoodToken, ); if (!fleetFoodTokenAccountInfo) { throw 'Fleet food token account not found'; } const fleetFuelToken = await getAssociatedTokenAddress( _fuel.publicKey, fleet.data.fuelTank, true, ); const fleetFuelTokenAccountInfo = await provider.connection.getAccountInfo( fleetFuelToken, ); if (!fleetFuelTokenAccountInfo) { throw 'Fleet fuel token account not found'; } const fleetAccountInfo = await provider.connection.getAccountInfo(fleetKey); // Wait until movement is complete await new Promise((f) => setTimeout(f, expectedDuration * 2000)); if (!fleetAccountInfo) { throw 'Fleet account not found'; } const fleetIx = Fleet.movementSubwarpHandler( program, cargoProgram, pointsProgram, playerProfile.publicKey(), fleetKey, fleet.data.fuelTank, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), fleetFuelToken, _fuel.publicKey, _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ); await buildSendAndCheck(fleetIx, walletSigner, provider.connection); let fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleetKey, Fleet, 'confirmed', ); if (!fleetDataAfter.state.Idle) { throw 'Fleet should be in idle state'; } const stoppedAtSector = fleetDataAfter.state.Idle.sector; const atlasTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); // transition to respawn state await buildSendAndCheck( Fleet.idleToRespawn( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, atlasTokenFrom, _atlasVault, _gameState, _game.publicKey(), { keyIndex: MOVE_FLEET, }, ), walletSigner, provider.connection, ); // Check that the FleetState data is correct fleetDataAfter = await readFromRPCOrError( provider.connection, program, fleetKey, Fleet, 'confirmed', ); if (!fleetDataAfter.state.Respawn) { throw 'Fleet should be in respawn state'; } expect( arrayDeepEquals( fleetDataAfter.state.Respawn.sector, stoppedAtSector, (a, b) => a.eq(b), ), ).toBe(true); // empty fleet cargo const dropCargoInstructions = [_fuel.publicKey, _food.publicKey].map( (it) => { return Fleet.forceDropFleetCargo( program, cargoProgram, fleetKey, it.equals(_fuel.publicKey) ? fleetDataAfter.data.fuelTank : fleetDataAfter.data.cargoHold, CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), it, 0, )[0], _cargoStatsDefinitionSigner.publicKey(), _game.publicKey(), it.equals(_fuel.publicKey) ? fleetFuelToken : fleetFoodToken, it, ); }, ); await buildSendAndCheck( (await Promise.all(dropCargoInstructions)).flat(), walletSigner, provider.connection, ); const fuelTankAfter: CargoPod = await readFromRPCOrError( provider.connection, cargoProgram, fleetDataAfter.data.fuelTank, CargoPod, 'confirmed', ); expect(fuelTankAfter.data.openTokenAccounts).toBe(0); const cargoHoldAfter: CargoPod = await readFromRPCOrError( provider.connection, cargoProgram, fleetDataAfter.data.cargoHold, CargoPod, 'confirmed', ); expect(cargoHoldAfter.data.openTokenAccounts).toBe(0); const ammoBankAfter: CargoPod = await readFromRPCOrError( provider.connection, cargoProgram, fleetDataAfter.data.ammoBank, CargoPod, 'confirmed', ); expect(ammoBankAfter.data.openTokenAccounts).toBe(0); // transition from respawn to loading bay // can't respawn until respawn time has passed let success = true; try { await buildSendAndCheck( Fleet.respawnToLoadingBay( program, _fleetRespawnSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, _starbaseKey, _starbasePlayerKey, fleetDataAfter.data.cargoHold, fleetDataAfter.data.fuelTank, fleetDataAfter.data.ammoBank, _game.publicKey(), _gameState, { keyIndex: CAN_RESPAWN_FLEET }, ), walletSigner, provider.connection, ); } catch (e) { expect(JSON.stringify(e as Error)).toContain('RespawnTimeNotElapsed'); success = false; } expect(success).toBe(false); const respawnTime = (fleetDataAfter.data.stats.miscStats as MiscStats) .respawnTime; // Sleep for 5 second to allow for the respawn time to pass await new Promise((f) => setTimeout(f, respawnTime * 1000)); success = true; // can't respawn to a starbase that isn't a Central space station const starbase5ePlayerKey = await _createStarbasePlayer(_starbase5Key); try { await buildSendAndCheck( Fleet.respawnToLoadingBay( program, _fleetRespawnSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, _starbase5Key, starbase5ePlayerKey, fleetDataAfter.data.cargoHold, fleetDataAfter.data.fuelTank, fleetDataAfter.data.ammoBank, _game.publicKey(), _gameState, { keyIndex: CAN_RESPAWN_FLEET }, ), walletSigner, provider.connection, ); } catch (e) { expect(JSON.stringify(e as Error)).toContain('NotAtCentralSpaceStation'); success = false; } expect(success).toBe(false); await buildSendAndCheck( Fleet.respawnToLoadingBay( program, _fleetRespawnSigner, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], fleetKey, _starbaseKey, _starbasePlayerKey, fleetDataAfter.data.cargoHold, fleetDataAfter.data.fuelTank, fleetDataAfter.data.ammoBank, _game.publicKey(), _gameState, { keyIndex: CAN_RESPAWN_FLEET }, ), walletSigner, provider.connection, ); const fleetAccountAfter1 = await readFromRPCOrError( provider.connection, program, fleetKey, Fleet, 'confirmed', ); expect( fleetAccountAfter1.state.StarbaseLoadingBay?.starbase.equals( _starbaseKey, ), ).toBe(true); }); it('warp lane movement resets warp cooldown', async () => { // create fleet and set it to Idle const thisFleet = await quickCreateFleet('Henry', 8000); const fuelCargoType = CargoType.findAddress( cargoProgram, _cargoStatsDefinitionSigner.publicKey(), _fuel.publicKey, 0, )[0]; const fuelTokenFrom = await getAssociatedTokenAddress( _fuel.publicKey, thisFleet.fuelTankKey[0], true, ); // warp so that we get a cooldown const sector2Acc = await readFromRPCOrError( provider.connection, program, _sector2Key, Sector, 'confirmed', ); await buildSendAndCheck( Fleet.warpToCoordinate( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], thisFleet.fleetKey[0], thisFleet.fuelTankKey[0], fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), fuelTokenFrom, _fuel.publicKey, _gameState, _game.publicKey(), cargoProgram, { keyIndex: MOVE_FLEET, toSector: sector2Acc.data.coordinates, }, ), walletSigner, provider.connection, ); const fleetData1 = await readFromRPCOrError( provider.connection, program, thisFleet.fleetKey[0], Fleet, 'confirmed', ); const sectorAcc = await readFromRPCOrError( provider.connection, program, _sectorKey, Sector, 'confirmed', ); const expectedDuration = Math.floor( Fleet.calculateWarpTimeWithCoords( fleetData1.data.stats, sectorAcc.data.coordinates as [BN, BN], sector2Acc.data.coordinates as [BN, BN], ), ); await new Promise((f) => setTimeout( f, Math.max( expectedDuration, (fleetData1.data.stats).movementStats.warpCoolDown, ) * 1050 + 1, ), ); // warp lane movement const feeTokenFrom = await getAssociatedTokenAddress( _atlas.publicKey, _moveFleetKey.publicKey(), true, ); await sendManyToChain( [ Sector.addConnection( program, _sageSigner, playerProfile.publicKey(), _sectorKey, _sector2Key, _game.publicKey(), sectorAcc.data.coordinates as [BN, BN], { isJumpPoint: true, }, sector2Acc.data.coordinates as [BN, BN], { isJumpPoint: true, }, SAGE_MANAGER, ), Fleet.moveWarpHandler( program, pointsProgram, playerProfile.publicKey(), thisFleet.fleetKey[0], _userPilotXp, _pilotXpCategory, _pilotXpModifier, _userCouncilRankXp, _councilRankXpCategory, _councilRankXpModifier, _game.publicKey(), ), Fleet.warpLane( program, _moveFleetKey, playerProfile.publicKey(), ProfileFactionAccount.findAddress( profileFactionProgram, playerProfile.publicKey(), )[0], thisFleet.fleetKey[0], _starbase2Key, _starbaseKey, _sector2Key, _sectorKey, thisFleet.fuelTankKey[0], fuelCargoType, _cargoStatsDefinitionSigner.publicKey(), fuelTokenFrom, _fuel.publicKey, feeTokenFrom, _atlasVault, _atlas.publicKey, _gameState, _game.publicKey(), cargoProgram, { keyIndex: MOVE_FLEET, fromSectorIndex: 1, toSectorIndex: 0, }, ), ], walletSigner, provider.connection, ); const fleetData2 = await readFromRPCOrError( provider.connection, program, thisFleet.fleetKey[0], Fleet, 'confirmed', ); expect( fleetData1.data.warpCooldownExpiresAt.gte( fleetData2.data.warpCooldownExpiresAt, ), ).toBe(true); }); });