335 lines
10 KiB
TypeScript
335 lines
10 KiB
TypeScript
module TK.SpaceTac {
|
|
/**
|
|
* Category of action
|
|
*/
|
|
export enum ActionCategory {
|
|
MOVE,
|
|
PASSIVE,
|
|
ACTIVE,
|
|
}
|
|
|
|
/**
|
|
* Target for an action
|
|
*/
|
|
export type ActionTarget = Readonly<{
|
|
distance?: number
|
|
angle?: number
|
|
}>
|
|
|
|
/**
|
|
* Target for an action, as applied from an hypothetical location
|
|
*/
|
|
export type ActionTargetFrom = Readonly<{
|
|
location: IArenaLocation
|
|
distance?: number
|
|
angle?: number
|
|
}>
|
|
|
|
/**
|
|
* Impact filter for an action.
|
|
*
|
|
* This will filter ships inside the targetted area, to determine which ones will receive the action effects.
|
|
*/
|
|
export enum ActionImpactFilter {
|
|
// Apply only on casting ship
|
|
SELF,
|
|
// Apply on all ships
|
|
ALL,
|
|
// Apply on all ships except the actor
|
|
ALL_BUT_SELF,
|
|
// Apply on all allies, including the actor
|
|
ALLIES,
|
|
// Apply on all allies, except the actor
|
|
ALLIES_BUT_SELF,
|
|
// Apply on all enemies
|
|
ENEMIES,
|
|
};
|
|
|
|
/**
|
|
* Priority of ships in the target area.
|
|
*
|
|
* If there are more ships that the action limit, the most prioritized ones will be affected.
|
|
*/
|
|
export enum ActionImpactPriority {
|
|
// Nearest ships are priority
|
|
NEAREST,
|
|
// Farthest ships are priority
|
|
FARTHEST,
|
|
// Ships with the most hull are priority
|
|
TOUGHEST,
|
|
// Ships with the less hull are priority
|
|
WEAKEST,
|
|
}
|
|
|
|
/**
|
|
* Configuration for the target of an action
|
|
*/
|
|
export interface BaseActionConfig {
|
|
// Identifying code (for assets and effects)
|
|
code: string
|
|
// Human-friendly name
|
|
name: string
|
|
// Power cost
|
|
power_cost?: number
|
|
// Maximal distance the target can be set
|
|
min_distance?: number
|
|
// Minimal distance the target can be set
|
|
max_distance?: number
|
|
// Angle that will span the affected area (around the target direction)
|
|
angular_span?: number
|
|
// Radius of the affected area (around the target location)
|
|
radius?: number
|
|
// Filtering of ships in the affected area
|
|
ship_filter?: ActionImpactFilter
|
|
// Maximal number of ships impacted in the affected area
|
|
ship_limit?: number
|
|
// Priority of ships in affected area
|
|
ship_priority?: ActionImpactPriority
|
|
}
|
|
|
|
/**
|
|
* Reasons for action unavailibility
|
|
*/
|
|
export enum ActionUnavailability {
|
|
// Ship is not playing
|
|
NOT_PLAYING = "Not this ship turn",
|
|
// Action is not available
|
|
NO_SUCH_ACTION = "Action not available",
|
|
// Action is overheated
|
|
OVERHEATED = "Overheated",
|
|
// Vigilance is activated
|
|
VIGILANCE = "In vigilance",
|
|
// Ship is pinned
|
|
PINNED = "Pinned",
|
|
}
|
|
|
|
/**
|
|
* Base class for a battle action.
|
|
*
|
|
* An action should be the only way to modify a battle state.
|
|
*/
|
|
export class BaseAction extends RObject implements BaseActionConfig {
|
|
code = "nothing"
|
|
name = "Nothing"
|
|
power_cost = 0
|
|
min_distance = 0
|
|
max_distance = Infinity
|
|
angular_span = undefined
|
|
radius = undefined
|
|
ship_filter = ActionImpactFilter.ALL
|
|
ship_limit = undefined
|
|
ship_priority = ActionImpactPriority.NEAREST
|
|
|
|
// Cooldown configuration
|
|
private cooldown = new Cooldown()
|
|
|
|
// Create the action
|
|
constructor(config?: Partial<Readonly<BaseActionConfig>>) {
|
|
super();
|
|
|
|
if (config) {
|
|
this.configure(config);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the category for this action
|
|
*/
|
|
getCategory(): ActionCategory {
|
|
return ActionCategory.PASSIVE;
|
|
}
|
|
|
|
/**
|
|
* Get the verb for this action
|
|
*/
|
|
getVerb(ship: Ship): string {
|
|
return "Do";
|
|
}
|
|
|
|
/**
|
|
* Get the full title for this action (verb and name)
|
|
*/
|
|
getTitle(ship: Ship): string {
|
|
return `${this.getVerb(ship)} ${this.name}`;
|
|
}
|
|
|
|
/**
|
|
* Get a default target for this action
|
|
*/
|
|
getDefaultTarget(ship: Ship): ActionTargetFrom {
|
|
return { location: ship.location };
|
|
}
|
|
|
|
/**
|
|
* Configure the base settings for this action
|
|
*/
|
|
configure(config: Partial<Readonly<BaseActionConfig>>): void {
|
|
copyfields(config, this);
|
|
}
|
|
|
|
/**
|
|
* Configure the cooldown for this action
|
|
*/
|
|
configureCooldown(overheat: number, cooling: number): void {
|
|
this.cooldown.configure(overheat, cooling);
|
|
}
|
|
|
|
/**
|
|
* Get the cooldown configuration
|
|
*/
|
|
getCooldown(): Cooldown {
|
|
// TODO Split configuration (readonly) and usage
|
|
return this.cooldown;
|
|
}
|
|
|
|
/**
|
|
* Check basic conditions to know if the ship can use this action at all, not accounting for power
|
|
*
|
|
* Method to extend to set conditions
|
|
*
|
|
* Returns an unavalability reason, null otherwise
|
|
*/
|
|
checkCannotBeApplied(ship: Ship): ActionUnavailability | null {
|
|
if (!ship.actions.getById(this.id)) {
|
|
return ActionUnavailability.NO_SUCH_ACTION;
|
|
}
|
|
|
|
// Check cooldown
|
|
if (!ship.actions.isUsable(this)) {
|
|
return ActionUnavailability.OVERHEATED;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the power cost of this action for the current state
|
|
*/
|
|
getPowerUsage(ship: Ship): number {
|
|
return this.power_cost;
|
|
}
|
|
|
|
/**
|
|
* Filter a list of ships to return only those impacted by this action
|
|
*
|
|
* This may be used as an indicator for helping the player in targetting, or to effectively apply the effects
|
|
*/
|
|
filterImpactedShips(ships: readonly Ship[], source: Ship, target: ActionTargetFrom): readonly Ship[] {
|
|
// TODO Allow to work with ghosts (location instead of ships?)
|
|
// TODO Apply radius and angle
|
|
// TODO Apply limit and priority
|
|
return ships.filter(ship => {
|
|
if (this.ship_filter == ActionImpactFilter.ALL) {
|
|
return true;
|
|
} else if (this.ship_filter == ActionImpactFilter.ALL_BUT_SELF) {
|
|
return !ship.is(source);
|
|
} else if (this.ship_filter == ActionImpactFilter.ALLIES) {
|
|
return ship.fleet.player.is(source.fleet.player);
|
|
} else if (this.ship_filter == ActionImpactFilter.ALLIES_BUT_SELF) {
|
|
return ship.fleet.player.is(source.fleet.player) && !ship.is(source);
|
|
} else if (this.ship_filter == ActionImpactFilter.ENEMIES) {
|
|
return !ship.fleet.player.is(source.fleet.player);
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a name to represent the group of ships specified by a target filter
|
|
*/
|
|
static getFilterDesc(filter: ActionImpactFilter, plural = true): string {
|
|
// TODO limit and priority
|
|
if (filter == ActionImpactFilter.ALL) {
|
|
return plural ? "ships" : "ship";
|
|
} else if (filter == ActionImpactFilter.ALL_BUT_SELF) {
|
|
return plural ? "other ships" : "other ship";
|
|
} else if (filter == ActionImpactFilter.ALLIES) {
|
|
return plural ? "team members" : "team member";
|
|
} else if (filter == ActionImpactFilter.ALLIES_BUT_SELF) {
|
|
return plural ? "teammates" : "teammates";
|
|
} else if (filter == ActionImpactFilter.ENEMIES) {
|
|
return plural ? "enemies" : "enemy";
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a target is suitable for this action
|
|
*
|
|
* Returns a suggested fixed target (may be the same as the input)
|
|
*/
|
|
checkTarget(ship: Ship, target: ActionTargetFrom): ActionTargetFrom | null {
|
|
if (this.checkCannotBeApplied(ship)) {
|
|
return null;
|
|
} else {
|
|
if (target.isShip()) {
|
|
return this.checkShipTarget(ship, target);
|
|
} else {
|
|
return this.checkLocationTarget(ship, target);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the full list of diffs caused by applying this action
|
|
*
|
|
* This does not perform any check, and assumes the action is doable
|
|
*/
|
|
getDiffs(ship: Ship, battle: Battle, target: ActionTargetFrom): BaseBattleDiff[] {
|
|
let result: BaseBattleDiff[] = [];
|
|
|
|
// Action usage
|
|
result.push(new ShipActionUsedDiff(ship, this, target));
|
|
|
|
// Action effects
|
|
result = result.concat(this.getSpecificDiffs(ship, battle, target));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Method to reimplement to return the diffs specific to this action
|
|
*/
|
|
protected getSpecificDiffs(ship: Ship, battle: Battle, target: ActionTargetFrom): BaseBattleDiff[] {
|
|
return []
|
|
}
|
|
|
|
/**
|
|
* Apply the action on a battle state
|
|
*
|
|
* This will first check that the action can be done, then get the battle diffs and apply them.
|
|
*/
|
|
apply(battle: Battle, ship: Ship, target: ActionTarget): boolean {
|
|
let reject = this.checkCannotBeApplied(ship);
|
|
if (reject) {
|
|
console.warn(`Action rejected - ${reject}`, ship, this, target);
|
|
return false;
|
|
}
|
|
|
|
let checked_target = this.checkTarget(ship, target);
|
|
if (!checked_target) {
|
|
console.warn("Action rejected - invalid target", ship, this, target);
|
|
return false;
|
|
}
|
|
|
|
let diffs = this.getDiffs(ship, battle, checked_target);
|
|
if (diffs.length) {
|
|
battle.applyDiffs(diffs);
|
|
return true;
|
|
} else {
|
|
console.error("Could not apply action, no diff produced");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get textual description of effects
|
|
*/
|
|
getEffectsDescription(): string {
|
|
return "";
|
|
}
|
|
}
|
|
}
|