enefel 2.9.1 → 2.11.0
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/.claude/settings.local.json +7 -0
- package/2025/newSkills.ts +769 -0
- package/2025/raceMigration.ts +66 -0
- package/2025/rawTeamsData.ts +1726 -0
- package/2025/skillMigration.ts +155 -0
- package/avatarsSeason.ts +61 -0
- package/career.ts +64 -8
- package/career2025.ts +2752 -0
- package/dice.ts +40 -8
- package/dist/2025/newSkills.d.ts +10 -0
- package/dist/2025/newSkills.js +663 -0
- package/dist/2025/raceMigration.d.ts +10 -0
- package/dist/2025/raceMigration.js +53 -0
- package/dist/2025/rawTeamsData.d.ts +12 -0
- package/dist/2025/rawTeamsData.js +1699 -0
- package/dist/2025/skillMigration.d.ts +31 -0
- package/dist/2025/skillMigration.js +95 -0
- package/dist/avatarsSeason.d.ts +22 -0
- package/dist/avatarsSeason.js +54 -0
- package/dist/career.d.ts +13 -4
- package/dist/career.js +36 -7
- package/dist/career2025.d.ts +192 -0
- package/dist/career2025.js +2611 -0
- package/dist/dice.d.ts +10 -2
- package/dist/dice.js +31 -8
- package/dist/index.d.ts +17 -2
- package/dist/index.js +44 -5
- package/dist/move.d.ts +6 -2
- package/dist/move.js +35 -0
- package/dist/package-lock.json +913 -7
- package/dist/package.json +6 -2
- package/dist/pitch.d.ts +10 -1
- package/dist/pitch.js +16 -0
- package/dist/player.d.ts +8 -4
- package/dist/player.js +104 -20
- package/dist/position.d.ts +4 -0
- package/dist/position.js +17 -1
- package/dist/race.d.ts +23 -12
- package/dist/race.js +210 -2
- package/dist/skill.d.ts +26 -3
- package/dist/skill.js +199 -81
- package/dist/skills/hitAndRun.d.ts +10 -0
- package/dist/skills/hitAndRun.js +12 -0
- package/dist/skills/safePairOfHands.d.ts +28 -0
- package/dist/skills/safePairOfHands.js +18 -0
- package/dist/skills/sidestep.d.ts +21 -0
- package/dist/skills/sidestep.js +20 -0
- package/dist/status.d.ts +4 -1
- package/dist/status.js +14 -4
- package/dist/store.d.ts +3 -0
- package/dist/store.js +3 -0
- package/dist/teams/career_v2025.d.ts +246 -0
- package/dist/teams/career_v2025.js +3512 -0
- package/dist/teams/convert-csv-to-career.d.ts +1 -0
- package/dist/teams/convert-csv-to-career.js +1085 -0
- package/dist/types/models.d.ts +4 -0
- package/index.ts +57 -4
- package/jest.config.js +3 -0
- package/move.ts +50 -2
- package/npm-login.sh +0 -0
- package/package.json +6 -2
- package/pitch.ts +22 -0
- package/player.ts +105 -22
- package/position.ts +16 -0
- package/race.ts +227 -13
- package/skill.ts +217 -83
- package/skills/hitAndRun.ts +14 -0
- package/skills/safePairOfHands.ts +33 -0
- package/skills/sidestep.ts +25 -0
- package/status.ts +15 -3
- package/store.ts +3 -0
- package/teams/README.md +53 -0
- package/teams/clean-stats.js +54 -0
- package/teams/convert-csv-to-career.ts +1209 -0
- package/teams/players_clean.csv +107 -0
- package/teams/players_next.csv +21 -0
- package/types/models.ts +4 -0
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const sync_1 = require("csv-parse/sync");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const CSV_PATH = "./teams/players_clean.csv";
|
|
9
|
+
const OUTPUT_PATH = "./career2025.ts";
|
|
10
|
+
const SKILL_FILE_PATH = "./skill.ts";
|
|
11
|
+
const CAREER_FILE_PATH = "./career.ts";
|
|
12
|
+
// Icônes déjà présentes côté client (sans l'extension .gif)
|
|
13
|
+
const KNOWN_ICONS = new Set([
|
|
14
|
+
"amazon-blitzer1",
|
|
15
|
+
"amazon-catcher1",
|
|
16
|
+
"amazon-linewomen1",
|
|
17
|
+
"amazon-thrower1",
|
|
18
|
+
"chaos-beastman1",
|
|
19
|
+
"chaos-dwarf-blocker1",
|
|
20
|
+
"chaos-dwarf-bull-centaur1",
|
|
21
|
+
"chaos-dwarf-hobgobelin1",
|
|
22
|
+
"chaos-minotaur1",
|
|
23
|
+
"chaos-warrior1",
|
|
24
|
+
"dark-elfe-blitzer1",
|
|
25
|
+
"dark-elfe-lineman1",
|
|
26
|
+
"dark-elfe-runner1",
|
|
27
|
+
"dark-elfe-witch1",
|
|
28
|
+
"dwarf-blitzer1",
|
|
29
|
+
"dwarf-deathroller1",
|
|
30
|
+
"dwarf-runner1",
|
|
31
|
+
"dwarf-troll-slayer1",
|
|
32
|
+
"dwarf1",
|
|
33
|
+
"elven-union-blitzer1",
|
|
34
|
+
"elven-union-catcher1",
|
|
35
|
+
"elven-union-lineman1",
|
|
36
|
+
"elven-union-thrower1",
|
|
37
|
+
"gobelin-lineman1",
|
|
38
|
+
"gobelin-looney1",
|
|
39
|
+
"gobelin-pogoer1",
|
|
40
|
+
"gobelin-troll1",
|
|
41
|
+
"halfling-treeman1",
|
|
42
|
+
"human-blitzer1",
|
|
43
|
+
"human-catcher1",
|
|
44
|
+
"human-ogre1",
|
|
45
|
+
"human-thrower1",
|
|
46
|
+
"human1",
|
|
47
|
+
"imperial-blitzer1",
|
|
48
|
+
"imperial-bodyguard1",
|
|
49
|
+
"imperial-lineman1",
|
|
50
|
+
"imperial-ogre1",
|
|
51
|
+
"imperial-thrower1",
|
|
52
|
+
"lizardmen-chameleon1",
|
|
53
|
+
"lizardmen-kroxigor1",
|
|
54
|
+
"lizardmen-saurus1",
|
|
55
|
+
"lizardmen-skink1",
|
|
56
|
+
"norse-berserker1",
|
|
57
|
+
"norse-catcher1",
|
|
58
|
+
"norse-lineman1",
|
|
59
|
+
"norse-thrower1",
|
|
60
|
+
"norse-ulfwereners1",
|
|
61
|
+
"norse-yhetee1",
|
|
62
|
+
"orc-black-orc1",
|
|
63
|
+
"orc-blitzer1",
|
|
64
|
+
"orc-thrower1",
|
|
65
|
+
"orc-troll1",
|
|
66
|
+
"orc1",
|
|
67
|
+
"rat-blitzer1",
|
|
68
|
+
"rat-ogre1",
|
|
69
|
+
"rat-runner1",
|
|
70
|
+
"rat-thrower1",
|
|
71
|
+
"rat1",
|
|
72
|
+
"silvan-catcher1",
|
|
73
|
+
"silvan-thrower1",
|
|
74
|
+
"silvan-wardancer1",
|
|
75
|
+
"silvan1",
|
|
76
|
+
"slann-blitzer1",
|
|
77
|
+
"slann-catcher1",
|
|
78
|
+
"slann-kroxigor1",
|
|
79
|
+
"slann-lineman1",
|
|
80
|
+
"snotling-lineman1",
|
|
81
|
+
"undead-ghoul1",
|
|
82
|
+
"undead-mummies1",
|
|
83
|
+
"undead-skeleton1",
|
|
84
|
+
"undead-wight1",
|
|
85
|
+
"undead-zombie1",
|
|
86
|
+
"vampire-thrall1",
|
|
87
|
+
"vampire-vampire1",
|
|
88
|
+
]);
|
|
89
|
+
// Adapter icônes générées -> icônes existantes
|
|
90
|
+
const ICON_ADAPTER = {
|
|
91
|
+
// Amazon
|
|
92
|
+
"amazon-linewoman1": "amazon-linewomen1",
|
|
93
|
+
"amazon-blocker1": "amazon-blitzer1",
|
|
94
|
+
// Chaos Chosen
|
|
95
|
+
"chaos-chosen-beastman1": "chaos-beastman1",
|
|
96
|
+
"chaos-chosen-chosen1": "chaos-warrior1",
|
|
97
|
+
"chaos-chosen-minotaur1": "chaos-minotaur1",
|
|
98
|
+
// Chaos Dwarf
|
|
99
|
+
"chaos-dwarf-hobgoblin1": "chaos-dwarf-hobgobelin1",
|
|
100
|
+
"chaos-dwarf-stabba1": "chaos-dwarf-hobgobelin1",
|
|
101
|
+
"chaos-dwarf-flamer1": "chaos-dwarf-blocker1",
|
|
102
|
+
"chaos-dwarf-minotaur1": "chaos-minotaur1",
|
|
103
|
+
// Dark Elf
|
|
104
|
+
"dark-elf-lineman1": "dark-elfe-lineman1",
|
|
105
|
+
"dark-elf-runner1": "dark-elfe-runner1",
|
|
106
|
+
"dark-elf-assassin1": "dark-elfe-blitzer1",
|
|
107
|
+
"dark-elf-blitzer1": "dark-elfe-blitzer1",
|
|
108
|
+
"dark-elf-witch-elf1": "dark-elfe-witch1",
|
|
109
|
+
// Dwarf
|
|
110
|
+
"dwarf-lineman1": "dwarf1",
|
|
111
|
+
// Human
|
|
112
|
+
"human-lineman1": "human1",
|
|
113
|
+
// Orc
|
|
114
|
+
"orc-lineman1": "orc1",
|
|
115
|
+
"orc-big-un1": "orc-black-orc1",
|
|
116
|
+
// Goblin
|
|
117
|
+
"goblin-lineman1": "gobelin-lineman1",
|
|
118
|
+
"goblin-loony1": "gobelin-looney1",
|
|
119
|
+
"goblin-pogoer1": "gobelin-pogoer1",
|
|
120
|
+
"goblin-troll1": "gobelin-troll1",
|
|
121
|
+
"goblin-ooligan1": "gobelin-lineman1",
|
|
122
|
+
"goblin-doom-diver1": "gobelin-pogoer1",
|
|
123
|
+
// Halfling (fallback V6)
|
|
124
|
+
"halfling-lineman1": "halfling-lineman1",
|
|
125
|
+
"halfling-hefty1": "halfling-hefty1",
|
|
126
|
+
"halfling-catcher1": "halfling-catcher1",
|
|
127
|
+
// Imperial Nobility (fallback V6)
|
|
128
|
+
"imperial-nobility-blitzer1": "imperial-blitzer1",
|
|
129
|
+
"imperial-nobility-bodyguard1": "imperial-bodyguard1",
|
|
130
|
+
"imperial-nobility-ogre1": "imperial-ogre1",
|
|
131
|
+
"imperial-nobility-retainer1": "imperial-lineman1",
|
|
132
|
+
"imperial-nobility-thrower1": "imperial-thrower1",
|
|
133
|
+
// Khorne (fallback V6)
|
|
134
|
+
"khorne-marauder1": "khorne-lineman1",
|
|
135
|
+
"khorne-khorngor1": "khorne-khorngor1",
|
|
136
|
+
"khorne-bloodseeker1": "khorne-bloodseeker1",
|
|
137
|
+
"khorne-bloodspawn1": "khorne-bloodspawn1",
|
|
138
|
+
// Necromantic Horror (fallback V6 necromantic)
|
|
139
|
+
"necromantic-horror-zombie1": "necromantic-zombie1",
|
|
140
|
+
"necromantic-horror-ghoul1": "necromantic-ghoul1",
|
|
141
|
+
"necromantic-horror-wraith1": "necromantic-wraith1",
|
|
142
|
+
"necromantic-horror-werewolf1": "necromantic-werewolf1",
|
|
143
|
+
"necromantic-horror-flesh-golem1": "necromantic-golem1",
|
|
144
|
+
// Norse (fallback V6)
|
|
145
|
+
"norse-raider1": "norse-lineman1",
|
|
146
|
+
"norse-ulfwerner1": "norse-ulfwereners1",
|
|
147
|
+
"norse-valkyrie1": "norse-catcher1",
|
|
148
|
+
// Nurgle (fallback V6)
|
|
149
|
+
"nurgle-rotter1": "nurgle-rotter1",
|
|
150
|
+
"nurgle-pestigor1": "nurgle-pestigor1",
|
|
151
|
+
"nurgle-bloater1": "nurgle-bloater1",
|
|
152
|
+
"nurgle-rotspawn1": "nurgle-rotspawn1",
|
|
153
|
+
// Skaven (fallback V6)
|
|
154
|
+
"skaven-thrower1": "rat-thrower1",
|
|
155
|
+
"skaven-gutter1": "rat-runner1",
|
|
156
|
+
"skaven-blitzer1": "rat-blitzer1",
|
|
157
|
+
"skaven-rat-ogre1": "rat-ogre1",
|
|
158
|
+
// Snotling (fallback V6 gobelin/snotling)
|
|
159
|
+
"snotling-hoppa1": "snotling-runner1",
|
|
160
|
+
"snotling-runna1": "snotling-lineman1",
|
|
161
|
+
"snotling-pump-wagon1": "snotling-wagon1",
|
|
162
|
+
"snotling-troll1": "snotling-troll1",
|
|
163
|
+
// Vampire (fallback V6)
|
|
164
|
+
"vampire-blitzer1": "vampire-vampire1",
|
|
165
|
+
"vampire-runner1": "vampire-vampire1",
|
|
166
|
+
"vampire-thrower1": "vampire-vampire1",
|
|
167
|
+
"vampire-vargheist1": "vampire-vampire1",
|
|
168
|
+
// Shambling Undead
|
|
169
|
+
"shambling-undead-skeleton1": "undead-skeleton1",
|
|
170
|
+
"shambling-undead-zombie1": "undead-zombie1",
|
|
171
|
+
"shambling-undead-ghoul1": "undead-ghoul1",
|
|
172
|
+
"shambling-undead-wight1": "undead-wight1",
|
|
173
|
+
"shambling-undead-mummy1": "undead-mummies1",
|
|
174
|
+
// Skaven
|
|
175
|
+
"skaven-clanrat1": "rat1",
|
|
176
|
+
// Wood Elf
|
|
177
|
+
"wood-elf-lineman1": "silvan1",
|
|
178
|
+
"wood-elf-thrower1": "silvan-thrower1",
|
|
179
|
+
"wood-elf-catcher1": "silvan-catcher1",
|
|
180
|
+
"wood-elf-wardancer1": "silvan-wardancer1",
|
|
181
|
+
"wood-elf-treeman1": "halfling-treeman1",
|
|
182
|
+
// High Elf (fallback vers Elven Union)
|
|
183
|
+
"high-elf-lineman1": "elven-union-lineman1",
|
|
184
|
+
"high-elf-thrower1": "elven-union-thrower1",
|
|
185
|
+
"high-elf-catcher1": "elven-union-catcher1",
|
|
186
|
+
"high-elf-blitzer1": "elven-union-blitzer1",
|
|
187
|
+
// Tomb King (fallback vers Undead)
|
|
188
|
+
"tomb-king-lineman1": "undead-skeleton1",
|
|
189
|
+
"tomb-king-thrower1": "undead-ghoul1",
|
|
190
|
+
"tomb-king-blitzer1": "undead-wight1",
|
|
191
|
+
"tomb-king-tomb-guardian1": "undead-mummies1",
|
|
192
|
+
};
|
|
193
|
+
const SKILL_NAME_MAP = {
|
|
194
|
+
"Always Hungry": "always-hungry",
|
|
195
|
+
"Animal Savagery": "animal-salvagery",
|
|
196
|
+
Animosity: "animosity",
|
|
197
|
+
"Big Guy": "big-guy",
|
|
198
|
+
Block: "block",
|
|
199
|
+
"Bone Head": "bone-head",
|
|
200
|
+
Brawler: "brawler",
|
|
201
|
+
"Break Tackle": "break-tackle",
|
|
202
|
+
Catch: "catch",
|
|
203
|
+
Chainsaw: "chainsaw",
|
|
204
|
+
Claws: "claw",
|
|
205
|
+
"Cloud Burster": "cloud-burster",
|
|
206
|
+
Dauntless: "dauntless",
|
|
207
|
+
Decay: "decay",
|
|
208
|
+
Defensive: "defensive",
|
|
209
|
+
"Dirty Player": "dirty-player",
|
|
210
|
+
"Disturbing Presence": "disturbing-presence",
|
|
211
|
+
"Diving Catch": "diving-catch",
|
|
212
|
+
"Diving Tackle": "diving-tackle",
|
|
213
|
+
Dodge: "dodge",
|
|
214
|
+
Drunkard: "drunkard",
|
|
215
|
+
"Dump-Off": "dump-off",
|
|
216
|
+
Fend: "fend",
|
|
217
|
+
"Foul Appearance": "foul-appearance",
|
|
218
|
+
Frenzy: "frenzy",
|
|
219
|
+
Fumblerooski: "fumblerooskie",
|
|
220
|
+
"Give and Go": "running-pass", // Mapped to GIVE_AND_GO for DB compatibility
|
|
221
|
+
Grab: "grab",
|
|
222
|
+
"Hail Mary Pass": "hail-mary-pass",
|
|
223
|
+
Horns: "horns",
|
|
224
|
+
"Hypnotic Gaze": "hypnotic-gaze",
|
|
225
|
+
"Iron Hard Skin": "iron-hard-skin",
|
|
226
|
+
Juggernaut: "juggernaut",
|
|
227
|
+
"Jump Up": "jump-up",
|
|
228
|
+
Leap: "leap",
|
|
229
|
+
Loner: "loner",
|
|
230
|
+
"Mighty Blow": "mighty-blow",
|
|
231
|
+
"Nerves of Steel": "nerves-of-steel",
|
|
232
|
+
"No Ball": "no-hand",
|
|
233
|
+
"No Hands": "no-hand",
|
|
234
|
+
"On The Ball": "on-the-ball",
|
|
235
|
+
"On the Ball": "on-the-ball",
|
|
236
|
+
Pass: "pass",
|
|
237
|
+
"Pick-me-up": "pick-me-up",
|
|
238
|
+
"Plague Ridden": "plague-ridden",
|
|
239
|
+
"Pogo Stick": "pogo-stick",
|
|
240
|
+
Pogo: "pogo-stick",
|
|
241
|
+
"Prehensile Tail": "prehensile-tail",
|
|
242
|
+
"Projectile Vomit": "projectile-vomit",
|
|
243
|
+
Pro: "pro",
|
|
244
|
+
Punt: "punt",
|
|
245
|
+
"Really Stupid": "really-stupid",
|
|
246
|
+
Regeneration: "regeneration",
|
|
247
|
+
"Right Stuff": "right-stuff",
|
|
248
|
+
"Safe Pass": "safe-pass",
|
|
249
|
+
"Safe Pair of Hands": "safe-pair-of-hands",
|
|
250
|
+
"Secret Weapon": "secret-weapon",
|
|
251
|
+
Shadowing: "shadowing",
|
|
252
|
+
Sidestep: "sidestep",
|
|
253
|
+
Sprint: "sprint",
|
|
254
|
+
"Stand Firm": "stand-firm",
|
|
255
|
+
Star: "star",
|
|
256
|
+
"Strip Ball": "strip-ball",
|
|
257
|
+
"Strong Arm": "strong-arm",
|
|
258
|
+
Stunty: "stunty",
|
|
259
|
+
"Sure Feet": "sure-feet",
|
|
260
|
+
"Sure Hands": "sure-hands",
|
|
261
|
+
Stab: "stab",
|
|
262
|
+
Tackle: "tackle",
|
|
263
|
+
"Take Root": "take-root",
|
|
264
|
+
Taunt: "taunt",
|
|
265
|
+
Tentacles: "tentacles",
|
|
266
|
+
"Thick Skull": "thick-skull",
|
|
267
|
+
"Throw Team-mate": "throw-team-mate",
|
|
268
|
+
Titchy: "titchy",
|
|
269
|
+
"Unchannelled Fury": "unchannelled-fury",
|
|
270
|
+
Unsteady: "unsteady",
|
|
271
|
+
Wrestle: "wrestle",
|
|
272
|
+
"Eye Gouge": "eye-gouge",
|
|
273
|
+
"Hit and Run": "hit-and-run",
|
|
274
|
+
"Ball & Chain": "ball-and-chain",
|
|
275
|
+
Bombardier: "bombardier",
|
|
276
|
+
"Breathe Fire": "breathe-fire",
|
|
277
|
+
Insignificant: "insignificant",
|
|
278
|
+
"Kick Team-mate": "kick-team-mate",
|
|
279
|
+
"Bloodlust (2+)": "bloodlust",
|
|
280
|
+
"Bloodlust (3+)": "bloodlust",
|
|
281
|
+
"Hatred (Troll)": "hatred-troll",
|
|
282
|
+
"Timmm-ber!": "timmm-ber",
|
|
283
|
+
Swoop: "swoop",
|
|
284
|
+
"Steady Footing": "steady-footing",
|
|
285
|
+
"Arm Bar": "arm-bar",
|
|
286
|
+
};
|
|
287
|
+
const TEAM_TO_RACE_MAP = {
|
|
288
|
+
Amazon: "amazon-career",
|
|
289
|
+
"Black Orc": "black-orc-career",
|
|
290
|
+
Bretonnian: "bretonnian-career",
|
|
291
|
+
"Chaos Chosen": "chaos-career",
|
|
292
|
+
"Chaos Dwarf": "chaos-dwarf-career",
|
|
293
|
+
"Chaos Renegate": "chaos-renegate-career",
|
|
294
|
+
"Dark Elf": "dark-elfe-career",
|
|
295
|
+
Dwarf: "dwarf-career",
|
|
296
|
+
"Elven Union": "elven-union-career",
|
|
297
|
+
Goblin: "gobelin-career",
|
|
298
|
+
Halfling: "halfling-career",
|
|
299
|
+
"High Elf": "high-elf-career",
|
|
300
|
+
Human: "human-career",
|
|
301
|
+
"Imperial Nobility": "imperial-career",
|
|
302
|
+
Khorne: "khorne-career",
|
|
303
|
+
Lizardmen: "lizardmen-career",
|
|
304
|
+
"Necromantic Horror": "necromantic-career",
|
|
305
|
+
Norse: "norse-career",
|
|
306
|
+
Nurgle: "nurgle-career",
|
|
307
|
+
Ogre: "ogre-career",
|
|
308
|
+
"Old World Alliance": "old-world-alliance-career",
|
|
309
|
+
Orc: "orc-career",
|
|
310
|
+
"Shambling Undead": "undead-career",
|
|
311
|
+
Skaven: "rat-career",
|
|
312
|
+
Snotling: "snotling-career",
|
|
313
|
+
"Tomb King": "tomb-king-career",
|
|
314
|
+
"Underworld Denizens": "underworld-denizens-career",
|
|
315
|
+
Vampire: "vampire-career",
|
|
316
|
+
"Wood Elf": "silvan-career",
|
|
317
|
+
};
|
|
318
|
+
const KEYWORD_TO_AVATAR_MAP = {
|
|
319
|
+
Human: "human",
|
|
320
|
+
"Human, Lineman": "human",
|
|
321
|
+
"Human, Thrower": "human",
|
|
322
|
+
"Human, Thorwer": "human",
|
|
323
|
+
"Human, Skeleton": "undead",
|
|
324
|
+
"Human, Thrall": "human",
|
|
325
|
+
"Human Woman": "human-woman",
|
|
326
|
+
"Human, Blocker": "human",
|
|
327
|
+
"Human, Blitzer": "human",
|
|
328
|
+
"Human, Catcher": "human",
|
|
329
|
+
"Human, Runner": "human",
|
|
330
|
+
"Human, Special": "human",
|
|
331
|
+
Goblin: "gobelin",
|
|
332
|
+
"Goblin, Lineman": "gobelin",
|
|
333
|
+
"Goblin, Special": "gobelin",
|
|
334
|
+
Orc: "orc",
|
|
335
|
+
"Lineman, Orc": "orc",
|
|
336
|
+
"Orc, Thrower": "orc",
|
|
337
|
+
"Orc, Blitzer": "orc",
|
|
338
|
+
"Blocker, Orc": "black-orc",
|
|
339
|
+
Dwarf: "dwarf",
|
|
340
|
+
"Dwarf, Lineman": "dwarf",
|
|
341
|
+
"Dwarf, Runner": "dwarf",
|
|
342
|
+
"Dwarf, Blitzer": "dwarf",
|
|
343
|
+
"Dwarf, Special": "dwarf",
|
|
344
|
+
"Blocker, Dwarf": "dwarf",
|
|
345
|
+
Elf: "elf",
|
|
346
|
+
"Elf, Lineman": "elf",
|
|
347
|
+
"Elf, Thrower": "elf",
|
|
348
|
+
"Elf, Runner": "elf",
|
|
349
|
+
"Elf, Special": "elf",
|
|
350
|
+
"Blitzer, Elf": "elf",
|
|
351
|
+
"Catcher, Elf": "elf",
|
|
352
|
+
"Dark Elf": "dark_elf",
|
|
353
|
+
Halfling: "halfing",
|
|
354
|
+
"Halfling, Lineman": "halfing",
|
|
355
|
+
"Blocker, Halfling": "halfing",
|
|
356
|
+
"Catcher, Halfling": "halfing",
|
|
357
|
+
Skaven: "rat",
|
|
358
|
+
"Lineman, Skaven": "rat",
|
|
359
|
+
"Skaven, Thrower": "rat",
|
|
360
|
+
"Runner, Skaven": "rat",
|
|
361
|
+
"Blitzer, Skaven": "rat",
|
|
362
|
+
Lizardman: "lezard",
|
|
363
|
+
"Lineman, Lizardman": "lezard",
|
|
364
|
+
"Lizardman, Thrower": "lezard",
|
|
365
|
+
"Blocker, Lizardman": "lezard",
|
|
366
|
+
Beastman: "beastman",
|
|
367
|
+
"Beastman, Lineman": "beastman",
|
|
368
|
+
"Beastman, Runner": "beastman",
|
|
369
|
+
Undead: "undead",
|
|
370
|
+
"Undead, Zombie": "undead",
|
|
371
|
+
"Undead, Wraith": "undead",
|
|
372
|
+
"Undead, Werewolf": "werewolf",
|
|
373
|
+
"Undead, Vampire": "human",
|
|
374
|
+
Skeleton: "undead",
|
|
375
|
+
"Human, Lineman, Skeleton, Undead": "undead",
|
|
376
|
+
"Human, Skeleton, Thrower, Undead": "undead",
|
|
377
|
+
"Blitzer, Human, Skeleton, Undead": "undead",
|
|
378
|
+
"Big Guy, Blocker, Human, Undead": "mummie",
|
|
379
|
+
Zombie: "undead",
|
|
380
|
+
"Human, Lineman, Undead, Zombie": "undead",
|
|
381
|
+
Ghoul: "undead",
|
|
382
|
+
"Ghoul, Runner, Undead": "undead",
|
|
383
|
+
Wight: "undead",
|
|
384
|
+
Mummy: "mummie",
|
|
385
|
+
Troll: "troll",
|
|
386
|
+
"Big Guy, Troll": "troll",
|
|
387
|
+
Ogre: "ogre",
|
|
388
|
+
"Big Guy, Ogre": "ogre",
|
|
389
|
+
"Big Guy, Blocker, Ogre": "ogre",
|
|
390
|
+
"Big Guy, Ogre, Thrower": "ogre",
|
|
391
|
+
Minotaur: "minotaur",
|
|
392
|
+
"Big Guy, Minotaur": "minotaur",
|
|
393
|
+
"Rat Ogre": "rat_ogre",
|
|
394
|
+
"Big Guy, Skaven": "rat_ogre",
|
|
395
|
+
Treeman: "treeman",
|
|
396
|
+
"Big Guy, Treeman": "treeman",
|
|
397
|
+
Spawn: "rotspawn",
|
|
398
|
+
"Big Guy, Spawn": "rotspawn",
|
|
399
|
+
Yhetee: "yhetee",
|
|
400
|
+
"Big Guy, Yhetee": "yhetee",
|
|
401
|
+
Gnoblar: "gobelin",
|
|
402
|
+
"Gnoblar, Lineman": "gobelin",
|
|
403
|
+
Snotling: "gobelin",
|
|
404
|
+
"Lineman, Snotling": "gobelin",
|
|
405
|
+
"Snotling, Special": "gobelin",
|
|
406
|
+
"Runner, Snotling": "gobelin",
|
|
407
|
+
"Big Guy, Snotling, Special": "wagon",
|
|
408
|
+
Construct: "golem",
|
|
409
|
+
"Blocker, Construct, Undead": "golem",
|
|
410
|
+
Centaur: "centaur",
|
|
411
|
+
"Blitzer, Dwarf": "centaur",
|
|
412
|
+
Animal: "gobelin",
|
|
413
|
+
"Animal, Special": "gobelin",
|
|
414
|
+
};
|
|
415
|
+
function enforceSpecialSkills(skills) {
|
|
416
|
+
const specials = ["big-guy", "star", "specialist"];
|
|
417
|
+
const present = new Set();
|
|
418
|
+
skills.forEach((skill) => {
|
|
419
|
+
if (typeof skill === "string" && specials.includes(skill)) {
|
|
420
|
+
present.add(skill);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const orderedKeep = [];
|
|
424
|
+
if (present.has("big-guy"))
|
|
425
|
+
orderedKeep.push("big-guy");
|
|
426
|
+
else if (present.has("star"))
|
|
427
|
+
orderedKeep.push("star");
|
|
428
|
+
else if (present.has("specialist"))
|
|
429
|
+
orderedKeep.push("specialist");
|
|
430
|
+
const allowed = new Set(orderedKeep.slice(0, 1));
|
|
431
|
+
const filtered = [];
|
|
432
|
+
for (const skill of skills) {
|
|
433
|
+
if (typeof skill === "string" && specials.includes(skill)) {
|
|
434
|
+
if (allowed.has(skill)) {
|
|
435
|
+
filtered.push(skill);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
filtered.push(skill);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return filtered;
|
|
443
|
+
}
|
|
444
|
+
function parseStat(stat) {
|
|
445
|
+
if (!stat || stat === "")
|
|
446
|
+
return 0;
|
|
447
|
+
const match = stat.match(/^(\d+)/);
|
|
448
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
449
|
+
}
|
|
450
|
+
function convertAGPAFrom2025To2020(value) {
|
|
451
|
+
if (value <= 0)
|
|
452
|
+
return 0;
|
|
453
|
+
return 7 - value;
|
|
454
|
+
}
|
|
455
|
+
function convertAVFrom2025To2020(value) {
|
|
456
|
+
if (value <= 0)
|
|
457
|
+
return 0;
|
|
458
|
+
return value - 1;
|
|
459
|
+
}
|
|
460
|
+
function parseCost(cost) {
|
|
461
|
+
if (!cost || cost === "")
|
|
462
|
+
return 0;
|
|
463
|
+
const match = cost.match(/^(\d+)/);
|
|
464
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
465
|
+
}
|
|
466
|
+
function parseSkills(skillsStr) {
|
|
467
|
+
if (!skillsStr || skillsStr.trim() === "")
|
|
468
|
+
return [];
|
|
469
|
+
const skills = [];
|
|
470
|
+
const skillParts = skillsStr.split(",").map((s) => s.trim());
|
|
471
|
+
for (const skillPart of skillParts) {
|
|
472
|
+
const lonerMatch = skillPart.match(/^Loner \(([\d+]+)\)$/i);
|
|
473
|
+
if (lonerMatch) {
|
|
474
|
+
const value = parseInt(lonerMatch[1].replace("+", ""), 10);
|
|
475
|
+
skills.push(["loner", value]);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const mightyBlowMatch = skillPart.match(/^Mighty Blow \(\+?(\d+)\)$/i);
|
|
479
|
+
if (mightyBlowMatch) {
|
|
480
|
+
const value = parseInt(mightyBlowMatch[1], 10);
|
|
481
|
+
skills.push(["mighty-blow", value]);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const dirtyPlayerMatch = skillPart.match(/^Dirty Player \((\d+)\)$/i);
|
|
485
|
+
if (dirtyPlayerMatch) {
|
|
486
|
+
const value = parseInt(dirtyPlayerMatch[1], 10);
|
|
487
|
+
skills.push(["dirty-player", value]);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const bloodlustMatch = skillPart.match(/^Bloodlust \(([\d+]+)\)$/i);
|
|
491
|
+
if (bloodlustMatch) {
|
|
492
|
+
const value = parseInt(bloodlustMatch[1].replace("+", ""), 10);
|
|
493
|
+
skills.push(["bloodlust", value]);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const animosityMatch = skillPart.match(/^Animosity \(([^)]+)\)$/i);
|
|
497
|
+
if (animosityMatch) {
|
|
498
|
+
skills.push("animosity");
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const hatredMatch = skillPart.match(/^Hatred \(([^)]+)\)$/i);
|
|
502
|
+
if (hatredMatch) {
|
|
503
|
+
skills.push("hatred-troll");
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const normalizedSkill = skillPart
|
|
507
|
+
.replace(/^\(/, "")
|
|
508
|
+
.replace(/\)$/, "")
|
|
509
|
+
.trim();
|
|
510
|
+
const skillKey = Object.keys(SKILL_NAME_MAP).find((key) => key.toLowerCase() === normalizedSkill.toLowerCase());
|
|
511
|
+
if (skillKey) {
|
|
512
|
+
skills.push(SKILL_NAME_MAP[skillKey]);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
console.warn(`⚠️ Skill non mappé: "${skillPart}"`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return skills;
|
|
519
|
+
}
|
|
520
|
+
function toKebabCase(str) {
|
|
521
|
+
return str
|
|
522
|
+
.toLowerCase()
|
|
523
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
524
|
+
.replace(/^-+|-+$/g, "");
|
|
525
|
+
}
|
|
526
|
+
function generateCareerId(team, position) {
|
|
527
|
+
const teamKebab = toKebabCase(team);
|
|
528
|
+
const positionKebab = toKebabCase(position);
|
|
529
|
+
return `${teamKebab}-${positionKebab}-2025`;
|
|
530
|
+
}
|
|
531
|
+
function generateRaceEnum(team) {
|
|
532
|
+
const race = TEAM_TO_RACE_MAP[team] || toKebabCase(team) + "-career";
|
|
533
|
+
return race.toUpperCase().replace(/-/g, "_");
|
|
534
|
+
}
|
|
535
|
+
function getExistingAvatars() {
|
|
536
|
+
const careerFileContent = fs_1.default.readFileSync(CAREER_FILE_PATH, "utf-8");
|
|
537
|
+
const existingAvatars = new Set();
|
|
538
|
+
const enumMatch = careerFileContent.match(/enum AVATAR \{([^}]+)\}/s);
|
|
539
|
+
if (enumMatch) {
|
|
540
|
+
const enumContent = enumMatch[1];
|
|
541
|
+
const avatarMatches = enumContent.matchAll(/(\w+)\s*=\s*"([^"]+)"/g);
|
|
542
|
+
for (const match of avatarMatches) {
|
|
543
|
+
existingAvatars.add(match[2]);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const avatarMatches = careerFileContent.matchAll(/avatars:\s*\[([^\]]+)\]/g);
|
|
547
|
+
for (const match of avatarMatches) {
|
|
548
|
+
const avatarsStr = match[1];
|
|
549
|
+
const avatarMatches2 = avatarsStr.matchAll(/AVATAR\.(\w+)/g);
|
|
550
|
+
for (const avatarMatch of avatarMatches2) {
|
|
551
|
+
const avatarKey = avatarMatch[1];
|
|
552
|
+
const enumMatch2 = careerFileContent.match(new RegExp(`${avatarKey}\\s*=\\s*"([^"]+)"`));
|
|
553
|
+
if (enumMatch2) {
|
|
554
|
+
existingAvatars.add(enumMatch2[1]);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return existingAvatars;
|
|
559
|
+
}
|
|
560
|
+
function getAvatarFromKeywords(keywords, position, team) {
|
|
561
|
+
const keywordParts = keywords.split(",").map((k) => k.trim());
|
|
562
|
+
let avatar = null;
|
|
563
|
+
for (const keyword of keywordParts) {
|
|
564
|
+
const mappedAvatar = KEYWORD_TO_AVATAR_MAP[keyword];
|
|
565
|
+
if (mappedAvatar) {
|
|
566
|
+
avatar = mappedAvatar;
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!avatar) {
|
|
571
|
+
const posLower = position.toLowerCase();
|
|
572
|
+
const teamLower = team.toLowerCase();
|
|
573
|
+
if (posLower.includes("troll")) {
|
|
574
|
+
avatar = "troll";
|
|
575
|
+
}
|
|
576
|
+
else if (posLower.includes("ogre")) {
|
|
577
|
+
avatar = "ogre";
|
|
578
|
+
}
|
|
579
|
+
else if (posLower.includes("treeman")) {
|
|
580
|
+
avatar = "treeman";
|
|
581
|
+
}
|
|
582
|
+
else if (posLower.includes("yhetee")) {
|
|
583
|
+
avatar = "yhetee";
|
|
584
|
+
}
|
|
585
|
+
else if (posLower.includes("minotaur")) {
|
|
586
|
+
avatar = "minotaur";
|
|
587
|
+
}
|
|
588
|
+
else if (posLower.includes("rat ogre") || posLower.includes("ratogre")) {
|
|
589
|
+
avatar = "rat_ogre";
|
|
590
|
+
}
|
|
591
|
+
else if (posLower.includes("werewolf")) {
|
|
592
|
+
avatar = "werewolf";
|
|
593
|
+
}
|
|
594
|
+
else if (posLower.includes("vampire")) {
|
|
595
|
+
avatar = "vampire";
|
|
596
|
+
}
|
|
597
|
+
else if (posLower.includes("golem")) {
|
|
598
|
+
avatar = "golem";
|
|
599
|
+
}
|
|
600
|
+
else if (posLower.includes("centaur")) {
|
|
601
|
+
avatar = "centaur";
|
|
602
|
+
}
|
|
603
|
+
else if (posLower.includes("wagon")) {
|
|
604
|
+
avatar = "wagon";
|
|
605
|
+
}
|
|
606
|
+
else if (teamLower.includes("elf") || teamLower.includes("elven")) {
|
|
607
|
+
avatar = "elf";
|
|
608
|
+
}
|
|
609
|
+
else if (teamLower.includes("dwarf")) {
|
|
610
|
+
avatar = "dwarf";
|
|
611
|
+
}
|
|
612
|
+
else if (teamLower.includes("orc")) {
|
|
613
|
+
avatar = "orc";
|
|
614
|
+
}
|
|
615
|
+
else if (teamLower.includes("goblin") || teamLower.includes("snotling")) {
|
|
616
|
+
avatar = "gobelin";
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
avatar = "human";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return avatar;
|
|
623
|
+
}
|
|
624
|
+
function determineRange(quantity) {
|
|
625
|
+
if (quantity.includes("0-16") || quantity.includes("0-12"))
|
|
626
|
+
return 0;
|
|
627
|
+
if (quantity.includes("0-6"))
|
|
628
|
+
return 5;
|
|
629
|
+
if (quantity.includes("0-4"))
|
|
630
|
+
return 4;
|
|
631
|
+
if (quantity.includes("0-3"))
|
|
632
|
+
return 3;
|
|
633
|
+
if (quantity.includes("0-2"))
|
|
634
|
+
return 2;
|
|
635
|
+
if (quantity.includes("0-1"))
|
|
636
|
+
return 6;
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
function getExistingSkills() {
|
|
640
|
+
const skillFileContent = fs_1.default.readFileSync(SKILL_FILE_PATH, "utf-8");
|
|
641
|
+
const existingSkills = new Set();
|
|
642
|
+
const enumMatch = skillFileContent.match(/enum SKILL_NAMES \{([^}]+)\}/s);
|
|
643
|
+
if (enumMatch) {
|
|
644
|
+
const enumContent = enumMatch[1];
|
|
645
|
+
const skillMatches = enumContent.matchAll(/(\w+)\s*=\s*"([^"]+)"/g);
|
|
646
|
+
for (const match of skillMatches) {
|
|
647
|
+
existingSkills.add(match[2]);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Alias pour la faute d'orthographe historique présente dans skill.ts
|
|
651
|
+
if (existingSkills.has("distrubing-presence")) {
|
|
652
|
+
existingSkills.add("disturbing-presence");
|
|
653
|
+
}
|
|
654
|
+
return existingSkills;
|
|
655
|
+
}
|
|
656
|
+
function getCommentedSkills() {
|
|
657
|
+
const skillFileContent = fs_1.default.readFileSync(SKILL_FILE_PATH, "utf-8");
|
|
658
|
+
const commentedSkills = new Set();
|
|
659
|
+
// Chercher les compétences commentées dans les tableaux (format: // SKILL_NAMES.XXX)
|
|
660
|
+
const commentedMatches = skillFileContent.matchAll(/\/\/\s*SKILL_NAMES\.(\w+)/g);
|
|
661
|
+
// Lire l'enum SKILL_NAMES pour mapper les clés aux valeurs
|
|
662
|
+
const enumMatch = skillFileContent.match(/enum SKILL_NAMES \{([^}]+)\}/s);
|
|
663
|
+
const enumToValueMap = {};
|
|
664
|
+
if (enumMatch) {
|
|
665
|
+
const enumContent = enumMatch[1];
|
|
666
|
+
const skillMatches = enumContent.matchAll(/(\w+)\s*=\s*"([^"]+)"/g);
|
|
667
|
+
for (const match of skillMatches) {
|
|
668
|
+
enumToValueMap[match[1]] = match[2];
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// Convertir les enum keys commentées en skill values
|
|
672
|
+
for (const match of commentedMatches) {
|
|
673
|
+
const enumKey = match[1];
|
|
674
|
+
if (enumToValueMap[enumKey]) {
|
|
675
|
+
commentedSkills.add(enumToValueMap[enumKey]);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return commentedSkills;
|
|
679
|
+
}
|
|
680
|
+
function toEnumKey(skillValue) {
|
|
681
|
+
return skillValue
|
|
682
|
+
.split("-")
|
|
683
|
+
.map((word) => word.toUpperCase())
|
|
684
|
+
.join("_");
|
|
685
|
+
}
|
|
686
|
+
function skillValueToEnumKey(skillValue) {
|
|
687
|
+
if (skillValue === "animal-salvagery") {
|
|
688
|
+
return "SKILL_NAMES.ANIMAL_SAVAGERY";
|
|
689
|
+
}
|
|
690
|
+
if (skillValue === "no-hand") {
|
|
691
|
+
return "SKILL_NAMES.NO_HANDS";
|
|
692
|
+
}
|
|
693
|
+
if (skillValue === "disturbing-presence" ||
|
|
694
|
+
skillValue === "distrubing-presence") {
|
|
695
|
+
return "SKILL_NAMES.DISTURBING_PRESENCE";
|
|
696
|
+
}
|
|
697
|
+
if (skillValue === "running-pass") {
|
|
698
|
+
return "SKILL_NAMES.GIVE_AND_GO";
|
|
699
|
+
}
|
|
700
|
+
const enumKey = toEnumKey(skillValue);
|
|
701
|
+
const existingSkills = getExistingSkills();
|
|
702
|
+
if (existingSkills.has(skillValue)) {
|
|
703
|
+
return `SKILL_NAMES.${enumKey}`;
|
|
704
|
+
}
|
|
705
|
+
return `SKILL_NAMES.${enumKey}`;
|
|
706
|
+
}
|
|
707
|
+
function checkAndReportMissingSkills(usedSkills) {
|
|
708
|
+
const existingSkills = getExistingSkills();
|
|
709
|
+
const missingSkills = new Set();
|
|
710
|
+
for (const skill of usedSkills) {
|
|
711
|
+
if (!existingSkills.has(skill)) {
|
|
712
|
+
missingSkills.add(skill);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (missingSkills.size > 0) {
|
|
716
|
+
console.log("\n⚠️ Compétences manquantes dans skill.ts:");
|
|
717
|
+
const sortedMissing = Array.from(missingSkills).sort();
|
|
718
|
+
for (const skill of sortedMissing) {
|
|
719
|
+
const enumKey = toEnumKey(skill);
|
|
720
|
+
console.log(` // TODO: Ajouter ${enumKey} = "${skill}"`);
|
|
721
|
+
console.log(` ${enumKey} = "${skill}",`);
|
|
722
|
+
}
|
|
723
|
+
console.log("");
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
console.log("✅ Toutes les compétences existent déjà dans skill.ts");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function main() {
|
|
730
|
+
const csvContent = fs_1.default.readFileSync(CSV_PATH, "utf-8");
|
|
731
|
+
const rows = (0, sync_1.parse)(csvContent, {
|
|
732
|
+
columns: true,
|
|
733
|
+
skip_empty_lines: true,
|
|
734
|
+
});
|
|
735
|
+
const races = new Set();
|
|
736
|
+
const careerIds = [];
|
|
737
|
+
const careers = [];
|
|
738
|
+
const usedSkills = new Set();
|
|
739
|
+
const missingIcons = new Set();
|
|
740
|
+
for (const row of rows) {
|
|
741
|
+
if (!row.Team || !row.Position)
|
|
742
|
+
continue;
|
|
743
|
+
const careerId = generateCareerId(row.Team, row.Position);
|
|
744
|
+
const race = TEAM_TO_RACE_MAP[row.Team] || toKebabCase(row.Team) + "-career";
|
|
745
|
+
races.add(race);
|
|
746
|
+
const skills = parseSkills(row.Skills || "");
|
|
747
|
+
const keywordsLower = (row.Keywords || "").toLowerCase();
|
|
748
|
+
const isBigGuy = keywordsLower.includes("big guy");
|
|
749
|
+
if (isBigGuy && !skills.includes("big-guy")) {
|
|
750
|
+
skills.push("big-guy");
|
|
751
|
+
}
|
|
752
|
+
if (!row.Quantity.includes("0-16") && !skills.includes("specialist")) {
|
|
753
|
+
skills.push("specialist");
|
|
754
|
+
}
|
|
755
|
+
const avatar = getAvatarFromKeywords(row.Keywords || "", row.Position, row.Team);
|
|
756
|
+
for (const skill of skills) {
|
|
757
|
+
if (typeof skill === "string") {
|
|
758
|
+
usedSkills.add(skill);
|
|
759
|
+
}
|
|
760
|
+
else if (Array.isArray(skill) && skill.length > 0) {
|
|
761
|
+
usedSkills.add(skill[0]);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const rawIcon = `${toKebabCase(row.Team)}-${toKebabCase(row.Position)}1`;
|
|
765
|
+
const mappedIcon = ICON_ADAPTER[rawIcon] || rawIcon;
|
|
766
|
+
if (!KNOWN_ICONS.has(mappedIcon)) {
|
|
767
|
+
missingIcons.add(mappedIcon);
|
|
768
|
+
}
|
|
769
|
+
const rawAG = parseStat(row.AG);
|
|
770
|
+
const rawPA = parseStat(row.PA);
|
|
771
|
+
const rawAV = parseStat(row.AR);
|
|
772
|
+
const careerData = {
|
|
773
|
+
MA: parseStat(row.MA),
|
|
774
|
+
ST: parseStat(row.ST),
|
|
775
|
+
AG: convertAGPAFrom2025To2020(rawAG),
|
|
776
|
+
PA: convertAGPAFrom2025To2020(rawPA),
|
|
777
|
+
AV: convertAVFrom2025To2020(rawAV),
|
|
778
|
+
normal: row.Primary || "G",
|
|
779
|
+
double: row.Secondary || "",
|
|
780
|
+
normalCoach: "G",
|
|
781
|
+
icons: [mappedIcon],
|
|
782
|
+
skills,
|
|
783
|
+
avatar,
|
|
784
|
+
badge: race,
|
|
785
|
+
range: determineRange(row.Quantity),
|
|
786
|
+
cost: parseCost(row.Cost),
|
|
787
|
+
hasSprite: false,
|
|
788
|
+
version: "V2025",
|
|
789
|
+
keywords: row.Keywords || "",
|
|
790
|
+
};
|
|
791
|
+
careerIds.push(careerId);
|
|
792
|
+
careers.push({
|
|
793
|
+
id: careerId,
|
|
794
|
+
team: row.Team,
|
|
795
|
+
position: row.Position,
|
|
796
|
+
data: careerData,
|
|
797
|
+
isBigGuy,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
// Ajout de la compétence star pour le joueur non-Big Guy le plus cher (>100k) par équipe
|
|
801
|
+
const mostExpensiveByTeam = new Map();
|
|
802
|
+
careers.forEach((career, index) => {
|
|
803
|
+
if (career.isBigGuy)
|
|
804
|
+
return;
|
|
805
|
+
if (typeof career.data.cost !== "number" || career.data.cost <= 100)
|
|
806
|
+
return;
|
|
807
|
+
const current = mostExpensiveByTeam.get(career.team);
|
|
808
|
+
if (!current || career.data.cost > current.cost) {
|
|
809
|
+
mostExpensiveByTeam.set(career.team, {
|
|
810
|
+
index,
|
|
811
|
+
cost: career.data.cost,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
mostExpensiveByTeam.forEach(({ index }) => {
|
|
816
|
+
const target = careers[index];
|
|
817
|
+
if (target &&
|
|
818
|
+
!target.data.skills.includes("star") &&
|
|
819
|
+
!target.isBigGuy &&
|
|
820
|
+
typeof target.data.cost === "number" &&
|
|
821
|
+
target.data.cost > 100) {
|
|
822
|
+
target.data.skills.push("star");
|
|
823
|
+
usedSkills.add("star");
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
// Répartition des ranges par équipe
|
|
827
|
+
const teams = new Map();
|
|
828
|
+
careers.forEach((career, idx) => {
|
|
829
|
+
const list = teams.get(career.team) ?? [];
|
|
830
|
+
list.push(idx);
|
|
831
|
+
teams.set(career.team, list);
|
|
832
|
+
});
|
|
833
|
+
teams.forEach((indices) => {
|
|
834
|
+
const bigGuyIndices = indices.filter((i) => careers[i].isBigGuy);
|
|
835
|
+
const starIndices = indices.filter((i) => careers[i].data.skills.some((s) => Array.isArray(s) ? s[0] === "star" : s === "star"));
|
|
836
|
+
const otherIndices = indices
|
|
837
|
+
.filter((i) => !bigGuyIndices.includes(i) && !starIndices.includes(i))
|
|
838
|
+
.sort((a, b) => {
|
|
839
|
+
const costA = careers[a].data.cost ?? 0;
|
|
840
|
+
const costB = careers[b].data.cost ?? 0;
|
|
841
|
+
return costA - costB;
|
|
842
|
+
});
|
|
843
|
+
// Assign ranges: big guy -> 6, star -> 5, autres 0..5 en boucle en évitant les slots réservés
|
|
844
|
+
bigGuyIndices.forEach((i) => {
|
|
845
|
+
careers[i].data.range = 6;
|
|
846
|
+
});
|
|
847
|
+
starIndices.forEach((i) => {
|
|
848
|
+
careers[i].data.range = 5;
|
|
849
|
+
});
|
|
850
|
+
const reserved = new Set();
|
|
851
|
+
if (bigGuyIndices.length)
|
|
852
|
+
reserved.add(6);
|
|
853
|
+
if (starIndices.length)
|
|
854
|
+
reserved.add(5);
|
|
855
|
+
const baseRanges = [0, 1, 2, 3, 4, 5].filter((r) => !reserved.has(r));
|
|
856
|
+
if (baseRanges.length === 0) {
|
|
857
|
+
baseRanges.push(0); // fallback
|
|
858
|
+
}
|
|
859
|
+
otherIndices.forEach((i, idx) => {
|
|
860
|
+
const range = baseRanges[idx % baseRanges.length];
|
|
861
|
+
careers[i].data.range = range;
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
// Appliquer la limite de 2 compétences spéciales (big-guy, star, specialist) par priorité
|
|
865
|
+
careers.forEach((career) => {
|
|
866
|
+
career.data.skills = enforceSpecialSkills(career.data.skills);
|
|
867
|
+
});
|
|
868
|
+
const raceEnumEntries = Array.from(races)
|
|
869
|
+
.sort()
|
|
870
|
+
.map((race) => {
|
|
871
|
+
const enumName = race.toUpperCase().replace(/-/g, "_");
|
|
872
|
+
return ` ${enumName} = "${race}",`;
|
|
873
|
+
});
|
|
874
|
+
const careerIdEnumEntries = careerIds.sort().map((id) => {
|
|
875
|
+
const enumName = id.toUpperCase().replace(/-/g, "_");
|
|
876
|
+
return ` ${enumName} = "${id}",`;
|
|
877
|
+
});
|
|
878
|
+
const avatarValueToKey = (value) => {
|
|
879
|
+
const mapping = {
|
|
880
|
+
beastman: "BEASTMAN",
|
|
881
|
+
"black-orc": "BLACK_ORC",
|
|
882
|
+
bloater: "BLOATER",
|
|
883
|
+
bloodspawn: "BLOODSPAWN",
|
|
884
|
+
centaur: "CENTAUR",
|
|
885
|
+
"chaos-dwarf": "CHAOS_DWARF",
|
|
886
|
+
"chaos-warrior": "CHAOS_WARRIOR",
|
|
887
|
+
dark_elf: "DARK_ELF",
|
|
888
|
+
dwarf: "DWARF",
|
|
889
|
+
elf: "ELF",
|
|
890
|
+
gobelin: "GOBELIN",
|
|
891
|
+
golem: "GOLEM",
|
|
892
|
+
halfing: "HALFING",
|
|
893
|
+
"human-woman": "HUMAN_WOMAN",
|
|
894
|
+
human: "HUMAN",
|
|
895
|
+
lezard: "LIZARDMEN",
|
|
896
|
+
minotaur: "MINOTAUR",
|
|
897
|
+
mummie: "MUMMIE",
|
|
898
|
+
ogre: "OGRE",
|
|
899
|
+
orc: "ORC",
|
|
900
|
+
rat_ogre: "RAT_OGRE",
|
|
901
|
+
rat: "RAT",
|
|
902
|
+
rotspawn: "ROTSPAWN",
|
|
903
|
+
slann: "SLANN",
|
|
904
|
+
treeman: "TREEMAN",
|
|
905
|
+
troll: "TROLL",
|
|
906
|
+
undead: "UNDEAD",
|
|
907
|
+
vampire: "VAMPIRE",
|
|
908
|
+
wagon: "WAGON",
|
|
909
|
+
werewolf: "WEREWOLF",
|
|
910
|
+
yhetee: "YHETEE",
|
|
911
|
+
};
|
|
912
|
+
return mapping[value] || "HUMAN";
|
|
913
|
+
};
|
|
914
|
+
const existingSkills = getExistingSkills();
|
|
915
|
+
const existingAvatars = getExistingAvatars();
|
|
916
|
+
const commentedSkills = getCommentedSkills();
|
|
917
|
+
const formatSkill = (skill, existingSkills, commentedSkills) => {
|
|
918
|
+
let skillValue;
|
|
919
|
+
let skillValueWithParam = null;
|
|
920
|
+
if (typeof skill === "string") {
|
|
921
|
+
skillValue = skill;
|
|
922
|
+
}
|
|
923
|
+
else if (Array.isArray(skill) && skill.length === 2) {
|
|
924
|
+
skillValue = skill[0];
|
|
925
|
+
skillValueWithParam = `[${skillValueToEnumKey(skill[0])}, ${skill[1]}]`;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
return JSON.stringify(skill);
|
|
929
|
+
}
|
|
930
|
+
const enumKey = skillValueToEnumKey(skillValue);
|
|
931
|
+
const exists = existingSkills.has(skillValue);
|
|
932
|
+
const isCommented = commentedSkills.has(skillValue);
|
|
933
|
+
if (skillValueWithParam) {
|
|
934
|
+
if (exists && !isCommented) {
|
|
935
|
+
return skillValueWithParam;
|
|
936
|
+
}
|
|
937
|
+
else if (isCommented) {
|
|
938
|
+
return `// ${skillValueWithParam},`;
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
return `// ${skillValueWithParam}, // TODO: Ajouter ${toEnumKey(skillValue)} = "${skillValue}" dans skill.ts`;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
if (exists && !isCommented) {
|
|
946
|
+
return enumKey;
|
|
947
|
+
}
|
|
948
|
+
else if (isCommented) {
|
|
949
|
+
return `// ${enumKey},`;
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
return `// ${enumKey}, // TODO: Ajouter ${toEnumKey(skillValue)} = "${skillValue}" dans skill.ts`;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
const formatAvatar = (avatar, existingAvatars) => {
|
|
957
|
+
const enumKey = avatarValueToKey(avatar);
|
|
958
|
+
const exists = existingAvatars.has(avatar);
|
|
959
|
+
if (exists) {
|
|
960
|
+
return `AVATAR.${enumKey}`;
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
return `// AVATAR.${enumKey}, // TODO: Ajouter ${enumKey} dans career.ts`;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
const careerEntries = careers.map((career) => {
|
|
967
|
+
const enumName = career.id.toUpperCase().replace(/-/g, "_");
|
|
968
|
+
const skillsFormatted = career.data.skills.map((skill) => formatSkill(skill, existingSkills, commentedSkills));
|
|
969
|
+
const skillsStr = skillsFormatted.join(",\n ");
|
|
970
|
+
const avatarStr = formatAvatar(career.data.avatar, existingAvatars);
|
|
971
|
+
return ` [CAREER_ID.${enumName}]: {
|
|
972
|
+
MA: ${career.data.MA},
|
|
973
|
+
ST: ${career.data.ST},
|
|
974
|
+
AG: ${career.data.AG},
|
|
975
|
+
PA: ${career.data.PA},
|
|
976
|
+
AV: ${career.data.AV},
|
|
977
|
+
normal: "${career.data.normal}",
|
|
978
|
+
double: "${career.data.double}",
|
|
979
|
+
normalCoach: "${career.data.normalCoach}",
|
|
980
|
+
icons: ${JSON.stringify(career.data.icons)},
|
|
981
|
+
skills: [
|
|
982
|
+
${skillsStr}
|
|
983
|
+
],
|
|
984
|
+
avatars: [${avatarStr}],
|
|
985
|
+
badge: RACE.${career.data.badge.toUpperCase().replace(/-/g, "_")},
|
|
986
|
+
range: ${career.data.range},
|
|
987
|
+
cost: ${career.data.cost},
|
|
988
|
+
hasSprite: ${career.data.hasSprite},
|
|
989
|
+
version: CAREER_VERSION.V2025,
|
|
990
|
+
},`;
|
|
991
|
+
});
|
|
992
|
+
const output = `// MA: Movement Allowance
|
|
993
|
+
// ST : Strength
|
|
994
|
+
// AG: Agility
|
|
995
|
+
// AV: Armour Value
|
|
996
|
+
|
|
997
|
+
import { SKILL_NAMES } from "./skill";
|
|
998
|
+
|
|
999
|
+
enum RACE {
|
|
1000
|
+
${raceEnumEntries.join("\n")}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
enum AVATAR {
|
|
1004
|
+
BEASTMAN = "beastman",
|
|
1005
|
+
BLACK_ORC = "black-orc",
|
|
1006
|
+
BLOATER = "bloater",
|
|
1007
|
+
BLOODSPAWN = "bloodspawn",
|
|
1008
|
+
CENTAUR = "centaur",
|
|
1009
|
+
CHAOS_DWARF = "chaos-dwarf",
|
|
1010
|
+
CHAOS_WARRIOR = "chaos-warrior",
|
|
1011
|
+
DARK_ELF = "dark_elf",
|
|
1012
|
+
DWARF = "dwarf",
|
|
1013
|
+
ELF = "elf",
|
|
1014
|
+
GOBELIN = "gobelin",
|
|
1015
|
+
GOLEM = "golem",
|
|
1016
|
+
HALFING = "halfing",
|
|
1017
|
+
HUMAN_WOMAN = "human-woman",
|
|
1018
|
+
HUMAN = "human",
|
|
1019
|
+
LIZARDMEN = "lezard",
|
|
1020
|
+
MINOTAUR = "minotaur",
|
|
1021
|
+
MUMMIE = "mummie",
|
|
1022
|
+
OGRE = "ogre",
|
|
1023
|
+
ORC = "orc",
|
|
1024
|
+
RAT_OGRE = "rat_ogre",
|
|
1025
|
+
RAT = "rat",
|
|
1026
|
+
ROTSPAWN = "rotspawn",
|
|
1027
|
+
SLANN = "slann",
|
|
1028
|
+
TREEMAN = "treeman",
|
|
1029
|
+
TROLL = "troll",
|
|
1030
|
+
UNDEAD = "undead",
|
|
1031
|
+
VAMPIRE = "vampire",
|
|
1032
|
+
WAGON = "wagon",
|
|
1033
|
+
WEREWOLF = "werewolf",
|
|
1034
|
+
YHETEE = "yhetee",
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
enum CAREER_ID {
|
|
1038
|
+
${careerIdEnumEntries.join("\n")}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
enum CAREER_VERSION {
|
|
1042
|
+
V6 = "v6",
|
|
1043
|
+
V2020 = "v2020",
|
|
1044
|
+
V2025 = "v2025",
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
export type Career = {
|
|
1048
|
+
MA: number;
|
|
1049
|
+
ST: number;
|
|
1050
|
+
AG: number;
|
|
1051
|
+
PA: number;
|
|
1052
|
+
AV: number;
|
|
1053
|
+
normal: string;
|
|
1054
|
+
double: string;
|
|
1055
|
+
normalCoach: string;
|
|
1056
|
+
icons: string[];
|
|
1057
|
+
skills: (SKILL_NAMES | [SKILL_NAMES, number])[];
|
|
1058
|
+
avatars: AVATAR[];
|
|
1059
|
+
badge: RACE;
|
|
1060
|
+
range: number;
|
|
1061
|
+
cost: number;
|
|
1062
|
+
hasSprite?: boolean;
|
|
1063
|
+
version: CAREER_VERSION;
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
const CAREER: Record<CAREER_ID, Career> = {
|
|
1067
|
+
${careerEntries.join("\n\n")}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
const getCareers = (): Record<CAREER_ID, Career> => {
|
|
1071
|
+
return CAREER;
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
export { AVATAR, CAREER_ID, CAREER_VERSION, getCareers, RACE };
|
|
1075
|
+
`;
|
|
1076
|
+
fs_1.default.writeFileSync(OUTPUT_PATH, output);
|
|
1077
|
+
console.log(`✅ Fichier généré: ${OUTPUT_PATH}`);
|
|
1078
|
+
console.log(`📊 ${careers.length} carrières générées`);
|
|
1079
|
+
console.log(`🏆 ${races.size} races trouvées`);
|
|
1080
|
+
if (missingIcons.size > 0) {
|
|
1081
|
+
console.warn("ICÔNES MANQUANTES (ajouter dans client/public/tile/player, sans .gif):", Array.from(missingIcons).sort());
|
|
1082
|
+
}
|
|
1083
|
+
checkAndReportMissingSkills(usedSkills);
|
|
1084
|
+
}
|
|
1085
|
+
main();
|