Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conditional gain on piloting abilities #781

Merged
merged 1 commit into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions server/game/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { SpaceArenaZone } from './core/zone/SpaceArenaZone';
import type { CaptureZone } from './core/zone/CaptureZone';
import type { IUnitCard } from './core/card/propertyMixins/UnitProperties';
import type { DelayedEffectType } from './gameSystems/DelayedEffectSystem';
import type { IUpgradeCard } from './core/card/CardInterfaces';

// allow block comments without spaces so we can have compact jsdoc descriptions in this file
/* eslint @stylistic/lines-around-comment: off */
Expand Down Expand Up @@ -234,6 +235,12 @@ export type IIfYouDoAbilityPropsWithSystems<TContext extends AbilityContext> = I
ifYouDoCondition?: (context?: TContext) => boolean;
};

export interface IGainCondition<TSource extends IUpgradeCard> {
gainCondition?: (context: AbilityContext<TSource>) => boolean;
}

export type IKeywordPropertiesWithGainCondition<TSource extends IUpgradeCard> = IKeywordProperties & IGainCondition<TSource>;

export interface IClientUIProperties {
lastPlayedCard?: ISetId;
}
Expand Down
25 changes: 4 additions & 21 deletions server/game/core/card/UpgradeCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,20 @@ import * as Contract from '../utils/Contract';
import type { MoveZoneDestination } from '../Constants';
import { AbilityType, CardType, ZoneName, WildcardRelativePlayer } from '../Constants';
import { PlayUpgradeAction } from '../../actions/PlayUpgradeAction';
import type { IActionAbilityProps, ITriggeredAbilityBaseProps, IConstantAbilityProps, IKeywordProperties, ITriggeredAbilityProps } from '../../Interfaces';
import type { IActionAbilityProps, ITriggeredAbilityBaseProps, IConstantAbilityProps, ITriggeredAbilityProps, IGainCondition, IKeywordPropertiesWithGainCondition } from '../../Interfaces';
import type { Card } from './Card';
import OngoingEffectLibrary from '../../ongoingEffects/OngoingEffectLibrary';
import { WithStandardAbilitySetup } from './propertyMixins/StandardAbilitySetup';
import type { AbilityContext } from '../ability/AbilityContext';
import type { IPlayCardActionProperties } from '../ability/PlayCardAction';
import type { IUnitCard } from './propertyMixins/UnitProperties';
import type { IPlayableCard } from './baseClasses/PlayableOrDeployableCard';
import type { ICardCanChangeControllers, IUpgradeCard } from './CardInterfaces';

interface IGainCondition<TSource extends UpgradeCard> {
gainCondition?: (context: AbilityContext<TSource>) => boolean;
}
type ITriggeredAbilityPropsWithGainCondition<TSource extends IUpgradeCard, TTarget extends Card> = ITriggeredAbilityProps<TTarget> & IGainCondition<TSource>;

type ITriggeredAbilityPropsWithGainCondition<TSource extends UpgradeCard, TTarget extends Card> = ITriggeredAbilityProps<TTarget> & IGainCondition<TSource>;
type ITriggeredAbilityBasePropsWithGainCondition<TSource extends IUpgradeCard, TTarget extends Card> = ITriggeredAbilityBaseProps<TTarget> & IGainCondition<TSource>;

type ITriggeredAbilityBasePropsWithGainCondition<TSource extends UpgradeCard, TTarget extends Card> = ITriggeredAbilityBaseProps<TTarget> & IGainCondition<TSource>;

type IActionAbilityPropsWithGainCondition<TSource extends UpgradeCard, TTarget extends Card> = IActionAbilityProps<TTarget> & IGainCondition<TSource>;

type IKeywordPropertiesWithGainCondition<TSource extends UpgradeCard> = IKeywordProperties & IGainCondition<TSource>;
type IActionAbilityPropsWithGainCondition<TSource extends IUpgradeCard, TTarget extends Card> = IActionAbilityProps<TTarget> & IGainCondition<TSource>;

const UpgradeCardParent = WithPrintedPower(WithPrintedHp(WithStandardAbilitySetup(InPlayCard)));

Expand Down Expand Up @@ -162,16 +155,6 @@ export class UpgradeCard extends UpgradeCardParent implements IUpgradeCard, IPla
});
}

