|  | 
|  | 1 | +import { AttackExecution } from "../src/core/execution/AttackExecution"; | 
| 1 | 2 | import { MarkDisconnectedExecution } from "../src/core/execution/MarkDisconnectedExecution"; | 
| 2 | 3 | import { SpawnExecution } from "../src/core/execution/SpawnExecution"; | 
| 3 |  | -import { Game, Player, PlayerInfo, PlayerType } from "../src/core/game/Game"; | 
|  | 4 | +import { TransportShipExecution } from "../src/core/execution/TransportShipExecution"; | 
|  | 5 | +import { WarshipExecution } from "../src/core/execution/WarshipExecution"; | 
|  | 6 | +import { | 
|  | 7 | +  Game, | 
|  | 8 | +  GameMode, | 
|  | 9 | +  Player, | 
|  | 10 | +  PlayerInfo, | 
|  | 11 | +  PlayerType, | 
|  | 12 | +  UnitType, | 
|  | 13 | +} from "../src/core/game/Game"; | 
|  | 14 | +import { toInt } from "../src/core/Util"; | 
| 4 | 15 | import { setup } from "./util/Setup"; | 
| 5 | 16 | import { executeTicks } from "./util/utils"; | 
| 6 | 17 | 
 | 
| 7 | 18 | let game: Game; | 
| 8 | 19 | let player1: Player; | 
| 9 | 20 | let player2: Player; | 
|  | 21 | +let enemy: Player; | 
| 10 | 22 | 
 | 
