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
package/dist/types/models.d.ts
CHANGED
|
@@ -77,10 +77,12 @@ export interface Player extends Point {
|
|
|
77
77
|
MA: number;
|
|
78
78
|
ST: number;
|
|
79
79
|
AG: number;
|
|
80
|
+
PA?: number | null;
|
|
80
81
|
AV: number;
|
|
81
82
|
current_MA: number;
|
|
82
83
|
current_ST: number;
|
|
83
84
|
current_AG: number;
|
|
85
|
+
current_PA?: number | null;
|
|
84
86
|
current_AV: number;
|
|
85
87
|
current_gfi: number;
|
|
86
88
|
date_next_action: Date | null;
|
|
@@ -99,6 +101,7 @@ export interface Player extends Point {
|
|
|
99
101
|
can_reroll: boolean;
|
|
100
102
|
can_reroll_rebound: boolean;
|
|
101
103
|
can_reroll_no_to: boolean;
|
|
104
|
+
secure_ball: boolean;
|
|
102
105
|
has_action: boolean;
|
|
103
106
|
pc: number;
|
|
104
107
|
aging: number;
|
|
@@ -106,6 +109,7 @@ export interface Player extends Point {
|
|
|
106
109
|
tempCarac: string;
|
|
107
110
|
userCreatedAt: Date;
|
|
108
111
|
avatar: string;
|
|
112
|
+
dice_id: string | null;
|
|
109
113
|
quote: string;
|
|
110
114
|
speak: string;
|
|
111
115
|
raz: boolean;
|
package/index.ts
CHANGED
|
@@ -8,16 +8,18 @@ import {
|
|
|
8
8
|
meteoMaxPassDistance,
|
|
9
9
|
} from "./meteo";
|
|
10
10
|
import { getMaxCarac, hasMove, PLAYER_CARAC } from "./player";
|
|
11
|
-
import { getEnemyTackleZones, isAdjacent } from "./position";
|
|
11
|
+
import { getAllAdjacentSquares, getEnemyTackleZones, isAdjacent } from "./position";
|
|
12
12
|
import { getCareerFromPlayer } from "./race";
|
|
13
13
|
import { getDisturbingPresenceMalus, hasSkill, SKILL_NAMES } from "./skill";
|
|
14
14
|
import {
|
|
15
|
+
hasStatusCanAssist,
|
|
15
16
|
hasStatusCanFaceup,
|
|
16
17
|
hasStatusCanStandup,
|
|
17
18
|
hasStatusFall,
|
|
18
19
|
hasStatusOnField,
|
|
19
20
|
hasStatusStanding,
|
|
20
21
|
hasStatusTacleZone,
|
|
22
|
+
PLAYER_STATUS,
|
|
21
23
|
} from "./status";
|
|
22
24
|
import version from "./version";
|
|
23
25
|
|
|
@@ -142,6 +144,7 @@ const GAME_HISTORY_INFO = {
|
|
|
142
144
|
ARMOR: "ar",
|
|
143
145
|
BALL: "ball",
|
|
144
146
|
BLOCK: "b",
|
|
147
|
+
BREATHE_FIRE: "brf",
|
|
145
148
|
BRIBE: "br",
|
|
146
149
|
CARAC_CHANGE: "cc",
|
|
147
150
|
CASUALTY: "ca",
|
|
@@ -166,17 +169,25 @@ const GAME_HISTORY_INFO = {
|
|
|
166
169
|
METEO: "met",
|
|
167
170
|
MOVE: "m",
|
|
168
171
|
PASS: "p",
|
|
172
|
+
PUNT_DIRECTION: "puntd",
|
|
173
|
+
PUNT_DISTANCE: "puntdist",
|
|
174
|
+
PUNT_TO_CROWD: "puntc",
|
|
175
|
+
PUNT_ON_PLAYER: "puntp",
|
|
176
|
+
PUNT_SCATTER: "punts",
|
|
177
|
+
PRO: "pro",
|
|
169
178
|
REJECTION: "r",
|
|
170
179
|
REROLL_COACH: "rc",
|
|
171
180
|
REROLL_INITIAL: "rei",
|
|
172
181
|
REROLL_METEO: "rm",
|
|
173
182
|
REROLL_RACE_MALUS: "rrm",
|
|
174
183
|
REROLL: "re",
|
|
184
|
+
SECURE_BALL: "sb",
|
|
175
185
|
SCATTER: "sc",
|
|
176
186
|
SKILL_GAIN: "skg",
|
|
177
187
|
SKILL_LOSS: "skl",
|
|
178
188
|
SKILL: "sk",
|
|
179
189
|
SPY: "spy",
|
|
190
|
+
STAB: "stab",
|
|
180
191
|
STANDUP: "st",
|
|
181
192
|
START_FAN: "fan",
|
|
182
193
|
STATUS: "s",
|
|
@@ -184,6 +195,8 @@ const GAME_HISTORY_INFO = {
|
|
|
184
195
|
TD: "td",
|
|
185
196
|
THREATENING: "thr",
|
|
186
197
|
THROW_TEAM_MATE: "ttm",
|
|
198
|
+
BLOODLUST_BITE: "blb",
|
|
199
|
+
BLOODLUST_NO_TARGET: "blnt",
|
|
187
200
|
TURNOVER: "to",
|
|
188
201
|
};
|
|
189
202
|
|
|
@@ -330,7 +343,8 @@ const getBlockAssistance = (
|
|
|
330
343
|
attackerPlayer: PlayerWithSkill,
|
|
331
344
|
defenderPlayer: PlayerWithSkill,
|
|
332
345
|
players: PlayerWithSkill[],
|
|
333
|
-
isAttacker = false
|
|
346
|
+
isAttacker = false,
|
|
347
|
+
isFoul = false
|
|
334
348
|
) => {
|
|
335
349
|
const OtherPlayersThanInvolved = players.filter((player) => {
|
|
336
350
|
return player.id !== attackerPlayer.id && player.id !== defenderPlayer.id;
|
|
@@ -346,15 +360,16 @@ const getBlockAssistance = (
|
|
|
346
360
|
|
|
347
361
|
// 1. Must be adjacent to the enemy player involved in the block,
|
|
348
362
|
// 3. Must be standing, and …
|
|
363
|
+
// 5. Must not be Eye Gouged (cannot provide assists)
|
|
349
364
|
const adjacentEnemyPlayers = OtherPlayersThanInvolved.filter((player) => {
|
|
350
|
-
return isValidAdjacentEnemy(player, defenderPlayer);
|
|
365
|
+
return isValidAdjacentEnemy(player, defenderPlayer) && hasStatusCanAssist(player);
|
|
351
366
|
});
|
|
352
367
|
// 2. Must not be in the tackle zone of any other player from the
|
|
353
368
|
// opposing team, and ...
|
|
354
369
|
const assistantPlayers = adjacentEnemyPlayers.filter(
|
|
355
370
|
(adjacentEnemyPlayer) => {
|
|
356
371
|
let hasGard = false;
|
|
357
|
-
if (hasSkill(adjacentEnemyPlayer, SKILL_NAMES.GUARD)) {
|
|
372
|
+
if (!isFoul && hasSkill(adjacentEnemyPlayer, SKILL_NAMES.GUARD)) {
|
|
358
373
|
hasGard = true;
|
|
359
374
|
if (
|
|
360
375
|
isAttacker && // player is the attacker player.
|
|
@@ -368,8 +383,15 @@ const getBlockAssistance = (
|
|
|
368
383
|
hasGard = false;
|
|
369
384
|
}
|
|
370
385
|
}
|
|
386
|
+
// Put the Boot In: can provide offensive assist on fouls even when marked
|
|
387
|
+
const hasPutTheBootIn =
|
|
388
|
+
isFoul &&
|
|
389
|
+
isAttacker &&
|
|
390
|
+
hasSkill(adjacentEnemyPlayer, SKILL_NAMES.PUT_THE_BOOT_IN);
|
|
391
|
+
|
|
371
392
|
return (
|
|
372
393
|
hasGard ||
|
|
394
|
+
hasPutTheBootIn ||
|
|
373
395
|
!OtherPlayersThanInvolved.some((player) => {
|
|
374
396
|
return (
|
|
375
397
|
// 4. Must have his tackle zones.
|
|
@@ -811,6 +833,7 @@ function canPass(
|
|
|
811
833
|
playerIdWithBall: string
|
|
812
834
|
) {
|
|
813
835
|
return (
|
|
836
|
+
player.status !== PLAYER_STATUS.BLOODLUST &&
|
|
814
837
|
hasStatusTacleZone(player) &&
|
|
815
838
|
player.has_action === true &&
|
|
816
839
|
playerTeam.date_next_pass === null &&
|
|
@@ -862,9 +885,20 @@ function canTransmit(
|
|
|
862
885
|
if (!player || !target || isSamePlayer(player, target)) {
|
|
863
886
|
return false;
|
|
864
887
|
}
|
|
888
|
+
// Cannot transmit from or to a player in Bloodlust state
|
|
889
|
+
if (player.status === PLAYER_STATUS.BLOODLUST) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
if (target.status === PLAYER_STATUS.BLOODLUST) {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
865
895
|
if (player.id !== playerIdWithBall && target.id !== playerIdWithBall) {
|
|
866
896
|
return false;
|
|
867
897
|
}
|
|
898
|
+
// Cannot transmit if player has already ended turn (e.g., after Bloodlust bite)
|
|
899
|
+
if (player.hasEndTurn === true) {
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
868
902
|
|
|
869
903
|
return (
|
|
870
904
|
player.teamId === target.teamId &&
|
|
@@ -1016,6 +1050,7 @@ export {
|
|
|
1016
1050
|
FORMATION_DEFAULT_NAME,
|
|
1017
1051
|
GAME_HISTORY_INFO,
|
|
1018
1052
|
GAME_HISTORY_TYPE,
|
|
1053
|
+
getAllAdjacentSquares,
|
|
1019
1054
|
getAttackerStrength,
|
|
1020
1055
|
getBestInterceptor,
|
|
1021
1056
|
getBlockAssistance,
|
|
@@ -1052,3 +1087,21 @@ export {
|
|
|
1052
1087
|
TAKE_BALL_TYPE,
|
|
1053
1088
|
version,
|
|
1054
1089
|
};
|
|
1090
|
+
|
|
1091
|
+
export {
|
|
1092
|
+
SidestepMode,
|
|
1093
|
+
SidestepStrategy,
|
|
1094
|
+
SidestepPreference,
|
|
1095
|
+
} from "./skills/sidestep";
|
|
1096
|
+
|
|
1097
|
+
export {
|
|
1098
|
+
HitAndRunMode,
|
|
1099
|
+
HitAndRunStrategy,
|
|
1100
|
+
HitAndRunPreference,
|
|
1101
|
+
} from "./skills/hitAndRun";
|
|
1102
|
+
|
|
1103
|
+
export {
|
|
1104
|
+
SafePairOfHandsMode,
|
|
1105
|
+
SafePairOfHandsStrategy,
|
|
1106
|
+
SafePairOfHandsPreference,
|
|
1107
|
+
} from "./skills/safePairOfHands";
|
package/jest.config.js
CHANGED
package/move.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnemyTackleZones } from "./position";
|
|
2
2
|
import { hasSkill, SKILL_NAMES } from "./skill";
|
|
3
|
-
import {
|
|
3
|
+
import { hasStatusStanding } from "./status";
|
|
4
|
+
import { Game, PlayerWithSkill, Point, Team } from "./types/models";
|
|
4
5
|
|
|
5
6
|
function bonusDodge(
|
|
6
7
|
player: PlayerWithSkill,
|
|
@@ -94,4 +95,51 @@ function modifJumping(
|
|
|
94
95
|
return { modif, enemies };
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
function hasOpponentStandingAroundBall(
|
|
99
|
+
players: Record<string, PlayerWithSkill> | PlayerWithSkill[],
|
|
100
|
+
ball: Point,
|
|
101
|
+
teamId: string,
|
|
102
|
+
distance = 2
|
|
103
|
+
) {
|
|
104
|
+
const list = Array.isArray(players) ? players : Object.values(players);
|
|
105
|
+
return list.some(
|
|
106
|
+
(p) =>
|
|
107
|
+
p.teamId !== teamId &&
|
|
108
|
+
hasStatusStanding(p) &&
|
|
109
|
+
p.x !== null &&
|
|
110
|
+
p.y !== null &&
|
|
111
|
+
ball.x !== null &&
|
|
112
|
+
ball.y !== null &&
|
|
113
|
+
Math.max(Math.abs(p.x - ball.x), Math.abs(p.y - ball.y)) <= distance
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getGameBall(game: Game | null | undefined): Point | null {
|
|
118
|
+
if (!game) return null;
|
|
119
|
+
if (game.ball_player_id !== null) return null;
|
|
120
|
+
if (game.ball_x == null || game.ball_y == null) return null;
|
|
121
|
+
return { x: game.ball_x, y: game.ball_y };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function listGamePlayers(game: Game | null | undefined): PlayerWithSkill[] {
|
|
125
|
+
if (!game) return [];
|
|
126
|
+
const team1Players = (game.team1 as Team | undefined)?.players || [];
|
|
127
|
+
const team2Players = (game.team2 as Team | undefined)?.players || [];
|
|
128
|
+
return [...team1Players, ...team2Players];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function canUseSecureBall(player: PlayerWithSkill): boolean {
|
|
132
|
+
return (
|
|
133
|
+
!hasSkill(player, SKILL_NAMES.BIG_GUY) &&
|
|
134
|
+
!hasSkill(player, SKILL_NAMES.UNSTEADY)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
bonusDodge,
|
|
140
|
+
canUseSecureBall,
|
|
141
|
+
getGameBall,
|
|
142
|
+
hasOpponentStandingAroundBall,
|
|
143
|
+
listGamePlayers,
|
|
144
|
+
modifJumping,
|
|
145
|
+
};
|
package/npm-login.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enefel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Blood Bowl 3 game engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,12 +20,16 @@
|
|
|
20
20
|
"watch": "tsc -w",
|
|
21
21
|
"prepare": "yarn build",
|
|
22
22
|
"test": "jest",
|
|
23
|
-
"test:watch": "jest --watch"
|
|
23
|
+
"test:watch": "jest --watch",
|
|
24
|
+
"convert:career": "npx ts-node teams/convert-csv-to-career.ts"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [],
|
|
26
27
|
"author": "Manest",
|
|
27
28
|
"license": "MIT",
|
|
28
29
|
"dependencies": {
|
|
30
|
+
"csv-parse": "^6.1.0",
|
|
31
|
+
"csv-stringify": "^6.6.0",
|
|
32
|
+
"jsdom": "^27.2.0",
|
|
29
33
|
"seedrandom": "3.0.5",
|
|
30
34
|
"yup": "^0.29.3"
|
|
31
35
|
},
|
package/pitch.ts
CHANGED
|
@@ -115,6 +115,27 @@ function getNumberPlayerZone(
|
|
|
115
115
|
return { playerScrimmage, playerLateralA, playerLateralB, playerCenter };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a tile at position (x, y) is the enemy team's touchdown zone
|
|
120
|
+
* @param x - X coordinate
|
|
121
|
+
* @param y - Y coordinate
|
|
122
|
+
* @param teamId - The player's team ID
|
|
123
|
+
* @param game - Game object containing stadium_zone, stadium_width, team1, team2
|
|
124
|
+
* @returns true if the tile is the enemy TD zone
|
|
125
|
+
*/
|
|
126
|
+
function isEnemyTDZone(
|
|
127
|
+
x: number,
|
|
128
|
+
y: number,
|
|
129
|
+
teamId: string,
|
|
130
|
+
game: Game
|
|
131
|
+
): boolean {
|
|
132
|
+
const tile = getTile(game, x, y);
|
|
133
|
+
// Determine if player is team1 or team2, then get enemy TD zone
|
|
134
|
+
const enemyTDTeamNumber = teamId === game.team1?.id ? 2 : 1;
|
|
135
|
+
const enemyTD = getTdTile(enemyTDTeamNumber);
|
|
136
|
+
return tile === enemyTD;
|
|
137
|
+
}
|
|
138
|
+
|
|
118
139
|
export {
|
|
119
140
|
coordinatesConvertorX,
|
|
120
141
|
coordinatesConvertorY,
|
|
@@ -127,5 +148,6 @@ export {
|
|
|
127
148
|
getScrimmageTile,
|
|
128
149
|
getTdTile,
|
|
129
150
|
getTile,
|
|
151
|
+
isEnemyTDZone,
|
|
130
152
|
TILES,
|
|
131
153
|
};
|
package/player.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CAREER_VERSION } from "./career";
|
|
1
2
|
import { getAgingMinExperience } from "./improvement";
|
|
2
3
|
import { meteoMaxMA } from "./meteo";
|
|
3
4
|
import { getCareerFromPlayer } from "./race";
|
|
@@ -20,6 +21,24 @@ enum PLAYER_CARAC {
|
|
|
20
21
|
PA = "PA",
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
// Valeurs maximales absolues des caractéristiques (Blood Bowl 2020 rulebook)
|
|
25
|
+
const CARAC_MAX: Record<PLAYER_CARAC, number> = {
|
|
26
|
+
[PLAYER_CARAC.MA]: 9,
|
|
27
|
+
[PLAYER_CARAC.ST]: 8,
|
|
28
|
+
[PLAYER_CARAC.AG]: 5, // interne 5 = affichage 1+
|
|
29
|
+
[PLAYER_CARAC.PA]: 5, // interne 5 = affichage 1+
|
|
30
|
+
[PLAYER_CARAC.AV]: 10, // interne 10 = affichage 11+
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Valeurs minimales absolues des caractéristiques
|
|
34
|
+
const CARAC_MIN: Record<PLAYER_CARAC, number> = {
|
|
35
|
+
[PLAYER_CARAC.MA]: 1,
|
|
36
|
+
[PLAYER_CARAC.ST]: 1,
|
|
37
|
+
[PLAYER_CARAC.AG]: 1, // interne 1 = affichage 6+
|
|
38
|
+
[PLAYER_CARAC.PA]: 0, // 0 = pas de PA
|
|
39
|
+
[PLAYER_CARAC.AV]: 2, // interne 2 = affichage 3+
|
|
40
|
+
};
|
|
41
|
+
|
|
23
42
|
function hasMove(player: PlayerWithSkill, game: Game) {
|
|
24
43
|
if (
|
|
25
44
|
player.current_MA >= getMaxCarac(player, PLAYER_CARAC.MA, game) &&
|
|
@@ -95,24 +114,29 @@ function getCareerBaseCaracLimit(
|
|
|
95
114
|
return value;
|
|
96
115
|
}
|
|
97
116
|
|
|
98
|
-
function getMaxCarac(player: Player, carac: PLAYER_CARAC, game
|
|
117
|
+
function getMaxCarac(player: Player, carac: PLAYER_CARAC, game?: Game) {
|
|
99
118
|
if (!player) {
|
|
100
119
|
return -1;
|
|
101
120
|
}
|
|
102
121
|
if (carac === PLAYER_CARAC.AG) {
|
|
103
|
-
return getMaxAG(player, game);
|
|
122
|
+
return getMaxAG(player, game || ({} as Game));
|
|
104
123
|
} else if (carac === PLAYER_CARAC.MA) {
|
|
105
|
-
return getMaxMA(player, game);
|
|
124
|
+
return getMaxMA(player, game || ({} as Game));
|
|
106
125
|
} else if (carac === PLAYER_CARAC.AV) {
|
|
107
|
-
return getMaxAV(player, game);
|
|
126
|
+
return getMaxAV(player, game || ({} as Game));
|
|
108
127
|
} else if (carac === PLAYER_CARAC.ST) {
|
|
109
|
-
return getMaxST(player, game);
|
|
128
|
+
return getMaxST(player, game || ({} as Game));
|
|
129
|
+
} else if (carac === PLAYER_CARAC.PA) {
|
|
130
|
+
return getMaxPA(player, game);
|
|
110
131
|
}
|
|
111
132
|
return -1;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
function getMaxCommun(player: Player, carac: PLAYER_CARAC, _game: Game) {
|
|
115
|
-
let c = player[carac];
|
|
136
|
+
let c: number | null | undefined = player[carac];
|
|
137
|
+
if (c == null) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
116
140
|
if (!player.tempCarac) {
|
|
117
141
|
return c;
|
|
118
142
|
}
|
|
@@ -137,29 +161,25 @@ function getMaxCommun(player: Player, carac: PLAYER_CARAC, _game: Game) {
|
|
|
137
161
|
return c;
|
|
138
162
|
}
|
|
139
163
|
|
|
140
|
-
function getLimitedValue(c: number) {
|
|
141
|
-
if (c > 10) {
|
|
142
|
-
c = 10;
|
|
143
|
-
}
|
|
144
|
-
if (c < 0) {
|
|
145
|
-
c = 0;
|
|
146
|
-
}
|
|
147
|
-
return c;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
164
|
function getMaxAV(player: Player, game: Game) {
|
|
151
165
|
let c = getMaxCommun(player, PLAYER_CARAC.AV, game);
|
|
152
|
-
|
|
166
|
+
if (c > CARAC_MAX[PLAYER_CARAC.AV]) c = CARAC_MAX[PLAYER_CARAC.AV];
|
|
167
|
+
if (c < CARAC_MIN[PLAYER_CARAC.AV]) c = CARAC_MIN[PLAYER_CARAC.AV];
|
|
168
|
+
return c;
|
|
153
169
|
}
|
|
154
170
|
|
|
155
171
|
function getMaxST(player: Player, game: Game) {
|
|
156
172
|
let c = getMaxCommun(player, PLAYER_CARAC.ST, game);
|
|
157
|
-
|
|
173
|
+
if (c > CARAC_MAX[PLAYER_CARAC.ST]) c = CARAC_MAX[PLAYER_CARAC.ST];
|
|
174
|
+
if (c < CARAC_MIN[PLAYER_CARAC.ST]) c = CARAC_MIN[PLAYER_CARAC.ST];
|
|
175
|
+
return c;
|
|
158
176
|
}
|
|
159
177
|
|
|
160
178
|
function getMaxAG(player: Player, game: Game) {
|
|
161
179
|
let c = getMaxCommun(player, PLAYER_CARAC.AG, game);
|
|
162
|
-
|
|
180
|
+
if (c > CARAC_MAX[PLAYER_CARAC.AG]) c = CARAC_MAX[PLAYER_CARAC.AG];
|
|
181
|
+
if (c < CARAC_MIN[PLAYER_CARAC.AG]) c = CARAC_MIN[PLAYER_CARAC.AG];
|
|
182
|
+
return c;
|
|
163
183
|
}
|
|
164
184
|
|
|
165
185
|
function getMaxMA(player: Player, game: Game) {
|
|
@@ -170,8 +190,57 @@ function getMaxMA(player: Player, game: Game) {
|
|
|
170
190
|
c = maxMA;
|
|
171
191
|
}
|
|
172
192
|
}
|
|
193
|
+
if (c > CARAC_MAX[PLAYER_CARAC.MA]) c = CARAC_MAX[PLAYER_CARAC.MA];
|
|
194
|
+
if (c < CARAC_MIN[PLAYER_CARAC.MA]) c = CARAC_MIN[PLAYER_CARAC.MA];
|
|
195
|
+
return c;
|
|
196
|
+
}
|
|
173
197
|
|
|
174
|
-
|
|
198
|
+
function clampPA(c: number): number {
|
|
199
|
+
if (c <= 0) return CARAC_MIN[PLAYER_CARAC.PA]; // 0 = pas de PA
|
|
200
|
+
if (c > CARAC_MAX[PLAYER_CARAC.PA]) return CARAC_MAX[PLAYER_CARAC.PA];
|
|
201
|
+
return c;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getMaxPA(player: Player, game?: Game) {
|
|
205
|
+
// Si le joueur a déjà une valeur PA en base, l'utiliser
|
|
206
|
+
if (player.PA != null) {
|
|
207
|
+
let c = getMaxCommun(player, PLAYER_CARAC.PA, game || ({} as Game));
|
|
208
|
+
return clampPA(c);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Sinon, déterminer la valeur selon la version de la career
|
|
212
|
+
const career = getCareerFromPlayer(player);
|
|
213
|
+
if (!career) {
|
|
214
|
+
// Fallback: utiliser AG si pas de career
|
|
215
|
+
return getMaxAG(player, game || ({} as Game));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Si c'est une career 2025, utiliser career.PA
|
|
219
|
+
if (career.version === CAREER_VERSION.V2025 && career.PA != null) {
|
|
220
|
+
let c = career.PA;
|
|
221
|
+
// Appliquer les modificateurs tempCarac si présents
|
|
222
|
+
if (player.tempCarac) {
|
|
223
|
+
let obj;
|
|
224
|
+
try {
|
|
225
|
+
obj = JSON.parse(player.tempCarac);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
return clampPA(c);
|
|
228
|
+
}
|
|
229
|
+
for (const value of Object.values(obj)) {
|
|
230
|
+
if ((value as any).gainCarac === PLAYER_CARAC.PA) {
|
|
231
|
+
c += 1;
|
|
232
|
+
}
|
|
233
|
+
if ((value as any).loseCarac === PLAYER_CARAC.PA) {
|
|
234
|
+
c -= 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
c = getCareerBaseCaracLimit(player, c, PLAYER_CARAC.PA);
|
|
238
|
+
}
|
|
239
|
+
return clampPA(c);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Sinon (career 2020 ou V6), utiliser AG
|
|
243
|
+
return getMaxAG(player, game || ({} as Game));
|
|
175
244
|
}
|
|
176
245
|
|
|
177
246
|
function getMaxGfi(player: PlayerWithSkill) {
|
|
@@ -219,15 +288,26 @@ function getAgingBonus(player: Player, totalGames: number) {
|
|
|
219
288
|
return bonus;
|
|
220
289
|
}
|
|
221
290
|
|
|
222
|
-
function BB2020Carac(
|
|
291
|
+
function BB2020Carac(
|
|
292
|
+
value: number,
|
|
293
|
+
carac: PLAYER_CARAC,
|
|
294
|
+
careerVersion?: CAREER_VERSION
|
|
295
|
+
) {
|
|
223
296
|
// CHARACTERISTIC TABLE
|
|
224
297
|
// Characteristic MA ST AG PA AV
|
|
225
298
|
// Maximum 9 8 1+ 1+ 11+
|
|
226
299
|
//Minimum 1 1 6+ 6+ 3+
|
|
300
|
+
// Les valeurs sont maintenant normalisées au format 2020 pour toutes les versions
|
|
301
|
+
// (les valeurs 2025 sont converties lors de la génération du fichier career2025.ts)
|
|
227
302
|
if (carac === PLAYER_CARAC.AV && value) {
|
|
228
303
|
// TODO: min3+, max11+
|
|
229
304
|
return `${+value + 1}+`;
|
|
230
|
-
} else if (
|
|
305
|
+
} else if (
|
|
306
|
+
(carac === PLAYER_CARAC.AG || carac === PLAYER_CARAC.PA) &&
|
|
307
|
+
value
|
|
308
|
+
) {
|
|
309
|
+
// Conversion pour afficher la notation "+"
|
|
310
|
+
// PA se comporte exactement comme AG (PA n'existait pas en 2020)
|
|
231
311
|
let newValue = 6;
|
|
232
312
|
if (value <= 1) newValue = 6;
|
|
233
313
|
if (value === 2) newValue = 5;
|
|
@@ -250,6 +330,8 @@ export {
|
|
|
250
330
|
areFromSameTeam,
|
|
251
331
|
arePlayingSameGame,
|
|
252
332
|
BB2020Carac,
|
|
333
|
+
CARAC_MAX,
|
|
334
|
+
CARAC_MIN,
|
|
253
335
|
gamesforAgingRoll,
|
|
254
336
|
getAgingBonus,
|
|
255
337
|
getCareerBaseCarac,
|
|
@@ -260,6 +342,7 @@ export {
|
|
|
260
342
|
getMaxCommun,
|
|
261
343
|
getMaxGfi,
|
|
262
344
|
getMaxMA,
|
|
345
|
+
getMaxPA,
|
|
263
346
|
getMaxST,
|
|
264
347
|
hasMove,
|
|
265
348
|
hasTeam,
|
package/position.ts
CHANGED
|
@@ -19,6 +19,22 @@ export const isAdjacent = (p1: Point, p2: Point): boolean => {
|
|
|
19
19
|
);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Returns all 8 adjacent squares around a given position
|
|
24
|
+
*/
|
|
25
|
+
export const getAllAdjacentSquares = (x: number, y: number): Point[] => {
|
|
26
|
+
return [
|
|
27
|
+
{ x: x - 1, y: y - 1 },
|
|
28
|
+
{ x: x, y: y - 1 },
|
|
29
|
+
{ x: x + 1, y: y - 1 },
|
|
30
|
+
{ x: x - 1, y: y },
|
|
31
|
+
{ x: x + 1, y: y },
|
|
32
|
+
{ x: x - 1, y: y + 1 },
|
|
33
|
+
{ x: x, y: y + 1 },
|
|
34
|
+
{ x: x + 1, y: y + 1 },
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
|
|
22
38
|
export const getEnemyTackleZones = (
|
|
23
39
|
location: Point,
|
|
24
40
|
teamId: string,
|