/// abstract class Purchasable extends Resource { public readonly resourceType: ResourceType = ResourceType.purchasable; public valueInWholeNumbers = true; public cost: ResourceNumber = {}; public inc?: (state: GameState) => number = undefined; public max?: (state: GameState) => number = undefined; public userActions: ResourceAction[] = [ { name: this._purchaseButtonText, description: this._purchaseDescription, isEnabled: (state: GameState): boolean => (this.max === undefined || this.value < this.max(state)) && state.isPurchasable(this.cost), performAction: (state: GameState): void => { this._purchase(state); }, }, ]; protected _costMultiplier: ResourceNumber = {}; protected _sellMultiplier?: number | ResourceNumber; protected _isUnlocked = false; constructor( public readonly label: string, public readonly singularName: string, public readonly pluralName: string, public readonly description: string, canSell = false, private readonly _purchaseButtonText = 'Purchase', private readonly _purchaseDescription = `Buy a ${singularName}.`, private readonly _sellButtonText = 'Sell', private readonly _sellDescription = `Sell a ${singularName}.` ) { super(); if (canSell) { this.userActions.push({ name: this._sellButtonText, description: this._sellDescription, isEnabled: (_state: GameState): boolean => this.value > 0, performAction: (state: GameState): void => { this._sell(state); }, }); } } public isUnlocked = (state: GameState): boolean => { if (!this._isUnlocked && state.isPurchasable(this.cost)) { this._isUnlocked = true; } return this._isUnlocked; }; public emitConfig = (): ResourceConfigValues => { return { isUnlocked: this._isUnlocked }; }; public restoreConfig = (config: ResourceConfig): void => { this.rawValue = config.value; if (config.cost !== undefined) this.cost = config.cost; if ( config.config !== undefined && typeof config.config.isUnlocked === 'boolean' ) { this._isUnlocked = config.config.isUnlocked; } }; protected _purchaseLog(amount: number, _state: GameState): string { let verb = 'purchased'; if (amount < 0) { verb = 'sold'; amount *= -1; } return `You ${verb} ${amount} ${ amount > 1 ? this.pluralName : this.singularName }.`; } private _purchase(state: GameState): void { if (this.max !== undefined && this.value >= this.max(state)) return; if (state.deductCost(this.cost)) { this.addValue(1, state); state.log(this._purchaseLog(1, state)); for (const key in this._costMultiplier) { const rkey = key; this.cost[rkey] = (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1); } } } private _sell(state: GameState): void { if (this.value <= 0) return; const costBack: ResourceNumber = {}; for (const key in this.cost) { const rkey = key; let cost = this.cost[rkey]; if (cost === undefined) continue; // revert cost multiplier cost /= this._costMultiplier[rkey] ?? 1; this.cost[rkey] = cost; const multiplier = this._sellMultiplier === undefined ? state.config.cfgDefaultSellMultiplier : typeof this._sellMultiplier === 'number' ? this._sellMultiplier : this._sellMultiplier[rkey] ?? state.config.cfgDefaultSellMultiplier; // penalize return on used item costBack[rkey] = cost * -1 * multiplier; state.deductCost(costBack); } this.addValue(1, state); state.log(this._purchaseLog(-1, state)); } }