| 11 | 23 | describe("Disconnected", () => { | 
| 12 | 24 |   beforeEach(async () => { | 
| @@ -158,4 +170,294 @@ describe("Disconnected", () => { | 
| 158 | 170 |       expect(player1.isDisconnected()).toBe(true); | 
| 159 | 171 |     }); | 
| 160 | 172 |   }); | 
|  | 173 | + | 
|  | 174 | +  describe("Disconnected team member interactions", () => { | 
|  | 175 | +    const coastX = 7; | 
|  | 176 | + | 
|  | 177 | +    beforeEach(async () => { | 
|  | 178 | +      const player1Info = new PlayerInfo( | 
|  | 179 | +        "[CLAN]Player1", | 
|  | 180 | +        PlayerType.Human, | 
|  | 181 | +        null, | 
|  | 182 | +        "player_1_id", | 
|  | 183 | +      ); | 
|  | 184 | +      const player2Info = new PlayerInfo( | 
|  | 185 | +        "[CLAN]Player2", | 
|  | 186 | +        PlayerType.Human, | 
|  | 187 | +        null, | 
|  | 188 | +        "player_2_id", | 
|  | 189 | +      ); | 
|  | 190 | + | 
|  | 191 | +      game = await setup( | 
|  | 192 | +        "half_land_half_ocean", | 
|  | 193 | +        { | 
|  | 194 | +          infiniteGold: true, | 
|  | 195 | +          instantBuild: true, | 
|  | 196 | +          gameMode: GameMode.Team, | 
|  | 197 | +          playerTeams: 2, // ignore player2 "kicked" console warn | 
|  | 198 | +        }, | 
|  | 199 | +        [player1Info, player2Info], | 
|  | 200 | +      ); | 
|  | 201 | + | 
|  | 202 | +      game.addExecution( | 
|  | 203 | +        new SpawnExecution(player1Info, game.map().ref(coastX - 2, 1)), | 
|  | 204 | +        new SpawnExecution(player2Info, game.map().ref(coastX - 2, 4)), | 
|  | 205 | +      ); | 
|  | 206 | + | 
|  | 207 | +      while (game.inSpawnPhase()) { | 
|  | 208 | +        game.executeNextTick(); | 
|  | 209 | +      } | 
|  | 210 | + | 
|  | 211 | +      player1 = game.player(player1Info.id); | 
|  | 212 | +      player2 = game.player(player2Info.id); | 
|  | 213 | +      player2.markDisconnected(false); | 
|  | 214 | + | 
|  | 215 | +      expect(player1.team()).not.toBeNull(); | 
|  | 216 | +      expect(player2.team()).not.toBeNull(); | 
|  | 217 | +      expect(player1.isOnSameTeam(player2)).toBe(true); | 
|  | 218 | +    }); | 
|  | 219 | + | 
|  | 220 | +    test("Team Warships should not attack disconnected team mate ships", () => { | 
|  | 221 | +      const warship = player1.buildUnit( | 
|  | 222 | +        UnitType.Warship, | 
|  | 223 | +        game.map().ref(coastX + 1, 10), | 
|  | 224 | +        { | 
|  | 225 | +          patrolTile: game.map().ref(coastX + 1, 10), | 
|  | 226 | +        }, | 
|  | 227 | +      ); | 
|  | 228 | +      game.addExecution(new WarshipExecution(warship)); | 
|  | 229 | + | 
|  | 230 | +      const transportShip = player2.buildUnit( | 
|  | 231 | +        UnitType.TransportShip, | 
|  | 232 | +        game.map().ref(coastX + 1, 11), | 
|  | 233 | +        { | 
|  | 234 | +          troops: 100, | 
|  | 235 | +        }, | 
|  | 236 | +      ); | 
|  | 237 | + | 
|  | 238 | +      player2.markDisconnected(true); | 
|  | 239 | +      executeTicks(game, 10); | 
|  | 240 | + | 
|  | 241 | +      expect(warship.targetUnit()).toBe(undefined); | 
|  | 242 | +      expect(transportShip.isActive()).toBe(true); | 
|  | 243 | +      expect(transportShip.owner()).toBe(player2); | 
|  | 244 | +    }); | 
|  | 245 | + | 
|  | 246 | +    test("Disconnected player Warship should not attack team members' ships", () => { | 
|  | 247 | +      const warship = player2.buildUnit( | 
|  | 248 | +        UnitType.Warship, | 
|  | 249 | +        game.map().ref(coastX + 1, 5), | 
|  | 250 | +        { | 
|  | 251 | +          patrolTile: game.map().ref(coastX + 1, 10), | 
|  | 252 | +        }, | 
|  | 253 | +      ); | 
|  | 254 | +      game.addExecution(new WarshipExecution(warship)); | 
|  | 255 | + | 
|  | 256 | +      const transportShip = player1.buildUnit( | 
|  | 257 | +        UnitType.TransportShip, | 
|  | 258 | +        game.map().ref(coastX + 1, 6), | 
|  | 259 | +        { | 
|  | 260 | +          troops: 100, | 
|  | 261 | +        }, | 
|  | 262 | +      ); | 
|  | 263 | + | 
|  | 264 | +      player2.markDisconnected(true); | 
|  | 265 | +      executeTicks(game, 10); | 
|  | 266 | + | 
|  | 267 | +      expect(warship.targetUnit()).toBe(undefined); | 
|  | 268 | +      expect(transportShip.isActive()).toBe(true); | 
|  | 269 | +      expect(transportShip.owner()).toBe(player1); | 
|  | 270 | +    }); | 
|  | 271 | + | 
|  | 272 | +    test("Player can attack disconnected team mate without troop loss", () => { | 
|  | 273 | +      player2.conquer(game.map().ref(coastX - 2, 2)); | 
|  | 274 | +      player2.conquer(game.map().ref(coastX - 2, 3)); | 
|  | 275 | +      player2.markDisconnected(true); | 
|  | 276 | + | 
|  | 277 | +      const attackTroops = 1000; | 
|  | 278 | +      player1.addTroops(attackTroops); | 
|  | 279 | + | 
|  | 280 | +      const troopIncPerTick = game.config().troopIncreaseRate(player1); | 
|  | 281 | +      const expectedTroopGrowth = toInt(troopIncPerTick * 1); | 
|  | 282 | +      const expectedFinalTroops = Number( | 
|  | 283 | +        toInt(player1.troops()) + expectedTroopGrowth, | 
|  | 284 | +      ); | 
|  | 285 | + | 
|  | 286 | +      game.addExecution( | 
|  | 287 | +        new AttackExecution(attackTroops, player1, player2.id(), null), | 
|  | 288 | +      ); | 
|  | 289 | + | 
|  | 290 | +      executeTicks(game, 2); // first tick is the initial tick, in 2nd troop growth happens | 
|  | 291 | +      expect(player1.troops()).toBe(expectedFinalTroops); | 
|  | 292 | +    }); | 
|  | 293 | + | 
|  | 294 | +    test("Conqueror gets conquered disconnected team member's transport- and warships", () => { | 
|  | 295 | +      const warship = player2.buildUnit( | 
|  | 296 | +        UnitType.Warship, | 
|  | 297 | +        game.map().ref(coastX + 1, 1), | 
|  | 298 | +        { | 
|  | 299 | +          patrolTile: game.map().ref(coastX + 1, 1), | 
|  | 300 | +        }, | 
|  | 301 | +      ); | 
|  | 302 | +      const transportShip = player2.buildUnit( | 
|  | 303 | +        UnitType.TransportShip, | 
|  | 304 | +        game.map().ref(coastX + 1, 3), | 
|  | 305 | +        { | 
|  | 306 | +          troops: 100, | 
|  | 307 | +        }, | 
|  | 308 | +      ); | 
|  | 309 | + | 
|  | 310 | +      player2.conquer(game.map().ref(coastX - 2, 1)); | 
|  | 311 | +      player2.markDisconnected(true); | 
|  | 312 | + | 
|  | 313 | +      game.addExecution(new AttackExecution(1000, player1, player2.id(), null)); | 
|  | 314 | + | 
|  | 315 | +      executeTicks(game, 10); | 
|  | 316 | + | 
|  | 317 | +      expect(player2.isAlive()).toBe(false); | 
|  | 318 | +      expect(warship.owner()).toBe(player1); | 
|  | 319 | +      expect(transportShip.owner()).toBe(player1); | 
|  | 320 | +    }); | 
|  | 321 | + | 
|  | 322 | +    test("Captured transport ship landing attack should be in name of new owner", () => { | 
|  | 323 | +      player2.conquer(game.map().ref(coastX, 1)); | 
|  | 324 | +      player2.conquer(game.map().ref(coastX - 1, 1)); | 
|  | 325 | +      player2.conquer(game.map().ref(coastX, 2)); | 
|  | 326 | + | 
|  | 327 | +      const enemyShoreTile = game.map().ref(coastX, 15); | 
|  | 328 | + | 
|  | 329 | +      game.addExecution( | 
|  | 330 | +        new TransportShipExecution( | 
|  | 331 | +          player2, | 
|  | 332 | +          null, | 
|  | 333 | +          enemyShoreTile, | 
|  | 334 | +          100, | 
|  | 335 | +          game.map().ref(coastX, 1), | 
|  | 336 | +          player2, | 
|  | 337 | +        ), | 
|  | 338 | +      ); | 
|  | 339 | + | 
|  | 340 | +      executeTicks(game, 1); | 
|  | 341 | + | 
|  | 342 | +      expect(player2.isAlive()).toBe(true); | 
|  | 343 | +      const transportShip = player2.units(UnitType.TransportShip)[0]; | 
|  | 344 | +      expect(player2.units(UnitType.TransportShip).length).toBe(1); | 
|  | 345 | + | 
|  | 346 | +      player2.markDisconnected(true); | 
|  | 347 | +      game.addExecution(new AttackExecution(1000, player1, player2.id(), null)); | 
|  | 348 | + | 
|  | 349 | +      executeTicks(game, 10); | 
|  | 350 | + | 
|  | 351 | +      expect(player2.isAlive()).toBe(false); | 
|  | 352 | +      expect(transportShip.owner()).toBe(player1); | 
|  | 353 | + | 
|  | 354 | +      executeTicks(game, 30); | 
|  | 355 | + | 
|  | 356 | +      // Verify ship landed and tile ownership transferred to new ship owner | 
|  | 357 | +      expect(game.owner(enemyShoreTile)).toBe(player1); | 
|  | 358 | +    }); | 
|  | 359 | + | 
|  | 360 | +    test("Captured transport ship should retreat to owner's shore tile", () => { | 
|  | 361 | +      player1.conquer(game.map().ref(coastX, 4)); | 
|  | 362 | +      player2.conquer(game.map().ref(coastX, 1)); | 
|  | 363 | + | 
|  | 364 | +      const enemyShoreTile = game.map().ref(coastX, 8); | 
|  | 365 | + | 
|  | 366 | +      game.addExecution( | 
|  | 367 | +        new TransportShipExecution( | 
|  | 368 | +          player2, | 
|  | 369 | +          null, | 
|  | 370 | +          enemyShoreTile, | 
|  | 371 | +          100, | 
|  | 372 | +          game.map().ref(coastX, 1), | 
|  | 373 | +          player2, | 
|  | 374 | +        ), | 
|  | 375 | +      ); | 
|  | 376 | +      executeTicks(game, 1); | 
|  | 377 | + | 
|  | 378 | +      const transportShip = player2.units(UnitType.TransportShip)[0]; | 
|  | 379 | +      expect(player2.units(UnitType.TransportShip).length).toBe(1); | 
|  | 380 | + | 
|  | 381 | +      expect(transportShip.targetTile()).toBe(enemyShoreTile); | 
|  | 382 | + | 
|  | 383 | +      player2.markDisconnected(true); | 
|  | 384 | +      game.addExecution(new AttackExecution(1000, player1, player2.id(), null)); | 
|  | 385 | +      executeTicks(game, 10); | 
|  | 386 | + | 
|  | 387 | +      expect(player2.isAlive()).toBe(false); | 
|  | 388 | +      expect(transportShip.owner()).toBe(player1); | 
|  | 389 | + | 
|  | 390 | +      transportShip.orderBoatRetreat(); | 
|  | 391 | +      executeTicks(game, 2); | 
|  | 392 | + | 
|  | 393 | +      expect(transportShip.targetTile()).not.toBe(enemyShoreTile); | 
|  | 394 | +      expect(game.owner(transportShip.targetTile()!)).toBe(player1); | 
|  | 395 | +    }); | 
|  | 396 | + | 
|  | 397 | +    test("Retreating transport ship is deleted if new owner has no shore tiles", () => { | 
|  | 398 | +      player2.conquer(game.map().ref(coastX, 1)); | 
|  | 399 | +      player2.conquer(game.map().ref(coastX - 6, 2)); | 
|  | 400 | +      player1.conquer(game.map().ref(coastX - 6, 3)); | 
|  | 401 | + | 
|  | 402 | +      const enemyShoreTile = game.map().ref(coastX, 15); | 
|  | 403 | + | 
|  | 404 | +      const boatTroops = 100; | 
|  | 405 | +      game.addExecution( | 
|  | 406 | +        new TransportShipExecution( | 
|  | 407 | +          player2, | 
|  | 408 | +          null, | 
|  | 409 | +          enemyShoreTile, | 
|  | 410 | +          boatTroops, | 
|  | 411 | +          game.map().ref(coastX, 1), | 
|  | 412 | +          player2, | 
|  | 413 | +        ), | 
|  | 414 | +      ); | 
|  | 415 | +      executeTicks(game, 1); | 
|  | 416 | + | 
|  | 417 | +      const transportShip = player2.units(UnitType.TransportShip)[0]; | 
|  | 418 | +      expect(player2.units(UnitType.TransportShip).length).toBe(1); | 
|  | 419 | + | 
|  | 420 | +      player2.markDisconnected(true); | 
|  | 421 | +      game.addExecution(new AttackExecution(1000, player1, player2.id(), null)); | 
|  | 422 | +      executeTicks(game, 2); | 
|  | 423 | + | 
|  | 424 | +      expect(player2.isAlive()).toBe(false); | 
|  | 425 | +      expect(transportShip.owner()).toBe(player1); | 
|  | 426 | + | 
|  | 427 | +      // Make sure player1 has no shore tiles for the ship to retreat to anymore | 
|  | 428 | +      const enemyInfo = new PlayerInfo( | 
|  | 429 | +        "Enemy", | 
|  | 430 | +        PlayerType.Human, | 
|  | 431 | +        null, | 
|  | 432 | +        "enemy_id", | 
|  | 433 | +      ); | 
|  | 434 | +      enemy = game.addPlayer(enemyInfo); | 
|  | 435 | + | 
|  | 436 | +      const shoreTiles = Array.from(player1.borderTiles()).filter((t) => | 
|  | 437 | +        game.isShore(t), | 
|  | 438 | +      ); | 
|  | 439 | +      shoreTiles.forEach((tile) => { | 
|  | 440 | +        enemy.conquer(tile); | 
|  | 441 | +      }); | 
|  | 442 | + | 
|  | 443 | +      expect( | 
|  | 444 | +        Array.from(player1.borderTiles()).filter((t) => game.isShore(t)).length, | 
|  | 445 | +      ).toBe(0); | 
|  | 446 | + | 
|  | 447 | +      executeTicks(game, 1); | 
|  | 448 | + | 
|  | 449 | +      const troopIncPerTick = game.config().troopIncreaseRate(player1); | 
|  | 450 | +      const expectedTroopGrowth = toInt(troopIncPerTick * 1); | 
|  | 451 | +      const expectedFinalTroops = Number( | 
|  | 452 | +        toInt(player1.troops()) + expectedTroopGrowth, | 
|  | 453 | +      ); | 
|  | 454 | + | 
|  | 455 | +      transportShip.orderBoatRetreat(); | 
|  | 456 | +      executeTicks(game, 1); | 
|  | 457 | + | 
|  | 458 | +      expect(transportShip.isActive()).toBe(false); | 
|  | 459 | +      // Also test if boat troops were returned to player1 as new ship owner | 
|  | 460 | +      expect(player1.troops()).toBe(expectedFinalTroops + boatTroops); | 
|  | 461 | +    }); | 
|  | 462 | +  }); | 
| 161 | 463 | }); | 
0 commit comments