feat(engine): add instant action execution
Implements executeInstant, which applies an instant action's costs and yields immediately with no queue slot and no duration requirement.
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
clearQueue,
|
clearQueue,
|
||||||
createGameState,
|
createGameState,
|
||||||
enqueueAction,
|
enqueueAction,
|
||||||
|
executeInstant,
|
||||||
startAction,
|
startAction,
|
||||||
tickGame,
|
tickGame,
|
||||||
} from '../game';
|
} from '../game';
|
||||||
@@ -367,3 +368,37 @@ describe('tickGame()', () => {
|
|||||||
expect(state.activeActionId).toBeNull();
|
expect(state.activeActionId).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('executeInstant()', () => {
|
||||||
|
const instantContent = buildContent({
|
||||||
|
resources: [
|
||||||
|
{ id: 'supplies', name: 'Supplies', startAmount: 0 },
|
||||||
|
{ id: 'coin', name: 'Coin', startAmount: 5 },
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'buy_supply',
|
||||||
|
name: 'Buy supply',
|
||||||
|
kind: 'instant',
|
||||||
|
group: { id: 'buy', label: 'Buy' },
|
||||||
|
costs: [{ resourceId: 'coin', amount: 2 }],
|
||||||
|
yields: [{ resourceId: 'supplies', amount: 1 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies costs and yields immediately without queueing', () => {
|
||||||
|
const state = createGameState(instantContent);
|
||||||
|
executeInstant(state, instantContent, 'buy_supply');
|
||||||
|
expect(state.resources.coin).toBe(3);
|
||||||
|
expect(state.resources.supplies).toBe(1);
|
||||||
|
expect(state.activeActionId).toBeNull();
|
||||||
|
expect(state.actionQueue).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when unaffordable', () => {
|
||||||
|
const state = createGameState(instantContent);
|
||||||
|
state.resources.coin = 0;
|
||||||
|
expect(() => executeInstant(state, instantContent, 'buy_supply')).toThrow(/Cannot/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -156,6 +156,23 @@ export function clearQueue(state: GameState): void {
|
|||||||
state.actionQueue.length = 0;
|
state.actionQueue.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an instant action immediately, deducting costs and granting yields
|
||||||
|
* without occupying a queue slot or requiring a duration.
|
||||||
|
* Throws if the action is not of kind 'instant' or is unavailable.
|
||||||
|
*/
|
||||||
|
export function executeInstant(state: GameState, content: Content, actionId: string): void {
|
||||||
|
const action = content.actionsById[actionId];
|
||||||
|
if (!action || action.kind !== 'instant') {
|
||||||
|
throw new Error(`Action "${actionId}" is not instant`);
|
||||||
|
}
|
||||||
|
if (!isActionAvailable(state, content, actionId)) {
|
||||||
|
throw new Error(`Cannot perform instant action "${actionId}"`);
|
||||||
|
}
|
||||||
|
deductCosts(state, content, actionId);
|
||||||
|
grantYields(state, content, actionId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance the active action by `tickMs`. On completion, grants yields and
|
* Advance the active action by `tickMs`. On completion, grants yields and
|
||||||
* advances the queue — actions do not auto-repeat when the queue is empty.
|
* advances the queue — actions do not auto-repeat when the queue is empty.
|
||||||
|
|||||||
Reference in New Issue
Block a user