From 8925f48ba73e08d6a11771e8cb51da71b92b164b Mon Sep 17 00:00:00 2001 From: Ilan Schemoul Date: Fri, 18 Apr 2025 04:02:50 +0200 Subject: [PATCH] sam protects againt mirv warhead (#376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #483 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ![Capture d'écran 2025-03-30 174759](https://github.com/user-attachments/assets/8245723d-68de-4b96-ab19-5d85be18e4d9) ## Please put your Discord username so you can be contacted if a bug or regression is found: respectful pinguin --- src/core/configuration/Config.ts | 1 + src/core/configuration/DefaultConfig.ts | 4 + src/core/configuration/DevConfig.ts | 8 ++ src/core/execution/MIRVExecution.ts | 1 - src/core/execution/SAMLauncherExecution.ts | 140 +++++++++++++++------ src/core/game/UnitImpl.ts | 2 +- 6 files changed, 113 insertions(+), 43 deletions(-) diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index f53d90aa2..35fa3368c 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -52,6 +52,7 @@ export interface NukeMagnitude { export interface Config { samHittingChance(): number; + samWarheadHittingChance(): number; spawnImmunityDuration(): Tick; serverConfig(): ServerConfig; gameConfig(): GameConfig; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 254bfc99e..6c20883b9 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -136,6 +136,10 @@ export class DefaultConfig implements Config { return 0.8; } + samWarheadHittingChance(): number { + return 0.5; + } + traitorDefenseDebuff(): number { return 0.8; } diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 853467814..99e616cc1 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -24,6 +24,14 @@ export class DevServerConfig extends DefaultServerConfig { return Math.random() < 0.5 ? 2 : 3; } + samWarheadHittingChance(): number { + return 1; + } + + samHittingChance(): number { + return 1; + } + discordRedirectURI(): string { return "http://localhost:3000/auth/callback"; } diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index bd0120c22..d2b118c0a 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -170,7 +170,6 @@ export class MirvExecution implements Execution { if (!this.mg.isValidCoord(x, y)) { continue; } - console.log(`got coord ${x}, ${y}`); const tile = this.mg.ref(x, y); if (!this.mg.isLand(tile)) { continue; diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index e3337f8fd..07f55b3c1 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -19,8 +19,13 @@ export class SAMLauncherExecution implements Execution { private active: boolean = true; private target: Unit = null; + private warheadTargets: Unit[] = []; private searchRangeRadius = 75; + // As MIRV go very fast we have to detect them very early but we only + // shoot the one targeting very close (MIRVWarheadProtectionRadius) + private MIRVWarheadSearchRadius = 400; + private MIRVWarheadProtectionRadius = 50; private pseudoRandom: PseudoRandom; @@ -39,6 +44,52 @@ export class SAMLauncherExecution implements Execution { this.player = mg.player(this.ownerId); } + private getSingleTarget(): Unit | null { + const nukes = this.mg + .nearbyUnits(this.sam.tile(), this.searchRangeRadius, [ + UnitType.AtomBomb, + UnitType.HydrogenBomb, + ]) + .filter( + ({ unit }) => + unit.owner() !== this.player && !this.player.isFriendly(unit.owner()), + ); + + return ( + nukes.sort((a, b) => { + const { unit: unitA, distSquared: distA } = a; + const { unit: unitB, distSquared: distB } = b; + + // Prioritize Hydrogen Bombs + if ( + unitA.type() === UnitType.HydrogenBomb && + unitB.type() !== UnitType.HydrogenBomb + ) + return -1; + if ( + unitA.type() !== UnitType.HydrogenBomb && + unitB.type() === UnitType.HydrogenBomb + ) + return 1; + + // If both are the same type, sort by distance (lower `distSquared` means closer) + return distA - distB; + })[0]?.unit ?? null + ); + } + + private isHit(type: UnitType, random: number): boolean { + if (type == UnitType.AtomBomb) { + return true; + } + + if (type == UnitType.MIRVWarhead) { + return random < this.mg.config().samWarheadHittingChance(); + } + + return random < this.mg.config().samHittingChance(); + } + tick(ticks: number): void { if (this.sam == null) { const spawnTile = this.player.canBuild(UnitType.SAMLauncher, this.tile); @@ -64,36 +115,26 @@ export class SAMLauncherExecution implements Execution { this.pseudoRandom = new PseudoRandom(this.sam.id()); } - const nukes = this.mg - .nearbyUnits(this.sam.tile(), this.searchRangeRadius, [ - UnitType.AtomBomb, - UnitType.HydrogenBomb, - ]) + this.warheadTargets = this.mg + .nearbyUnits( + this.sam.tile(), + this.MIRVWarheadSearchRadius, + UnitType.MIRVWarhead, + ) + .map(({ unit }) => unit) .filter( - ({ unit }) => + (unit) => unit.owner() !== this.player && !this.player.isFriendly(unit.owner()), + ) + .filter( + (unit) => + this.mg.manhattanDist(unit.detonationDst(), this.sam.tile()) < + this.MIRVWarheadProtectionRadius, ); - this.target = - nukes.sort((a, b) => { - const { unit: unitA, distSquared: distA } = a; - const { unit: unitB, distSquared: distB } = b; - - // Prioritize Hydrogen Bombs - if ( - unitA.type() === UnitType.HydrogenBomb && - unitB.type() !== UnitType.HydrogenBomb - ) - return -1; - if ( - unitA.type() !== UnitType.HydrogenBomb && - unitB.type() === UnitType.HydrogenBomb - ) - return 1; - - // If both are the same type, sort by distance (lower `distSquared` means closer) - return distA - distB; - })[0]?.unit ?? null; + if (this.warheadTargets.length == 0) { + this.target = this.getSingleTarget(); + } if ( this.sam.isCooldown() && @@ -102,29 +143,46 @@ export class SAMLauncherExecution implements Execution { this.sam.setCooldown(false); } - if (this.target && !this.sam.isCooldown() && !this.target.targetedBySAM()) { + const isSingleTarget = this.target && !this.target.targetedBySAM(); + if ( + (isSingleTarget || this.warheadTargets.length > 0) && + !this.sam.isCooldown() + ) { this.sam.setCooldown(true); + const type = + this.warheadTargets.length > 0 + ? UnitType.MIRVWarhead + : this.target.type(); const random = this.pseudoRandom.next(); - let hit = true; - if (this.target.type() != UnitType.AtomBomb) { - hit = random < this.mg.config().samHittingChance(); - } + const hit = this.isHit(type, random); if (!hit) { this.mg.displayMessage( - `Missile failed to intercept ${this.target.type()}`, + `Missile failed to intercept ${type}`, MessageType.ERROR, this.sam.owner().id(), ); } else { - this.target.setTargetedBySAM(true); - this.mg.addExecution( - new SAMMissileExecution( - this.sam.tile(), - this.sam.owner(), - this.sam, - this.target, - ), - ); + if (this.warheadTargets.length > 0) { + // Message + this.mg.displayMessage( + `${this.warheadTargets.length} MIRV warheads intercepted`, + MessageType.SUCCESS, + this.sam.owner().id(), + ); + // Delete warheads + this.warheadTargets.forEach((u) => u.delete()); + } else { + this.target.setTargetedBySAM(true); + this.mg.addExecution( + new SAMMissileExecution( + this.sam.tile(), + this.sam.owner(), + this.sam, + this.target, + ), + ); + this.warheadTargets = []; + } } } } diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index ac5b80fc6..f0dd2796d 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -141,7 +141,7 @@ export class UnitImpl implements Unit { this._active = false; this.mg.addUpdate(this.toUpdate()); this.mg.removeUnit(this); - if (displayMessage) { + if (displayMessage && this.type() != UnitType.MIRVWarhead) { this.mg.displayMessage( `Your ${this.type()} was destroyed`, MessageType.ERROR,