Skip to content

Commit 6724422

Browse files
authored
Merge pull request #8 from FightCore/feat/hitbox-rework
Feat/hitbox rework
2 parents c122f81 + a13e074 commit 6724422

File tree

12 files changed

+1313
-548
lines changed

12 files changed

+1313
-548
lines changed

nodemon.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"src"
44
],
55
"ext": "ts",
6-
"exec": "tsc && node -r dotenv/config ./build/index.js"
6+
"exec": "tsc -p tsconfig-build.json && node -r dotenv/config ./build/index.js"
77
}

package-lock.json

Lines changed: 958 additions & 343 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,26 @@
2626
},
2727
"homepage": "https://github.com/FightCore/bot-js#readme",
2828
"dependencies": {
29-
"@apollo/client": "^3.10.5",
29+
"@apollo/client": "^3.11.3",
3030
"@datalust/winston-seq": "^2.0.0",
3131
"@elastic/elasticsearch": "^8.14.0",
32-
"axios": "^1.7.2",
32+
"axios": "^1.7.3",
3333
"discord.js": "^14.15.3",
3434
"dotenv": "^16.4.5",
3535
"inversify": "^6.0.2",
3636
"jaro-winkler-typescript": "^1.0.1",
3737
"reflect-metadata": "^0.1.14",
3838
"sqlite3": "^5.1.7",
39-
"winston": "^3.13.0"
39+
"winston": "^3.13.1"
4040
},
4141
"devDependencies": {
4242
"@jest/globals": "^29.7.0",
43-
"@types/node": "^16.18.100",
43+
"@types/node": "^16.18.104",
4444
"@typescript-eslint/eslint-plugin": "^6.21.0",
4545
"@typescript-eslint/parser": "^6.21.0",
4646
"eslint": "^8.57.0",
4747
"jest": "^29.7.0",
48-
"ts-jest": "^29.1.5",
49-
"typescript": "^5.4.5"
48+
"ts-jest": "^29.2.4",
49+
"typescript": "^5.5.4"
5050
}
51-
}
51+
}

src/assets/framedata.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/data/unique-move-fixes.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Character } from '../models/character';
2+
import { Move } from '../models/move';
3+
4+
/**
5+
* Fixes unique moves by modifying the provided move object.
6+
*
7+
* @param {Move} move - The move object to be fixed.
8+
* @return {Move} The modified move object.
9+
*/
10+
export function fixUniqueMoves(move: Move, character: Character): Move {
11+
if ((move.normalizedName === 'neutralb' || move.normalizedName === 'aneutralb') && character.normalizedName === 'ness') {
12+
return fixNessNeutralB(move);
13+
}
14+
if (move.normalizedName == 'nessspecial' && character.normalizedName === 'kirby') {
15+
return fixNessNeutralB(move);
16+
}
17+
18+
return move;
19+
}
20+
21+
function fixNessNeutralB(move: Move): Move {
22+
move.hits = move.hits.slice(move.hits.length - 1);
23+
24+
// Add notes if they don't exist previously and ensure they start on a new line.
25+
if (!move.notes) {
26+
move.notes = '';
27+
} else {
28+
move.notes += '\n';
29+
}
30+
move.notes +=
31+
'Damage starts as 11 but increases every frame by 1. Only the last hitbox is shown. Please check the website for more info.';
32+
return move;
33+
}

src/embeds/field-creator/hitlag-field-creator.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,29 @@ export class HitlagFieldCreator {
2626
value: hitlagValues.map((hitlag) => hitlag.hitlagAttacker).join('/'),
2727
},
2828
{
29-
title: 'Hitlag for victim',
29+
title: 'Hitlag for defender',
3030
value: hitlagValues.map((hitlag) => hitlag.hitlagDefender).join('/'),
3131
},
3232
{
3333
title: 'Hitlag for attacker (crouch canceled)',
3434
value: hitlagValues.map((hitlag) => hitlag.hitlagAttackerCrouch).join('/'),
3535
},
3636
{
37-
title: 'Hitlag for victim (crouch canceled)',
37+
title: 'Hitlag for defender (crouch canceled)',
3838
value: hitlagValues.map((hitlag) => hitlag.hitlagDefenderCrouch).join('/'),
3939
},
4040
]);
4141
}
4242

