Skip to content

Commit

Permalink
Add custom option "Dragon revenge" and refactor handling after unit loss
Browse files Browse the repository at this point in the history
  • Loading branch information
gereon77 committed Jun 12, 2024
1 parent 017dafa commit a61ac9e
Show file tree
Hide file tree
Showing 20 changed files with 285 additions and 171 deletions.
28 changes: 27 additions & 1 deletion agot-bg-game-server/src/client/EntireGameComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,33 @@ export default class EntireGameComponent extends Component<EntireGameComponentPr
<h4><Badge variant="primary"><FontAwesomeIcon icon={faTriangleExclamation} /></Badge></h4>
</OverlayTrigger>
</Col>}
{this.props.entireGame.gameSettings.fogOfWar &&
{(this.settings.dragonWar || this.settings.dragonRevenge) &&
<Col xs="auto">
<OverlayTrigger
placement="auto"
overlay={
<Tooltip id="dragon-mode-tooltip" className="tooltip-w-100">
<div className="text-center">
{this.settings.dragonWar && <>
<p>
<h6>Dragon war</h6>
<small>Balon Greyjoy and Aeron Damphair (DwD) have been nerfed!</small>
</p>
</>}
{this.settings.dragonRevenge && <>
<p>
<h6>Dragon revenge</h6>
<small>The last remaining non-dragon land unit will transform into a dragon!</small>
</p>
</>}
</div>
</Tooltip>}
popperConfig={{ modifiers: [preventOverflow] }}
>
<h4><Badge variant="primary">🐉</Badge></h4>
</OverlayTrigger>
</Col>}
{this.settings.fogOfWar &&
<Col xs="auto">
<h4><Badge variant="warning">BETA</Badge></h4>
</Col>}
Expand Down
9 changes: 9 additions & 0 deletions agot-bg-game-server/src/client/GameLogListComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2072,6 +2072,15 @@ export default class GameLogListComponent extends Component<GameLogListComponent
There was no further loyalty token available that could have been placed in <b>{this.fogOfWar ? fogOfWarPlaceholder : region.name}</b>.
</p>;
}
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 <>
<b>Dragon revenge</b>: The last <b>{unitType.name}</b> of House <b>{house.name}</b> has been transformed into a <b>Dragon</b> in <b>{region.name}</b>.
</>;
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions agot-bg-game-server/src/client/GameSettingsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,21 @@ export default class GameSettingsComponent extends Component<GameSettingsCompone
onChange={() => this.changeGameSettings(() => this.gameSettings.dragonWar = !this.gameSettings.dragonWar)}
/>
</Col>
<Col xs="12">
<FormCheck
id="dragon-revenge-setting"
type="switch"
label={
<OverlayTrigger overlay={
<Tooltip id="dragon-revenge-tooltip">
If a player has only one remaining non-dragon land unit and no more castles, it will turn into a dragon.
</Tooltip>}>
<label htmlFor="dragon-revenge-setting">Dragon revenge</label>
</OverlayTrigger>}
checked={this.gameSettings.dragonRevenge}
onChange={() => this.changeGameSettings(() => this.gameSettings.dragonRevenge = !this.gameSettings.dragonRevenge)}
/>
</Col>
</Col>
{this.gameSettings.draftHouseCards && <Col xs="6" lg="auto" id="draft-decks-settings-col" className="no-gutters">
<Col xs="12">
Expand Down
2 changes: 1 addition & 1 deletion agot-bg-game-server/src/client/IngameComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ export default class IngameComponent extends Component<IngameComponentProps> {
</div>
</div>
</Row>
{(this.gameSettings.playerCount >= 8 || this.gameSettings.dragonWar) && <Row className="mx-0 mt-3">
{this.ingame.isDragonGame && <Row className="mx-0 mt-3">
<OverlayTrigger overlay={this.renderDragonStrengthTooltip()}
placement="auto">
<div>
Expand Down
172 changes: 92 additions & 80 deletions agot-bg-game-server/src/client/utils/SfxManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -167,96 +171,36 @@ 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<void> {
if (this.gameClient.muted) {
return Promise.resolve();
}

return this.playNotification(notificationSound, this.gameClient.notificationsVolume);
return this.playNotification(notificationSound);
}

playVoteNotificationSound(): Promise<void> {
if (this.gameClient.muted) {
return Promise.resolve();
}

return this.playNotification(voteSound, this.gameClient.notificationsVolume);
return this.playNotification(voteSound);
}

playNewMessageReceivedSound(): Promise<void> {
if (this.gameClient.muted) {
return Promise.resolve();
}

return this.playNotification(ravenCallSound, this.gameClient.notificationsVolume);
return this.playNotification(ravenCallSound);
}

playGotTheme(): Promise<void> {
if (this.gameClient.muted) {
return Promise.resolve();
}

return this.playMusic(introSound, this.gameClient.musicVolume);
return this.playMusic(introSound);
}

playCombatSound(attackerId?: string): Promise<void> {
Expand All @@ -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<void> {
Expand All @@ -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
Expand All @@ -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<void> {
Expand All @@ -303,71 +247,139 @@ 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;
}
case "immediatly-killed-after-combat": {
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<void> {
private playRandomEffect(files: string[], withCurrentPlayingCheck: boolean): Promise<void> {
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<void> {
private playEffect(file: string, withCurrentPlayingCheck: boolean): Promise<void> {
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<void> {
private playMusic(file: string): Promise<void> {
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<void> {
private playNotification(file: string): Promise<void> {
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;
3 changes: 2 additions & 1 deletion agot-bg-game-server/src/common/EntireGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default class EntireGame extends GameState<null, LobbyGameState | IngameG
draftHouseCards: false, thematicDraft: false, limitedDraft: false, randomDraft: false, blindDraft: false, selectedDraftDecks: HouseCardDecks.All,
mixedWesterosDeck1: false, cokWesterosPhase: false, fogOfWar: false, victoryPointsCountNeededToWin: 7, loyaltyTokenCountNeededToWin: 7, endless: false, faceless: false,
useVassalPositions: false, precedingMustering: false, randomStartPositions: false, initialLiveClock: 60,
noPrivateChats: false, tournamentMode: false, fixedClock: false, holdVictoryPointsUntilEndOfRound: false, dragonWar: false
noPrivateChats: false, tournamentMode: false, fixedClock: false, holdVictoryPointsUntilEndOfRound: false, dragonWar: false, dragonRevenge: false
};

onSendClientMessage?: (message: ClientMessage) => void;
Expand Down Expand Up @@ -821,4 +821,5 @@ export interface GameSettings {
holdVictoryPointsUntilEndOfRound: boolean;
fogOfWar: boolean;
dragonWar: boolean;
dragonRevenge: boolean;
}
Loading

0 comments on commit a61ac9e

Please sign in to comment.