volleyballsimtypes 0.0.400 → 0.0.402
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.
- package/dist/cjs/src/api/index.d.ts +11 -1
- package/dist/cjs/src/data/models/currency-transaction.d.ts +2 -1
- package/dist/cjs/src/data/models/currency-transaction.js +1 -0
- package/dist/cjs/src/data/models/tactics.d.ts +15 -2
- package/dist/cjs/src/data/models/tactics.js +12 -0
- package/dist/cjs/src/data/transformers/tactics.js +6 -0
- package/dist/cjs/src/service/team/index.d.ts +1 -0
- package/dist/cjs/src/service/team/index.js +1 -0
- package/dist/cjs/src/service/team/libero-sub.d.ts +9 -0
- package/dist/cjs/src/service/team/libero-sub.js +2 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +31 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.js +20 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.test.d.ts +1 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.test.js +58 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +18 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.js +36 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.test.d.ts +1 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.test.js +73 -0
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +18 -0
- package/dist/cjs/src/service/team/tactics.d.ts +7 -0
- package/dist/cjs/src/service/team/tactics.js +4 -1
- package/dist/esm/src/api/index.d.ts +11 -1
- package/dist/esm/src/data/models/currency-transaction.d.ts +2 -1
- package/dist/esm/src/data/models/currency-transaction.js +1 -0
- package/dist/esm/src/data/models/tactics.d.ts +15 -2
- package/dist/esm/src/data/models/tactics.js +12 -0
- package/dist/esm/src/data/transformers/tactics.js +6 -0
- package/dist/esm/src/service/team/index.d.ts +1 -0
- package/dist/esm/src/service/team/index.js +1 -0
- package/dist/esm/src/service/team/libero-sub.d.ts +9 -0
- package/dist/esm/src/service/team/libero-sub.js +1 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +31 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.js +17 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.test.d.ts +1 -0
- package/dist/esm/src/service/team/schemas/libero-sub.z.test.js +56 -0
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +18 -0
- package/dist/esm/src/service/team/schemas/tactics.z.js +36 -0
- package/dist/esm/src/service/team/schemas/tactics.z.test.d.ts +1 -0
- package/dist/esm/src/service/team/schemas/tactics.z.test.js +71 -0
- package/dist/esm/src/service/team/schemas/team.z.d.ts +18 -0
- package/dist/esm/src/service/team/tactics.d.ts +7 -0
- package/dist/esm/src/service/team/tactics.js +4 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
|
|
1
|
+
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
|
|
2
2
|
export type Rally = DataProps<_Rally> & {
|
|
3
3
|
homePlayerPosition: PlayerPosition[];
|
|
4
4
|
awayPlayerPosition: PlayerPosition[];
|
|
@@ -76,6 +76,13 @@ export interface ApiOffensivePreference {
|
|
|
76
76
|
rotation: number;
|
|
77
77
|
order: string[];
|
|
78
78
|
}
|
|
79
|
+
export interface ApiLiberoSetSubConfig {
|
|
80
|
+
mode: LiberoSubMode;
|
|
81
|
+
fatigueBand?: EnergyBand;
|
|
82
|
+
}
|
|
83
|
+
export interface ApiLiberoSubConfig extends ApiLiberoSetSubConfig {
|
|
84
|
+
sets?: ApiLiberoSetSubConfig[];
|
|
85
|
+
}
|
|
79
86
|
export interface Tactics {
|
|
80
87
|
lineup: StartingLineup;
|
|
81
88
|
substitutionTolerance: number;
|
|
@@ -84,6 +91,9 @@ export interface Tactics {
|
|
|
84
91
|
receiveRotationOffset: boolean;
|
|
85
92
|
designatedSubs: ApiDesignatedSub[];
|
|
86
93
|
substitutionBand: SubBand;
|
|
94
|
+
secondLibero?: string;
|
|
95
|
+
liberoSub?: ApiLiberoSubConfig;
|
|
96
|
+
liberoReplacementSets?: string[][];
|
|
87
97
|
rotationSystem: RotationSystemEnum;
|
|
88
98
|
designatedSetters: string[];
|
|
89
99
|
offensivePreferences: ApiOffensivePreference[];
|
|
@@ -11,10 +11,11 @@ export declare enum CurrencyTransactionSourceEnum {
|
|
|
11
11
|
GACHA_PULL = "GACHA_PULL",
|
|
12
12
|
COACH_UPGRADE = "COACH_UPGRADE",
|
|
13
13
|
PLAYER_DISMISS = "PLAYER_DISMISS",
|
|
14
|
+
PLAYER_RETIRE = "PLAYER_RETIRE",
|
|
14
15
|
ADMIN_GRANT = "ADMIN_GRANT",
|
|
15
16
|
SEASON_REWARD = "SEASON_REWARD"
|
|
16
17
|
}
|
|
17
|
-
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD;
|
|
18
|
+
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD;
|
|
18
19
|
export interface CurrencyTransactionAttributes {
|
|
19
20
|
transaction_id: string;
|
|
20
21
|
user_id: string;
|
|
@@ -13,6 +13,7 @@ var CurrencyTransactionSourceEnum;
|
|
|
13
13
|
CurrencyTransactionSourceEnum["GACHA_PULL"] = "GACHA_PULL";
|
|
14
14
|
CurrencyTransactionSourceEnum["COACH_UPGRADE"] = "COACH_UPGRADE";
|
|
15
15
|
CurrencyTransactionSourceEnum["PLAYER_DISMISS"] = "PLAYER_DISMISS";
|
|
16
|
+
CurrencyTransactionSourceEnum["PLAYER_RETIRE"] = "PLAYER_RETIRE";
|
|
16
17
|
CurrencyTransactionSourceEnum["ADMIN_GRANT"] = "ADMIN_GRANT";
|
|
17
18
|
CurrencyTransactionSourceEnum["SEASON_REWARD"] = "SEASON_REWARD";
|
|
18
19
|
})(CurrencyTransactionSourceEnum || (exports.CurrencyTransactionSourceEnum = CurrencyTransactionSourceEnum = {}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Sequelize from 'sequelize';
|
|
2
2
|
import { Model, Optional } from 'sequelize';
|
|
3
3
|
import { PlayerId, TeamId, TeamModel } from '.';
|
|
4
|
-
import { ConditionLogic, CourtPosition, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
|
|
4
|
+
import { ConditionLogic, CourtPosition, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
|
|
5
5
|
export interface TacticsLineupAttributes {
|
|
6
6
|
[CourtPosition.LIBERO_ZONE]?: PlayerId;
|
|
7
7
|
[CourtPosition.LEFT_BACK]: PlayerId;
|
|
@@ -34,6 +34,13 @@ export interface OffensivePreferenceAttributes {
|
|
|
34
34
|
rotation: number;
|
|
35
35
|
order: PlayerId[];
|
|
36
36
|
}
|
|
37
|
+
export interface LiberoSetSubConfigAttributes {
|
|
38
|
+
mode: LiberoSubMode;
|
|
39
|
+
fatigueBand?: EnergyBand;
|
|
40
|
+
}
|
|
41
|
+
export interface LiberoSubConfigAttributes extends LiberoSetSubConfigAttributes {
|
|
42
|
+
sets?: LiberoSetSubConfigAttributes[];
|
|
43
|
+
}
|
|
37
44
|
export interface TacticsAttributes {
|
|
38
45
|
team_id: string;
|
|
39
46
|
substitution_tolerance: number;
|
|
@@ -43,13 +50,16 @@ export interface TacticsAttributes {
|
|
|
43
50
|
receive_rotation_offset: boolean;
|
|
44
51
|
designated_subs: DesignatedSubAttributes[];
|
|
45
52
|
substitution_band: SubBand;
|
|
53
|
+
second_libero?: PlayerId;
|
|
54
|
+
libero_sub?: LiberoSubConfigAttributes;
|
|
55
|
+
libero_replacement_sets?: PlayerId[][];
|
|
46
56
|
rotation_system: RotationSystemEnum;
|
|
47
57
|
designated_setters: PlayerId[];
|
|
48
58
|
offensive_preferences: OffensivePreferenceAttributes[];
|
|
49
59
|
}
|
|
50
60
|
export type TacticsPk = 'team_id';
|
|
51
61
|
export type TacticsId = TacticsModel[TacticsPk];
|
|
52
|
-
export type TacticsOptionalAttributes = 'substitution_tolerance' | 'lineup' | 'libero_replacements' | 'pinch_server_subs' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'rotation_system' | 'designated_setters' | 'offensive_preferences';
|
|
62
|
+
export type TacticsOptionalAttributes = 'substitution_tolerance' | 'lineup' | 'libero_replacements' | 'pinch_server_subs' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences';
|
|
53
63
|
export type TacticsCreationAttributes = Optional<TacticsAttributes, TacticsOptionalAttributes>;
|
|
54
64
|
export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreationAttributes> implements TacticsAttributes {
|
|
55
65
|
team_id: string;
|
|
@@ -60,6 +70,9 @@ export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreati
|
|
|
60
70
|
receive_rotation_offset: boolean;
|
|
61
71
|
designated_subs: DesignatedSubAttributes[];
|
|
62
72
|
substitution_band: SubBand;
|
|
73
|
+
second_libero?: PlayerId;
|
|
74
|
+
libero_sub?: LiberoSubConfigAttributes;
|
|
75
|
+
libero_replacement_sets?: PlayerId[][];
|
|
63
76
|
rotation_system: RotationSystemEnum;
|
|
64
77
|
designated_setters: PlayerId[];
|
|
65
78
|
offensive_preferences: OffensivePreferenceAttributes[];
|
|
@@ -50,6 +50,18 @@ class TacticsModel extends sequelize_1.Model {
|
|
|
50
50
|
allowNull: false,
|
|
51
51
|
defaultValue: 'TIRED'
|
|
52
52
|
},
|
|
53
|
+
second_libero: {
|
|
54
|
+
type: sequelize_1.DataTypes.UUID,
|
|
55
|
+
allowNull: true
|
|
56
|
+
},
|
|
57
|
+
libero_sub: {
|
|
58
|
+
type: sequelize_1.DataTypes.JSONB,
|
|
59
|
+
allowNull: true
|
|
60
|
+
},
|
|
61
|
+
libero_replacement_sets: {
|
|
62
|
+
type: sequelize_1.DataTypes.JSONB,
|
|
63
|
+
allowNull: true
|
|
64
|
+
},
|
|
53
65
|
rotation_system: {
|
|
54
66
|
type: sequelize_1.DataTypes.STRING,
|
|
55
67
|
allowNull: false,
|
|
@@ -54,6 +54,9 @@ function transformToAttributes(tactics, teamId) {
|
|
|
54
54
|
libero_replacements: tactics.liberoReplacements.map((lr) => lr.id),
|
|
55
55
|
pinch_server_subs: Object.fromEntries([...tactics.pinchServerSubs].map(([k, v]) => [k.id, v.id])),
|
|
56
56
|
substitution_band: tactics.substitutionBand,
|
|
57
|
+
second_libero: tactics.secondLibero?.id,
|
|
58
|
+
libero_sub: tactics.liberoSub,
|
|
59
|
+
libero_replacement_sets: tactics.liberoReplacementSets?.map(set => set.map((p) => p.id)),
|
|
57
60
|
designated_subs: tactics.designatedSubs.map(ds => ({
|
|
58
61
|
starterId: ds.starter.id,
|
|
59
62
|
benchId: ds.bench?.id,
|
|
@@ -92,6 +95,9 @@ function transformToObject(model, roster) {
|
|
|
92
95
|
liberoReplacements: model.libero_replacements.map((lr) => findPlayer(lr, roster)),
|
|
93
96
|
pinchServerSubs: buildPinchServerSubs(model.pinch_server_subs, roster),
|
|
94
97
|
substitutionBand: model.substitution_band,
|
|
98
|
+
secondLibero: model.second_libero != null ? findPlayer(model.second_libero, roster) : undefined,
|
|
99
|
+
liberoSub: model.libero_sub ?? undefined,
|
|
100
|
+
liberoReplacementSets: model.libero_replacement_sets?.map(set => set.map((id) => findPlayer(id, roster))),
|
|
95
101
|
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
96
102
|
starter: findPlayer(d.starterId, roster),
|
|
97
103
|
bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
|
|
@@ -6,6 +6,7 @@ export * from './team-name';
|
|
|
6
6
|
export * from './energy-band';
|
|
7
7
|
export * from './pinch-condition';
|
|
8
8
|
export * from './designated-sub';
|
|
9
|
+
export * from './libero-sub';
|
|
9
10
|
export * from './rotation-system';
|
|
10
11
|
export * from './lineup-function';
|
|
11
12
|
export * from './base-position';
|
|
@@ -22,6 +22,7 @@ __exportStar(require("./team-name"), exports);
|
|
|
22
22
|
__exportStar(require("./energy-band"), exports);
|
|
23
23
|
__exportStar(require("./pinch-condition"), exports);
|
|
24
24
|
__exportStar(require("./designated-sub"), exports);
|
|
25
|
+
__exportStar(require("./libero-sub"), exports);
|
|
25
26
|
__exportStar(require("./rotation-system"), exports);
|
|
26
27
|
__exportStar(require("./lineup-function"), exports);
|
|
27
28
|
__exportStar(require("./base-position"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { EnergyBand } from './energy-band';
|
|
2
|
+
export type LiberoSubMode = 'NEVER' | 'FATIGUE' | 'ALWAYS';
|
|
3
|
+
export interface LiberoSetSubConfig {
|
|
4
|
+
readonly mode: LiberoSubMode;
|
|
5
|
+
readonly fatigueBand?: EnergyBand;
|
|
6
|
+
}
|
|
7
|
+
export interface LiberoSubConfig extends LiberoSetSubConfig {
|
|
8
|
+
readonly sets?: LiberoSetSubConfig[];
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { EnergyBand } from '../energy-band';
|
|
3
|
+
export declare const LiberoSubModeSchema: z.ZodEnum<{
|
|
4
|
+
NEVER: "NEVER";
|
|
5
|
+
FATIGUE: "FATIGUE";
|
|
6
|
+
ALWAYS: "ALWAYS";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const LiberoSetSubConfigSchema: z.ZodObject<{
|
|
9
|
+
mode: z.ZodEnum<{
|
|
10
|
+
NEVER: "NEVER";
|
|
11
|
+
FATIGUE: "FATIGUE";
|
|
12
|
+
ALWAYS: "ALWAYS";
|
|
13
|
+
}>;
|
|
14
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
17
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
|
+
mode: z.ZodEnum<{
|
|
19
|
+
NEVER: "NEVER";
|
|
20
|
+
FATIGUE: "FATIGUE";
|
|
21
|
+
ALWAYS: "ALWAYS";
|
|
22
|
+
}>;
|
|
23
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
24
|
+
}, z.core.$strip>>>;
|
|
25
|
+
mode: z.ZodEnum<{
|
|
26
|
+
NEVER: "NEVER";
|
|
27
|
+
FATIGUE: "FATIGUE";
|
|
28
|
+
ALWAYS: "ALWAYS";
|
|
29
|
+
}>;
|
|
30
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
31
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LiberoSubConfigSchema = exports.LiberoSetSubConfigSchema = exports.LiberoSubModeSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const energy_band_1 = require("../energy-band");
|
|
6
|
+
exports.LiberoSubModeSchema = zod_1.z.enum(['NEVER', 'FATIGUE', 'ALWAYS']);
|
|
7
|
+
// Fields shared by the all-sets config and each per-set config. Unlike a designated sub there is no bench or
|
|
8
|
+
// pinch condition here (the swapped-in player is always the dedicated `secondLibero`), so there is nothing to
|
|
9
|
+
// cross-validate.
|
|
10
|
+
const liberoSetSubConfigShape = {
|
|
11
|
+
mode: exports.LiberoSubModeSchema,
|
|
12
|
+
// fatigue mode only (absent => the team default `substitutionBand`):
|
|
13
|
+
fatigueBand: zod_1.z.nativeEnum(energy_band_1.EnergyBand).optional()
|
|
14
|
+
};
|
|
15
|
+
exports.LiberoSetSubConfigSchema = zod_1.z.object(liberoSetSubConfigShape);
|
|
16
|
+
exports.LiberoSubConfigSchema = zod_1.z.object({
|
|
17
|
+
...liberoSetSubConfigShape,
|
|
18
|
+
// Optional per-set overrides (index 0 = set 1 ... index 4 = set 5); absent => the flat config for all sets.
|
|
19
|
+
sets: zod_1.z.array(exports.LiberoSetSubConfigSchema).max(5).optional()
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const libero_sub_z_1 = require("./libero-sub.z");
|
|
5
|
+
const energy_band_1 = require("../energy-band");
|
|
6
|
+
(0, globals_1.describe)('LiberoSubModeSchema', () => {
|
|
7
|
+
(0, globals_1.it)('accepts NEVER, FATIGUE and ALWAYS', () => {
|
|
8
|
+
for (const mode of ['NEVER', 'FATIGUE', 'ALWAYS']) {
|
|
9
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSubModeSchema.safeParse(mode).success).toBe(true);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
(0, globals_1.it)('rejects an unknown mode', () => {
|
|
13
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSubModeSchema.safeParse('SOMETIMES').success).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
(0, globals_1.describe)('LiberoSetSubConfigSchema', () => {
|
|
17
|
+
(0, globals_1.it)('accepts a mode with no band', () => {
|
|
18
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'ALWAYS' }).success).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
(0, globals_1.it)('accepts a fatigue mode with a band', () => {
|
|
21
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.TIRED }).success).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
(0, globals_1.it)('rejects an unknown band', () => {
|
|
24
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: 'SLEEPY' }).success).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
(0, globals_1.describe)('LiberoSubConfigSchema', () => {
|
|
28
|
+
(0, globals_1.it)('accepts a flat config with no per-set overrides', () => {
|
|
29
|
+
(0, globals_1.expect)(libero_sub_z_1.LiberoSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.WORN }).success).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
(0, globals_1.it)('accepts up to five per-set configs', () => {
|
|
32
|
+
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
33
|
+
mode: 'NEVER',
|
|
34
|
+
sets: [
|
|
35
|
+
{ mode: 'NEVER' },
|
|
36
|
+
{ mode: 'ALWAYS' },
|
|
37
|
+
{ mode: 'FATIGUE', fatigueBand: energy_band_1.EnergyBand.TIRED },
|
|
38
|
+
{ mode: 'NEVER' },
|
|
39
|
+
{ mode: 'ALWAYS' }
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
(0, globals_1.it)('rejects more than five per-set configs', () => {
|
|
45
|
+
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
46
|
+
mode: 'NEVER',
|
|
47
|
+
sets: Array.from({ length: 6 }, () => ({ mode: 'NEVER' }))
|
|
48
|
+
});
|
|
49
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
(0, globals_1.it)('rejects an unknown mode in a per-set config', () => {
|
|
52
|
+
const res = libero_sub_z_1.LiberoSubConfigSchema.safeParse({
|
|
53
|
+
mode: 'NEVER',
|
|
54
|
+
sets: [{ mode: 'WHENEVER' }]
|
|
55
|
+
});
|
|
56
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -91,6 +91,24 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
91
91
|
starter: z.ZodCustom<Player, Player>;
|
|
92
92
|
}, z.core.$strip>>>;
|
|
93
93
|
substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
|
|
94
|
+
secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
95
|
+
liberoSub: z.ZodOptional<z.ZodObject<{
|
|
96
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
97
|
+
mode: z.ZodEnum<{
|
|
98
|
+
NEVER: "NEVER";
|
|
99
|
+
FATIGUE: "FATIGUE";
|
|
100
|
+
ALWAYS: "ALWAYS";
|
|
101
|
+
}>;
|
|
102
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
103
|
+
}, z.core.$strip>>>;
|
|
104
|
+
mode: z.ZodEnum<{
|
|
105
|
+
NEVER: "NEVER";
|
|
106
|
+
FATIGUE: "FATIGUE";
|
|
107
|
+
ALWAYS: "ALWAYS";
|
|
108
|
+
}>;
|
|
109
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
110
|
+
}, z.core.$strip>>;
|
|
111
|
+
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
94
112
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof RotationSystemEnum>>;
|
|
95
113
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
96
114
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -6,6 +6,7 @@ const player_1 = require("../../player");
|
|
|
6
6
|
const energy_band_1 = require("../energy-band");
|
|
7
7
|
const rotation_system_1 = require("../rotation-system");
|
|
8
8
|
const designated_sub_z_1 = require("./designated-sub.z");
|
|
9
|
+
const libero_sub_z_1 = require("./libero-sub.z");
|
|
9
10
|
const playerInstanceSchema = zod_1.z.custom((v) => v instanceof player_1.Player, {
|
|
10
11
|
message: 'INVALID_PLAYER_INSTANCE'
|
|
11
12
|
});
|
|
@@ -64,6 +65,11 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
64
65
|
// New (designated-subs feature). Defaults keep every existing Tactics.create({...}) call valid.
|
|
65
66
|
designatedSubs: zod_1.z.array(designated_sub_z_1.DesignatedSubSchema).default([]),
|
|
66
67
|
substitutionBand: designated_sub_z_1.SubBandSchema.default(energy_band_1.EnergyBand.TIRED),
|
|
68
|
+
// Second libero (L2). All optional => an unconfigured team behaves exactly as before. Cross-field rules
|
|
69
|
+
// (reserve-only, requires a libero, swap requires a second libero, per-set replacements are starters) below.
|
|
70
|
+
secondLibero: playerInstanceSchema.optional(),
|
|
71
|
+
liberoSub: libero_sub_z_1.LiberoSubConfigSchema.optional(),
|
|
72
|
+
liberoReplacementSets: zod_1.z.array(zod_1.z.array(playerInstanceSchema).max(2)).max(5).optional(),
|
|
67
73
|
// Advanced tactics (rotation systems). Defaults (6-0 / no setters / no preferences) keep every existing
|
|
68
74
|
// Tactics.create({...}) call valid and make an unconfigured team play exactly as before.
|
|
69
75
|
rotationSystem: zod_1.z.nativeEnum(rotation_system_1.RotationSystemEnum).default(rotation_system_1.RotationSystemEnum.SIX_ZERO),
|
|
@@ -123,4 +129,34 @@ exports.TacticsInputSchema = zod_1.z.object({
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
}
|
|
132
|
+
// Second libero (L2). The second libero is a reserve (not a starter, the libero, or a bench player). A swap
|
|
133
|
+
// rule (FATIGUE/ALWAYS in any set) needs someone to swap in, so it requires a second libero, and a second
|
|
134
|
+
// libero needs a starting libero to replace ("no L2 without an L"). Per-set libero replacements, like the
|
|
135
|
+
// flat ones, are back-row court starters.
|
|
136
|
+
const libero = data.lineup[LIBERO_ZONE];
|
|
137
|
+
const inLineupIds = new Set([
|
|
138
|
+
...starterIds,
|
|
139
|
+
...(libero != null ? [libero.id] : []),
|
|
140
|
+
...data.lineup.bench.map(p => p.id)
|
|
141
|
+
]);
|
|
142
|
+
const liberoSubModes = data.liberoSub != null
|
|
143
|
+
? [data.liberoSub.mode, ...(data.liberoSub.sets ?? []).map(s => s.mode)]
|
|
144
|
+
: [];
|
|
145
|
+
const wantsLiberoSwap = liberoSubModes.some(mode => mode !== 'NEVER');
|
|
146
|
+
if (wantsLiberoSwap && data.secondLibero == null) {
|
|
147
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO', path: ['secondLibero'] });
|
|
148
|
+
}
|
|
149
|
+
if ((data.secondLibero != null || wantsLiberoSwap) && libero == null) {
|
|
150
|
+
ctx.addIssue({ code: 'custom', message: 'SECOND_LIBERO_REQUIRES_LIBERO', path: ['secondLibero'] });
|
|
151
|
+
}
|
|
152
|
+
if (data.secondLibero != null && inLineupIds.has(data.secondLibero.id)) {
|
|
153
|
+
ctx.addIssue({ code: 'custom', message: 'SECOND_LIBERO_MUST_BE_RESERVE', path: ['secondLibero'] });
|
|
154
|
+
}
|
|
155
|
+
for (const set of data.liberoReplacementSets ?? []) {
|
|
156
|
+
for (const player of set) {
|
|
157
|
+
if (!starterIds.has(player.id)) {
|
|
158
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_REPLACEMENT_NOT_A_STARTER', path: ['liberoReplacementSets'] });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
126
162
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const tactics_z_1 = require("./tactics.z");
|
|
5
|
+
const test_helpers_1 = require("../../test-helpers");
|
|
6
|
+
(0, globals_1.describe)('TacticsInputSchema — second libero rules', () => {
|
|
7
|
+
const country = (0, test_helpers_1.makeCountry)();
|
|
8
|
+
const p1 = (0, test_helpers_1.makePlayer)(country);
|
|
9
|
+
const p2 = (0, test_helpers_1.makePlayer)(country);
|
|
10
|
+
const p3 = (0, test_helpers_1.makePlayer)(country);
|
|
11
|
+
const p4 = (0, test_helpers_1.makePlayer)(country);
|
|
12
|
+
const p5 = (0, test_helpers_1.makePlayer)(country);
|
|
13
|
+
const p6 = (0, test_helpers_1.makePlayer)(country);
|
|
14
|
+
const libero = (0, test_helpers_1.makePlayer)(country);
|
|
15
|
+
const reserve = (0, test_helpers_1.makePlayer)(country);
|
|
16
|
+
// A minimal valid TacticsInput (6-0, no setters needed). `overrides` adds the libero fields under test.
|
|
17
|
+
function baseInput(overrides = {}, withLibero = true) {
|
|
18
|
+
return {
|
|
19
|
+
lineup: {
|
|
20
|
+
4: p4,
|
|
21
|
+
3: p3,
|
|
22
|
+
2: p2,
|
|
23
|
+
5: p5,
|
|
24
|
+
6: p6,
|
|
25
|
+
1: p1,
|
|
26
|
+
...(withLibero ? { 0: libero } : {}),
|
|
27
|
+
bench: []
|
|
28
|
+
},
|
|
29
|
+
substitutionTolerance: 1,
|
|
30
|
+
liberoReplacements: [],
|
|
31
|
+
pinchServerSubs: new Map(),
|
|
32
|
+
...overrides
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
(0, globals_1.it)('accepts an unconfigured team (no second libero, no liberoSub)', () => {
|
|
36
|
+
(0, globals_1.expect)(tactics_z_1.TacticsInputSchema.safeParse(baseInput()).success).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
(0, globals_1.it)('accepts a reserve second libero with an ALWAYS swap and a starting libero', () => {
|
|
39
|
+
const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({
|
|
40
|
+
secondLibero: reserve,
|
|
41
|
+
liberoSub: { mode: 'ALWAYS' }
|
|
42
|
+
}));
|
|
43
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
(0, globals_1.it)('rejects a swap rule with no second libero', () => {
|
|
46
|
+
const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({ liberoSub: { mode: 'ALWAYS' } }));
|
|
47
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
48
|
+
if (!res.success) {
|
|
49
|
+
(0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO')).toBe(true);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
(0, globals_1.it)('rejects a second libero with no starting libero', () => {
|
|
53
|
+
const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({ secondLibero: reserve }, false));
|
|
54
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
55
|
+
if (!res.success) {
|
|
56
|
+
(0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'SECOND_LIBERO_REQUIRES_LIBERO')).toBe(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
(0, globals_1.it)('rejects a second libero that is a starter (must be a reserve)', () => {
|
|
60
|
+
const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({ secondLibero: p1 }));
|
|
61
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
62
|
+
if (!res.success) {
|
|
63
|
+
(0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'SECOND_LIBERO_MUST_BE_RESERVE')).toBe(true);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
(0, globals_1.it)('rejects per-set libero replacements that are not court starters', () => {
|
|
67
|
+
const res = tactics_z_1.TacticsInputSchema.safeParse(baseInput({ liberoReplacementSets: [[reserve]] }));
|
|
68
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
69
|
+
if (!res.success) {
|
|
70
|
+
(0, globals_1.expect)(res.error.issues.some(issue => issue.message === 'LIBERO_REPLACEMENT_NOT_A_STARTER')).toBe(true);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -99,6 +99,24 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
99
99
|
starter: z.ZodCustom<Player, Player>;
|
|
100
100
|
}, z.core.$strip>>>;
|
|
101
101
|
substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
|
|
102
|
+
secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
103
|
+
liberoSub: z.ZodOptional<z.ZodObject<{
|
|
104
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
105
|
+
mode: z.ZodEnum<{
|
|
106
|
+
NEVER: "NEVER";
|
|
107
|
+
FATIGUE: "FATIGUE";
|
|
108
|
+
ALWAYS: "ALWAYS";
|
|
109
|
+
}>;
|
|
110
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
111
|
+
}, z.core.$strip>>>;
|
|
112
|
+
mode: z.ZodEnum<{
|
|
113
|
+
NEVER: "NEVER";
|
|
114
|
+
FATIGUE: "FATIGUE";
|
|
115
|
+
ALWAYS: "ALWAYS";
|
|
116
|
+
}>;
|
|
117
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
118
|
+
}, z.core.$strip>>;
|
|
119
|
+
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
102
120
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof import("..").RotationSystemEnum>>;
|
|
103
121
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
104
122
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CourtPosition } from '../match';
|
|
2
2
|
import { Player } from '../player';
|
|
3
3
|
import { DesignatedSub } from './designated-sub';
|
|
4
|
+
import { LiberoSubConfig } from './libero-sub';
|
|
4
5
|
import { SubBand } from './energy-band';
|
|
5
6
|
import { RotationSystemEnum } from './rotation-system';
|
|
6
7
|
import { OffensivePreference } from './offensive-preference';
|
|
@@ -22,6 +23,9 @@ export interface TacticsOpts {
|
|
|
22
23
|
readonly receiveRotationOffset: boolean;
|
|
23
24
|
readonly designatedSubs: DesignatedSub[];
|
|
24
25
|
readonly substitutionBand: SubBand;
|
|
26
|
+
readonly secondLibero?: Player;
|
|
27
|
+
readonly liberoSub?: LiberoSubConfig;
|
|
28
|
+
readonly liberoReplacementSets?: Player[][];
|
|
25
29
|
readonly rotationSystem: RotationSystemEnum;
|
|
26
30
|
readonly designatedSetters: Player[];
|
|
27
31
|
readonly offensivePreferences: OffensivePreference[];
|
|
@@ -34,6 +38,9 @@ export declare class Tactics {
|
|
|
34
38
|
readonly receiveRotationOffset: boolean;
|
|
35
39
|
readonly designatedSubs: DesignatedSub[];
|
|
36
40
|
readonly substitutionBand: SubBand;
|
|
41
|
+
readonly secondLibero?: Player;
|
|
42
|
+
readonly liberoSub?: LiberoSubConfig;
|
|
43
|
+
readonly liberoReplacementSets?: Player[][];
|
|
37
44
|
readonly rotationSystem: RotationSystemEnum;
|
|
38
45
|
readonly designatedSetters: Player[];
|
|
39
46
|
readonly offensivePreferences: OffensivePreference[];
|
|
@@ -18,7 +18,7 @@ class Tactics {
|
|
|
18
18
|
}
|
|
19
19
|
return new Tactics(result.data);
|
|
20
20
|
}
|
|
21
|
-
constructor({ lineup, pinchServerSubs, liberoReplacements, substitutionTolerance = 1, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [] }) {
|
|
21
|
+
constructor({ lineup, pinchServerSubs, liberoReplacements, substitutionTolerance = 1, receiveRotationOffset = false, designatedSubs = [], substitutionBand = energy_band_1.EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = rotation_system_1.RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [] }) {
|
|
22
22
|
this.lineup = lineup;
|
|
23
23
|
this.substitutionTolerance = substitutionTolerance;
|
|
24
24
|
this.pinchServerSubs = pinchServerSubs;
|
|
@@ -26,6 +26,9 @@ class Tactics {
|
|
|
26
26
|
this.receiveRotationOffset = receiveRotationOffset;
|
|
27
27
|
this.designatedSubs = designatedSubs;
|
|
28
28
|
this.substitutionBand = substitutionBand;
|
|
29
|
+
this.secondLibero = secondLibero;
|
|
30
|
+
this.liberoSub = liberoSub;
|
|
31
|
+
this.liberoReplacementSets = liberoReplacementSets;
|
|
29
32
|
this.rotationSystem = rotationSystem;
|
|
30
33
|
this.designatedSetters = designatedSetters;
|
|
31
34
|
this.offensivePreferences = offensivePreferences;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
|
|
1
|
+
import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, CourtPosition, ConditionLogic, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
|
|
2
2
|
export type Rally = DataProps<_Rally> & {
|
|
3
3
|
homePlayerPosition: PlayerPosition[];
|
|
4
4
|
awayPlayerPosition: PlayerPosition[];
|
|
@@ -76,6 +76,13 @@ export interface ApiOffensivePreference {
|
|
|
76
76
|
rotation: number;
|
|
77
77
|
order: string[];
|
|
78
78
|
}
|
|
79
|
+
export interface ApiLiberoSetSubConfig {
|
|
80
|
+
mode: LiberoSubMode;
|
|
81
|
+
fatigueBand?: EnergyBand;
|
|
82
|
+
}
|
|
83
|
+
export interface ApiLiberoSubConfig extends ApiLiberoSetSubConfig {
|
|
84
|
+
sets?: ApiLiberoSetSubConfig[];
|
|
85
|
+
}
|
|
79
86
|
export interface Tactics {
|
|
80
87
|
lineup: StartingLineup;
|
|
81
88
|
substitutionTolerance: number;
|
|
@@ -84,6 +91,9 @@ export interface Tactics {
|
|
|
84
91
|
receiveRotationOffset: boolean;
|
|
85
92
|
designatedSubs: ApiDesignatedSub[];
|
|
86
93
|
substitutionBand: SubBand;
|
|
94
|
+
secondLibero?: string;
|
|
95
|
+
liberoSub?: ApiLiberoSubConfig;
|
|
96
|
+
liberoReplacementSets?: string[][];
|
|
87
97
|
rotationSystem: RotationSystemEnum;
|
|
88
98
|
designatedSetters: string[];
|
|
89
99
|
offensivePreferences: ApiOffensivePreference[];
|
|
@@ -11,10 +11,11 @@ export declare enum CurrencyTransactionSourceEnum {
|
|
|
11
11
|
GACHA_PULL = "GACHA_PULL",
|
|
12
12
|
COACH_UPGRADE = "COACH_UPGRADE",
|
|
13
13
|
PLAYER_DISMISS = "PLAYER_DISMISS",
|
|
14
|
+
PLAYER_RETIRE = "PLAYER_RETIRE",
|
|
14
15
|
ADMIN_GRANT = "ADMIN_GRANT",
|
|
15
16
|
SEASON_REWARD = "SEASON_REWARD"
|
|
16
17
|
}
|
|
17
|
-
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD;
|
|
18
|
+
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD;
|
|
18
19
|
export interface CurrencyTransactionAttributes {
|
|
19
20
|
transaction_id: string;
|
|
20
21
|
user_id: string;
|
|
@@ -10,6 +10,7 @@ export var CurrencyTransactionSourceEnum;
|
|
|
10
10
|
CurrencyTransactionSourceEnum["GACHA_PULL"] = "GACHA_PULL";
|
|
11
11
|
CurrencyTransactionSourceEnum["COACH_UPGRADE"] = "COACH_UPGRADE";
|
|
12
12
|
CurrencyTransactionSourceEnum["PLAYER_DISMISS"] = "PLAYER_DISMISS";
|
|
13
|
+
CurrencyTransactionSourceEnum["PLAYER_RETIRE"] = "PLAYER_RETIRE";
|
|
13
14
|
CurrencyTransactionSourceEnum["ADMIN_GRANT"] = "ADMIN_GRANT";
|
|
14
15
|
CurrencyTransactionSourceEnum["SEASON_REWARD"] = "SEASON_REWARD";
|
|
15
16
|
})(CurrencyTransactionSourceEnum || (CurrencyTransactionSourceEnum = {}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Sequelize from 'sequelize';
|
|
2
2
|
import { Model, Optional } from 'sequelize';
|
|
3
3
|
import { PlayerId, TeamId, TeamModel } from '.';
|
|
4
|
-
import { ConditionLogic, CourtPosition, EnergyBand, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
|
|
4
|
+
import { ConditionLogic, CourtPosition, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
|
|
5
5
|
export interface TacticsLineupAttributes {
|
|
6
6
|
[CourtPosition.LIBERO_ZONE]?: PlayerId;
|
|
7
7
|
[CourtPosition.LEFT_BACK]: PlayerId;
|
|
@@ -34,6 +34,13 @@ export interface OffensivePreferenceAttributes {
|
|
|
34
34
|
rotation: number;
|
|
35
35
|
order: PlayerId[];
|
|
36
36
|
}
|
|
37
|
+
export interface LiberoSetSubConfigAttributes {
|
|
38
|
+
mode: LiberoSubMode;
|
|
39
|
+
fatigueBand?: EnergyBand;
|
|
40
|
+
}
|
|
41
|
+
export interface LiberoSubConfigAttributes extends LiberoSetSubConfigAttributes {
|
|
42
|
+
sets?: LiberoSetSubConfigAttributes[];
|
|
43
|
+
}
|
|
37
44
|
export interface TacticsAttributes {
|
|
38
45
|
team_id: string;
|
|
39
46
|
substitution_tolerance: number;
|
|
@@ -43,13 +50,16 @@ export interface TacticsAttributes {
|
|
|
43
50
|
receive_rotation_offset: boolean;
|
|
44
51
|
designated_subs: DesignatedSubAttributes[];
|
|
45
52
|
substitution_band: SubBand;
|
|
53
|
+
second_libero?: PlayerId;
|
|
54
|
+
libero_sub?: LiberoSubConfigAttributes;
|
|
55
|
+
libero_replacement_sets?: PlayerId[][];
|
|
46
56
|
rotation_system: RotationSystemEnum;
|
|
47
57
|
designated_setters: PlayerId[];
|
|
48
58
|
offensive_preferences: OffensivePreferenceAttributes[];
|
|
49
59
|
}
|
|
50
60
|
export type TacticsPk = 'team_id';
|
|
51
61
|
export type TacticsId = TacticsModel[TacticsPk];
|
|
52
|
-
export type TacticsOptionalAttributes = 'substitution_tolerance' | 'lineup' | 'libero_replacements' | 'pinch_server_subs' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'rotation_system' | 'designated_setters' | 'offensive_preferences';
|
|
62
|
+
export type TacticsOptionalAttributes = 'substitution_tolerance' | 'lineup' | 'libero_replacements' | 'pinch_server_subs' | 'receive_rotation_offset' | 'designated_subs' | 'substitution_band' | 'second_libero' | 'libero_sub' | 'libero_replacement_sets' | 'rotation_system' | 'designated_setters' | 'offensive_preferences';
|
|
53
63
|
export type TacticsCreationAttributes = Optional<TacticsAttributes, TacticsOptionalAttributes>;
|
|
54
64
|
export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreationAttributes> implements TacticsAttributes {
|
|
55
65
|
team_id: string;
|
|
@@ -60,6 +70,9 @@ export declare class TacticsModel extends Model<TacticsAttributes, TacticsCreati
|
|
|
60
70
|
receive_rotation_offset: boolean;
|
|
61
71
|
designated_subs: DesignatedSubAttributes[];
|
|
62
72
|
substitution_band: SubBand;
|
|
73
|
+
second_libero?: PlayerId;
|
|
74
|
+
libero_sub?: LiberoSubConfigAttributes;
|
|
75
|
+
libero_replacement_sets?: PlayerId[][];
|
|
63
76
|
rotation_system: RotationSystemEnum;
|
|
64
77
|
designated_setters: PlayerId[];
|
|
65
78
|
offensive_preferences: OffensivePreferenceAttributes[];
|
|
@@ -47,6 +47,18 @@ export class TacticsModel extends Model {
|
|
|
47
47
|
allowNull: false,
|
|
48
48
|
defaultValue: 'TIRED'
|
|
49
49
|
},
|
|
50
|
+
second_libero: {
|
|
51
|
+
type: DataTypes.UUID,
|
|
52
|
+
allowNull: true
|
|
53
|
+
},
|
|
54
|
+
libero_sub: {
|
|
55
|
+
type: DataTypes.JSONB,
|
|
56
|
+
allowNull: true
|
|
57
|
+
},
|
|
58
|
+
libero_replacement_sets: {
|
|
59
|
+
type: DataTypes.JSONB,
|
|
60
|
+
allowNull: true
|
|
61
|
+
},
|
|
50
62
|
rotation_system: {
|
|
51
63
|
type: DataTypes.STRING,
|
|
52
64
|
allowNull: false,
|
|
@@ -50,6 +50,9 @@ function transformToAttributes(tactics, teamId) {
|
|
|
50
50
|
libero_replacements: tactics.liberoReplacements.map((lr) => lr.id),
|
|
51
51
|
pinch_server_subs: Object.fromEntries([...tactics.pinchServerSubs].map(([k, v]) => [k.id, v.id])),
|
|
52
52
|
substitution_band: tactics.substitutionBand,
|
|
53
|
+
second_libero: tactics.secondLibero?.id,
|
|
54
|
+
libero_sub: tactics.liberoSub,
|
|
55
|
+
libero_replacement_sets: tactics.liberoReplacementSets?.map(set => set.map((p) => p.id)),
|
|
53
56
|
designated_subs: tactics.designatedSubs.map(ds => ({
|
|
54
57
|
starterId: ds.starter.id,
|
|
55
58
|
benchId: ds.bench?.id,
|
|
@@ -88,6 +91,9 @@ function transformToObject(model, roster) {
|
|
|
88
91
|
liberoReplacements: model.libero_replacements.map((lr) => findPlayer(lr, roster)),
|
|
89
92
|
pinchServerSubs: buildPinchServerSubs(model.pinch_server_subs, roster),
|
|
90
93
|
substitutionBand: model.substitution_band,
|
|
94
|
+
secondLibero: model.second_libero != null ? findPlayer(model.second_libero, roster) : undefined,
|
|
95
|
+
liberoSub: model.libero_sub ?? undefined,
|
|
96
|
+
liberoReplacementSets: model.libero_replacement_sets?.map(set => set.map((id) => findPlayer(id, roster))),
|
|
91
97
|
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
92
98
|
starter: findPlayer(d.starterId, roster),
|
|
93
99
|
bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
|
|
@@ -6,6 +6,7 @@ export * from './team-name';
|
|
|
6
6
|
export * from './energy-band';
|
|
7
7
|
export * from './pinch-condition';
|
|
8
8
|
export * from './designated-sub';
|
|
9
|
+
export * from './libero-sub';
|
|
9
10
|
export * from './rotation-system';
|
|
10
11
|
export * from './lineup-function';
|
|
11
12
|
export * from './base-position';
|
|
@@ -6,6 +6,7 @@ export * from './team-name';
|
|
|
6
6
|
export * from './energy-band';
|
|
7
7
|
export * from './pinch-condition';
|
|
8
8
|
export * from './designated-sub';
|
|
9
|
+
export * from './libero-sub';
|
|
9
10
|
export * from './rotation-system';
|
|
10
11
|
export * from './lineup-function';
|
|
11
12
|
export * from './base-position';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { EnergyBand } from './energy-band';
|
|
2
|
+
export type LiberoSubMode = 'NEVER' | 'FATIGUE' | 'ALWAYS';
|
|
3
|
+
export interface LiberoSetSubConfig {
|
|
4
|
+
readonly mode: LiberoSubMode;
|
|
5
|
+
readonly fatigueBand?: EnergyBand;
|
|
6
|
+
}
|
|
7
|
+
export interface LiberoSubConfig extends LiberoSetSubConfig {
|
|
8
|
+
readonly sets?: LiberoSetSubConfig[];
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { EnergyBand } from '../energy-band';
|
|
3
|
+
export declare const LiberoSubModeSchema: z.ZodEnum<{
|
|
4
|
+
NEVER: "NEVER";
|
|
5
|
+
FATIGUE: "FATIGUE";
|
|
6
|
+
ALWAYS: "ALWAYS";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const LiberoSetSubConfigSchema: z.ZodObject<{
|
|
9
|
+
mode: z.ZodEnum<{
|
|
10
|
+
NEVER: "NEVER";
|
|
11
|
+
FATIGUE: "FATIGUE";
|
|
12
|
+
ALWAYS: "ALWAYS";
|
|
13
|
+
}>;
|
|
14
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
17
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
|
+
mode: z.ZodEnum<{
|
|
19
|
+
NEVER: "NEVER";
|
|
20
|
+
FATIGUE: "FATIGUE";
|
|
21
|
+
ALWAYS: "ALWAYS";
|
|
22
|
+
}>;
|
|
23
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
24
|
+
}, z.core.$strip>>>;
|
|
25
|
+
mode: z.ZodEnum<{
|
|
26
|
+
NEVER: "NEVER";
|
|
27
|
+
FATIGUE: "FATIGUE";
|
|
28
|
+
ALWAYS: "ALWAYS";
|
|
29
|
+
}>;
|
|
30
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
31
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { EnergyBand } from '../energy-band';
|
|
3
|
+
export const LiberoSubModeSchema = z.enum(['NEVER', 'FATIGUE', 'ALWAYS']);
|
|
4
|
+
// Fields shared by the all-sets config and each per-set config. Unlike a designated sub there is no bench or
|
|
5
|
+
// pinch condition here (the swapped-in player is always the dedicated `secondLibero`), so there is nothing to
|
|
6
|
+
// cross-validate.
|
|
7
|
+
const liberoSetSubConfigShape = {
|
|
8
|
+
mode: LiberoSubModeSchema,
|
|
9
|
+
// fatigue mode only (absent => the team default `substitutionBand`):
|
|
10
|
+
fatigueBand: z.nativeEnum(EnergyBand).optional()
|
|
11
|
+
};
|
|
12
|
+
export const LiberoSetSubConfigSchema = z.object(liberoSetSubConfigShape);
|
|
13
|
+
export const LiberoSubConfigSchema = z.object({
|
|
14
|
+
...liberoSetSubConfigShape,
|
|
15
|
+
// Optional per-set overrides (index 0 = set 1 ... index 4 = set 5); absent => the flat config for all sets.
|
|
16
|
+
sets: z.array(LiberoSetSubConfigSchema).max(5).optional()
|
|
17
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { LiberoSetSubConfigSchema, LiberoSubConfigSchema, LiberoSubModeSchema } from './libero-sub.z';
|
|
3
|
+
import { EnergyBand } from '../energy-band';
|
|
4
|
+
describe('LiberoSubModeSchema', () => {
|
|
5
|
+
it('accepts NEVER, FATIGUE and ALWAYS', () => {
|
|
6
|
+
for (const mode of ['NEVER', 'FATIGUE', 'ALWAYS']) {
|
|
7
|
+
expect(LiberoSubModeSchema.safeParse(mode).success).toBe(true);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
it('rejects an unknown mode', () => {
|
|
11
|
+
expect(LiberoSubModeSchema.safeParse('SOMETIMES').success).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('LiberoSetSubConfigSchema', () => {
|
|
15
|
+
it('accepts a mode with no band', () => {
|
|
16
|
+
expect(LiberoSetSubConfigSchema.safeParse({ mode: 'ALWAYS' }).success).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it('accepts a fatigue mode with a band', () => {
|
|
19
|
+
expect(LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: EnergyBand.TIRED }).success).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it('rejects an unknown band', () => {
|
|
22
|
+
expect(LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: 'SLEEPY' }).success).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('LiberoSubConfigSchema', () => {
|
|
26
|
+
it('accepts a flat config with no per-set overrides', () => {
|
|
27
|
+
expect(LiberoSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: EnergyBand.WORN }).success).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
it('accepts up to five per-set configs', () => {
|
|
30
|
+
const res = LiberoSubConfigSchema.safeParse({
|
|
31
|
+
mode: 'NEVER',
|
|
32
|
+
sets: [
|
|
33
|
+
{ mode: 'NEVER' },
|
|
34
|
+
{ mode: 'ALWAYS' },
|
|
35
|
+
{ mode: 'FATIGUE', fatigueBand: EnergyBand.TIRED },
|
|
36
|
+
{ mode: 'NEVER' },
|
|
37
|
+
{ mode: 'ALWAYS' }
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
expect(res.success).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('rejects more than five per-set configs', () => {
|
|
43
|
+
const res = LiberoSubConfigSchema.safeParse({
|
|
44
|
+
mode: 'NEVER',
|
|
45
|
+
sets: Array.from({ length: 6 }, () => ({ mode: 'NEVER' }))
|
|
46
|
+
});
|
|
47
|
+
expect(res.success).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('rejects an unknown mode in a per-set config', () => {
|
|
50
|
+
const res = LiberoSubConfigSchema.safeParse({
|
|
51
|
+
mode: 'NEVER',
|
|
52
|
+
sets: [{ mode: 'WHENEVER' }]
|
|
53
|
+
});
|
|
54
|
+
expect(res.success).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -91,6 +91,24 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
91
91
|
starter: z.ZodCustom<Player, Player>;
|
|
92
92
|
}, z.core.$strip>>>;
|
|
93
93
|
substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
|
|
94
|
+
secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
95
|
+
liberoSub: z.ZodOptional<z.ZodObject<{
|
|
96
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
97
|
+
mode: z.ZodEnum<{
|
|
98
|
+
NEVER: "NEVER";
|
|
99
|
+
FATIGUE: "FATIGUE";
|
|
100
|
+
ALWAYS: "ALWAYS";
|
|
101
|
+
}>;
|
|
102
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
103
|
+
}, z.core.$strip>>>;
|
|
104
|
+
mode: z.ZodEnum<{
|
|
105
|
+
NEVER: "NEVER";
|
|
106
|
+
FATIGUE: "FATIGUE";
|
|
107
|
+
ALWAYS: "ALWAYS";
|
|
108
|
+
}>;
|
|
109
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
110
|
+
}, z.core.$strip>>;
|
|
111
|
+
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
94
112
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof RotationSystemEnum>>;
|
|
95
113
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
96
114
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -3,6 +3,7 @@ import { Player } from '../../player';
|
|
|
3
3
|
import { EnergyBand } from '../energy-band';
|
|
4
4
|
import { RotationSystemEnum, setterCountFor } from '../rotation-system';
|
|
5
5
|
import { DesignatedSubSchema, SubBandSchema } from './designated-sub.z';
|
|
6
|
+
import { LiberoSubConfigSchema } from './libero-sub.z';
|
|
6
7
|
const playerInstanceSchema = z.custom((v) => v instanceof Player, {
|
|
7
8
|
message: 'INVALID_PLAYER_INSTANCE'
|
|
8
9
|
});
|
|
@@ -61,6 +62,11 @@ export const TacticsInputSchema = z.object({
|
|
|
61
62
|
// New (designated-subs feature). Defaults keep every existing Tactics.create({...}) call valid.
|
|
62
63
|
designatedSubs: z.array(DesignatedSubSchema).default([]),
|
|
63
64
|
substitutionBand: SubBandSchema.default(EnergyBand.TIRED),
|
|
65
|
+
// Second libero (L2). All optional => an unconfigured team behaves exactly as before. Cross-field rules
|
|
66
|
+
// (reserve-only, requires a libero, swap requires a second libero, per-set replacements are starters) below.
|
|
67
|
+
secondLibero: playerInstanceSchema.optional(),
|
|
68
|
+
liberoSub: LiberoSubConfigSchema.optional(),
|
|
69
|
+
liberoReplacementSets: z.array(z.array(playerInstanceSchema).max(2)).max(5).optional(),
|
|
64
70
|
// Advanced tactics (rotation systems). Defaults (6-0 / no setters / no preferences) keep every existing
|
|
65
71
|
// Tactics.create({...}) call valid and make an unconfigured team play exactly as before.
|
|
66
72
|
rotationSystem: z.nativeEnum(RotationSystemEnum).default(RotationSystemEnum.SIX_ZERO),
|
|
@@ -120,4 +126,34 @@ export const TacticsInputSchema = z.object({
|
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
}
|
|
129
|
+
// Second libero (L2). The second libero is a reserve (not a starter, the libero, or a bench player). A swap
|
|
130
|
+
// rule (FATIGUE/ALWAYS in any set) needs someone to swap in, so it requires a second libero, and a second
|
|
131
|
+
// libero needs a starting libero to replace ("no L2 without an L"). Per-set libero replacements, like the
|
|
132
|
+
// flat ones, are back-row court starters.
|
|
133
|
+
const libero = data.lineup[LIBERO_ZONE];
|
|
134
|
+
const inLineupIds = new Set([
|
|
135
|
+
...starterIds,
|
|
136
|
+
...(libero != null ? [libero.id] : []),
|
|
137
|
+
...data.lineup.bench.map(p => p.id)
|
|
138
|
+
]);
|
|
139
|
+
const liberoSubModes = data.liberoSub != null
|
|
140
|
+
? [data.liberoSub.mode, ...(data.liberoSub.sets ?? []).map(s => s.mode)]
|
|
141
|
+
: [];
|
|
142
|
+
const wantsLiberoSwap = liberoSubModes.some(mode => mode !== 'NEVER');
|
|
143
|
+
if (wantsLiberoSwap && data.secondLibero == null) {
|
|
144
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO', path: ['secondLibero'] });
|
|
145
|
+
}
|
|
146
|
+
if ((data.secondLibero != null || wantsLiberoSwap) && libero == null) {
|
|
147
|
+
ctx.addIssue({ code: 'custom', message: 'SECOND_LIBERO_REQUIRES_LIBERO', path: ['secondLibero'] });
|
|
148
|
+
}
|
|
149
|
+
if (data.secondLibero != null && inLineupIds.has(data.secondLibero.id)) {
|
|
150
|
+
ctx.addIssue({ code: 'custom', message: 'SECOND_LIBERO_MUST_BE_RESERVE', path: ['secondLibero'] });
|
|
151
|
+
}
|
|
152
|
+
for (const set of data.liberoReplacementSets ?? []) {
|
|
153
|
+
for (const player of set) {
|
|
154
|
+
if (!starterIds.has(player.id)) {
|
|
155
|
+
ctx.addIssue({ code: 'custom', message: 'LIBERO_REPLACEMENT_NOT_A_STARTER', path: ['liberoReplacementSets'] });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
123
159
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { TacticsInputSchema } from './tactics.z';
|
|
3
|
+
import { makeCountry, makePlayer } from '../../test-helpers';
|
|
4
|
+
describe('TacticsInputSchema — second libero rules', () => {
|
|
5
|
+
const country = makeCountry();
|
|
6
|
+
const p1 = makePlayer(country);
|
|
7
|
+
const p2 = makePlayer(country);
|
|
8
|
+
const p3 = makePlayer(country);
|
|
9
|
+
const p4 = makePlayer(country);
|
|
10
|
+
const p5 = makePlayer(country);
|
|
11
|
+
const p6 = makePlayer(country);
|
|
12
|
+
const libero = makePlayer(country);
|
|
13
|
+
const reserve = makePlayer(country);
|
|
14
|
+
// A minimal valid TacticsInput (6-0, no setters needed). `overrides` adds the libero fields under test.
|
|
15
|
+
function baseInput(overrides = {}, withLibero = true) {
|
|
16
|
+
return {
|
|
17
|
+
lineup: {
|
|
18
|
+
4: p4,
|
|
19
|
+
3: p3,
|
|
20
|
+
2: p2,
|
|
21
|
+
5: p5,
|
|
22
|
+
6: p6,
|
|
23
|
+
1: p1,
|
|
24
|
+
...(withLibero ? { 0: libero } : {}),
|
|
25
|
+
bench: []
|
|
26
|
+
},
|
|
27
|
+
substitutionTolerance: 1,
|
|
28
|
+
liberoReplacements: [],
|
|
29
|
+
pinchServerSubs: new Map(),
|
|
30
|
+
...overrides
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
it('accepts an unconfigured team (no second libero, no liberoSub)', () => {
|
|
34
|
+
expect(TacticsInputSchema.safeParse(baseInput()).success).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('accepts a reserve second libero with an ALWAYS swap and a starting libero', () => {
|
|
37
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
38
|
+
secondLibero: reserve,
|
|
39
|
+
liberoSub: { mode: 'ALWAYS' }
|
|
40
|
+
}));
|
|
41
|
+
expect(res.success).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it('rejects a swap rule with no second libero', () => {
|
|
44
|
+
const res = TacticsInputSchema.safeParse(baseInput({ liberoSub: { mode: 'ALWAYS' } }));
|
|
45
|
+
expect(res.success).toBe(false);
|
|
46
|
+
if (!res.success) {
|
|
47
|
+
expect(res.error.issues.some(issue => issue.message === 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO')).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
it('rejects a second libero with no starting libero', () => {
|
|
51
|
+
const res = TacticsInputSchema.safeParse(baseInput({ secondLibero: reserve }, false));
|
|
52
|
+
expect(res.success).toBe(false);
|
|
53
|
+
if (!res.success) {
|
|
54
|
+
expect(res.error.issues.some(issue => issue.message === 'SECOND_LIBERO_REQUIRES_LIBERO')).toBe(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
it('rejects a second libero that is a starter (must be a reserve)', () => {
|
|
58
|
+
const res = TacticsInputSchema.safeParse(baseInput({ secondLibero: p1 }));
|
|
59
|
+
expect(res.success).toBe(false);
|
|
60
|
+
if (!res.success) {
|
|
61
|
+
expect(res.error.issues.some(issue => issue.message === 'SECOND_LIBERO_MUST_BE_RESERVE')).toBe(true);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it('rejects per-set libero replacements that are not court starters', () => {
|
|
65
|
+
const res = TacticsInputSchema.safeParse(baseInput({ liberoReplacementSets: [[reserve]] }));
|
|
66
|
+
expect(res.success).toBe(false);
|
|
67
|
+
if (!res.success) {
|
|
68
|
+
expect(res.error.issues.some(issue => issue.message === 'LIBERO_REPLACEMENT_NOT_A_STARTER')).toBe(true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -99,6 +99,24 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
99
99
|
starter: z.ZodCustom<Player, Player>;
|
|
100
100
|
}, z.core.$strip>>>;
|
|
101
101
|
substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
|
|
102
|
+
secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
103
|
+
liberoSub: z.ZodOptional<z.ZodObject<{
|
|
104
|
+
sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
105
|
+
mode: z.ZodEnum<{
|
|
106
|
+
NEVER: "NEVER";
|
|
107
|
+
FATIGUE: "FATIGUE";
|
|
108
|
+
ALWAYS: "ALWAYS";
|
|
109
|
+
}>;
|
|
110
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
111
|
+
}, z.core.$strip>>>;
|
|
112
|
+
mode: z.ZodEnum<{
|
|
113
|
+
NEVER: "NEVER";
|
|
114
|
+
FATIGUE: "FATIGUE";
|
|
115
|
+
ALWAYS: "ALWAYS";
|
|
116
|
+
}>;
|
|
117
|
+
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
118
|
+
}, z.core.$strip>>;
|
|
119
|
+
liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
|
|
102
120
|
rotationSystem: z.ZodDefault<z.ZodEnum<typeof import("..").RotationSystemEnum>>;
|
|
103
121
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
104
122
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CourtPosition } from '../match';
|
|
2
2
|
import { Player } from '../player';
|
|
3
3
|
import { DesignatedSub } from './designated-sub';
|
|
4
|
+
import { LiberoSubConfig } from './libero-sub';
|
|
4
5
|
import { SubBand } from './energy-band';
|
|
5
6
|
import { RotationSystemEnum } from './rotation-system';
|
|
6
7
|
import { OffensivePreference } from './offensive-preference';
|
|
@@ -22,6 +23,9 @@ export interface TacticsOpts {
|
|
|
22
23
|
readonly receiveRotationOffset: boolean;
|
|
23
24
|
readonly designatedSubs: DesignatedSub[];
|
|
24
25
|
readonly substitutionBand: SubBand;
|
|
26
|
+
readonly secondLibero?: Player;
|
|
27
|
+
readonly liberoSub?: LiberoSubConfig;
|
|
28
|
+
readonly liberoReplacementSets?: Player[][];
|
|
25
29
|
readonly rotationSystem: RotationSystemEnum;
|
|
26
30
|
readonly designatedSetters: Player[];
|
|
27
31
|
readonly offensivePreferences: OffensivePreference[];
|
|
@@ -34,6 +38,9 @@ export declare class Tactics {
|
|
|
34
38
|
readonly receiveRotationOffset: boolean;
|
|
35
39
|
readonly designatedSubs: DesignatedSub[];
|
|
36
40
|
readonly substitutionBand: SubBand;
|
|
41
|
+
readonly secondLibero?: Player;
|
|
42
|
+
readonly liberoSub?: LiberoSubConfig;
|
|
43
|
+
readonly liberoReplacementSets?: Player[][];
|
|
37
44
|
readonly rotationSystem: RotationSystemEnum;
|
|
38
45
|
readonly designatedSetters: Player[];
|
|
39
46
|
readonly offensivePreferences: OffensivePreference[];
|
|
@@ -15,7 +15,7 @@ export class Tactics {
|
|
|
15
15
|
}
|
|
16
16
|
return new Tactics(result.data);
|
|
17
17
|
}
|
|
18
|
-
constructor({ lineup, pinchServerSubs, liberoReplacements, substitutionTolerance = 1, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [] }) {
|
|
18
|
+
constructor({ lineup, pinchServerSubs, liberoReplacements, substitutionTolerance = 1, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [] }) {
|
|
19
19
|
this.lineup = lineup;
|
|
20
20
|
this.substitutionTolerance = substitutionTolerance;
|
|
21
21
|
this.pinchServerSubs = pinchServerSubs;
|
|
@@ -23,6 +23,9 @@ export class Tactics {
|
|
|
23
23
|
this.receiveRotationOffset = receiveRotationOffset;
|
|
24
24
|
this.designatedSubs = designatedSubs;
|
|
25
25
|
this.substitutionBand = substitutionBand;
|
|
26
|
+
this.secondLibero = secondLibero;
|
|
27
|
+
this.liberoSub = liberoSub;
|
|
28
|
+
this.liberoReplacementSets = liberoReplacementSets;
|
|
26
29
|
this.rotationSystem = rotationSystem;
|
|
27
30
|
this.designatedSetters = designatedSetters;
|
|
28
31
|
this.offensivePreferences = offensivePreferences;
|