volleyballsimtypes 0.0.451 → 0.0.453

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cjs/src/service/event/event-types.d.ts +3 -2
  2. package/dist/cjs/src/service/event/free-ball.d.ts +17 -0
  3. package/dist/cjs/src/service/event/free-ball.js +47 -0
  4. package/dist/cjs/src/service/event/index.d.ts +1 -0
  5. package/dist/cjs/src/service/event/index.js +1 -0
  6. package/dist/cjs/src/service/event/rally-event.d.ts +3 -2
  7. package/dist/cjs/src/service/event/rally-event.js +1 -0
  8. package/dist/cjs/src/service/event/schemas/block.z.d.ts +9 -0
  9. package/dist/cjs/src/service/event/schemas/free-ball.z.d.ts +30 -0
  10. package/dist/cjs/src/service/event/schemas/free-ball.z.js +16 -0
  11. package/dist/cjs/src/service/event/schemas/reception.z.d.ts +9 -0
  12. package/dist/cjs/src/service/event/schemas/serve.z.d.ts +9 -0
  13. package/dist/cjs/src/service/event/schemas/set.z.d.ts +9 -0
  14. package/dist/cjs/src/service/event/schemas/spike.z.d.ts +9 -0
  15. package/dist/cjs/src/service/player/schemas/player.z.d.ts +9 -0
  16. package/dist/cjs/src/service/player/trait.d.ts +47 -2
  17. package/dist/cjs/src/service/player/trait.js +55 -1
  18. package/dist/cjs/src/stat-config/traits.json +87 -0
  19. package/dist/esm/src/service/event/event-types.d.ts +3 -2
  20. package/dist/esm/src/service/event/free-ball.d.ts +17 -0
  21. package/dist/esm/src/service/event/free-ball.js +43 -0
  22. package/dist/esm/src/service/event/index.d.ts +1 -0
  23. package/dist/esm/src/service/event/index.js +1 -0
  24. package/dist/esm/src/service/event/rally-event.d.ts +3 -2
  25. package/dist/esm/src/service/event/rally-event.js +1 -0
  26. package/dist/esm/src/service/event/schemas/block.z.d.ts +9 -0
  27. package/dist/esm/src/service/event/schemas/free-ball.z.d.ts +30 -0
  28. package/dist/esm/src/service/event/schemas/free-ball.z.js +13 -0
  29. package/dist/esm/src/service/event/schemas/reception.z.d.ts +9 -0
  30. package/dist/esm/src/service/event/schemas/serve.z.d.ts +9 -0
  31. package/dist/esm/src/service/event/schemas/set.z.d.ts +9 -0
  32. package/dist/esm/src/service/event/schemas/spike.z.d.ts +9 -0
  33. package/dist/esm/src/service/player/schemas/player.z.d.ts +9 -0
  34. package/dist/esm/src/service/player/trait.d.ts +47 -2
  35. package/dist/esm/src/service/player/trait.js +55 -1
  36. package/dist/esm/src/stat-config/traits.json +87 -0
  37. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  import type { BlockFailure, BlockType } from './block';
2
+ import type { FreeBallFailure, FreeBallType } from './free-ball';
2
3
  import type { ReceptionFailure, ReceptionType } from './reception';
3
4
  import type { ServeFailure, ServeType } from './serve';
4
5
  import type { SetFailure, SetType } from './set';
5
6
  import type { SpikeFailure, SpikeType } from './spike';