/**
* This is required because a gainCondition call can happen after an upgrade is discarded,
* so we need to short-circuit in that case to keep from trying to access illegal state such as parentCard
*/
private addZoneCheckToGainCondition(gainCondition?: (context: AbilityContext<this>) => boolean) {
return gainCondition == null
? null
: (context: AbilityContext<this>) => this.isInPlay() && gainCondition(context);
}

/** Adds a condition that must return true for the upgrade to be allowed to attach to the passed card. */
protected setAttachCondition(attachCondition: (card: Card) => boolean) {
Contract.assertIsNullLike(this.attachCondition, 'Attach condition is already set');
Expand Down
10 changes: 10 additions & 0 deletions server/game/core/card/baseClasses/InPlayCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ export class InPlayCard extends InPlayCardParent implements IInPlayCard {
return true;
}

/**
* This is required because a gainCondition call can happen after an upgrade is discarded,
* so we need to short-circuit in that case to keep from trying to access illegal state such as parentCard
*/
protected addZoneCheckToGainCondition(gainCondition?: (context: AbilityContext<this>) => boolean) {
return gainCondition == null
? null
: (context: AbilityContext<this>) => this.isInPlay() && gainCondition(context);
}

public override getSummary(activePlayer: Player) {
return { ...super.getSummary(activePlayer),
parentCardId: this._parentCard ? this._parentCard.uuid : null };
Expand Down
13 changes: 9 additions & 4 deletions server/game/core/card/propertyMixins/UnitProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { ICardWithPrintedPowerProperty } from './PrintedPower';
import { WithPrintedPower } from './PrintedPower';
import * as EnumHelpers from '../../utils/EnumHelpers';
import type { Card } from '../Card';
import type { IAbilityPropsWithType, IConstantAbilityProps, IKeywordProperties, ITriggeredAbilityBaseProps, ITriggeredAbilityProps } from '../../../Interfaces';
import type { IAbilityPropsWithType, IConstantAbilityProps, IGainCondition, IKeywordProperties, ITriggeredAbilityBaseProps, ITriggeredAbilityProps } from '../../../Interfaces';
import { BountyKeywordInstance } from '../../ability/KeywordInstance';
import { KeywordWithAbilityDefinition } from '../../ability/KeywordInstance';
import TriggeredAbility from '../../ability/TriggeredAbility';
Expand All @@ -39,6 +39,8 @@ import type { AbilityContext } from '../../ability/AbilityContext';

export const UnitPropertiesCard = WithUnitProperties(InPlayCard);

type IAbilityPropsWithGainCondition<TSource extends IUpgradeCard, TTarget extends Card> = IAbilityPropsWithType<TTarget> & IGainCondition<TSource>;

export interface IUnitCard extends IInPlayCard, ICardWithDamageProperty, ICardWithPrintedPowerProperty {
get defaultArena(): Arena;
get capturedUnits(): IUnitCard[];
Expand Down Expand Up @@ -364,7 +366,7 @@ export function WithUnitProperties<TBaseClass extends InPlayCardConstructor>(Bas
title: properties.title,
matchTarget: (card, context) => card === context.source.parentCard,
targetController: WildcardRelativePlayer.Any, // this means that the effect continues to work even if the other player gains control of the upgrade
condition: properties.condition,
condition: this.addZoneCheckToGainCondition(properties.condition),
ongoingEffect: properties.ongoingEffect
});
}
Expand All @@ -376,10 +378,13 @@ export function WithUnitProperties<TBaseClass extends InPlayCardConstructor>(Bas
});
}

public addPilotingGainAbilityTargetingAttached(properties: IAbilityPropsWithType<this>) {
public addPilotingGainAbilityTargetingAttached(properties: IAbilityPropsWithGainCondition<this, IUnitCard>) {
const { gainCondition, ...gainedAbilityProperties } = properties;

this.addPilotingConstantAbilityTargetingAttached({
title: 'Give ability to the attached card',
ongoingEffect: OngoingEffectLibrary.gainAbility(properties)
condition: this.addZoneCheckToGainCondition(gainCondition),
ongoingEffect: OngoingEffectLibrary.gainAbility(gainedAbilityProperties)
});
}

Expand Down