volleyballsimtypes 0.0.521 → 0.0.523
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 +1 -1
- package/dist/cjs/src/data/country-locales.json +12 -0
- package/dist/cjs/src/data/init-models.js +4 -0
- package/dist/cjs/src/data/models/currency-transaction.d.ts +3 -2
- package/dist/cjs/src/data/models/currency-transaction.js +1 -0
- package/dist/cjs/src/data/models/index.d.ts +1 -0
- package/dist/cjs/src/data/models/index.js +1 -0
- package/dist/cjs/src/data/models/user-achievements.d.ts +19 -0
- package/dist/cjs/src/data/models/user-achievements.js +37 -0
- package/dist/cjs/src/service/achievement/achievement-status.d.ts +5 -0
- package/dist/cjs/src/service/achievement/achievement-status.js +13 -0
- package/dist/cjs/src/service/achievement/achievement-type.d.ts +24 -0
- package/dist/cjs/src/service/achievement/achievement-type.js +85 -0
- package/dist/cjs/src/service/achievement/index.d.ts +3 -0
- package/dist/cjs/src/service/achievement/index.js +19 -0
- package/dist/cjs/src/service/achievement/schemas/achievements.z.d.ts +3 -0
- package/dist/cjs/src/service/achievement/schemas/achievements.z.js +9 -0
- package/dist/cjs/src/service/index.d.ts +1 -0
- package/dist/cjs/src/service/index.js +1 -0
- package/dist/cjs/src/service/match/vper.js +23 -3
- package/dist/cjs/src/service/match/vper.test.js +86 -0
- package/dist/cjs/src/service/notification/catalog/en.json +4 -0
- package/dist/cjs/src/service/notification/catalog/es.json +4 -0
- package/dist/cjs/src/service/notification/catalog/fr.json +4 -0
- package/dist/cjs/src/service/notification/catalog/it.json +4 -0
- package/dist/cjs/src/service/notification/catalog/pl.json +4 -0
- package/dist/cjs/src/service/notification/catalog/pt-BR.json +4 -0
- package/dist/cjs/src/service/notification/catalog/tr.json +4 -0
- package/dist/cjs/src/service/notification/notification-type.d.ts +1 -1
- package/dist/cjs/src/service/notification/notification-type.js +2 -1
- package/dist/cjs/src/service/notification/registry.js +9 -0
- package/dist/cjs/src/service/team/base-config.js +8 -4
- package/dist/cjs/src/service/team/default-base-config.js +11 -9
- package/dist/cjs/src/service/team/default-base-config.test.js +52 -0
- package/dist/esm/src/api/index.d.ts +1 -1
- package/dist/esm/src/data/country-locales.json +12 -0
- package/dist/esm/src/data/init-models.js +5 -1
- package/dist/esm/src/data/models/currency-transaction.d.ts +3 -2
- package/dist/esm/src/data/models/currency-transaction.js +1 -0
- package/dist/esm/src/data/models/index.d.ts +1 -0
- package/dist/esm/src/data/models/index.js +1 -0
- package/dist/esm/src/data/models/user-achievements.d.ts +19 -0
- package/dist/esm/src/data/models/user-achievements.js +33 -0
- package/dist/esm/src/service/achievement/achievement-status.d.ts +5 -0
- package/dist/esm/src/service/achievement/achievement-status.js +10 -0
- package/dist/esm/src/service/achievement/achievement-type.d.ts +24 -0
- package/dist/esm/src/service/achievement/achievement-type.js +77 -0
- package/dist/esm/src/service/achievement/index.d.ts +3 -0
- package/dist/esm/src/service/achievement/index.js +3 -0
- package/dist/esm/src/service/achievement/schemas/achievements.z.d.ts +3 -0
- package/dist/esm/src/service/achievement/schemas/achievements.z.js +6 -0
- package/dist/esm/src/service/index.d.ts +1 -0
- package/dist/esm/src/service/index.js +1 -0
- package/dist/esm/src/service/match/vper.js +23 -3
- package/dist/esm/src/service/match/vper.test.js +86 -0
- package/dist/esm/src/service/notification/catalog/en.json +4 -0
- package/dist/esm/src/service/notification/catalog/es.json +4 -0
- package/dist/esm/src/service/notification/catalog/fr.json +4 -0
- package/dist/esm/src/service/notification/catalog/it.json +4 -0
- package/dist/esm/src/service/notification/catalog/pl.json +4 -0
- package/dist/esm/src/service/notification/catalog/pt-BR.json +4 -0
- package/dist/esm/src/service/notification/catalog/tr.json +4 -0
- package/dist/esm/src/service/notification/notification-type.d.ts +1 -1
- package/dist/esm/src/service/notification/notification-type.js +2 -1
- package/dist/esm/src/service/notification/registry.js +9 -0
- package/dist/esm/src/service/team/base-config.js +8 -4
- package/dist/esm/src/service/team/default-base-config.js +11 -9
- package/dist/esm/src/service/team/default-base-config.test.js +53 -1
- package/package.json +1 -1
|
@@ -60,7 +60,7 @@ export interface ApiPlayerInjuryStatus {
|
|
|
60
60
|
severity: number;
|
|
61
61
|
injuredUntil: string;
|
|
62
62
|
}
|
|
63
|
-
export type ChampionRegion = 'azureich' | 'borealas' | 'varune' | 'tirdeas';
|
|
63
|
+
export type ChampionRegion = 'azureich' | 'borealas' | 'varune' | 'tirdeas' | 'kyorin';
|
|
64
64
|
export type ChampionBadgeSpec = {
|
|
65
65
|
type: 'league';
|
|
66
66
|
} | {
|
|
@@ -28,6 +28,7 @@ function initModels(sequelize) {
|
|
|
28
28
|
const PremiumGrant = models_1.PremiumGrantModel.initModel(sequelize);
|
|
29
29
|
const Wishlist = models_1.WishlistModel.initModel(sequelize);
|
|
30
30
|
const UserSettings = models_1.UserSettingsModel.initModel(sequelize);
|
|
31
|
+
const UserAchievements = models_1.UserAchievementsModel.initModel(sequelize);
|
|
31
32
|
const UserDeviceToken = models_1.UserDeviceTokenModel.initModel(sequelize);
|
|
32
33
|
const BasePositionPreset = models_1.BasePositionPresetModel.initModel(sequelize);
|
|
33
34
|
const Notification = models_1.NotificationModel.initModel(sequelize);
|
|
@@ -78,6 +79,7 @@ function initModels(sequelize) {
|
|
|
78
79
|
AuthUser.hasOne(GachaPity, { as: 'GachaPity', foreignKey: 'user_id' });
|
|
79
80
|
AuthUser.hasOne(Wishlist, { as: 'Wishlist', foreignKey: 'user_id' });
|
|
80
81
|
AuthUser.hasOne(UserSettings, { as: 'UserSettings', foreignKey: 'user_id' });
|
|
82
|
+
AuthUser.hasOne(UserAchievements, { as: 'UserAchievements', foreignKey: 'user_id' });
|
|
81
83
|
AuthUser.hasMany(UserDeviceToken, { as: 'UserDeviceTokens', foreignKey: 'user_id' });
|
|
82
84
|
AuthUser.hasMany(BasePositionPreset, { as: 'BasePositionPresets', foreignKey: 'user_id' });
|
|
83
85
|
AuthUser.hasMany(Notification, { as: 'Notifications', foreignKey: 'user_id' });
|
|
@@ -101,6 +103,7 @@ function initModels(sequelize) {
|
|
|
101
103
|
PremiumGrant.belongsTo(AuthUser, { as: 'grantedByUser', foreignKey: 'granted_by' });
|
|
102
104
|
Wishlist.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
103
105
|
UserSettings.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
106
|
+
UserAchievements.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
|
|
104
107
|
Competition.belongsTo(Iteration, { as: 'Iteration', foreignKey: 'iteration' });
|
|
105
108
|
Competition.hasMany(CompetitionMatch, { as: 'CompetitionMatches', foreignKey: 'competition_id' });
|
|
106
109
|
Competition.hasMany(CompetitionStandings, { as: 'CompetitionStandings', foreignKey: 'competition_id' });
|
|
@@ -370,6 +373,7 @@ function initModels(sequelize) {
|
|
|
370
373
|
PremiumGrant,
|
|
371
374
|
Wishlist,
|
|
372
375
|
UserSettings,
|
|
376
|
+
UserAchievements,
|
|
373
377
|
UserDeviceToken,
|
|
374
378
|
BasePositionPreset,
|
|
375
379
|
Notification,
|
|
@@ -17,9 +17,10 @@ export declare enum CurrencyTransactionSourceEnum {
|
|
|
17
17
|
SEASON_REWARD = "SEASON_REWARD",
|
|
18
18
|
COMPETITION_REWARD = "COMPETITION_REWARD",
|
|
19
19
|
PHYSICIAN_UPGRADE = "PHYSICIAN_UPGRADE",
|
|
20
|
-
PHYSIOTHERAPIST_UPGRADE = "PHYSIOTHERAPIST_UPGRADE"
|
|
20
|
+
PHYSIOTHERAPIST_UPGRADE = "PHYSIOTHERAPIST_UPGRADE",
|
|
21
|
+
ACHIEVEMENT_CLAIM = "ACHIEVEMENT_CLAIM"
|
|
21
22
|
}
|
|
22
|
-
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.SCOUT_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD | CurrencyTransactionSourceEnum.COMPETITION_REWARD | CurrencyTransactionSourceEnum.PHYSICIAN_UPGRADE | CurrencyTransactionSourceEnum.PHYSIOTHERAPIST_UPGRADE;
|
|
23
|
+
export type CurrencyTransactionSource = CurrencyTransactionSourceEnum.MATCH_REWARD | CurrencyTransactionSourceEnum.GACHA_PULL | CurrencyTransactionSourceEnum.COACH_UPGRADE | CurrencyTransactionSourceEnum.SCOUT_UPGRADE | CurrencyTransactionSourceEnum.PLAYER_DISMISS | CurrencyTransactionSourceEnum.PLAYER_RETIRE | CurrencyTransactionSourceEnum.ADMIN_GRANT | CurrencyTransactionSourceEnum.SEASON_REWARD | CurrencyTransactionSourceEnum.COMPETITION_REWARD | CurrencyTransactionSourceEnum.PHYSICIAN_UPGRADE | CurrencyTransactionSourceEnum.PHYSIOTHERAPIST_UPGRADE | CurrencyTransactionSourceEnum.ACHIEVEMENT_CLAIM;
|
|
23
24
|
export interface CurrencyTransactionAttributes {
|
|
24
25
|
transaction_id: string;
|
|
25
26
|
user_id: string;
|
|
@@ -20,6 +20,7 @@ var CurrencyTransactionSourceEnum;
|
|
|
20
20
|
CurrencyTransactionSourceEnum["COMPETITION_REWARD"] = "COMPETITION_REWARD";
|
|
21
21
|
CurrencyTransactionSourceEnum["PHYSICIAN_UPGRADE"] = "PHYSICIAN_UPGRADE";
|
|
22
22
|
CurrencyTransactionSourceEnum["PHYSIOTHERAPIST_UPGRADE"] = "PHYSIOTHERAPIST_UPGRADE";
|
|
23
|
+
CurrencyTransactionSourceEnum["ACHIEVEMENT_CLAIM"] = "ACHIEVEMENT_CLAIM";
|
|
23
24
|
})(CurrencyTransactionSourceEnum || (exports.CurrencyTransactionSourceEnum = CurrencyTransactionSourceEnum = {}));
|
|
24
25
|
class CurrencyTransactionModel extends sequelize_1.Model {
|
|
25
26
|
static initModel(sequelize) {
|
|
@@ -58,6 +58,7 @@ export * from './wishlist';
|
|
|
58
58
|
export * from './currency-transaction';
|
|
59
59
|
export * from './user-pull-grant';
|
|
60
60
|
export * from './user-settings';
|
|
61
|
+
export * from './user-achievements';
|
|
61
62
|
export * from './user-device-token';
|
|
62
63
|
export * from './notification';
|
|
63
64
|
export * from './vper';
|
|
@@ -74,6 +74,7 @@ __exportStar(require("./wishlist"), exports);
|
|
|
74
74
|
__exportStar(require("./currency-transaction"), exports);
|
|
75
75
|
__exportStar(require("./user-pull-grant"), exports);
|
|
76
76
|
__exportStar(require("./user-settings"), exports);
|
|
77
|
+
__exportStar(require("./user-achievements"), exports);
|
|
77
78
|
__exportStar(require("./user-device-token"), exports);
|
|
78
79
|
__exportStar(require("./notification"), exports);
|
|
79
80
|
__exportStar(require("./vper"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model } from 'sequelize';
|
|
3
|
+
import { AuthUserId, AuthUserModel } from '.';
|
|
4
|
+
export interface UserAchievementsAttributes {
|
|
5
|
+
user_id: string;
|
|
6
|
+
achievements: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export type UserAchievementsPk = 'user_id';
|
|
9
|
+
export type UserAchievementsId = UserAchievementsModel[UserAchievementsPk];
|
|
10
|
+
export type UserAchievementsCreationAttributes = Sequelize.Optional<UserAchievementsAttributes, 'achievements'>;
|
|
11
|
+
export declare class UserAchievementsModel extends Model<UserAchievementsAttributes, UserAchievementsCreationAttributes> implements UserAchievementsAttributes {
|
|
12
|
+
user_id: string;
|
|
13
|
+
achievements: Record<string, string>;
|
|
14
|
+
user: AuthUserModel;
|
|
15
|
+
getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
|
|
16
|
+
setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
|
|
17
|
+
createUser: Sequelize.BelongsToCreateAssociationMixin<AuthUserModel>;
|
|
18
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof UserAchievementsModel;
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserAchievementsModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class UserAchievementsModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return UserAchievementsModel.init({
|
|
8
|
+
user_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
references: {
|
|
13
|
+
model: 'AuthUser',
|
|
14
|
+
key: 'user_id'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
achievements: {
|
|
18
|
+
type: sequelize_1.DataTypes.JSONB,
|
|
19
|
+
allowNull: false,
|
|
20
|
+
defaultValue: {}
|
|
21
|
+
}
|
|
22
|
+
}, {
|
|
23
|
+
sequelize,
|
|
24
|
+
tableName: 'UserAchievements',
|
|
25
|
+
schema: 'public',
|
|
26
|
+
timestamps: false,
|
|
27
|
+
indexes: [
|
|
28
|
+
{
|
|
29
|
+
name: 'UserAchievements_pk',
|
|
30
|
+
unique: true,
|
|
31
|
+
fields: [{ name: 'user_id' }]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.UserAchievementsModel = UserAchievementsModel;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AchievementStatus = void 0;
|
|
4
|
+
// Per-user achievement progress state. Stored only inside the UserAchievements.achievements jsonb map
|
|
5
|
+
// (achievementKey -> status); an absent key means NOT_DONE, so no data backfill is ever needed when new
|
|
6
|
+
// achievements are added. Because it lives only inside jsonb it is NOT a Sequelize DataTypes.ENUM and has no
|
|
7
|
+
// Postgres CREATE TYPE (mirrors how Tactics stores RoleEnum values inside jsonb without a DB enum).
|
|
8
|
+
var AchievementStatus;
|
|
9
|
+
(function (AchievementStatus) {
|
|
10
|
+
AchievementStatus["NOT_DONE"] = "NOT_DONE";
|
|
11
|
+
AchievementStatus["DONE"] = "DONE";
|
|
12
|
+
AchievementStatus["CLAIMED"] = "CLAIMED";
|
|
13
|
+
})(AchievementStatus || (exports.AchievementStatus = AchievementStatus = {}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AchievementStatus } from './achievement-status';
|
|
2
|
+
export declare const ACHIEVEMENT_KEYS: readonly ["NORMAL_RECRUIT", "SPECIAL_RECRUIT", "DISMISS_PLAYER", "RETIRE_PLAYER", "GIVE_NICKNAME", "PLAY_MATCH", "WIN_MATCH", "FINISH_LEAGUE_SEASON", "UPGRADE_COACH", "UPGRADE_SCOUT", "UPGRADE_PHYSICIAN", "UPGRADE_PHYSIOTHERAPIST", "CHANGE_TRAINING_FOCUS", "CHANGE_TACTICS", "REGISTER_EVENT", "WIN_LEAGUE", "WIN_NATIONALS", "WIN_REGIONALS", "WIN_WCC", "READ_WIKI", "CLAIM_DAILY", "VIEW_MATCH", "PREVIEW_MATCH"];
|
|
3
|
+
export type AchievementKey = typeof ACHIEVEMENT_KEYS[number];
|
|
4
|
+
export declare const ACHIEVEMENT_REWARD = 500;
|
|
5
|
+
export declare const MILESTONE_ACHIEVEMENTS: readonly AchievementKey[];
|
|
6
|
+
export declare function isMilestone(key: AchievementKey): boolean;
|
|
7
|
+
export declare const MILESTONE_THRESHOLDS: readonly number[];
|
|
8
|
+
export declare function nextMilestone(claimedLevel: number): number | null;
|
|
9
|
+
export interface MilestoneProgress {
|
|
10
|
+
c: number;
|
|
11
|
+
l: number;
|
|
12
|
+
}
|
|
13
|
+
export interface MilestoneState {
|
|
14
|
+
count: number;
|
|
15
|
+
claimed: number;
|
|
16
|
+
target: number | null;
|
|
17
|
+
claimable: boolean;
|
|
18
|
+
maxed: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function milestoneState(value: unknown): MilestoneState;
|
|
21
|
+
export type AchievementMap = Partial<Record<AchievementKey, AchievementStatus | MilestoneProgress>>;
|
|
22
|
+
export declare const CLIENT_COMPLETABLE_ACHIEVEMENTS: readonly AchievementKey[];
|
|
23
|
+
export declare function isAchievementKey(value: string): value is AchievementKey;
|
|
24
|
+
export declare function isClientCompletable(key: AchievementKey): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLIENT_COMPLETABLE_ACHIEVEMENTS = exports.MILESTONE_THRESHOLDS = exports.MILESTONE_ACHIEVEMENTS = exports.ACHIEVEMENT_REWARD = exports.ACHIEVEMENT_KEYS = void 0;
|
|
4
|
+
exports.isMilestone = isMilestone;
|
|
5
|
+
exports.nextMilestone = nextMilestone;
|
|
6
|
+
exports.milestoneState = milestoneState;
|
|
7
|
+
exports.isAchievementKey = isAchievementKey;
|
|
8
|
+
exports.isClientCompletable = isClientCompletable;
|
|
9
|
+
// Single source of truth for the set of achievement keys (the tutorial checklist). Adding an achievement:
|
|
10
|
+
// 1. add its key here,
|
|
11
|
+
// 2. add its display config in the UI (src/view/achievements/achievements-config.ts),
|
|
12
|
+
// 3. add its name + description to every locale file,
|
|
13
|
+
// 4. wire its completion trigger (API handler or sim engine) to call completeAchievement.
|
|
14
|
+
// The key is only ever a jsonb map key, so there is no Postgres enum/column to migrate when the list grows.
|
|
15
|
+
exports.ACHIEVEMENT_KEYS = [
|
|
16
|
+
'NORMAL_RECRUIT',
|
|
17
|
+
'SPECIAL_RECRUIT',
|
|
18
|
+
'DISMISS_PLAYER',
|
|
19
|
+
'RETIRE_PLAYER',
|
|
20
|
+
'GIVE_NICKNAME',
|
|
21
|
+
'PLAY_MATCH',
|
|
22
|
+
'WIN_MATCH',
|
|
23
|
+
'FINISH_LEAGUE_SEASON',
|
|
24
|
+
'UPGRADE_COACH',
|
|
25
|
+
'UPGRADE_SCOUT',
|
|
26
|
+
'UPGRADE_PHYSICIAN',
|
|
27
|
+
'UPGRADE_PHYSIOTHERAPIST',
|
|
28
|
+
'CHANGE_TRAINING_FOCUS',
|
|
29
|
+
'CHANGE_TACTICS',
|
|
30
|
+
'REGISTER_EVENT',
|
|
31
|
+
'WIN_LEAGUE',
|
|
32
|
+
'WIN_NATIONALS',
|
|
33
|
+
'WIN_REGIONALS',
|
|
34
|
+
'WIN_WCC',
|
|
35
|
+
'READ_WIKI',
|
|
36
|
+
'CLAIM_DAILY',
|
|
37
|
+
'VIEW_MATCH',
|
|
38
|
+
'PREVIEW_MATCH'
|
|
39
|
+
];
|
|
40
|
+
// Every achievement grants the same flat coin reward when the user claims it (each milestone reached is a fresh claim).
|
|
41
|
+
exports.ACHIEVEMENT_REWARD = 500;
|
|
42
|
+
// Recurring "milestone" achievements: instead of a one-shot NOT_DONE->DONE->CLAIMED status, these store a running count
|
|
43
|
+
// and re-arm at escalating thresholds. Their map value is a MilestoneProgress object, not a status string.
|
|
44
|
+
exports.MILESTONE_ACHIEVEMENTS = [
|
|
45
|
+
'CLAIM_DAILY',
|
|
46
|
+
'PLAY_MATCH',
|
|
47
|
+
'WIN_MATCH',
|
|
48
|
+
'NORMAL_RECRUIT',
|
|
49
|
+
'DISMISS_PLAYER',
|
|
50
|
+
'WIN_LEAGUE',
|
|
51
|
+
'WIN_NATIONALS',
|
|
52
|
+
'WIN_REGIONALS',
|
|
53
|
+
'WIN_WCC'
|
|
54
|
+
];
|
|
55
|
+
function isMilestone(key) {
|
|
56
|
+
return exports.MILESTONE_ACHIEVEMENTS.includes(key);
|
|
57
|
+
}
|
|
58
|
+
// The claim thresholds every milestone steps through (x5, x2 alternating). Past the last one the milestone is "maxed".
|
|
59
|
+
exports.MILESTONE_THRESHOLDS = [1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000];
|
|
60
|
+
// The next threshold strictly greater than `claimedLevel` (0 = nothing claimed yet), or null when all are claimed.
|
|
61
|
+
function nextMilestone(claimedLevel) {
|
|
62
|
+
return exports.MILESTONE_THRESHOLDS.find(t => t > claimedLevel) ?? null;
|
|
63
|
+
}
|
|
64
|
+
// Derive a milestone's display/claim state from its raw map value (a MilestoneProgress object, a legacy status string,
|
|
65
|
+
// or undefined). Anything that is not a well-formed object counts as no progress ({c:0, l:0}). Shared by the API claim
|
|
66
|
+
// path and the UI.
|
|
67
|
+
function milestoneState(value) {
|
|
68
|
+
const mp = (value != null && typeof value === 'object') ? value : {};
|
|
69
|
+
const count = typeof mp.c === 'number' && Number.isFinite(mp.c) ? mp.c : 0;
|
|
70
|
+
const claimed = typeof mp.l === 'number' && Number.isFinite(mp.l) ? mp.l : 0;
|
|
71
|
+
const target = nextMilestone(claimed);
|
|
72
|
+
return { count, claimed, target, claimable: target != null && count >= target, maxed: target == null };
|
|
73
|
+
}
|
|
74
|
+
// Achievements the client is allowed to self-complete because there is no server-side action to hook: opening the
|
|
75
|
+
// wiki and opening a match detail. The POST /achievements/:key/complete endpoint MUST reject any key not in this set,
|
|
76
|
+
// so a client can never self-complete a server-authoritative achievement (recruit, win, upgrade, ...).
|
|
77
|
+
exports.CLIENT_COMPLETABLE_ACHIEVEMENTS = ['READ_WIKI', 'VIEW_MATCH', 'PREVIEW_MATCH'];
|
|
78
|
+
// Whether `value` is one of the known achievement keys (use before trusting a request param).
|
|
79
|
+
function isAchievementKey(value) {
|
|
80
|
+
return exports.ACHIEVEMENT_KEYS.includes(value);
|
|
81
|
+
}
|
|
82
|
+
// Whether the client is allowed to fire completion for this key (only READ_WIKI / VIEW_MATCH).
|
|
83
|
+
function isClientCompletable(key) {
|
|
84
|
+
return exports.CLIENT_COMPLETABLE_ACHIEVEMENTS.includes(key);
|
|
85
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./achievement-status"), exports);
|
|
18
|
+
__exportStar(require("./achievement-type"), exports);
|
|
19
|
+
__exportStar(require("./schemas/achievements.z"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.achievementMapSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const achievement_status_1 = require("../achievement-status");
|
|
6
|
+
// Validates a UserAchievements.achievements jsonb map (achievementKey -> status). Keys are loose strings so an
|
|
7
|
+
// unknown/legacy key never fails validation; values must be a known AchievementStatus. Mirrors the z.record idiom
|
|
8
|
+
// used by Tactics (tactics.z.ts).
|
|
9
|
+
exports.achievementMapSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.nativeEnum(achievement_status_1.AchievementStatus));
|
|
@@ -22,4 +22,5 @@ __exportStar(require("./player"), exports);
|
|
|
22
22
|
__exportStar(require("./competition"), exports);
|
|
23
23
|
__exportStar(require("./notification"), exports);
|
|
24
24
|
__exportStar(require("./premium"), exports);
|
|
25
|
+
__exportStar(require("./achievement"), exports);
|
|
25
26
|
__exportStar(require("./utils"), exports);
|
|
@@ -13,11 +13,14 @@ const WEIGHTS = {
|
|
|
13
13
|
BLOCK_TOUCH: 0.2,
|
|
14
14
|
DEFENSE_DIG: 0.25,
|
|
15
15
|
SETTING_PASS: 0.15,
|
|
16
|
+
// A swing that neither killed nor was stuffed nor sailed: the ball is still alive and the defence had to play it.
|
|
17
|
+
ATTACK_IN_PLAY: 0.10,
|
|
16
18
|
// reception quality
|
|
17
19
|
RECEPTION_PERFECT: 0.35,
|
|
18
20
|
RECEPTION_POSITIVE: 0.15,
|
|
19
21
|
RECEPTION_OVERPASS: -0.6,
|
|
20
|
-
// terminal errors (always point against)
|
|
22
|
+
// terminal errors (always point against). A stuffed attack is booked as an attack error by the simulator, so
|
|
23
|
+
// being blocked is penalised here at the same -1.0 as a swing into the net.
|
|
21
24
|
TERMINAL_ERROR: -1.0
|
|
22
25
|
};
|
|
23
26
|
// Minimum points a player must be on court for in a match to receive a VPER. VPER is a per-100-points rate,
|
|
@@ -47,14 +50,31 @@ class VPER {
|
|
|
47
50
|
let pointsPlayedSum = 0;
|
|
48
51
|
for (const box of boxScores) {
|
|
49
52
|
const w = WEIGHTS;
|
|
53
|
+
// Each action is scored ONCE, at its highest-value outcome. A set that became a kill is an assist, not an
|
|
54
|
+
// assist plus a pass; a touch that became a stuff is a block point, not a block point plus a touch. The
|
|
55
|
+
// subtractions below strip the lower-value credit that the simulator books alongside the terminal one.
|
|
56
|
+
//
|
|
57
|
+
// Each remainder is clamped at 0. A well-formed row can never make one negative (a swing yields at most one
|
|
58
|
+
// of {kill, error}; every assist belongs to a set that booked a pass on this same row; BlockStatsSchema
|
|
59
|
+
// enforces solo + assists <= touches), so the clamp only guards against a malformed or hand-built row
|
|
60
|
+
// silently turning a subtraction into a bonus.
|
|
61
|
+
//
|
|
62
|
+
// A swing that stayed alive: attempts minus the ones that terminated (a kill, or an error, which now
|
|
63
|
+
// includes being stuffed).
|
|
64
|
+
const inPlayAttacks = Math.max(0, box.attack.attempts - box.attack.kills - box.attack.errors);
|
|
65
|
+
// Sets that did NOT convert into a kill.
|
|
66
|
+
const unconvertedSets = Math.max(0, box.setting.pass - box.setting.assists);
|
|
67
|
+
// Touches that did not become a block point.
|
|
68
|
+
const nonScoringTouches = Math.max(0, box.block.touches - box.block.solo - box.block.assists);
|
|
50
69
|
const positive = w.ATTACK_KILL * box.attack.kills +
|
|
70
|
+
w.ATTACK_IN_PLAY * inPlayAttacks +
|
|
51
71
|
w.SERVE_ACE * box.serve.aces +
|
|
52
72
|
w.BLOCK_SOLO * box.block.solo +
|
|
53
73
|
w.BLOCK_ASSIST * box.block.assists +
|
|
54
74
|
w.SETTING_ASSIST * box.setting.assists +
|
|
55
|
-
w.BLOCK_TOUCH *
|
|
75
|
+
w.BLOCK_TOUCH * nonScoringTouches +
|
|
56
76
|
w.DEFENSE_DIG * box.defense.digs +
|
|
57
|
-
w.SETTING_PASS *
|
|
77
|
+
w.SETTING_PASS * unconvertedSets +
|
|
58
78
|
w.RECEPTION_PERFECT * box.reception.perfect +
|
|
59
79
|
w.RECEPTION_POSITIVE * box.reception.positive;
|
|
60
80
|
const negative = w.RECEPTION_OVERPASS * box.reception.overpasses +
|
|
@@ -106,6 +106,92 @@ function makeBox() {
|
|
|
106
106
|
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(14, 5);
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
|
+
// ─── each action scores once, at its highest-value outcome ────────────────────
|
|
110
|
+
(0, globals_1.describe)('VPER.computeVPERForMatch() — no double-counting', () => {
|
|
111
|
+
(0, globals_1.it)('scores a converted set as an assist only, never assist + pass', () => {
|
|
112
|
+
const box = makeBox();
|
|
113
|
+
box.setting.pass = 1;
|
|
114
|
+
box.setting.assists = 1;
|
|
115
|
+
box.misc.pointsPlayed = 10;
|
|
116
|
+
// unconvertedSets = 1 - 1 = 0, so the setter earns 0.7, not 0.85.
|
|
117
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(7, 5);
|
|
118
|
+
});
|
|
119
|
+
(0, globals_1.it)('pays the pass weight only on sets that did not convert', () => {
|
|
120
|
+
const box = makeBox();
|
|
121
|
+
box.setting.pass = 10;
|
|
122
|
+
box.setting.assists = 4;
|
|
123
|
+
box.misc.pointsPlayed = 10;
|
|
124
|
+
// assists 4 * 0.7 = 2.8; unconverted 6 * 0.15 = 0.9; raw 3.7
|
|
125
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(37, 5);
|
|
126
|
+
});
|
|
127
|
+
(0, globals_1.it)('scores a solo stuff as a block point only, never solo + touch', () => {
|
|
128
|
+
const box = makeBox();
|
|
129
|
+
box.block.touches = 1;
|
|
130
|
+
box.block.solo = 1;
|
|
131
|
+
box.misc.pointsPlayed = 10;
|
|
132
|
+
// nonScoringTouches = 1 - 1 - 0 = 0, so the blocker earns 1.0, not 1.2.
|
|
133
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(10, 5);
|
|
134
|
+
});
|
|
135
|
+
(0, globals_1.it)('pays the touch weight only on touches that did not become a point', () => {
|
|
136
|
+
const box = makeBox();
|
|
137
|
+
box.block.touches = 5;
|
|
138
|
+
box.block.solo = 1;
|
|
139
|
+
box.block.assists = 2;
|
|
140
|
+
box.misc.pointsPlayed = 10;
|
|
141
|
+
// solo 1.0 + assists 2 * 0.5 = 1.0 + nonScoringTouches 2 * 0.2 = 0.4; raw 2.4
|
|
142
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(24, 5);
|
|
143
|
+
});
|
|
144
|
+
(0, globals_1.it)('never lets a malformed row turn a subtraction into a bonus', () => {
|
|
145
|
+
// kills without attempts, assists without passes, blocks without touches: every remainder clamps to 0.
|
|
146
|
+
const box = makeBox();
|
|
147
|
+
box.attack.kills = 5;
|
|
148
|
+
box.setting.assists = 10;
|
|
149
|
+
box.block.solo = 3;
|
|
150
|
+
box.misc.pointsPlayed = 10;
|
|
151
|
+
// 5 * 1.0 + 10 * 0.7 + 3 * 1.0 = 15, with no negative remainder contribution
|
|
152
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(150, 5);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
// ─── the non-scoring attack ──────────────────────────────────────────────────
|
|
156
|
+
(0, globals_1.describe)('VPER.computeVPERForMatch() — attack outcomes', () => {
|
|
157
|
+
(0, globals_1.it)('rewards a swing that stays in play (weight 0.10)', () => {
|
|
158
|
+
const box = makeBox();
|
|
159
|
+
box.attack.attempts = 10;
|
|
160
|
+
box.attack.kills = 3;
|
|
161
|
+
box.attack.errors = 1;
|
|
162
|
+
box.misc.pointsPlayed = 10;
|
|
163
|
+
// kills 3 * 1.0 = 3; inPlay (10 - 3 - 1) = 6 * 0.10 = 0.6; errors 1 * -1.0; raw 2.6
|
|
164
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(26, 5);
|
|
165
|
+
});
|
|
166
|
+
(0, globals_1.it)('penalises a stuffed swing at -1.0 (the simulator books it as an attack error)', () => {
|
|
167
|
+
const box = makeBox();
|
|
168
|
+
box.attack.attempts = 1;
|
|
169
|
+
box.attack.errors = 1;
|
|
170
|
+
box.misc.pointsPlayed = 10;
|
|
171
|
+
// inPlay = 0; the stuff is a terminal error
|
|
172
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(-10, 5);
|
|
173
|
+
});
|
|
174
|
+
(0, globals_1.it)('separates volume from efficiency: more dug swings for the same kills rates higher', () => {
|
|
175
|
+
const efficient = makeBox();
|
|
176
|
+
efficient.attack.attempts = 12;
|
|
177
|
+
efficient.attack.kills = 10;
|
|
178
|
+
efficient.misc.pointsPlayed = 100;
|
|
179
|
+
const volume = makeBox();
|
|
180
|
+
volume.attack.attempts = 30;
|
|
181
|
+
volume.attack.kills = 10;
|
|
182
|
+
volume.misc.pointsPlayed = 100;
|
|
183
|
+
// Pre-fix both scored exactly 10.0. Now the extra 18 dug swings are worth 0.10 each.
|
|
184
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([efficient])).toBeCloseTo(10.2, 5);
|
|
185
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([volume])).toBeCloseTo(12.0, 5);
|
|
186
|
+
});
|
|
187
|
+
(0, globals_1.it)('a swing that is killed scores the kill, not the kill plus an in-play credit', () => {
|
|
188
|
+
const box = makeBox();
|
|
189
|
+
box.attack.attempts = 1;
|
|
190
|
+
box.attack.kills = 1;
|
|
191
|
+
box.misc.pointsPlayed = 10;
|
|
192
|
+
(0, globals_1.expect)(vper_1.VPER.computeVPERForMatch([box])).toBeCloseTo(10, 5);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
109
195
|
// ─── VPER eligibility threshold ───────────────────────────────────────────────
|
|
110
196
|
(0, globals_1.describe)('VPER.qualifiesForVPER()', () => {
|
|
111
197
|
function boxWithPoints(points) {
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Event finished",
|
|
59
59
|
"body": "The event is over. See the final table and your rewards.",
|
|
60
60
|
"placed": "The event is over. You finished {{position}} of {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Your team is moving",
|
|
64
|
+
"body": "Your team has been chosen to be moved to the new region: Kyorin. Nothing has changed in terms of the team you manage, your players or anything else."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Evento terminado",
|
|
59
59
|
"body": "El evento ha terminado. Mira la tabla final y tus recompensas.",
|
|
60
60
|
"placed": "El evento ha terminado. Has quedado {{position}} de {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Tu equipo se traslada",
|
|
64
|
+
"body": "Tu equipo ha sido elegido para trasladarse a la nueva región: Kyorin. No cambia nada en cuanto al equipo que gestionas, tus jugadores ni nada más."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Événement terminé",
|
|
59
59
|
"body": "L'événement est terminé. Consulte le classement final et tes récompenses.",
|
|
60
60
|
"placed": "L'événement est terminé. Tu finis {{position}} sur {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Votre équipe déménage",
|
|
64
|
+
"body": "Votre équipe a été choisie pour être transférée vers la nouvelle région : Kyorin. Rien ne change concernant l'équipe que vous gérez, vos joueurs ni quoi que ce soit d'autre."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Evento concluso",
|
|
59
59
|
"body": "L'evento è finito. Guarda la classifica finale e i tuoi premi.",
|
|
60
60
|
"placed": "L'evento è finito. Hai chiuso {{position}} su {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "La tua squadra si trasferisce",
|
|
64
|
+
"body": "La tua squadra è stata scelta per essere spostata nella nuova regione: Kyorin. Non cambia nulla riguardo alla squadra che gestisci, ai tuoi giocatori o a qualsiasi altra cosa."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Wydarzenie zakończone",
|
|
59
59
|
"body": "Wydarzenie się skończyło. Zobacz tabelę końcową i nagrody.",
|
|
60
60
|
"placed": "Wydarzenie się skończyło. Zająłeś {{position}} miejsce na {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Twoja drużyna się przenosi",
|
|
64
|
+
"body": "Twoja drużyna została wybrana do przeniesienia do nowego regionu: Kyorin. Nic się nie zmienia, jeśli chodzi o zarządzaną przez ciebie drużynę, twoich zawodników ani cokolwiek innego."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Evento encerrado",
|
|
59
59
|
"body": "O evento acabou. Veja a tabela final e suas recompensas.",
|
|
60
60
|
"placed": "O evento acabou. Você terminou em {{position}} de {{entrants}}."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Seu time está mudando",
|
|
64
|
+
"body": "Seu time foi escolhido para ser movido para a nova região: Kyorin. Nada muda em relação ao time que você gerencia, seus jogadores ou qualquer outra coisa."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
"title": "Etkinlik bitti",
|
|
59
59
|
"body": "Etkinlik sona erdi. Final tablosuna ve ödüllerine bak.",
|
|
60
60
|
"placed": "Etkinlik sona erdi. {{entrants}} takım arasında {{position}}. oldun."
|
|
61
|
+
},
|
|
62
|
+
"regionRelocation": {
|
|
63
|
+
"title": "Takımın taşınıyor",
|
|
64
|
+
"body": "Takımın yeni bölgeye, Kyorin'e taşınmak üzere seçildi. Yönettiğin takım, oyuncuların ya da başka hiçbir şey açısından hiçbir şey değişmiyor."
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED"];
|
|
1
|
+
export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED", "REGION_RELOCATION"];
|
|
2
2
|
export type NotificationType = typeof NOTIFICATION_TYPES[number];
|
|
3
3
|
export declare const NOTIFICATION_LOCALES: readonly ["en", "es", "fr", "it", "pl", "pt-BR", "tr"];
|
|
4
4
|
export type NotificationLocale = typeof NOTIFICATION_LOCALES[number];
|
|
@@ -21,7 +21,8 @@ exports.NOTIFICATION_TYPES = [
|
|
|
21
21
|
'EVENT_REGISTRATION_CLOSING',
|
|
22
22
|
'EVENT_STARTED',
|
|
23
23
|
'EVENT_FINAL_ROUND',
|
|
24
|
-
'EVENT_FINISHED'
|
|
24
|
+
'EVENT_FINISHED',
|
|
25
|
+
'REGION_RELOCATION'
|
|
25
26
|
];
|
|
26
27
|
// The 7 supported locales. Must stay in sync with the API VALID_LOCALES set and the UI src/locales/*.json.
|
|
27
28
|
exports.NOTIFICATION_LOCALES = ['en', 'es', 'fr', 'it', 'pl', 'pt-BR', 'tr'];
|
|
@@ -162,6 +162,15 @@ exports.NOTIFICATION_REGISTRY = {
|
|
|
162
162
|
emitsPush: false,
|
|
163
163
|
richRender: 'COMPENSATION',
|
|
164
164
|
catalogKeys: []
|
|
165
|
+
},
|
|
166
|
+
// One-off relocation warning: sent to users whose team is being moved to Kyorin. Informational; opens their team.
|
|
167
|
+
REGION_RELOCATION: {
|
|
168
|
+
emitsPush: true,
|
|
169
|
+
deepLink: teamPath,
|
|
170
|
+
pushTitleKey: 'regionRelocation.title',
|
|
171
|
+
pushBodyKey: 'regionRelocation.body',
|
|
172
|
+
inAppMessageKey: 'regionRelocation.body',
|
|
173
|
+
catalogKeys: ['regionRelocation.title', 'regionRelocation.body']
|
|
165
174
|
}
|
|
166
175
|
};
|
|
167
176
|
// The notification types that emit a mobile push (everything except COMPENSATION). Source of truth = each
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
// (and populated from a picked default preset); consumed by the positional engine to place players. A team with no
|
|
6
6
|
// `basePositions` falls back to the default preset for its `rotationSystem`.
|
|
7
7
|
//
|
|
8
|
-
// KEYING: rotation 1..6 and zone 1..6 are the ROTATIONAL (at-the-whistle) slots. The
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
8
|
+
// KEYING: rotation 1..6 and zone 1..6 are the ROTATIONAL (at-the-whistle) slots. The `rotation` key is the MAIN
|
|
9
|
+
// designated setter's (designatedSetters[0]) rotational ZONE, raw 1..6 (`rotation` == that setter's zone; there is
|
|
10
|
+
// NO setter-zone->rotation remap). This is the single convention everywhere: the editor writes it (posOf anchored on
|
|
11
|
+
// the main setter, saved verbatim), the engine reads it (positioning.rotationOf = getMainSetter zone), and the
|
|
12
|
+
// offensive/tempo/blocking prefs key by mainSetter.position. `coords[formation][rotation][zone]` is the standing
|
|
13
|
+
// coordinate for that formation; `receive[rotation][zone]` is the reception-participation flag (absent zone => default
|
|
14
|
+
// to participating). (History: rotationOf once remapped via SETTER_ZONE_TO_ROTATION and defaultReceiveMap once keyed
|
|
15
|
+
// by that index; both were unified to the raw main-setter zone. See VolleyballSim/plans/rotation-key-main-setter.md.)
|
|
12
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
17
|
exports.BASE_FORMATIONS = void 0;
|
|
14
18
|
exports.BASE_FORMATIONS = ['SERVE', 'RECEIVE', 'DEFENCE', 'DEF_L', 'DEF_M', 'DEF_R', 'ATTACK'];
|