diff --git a/agot-bg-game-server/public/images/house-cards/AeronDamphairDwD-nerved.png b/agot-bg-game-server/public/images/house-cards/AeronDamphairDwD-nerved.png new file mode 100644 index 000000000..c8863e3ae Binary files /dev/null and b/agot-bg-game-server/public/images/house-cards/AeronDamphairDwD-nerved.png differ diff --git a/agot-bg-game-server/public/images/house-cards/Balon-nerved.png b/agot-bg-game-server/public/images/house-cards/Balon-nerved.png new file mode 100644 index 000000000..791916d68 Binary files /dev/null and b/agot-bg-game-server/public/images/house-cards/Balon-nerved.png differ diff --git a/agot-bg-game-server/public/images/units/ArrDragon.png b/agot-bg-game-server/public/images/units/ArrDragon.png new file mode 100644 index 000000000..e0609e654 Binary files /dev/null and b/agot-bg-game-server/public/images/units/ArrDragon.png differ diff --git a/agot-bg-game-server/public/images/units/BaraDragon.png b/agot-bg-game-server/public/images/units/BaraDragon.png new file mode 100644 index 000000000..dc46a3e86 Binary files /dev/null and b/agot-bg-game-server/public/images/units/BaraDragon.png differ diff --git a/agot-bg-game-server/public/images/units/BoltDragon.png b/agot-bg-game-server/public/images/units/BoltDragon.png new file mode 100644 index 000000000..16921ad37 Binary files /dev/null and b/agot-bg-game-server/public/images/units/BoltDragon.png differ diff --git a/agot-bg-game-server/public/images/units/GreyDragon.png b/agot-bg-game-server/public/images/units/GreyDragon.png new file mode 100644 index 000000000..12b6a4267 Binary files /dev/null and b/agot-bg-game-server/public/images/units/GreyDragon.png differ diff --git a/agot-bg-game-server/public/images/units/LannDragon.png b/agot-bg-game-server/public/images/units/LannDragon.png new file mode 100644 index 000000000..67b00f72b Binary files /dev/null and b/agot-bg-game-server/public/images/units/LannDragon.png differ diff --git a/agot-bg-game-server/public/images/units/MartDragon.png b/agot-bg-game-server/public/images/units/MartDragon.png new file mode 100644 index 000000000..0faef0598 Binary files /dev/null and b/agot-bg-game-server/public/images/units/MartDragon.png differ diff --git a/agot-bg-game-server/public/images/units/StarDragon.png b/agot-bg-game-server/public/images/units/StarDragon.png new file mode 100644 index 000000000..fae5ca65f Binary files /dev/null and b/agot-bg-game-server/public/images/units/StarDragon.png differ diff --git a/agot-bg-game-server/public/images/units/TyrDragon.png b/agot-bg-game-server/public/images/units/TyrDragon.png new file mode 100644 index 000000000..2479e6fe3 Binary files /dev/null and b/agot-bg-game-server/public/images/units/TyrDragon.png differ diff --git a/agot-bg-game-server/src/client/EntireGameComponent.tsx b/agot-bg-game-server/src/client/EntireGameComponent.tsx index 621fb3d48..5f7a98fb1 100644 --- a/agot-bg-game-server/src/client/EntireGameComponent.tsx +++ b/agot-bg-game-server/src/client/EntireGameComponent.tsx @@ -265,7 +265,33 @@ export default class EntireGameComponent extends Component } - {this.props.entireGame.gameSettings.fogOfWar && + {(this.settings.dragonWar || this.settings.dragonRevenge) && + + +
+ {this.settings.dragonWar && <> +

+

Dragon war
+ Balon Greyjoy and Aeron Damphair (DwD) have been nerfed! +

+ } + {this.settings.dragonRevenge && <> +

+

Dragon revenge
+ The last remaining non-dragon land unit will transform into a dragon! +

+ } +
+ } + popperConfig={{ modifiers: [preventOverflow] }} + > +

🐉

+
+ } + {this.settings.fogOfWar &&

BETA

} diff --git a/agot-bg-game-server/src/client/GameLogListComponent.tsx b/agot-bg-game-server/src/client/GameLogListComponent.tsx index e2099c35b..6c235e00d 100644 --- a/agot-bg-game-server/src/client/GameLogListComponent.tsx +++ b/agot-bg-game-server/src/client/GameLogListComponent.tsx @@ -21,7 +21,7 @@ import orders from "../common/ingame-game-state/game-data-structure/orders"; import CombatInfoComponent from "./CombatInfoComponent"; import { OverlayTrigger, Tooltip } from "react-bootstrap"; import User from "../server/User"; -import { baseHouseCardsData, adwdHouseCardsData, ffcHouseCardsData, modBHouseCardsData , HouseCardData, asosHouseCardsData } from "../common/ingame-game-state/game-data-structure/createGame"; +import { baseHouseCardsData, adwdHouseCardsData, ffcHouseCardsData, modBHouseCardsData , HouseCardData, asosHouseCardsData, createHouseCard } from "../common/ingame-game-state/game-data-structure/createGame"; import HouseCard from "../common/ingame-game-state/game-data-structure/house-card/HouseCard"; import houseCardAbilities from "../common/ingame-game-state/game-data-structure/house-card/houseCardAbilities"; import BetterMap from "../utils/BetterMap"; @@ -97,6 +97,16 @@ export default class GameLogListComponent extends Component - Jaqen H'ghar: House {house.name} randomly chose {newHouseCard.name} as + {usedByHouseCard.name}: House {house.name} randomly chose {newHouseCard.name} as {affectedHouse.name}'s new House card.

; } @@ -2060,6 +2072,15 @@ export default class GameLogListComponent extends Component{this.fogOfWar ? fogOfWarPlaceholder : region.name}
.