6
- export type EventSubType = BlockType | ReceptionType | ServeType | SetType | SpikeType;
7
- export type EventFailureType = BlockFailure | ReceptionFailure | ServeFailure | SetFailure | SpikeFailure;
7
+ export type EventSubType = BlockType | FreeBallType | ReceptionType | ServeType | SetType | SpikeType;
8
+ export type EventFailureType = BlockFailure | FreeBallFailure | ReceptionFailure | ServeFailure | SetFailure | SpikeFailure;
@@ -0,0 +1,17 @@
1
+ import { InPlayEvent } from './in-play-event';
2
+ export declare enum FreeBallTypeEnum {
3
+ OVER = 0,
4
+ STAY = 1
5
+ }
6
+ export type FreeBallType = FreeBallTypeEnum.OVER | FreeBallTypeEnum.STAY;
7
+ export declare enum FreeBallFailureEnum {
8
+ NO_FAILURE = 0
9
+ }
10
+ export type FreeBallFailure = FreeBallFailureEnum.NO_FAILURE;
11
+ export declare class FreeBall extends InPlayEvent<FreeBallFailure, FreeBallType> {
12
+ readonly failure: FreeBallFailure;
13
+ readonly type: FreeBallType;
14
+ static create(input: unknown): FreeBall;
15
+ private constructor();
16
+ toString(): string;
17
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FreeBall = exports.FreeBallFailureEnum = exports.FreeBallTypeEnum = void 0;
4
+ const rally_event_1 = require("./rally-event");
5
+ const in_play_event_1 = require("./in-play-event");
6
+ const free_ball_z_1 = require("./schemas/free-ball.z");
7
+ var FreeBallTypeEnum;
8
+ (function (FreeBallTypeEnum) {
9
+ // The save sent the ball over the net as a free ball -- the opponent plays it out.
10
+ FreeBallTypeEnum[FreeBallTypeEnum["OVER"] = 0] = "OVER";
11
+ // The save kept the ball on the team's side -- the team plays out its remaining touch(es).
12
+ FreeBallTypeEnum[FreeBallTypeEnum["STAY"] = 1] = "STAY";
13
+ })(FreeBallTypeEnum || (exports.FreeBallTypeEnum = FreeBallTypeEnum = {}));
14
+ var FreeBallFailureEnum;
15
+ (function (FreeBallFailureEnum) {
16
+ // A Last Resort save is guaranteed -- it always produces a free ball, never an error.
17
+ FreeBallFailureEnum[FreeBallFailureEnum["NO_FAILURE"] = 0] = "NO_FAILURE";
18
+ })(FreeBallFailureEnum || (exports.FreeBallFailureEnum = FreeBallFailureEnum = {}));
19
+ const eventType = rally_event_1.EventTypeEnum.FREE_BALL;
20
+ // A FREE_BALL event is a Last Resort save: the libero digs a ball that would otherwise have lost the rally and
21
+ // puts it back in play as a neutral free ball (over to the opponent, or staying on side). It is a real in-play
22
+ // event (score + target) so the next receiver handles it like any free ball.
23
+ class FreeBall extends in_play_event_1.InPlayEvent {
24
+ static create(input) {
25
+ const result = free_ball_z_1.FreeBallInputSchema.safeParse(input);
26
+ if (!result.success) {
27
+ throw new Error(`INVALID_FREE_BALL: ${result.error.message}. RECEIVED=${(() => { try {
28
+ return JSON.stringify(input);
29
+ }
30
+ catch {
31
+ return String(input);
32
+ } })()}`);
33
+ }
34
+ return new FreeBall(result.data);
35
+ }
36
+ constructor({ score, playerId, target, failure, type, activeTraits }) {
37
+ super({ score, playerId, target, activeTraits, eventType });
38
+ this.type = type;
39
+ this.failure = failure;
40
+ }
41
+ toString() {
42
+ const failure = `"failure":${this.failure}`;
43
+ const type = `"type":${this.type}`;
44
+ return `{${this._toString()},${failure},${type}}`;
45
+ }
46
+ }
47
+ exports.FreeBall = FreeBall;
@@ -1,5 +1,6 @@
1
1
  export * from './block';
2
2
  export * from './event-types';
3
+ export * from './free-ball';
3
4
  export * from './in-play-event';
4
5
  export * from './libero-replacement';
5
6
  export * from './rally-event';
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./block"), exports);
18
18
  __exportStar(require("./event-types"), exports);
19
+ __exportStar(require("./free-ball"), exports);
19
20
  __exportStar(require("./in-play-event"), exports);
20
21
  __exportStar(require("./libero-replacement"), exports);
21
22
  __exportStar(require("./rally-event"), exports);
@@ -5,9 +5,10 @@ export declare enum EventTypeEnum {
5
5
  RECEPTION = 3,
6
6
  SET = 4,
7
7
  SPIKE = 5,
8
- BLOCK = 6
8
+ BLOCK = 6,
9
+ FREE_BALL = 7
9
10
  }
10
- export type EventType = EventTypeEnum.LIBERO_REPLACEMENT | EventTypeEnum.SUBSTITUTION | EventTypeEnum.SERVE | EventTypeEnum.RECEPTION | EventTypeEnum.SET | EventTypeEnum.SPIKE | EventTypeEnum.BLOCK;
11
+ export type EventType = EventTypeEnum.LIBERO_REPLACEMENT | EventTypeEnum.SUBSTITUTION | EventTypeEnum.SERVE | EventTypeEnum.RECEPTION | EventTypeEnum.SET | EventTypeEnum.SPIKE | EventTypeEnum.BLOCK | EventTypeEnum.FREE_BALL;
11
12
  export interface RallyEventOpts {
12
13
  readonly playerId: string;
13
14
  readonly eventType: EventType;
@@ -10,6 +10,7 @@ var EventTypeEnum;
10
10
  EventTypeEnum[EventTypeEnum["SET"] = 4] = "SET";
11
11
  EventTypeEnum[EventTypeEnum["SPIKE"] = 5] = "SPIKE";
12
12
  EventTypeEnum[EventTypeEnum["BLOCK"] = 6] = "BLOCK";
13
+ EventTypeEnum[EventTypeEnum["FREE_BALL"] = 7] = "FREE_BALL";
13
14
  })(EventTypeEnum || (exports.EventTypeEnum = EventTypeEnum = {}));
