You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
14 KiB
350 lines
14 KiB
import { WeightedTextSelector } from './weightedText'
|
|
|
|
export enum DieFace {
|
|
FAIL = 'F',
|
|
STOP = 'S',
|
|
SCORE_0 = '0',
|
|
SCORE_1 = '1',
|
|
SCORE_2 = '2',
|
|
SCORE_3 = '3',
|
|
SCORE_4 = '4',
|
|
SCORE_5 = '5',
|
|
WILD = '*',
|
|
HEART = '<3',
|
|
QUESTION = '?',
|
|
}
|
|
|
|
export enum DieState {
|
|
ROLLED = 'rolled',
|
|
SELECTED = 'selected',
|
|
HELD = 'held',
|
|
HELD_SELECTED = 'held_selected',
|
|
SCORED = 'scored',
|
|
}
|
|
|
|
export function isHeldState(d: DieState): d is DieState.HELD | DieState.HELD_SELECTED {
|
|
return d === DieState.HELD || d === DieState.HELD_SELECTED
|
|
}
|
|
|
|
export function isSelectedState(d: DieState): d is DieState.SELECTED | DieState.HELD_SELECTED {
|
|
return d === DieState.SELECTED || d === DieState.HELD_SELECTED
|
|
}
|
|
|
|
export function setSelected(d: DieState): DieState.SELECTED | DieState.HELD_SELECTED | DieState.SCORED {
|
|
switch (d) {
|
|
case DieState.ROLLED:
|
|
return DieState.SELECTED
|
|
case DieState.HELD:
|
|
return DieState.HELD_SELECTED
|
|
case DieState.SELECTED:
|
|
case DieState.HELD_SELECTED:
|
|
case DieState.SCORED:
|
|
return d
|
|
}
|
|
}
|
|
|
|
export function setDeselected(d: DieState): DieState.ROLLED | DieState.HELD | DieState.SCORED {
|
|
switch (d) {
|
|
case DieState.SELECTED:
|
|
return DieState.ROLLED
|
|
case DieState.HELD_SELECTED:
|
|
return DieState.HELD
|
|
case DieState.ROLLED:
|
|
case DieState.HELD:
|
|
case DieState.SCORED:
|
|
return d
|
|
}
|
|
}
|
|
|
|
export function toggleSelected(d: DieState): DieState {
|
|
switch (d) {
|
|
case DieState.ROLLED:
|
|
return DieState.SELECTED
|
|
case DieState.SELECTED:
|
|
return DieState.ROLLED
|
|
case DieState.HELD:
|
|
return DieState.HELD_SELECTED
|
|
case DieState.HELD_SELECTED:
|
|
return DieState.HELD
|
|
case DieState.SCORED:
|
|
return DieState.SCORED
|
|
}
|
|
}
|
|
|
|
export interface DieResult {
|
|
readonly type: DieType
|
|
readonly face: DieFace
|
|
readonly state: DieState
|
|
}
|
|
|
|
export interface DieType {
|
|
// The name of this die type
|
|
readonly name: string
|
|
// The faces of this die type; faces may be repeated to increase their odds
|
|
readonly faces: readonly DieFace[]
|
|
}
|
|
|
|
export interface AIWeights {
|
|
// How much this AI likes to choose actions that increase the damage bonus
|
|
// damageBonusWeight is static and does not change
|
|
readonly damageBonusWeight: number
|
|
// How much this AI likes to choose actions that increase the requirement of stop dice in a turn
|
|
// stopWeight is multiplied by the number of stop dice both players can gain
|
|
readonly stopWeight: number
|
|
// How much this AI likes to choose actions that decrease the limit of fail dice in a turn
|
|
// failWeight is multiplied by the number of fail dice both players can lose
|
|
readonly failWeight: number
|
|
// How much this AI likes to choose actions that are finishers for the opponent.
|
|
// finisherWeight is multiplied by the percentage of max damage the opponent has taken
|
|
readonly finisherWeight: number
|
|
// How much this AI dislikes choosing actions that are finishers for itself.
|
|
// selfFinisherWeight is multiplied by the percentage of max damage the AI has taken
|
|
// If this would reduce the weight of an action to 0 or less, that action has a weight of 0 and will only be
|
|
// selected randomly if all actions have a weight of 0.
|
|
readonly selfFinisherWeight: number
|
|
// Approximate lead that the AI would prefer to have before ending its turn when it has the option to keep pressing
|
|
readonly desiredLead: number
|
|
// Approximate damage behind the previous turn total that the AI is willing to take to play it safe,
|
|
// assuming doing so wouldn't be fatal (not enough damage to kill or not a finisher)
|
|
// When there's one fail die left, the AI will prioritize stopping as long as the damage it will be taking is within
|
|
// a randomly selected 50-150% of this value
|
|
readonly allowedDamage: number
|
|
// Integer from 0-100:
|
|
// 100 means AI keeps rolling until it has one fail die left before trying to stop
|
|
// 0 means AI always stops as soon as it has at least the desired lead
|
|
// Anything in between is the percentage chance of continuing, which is rolled once for each fail die remaining
|
|
// besides the last. If the chance fails once, then the AI uses that turn as if it were trying to stop.
|
|
// This roll is only performed when the AI already has the lead, is ahead by a value within an acceptable distance
|
|
// of the desired lead, and the AI has at least two additional fail dice remaining before failing.
|
|
readonly recklessnessPercent: number
|
|
// Integer from 0-100:
|
|
// 0 means AI never holds stops until it's ready to end its turn
|
|
// 100 means AI always holds stops it sees
|
|
// Anything in between is the percentage chance of locking a new stop die that appears, rolled once per stop die
|
|
// needed to end the turn per die that comes up stop. If the chance succeeds once, then the AI holds that stop die.
|
|
// This roll is only performed when the AI is not already stopping its turn.
|
|
readonly holdStopsPercent: number
|
|
}
|
|
|
|
export interface PlayerStartingState extends Pick<PlayerState, 'damageMax' | 'stopCount' | 'failCount'> {
|
|
// Percentage of damage that this player starts off dealing
|
|
readonly damageBonusBase: number
|
|
// Percentage of damage this player's damage increases by with each damage bonus earned
|
|
readonly damageBonusIncrement: number
|
|
// Maximum number of damage bonuses that this player can have, or 0 if there's no upper limit
|
|
readonly maxDamageBonuses: number
|
|
|
|
// Minimum number of dice required to stop
|
|
readonly minStopCount: number
|
|
// Minimum number of dice required to fail
|
|
readonly minFailCount: number
|
|
|
|
// Maximum number of dice required to stop
|
|
readonly maxStopCount: number
|
|
// Maximum number of dice required to fail
|
|
readonly maxFailCount: number
|
|
|
|
// Base amount of damage that must be dealt to recover, or 0 if recovery is forbidden
|
|
// Defaults to 1000
|
|
readonly recoverBase?: number
|
|
// Amount by which the amount needed to recover increases each time recovery is earned
|
|
// Defaults to 1000
|
|
readonly recoverIncrement?: number
|
|
// Percentage of current damage taken that is removed when recovery is earned
|
|
// Defaults to 50
|
|
readonly recoverPercent?: number
|
|
|
|
// Instructions for if this player is an AI player. If not specified, this player cannot be played by the AI.
|
|
readonly aiData?: AIWeights
|
|
}
|
|
|
|
export interface Difficulty {
|
|
// Name of the difficulty in the select menu
|
|
readonly name: string
|
|
// Short description of the difficulty in the select menu
|
|
readonly shortDescription?: string
|
|
// Description of the difficulty when selected
|
|
readonly description?: string
|
|
|
|
// Starting values for the top
|
|
readonly topStats: PlayerStartingState
|
|
// Starting values for the bottom
|
|
readonly bottomStats: PlayerStartingState
|
|
}
|
|
|
|
export interface PlayerText {
|
|
// Possible random names for this player
|
|
readonly names?: readonly string[]
|
|
|
|
// Text given at the start of the game from this side
|
|
readonly startText?: TriggeredText
|
|
// Text given at the end of the game from this side
|
|
readonly endText?: TriggeredText
|
|
|
|
// The name of the damage value for this player
|
|
readonly damage: string
|
|
// The name of the player's damage limit, defaulting to "Max " + damage
|
|
readonly maxDamage?: string
|
|
// The name of this player's stop count requirement
|
|
readonly stopCount: string
|
|
// The name of this player's fail count limit
|
|
readonly failCount: string
|
|
// The name of this player's incoming damage bonus
|
|
readonly damageBonus: string
|
|
}
|
|
|
|
export interface GameTheme {
|
|
readonly name: string
|
|
readonly shortDescription: string
|
|
readonly description: string
|
|
|
|
readonly actions: readonly GameAction[]
|
|
|
|
readonly difficulties: readonly Difficulty[]
|
|
|
|
readonly commonText?: { readonly [key: string]: WeightedTextSelector | undefined }
|
|
|
|
readonly narratorName: string
|
|
readonly topText: PlayerText
|
|
readonly bottomText: PlayerText
|
|
}
|
|
|
|
export interface TriggeredText {
|
|
// The dialogues that can be triggered for this action.
|
|
// Dialogue can be disabled for characters run by human players.
|
|
readonly dialogue?: WeightedTextSelector
|
|
// The descriptions that can be triggered for this action.
|
|
// Descriptions are always displayed, regardless of player.
|
|
readonly description?: WeightedTextSelector
|
|
}
|
|
|
|
export interface ActionText {
|
|
// Name of the action in the select list for this side
|
|
readonly name: string
|
|
// Description of the action in the select list for this side
|
|
readonly shortDescription: string
|
|
// Description of the action when selected for this side
|
|
readonly description: string
|
|
|
|
// Text given when this side selects this action.
|
|
readonly selectAction?: TriggeredText
|
|
// Text given when the other side selects this action.
|
|
readonly opponentSelectsAction?: TriggeredText
|
|
// Text given when this side finishes a turn in this action without failing.
|
|
readonly passTurn?: TriggeredText
|
|
// Text given when this side receives a turn from the opponent in this action.
|
|
readonly opponentPassesTurn?: TriggeredText
|
|
// Text given when this side rerolls the dice for this action.
|
|
readonly reroll?: TriggeredText
|
|
// Text given when the opposing side rerolls the dice for this action.
|
|
readonly opponentRerolls?: TriggeredText
|
|
// Text given the first time each turn this side is one fail die away from failing
|
|
readonly aboutToFailTurn?: TriggeredText
|
|
// Text given the first time each turn the opposing side is one fail die away from failing
|
|
readonly opponentAboutToFailTurn?: TriggeredText
|
|
// Text given when this side fails the turn by accumulating fail dice
|
|
readonly failTurn?: TriggeredText
|
|
// Text given when the opposing side fails the turn by accumulating fail dice
|
|
readonly opponentFailsTurn?: TriggeredText
|
|
// Text given when this side fails the turn by stopping without enough points
|
|
readonly abandonTurn?: TriggeredText
|
|
// Text given when the opposing side fails the turn by stopping without enough points
|
|
readonly opponentAbandonsTurn?: TriggeredText
|
|
// Text given when this side is defeated by being pushed over their max damage with this action
|
|
readonly defeated?: TriggeredText
|
|
// Text given when this side defeats the opponent by pushing them over their max damage with this action
|
|
readonly opponentDefeated?: TriggeredText
|
|
}
|
|
|
|
export interface GameAction {
|
|
// The text from the top's perspective, and for the top's actions
|
|
readonly topText: ActionText
|
|
// The text from the bottom's perspective, and for the bottom's actions
|
|
readonly bottomText: ActionText
|
|
|
|
// The dice that are rolled for this action
|
|
readonly dice: readonly DieType[]
|
|
// True if the loser of this action increases the amount of damage they take
|
|
readonly givesDamageBonus: boolean
|
|
// True if the loser of this action has to get an additional stop die to end their turn
|
|
readonly givesStopCount: boolean
|
|
// True if the loser of this action has one fewer buffer for fail dice
|
|
readonly givesFailCount: boolean
|
|
// Whether this action can end the game in a loss for the top
|
|
readonly canFinishTop: boolean
|
|
// Whether this action can end the game in a loss for the bottom
|
|
readonly canFinishBottom: boolean
|
|
}
|
|
|
|
export interface PlayerState {
|
|
// This player's name
|
|
readonly name: string
|
|
|
|
// Amount of damage taken so far this battle
|
|
readonly damage: number
|
|
// Max damage that can be taken, 0 for endless mode
|
|
readonly damageMax: number
|
|
// The total damage taken across this battle
|
|
readonly damageTotal: number
|
|
|
|
// Number of stop dice required to end a turn with stops
|
|
readonly stopCount: number
|
|
// Number of fail dice required to fail a turn by accumulating too many fail dice
|
|
readonly failCount: number
|
|
// Incoming damage for this side is multiplied by 100% + 10% * damageBonuses
|
|
readonly damageBonuses: number
|
|
|
|
// Amount that Total Damage must reach to reduce this player's damage by 50% of its current value
|
|
// 0 means no recovery is allowed for this player
|
|
readonly nextRecoverAt: number
|
|
// Number of times damage was recovered so far this game (adds 1000 + 100 * timesRecovered to nextRecoverAt)
|
|
readonly timesRecovered: number
|
|
}
|
|
|
|
export enum GameState {
|
|
ONGOING = 'ongoing',
|
|
ABORTED = 'aborted',
|
|
TOP_WINS = 'top_wins',
|
|
BOTTOM_WINS = 'bottom_wins',
|
|
}
|
|
|
|
export interface GameData {
|
|
// The version of the serialization format this data was saved with.
|
|
readonly version: number
|
|
// Whether the game is still ongoing, or if not, who won (if anyone).
|
|
readonly gameState: GameState
|
|
|
|
// The state of the top player
|
|
readonly top: PlayerState
|
|
// The state of the bottom player
|
|
readonly bottom: PlayerState
|
|
|
|
// The theme that defines this game for the players.
|
|
readonly theme: GameTheme
|
|
// The difficulty selected for this game.
|
|
readonly difficulty: Difficulty
|
|
|
|
// If true, the top chose/is choosing the action this round.
|
|
readonly isTopRound: boolean
|
|
// The action chosen for this round, or null if the current player has not chosen an action yet.
|
|
// The action is locked in if lastRoll is not null or lastTurnTotal is not 0.
|
|
readonly action: GameAction | null
|
|
|
|
// If true, the top is rolling this turn.
|
|
readonly isTopTurn: boolean
|
|
// The total for the previous turn, or 0 if the current player is taking the first turn
|
|
// If the current player fails when the turn total is 0, they take the penalty and pass the initiative without taking damage.
|
|
// This can still end the game if the current action is capable of ending the game for this player and
|
|
// this player has more damage than their maximum.
|
|
readonly lastTurnTotal: number
|
|
// The dice available for the current turn, or null if the current player has not rolled yet this turn.
|
|
readonly lastRoll: readonly DieResult[] | null
|
|
// The total value of the selected dice, or 0 if the selected dice cannot be scored.
|
|
readonly selectedDiceValue: number
|
|
// Whether the selected dice are sufficient to end the turn.
|
|
readonly selectedDiceEndTurn: boolean
|
|
// The number of dice that have come up failures so far in the current turn
|
|
readonly countedFails: number
|
|
// The total of the dice that have been scored so far this turn
|
|
readonly currentTurnTotal: number
|
|
}
|
|
|