diff --git a/agot-bg-game-server/src/client/utils/SfxManager.ts b/agot-bg-game-server/src/client/utils/SfxManager.ts
index 2c8784b56..512f4aadf 100644
--- a/agot-bg-game-server/src/client/utils/SfxManager.ts
+++ b/agot-bg-game-server/src/client/utils/SfxManager.ts
@@ -232,7 +232,7 @@ class SfxManager {
return Promise.resolve();
}
- return this.playNotification(notificationSound, this.gameClient.notificationsVolume);
+ return this.playNotification(notificationSound);
}
playVoteNotificationSound(): Promise {
@@ -240,7 +240,7 @@ class SfxManager {
return Promise.resolve();
}
- return this.playNotification(voteSound, this.gameClient.notificationsVolume);
+ return this.playNotification(voteSound);
}
playNewMessageReceivedSound(): Promise {
@@ -248,7 +248,7 @@ class SfxManager {
return Promise.resolve();
}
- return this.playNotification(ravenCallSound, this.gameClient.notificationsVolume);
+ return this.playNotification(ravenCallSound);
}
playGotTheme(): Promise {
@@ -256,7 +256,7 @@ class SfxManager {
return Promise.resolve();
}
- return this.playMusic(introSound, this.gameClient.musicVolume);
+ return this.playMusic(introSound);
}
playCombatSound(attackerId?: string): Promise {
@@ -265,7 +265,11 @@ class SfxManager {
}
const sound = attackerId ? houseThemes.tryGet(attackerId, combatSound) : combatSound;
- return this.playMusic(sound, this.gameClient.musicVolume);
+ return this.playMusic(sound);
+ }
+
+ get currentDragonStrengthIsLessOrEqualThanTwo(): boolean {
+ return (this.gameClient.entireGame?.ingameGameState?.game.currentDragonStrength ?? -1) <= 2;
}
playSoundWhenClickingMarchOrder(region: Region): Promise {
@@ -280,7 +284,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 +296,7 @@ class SfxManager {
? soundsForShips
: soundsForFootmenOnly;
- return this.playRandomEffect(files, this.gameClient.sfxVolume, true);
+ return this.playRandomEffect(files, true);
}
playSoundForLogEvent(log: GameLogData): Promise {
@@ -303,14 +307,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,54 +322,62 @@ 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();
}
}
diff --git a/agot-bg-game-server/src/common/EntireGame.ts b/agot-bg-game-server/src/common/EntireGame.ts
index 558be0659..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;
@@ -821,4 +821,5 @@ export interface GameSettings {
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 0c2a97ea6..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
@@ -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);
@@ -1090,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 region.canRegainGarrison);
}
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;