14
15
  class RallyEvent {
15
16
  constructor({ eventType, playerId }) {
@@ -14,6 +14,15 @@ export declare const BlockInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import { TraitEnum } from '../../player';
3
+ export declare const FreeBallInputSchema: z.ZodObject<{
4
+ playerId: z.ZodUUID;
5
+ score: z.ZodNumber;
6
+ target: z.ZodNumber;
7
+ activeTraits: z.ZodDefault<z.ZodArray<z.ZodEnum<{
8
+ MASTER_MIND: TraitEnum.MASTER_MIND;
9
+ MOVING_WALL: TraitEnum.MOVING_WALL;
10
+ SURPRISE_ATTACK: TraitEnum.SURPRISE_ATTACK;
11
+ MARKSMAN: TraitEnum.MARKSMAN;
12
+ METEOR_SERVE: TraitEnum.METEOR_SERVE;
13
+ VIGOROUS: TraitEnum.VIGOROUS;
14
+ GUARDIAN: TraitEnum.GUARDIAN;
15
+ FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
+ DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
26
+ }>>>;
27
+ failure: z.ZodNumber;
28
+ type: z.ZodNumber;
29
+ }, z.core.$strip>;
30
+ export type FreeBallInput = z.infer<typeof FreeBallInputSchema>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FreeBallInputSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const player_1 = require("../../player");
6
+ const freeBallTypeValues = [0, 1];
7
+ const freeBallFailureValues = [0];
8
+ const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
9
+ exports.FreeBallInputSchema = zod_1.z.object({
10
+ playerId: zod_1.z.uuid(),
11
+ score: zod_1.z.number().finite(),
12
+ target: zod_1.z.number().int().refine((v) => courtTargetValues.includes(v), { message: 'INVALID_COURT_TARGET' }),
13
+ activeTraits: zod_1.z.array(zod_1.z.enum(Object.values(player_1.TraitEnum))).default([]),
14
+ failure: zod_1.z.number().int().refine((v) => freeBallFailureValues.includes(v), { message: 'INVALID_FREE_BALL_FAILURE' }),
15
+ type: zod_1.z.number().int().refine((v) => freeBallTypeValues.includes(v), { message: 'INVALID_FREE_BALL_TYPE' })
16
+ });
@@ -14,6 +14,15 @@ export declare const ReceptionInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const ServeInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const SetInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const SpikeInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -53,6 +53,15 @@ export declare const PlayerInputSchema: z.ZodObject<{
53
53
  GUARDIAN: TraitEnum.GUARDIAN;
54
54
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
55
55
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
56
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
57
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
58
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
59
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
60
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
61
+ CRAFTY: TraitEnum.CRAFTY;
62
+ TENACIOUS: TraitEnum.TENACIOUS;
63
+ HUNTER: TraitEnum.HUNTER;
64
+ LAST_RESORT: TraitEnum.LAST_RESORT;
56
65
  }>>;
57
66
  rarity: z.ZodEnum<{
58
67
  COMMON: RarityEnum.COMMON;
@@ -5,6 +5,7 @@ interface TraitProps {
5
5
  readonly modifier?: number;
6
6
  readonly chance: number;
7
7
  readonly roles: Role[];
8
+ readonly maxRarity?: Rarity;
8
9
  }
9
10
  export declare enum TraitEnum {
10
11
  /**
@@ -45,9 +46,53 @@ export declare enum TraitEnum {
45
46
  /**
46
47
  * Gives the player a chance to perform a perfect reception.
47
48
  */
48
- DEFENSIVE_SPECIALIST = "DEFENSIVE_SPECIALIST"
49
+ DEFENSIVE_SPECIALIST = "DEFENSIVE_SPECIALIST",
50
+ /**
51
+ * Three good receptions/digs/sets in a row make the next reception/dig/set a guaranteed critical that
52
+ * cannot fail.
53
+ */
54
+ QUICK_LEARNER = "QUICK_LEARNER",
55
+ /**
56
+ * Each successful attack ramps up the spike, and a kill ramps it more. Starts with a small penalty and
57
+ * resets on an attacking error.
58
+ */
59
+ MOMENTUM_ADDICT = "MOMENTUM_ADDICT",
60
+ /**
61
+ * A front-row kill demoralizes the opponents who faced it, debuffing them for a few rallies.
62
+ */
63
+ LOUDMOUTH = "LOUDMOUTH",
64
+ /**
65
+ * When a teammate makes a non-block mistake, that teammate's next action cannot fail.
66
+ */
67
+ TRUE_LEADER = "TRUE_LEADER",
68
+ /**
69
+ * Reacting to a strong opponent action (block/receive), this player can rise to match it. Limited to
70
+ * Mythic rarity and below.
71
+ */
72
+ OVERACHIEVER = "OVERACHIEVER",
73
+ /**
74
+ * Opponents who interact with this player's serve or attack spend twice the energy (VIGOROUS is nullified).
75
+ */
76
+ CRAFTY = "CRAFTY",
77
+ /**
78
+ * When the team loses several points in a row, the whole on-court team gets a buff that grows with the
79
+ * streak and disappears the moment they win a point.
80
+ */
81
+ TENACIOUS = "TENACIOUS",
82
+ /**
83
+ * Serve trait, rolled before the ambient setter/opposite hunt: a chance to serve a precise target (the
84
+ * setter, opposite, or best back-row attacker, depending on serve type and rotation), bypassing GUARDIAN,
85
+ * with a slight serve bonus and reducing the hunted player's attack involvement that rally.
86
+ */
87
+ HUNTER = "HUNTER",
88
+ /**
89
+ * Libero only. When a teammate's touch would lose the rally (a reception/dig error, a set error, or a failed
90
+ * dig of a blocked-back ball) and a save is still legal, a small chance the libero digs it into a neutral free
91
+ * ball, keeping the rally alive (over the net, or staying on side, depending on the touch).
92
+ */
93
+ LAST_RESORT = "LAST_RESORT"
49
94
  }
50
- export type Trait = TraitEnum.FLOAT_SERVE | TraitEnum.SURPRISE_ATTACK | TraitEnum.GUARDIAN | TraitEnum.MASTER_MIND | TraitEnum.MOVING_WALL | TraitEnum.MARKSMAN | TraitEnum.METEOR_SERVE | TraitEnum.VIGOROUS | TraitEnum.DEFENSIVE_SPECIALIST;
95
+ export type Trait = TraitEnum.FLOAT_SERVE | TraitEnum.SURPRISE_ATTACK | TraitEnum.GUARDIAN | TraitEnum.MASTER_MIND | TraitEnum.MOVING_WALL | TraitEnum.MARKSMAN | TraitEnum.METEOR_SERVE | TraitEnum.VIGOROUS | TraitEnum.DEFENSIVE_SPECIALIST | TraitEnum.QUICK_LEARNER | TraitEnum.MOMENTUM_ADDICT | TraitEnum.LOUDMOUTH | TraitEnum.TRUE_LEADER | TraitEnum.OVERACHIEVER | TraitEnum.CRAFTY | TraitEnum.TENACIOUS | TraitEnum.HUNTER | TraitEnum.LAST_RESORT;
51
96
  export declare function assignTraits(roles: Role[], rarity: Rarity, maxTraits?: boolean): Trait[];
52
97
  export declare const traitMap: Map<Trait, TraitProps>;
53
98
  export {};
@@ -10,6 +10,14 @@ const node_crypto_1 = require("node:crypto");
10
10
  const traits_json_1 = __importDefault(require("../../stat-config/traits.json"));
11
11
  const utils_1 = require("../utils");
12
12
  const traits = traits_json_1.default;
13
+ // Ascending rarity order, used by the maxRarity trait-eligibility gate in assignTraits.
14
+ const RARITY_RANK = {
15
+ [rarity_1.RarityEnum.COMMON]: 0,
16
+ [rarity_1.RarityEnum.RARE]: 1,
17
+ [rarity_1.RarityEnum.LEGENDARY]: 2,
18
+ [rarity_1.RarityEnum.MYTHIC]: 3,
19
+ [rarity_1.RarityEnum.SPECIAL]: 4
20
+ };
13
21
  var TraitEnum;
14
22
  (function (TraitEnum) {
15
23
  /**
@@ -51,6 +59,50 @@ var TraitEnum;
51
59
  * Gives the player a chance to perform a perfect reception.
52
60
  */
53
61
  TraitEnum["DEFENSIVE_SPECIALIST"] = "DEFENSIVE_SPECIALIST";
62
+ /**
63
+ * Three good receptions/digs/sets in a row make the next reception/dig/set a guaranteed critical that
64
+ * cannot fail.
65
+ */
66
+ TraitEnum["QUICK_LEARNER"] = "QUICK_LEARNER";
67
+ /**
68
+ * Each successful attack ramps up the spike, and a kill ramps it more. Starts with a small penalty and
69
+ * resets on an attacking error.
70
+ */
71
+ TraitEnum["MOMENTUM_ADDICT"] = "MOMENTUM_ADDICT";
72
+ /**
73
+ * A front-row kill demoralizes the opponents who faced it, debuffing them for a few rallies.
74
+ */
75
+ TraitEnum["LOUDMOUTH"] = "LOUDMOUTH";
76
+ /**
77
+ * When a teammate makes a non-block mistake, that teammate's next action cannot fail.
78
+ */
79
+ TraitEnum["TRUE_LEADER"] = "TRUE_LEADER";
80
+ /**
81
+ * Reacting to a strong opponent action (block/receive), this player can rise to match it. Limited to
82
+ * Mythic rarity and below.
83
+ */
84
+ TraitEnum["OVERACHIEVER"] = "OVERACHIEVER";
85
+ /**
86
+ * Opponents who interact with this player's serve or attack spend twice the energy (VIGOROUS is nullified).
87
+ */
88
+ TraitEnum["CRAFTY"] = "CRAFTY";
89
+ /**
90
+ * When the team loses several points in a row, the whole on-court team gets a buff that grows with the
91
+ * streak and disappears the moment they win a point.
92
+ */
93
+ TraitEnum["TENACIOUS"] = "TENACIOUS";
94
+ /**
95
+ * Serve trait, rolled before the ambient setter/opposite hunt: a chance to serve a precise target (the
96
+ * setter, opposite, or best back-row attacker, depending on serve type and rotation), bypassing GUARDIAN,
97
+ * with a slight serve bonus and reducing the hunted player's attack involvement that rally.
98
+ */
99
+ TraitEnum["HUNTER"] = "HUNTER";
100
+ /**
101
+ * Libero only. When a teammate's touch would lose the rally (a reception/dig error, a set error, or a failed
102
+ * dig of a blocked-back ball) and a save is still legal, a small chance the libero digs it into a neutral free
103
+ * ball, keeping the rally alive (over the net, or staying on side, depending on the touch).
104
+ */
105
+ TraitEnum["LAST_RESORT"] = "LAST_RESORT";
54
106
  })(TraitEnum || (exports.TraitEnum = TraitEnum = {}));
55
107
  function assignTraits(roles, rarity, maxTraits) {
56
108
  let traitCount;
@@ -82,7 +134,9 @@ function assignTraits(roles, rarity, maxTraits) {
82
134
  return [];
83
135
  const filteredTraits = traits.filter((trait) => {
84
136
  const setA = new Set(trait.roles);
85
- return roles.some(el => setA.has(el));
137
+ const roleMatch = roles.some(el => setA.has(el));
138
+ const rarityOk = trait.maxRarity == null || RARITY_RANK[rarity] <= RARITY_RANK[trait.maxRarity];
139
+ return roleMatch && rarityOk;
86
140
  }).map(trait => trait.name);
87
141
  return (0, utils_1.shuffle)(filteredTraits).slice(0, traitCount);
88
142
  }
@@ -90,5 +90,92 @@
90
90
  "OUTSIDE_HITTER",
91
91
  "MIDDLE_BLOCKER"
92
92
  ]
93
+ },
94
+ {
95
+ "name": "QUICK_LEARNER",
96
+ "chance": 1,
97
+ "roles": [
98
+ "SETTER",
99
+ "LIBERO",
100
+ "OUTSIDE_HITTER"
101
+ ]
102
+ },
103
+ {
104
+ "name": "MOMENTUM_ADDICT",
105
+ "chance": 1,
106
+ "roles": [
107
+ "OUTSIDE_HITTER",
108
+ "OPPOSITE_HITTER"
109
+ ]
110
+ },
111
+ {
112
+ "name": "LOUDMOUTH",
113
+ "chance": 0.3333,
114
+ "roles": [
115
+ "OUTSIDE_HITTER",
116
+ "OPPOSITE_HITTER",
117
+ "MIDDLE_BLOCKER"
118
+ ]
119
+ },
120
+ {
121
+ "name": "TRUE_LEADER",
122
+ "chance": 0.3,
123
+ "roles": [
124
+ "SETTER",
125
+ "OUTSIDE_HITTER",
126
+ "OPPOSITE_HITTER",
127
+ "MIDDLE_BLOCKER",
128
+ "LIBERO"
129
+ ]
130
+ },
131
+ {
132
+ "name": "OVERACHIEVER",
133
+ "chance": 0.2,
134
+ "maxRarity": "MYTHIC",
135
+ "roles": [
136
+ "SETTER",
137
+ "OUTSIDE_HITTER",
138
+ "OPPOSITE_HITTER",
139
+ "MIDDLE_BLOCKER",
140
+ "LIBERO"
141
+ ]
142
+ },
143
+ {
144
+ "name": "CRAFTY",
145
+ "chance": 0.25,
146
+ "roles": [
147
+ "SETTER",
148
+ "OUTSIDE_HITTER",
149
+ "OPPOSITE_HITTER",
150
+ "MIDDLE_BLOCKER"
151
+ ]
152
+ },
153
+ {
154
+ "name": "TENACIOUS",
155
+ "chance": 1,
156
+ "roles": [
157
+ "SETTER",
158
+ "OUTSIDE_HITTER",
159
+ "OPPOSITE_HITTER",
160
+ "MIDDLE_BLOCKER",
161
+ "LIBERO"
162
+ ]
163
+ },
164
+ {
165
+ "name": "HUNTER",
166
+ "chance": 0.15,
167
+ "roles": [
168
+ "SETTER",
169
+ "OUTSIDE_HITTER",
170
+ "OPPOSITE_HITTER",
171
+ "MIDDLE_BLOCKER"
172
+ ]
173
+ },
174
+ {
175
+ "name": "LAST_RESORT",
176
+ "chance": 0.05,
177
+ "roles": [
178
+ "LIBERO"
179
+ ]
93
180
  }
94
181
  ]
@@ -1,7 +1,8 @@
1
1
  import type { BlockFailure, BlockType } from './block';
2
+ import type { FreeBallFailure, FreeBallType } from './free-ball';
2
3
  import type { ReceptionFailure, ReceptionType } from './reception';
3
4
  import type { ServeFailure, ServeType } from './serve';
4
5
  import type { SetFailure, SetType } from './set';
5
6
  import type { SpikeFailure, SpikeType } from './spike';
6
- export type EventSubType = BlockType | ReceptionType | ServeType | SetType | SpikeType;
7
- export type EventFailureType = BlockFailure | ReceptionFailure | ServeFailure | SetFailure | SpikeFailure;
7
+ export type EventSubType = BlockType | FreeBallType | ReceptionType | ServeType | SetType | SpikeType;
8
+ export type EventFailureType = BlockFailure | FreeBallFailure | ReceptionFailure | ServeFailure | SetFailure | SpikeFailure;
@@ -0,0 +1,17 @@
1
+ import { InPlayEvent } from './in-play-event';
2
+ export declare enum FreeBallTypeEnum {
3
+ OVER = 0,
4
+ STAY = 1
5
+ }
6
+ export type FreeBallType = FreeBallTypeEnum.OVER | FreeBallTypeEnum.STAY;
7
+ export declare enum FreeBallFailureEnum {
8
+ NO_FAILURE = 0
9
+ }
10
+ export type FreeBallFailure = FreeBallFailureEnum.NO_FAILURE;
11
+ export declare class FreeBall extends InPlayEvent<FreeBallFailure, FreeBallType> {
12
+ readonly failure: FreeBallFailure;
13
+ readonly type: FreeBallType;
14
+ static create(input: unknown): FreeBall;
15
+ private constructor();
16
+ toString(): string;
17
+ }
@@ -0,0 +1,43 @@
1
+ import { EventTypeEnum } from './rally-event';
2
+ import { InPlayEvent } from './in-play-event';
3
+ import { FreeBallInputSchema } from './schemas/free-ball.z';
4
+ export var FreeBallTypeEnum;
5
+ (function (FreeBallTypeEnum) {
6
+ // The save sent the ball over the net as a free ball -- the opponent plays it out.
7
+ FreeBallTypeEnum[FreeBallTypeEnum["OVER"] = 0] = "OVER";
8
+ // The save kept the ball on the team's side -- the team plays out its remaining touch(es).
9
+ FreeBallTypeEnum[FreeBallTypeEnum["STAY"] = 1] = "STAY";
10
+ })(FreeBallTypeEnum || (FreeBallTypeEnum = {}));
11
+ export var FreeBallFailureEnum;
12
+ (function (FreeBallFailureEnum) {
13
+ // A Last Resort save is guaranteed -- it always produces a free ball, never an error.
14
+ FreeBallFailureEnum[FreeBallFailureEnum["NO_FAILURE"] = 0] = "NO_FAILURE";
15
+ })(FreeBallFailureEnum || (FreeBallFailureEnum = {}));
16
+ const eventType = EventTypeEnum.FREE_BALL;
17
+ // A FREE_BALL event is a Last Resort save: the libero digs a ball that would otherwise have lost the rally and
18
+ // puts it back in play as a neutral free ball (over to the opponent, or staying on side). It is a real in-play
19
+ // event (score + target) so the next receiver handles it like any free ball.
20
+ export class FreeBall extends InPlayEvent {
21
+ static create(input) {
22
+ const result = FreeBallInputSchema.safeParse(input);
23
+ if (!result.success) {
24
+ throw new Error(`INVALID_FREE_BALL: ${result.error.message}. RECEIVED=${(() => { try {
25
+ return JSON.stringify(input);
26
+ }
27
+ catch {
28
+ return String(input);
29
+ } })()}`);
30
+ }
31
+ return new FreeBall(result.data);
32
+ }
33
+ constructor({ score, playerId, target, failure, type, activeTraits }) {
34
+ super({ score, playerId, target, activeTraits, eventType });
35
+ this.type = type;
36
+ this.failure = failure;
37
+ }
38
+ toString() {
39
+ const failure = `"failure":${this.failure}`;
40
+ const type = `"type":${this.type}`;
41
+ return `{${this._toString()},${failure},${type}}`;
42
+ }
43
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './block';
2
2
  export * from './event-types';
3
+ export * from './free-ball';
3
4
  export * from './in-play-event';
4
5
  export * from './libero-replacement';
5
6
  export * from './rally-event';
@@ -1,5 +1,6 @@
1
1
  export * from './block';
2
2
  export * from './event-types';
3
+ export * from './free-ball';
3
4
  export * from './in-play-event';
4
5
  export * from './libero-replacement';
5
6
  export * from './rally-event';
@@ -5,9 +5,10 @@ export declare enum EventTypeEnum {
5
5
  RECEPTION = 3,
6
6
  SET = 4,
7
7
  SPIKE = 5,
8
- BLOCK = 6
8
+ BLOCK = 6,
9
+ FREE_BALL = 7
9
10
  }
10
- export type EventType = EventTypeEnum.LIBERO_REPLACEMENT | EventTypeEnum.SUBSTITUTION | EventTypeEnum.SERVE | EventTypeEnum.RECEPTION | EventTypeEnum.SET | EventTypeEnum.SPIKE | EventTypeEnum.BLOCK;
11
+ export type EventType = EventTypeEnum.LIBERO_REPLACEMENT | EventTypeEnum.SUBSTITUTION | EventTypeEnum.SERVE | EventTypeEnum.RECEPTION | EventTypeEnum.SET | EventTypeEnum.SPIKE | EventTypeEnum.BLOCK | EventTypeEnum.FREE_BALL;
11
12
  export interface RallyEventOpts {
12
13
  readonly playerId: string;
13
14
  readonly eventType: EventType;
@@ -7,6 +7,7 @@ export var EventTypeEnum;
7
7
  EventTypeEnum[EventTypeEnum["SET"] = 4] = "SET";
8
8
  EventTypeEnum[EventTypeEnum["SPIKE"] = 5] = "SPIKE";
9
9
  EventTypeEnum[EventTypeEnum["BLOCK"] = 6] = "BLOCK";
10
+ EventTypeEnum[EventTypeEnum["FREE_BALL"] = 7] = "FREE_BALL";
10
11
  })(EventTypeEnum || (EventTypeEnum = {}));
11
12
  export class RallyEvent {
12
13
  constructor({ eventType, playerId }) {
@@ -14,6 +14,15 @@ export declare const BlockInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import { TraitEnum } from '../../player';
3
+ export declare const FreeBallInputSchema: z.ZodObject<{
4
+ playerId: z.ZodUUID;
5
+ score: z.ZodNumber;
6
+ target: z.ZodNumber;
7
+ activeTraits: z.ZodDefault<z.ZodArray<z.ZodEnum<{
8
+ MASTER_MIND: TraitEnum.MASTER_MIND;
9
+ MOVING_WALL: TraitEnum.MOVING_WALL;
10
+ SURPRISE_ATTACK: TraitEnum.SURPRISE_ATTACK;
11
+ MARKSMAN: TraitEnum.MARKSMAN;
12
+ METEOR_SERVE: TraitEnum.METEOR_SERVE;
13
+ VIGOROUS: TraitEnum.VIGOROUS;
14
+ GUARDIAN: TraitEnum.GUARDIAN;
15
+ FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
+ DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
26
+ }>>>;
27
+ failure: z.ZodNumber;
28
+ type: z.ZodNumber;
29
+ }, z.core.$strip>;
30
+ export type FreeBallInput = z.infer<typeof FreeBallInputSchema>;
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import { TraitEnum } from '../../player';
3
+ const freeBallTypeValues = [0, 1];
4
+ const freeBallFailureValues = [0];
5
+ const courtTargetValues = Array.from({ length: 13 }, (_, i) => i);
6
+ export const FreeBallInputSchema = z.object({
7
+ playerId: z.uuid(),
8
+ score: z.number().finite(),
9
+ target: z.number().int().refine((v) => courtTargetValues.includes(v), { message: 'INVALID_COURT_TARGET' }),
10
+ activeTraits: z.array(z.enum(Object.values(TraitEnum))).default([]),
11
+ failure: z.number().int().refine((v) => freeBallFailureValues.includes(v), { message: 'INVALID_FREE_BALL_FAILURE' }),
12
+ type: z.number().int().refine((v) => freeBallTypeValues.includes(v), { message: 'INVALID_FREE_BALL_TYPE' })
13
+ });
@@ -14,6 +14,15 @@ export declare const ReceptionInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const ServeInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const SetInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -14,6 +14,15 @@ export declare const SpikeInputSchema: z.ZodObject<{
14
14
  GUARDIAN: TraitEnum.GUARDIAN;
15
15
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
16
16
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
17
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
18
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
19
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
20
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
21
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
22
+ CRAFTY: TraitEnum.CRAFTY;
23
+ TENACIOUS: TraitEnum.TENACIOUS;
24
+ HUNTER: TraitEnum.HUNTER;
25
+ LAST_RESORT: TraitEnum.LAST_RESORT;
17
26
  }>>>;
18
27
  failure: z.ZodNumber;
19
28
  type: z.ZodNumber;
@@ -53,6 +53,15 @@ export declare const PlayerInputSchema: z.ZodObject<{
53
53
  GUARDIAN: TraitEnum.GUARDIAN;
54
54
  FLOAT_SERVE: TraitEnum.FLOAT_SERVE;
55
55
  DEFENSIVE_SPECIALIST: TraitEnum.DEFENSIVE_SPECIALIST;
56
+ QUICK_LEARNER: TraitEnum.QUICK_LEARNER;
57
+ MOMENTUM_ADDICT: TraitEnum.MOMENTUM_ADDICT;
58
+ LOUDMOUTH: TraitEnum.LOUDMOUTH;
59
+ TRUE_LEADER: TraitEnum.TRUE_LEADER;
60
+ OVERACHIEVER: TraitEnum.OVERACHIEVER;
61
+ CRAFTY: TraitEnum.CRAFTY;
62
+ TENACIOUS: TraitEnum.TENACIOUS;
63
+ HUNTER: TraitEnum.HUNTER;
64
+ LAST_RESORT: TraitEnum.LAST_RESORT;
56
65
  }>>;
57
66
  rarity: z.ZodEnum<{
58
67
  COMMON: RarityEnum.COMMON;
@@ -5,6 +5,7 @@ interface TraitProps {
5
5
  readonly modifier?: number;
6
6
  readonly chance: number;
7
7
  readonly roles: Role[];
8
+ readonly maxRarity?: Rarity;
8
9
  }
9
10
  export declare enum TraitEnum {
10
11
  /**
@@ -45,9 +46,53 @@ export declare enum TraitEnum {
45
46
  /**
46
47
  * Gives the player a chance to perform a perfect reception.
47
48
  */
48
- DEFENSIVE_SPECIALIST = "DEFENSIVE_SPECIALIST"
49
+ DEFENSIVE_SPECIALIST = "DEFENSIVE_SPECIALIST",
50
+ /**
51
+ * Three good receptions/digs/sets in a row make the next reception/dig/set a guaranteed critical that
52
+ * cannot fail.
53
+ */
54
+ QUICK_LEARNER = "QUICK_LEARNER",
55
+ /**
56
+ * Each successful attack ramps up the spike, and a kill ramps it more. Starts with a small penalty and
57
+ * resets on an attacking error.
58
+ */
59
+ MOMENTUM_ADDICT = "MOMENTUM_ADDICT",
60
+ /**
61
+ * A front-row kill demoralizes the opponents who faced it, debuffing them for a few rallies.
62
+ */
63
+ LOUDMOUTH = "LOUDMOUTH",
64
+ /**
65
+ * When a teammate makes a non-block mistake, that teammate's next action cannot fail.
66
+ */
67
+ TRUE_LEADER = "TRUE_LEADER",
68
+ /**
69
+ * Reacting to a strong opponent action (block/receive), this player can rise to match it. Limited to
70
+ * Mythic rarity and below.
71
+ */
72
+ OVERACHIEVER = "OVERACHIEVER",
73
+ /**
74
+ * Opponents who interact with this player's serve or attack spend twice the energy (VIGOROUS is nullified).
75
+ */
76
+ CRAFTY = "CRAFTY",
77
+ /**
78
+ * When the team loses several points in a row, the whole on-court team gets a buff that grows with the
79
+ * streak and disappears the moment they win a point.
80
+ */
81
+ TENACIOUS = "TENACIOUS",
82
+ /**
83
+ * Serve trait, rolled before the ambient setter/opposite hunt: a chance to serve a precise target (the
84
+ * setter, opposite, or best back-row attacker, depending on serve type and rotation), bypassing GUARDIAN,
85
+ * with a slight serve bonus and reducing the hunted player's attack involvement that rally.
86
+ */
87
+ HUNTER = "HUNTER",
88
+ /**
89
+ * Libero only. When a teammate's touch would lose the rally (a reception/dig error, a set error, or a failed
90
+ * dig of a blocked-back ball) and a save is still legal, a small chance the libero digs it into a neutral free
91
+ * ball, keeping the rally alive (over the net, or staying on side, depending on the touch).
92
+ */
93
+ LAST_RESORT = "LAST_RESORT"
49
94
  }
50
- export type Trait = TraitEnum.FLOAT_SERVE | TraitEnum.SURPRISE_ATTACK | TraitEnum.GUARDIAN | TraitEnum.MASTER_MIND | TraitEnum.MOVING_WALL | TraitEnum.MARKSMAN | TraitEnum.METEOR_SERVE | TraitEnum.VIGOROUS | TraitEnum.DEFENSIVE_SPECIALIST;
95
+ export type Trait = TraitEnum.FLOAT_SERVE | TraitEnum.SURPRISE_ATTACK | TraitEnum.GUARDIAN | TraitEnum.MASTER_MIND | TraitEnum.MOVING_WALL | TraitEnum.MARKSMAN | TraitEnum.METEOR_SERVE | TraitEnum.VIGOROUS | TraitEnum.DEFENSIVE_SPECIALIST | TraitEnum.QUICK_LEARNER | TraitEnum.MOMENTUM_ADDICT | TraitEnum.LOUDMOUTH | TraitEnum.TRUE_LEADER | TraitEnum.OVERACHIEVER | TraitEnum.CRAFTY | TraitEnum.TENACIOUS | TraitEnum.HUNTER | TraitEnum.LAST_RESORT;
51
96
  export declare function assignTraits(roles: Role[], rarity: Rarity, maxTraits?: boolean): Trait[];
52
97
  export declare const traitMap: Map<Trait, TraitProps>;
53
98
  export {};
@@ -3,6 +3,14 @@ import { randomInt } from 'node:crypto';
3
3
  import rawTraits from '../../stat-config/traits.json';
4
4
  import { shuffle } from '../utils';
5
5
  const traits = rawTraits;
6
+ // Ascending rarity order, used by the maxRarity trait-eligibility gate in assignTraits.
7
+ const RARITY_RANK = {
8
+ [RarityEnum.COMMON]: 0,
9
+ [RarityEnum.RARE]: 1,
10
+ [RarityEnum.LEGENDARY]: 2,
11
+ [RarityEnum.MYTHIC]: 3,
12
+ [RarityEnum.SPECIAL]: 4
13
+ };
6
14
  export var TraitEnum;
7
15
  (function (TraitEnum) {
8
16
  /**
@@ -44,6 +52,50 @@ export var TraitEnum;
44
52
  * Gives the player a chance to perform a perfect reception.
45
53
  */
46
54
  TraitEnum["DEFENSIVE_SPECIALIST"] = "DEFENSIVE_SPECIALIST";
55
+ /**
56
+ * Three good receptions/digs/sets in a row make the next reception/dig/set a guaranteed critical that
57
+ * cannot fail.
58
+ */
59
+ TraitEnum["QUICK_LEARNER"] = "QUICK_LEARNER";
60
+ /**
61
+ * Each successful attack ramps up the spike, and a kill ramps it more. Starts with a small penalty and
62
+ * resets on an attacking error.
63
+ */
64
+ TraitEnum["MOMENTUM_ADDICT"] = "MOMENTUM_ADDICT";
65
+ /**
66
+ * A front-row kill demoralizes the opponents who faced it, debuffing them for a few rallies.
67
+ */
68
+ TraitEnum["LOUDMOUTH"] = "LOUDMOUTH";
69
+ /**
70
+ * When a teammate makes a non-block mistake, that teammate's next action cannot fail.
71
+ */
72
+ TraitEnum["TRUE_LEADER"] = "TRUE_LEADER";
73
+ /**
74
+ * Reacting to a strong opponent action (block/receive), this player can rise to match it. Limited to
75
+ * Mythic rarity and below.
76
+ */
77
+ TraitEnum["OVERACHIEVER"] = "OVERACHIEVER";
78
+ /**
79
+ * Opponents who interact with this player's serve or attack spend twice the energy (VIGOROUS is nullified).
80
+ */
81
+ TraitEnum["CRAFTY"] = "CRAFTY";
82
+ /**
83
+ * When the team loses several points in a row, the whole on-court team gets a buff that grows with the
84
+ * streak and disappears the moment they win a point.
85
+ */
86
+ TraitEnum["TENACIOUS"] = "TENACIOUS";
87
+ /**
88
+ * Serve trait, rolled before the ambient setter/opposite hunt: a chance to serve a precise target (the
89
+ * setter, opposite, or best back-row attacker, depending on serve type and rotation), bypassing GUARDIAN,
90
+ * with a slight serve bonus and reducing the hunted player's attack involvement that rally.
91
+ */
92
+ TraitEnum["HUNTER"] = "HUNTER";
93
+ /**
94
+ * Libero only. When a teammate's touch would lose the rally (a reception/dig error, a set error, or a failed
95
+ * dig of a blocked-back ball) and a save is still legal, a small chance the libero digs it into a neutral free
96
+ * ball, keeping the rally alive (over the net, or staying on side, depending on the touch).
97
+ */
98
+ TraitEnum["LAST_RESORT"] = "LAST_RESORT";
47
99
  })(TraitEnum || (TraitEnum = {}));
48
100
  export function assignTraits(roles, rarity, maxTraits) {
49
101
  let traitCount;
@@ -75,7 +127,9 @@ export function assignTraits(roles, rarity, maxTraits) {
75
127
  return [];
76
128
  const filteredTraits = traits.filter((trait) => {
77
129
  const setA = new Set(trait.roles);
78
- return roles.some(el => setA.has(el));
130
+ const roleMatch = roles.some(el => setA.has(el));
131
+ const rarityOk = trait.maxRarity == null || RARITY_RANK[rarity] <= RARITY_RANK[trait.maxRarity];
132
+ return roleMatch && rarityOk;
79
133
  }).map(trait => trait.name);
80
134
  return shuffle(filteredTraits).slice(0, traitCount);
81
135
  }
@@ -90,5 +90,92 @@
90
90
  "OUTSIDE_HITTER",
91
91
  "MIDDLE_BLOCKER"
92
92
  ]
93
+ },
94
+ {
95
+ "name": "QUICK_LEARNER",
96
+ "chance": 1,
97
+ "roles": [
98
+ "SETTER",
99
+ "LIBERO",
100
+ "OUTSIDE_HITTER"
101
+ ]
102
+ },
103
+ {
104
+ "name": "MOMENTUM_ADDICT",
105
+ "chance": 1,
106
+ "roles": [
107
+ "OUTSIDE_HITTER",
108
+ "OPPOSITE_HITTER"
109
+ ]
110
+ },
111
+ {
112
+ "name": "LOUDMOUTH",
113
+ "chance": 0.3333,
114
+ "roles": [
115
+ "OUTSIDE_HITTER",
116
+ "OPPOSITE_HITTER",
117
+ "MIDDLE_BLOCKER"
118
+ ]
119
+ },
120
+ {
121
+ "name": "TRUE_LEADER",
122
+ "chance": 0.3,
123
+ "roles": [
124
+ "SETTER",
125
+ "OUTSIDE_HITTER",
126
+ "OPPOSITE_HITTER",
127
+ "MIDDLE_BLOCKER",
128
+ "LIBERO"
129
+ ]
130
+ },
131
+ {
132
+ "name": "OVERACHIEVER",
133
+ "chance": 0.2,
134
+ "maxRarity": "MYTHIC",
135
+ "roles": [
136
+ "SETTER",
137
+ "OUTSIDE_HITTER",
138
+ "OPPOSITE_HITTER",
139
+ "MIDDLE_BLOCKER",
140
+ "LIBERO"
141
+ ]
142
+ },
143
+ {
144
+ "name": "CRAFTY",
145
+ "chance": 0.25,
146
+ "roles": [
147
+ "SETTER",
148
+ "OUTSIDE_HITTER",
149
+ "OPPOSITE_HITTER",
150
+ "MIDDLE_BLOCKER"
151
+ ]
152
+ },
153
+ {
154
+ "name": "TENACIOUS",
155
+ "chance": 1,
156
+ "roles": [
157
+ "SETTER",
158
+ "OUTSIDE_HITTER",
159
+ "OPPOSITE_HITTER",
160
+ "MIDDLE_BLOCKER",
161
+ "LIBERO"
162
+ ]
163
+ },
164
+ {
165
+ "name": "HUNTER",
166
+ "chance": 0.15,
167
+ "roles": [
168
+ "SETTER",
169
+ "OUTSIDE_HITTER",
170
+ "OPPOSITE_HITTER",
171
+ "MIDDLE_BLOCKER"
172
+ ]
173
+ },
174
+ {
175
+ "name": "LAST_RESORT",
176
+ "chance": 0.05,
177
+ "roles": [
178
+ "LIBERO"
179
+ ]
93
180
  }
94
181
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.451",
3
+ "version": "0.0.453",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",