fightbook 1.0.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.
@@ -0,0 +1,605 @@
1
+ import {
2
+ STRIKING_TECHNIQUES,
3
+ GRAPPLING_TECHNIQUES,
4
+ SUBMISSION_TECHNIQUES,
5
+ GROUND_TECHNIQUES,
6
+ ROUND_DURATION,
7
+ TOTAL_ROUNDS,
8
+ DEFAULT_FIGHTER
9
+ } from "@/types/fight";
10
+ class FightEngine {
11
+ fight;
12
+ intervalId = null;
13
+ onAction = null;
14
+ onRoundEnd = null;
15
+ onFightEnd = null;
16
+ tickRate = 1e3;
17
+ // 1 second per tick for real-time feel
18
+ // LLM and CPU callbacks
19
+ llmCallback;
20
+ cpuCallback;
21
+ mixedMode = false;
22
+ llmFighterId = "fighter1";
23
+ // Which fighter uses LLM
24
+ constructor(fighter1Stats, fighter2Stats, callbacks) {
25
+ this.fight = this.initializeFight(fighter1Stats, fighter2Stats);
26
+ this.onAction = callbacks?.onAction || null;
27
+ this.onRoundEnd = callbacks?.onRoundEnd || null;
28
+ this.onFightEnd = callbacks?.onFightEnd || null;
29
+ this.llmCallback = callbacks?.llmCallback;
30
+ this.cpuCallback = callbacks?.cpuCallback;
31
+ this.mixedMode = callbacks?.mixedMode || false;
32
+ this.llmFighterId = callbacks?.llmFighterId || "fighter1";
33
+ }
34
+ initializeFight(f1, f2) {
35
+ const fighter1 = {
36
+ ...f1,
37
+ id: "fighter1",
38
+ currentHealth: 100,
39
+ currentStamina: 100,
40
+ position: "standing",
41
+ hasMount: false,
42
+ hasBack: false,
43
+ isGrounded: false,
44
+ cuts: 0,
45
+ knockdowns: 0,
46
+ takedownsLanded: 0,
47
+ takedownsAttempted: 0,
48
+ significantStrikes: 0,
49
+ totalStrikes: 0
50
+ };
51
+ const fighter2 = {
52
+ ...f2,
53
+ id: "fighter2",
54
+ currentHealth: 100,
55
+ currentStamina: 100,
56
+ position: "standing",
57
+ hasMount: false,
58
+ hasBack: false,
59
+ isGrounded: false,
60
+ cuts: 0,
61
+ knockdowns: 0,
62
+ takedownsLanded: 0,
63
+ takedownsAttempted: 0,
64
+ significantStrikes: 0,
65
+ totalStrikes: 0
66
+ };
67
+ return {
68
+ id: Math.random().toString(36).substring(7),
69
+ fighter1,
70
+ fighter2,
71
+ currentRound: 1,
72
+ rounds: [this.createRound(1)],
73
+ isComplete: false
74
+ };
75
+ }
76
+ createRound(roundNum) {
77
+ return {
78
+ round: roundNum,
79
+ timeRemaining: ROUND_DURATION,
80
+ isActive: false,
81
+ actions: []
82
+ };
83
+ }
84
+ // Start the fight
85
+ start() {
86
+ if (this.intervalId) return;
87
+ this.fight.rounds[this.fight.currentRound - 1].isActive = true;
88
+ this.intervalId = setInterval(() => this.tick(), this.tickRate);
89
+ }
90
+ // Pause the fight
91
+ pause() {
92
+ if (this.intervalId) {
93
+ clearInterval(this.intervalId);
94
+ this.intervalId = null;
95
+ }
96
+ }
97
+ // Resume the fight
98
+ resume() {
99
+ if (!this.intervalId && !this.fight.isComplete) {
100
+ this.intervalId = setInterval(() => this.tick(), this.tickRate);
101
+ }
102
+ }
103
+ // Stop the fight completely
104
+ stop() {
105
+ this.pause();
106
+ this.fight.isComplete = true;
107
+ }
108
+ // Get current fight state
109
+ getState() {
110
+ return { ...this.fight };
111
+ }
112
+ // Get simplified game state for LLM/CPU context
113
+ getGameState(actorId) {
114
+ const actor = actorId === "fighter1" ? this.fight.fighter1 : this.fight.fighter2;
115
+ const target = actorId === "fighter1" ? this.fight.fighter2 : this.fight.fighter1;
116
+ const currentRound = this.fight.rounds[this.fight.currentRound - 1];
117
+ const recentActions = currentRound?.actions.slice(-3).map((a) => a.description) || [];
118
+ return {
119
+ round: this.fight.currentRound,
120
+ timeRemaining: currentRound?.timeRemaining || 0,
121
+ myHealth: actor.currentHealth,
122
+ myStamina: actor.currentStamina,
123
+ myPosition: actor.position,
124
+ oppHealth: target.currentHealth,
125
+ oppStamina: target.currentStamina,
126
+ oppPosition: target.position,
127
+ recentActions
128
+ };
129
+ }
130
+ // Main game loop tick - runs every second
131
+ tick() {
132
+ const currentRound = this.fight.rounds[this.fight.currentRound - 1];
133
+ if (currentRound.timeRemaining <= 0) {
134
+ this.endRound();
135
+ return;
136
+ }
137
+ currentRound.timeRemaining--;
138
+ this.recoverStamina();
139
+ const actionChance = 0.7 + (this.fight.fighter1.aggression + this.fight.fighter2.aggression) / 2;
140
+ if (Math.random() < actionChance) {
141
+ const f1Aggression = this.fight.fighter1.aggression;
142
+ const f2Aggression = this.fight.fighter2.aggression;
143
+ const total = f1Aggression + f2Aggression || 1;
144
+ const actingFighter = Math.random() < f1Aggression / total ? "fighter1" : "fighter2";
145
+ const action = this.generateAction(actingFighter);
146
+ if (action) {
147
+ currentRound.actions.push(action);
148
+ this.applyAction(action);
149
+ this.onAction?.(action);
150
+ if (this.checkFightEnd()) {
151
+ this.endFight();
152
+ }
153
+ }
154
+ }
155
+ if (this.checkExhaustion()) {
156
+ this.endFight();
157
+ }
158
+ }
159
+ recoverStamina() {
160
+ const recovery1 = this.fight.fighter1.recovery / 100 * 0.3;
161
+ const recovery2 = this.fight.fighter2.recovery / 100 * 0.3;
162
+ this.fight.fighter1.currentStamina = Math.min(100, this.fight.fighter1.currentStamina + recovery1);
163
+ this.fight.fighter2.currentStamina = Math.min(100, this.fight.fighter2.currentStamina + recovery2);
164
+ }
165
+ generateAction(actingFighterId) {
166
+ const actor = actingFighterId === "fighter1" ? this.fight.fighter1 : this.fight.fighter2;
167
+ const target = actingFighterId === "fighter1" ? this.fight.fighter2 : this.fight.fighter1;
168
+ let technique = null;
169
+ if (this.mixedMode) {
170
+ const gameState = this.getGameState(actingFighterId);
171
+ if (actingFighterId === this.llmFighterId && this.llmCallback) {
172
+ return null;
173
+ }
174
+ if (actingFighterId !== this.llmFighterId && this.cpuCallback) {
175
+ const techniqueName = this.cpuCallback(actor.name, target.name, gameState);
176
+ technique = this.getTechniqueByName(techniqueName);
177
+ }
178
+ }
179
+ if (!technique) {
180
+ technique = this.selectTechnique(actor, target);
181
+ }
182
+ if (!technique) return null;
183
+ const success = this.calculateSuccess(actor, target, technique);
184
+ const { damage, staminaCost, description, impact } = this.calculateOutcome(
185
+ actor,
186
+ target,
187
+ technique,
188
+ success
189
+ );
190
+ return {
191
+ timestamp: Date.now(),
192
+ round: this.fight.currentRound,
193
+ timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,
194
+ actor: actor.name,
195
+ target: target.name,
196
+ type: technique.type,
197
+ technique,
198
+ success,
199
+ damage,
200
+ staminaCost,
201
+ description,
202
+ impact
203
+ };
204
+ }
205
+ // Get technique by name (for LLM/CPU callbacks)
206
+ getTechniqueByName(name) {
207
+ const allTechs = [
208
+ ...STRIKING_TECHNIQUES,
209
+ ...GRAPPLING_TECHNIQUES,
210
+ ...SUBMISSION_TECHNIQUES,
211
+ ...GROUND_TECHNIQUES
212
+ ];
213
+ let tech = allTechs.find((t) => t.name.toLowerCase() === name.toLowerCase());
214
+ if (tech) return tech;
215
+ tech = allTechs.find((t) => t.name.toLowerCase().includes(name.toLowerCase()));
216
+ return tech || null;
217
+ }
218
+ // Generate async action for LLM fighters
219
+ async generateLlmAction(actingFighterId) {
220
+ if (!this.llmCallback) return null;
221
+ const actor = actingFighterId === "fighter1" ? this.fight.fighter1 : this.fight.fighter2;
222
+ const target = actingFighterId === "fighter1" ? this.fight.fighter2 : this.fight.fighter1;
223
+ const gameState = this.getGameState(actingFighterId);
224
+ try {
225
+ const techniqueName = await this.llmCallback(actor.name, target.name, gameState);
226
+ const technique = this.getTechniqueByName(techniqueName);
227
+ if (!technique) {
228
+ console.warn("Invalid technique from LLM:", techniqueName);
229
+ return null;
230
+ }
231
+ const success = this.calculateSuccess(actor, target, technique);
232
+ const { damage, staminaCost, description, impact } = this.calculateOutcome(
233
+ actor,
234
+ target,
235
+ technique,
236
+ success
237
+ );
238
+ return {
239
+ timestamp: Date.now(),
240
+ round: this.fight.currentRound,
241
+ timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,
242
+ actor: actor.name,
243
+ target: target.name,
244
+ type: technique.type,
245
+ technique,
246
+ success,
247
+ damage,
248
+ staminaCost,
249
+ description,
250
+ impact
251
+ };
252
+ } catch (error) {
253
+ console.error("LLM callback error:", error);
254
+ const technique = this.selectTechnique(actor, target);
255
+ if (!technique) return null;
256
+ const success = this.calculateSuccess(actor, target, technique);
257
+ const { damage, staminaCost, description, impact } = this.calculateOutcome(
258
+ actor,
259
+ target,
260
+ technique,
261
+ success
262
+ );
263
+ return {
264
+ timestamp: Date.now(),
265
+ round: this.fight.currentRound,
266
+ timeRemaining: this.fight.rounds[this.fight.currentRound - 1].timeRemaining,
267
+ actor: actor.name,
268
+ target: target.name,
269
+ type: technique.type,
270
+ technique,
271
+ success,
272
+ damage,
273
+ staminaCost,
274
+ description,
275
+ impact
276
+ };
277
+ }
278
+ }
279
+ selectTechnique(actor, target) {
280
+ let availableTechs = [];
281
+ switch (actor.position) {
282
+ case "standing":
283
+ availableTechs = [
284
+ ...STRIKING_TECHNIQUES.filter((t) => t.position.includes("standing")),
285
+ ...GRAPPLING_TECHNIQUES.filter((t) => t.position.includes("standing"))
286
+ ];
287
+ break;
288
+ case "clinch":
289
+ availableTechs = [
290
+ ...STRIKING_TECHNIQUES.filter((t) => t.position.includes("clinch")),
291
+ ...GRAPPLING_TECHNIQUES.filter((t) => t.position.includes("clinch")),
292
+ { name: "Break Clinch", type: "break_clinch", baseDamage: 0, staminaCost: 5, accuracy: 0.8, position: ["clinch"] }
293
+ ];
294
+ break;
295
+ case "ground_top":
296
+ availableTechs = [
297
+ ...GROUND_TECHNIQUES.filter((t) => t.position.includes("ground_top")),
298
+ ...SUBMISSION_TECHNIQUES.filter((t) => t.position.includes("ground_top"))
299
+ ];
300
+ break;
301
+ case "ground_bottom":
302
+ availableTechs = [
303
+ ...GROUND_TECHNIQUES.filter((t) => t.position.includes("ground_bottom")),
304
+ ...SUBMISSION_TECHNIQUES.filter((t) => t.position.includes("ground_bottom"))
305
+ ];
306
+ break;
307
+ }
308
+ if (availableTechs.length === 0) return null;
309
+ const weightedTechs = availableTechs.map((tech) => {
310
+ let weight = 1;
311
+ if (tech.type === "strike") weight *= actor.striking / 50;
312
+ if (tech.type === "takedown") weight *= actor.wrestling / 50;
313
+ if (tech.type === "submission_attempt") weight *= actor.submissions / 50;
314
+ if (tech.type === "ground_pound") weight *= actor.groundGame / 50;
315
+ if (actor.fightIQ > 70) weight *= tech.accuracy * 2;
316
+ if (target.currentHealth < 30 && tech.type === "strike") weight *= 1.5;
317
+ if (actor.currentStamina < 30 && tech.staminaCost > 8) weight *= 0.3;
318
+ if (target.position === "ground_bottom" && tech.type === "ground_pound") weight *= 1.3;
319
+ return { tech, weight };
320
+ });
321
+ const totalWeight = weightedTechs.reduce((sum, wt) => sum + wt.weight, 0);
322
+ let random = Math.random() * totalWeight;
323
+ for (const wt of weightedTechs) {
324
+ random -= wt.weight;
325
+ if (random <= 0) return wt.tech;
326
+ }
327
+ return weightedTechs[0]?.tech || null;
328
+ }
329
+ calculateSuccess(actor, target, tech) {
330
+ let accuracy = tech.accuracy;
331
+ if (tech.type === "strike") {
332
+ accuracy *= actor.striking / 50;
333
+ accuracy *= 1 - target.headMovement / 200;
334
+ }
335
+ if (tech.type === "takedown") {
336
+ accuracy *= actor.wrestling / 50;
337
+ accuracy *= 1 - target.takedownDefense / 150;
338
+ }
339
+ if (tech.type === "submission_attempt") {
340
+ accuracy *= actor.submissions / 50;
341
+ accuracy *= 1 - target.submissionDefense / 150;
342
+ }
343
+ accuracy *= 0.5 + actor.currentStamina / 200;
344
+ if (tech.type === "strike") {
345
+ accuracy *= 1 + (100 - target.currentHealth) / 300;
346
+ }
347
+ return Math.random() < Math.min(0.95, accuracy);
348
+ }
349
+ calculateOutcome(actor, target, tech, success) {
350
+ if (!success) {
351
+ const misses = [
352
+ `${actor.name} throws a ${tech.name} but misses!`,
353
+ `${actor.name} attempts a ${tech.name} but ${target.name} defends well!`,
354
+ `${actor.name} goes for a ${tech.name} - no connection!`,
355
+ `${target.name} slips the ${tech.name} from ${actor.name}!`
356
+ ];
357
+ return {
358
+ damage: 0,
359
+ staminaCost: tech.staminaCost * 0.5,
360
+ description: misses[Math.floor(Math.random() * misses.length)]
361
+ };
362
+ }
363
+ let damage = tech.baseDamage;
364
+ let impact = "light";
365
+ if (tech.type === "strike") {
366
+ damage *= actor.striking / 50;
367
+ damage *= 0.8 + actor.punchSpeed / 250;
368
+ if (Math.random() < 0.1) {
369
+ damage *= 1.5;
370
+ impact = "devastating";
371
+ }
372
+ }
373
+ if (tech.type === "ground_pound") {
374
+ damage *= actor.groundGame / 50;
375
+ damage *= 0.9 + actor.striking / 500;
376
+ }
377
+ if (tech.type === "strike" || tech.type === "ground_pound") {
378
+ const chinReduction = target.chin / 100;
379
+ damage *= 1.1 - chinReduction;
380
+ if (damage > 15) impact = "devastating";
381
+ else if (damage > 10) impact = "heavy";
382
+ else if (damage > 5) impact = "moderate";
383
+ }
384
+ damage *= 0.85 + Math.random() * 0.3;
385
+ const desc = this.generateDescription(actor, target, tech, damage, impact);
386
+ return {
387
+ damage: Math.round(damage),
388
+ staminaCost: tech.staminaCost,
389
+ description: desc,
390
+ impact
391
+ };
392
+ }
393
+ generateDescription(actor, target, tech, damage, impact) {
394
+ const adjectives = impact === "devastating" ? ["CRUSHING", "BRUTAL", "DEVASTATING", "MASSIVE"] : impact === "heavy" ? ["SOLID", "HARD", "CLEAN", "STIFF"] : impact === "moderate" ? ["nice", "clean", "good", "sharp"] : ["light", "partial", "glancing"];
395
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
396
+ if (tech.type === "submission_attempt") {
397
+ return `${actor.name} locks in a ${tech.name}! ${target.name} is in trouble!`;
398
+ }
399
+ if (tech.type === "takedown") {
400
+ return `${actor.name} scores with a ${tech.name}! Fight goes to the ground!`;
401
+ }
402
+ if (damage > 12) {
403
+ return `${adj} ${tech.name} by ${actor.name}! ${target.name} is hurt!`;
404
+ } else if (damage > 6) {
405
+ return `${actor.name} lands a ${adj} ${tech.name}!`;
406
+ } else {
407
+ return `${actor.name} connects with a ${tech.name}.`;
408
+ }
409
+ }
410
+ applyAction(action) {
411
+ const actor = action.actor === this.fight.fighter1.name ? this.fight.fighter1 : this.fight.fighter2;
412
+ const target = action.target === this.fight.fighter1.name ? this.fight.fighter1 : this.fight.fighter2;
413
+ actor.currentStamina = Math.max(0, actor.currentStamina - action.staminaCost);
414
+ if (action.damage > 0) {
415
+ target.currentHealth = Math.max(0, target.currentHealth - action.damage);
416
+ if (action.damage >= 5) {
417
+ if (actor === this.fight.fighter1) this.fight.fighter1.significantStrikes++;
418
+ else this.fight.fighter2.significantStrikes++;
419
+ }
420
+ if (actor === this.fight.fighter1) this.fight.fighter1.totalStrikes++;
421
+ else this.fight.fighter2.totalStrikes++;
422
+ }
423
+ if (action.type === "takedown" && action.success) {
424
+ actor.position = "ground_top";
425
+ target.position = "ground_bottom";
426
+ actor.takedownsLanded++;
427
+ actor.isGrounded = true;
428
+ target.isGrounded = true;
429
+ }
430
+ if (action.type === "break_clinch" && action.success) {
431
+ actor.position = "standing";
432
+ target.position = "standing";
433
+ }
434
+ if ((action.type === "strike" || action.type === "ground_pound") && action.impact === "devastating" && action.damage > 10) {
435
+ if (Math.random() < 0.3) {
436
+ target.knockdowns++;
437
+ if (target.position === "standing") {
438
+ target.position = "ground_bottom";
439
+ actor.position = "ground_top";
440
+ }
441
+ }
442
+ }
443
+ }
444
+ checkFightEnd() {
445
+ const f1 = this.fight.fighter1;
446
+ const f2 = this.fight.fighter2;
447
+ if (f1.currentHealth <= 0) {
448
+ this.fight.winner = f2.name;
449
+ this.fight.method = f2.position === "ground_top" || f2.position === "ground_bottom" ? "TKO" : "KO";
450
+ return true;
451
+ }
452
+ if (f2.currentHealth <= 0) {
453
+ this.fight.winner = f1.name;
454
+ this.fight.method = f1.position === "ground_top" || f1.position === "ground_bottom" ? "TKO" : "KO";
455
+ return true;
456
+ }
457
+ if (f1.cuts >= 3) {
458
+ this.fight.winner = f2.name;
459
+ this.fight.method = "TKO";
460
+ return true;
461
+ }
462
+ if (f2.cuts >= 3) {
463
+ this.fight.winner = f1.name;
464
+ this.fight.method = "TKO";
465
+ return true;
466
+ }
467
+ return false;
468
+ }
469
+ checkExhaustion() {
470
+ if (this.fight.fighter1.currentStamina < 5 && this.fight.fighter2.currentStamina < 5) {
471
+ const f1Damage = 100 - this.fight.fighter2.currentHealth;
472
+ const f2Damage = 100 - this.fight.fighter1.currentHealth;
473
+ if (f1Damage > f2Damage) {
474
+ this.fight.winner = this.fight.fighter1.name;
475
+ } else if (f2Damage > f1Damage) {
476
+ this.fight.winner = this.fight.fighter2.name;
477
+ } else {
478
+ this.fight.winner = void 0;
479
+ this.fight.method = "DRAW";
480
+ return true;
481
+ }
482
+ this.fight.method = "TKO";
483
+ return true;
484
+ }
485
+ return false;
486
+ }
487
+ endRound() {
488
+ const currentRound = this.fight.rounds[this.fight.currentRound - 1];
489
+ currentRound.isActive = false;
490
+ this.fight.fighter1.currentHealth = Math.min(100, this.fight.fighter1.currentHealth + this.fight.fighter1.recovery * 0.5);
491
+ this.fight.fighter2.currentHealth = Math.min(100, this.fight.fighter2.currentHealth + this.fight.fighter2.recovery * 0.5);
492
+ this.fight.fighter1.currentStamina = 100;
493
+ this.fight.fighter2.currentStamina = 100;
494
+ this.fight.fighter1.position = "standing";
495
+ this.fight.fighter2.position = "standing";
496
+ this.onRoundEnd?.(currentRound);
497
+ if (this.fight.currentRound >= TOTAL_ROUNDS) {
498
+ this.endFightByDecision();
499
+ } else {
500
+ this.fight.currentRound++;
501
+ this.fight.rounds.push(this.createRound(this.fight.currentRound));
502
+ this.fight.rounds[this.fight.currentRound - 1].isActive = true;
503
+ }
504
+ }
505
+ endFightByDecision() {
506
+ const f1Score = 100 - this.fight.fighter2.currentHealth + this.fight.fighter1.takedownsLanded * 10 + this.fight.fighter1.significantStrikes * 2;
507
+ const f2Score = 100 - this.fight.fighter1.currentHealth + this.fight.fighter2.takedownsLanded * 10 + this.fight.fighter2.significantStrikes * 2;
508
+ if (f1Score > f2Score) {
509
+ this.fight.winner = this.fight.fighter1.name;
510
+ } else if (f2Score > f1Score) {
511
+ this.fight.winner = this.fight.fighter2.name;
512
+ } else {
513
+ this.fight.winner = void 0;
514
+ }
515
+ this.fight.method = this.fight.winner ? "DEC" : "DRAW";
516
+ this.endFight();
517
+ }
518
+ endFight() {
519
+ this.pause();
520
+ this.fight.isComplete = true;
521
+ this.fight.endRound = this.fight.currentRound;
522
+ this.fight.endTime = this.fight.rounds[this.fight.currentRound - 1]?.timeRemaining;
523
+ this.onFightEnd?.(this.fight);
524
+ }
525
+ // Parse skills.md content into FighterStats
526
+ static parseSkillsMd(content) {
527
+ const stats = { ...DEFAULT_FIGHTER };
528
+ const lines = content.split("\n");
529
+ for (const line of lines) {
530
+ const [key, value] = line.split(":").map((s) => s.trim());
531
+ if (!key || !value) continue;
532
+ const numValue = parseFloat(value);
533
+ if (isNaN(numValue)) {
534
+ if (key === "name") stats.name = value;
535
+ if (key === "nickname") stats.nickname = value;
536
+ continue;
537
+ }
538
+ switch (key.toLowerCase()) {
539
+ case "striking":
540
+ case "boxing":
541
+ stats.striking = numValue;
542
+ break;
543
+ case "punch_speed":
544
+ case "handspeed":
545
+ stats.punchSpeed = numValue;
546
+ break;
547
+ case "kicks":
548
+ case "kick_power":
549
+ stats.kickPower = numValue;
550
+ break;
551
+ case "head_movement":
552
+ case "defense":
553
+ stats.headMovement = numValue;
554
+ break;
555
+ case "wrestling":
556
+ case "takedowns":
557
+ stats.wrestling = numValue;
558
+ break;
559
+ case "takedown_defense":
560
+ case "tdd":
561
+ stats.takedownDefense = numValue;
562
+ break;
563
+ case "bjj":
564
+ case "submissions":
565
+ stats.submissions = numValue;
566
+ break;
567
+ case "submission_defense":
568
+ stats.submissionDefense = numValue;
569
+ break;
570
+ case "ground_game":
571
+ case "grappling":
572
+ stats.groundGame = numValue;
573
+ break;
574
+ case "cardio":
575
+ case "stamina":
576
+ stats.cardio = numValue;
577
+ break;
578
+ case "chin":
579
+ case "durability":
580
+ stats.chin = numValue;
581
+ break;
582
+ case "recovery":
583
+ stats.recovery = numValue;
584
+ break;
585
+ case "iq":
586
+ case "fight_iq":
587
+ stats.fightIQ = numValue;
588
+ break;
589
+ case "heart":
590
+ stats.heart = numValue;
591
+ break;
592
+ case "aggression":
593
+ stats.aggression = numValue;
594
+ break;
595
+ }
596
+ }
597
+ return stats;
598
+ }
599
+ }
600
+ var FightEngine_default = FightEngine;
601
+ export {
602
+ FightEngine,
603
+ FightEngine_default as default
604
+ };
605
+ //# sourceMappingURL=FightEngine.js.map