; } + case "last-land-unit-transformed-to-dragon": { + const house = this.game.houses.get(data.house); + const unitType = unitTypes.get(data.transformedUnitType); + const region = this.world.regions.get(data.region); + + return <> + Dragon revenge: The last {unitType.name} of House {house.name} has been transformed into a Dragon in {region.name}. + ; + } } } diff --git a/agot-bg-game-server/src/client/GameSettingsComponent.tsx b/agot-bg-game-server/src/client/GameSettingsComponent.tsx index 9f1922080..be1bda23d 100644 --- a/agot-bg-game-server/src/client/GameSettingsComponent.tsx +++ b/agot-bg-game-server/src/client/GameSettingsComponent.tsx @@ -811,6 +811,37 @@ export default class GameSettingsComponent extends Component this.changeGameSettings(() => this.gameSettings.limitedDraft = !this.gameSettings.limitedDraft)} /> + + + All houses, except Targaryen, will replace their starting knight by a dragon. + (Inspired by LordWazza) + }> + + } + checked={this.gameSettings.dragonWar} + onChange={() => this.changeGameSettings(() => this.gameSettings.dragonWar = !this.gameSettings.dragonWar)} + /> + + + + If a player has only one remaining non-dragon land unit and no more castles, it will turn into a dragon. + }> + + } + checked={this.gameSettings.dragonRevenge} + onChange={() => this.changeGameSettings(() => this.gameSettings.dragonRevenge = !this.gameSettings.dragonRevenge)} + /> + {this.gameSettings.draftHouseCards && diff --git a/agot-bg-game-server/src/client/IngameComponent.tsx b/agot-bg-game-server/src/client/IngameComponent.tsx index 7410e63a5..b380b03f0 100644 --- a/agot-bg-game-server/src/client/IngameComponent.tsx +++ b/agot-bg-game-server/src/client/IngameComponent.tsx @@ -978,7 +978,7 @@ export default class IngameComponent extends Component { - {this.gameSettings.playerCount >= 8 && + {this.ingame.isDragonGame &&
diff --git a/agot-bg-game-server/src/client/houseCardImages.ts b/agot-bg-game-server/src/client/houseCardImages.ts index b2dcfabaf..5bdc96022 100644 --- a/agot-bg-game-server/src/client/houseCardImages.ts +++ b/agot-bg-game-server/src/client/houseCardImages.ts @@ -5,6 +5,7 @@ import aeroImage from "../../public/images/house-cards/Areo.png"; import arianneImage from "../../public/images/house-cards/Arianne.png"; import ashaImage from "../../public/images/house-cards/Asha.png"; import balonImage from "../../public/images/house-cards/Balon.png"; +import balonNervedImage from "../../public/images/house-cards/Balon-nerved.png"; import blackfishImage from "../../public/images/house-cards/Blackfish.png"; import brienneImage from "../../public/images/house-cards/Brienne.png"; import catelynImage from "../../public/images/house-cards/Catelyn.png"; @@ -41,11 +42,13 @@ import theonImage from "../../public/images/house-cards/Theon.png"; import tyrionImage from "../../public/images/house-cards/Tyrion.png"; import tywinImage from "../../public/images/house-cards/Tywin.png"; import victarionImage from "../../public/images/house-cards/Victarion.png"; + import rayderImage from "../../public/images/house-cards/Rayder.png"; import melisandreDwDImage from "../../public/images/house-cards/Melisandre_DwD.png"; import jonSnow from "../../public/images/house-cards/JonSnow.png"; import stannisDwDImage from "../../public/images/house-cards/StannisBaratheonDwD.png"; import aeronDwDImage from "../../public/images/house-cards/AeronDamphairDwD.png"; +import aeronDwDNervedImage from "../../public/images/house-cards/AeronDamphairDwD-nerved.png"; import qarlTheMaidImage from "../../public/images/house-cards/QarlTheMaid.png"; import rodrikTheReaderImage from "../../public/images/house-cards/RodrikTheReader.png"; import euronDwDImage from "../../public/images/house-cards/EuronCrowsEye.png"; @@ -61,30 +64,24 @@ import ramsayBoltonImage from "../../public/images/house-cards/RamsayBolton.png" import queenOfThornsDwDImage from "../../public/images/house-cards/QueenOfThorns.png"; import paxterRedwyneImage from "../../public/images/house-cards/PaxterRedwyne.png"; import margaeryTyrellDwDImage from "../../public/images/house-cards/MargaeryTyrell.png"; - import maceTyrellDwDImage from "../../public/images/house-cards/MaceTyrell.png"; import randyllTarlyDwDImage from "../../public/images/house-cards/RandyllTarly.png"; import willasTyrellImage from "../../public/images/house-cards/WillasTyrell.png"; import serJonFossowayImage from "../../public/images/house-cards/SerJonFossoway.png"; - import nymeriaSandDwDImage from "../../public/images/house-cards/NymeriaSand.png"; import areoHotahDwDImage from "../../public/images/house-cards/AreoHotah.png"; import bastardOfGodsgraceImage from "../../public/images/house-cards/BastardOfGodsgrace.png"; import bigManImage from "../../public/images/house-cards/BigMan.png"; - import serHarrasHarlawImage from "../../public/images/house-cards/SerHarrasHarlaw.png"; import victarionGreyjoyDwDImage from "../../public/images/house-cards/VictarionGreyjoy.png"; import ashaGreyjoyDwDImage from "../../public/images/house-cards/AshaGreyjoy.png"; - import bastardOfNightsongImage from "../../public/images/house-cards/BastardOfNightsong.png"; import serDavosSeaworthDwDImage from "../../public/images/house-cards/SerDavosSeaworth.png"; import serAxellFlorentImage from "../../public/images/house-cards/SerAxellFlorent.png"; - import blackWalderImage from "../../public/images/house-cards/BlackWalder.png"; import steelshanksWaltonImage from "../../public/images/house-cards/SteelshanksWalton.png"; import damonDanceForMeImage from "../../public/images/house-cards/DamonDanceForMe.png"; import rooseBoltonDwDImage from "../../public/images/house-cards/RooseBolton.png"; - import cerseiLannisterDwDImage from "../../public/images/house-cards/CerseiLannister.png"; import serJaimeLannisterDwDImage from "../../public/images/house-cards/SerJaimeLannister.png"; import serKevanLannisterDwDImage from "../../public/images/house-cards/SerKevanLannister.png"; @@ -178,6 +175,7 @@ const houseCardImages = new BetterMap([ ["areo-hotah", aeroImage], ["asha-greyjoy", ashaImage], ["balon-greyjoy", balonImage], + ["balon-greyjoy-nerved", balonNervedImage], ["the-blackfish", blackfishImage], ["brienne-of-tarth", brienneImage], ["catelyn-stark", catelynImage], @@ -219,6 +217,7 @@ const houseCardImages = new BetterMap([ ["jon-snow", jonSnow], ["stannis-baratheon-dwd", stannisDwDImage], ["aeron-damphair-dwd", aeronDwDImage], + ["aeron-damphair-dwd-nerved", aeronDwDNervedImage], ["qarl-the-maid", qarlTheMaidImage], ["rodrik-the-reader", rodrikTheReaderImage], ["euron-crows-eye-dwd", euronDwDImage], diff --git a/agot-bg-game-server/src/client/unitImages.ts b/agot-bg-game-server/src/client/unitImages.ts index 3a4a0e49d..3142369df 100644 --- a/agot-bg-game-server/src/client/unitImages.ts +++ b/agot-bg-game-server/src/client/unitImages.ts @@ -1,17 +1,17 @@ import BetterMap from "../utils/BetterMap"; import {dragon, footman, knight, ship, siegeEngine} from "../common/ingame-game-state/game-data-structure/unitTypes"; -import barathFoot from "../../public/images/units/BarathFoot.png"; -import barathKnight from "../../public/images/units/BarathKnight.png"; -import barathSiege from "../../public/images/units/BarathSeige.png"; -import barathShip from "../../public/images/units/BarathShip.png"; -import boltonFoot from "../../public/images/units/BoltonFoot.png"; -import boltonKnight from "../../public/images/units/BoltonKnight.png"; -import boltonSiege from "../../public/images/units/BoltonSiege.png"; -import boltonShip from "../../public/images/units/BoltonShip.png"; -import lanFoot from "../../public/images/units/LanFoot.png"; -import lanKnight from "../../public/images/units/LanKnight.png"; -import lanSiege from "../../public/images/units/LanSeige.png"; -import lanShip from "../../public/images/units/LanShip.png"; +import baraFoot from "../../public/images/units/BarathFoot.png"; +import baraKnight from "../../public/images/units/BarathKnight.png"; +import baraSiege from "../../public/images/units/BarathSeige.png"; +import baraShip from "../../public/images/units/BarathShip.png"; +import boltFoot from "../../public/images/units/BoltonFoot.png"; +import boltKnight from "../../public/images/units/BoltonKnight.png"; +import boltSiege from "../../public/images/units/BoltonSiege.png"; +import boltShip from "../../public/images/units/BoltonShip.png"; +import lannFoot from "../../public/images/units/LanFoot.png"; +import lannKnight from "../../public/images/units/LanKnight.png"; +import lannSiege from "../../public/images/units/LanSeige.png"; +import lannShip from "../../public/images/units/LanShip.png"; import starFoot from "../../public/images/units/StarFoot.png"; import starKnight from "../../public/images/units/StarKnight.png"; import starSiege from "../../public/images/units/StarSeige.png"; @@ -28,70 +28,86 @@ import tyrFoot from "../../public/images/units/TyrFoot.png"; import tyrKnight from "../../public/images/units/TyrKnight.png"; import tyrSiege from "../../public/images/units/TyrSeige.png"; import tyrShip from "../../public/images/units/TyrShip.png"; -import arrynFoot from "../../public/images/units/ArrynFoot.png" -import arrynKnight from "../../public/images/units/ArrynKnight.png"; -import arrynSiege from "../../public/images/units/ArrynSiege.png"; -import arrynShip from "../../public/images/units/ArrynShip.png"; -import targaryenShip from "../../public/images/units/TargShip.png"; -import targaryenFoot from "../../public/images/units/TargFoot.png"; +import arrFoot from "../../public/images/units/ArrynFoot.png" +import arrKnight from "../../public/images/units/ArrynKnight.png"; +import arrSiege from "../../public/images/units/ArrynSiege.png"; +import arrShip from "../../public/images/units/ArrynShip.png"; +import targShip from "../../public/images/units/TargShip.png"; +import targFoot from "../../public/images/units/TargFoot.png"; import targKnight from "../../public/images/units/TargKnight.png"; import targDragon from "../../public/images/units/TargDragon.png"; +import baraDragon from "../../public/images/units/BaraDragon.png"; +import lannDragon from "../../public/images/units/LannDragon.png"; +import starDragon from "../../public/images/units/StarDragon.png"; +import boltDragon from "../../public/images/units/BoltDragon.png"; +import greyDragon from "../../public/images/units/GreyDragon.png"; +import martDragon from "../../public/images/units/MartDragon.png"; +import tyrDragon from "../../public/images/units/TyrDragon.png"; +import arrDragon from "../../public/images/units/ArrDragon.png"; const unitImages = new BetterMap([ ["baratheon", new BetterMap([ - [footman.id, barathFoot], - [knight.id, barathKnight], - [siegeEngine.id, barathSiege], - [ship.id, barathShip] + [footman.id, baraFoot], + [knight.id, baraKnight], + [siegeEngine.id, baraSiege], + [ship.id, baraShip], + [dragon.id, baraDragon] ])], ["lannister", new BetterMap([ - [footman.id, lanFoot], - [knight.id, lanKnight], - [siegeEngine.id, lanSiege], - [ship.id, lanShip] + [footman.id, lannFoot], + [knight.id, lannKnight], + [siegeEngine.id, lannSiege], + [ship.id, lannShip], + [dragon.id, lannDragon] ])], ["stark", new BetterMap([ [footman.id, starFoot], [knight.id, starKnight], [siegeEngine.id, starSiege], - [ship.id, starShip] + [ship.id, starShip], + [dragon.id, starDragon] ])], ["greyjoy", new BetterMap([ [footman.id, greyFoot], [knight.id, greyKnight], [siegeEngine.id, greySiege], - [ship.id, greyShip] + [ship.id, greyShip], + [dragon.id, greyDragon] ])], ["martell", new BetterMap([ [footman.id, martFoot], [knight.id, martKnight], [siegeEngine.id, martSiege], - [ship.id, martShip] + [ship.id, martShip], + [dragon.id, martDragon] ])], ["tyrell", new BetterMap([ [footman.id, tyrFoot], [knight.id, tyrKnight], [siegeEngine.id, tyrSiege], - [ship.id, tyrShip] + [ship.id, tyrShip], + [dragon.id, tyrDragon] ])], ["arryn", new BetterMap([ - [footman.id, arrynFoot], - [knight.id, arrynKnight], - [siegeEngine.id, arrynSiege], - [ship.id, arrynShip] + [footman.id, arrFoot], + [knight.id, arrKnight], + [siegeEngine.id, arrSiege], + [ship.id, arrShip], + [dragon.id, arrDragon] ])], ["bolton", new BetterMap([ - [footman.id, boltonFoot], - [knight.id, boltonKnight], - [siegeEngine.id, boltonSiege], - [ship.id, boltonShip] + [footman.id, boltFoot], + [knight.id, boltKnight], + [siegeEngine.id, boltSiege], + [ship.id, boltShip], + [dragon.id, boltDragon] ])], ["targaryen", new BetterMap([ - [footman.id, targaryenFoot], + [footman.id, targFoot], [knight.id, targKnight], [dragon.id, targDragon], - [ship.id, targaryenShip] + [ship.id, targShip] ])] ]); diff --git a/agot-bg-game-server/src/client/utils/SfxManager.ts b/agot-bg-game-server/src/client/utils/SfxManager.ts index 2c8784b56..6e4629f1e 100644 --- a/agot-bg-game-server/src/client/utils/SfxManager.ts +++ b/agot-bg-game-server/src/client/utils/SfxManager.ts @@ -105,6 +105,10 @@ class SfxManager { currentlyPlayingNotifications: HTMLAudioElement[] = []; currentlyPlayingSfx: HTMLAudioElement[] = []; + get currentDragonStrengthIsLessOrEqualThanTwo(): boolean { + return (this.gameClient.entireGame?.ingameGameState?.game.currentDragonStrength ?? -1) <= 2; + } + constructor(private gameClient: GameClient) {} notificationVolumeChanged(newVal: number): void { @@ -167,72 +171,12 @@ class SfxManager { this.fadeOutMusic(4000, 40); } - private fadeOutMusic(fadeOutDuration: number, fadeOutSteps: number): void { - if (this.currentlyPlayingMusic.length === 0) { - return; - } - - const fadeOutInterval = fadeOutDuration / fadeOutSteps; - const volumeStep = this.gameClient.musicVolume / fadeOutSteps; - - let interval = window.setInterval(() => { - if (this.currentlyPlayingMusic.length === 0 && interval > 0) { - clearInterval(interval); - interval = 0; - return; - } - - this.currentlyPlayingMusic.forEach(audio => { - if (audio.volume - volumeStep > 0) { - audio.volume -= volumeStep; - } else { - audio.volume = 0; - if (!audio.paused) { - audio.pause(); - audio.currentTime = 0; - } - audio.dispatchEvent(new Event('ended')); - } - }); - }, fadeOutInterval); - } - - private fadeOutSfx(fadeOutDuration: number, fadeOutSteps: number): void { - if (this.currentlyPlayingSfx.length === 0) { - return; - } - - const fadeOutInterval = fadeOutDuration / fadeOutSteps; - const volumeStep = this.gameClient.sfxVolume / fadeOutSteps; - - let interval = window.setInterval(() => { - if (this.currentlyPlayingSfx.length === 0 && interval > 0) { - clearInterval(interval); - interval = 0; - return; - } - - this.currentlyPlayingSfx.forEach(audio => { - if (audio.volume - volumeStep > 0) { - audio.volume -= volumeStep; - } else { - audio.volume = 0; - if (!audio.paused) { - audio.pause(); - audio.currentTime = 0; - } - audio.dispatchEvent(new Event('ended')); - } - }); - }, fadeOutInterval); - } - playNotificationSound(): Promise { if (this.gameClient.muted) { return Promise.resolve(); } - return this.playNotification(notificationSound, this.gameClient.notificationsVolume); + return this.playNotification(notificationSound); } playVoteNotificationSound(): Promise { @@ -240,7 +184,7 @@ class SfxManager { return Promise.resolve(); } - return this.playNotification(voteSound, this.gameClient.notificationsVolume); + return this.playNotification(voteSound); } playNewMessageReceivedSound(): Promise { @@ -248,7 +192,7 @@ class SfxManager { return Promise.resolve(); } - return this.playNotification(ravenCallSound, this.gameClient.notificationsVolume); + return this.playNotification(ravenCallSound); } playGotTheme(): Promise { @@ -256,7 +200,7 @@ class SfxManager { return Promise.resolve(); } - return this.playMusic(introSound, this.gameClient.musicVolume); + return this.playMusic(introSound); } playCombatSound(attackerId?: string): Promise { @@ -265,7 +209,7 @@ class SfxManager { } const sound = attackerId ? houseThemes.tryGet(attackerId, combatSound) : combatSound; - return this.playMusic(sound, this.gameClient.musicVolume); + return this.playMusic(sound); } playSoundWhenClickingMarchOrder(region: Region): Promise { @@ -280,7 +224,7 @@ class SfxManager { const hasSiegeEngines = army.some(u => u.type == siegeEngine); const hasDragons = army.some(u => u.type == dragon); - const files = hasDragons && (this.gameClient.entireGame?.ingameGameState?.game.currentDragonStrength ?? -1) <= 2 + const files = hasDragons && this.currentDragonStrengthIsLessOrEqualThanTwo ? soundsForSmallDragons : hasDragons ? soundsForBigDragons @@ -292,7 +236,7 @@ class SfxManager { ? soundsForShips : soundsForFootmenOnly; - return this.playRandomEffect(files, this.gameClient.sfxVolume, true); + return this.playRandomEffect(files, true); } playSoundForLogEvent(log: GameLogData): Promise { @@ -303,14 +247,14 @@ class SfxManager { switch(log.type) { case "doran-martell-asos-used": case "doran-used": - this.playEffect(hystericalLaughSound, this.gameClient.musicVolume, false); + this.playEffect(hystericalLaughSound, false); break; case "killed-after-combat": { const units = log.killed.map(ut => unitTypes.get(ut)); if (units.includes(dragon)) { - return this.playRandomEffect(soundsWhenDragonsAreDestroyed, this.gameClient.musicVolume, false); + return this.playRandomEffect(soundsWhenDragonsAreDestroyed, false); } else { - return this.playRandomEffect(soundsWhenUnitsAreDestroyedBySwords, this.gameClient.musicVolume, false); + return this.playRandomEffect(soundsWhenUnitsAreDestroyedBySwords, false); } break; } @@ -318,56 +262,124 @@ class SfxManager { const killed = _.concat(log.killedBecauseCantRetreat, log.killedBecauseWounded); const units = killed.map(ut => unitTypes.get(ut)); if (units.includes(dragon)) { - return this.playRandomEffect(soundsWhenDragonsAreDestroyed, this.gameClient.musicVolume, false); + return this.playRandomEffect(soundsWhenDragonsAreDestroyed, false); } break; } case "retreat-casualties-suffered": { const units = log.units.map(ut => unitTypes.get(ut)); if (units.includes(dragon)) { - return this.playRandomEffect(soundsWhenDragonsAreDestroyed, this.gameClient.musicVolume, false); + return this.playRandomEffect(soundsWhenDragonsAreDestroyed, false); } break; } + case "last-land-unit-transformed-to-dragon": { + this.playRandomEffect( + this.currentDragonStrengthIsLessOrEqualThanTwo + ? soundsForSmallDragons + : soundsForBigDragons + , false); + break; + } } return Promise.resolve(); } - private playRandomEffect(files: string[], volume: number, withCurrentPlayingCheck: boolean): Promise { + private playRandomEffect(files: string[], withCurrentPlayingCheck: boolean): Promise { const randomFile = pickRandom(files); if (!randomFile) { return Promise.resolve(); } - return this.playEffect(randomFile, volume, withCurrentPlayingCheck); + return this.playEffect(randomFile, withCurrentPlayingCheck); } - private playEffect(file: string, volume: number, withCurrentPlayingCheck: boolean): Promise { + private playEffect(file: string, withCurrentPlayingCheck: boolean): Promise { if (withCurrentPlayingCheck && this.currentlyPlayingSfx.length > 0) { return Promise.resolve(); } const audio = new Audio(file); this.currentlyPlayingSfx.push(audio); audio.onended = () => _.pull(this.currentlyPlayingSfx, audio); - audio.volume = volume; + audio.volume = this.gameClient.sfxVolume; return audio.play(); } - private playMusic(file: string, volume: number): Promise { + private playMusic(file: string): Promise { const audio = new Audio(file); this.currentlyPlayingMusic.push(audio); audio.onended = () => _.pull(this.currentlyPlayingMusic, audio); - audio.volume = volume; + audio.volume = this.gameClient.musicVolume; return audio.play(); } - private playNotification(file: string, volume: number): Promise { + private playNotification(file: string): Promise { const audio = new Audio(file); this.currentlyPlayingNotifications.push(audio); audio.onended = () => _.pull(this.currentlyPlayingNotifications, audio); - audio.volume = volume; + audio.volume = this.gameClient.notificationsVolume; return audio.play(); } + + private fadeOutMusic(fadeOutDuration: number, fadeOutSteps: number): void { + if (this.currentlyPlayingMusic.length === 0) { + return; + } + + const fadeOutInterval = fadeOutDuration / fadeOutSteps; + const volumeStep = this.gameClient.musicVolume / fadeOutSteps; + + let interval = window.setInterval(() => { + if (this.currentlyPlayingMusic.length === 0 && interval > 0) { + clearInterval(interval); + interval = 0; + return; + } + + this.currentlyPlayingMusic.forEach(audio => { + if (audio.volume - volumeStep > 0) { + audio.volume -= volumeStep; + } else { + audio.volume = 0; + if (!audio.paused) { + audio.pause(); + audio.currentTime = 0; + } + audio.dispatchEvent(new Event('ended')); + } + }); + }, fadeOutInterval); + } + + private fadeOutSfx(fadeOutDuration: number, fadeOutSteps: number): void { + if (this.currentlyPlayingSfx.length === 0) { + return; + } + + const fadeOutInterval = fadeOutDuration / fadeOutSteps; + const volumeStep = this.gameClient.sfxVolume / fadeOutSteps; + + let interval = window.setInterval(() => { + if (this.currentlyPlayingSfx.length === 0 && interval > 0) { + clearInterval(interval); + interval = 0; + return; + } + + this.currentlyPlayingSfx.forEach(audio => { + if (audio.volume - volumeStep > 0) { + audio.volume -= volumeStep; + } else { + audio.volume = 0; + if (!audio.paused) { + audio.pause(); + audio.currentTime = 0; + } + audio.dispatchEvent(new Event('ended')); + } + }); + }, fadeOutInterval); + } } export default SfxManager; diff --git a/agot-bg-game-server/src/common/EntireGame.ts b/agot-bg-game-server/src/common/EntireGame.ts index 3f77b275d..748a576a4 100644 --- a/agot-bg-game-server/src/common/EntireGame.ts +++ b/agot-bg-game-server/src/common/EntireGame.ts @@ -54,7 +54,7 @@ export default class EntireGame extends GameState void; @@ -820,4 +820,6 @@ export interface GameSettings { fixedClock: boolean; holdVictoryPointsUntilEndOfRound: boolean; fogOfWar: boolean; + dragonWar: boolean; + dragonRevenge: boolean; } diff --git a/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts index 794c5ed17..f2f80a103 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts @@ -11,7 +11,7 @@ import ActionGameState, {SerializedActionGameState} from "./action-game-state/Ac import Order from "./game-data-structure/Order"; import Game, {SerializedGame} from "./game-data-structure/Game"; import WesterosGameState, {SerializedWesterosGameState} from "./westeros-game-state/WesterosGameState"; -import createGame, { applyChangesForDanceWithMotherOfDragons } from "./game-data-structure/createGame"; +import createGame, { applyChangesForDanceWithMotherOfDragons, applyChangesForDragonWar } from "./game-data-structure/createGame"; import BetterMap from "../../utils/BetterMap"; import House from "./game-data-structure/House"; import Unit from "./game-data-structure/Unit"; @@ -46,6 +46,8 @@ import orders from "./game-data-structure/orders"; import { OrderOnMapProperties, UnitOnMapProperties } from "../../client/MapControls"; import houseCardAbilities from "./game-data-structure/house-card/houseCardAbilities"; import SnrError from "../../utils/snrError"; +import { TakeOverPort, findOrphanedShipsAndDestroyThem, isTakeControlOfEnemyPortRequired } from "./port-helper/PortHelper"; +import { dragon } from "./game-data-structure/unitTypes"; export const NOTE_MAX_LENGTH = 5000; @@ -54,6 +56,11 @@ export const enum ReplacementReason { CLOCK_TIMEOUT } +export interface UnitLossConsequence { + victoryConditionsFulfilled?: true; + takeOverPort?: TakeOverPort; +} + export default class IngameGameState extends GameState< EntireGame, WesterosGameState | PlanningGameState | ActionGameState | CancelledGameState | GameEndedGameState @@ -122,6 +129,12 @@ export default class IngameGameState extends GameState< return this.entireGame.gameSettings.fogOfWar; } + get isDragonGame(): boolean { + return this.entireGame.gameSettings.playerCount == 8 || + this.entireGame.gameSettings.dragonWar || + this.entireGame.gameSettings.dragonRevenge; + } + constructor(entireGame: EntireGame) { super(entireGame); @@ -146,6 +159,10 @@ export default class IngameGameState extends GameState< applyChangesForDanceWithMotherOfDragons(this); } + if (this.entireGame.gameSettings.dragonWar) { + applyChangesForDragonWar(this); + } + if (this.entireGame.gameSettings.onlyLive) { this.players.values.forEach(p => p.liveClockData = { remainingSeconds: this.entireGame.gameSettings.initialLiveClock * 60, @@ -1086,6 +1103,65 @@ export default class IngameGameState extends GameState< } } + // returns true, if game is over and calling state needs to exit from processing + processPossibleConsequencesOfUnitLoss(): UnitLossConsequence { + // Check for last unit in dragon revenge + if (this.entireGame.gameSettings.dragonRevenge) { + for (const house of this.game.houses.values) { + const noCastles = this.world.regions.values.filter(r => r.castleLevel > 0 && r.getController() == house).length == 0; + + if (noCastles) { + const nonDragonLandUnits = this.world.getUnitsOfHouse(house).filter(u => u.type.id != "ship" && u.type.id != "dragon"); + if (nonDragonLandUnits.length == 1) { + const unit = nonDragonLandUnits[0]; + this.log({ + type: "last-land-unit-transformed-to-dragon", + house: house.id, + transformedUnitType: unit.type.id, + region: unit.region.id + }, true); + this.transformUnits(unit.region, [unit], dragon); + } + } + } + } + + // Restore Pentos garrison + this.world.regionsThatRegainGarrison.forEach(staticRegion => { + const region = this.world.getRegion(staticRegion); + if (region.getController() == region.superControlPowerToken && region.garrison != staticRegion.startingGarrison) { + region.garrison = staticRegion.startingGarrison; + this.sendMessageToUsersWhoCanSeeRegion({ + type: "change-garrison", + region: region.id, + newGarrison: region.garrison + }, region); + this.log({ + type: "garrison-returned", + region: region.id, + strength: region.garrison + }); + } + }); + + // Destroy orphaned ships in ports + findOrphanedShipsAndDestroyThem(this, this.actionState); + + // Check for Port take over + const takeOverRequired = isTakeControlOfEnemyPortRequired(this); + if (takeOverRequired) { + return { takeOverPort: takeOverRequired }; + } + + // A unit loss may result in a win, if the lost unit + // was located in an enemy capital => check winning conditions + if (this.checkVictoryConditions()) { + return { victoryConditionsFulfilled: true }; + } + + return { }; + } + onServerMessage(message: ServerMessage): void { if (message.type == "supply-adjusted") { const supplies: [House, number][] = message.supplies.map(([houseId, supply]) => [this.game.houses.get(houseId), supply]); diff --git a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-consolidate-power-game-state/execute-loan-game-state/ExecuteLoanGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-consolidate-power-game-state/execute-loan-game-state/ExecuteLoanGameState.ts index bf6ba99eb..96b98b2c0 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-consolidate-power-game-state/execute-loan-game-state/ExecuteLoanGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-consolidate-power-game-state/execute-loan-game-state/ExecuteLoanGameState.ts @@ -17,7 +17,7 @@ import LoyalMaesterGameState, { SerializedLoyalMaesterGameState } from "./loyal- import MasterAtArmsGameState, { SerializedMasterAtArmsGameState } from "./master-at-arms-game-state/MasterAtArmsGameState"; import SavvyStewardGameState, { SerializedSavvyStewardGameState } from "./savvy-steward-game-state/SavvyStewardGameState"; import SpymasterGameState, { SerializedSpymasterGameState } from "./spymaster-game-state/SpymasterGameState"; -import { findOrphanedShipsAndDestroyThem, isTakeControlOfEnemyPortGameStateRequired } from "../../../../../common/ingame-game-state/port-helper/PortHelper"; +import { isTakeControlOfEnemyPortRequired } from "../../../../../common/ingame-game-state/port-helper/PortHelper"; import TakeControlOfEnemyPortGameState, { SerializedTakeControlOfEnemyPortGameState } from "../../../take-control-of-enemy-port-game-state/TakeControlOfEnemyPortGameState"; import ActionGameState from "../../ActionGameState"; @@ -86,19 +86,12 @@ export default class ExecuteLoanGameState extends GameState { - const region = this.world.getRegion(staticRegion); - if (region.getController() == region.superControlPowerToken && region.garrison != staticRegion.startingGarrison) { - region.garrison = staticRegion.startingGarrison; - this.ingame.sendMessageToUsersWhoCanSeeRegion({ - type: "change-garrison", - region: region.id, - newGarrison: region.garrison - }, region); - this.ingameGameState.log({ - type: "garrison-returned", - region: region.id, - strength: region.garrison - }); - } - }) - // Gain Loyalty tokens this.ingameGameState.gainLoyaltyTokens(); - // ... destroy orphaned ships (e.g. caused by Arianne) - findOrphanedShipsAndDestroyThem(this.ingameGameState, this.actionGameState); - // ... check if ships can be converted - const analyzePortResult = isTakeControlOfEnemyPortGameStateRequired(this.ingameGameState); - if (analyzePortResult) { - this.setChildGameState(new TakeControlOfEnemyPortGameState(this)).firstStart(analyzePortResult.port, analyzePortResult.newController, house); + // Handle possible unit loss consequences (checks winning condition) + const consequence = this.ingame.processPossibleConsequencesOfUnitLoss(); + if (consequence.victoryConditionsFulfilled) { return; - } - - // ... check victory conditions - if (this.ingameGameState.checkVictoryConditions()) { + } else if (consequence.takeOverPort) { + this.setChildGameState(new TakeControlOfEnemyPortGameState(this)) + .firstStart(consequence.takeOverPort.port, consequence.takeOverPort.newController, house); return; } - // ... check if an other march order can be resolved + // Find next march order to resolve this.proceedNextResolveSingleMarchOrder(); } diff --git a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/resolve-single-march-order-game-state/ResolveSingleMarchOrderGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/resolve-single-march-order-game-state/ResolveSingleMarchOrderGameState.ts index 09f9f029a..8db363692 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/resolve-single-march-order-game-state/ResolveSingleMarchOrderGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/resolve-single-march-order-game-state/ResolveSingleMarchOrderGameState.ts @@ -144,10 +144,10 @@ export default class ResolveSingleMarchOrderGameState extends GameState (_.last(this.dragonStrengthTokens) as number)) { + if (this.turn > (_.last(dragonTokens) as number)) { return 5; } // If a dragon strength token has been removed from the round track // the initial value is 1 instead of 0 - const result = this.ingame.entireGame.isDanceWithMotherOfDragons - ? this.removedDragonStrengthToken == 0 + const result = isADwMoD + ? removedToken == 0 ? 1 : 2 - : this.removedDragonStrengthToken == 0 + : removedToken == 0 ? 0 : 1; - for (let i=0; i this.turn) { + if (dragonTokens[i] > this.turn) { // If the current token value is greater than current round we add the current index, // which is the value of the previous dragon strength token on the round track (0-based index) return result + i; diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/GameLog.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/GameLog.ts index 071f611a6..c7888e76f 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/GameLog.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/GameLog.ts @@ -50,7 +50,7 @@ export type GameLogData = TurnBegin | SupportDeclared | SupportRefused | Attack | BalonGreyjoyASoSPowerTokensGained | MaceTyrellASoSOrderPlaced | BranStarkUsed | CerseiLannisterASoSPowerTokensDiscarded | DoranMartellASoSUsed | MelisandreOfAsshaiPowerTokensGained | SalladharSaanASoSPowerTokensChanged | SerDavosSeaworthASoSFortificationGained | CasualtiesPrevented | SerIlynPayneASoSCasualtySuffered | StannisBaratheonASoSUsed | AeronDamphairHouseCardChanged | ControlPowerTokenRemoved - | GamePaused | GameResumed | SupportAttackAgainstNeutralForce | HousesSwapped | NoLoyaltyTokenAvailable; + | GamePaused | GameResumed | SupportAttackAgainstNeutralForce | HousesSwapped | NoLoyaltyTokenAvailable | LastLandUnitTransformedToDragon; export enum PlayerActionType { ORDERS_PLACED, @@ -716,6 +716,7 @@ interface JaqenHGharUsed { affectedHouse: string; oldHouseCard: string; newHouseCard: string; + usedById: string; } interface JonConningtonUsed { @@ -1173,4 +1174,11 @@ export interface WesterosDeck4Skipped { export interface NoLoyaltyTokenAvailable { type: "no-loyalty-token-available"; region: string; +} + +export interface LastLandUnitTransformedToDragon { + type: "last-land-unit-transformed-to-dragon"; + house: string; + transformedUnitType: string; + region: string; } \ No newline at end of file diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/World.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/World.ts index 872e1de0a..6b107dc14 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/World.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/World.ts @@ -24,7 +24,7 @@ export default class World { return getStaticWorld(this.settings).staticBorders; } - get regionsWhichCanRegainGarrison(): StaticRegion[] { + get regionsThatRegainGarrison(): StaticRegion[] { return getStaticWorld(this.settings).staticRegions.values.filter(region => region.canRegainGarrison); } @@ -239,9 +239,12 @@ export default class World { return region.units.get(unitId); } - getCapitalOfHouse(house: House): Region | null { + getCapitalOfHouse(house: House): Region { const capital = this.regions.values.filter(r => r.superControlPowerToken == house); - return capital.length == 1 ? capital[0] : null; + if (capital.length != 1) { + throw new Error("Every house must have exactly one capital"); + } + return capital[0]; } getSnapshot(): RegionSnapshot[] { diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/createGame.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/createGame.ts index 07f50e884..ab443317a 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/createGame.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/createGame.ts @@ -5,7 +5,8 @@ import Region from "./Region"; import World from "./World"; import WesterosCard from "./westeros-card/WesterosCard"; import {fireMadeFlesh, westerosCardTypes} from "./westeros-card/westerosCardTypes"; -import unitTypes, { ship } from "./unitTypes"; +import unitTypes from "./unitTypes"; +import Unit from "./Unit"; import Game from "./Game"; import WildlingCard from "./wildling-card/WildlingCard"; import wildlingCardTypes from "./wildling-card/wildlingCardTypes"; @@ -20,7 +21,7 @@ import IronBank from "./IronBank"; import loanCardTypes from "./loan-card/loanCardTypes"; import LoanCard from "./loan-card/LoanCard"; import { specialObjectiveCards } from "./static-data-structure/objectiveCards"; -import popRandom from "../../../utils/popRandom"; +import popRandom, { pickRandom } from "../../../utils/popRandom"; import { HouseCardDecks } from "../../../common/EntireGame"; interface HouseCardContainer { @@ -103,7 +104,7 @@ function getTrackWithAdjustedVassalPositions(track: House[], playerHouses: strin return track; } -function createHouseCard(id: string, houseCardData: HouseCardData, houseId: string): HouseCard { +export function createHouseCard(id: string, houseCardData: HouseCardData, houseId: string): HouseCard { return new HouseCard( id, houseCardData.name, @@ -523,7 +524,7 @@ export default function createGame(ingame: IngameGameState, housesToCreate: stri const redwyneStraights = game.world.regions.get("redwyne-straights"); const house = redwyneStraights.units.size > 0 ? redwyneStraights.units.values[0].allegiance : null; if (house && playerHouses.includes(house.id)) { - const newShip = game.createUnit(redwyneStraights, ship, house); + const newShip = game.createUnit(redwyneStraights, unitTypes.get("ship"), house); redwyneStraights.units.set(newShip.id, newShip); } } @@ -591,4 +592,64 @@ export function applyChangesForDanceWithMotherOfDragons(ingame: IngameGameState) while (_.some(_.take(game.westerosDecks[3], 2), wc => wc.type == fireMadeFlesh)) { shuffleInPlace(game.westerosDecks[3]); } +} + +export function applyChangesForDragonWar(ingame: IngameGameState): void { + const game = ingame.game; + const houses = _.without(game.houses.values, game.targaryen) as House[]; + + for (const house of houses) { + let unit = findUnitToReplace(ingame, house, "knight"); + + if (!unit) { + unit = findUnitToReplace(ingame, house, "footman"); + + if (!unit) { + throw new Error("Every house starts with at least one footman"); + } + } + + const region = unit.region; + region.units.delete(unit.id); + const newDragon = game.createUnit(region, unitTypes.get("dragon"), house); + region.units.set(newDragon.id, newDragon); + } + + if (game.dragonStrengthTokens.length == 0) { + game.dragonStrengthTokens = ingame.entireGame.isDanceWithDragons + ? [2, 4, 5, 6] + : [2, 4, 6, 8, 10]; + } + + nervHouseCard(game, "balon-greyjoy", "jaqen-h-ghar"); + nervHouseCard(game, "aeron-damphair-dwd", "aeron-damphair"); + + game.world.regions.values.filter(r => r.superControlPowerToken != null && r.garrison == 4).forEach(r => r.garrison = 6); + game.world.regions.values.filter(r => r.superControlPowerToken != null && r.garrison == 2).forEach(r => r.garrison = 4); +} + +function findUnitToReplace(ingame: IngameGameState, house: House, unitType: string): Unit | null { + const capital = ingame.world.getCapitalOfHouse(house); + const unit = _.first(capital.units.values.filter(u => u.type.id == unitType)); + + // Return either the first unit in the capital or any random unit on the board. + return unit + ? unit + : pickRandom(ingame.world.getUnitsOfHouse(house).filter(u => u.type.id == unitType)); +} + +function nervHouseCard(game: Game, hcId: string, newAbilityId: string): void { + const houseCard = game.draftableHouseCards.has(hcId) + ? game.draftableHouseCards.get(hcId) + : _.flatMap(game.houses.values.map(h => h.houseCards.values)).find(hc => hc.id == hcId); + + if (houseCard) { + houseCard.id = hcId + "-nerved"; + houseCard.ability = houseCardAbilities.get(newAbilityId); + + if (game.draftableHouseCards.has(hcId)) { + game.draftableHouseCards.delete(hcId); + game.draftableHouseCards.set(hcId + "-nerved", houseCard); + } + } } \ No newline at end of file diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/AlayneStoneHouseCardAbility.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/AlayneStoneHouseCardAbility.ts index d92b7943e..74ba7360f 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/AlayneStoneHouseCardAbility.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/AlayneStoneHouseCardAbility.ts @@ -9,11 +9,11 @@ export default class AlayneStoneHouseCardAbility extends HouseCardAbility { afterWinnerDetermination(afterWinnerDetermination: AfterWinnerDeterminationGameState, house: House, _houseCard: HouseCard): void { const enemy = afterWinnerDetermination.combatGameState.getEnemy(house); if (afterWinnerDetermination.postCombatGameState.winner == house && !afterWinnerDetermination.combatGameState.ingameGameState.isVassalHouse(enemy)) { - const capitalOfHouse = afterWinnerDetermination.combatGameState.world.getCapitalOfHouse(house); + const capital = afterWinnerDetermination.combatGameState.world.getCapitalOfHouse(house); // getController() will return the wrong house when the player regains their capital with the current fight // as the attacking army hasn't moved into the attacked region yet. Therefore we need an extra handling. - if ((capitalOfHouse && capitalOfHouse.getController() == house) || - (afterWinnerDetermination.postCombatGameState.combat.defendingRegion == capitalOfHouse)) { + if (capital.getController() == house || + (afterWinnerDetermination.postCombatGameState.combat.defendingRegion == capital)) { afterWinnerDetermination.childGameState .setChildGameState(new AlayneStoneAbilityGameState(afterWinnerDetermination.childGameState)) .firstStart(house); diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/JaqenHGharHouseCardAbility.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/JaqenHGharHouseCardAbility.ts index 9fd0fe8ce..45ce7e968 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/JaqenHGharHouseCardAbility.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/JaqenHGharHouseCardAbility.ts @@ -7,7 +7,7 @@ import shuffleInPlace from "../../../../utils/shuffleInPlace"; import _ from "lodash"; export default class JaqenHGharHouseCardAbility extends HouseCardAbility { - cancel(cancelResolutionState: CancelHouseCardAbilitiesGameState, house: House, _houseCard: HouseCard): void { + cancel(cancelResolutionState: CancelHouseCardAbilitiesGameState, house: House, houseCard: HouseCard): void { const combat = cancelResolutionState.combatGameState; const enemy = combat.getEnemy(house); @@ -19,7 +19,8 @@ export default class JaqenHGharHouseCardAbility extends HouseCardAbility { house: house.id, affectedHouse: enemy.id, oldHouseCard: (combat.houseCombatDatas.get(enemy).houseCard as HouseCard).id, - newHouseCard: newHouseCard.id + newHouseCard: newHouseCard.id, + usedById: houseCard.id }); // Change the new house card to the chosen one diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/LittlefingerHouseCardAbility.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/LittlefingerHouseCardAbility.ts index fbcec1e7f..f6db13333 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/LittlefingerHouseCardAbility.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/house-card/LittlefingerHouseCardAbility.ts @@ -6,8 +6,8 @@ import House from "../House"; export default class LittlefingerHouseCardAbility extends HouseCardAbility { afterCombat(afterCombat: AfterCombatHouseCardAbilitiesGameState, house: House, _houseCard: HouseCard): void { - const capitalOfHouse = afterCombat.combatGameState.world.getCapitalOfHouse(house); - if (capitalOfHouse && capitalOfHouse.getController() == house) { + const capital = afterCombat.combatGameState.world.getCapitalOfHouse(house); + if (capital.getController() == house) { const enemy = afterCombat.combatGameState.getEnemy(house); const enemyHouseCard = afterCombat.combatGameState.houseCombatDatas.get(enemy).houseCard; const gainedPowerTokens = afterCombat.combatGameState.ingameGameState.changePowerTokens(house, enemyHouseCard ? enemyHouseCard.combatStrength * 2 : 0); diff --git a/agot-bg-game-server/src/common/ingame-game-state/pay-debts-game-state/PayDebtsGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/pay-debts-game-state/PayDebtsGameState.ts index 23f236521..8c7089f91 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/pay-debts-game-state/PayDebtsGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/pay-debts-game-state/PayDebtsGameState.ts @@ -6,7 +6,6 @@ import {ServerMessage} from "../../../messages/ServerMessage"; import IngameGameState from "../IngameGameState"; import BetterMap from "../../../utils/BetterMap"; import ResolveSinglePayDebtGameState, { SerializedResolveSinglePayDebtGameState } from "./resolve-single-pay-debt-game-state/ResolveSinglePayDebtGameState"; -import { findOrphanedShipsAndDestroyThem, isTakeControlOfEnemyPortGameStateRequired } from "../port-helper/PortHelper"; import TakeControlOfEnemyPortGameState, { SerializedTakeControlOfEnemyPortGameState } from "../take-control-of-enemy-port-game-state/TakeControlOfEnemyPortGameState"; import ActionGameState from "../action-game-state/ActionGameState"; @@ -27,11 +26,12 @@ export default class PayDebtsGameState extends GameState r.type == port && r.units.size > 0 @@ -85,7 +85,7 @@ export function isTakeControlOfEnemyPortGameStateRequired(ingame: IngameGameStat throw new Error(`Port with id '${portRegion.id}' contains orphaned ships which should have been removed before!`); } -export interface TakeControlOfEnemyPortResult { +export interface TakeOverPort { port: Region; newController: House; } \ No newline at end of file diff --git a/agot-bg-game-server/src/common/ingame-game-state/westeros-game-state/wildlings-attack-game-state/WildlingCardEffectInTurnOrderGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/westeros-game-state/wildlings-attack-game-state/WildlingCardEffectInTurnOrderGameState.ts index 1c98c194d..2839a0d92 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/westeros-game-state/wildlings-attack-game-state/WildlingCardEffectInTurnOrderGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/westeros-game-state/wildlings-attack-game-state/WildlingCardEffectInTurnOrderGameState.ts @@ -2,7 +2,7 @@ import GameState from "../../../GameState"; import WildlingsAttackGameState from "./WildlingsAttackGameState"; import Game from "../../game-data-structure/Game"; import House from "../../game-data-structure/House"; -import { findOrphanedShipsAndDestroyThem, isTakeControlOfEnemyPortGameStateRequired, TakeControlOfEnemyPortResult } from "../../port-helper/PortHelper"; +import { TakeOverPort } from "../../port-helper/PortHelper"; import IngameGameState from "../../IngameGameState"; /** @@ -40,13 +40,11 @@ export default abstract class WildlingCardEffectInTurnOrderGameState { settings.randomVassalAssignment = false; } + if (settings.dragonWar && !this.settings.dragonWar) { + settings.dragonRevenge = true; + } + const hideOrRevealUserNames = settings.faceless != this.settings.faceless; this.entireGame.gameSettings = settings; diff --git a/agot-bg-game-server/src/server/serializedGameMigrations.ts b/agot-bg-game-server/src/server/serializedGameMigrations.ts index 1dfa146f6..387b119ce 100644 --- a/agot-bg-game-server/src/server/serializedGameMigrations.ts +++ b/agot-bg-game-server/src/server/serializedGameMigrations.ts @@ -2210,6 +2210,18 @@ const serializedGameMigrations: {version: string; migrate: (serializeGamed: any) serializedGame.gameSettings.selectedDraftDecks = 7; // Currently All: Base + Dwd / FfC + ASoS return serializedGame; } + }, + { + version: "115", + migrate: (serializedGame: any) => { + if (serializedGame.childGameState.type == "ingame") { + const ingame = serializedGame.childGameState; + + const jaqenLogs = ingame.gameLogManager.logs.filter((l: any) => l.data.type == "jaqen-h-ghar-house-card-replaced"); + jaqenLogs.forEach((l: any) => l.data.usedById = "jaqen-h-ghar"); + } + return serializedGame; + } } ];