4343
return BodyFormatter.create([
4444
{
45-
title: 'Hitlag',
45+
title: 'Hitlag attacker & defender',
4646
value: hitlagValues.map((hitlag) => hitlag.hitlagAttacker).join('/'),
4747
},
4848
{
49-
title: 'Hitlag (crouch canceled)',
49+
title: 'Hitlag attacker & defender (crouch canceled)',
5050
value: hitlagValues.map((hitlag) => hitlag.hitlagAttackerCrouch).join('/'),
5151
},
52-
{
53-
title: 'Note',
54-
value: 'Hitlag is the same for attacker and defender',
55-
},
5652
]);
5753
}
5854
}

src/embeds/knockback-embed-creator.ts

Lines changed: 87 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Loader } from '../data/loader';
99
import { Hitbox } from '../models/hitbox';
1010
import { versionNumber } from '../meta-data';
1111
import { getMoveLink } from '../utils/fightcore-link';
12+
import { processDuplicateHits, processDuplicateHitboxesForCrouchCancel } from '../utils/hitbox-utils';
1213

1314
export abstract class KnockbackEmbedCreator extends BaseEmbedCreator {
1415
constructor(private knockbackTarget: number, private longTerm: string, private shortTerm: string) {
@@ -40,80 +41,94 @@ export abstract class KnockbackEmbedCreator extends BaseEmbedCreator {
4041
const characterEmote = CharacterEmoji.getEmoteId(character.normalizedName);
4142
const targetEmote = CharacterEmoji.getEmoteId(target.normalizedName);
4243
embedBuilder.setTitle(`${characterEmote} ${character.name} - ${move.name} vs ${target.name} ${targetEmote} `);
43-
for (const hitbox of move.hitboxes.sort(this.orderHitboxes)) {
44-
if (hitbox.angle > 179 && hitbox.angle != 361) {
45-
hitboxMap.set(hitbox.name, `Can not be ${this.shortTerm} due to angle being higher than 179 (${hitbox.angle})`);
46-
} else if (hitbox.angle === 0) {
47-
hitboxMap.set(hitbox.name, `Can not be ${this.shortTerm} due to angle being 0`);
48-
} else if (hitbox.setKnockback) {
49-
const canBeCanceled = !CrouchCancelCalculator.meetsKnockbackTarget(hitbox, target, this.knockbackTarget);
50-
hitboxMap.set(hitbox.name, `Can ${canBeCanceled ? 'always' : 'not'} be ${this.shortTerm}`);
51-
} else {
52-
const crouchCancelPercentage = this.getCrouchCancelPercentageOrImpossible(hitbox, target);
53-
hitboxMap.set(hitbox.name, crouchCancelPercentage);
54-
}
44+
45+
const hits = processDuplicateHits(processDuplicateHitboxesForCrouchCancel(move.hits));
46+
if (hits.length > 20) {
47+
return this.createErrorEmbed(embedBuilder);
5548
}
49+
5650
let result = `${this.longTerm} breaks at the following percentages for each hitbox.\n`;
57-
for (const keyValuePair of hitboxMap) {
58-
result += InfoLine.createLineWithTitle(keyValuePair[0], keyValuePair[1]) + '\n';
51+
for (const hit of hits) {
52+
const name = hit.name ? hit.name : `Frames ${hit.aggregatedStart} - ${hit.aggregatedEnd}`;
53+
54+
for (const hitbox of hit.hitboxes.sort(this.orderHitboxes)) {
55+
if (hitbox.angle > 179 && hitbox.angle != 361) {
56+
hitboxMap.set(
57+
name + ' - ' + hitbox.name,
58+
`Can not be ${this.shortTerm} due to angle being higher than 179 (${hitbox.angle})`
59+
);
60+
} else if (hitbox.angle === 0) {
61+
hitboxMap.set(name + ' - ' + hitbox.name, `Can not be ${this.shortTerm} due to angle being 0`);
62+
} else if (hitbox.setKnockback) {
63+
const canBeCanceled = !CrouchCancelCalculator.meetsKnockbackTarget(hitbox, target, this.knockbackTarget);
64+
hitboxMap.set(name + ' - ' + hitbox.name, `Can ${canBeCanceled ? 'always' : 'not'} be ${this.shortTerm}`);
65+
} else {
66+
const crouchCancelPercentage = this.getCrouchCancelPercentageOrImpossible(hitbox, target);
67+
hitboxMap.set(name + ' - ' + hitbox.name, crouchCancelPercentage);
68+
}
69+
}
70+
71+
for (const keyValuePair of hitboxMap) {
72+
result += InfoLine.createLineWithTitle(keyValuePair[0], keyValuePair[1]) + '\n';
73+
}
5974
}
75+
6076
embedBuilder.addFields({ name: `${this.longTerm} percentage`, value: result });
6177
return [embedBuilder];
6278
}
6379

6480
private createForAll(character: Character, move: Move, embedBuilder: EmbedBuilder, dataLoader: Loader): EmbedBuilder[] {
6581
const characterEmote = CharacterEmoji.getEmoteId(character.normalizedName);
6682
embedBuilder.setTitle(`${characterEmote} ${character.name} - ${move.name}`);
67-
for (const hitbox of move.hitboxes.sort(this.orderHitboxes)) {
68-
if (hitbox.angle > 179 && hitbox.angle != 361) {
69-
embedBuilder.addFields({
70-
name: hitbox.name,
71-
value: `Can not be ${this.shortTerm} due to angle being higher than 179 (${hitbox.angle})`,
72-
});
73-
continue;
74-
} else if (hitbox.angle === 0) {
75-
embedBuilder.addFields({ name: hitbox.name, value: `Can not be ${this.shortTerm} due to angle being 0` });
76-
continue;
77-
} else if (hitbox.setKnockback) {
78-
this.addSetKnockbackField(embedBuilder, hitbox, dataLoader);
79-
continue;
80-
}
8183

82-
const hitboxMap = new Map<Character, string>();
83-
for (const target of dataLoader.data
84+
const hits = processDuplicateHits(processDuplicateHitboxesForCrouchCancel(move.hits));
85+
if (hits.length > 20) {
86+
return this.createErrorEmbed(embedBuilder);
87+
}
88+
for (const hit of hits) {
89+
const hitboxes = hit.hitboxes;
90+
const characters = dataLoader.data
8491
.filter((character) => character.characterStatistics.weight > 0)
85-
.sort(this.orderCharacters)) {
86-
const crouchCancelPercentage = this.getCrouchCancelPercentageOrImpossible(hitbox, target);
87-
hitboxMap.set(target, crouchCancelPercentage);
88-
}
92+
.sort(this.orderCharacters);
93+
for (const hitbox of hitboxes) {
94+
const name = hit.name ? hit.name : `Frames ${hit.aggregatedStart} - ${hit.aggregatedEnd}`;
95+
96+
if (hitbox.angle > 179 && hitbox.angle != 361) {
97+
embedBuilder.addFields({
98+
name: name + ' - ' + hitbox.name,
99+
value: `Can not be ${this.shortTerm} due to angle being higher than 179 (${hitbox.angle})`,
100+
});
101+
continue;
102+
} else if (hitbox.angle === 0) {
103+
embedBuilder.addFields({
104+
name: name + ' - ' + hitbox.name,
105+
value: `Can not be ${this.shortTerm} due to angle being 0`,
106+
});
107+
continue;
108+
} else if (hitbox.setKnockback) {
109+
this.addSetKnockbackField(embedBuilder, hitbox, dataLoader, name);
110+
continue;
111+
}
89112

90-
let fieldText = '';
91-
let iterator = 0;
92-
for (const keyValuePair of hitboxMap) {
93-
const emote = CharacterEmoji.getEmoteId(keyValuePair[0].normalizedName);
94-
fieldText += `${emote} ${keyValuePair[1]} `;
95-
iterator++;
96-
if (iterator === 4) {
97-
iterator = 0;
98-
fieldText += '\n';
113+
let fieldText = '';
114+
let iterator = 0;
115+
for (const character of characters) {
116+
const emote = CharacterEmoji.getEmoteId(character.normalizedName);
117+
fieldText += `${emote} ${this.getCrouchCancelPercentageOrImpossible(hitbox, character)} `;
118+
iterator++;
119+
if (iterator === 4) {
120+
iterator = 0;
121+
fieldText += '\n';
122+
}
99123
}
124+
embedBuilder.addFields({ name: name + ' - ' + hitbox.name, value: fieldText, inline: false });
100125
}
101-
102-
embedBuilder.addFields({ name: hitbox.name, value: fieldText });
103126
}
127+
104128
// Discord has a max length size to the embed.
105129
// With a large amount of hitboxes (like G&W)
106130
if (embedBuilder.length >= 6000) {
107-
const errorEmbed = this.baseEmbed();
108-
errorEmbed.setColor(Colors.DarkRed);
109-
errorEmbed.setTitle(embedBuilder.data.title!);
110-
errorEmbed.addFields({
111-
name: 'Too many hitboxes',
112-
value: `This move has too many hitboxes to be displayed on Discord, either supply a target character or visit our [website](${embedBuilder
113-
.data.url!})`,
114-
});
115-
116-
return [errorEmbed];
131+
return this.createErrorEmbed(embedBuilder);
117132
}
118133
return [embedBuilder];
119134
}
@@ -135,7 +150,7 @@ export abstract class KnockbackEmbedCreator extends BaseEmbedCreator {
135150
return hitboxOne.name.localeCompare(hitboxTwo.name, undefined, { numeric: true, sensitivity: 'base' });
136151
}
137152

138-
private addSetKnockbackField(embedBuilder: EmbedBuilder, hitbox: Hitbox, dataLoader: Loader): void {
153+
private addSetKnockbackField(embedBuilder: EmbedBuilder, hitbox: Hitbox, dataLoader: Loader, hitName: string): void {
139154
const succeedsArray: Character[][] = [[], []];
140155
for (const character of dataLoader.data
141156
.filter((character) => character.characterStatistics.weight > 0)
@@ -149,13 +164,13 @@ export abstract class KnockbackEmbedCreator extends BaseEmbedCreator {
149164

150165
if (succeedsArray[0].length == 0) {
151166
embedBuilder.addFields({
152-
name: hitbox.name,
167+
name: hitName + ' - ' + hitbox.name,
153168
value: `Can never be ${this.shortTerm} by all characters.`,
154169
});
155170
return;
156171
} else if (succeedsArray[1].length == 0) {
157172
embedBuilder.addFields({
158-
name: hitbox.name,
173+
name: hitName + ' - ' + hitbox.name,
159174
value: `Can always be ${this.shortTerm} by all characters.`,
160175
});
161176
return;
@@ -169,8 +184,21 @@ export abstract class KnockbackEmbedCreator extends BaseEmbedCreator {
169184
succeedsArray[1].map((character) => CharacterEmoji.getEmoteId(character.normalizedName)).join(' ');
170185

171186
embedBuilder.addFields({
172-
name: hitbox.name,
187+
name: hitName + ' - ' + hitbox.name,
173188
value: canText + '\n' + canNotText,
174189
});
175190
}
191+
192+
private createErrorEmbed(embedBuilder: EmbedBuilder): EmbedBuilder[] {
193+
const errorEmbed = this.baseEmbed();
194+
errorEmbed.setColor(Colors.DarkRed);
195+
errorEmbed.setTitle(embedBuilder.data.title!);
196+
errorEmbed.addFields({
197+
name: 'Too many hitboxes',
198+
value: `This move has too many hitboxes to be displayed on Discord, either supply a target character or visit our [website](${embedBuilder
199+
.data.url!})`,
200+
});
201+
202+
return [errorEmbed];
203+
}
176204
}

0 commit comments

Comments
 (0)