2024-09-06 22:05:06 -05:00
import { tankIcon } from '@blitzkit/core/src/blitzkit/tankIcon' ;
2024-04-24 16:22:48 -05:00
import { clamp } from 'lodash' ;
import CommandWrapper from '../components/CommandWrapper' ;
import TitleBar from '../components/TitleBar' ;
2024-04-27 11:07:58 -05:00
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo' ;
import { idToRegion } from '../core/blitz/idToRegion' ;
import { emblemURL } from '../core/blitzkit/emblemURL' ;
2024-04-24 16:22:48 -05:00
import { iconPng } from '../core/blitzkit/iconPng' ;
2024-04-27 11:07:58 -05:00
import { mapDefinitions } from '../core/blitzkit/mapDefinitions' ;
2024-04-24 16:22:48 -05:00
import { RenderConfiguration } from '../core/blitzkit/renderConfiguration' ;
import { tankDefinitions } from '../core/blitzkit/tankDefinitions' ;
import { TIER_ROMAN_NUMERALS } from '../core/blitzkit/tankDefinitions/constants' ;
2024-04-26 12:34:59 -05:00
import { tankAverages } from '../core/blitzstars/tankAverages' ;
2024-04-24 16:22:48 -05:00
import buttonLink from '../core/discord/buttonLink' ;
2024-04-23 16:07:59 -05:00
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand' ;
2024-04-26 12:34:59 -05:00
import calculateWN8 from '../core/statistics/calculateWN8' ;
2024-04-23 16:07:59 -05:00
import { CommandRegistry } from '../events/interactionCreate' ;
2024-04-24 16:22:48 -05:00
import { theme } from '../stitches.config' ;
2024-04-23 16:07:59 -05:00
2024-04-24 16:22:48 -05:00
type WotInspectorReplaySubmission =
2024-04-23 16:07:59 -05:00
| { status : 'error' ; data : unknown ; error : { message : string } }
| {
status : 'ok' ;
data : {
download_url : string ;
view_url : string ;
summary : {
battle_start_time : string ;
map_name : string ;
battle_start_timestamp : number ;
battle_duration : number ;
title : string ;
arena_unique_id : string ;
allies : number [];
enemies : number [];
protagonist : number ;
player_name : string ;
vehicle : string ;
exp_base : number ;
exp_total : number ;
credits_base : number ;
credits_total : number ;
room_type : number ;
battle_type : number ;
mastery_badge : number ;
battle_result : number ;
vehicle_tier : number ;
vehicle_type : number ;
protagonist_team : number ;
winner_team : number ;
details : {
achievements : unknown [];
base_capture_points : number ;
base_defend_points : number ;
chassis_id : unknown | null ;
clan_tag : string ;
clanid : number ;
credits : number ;
damage_assisted : number ;
damage_assisted_track : number ;
damage_blocked : unknown | null ;
damage_made : number ;
damage_received : number ;
dbid : number ;
death_reason : number ;
distance_travelled : number ;
enemies_damaged : number ;
enemies_destroyed : number ;
enemies_spotted : number ;
entity_id : number ;
exp : number ;
exp_for_assist : number ;
exp_for_damage : number ;
exp_team_bonus : number ;
gun_id : unknown | null ;
hero_bonus_credits : number ;
hero_bonus_exp : number ;
hitpoints_left : number ;
hits_bounced : number ;
hits_pen : number ;
hits_received : number ;
hits_splash : number ;
killed_by : number ;
name : string ;
shots_hit : number ;
shots_made : number ;
shots_pen : number ;
shots_splash : number ;
squad_index : number ;
team : number ;
time_alive : number ;
turret_id : unknown | null ;
vehicle_descr : number ;
wp_points_earned : number ;
wp_points_stolen : number ;
};
};
};
error : {};
};
2024-04-24 16:22:48 -05:00
interface WotInspectorReplay {
id : string ;
map_id : number ;
battle_duration : number ;
2024-04-26 12:51:35 -05:00
title : unknown | null ;
2024-04-24 16:22:48 -05:00
player_name : string ;
protagonist : number ;
vehicle_descr : number ;
mastery_badge : number ;
exp_base : number ;
enemies_spotted : number ;
enemies_destroyed : number ;
damage_assisted : number ;
damage_made : number ;
details_url : string ;
download_url : string ;
2024-04-26 12:51:35 -05:00
game_version : {
name : string ;
package : string ;
};
2024-04-24 16:22:48 -05:00
arena_unique_id : string ;
download_count : number ;
data_version : number ;
private : boolean ;
private_clan : boolean ;
battle_start_time : Date ;
upload_time : Date ;
allies : number [];
enemies : number [];
protagonist_clan : number ;
protagonist_team : number ;
battle_result : number ;
credits_base : number ;
tags : number [];
battle_type : number ;
room_type : number ;
last_accessed_time : Date ;
winner_team : number ;
finish_reason : number ;
players_data : WotInspectorReplayPlayerData [];
exp_total : number ;
credits_total : number ;
repair_cost : number ;
exp_free : number ;
exp_free_base : number ;
exp_penalty : number ;
credits_penalty : number ;
credits_contribution_in : number ;
credits_contribution_out : number ;
camouflage_id : number ;
}
export interface WotInspectorReplayPlayerData {
team : number ;
name : string ;
entity_id : number ;
dbid : number ;
clanid : number | null ;
clan_tag : null | string ;
hitpoints_left : number ;
credits : number ;
exp : number ;
shots_made : number ;
shots_hit : number ;
shots_splash : number ;
shots_pen : number ;
damage_made : number ;
damage_received : number ;
damage_assisted : number ;
damage_assisted_track : number ;
hits_received : number ;
hits_bounced : number ;
hits_splash : number ;
hits_pen : number ;
enemies_spotted : number ;
enemies_damaged : number ;
enemies_destroyed : number ;
time_alive : number ;
distance_travelled : number ;
killed_by : number ;
base_capture_points : number ;
base_defend_points : number ;
exp_for_damage : number ;
exp_for_assist : number ;
exp_team_bonus : number ;
wp_points_earned : number ;
wp_points_stolen : number ;
hero_bonus_credits : number ;
hero_bonus_exp : number ;
death_reason : number ;
achievements : any [];
vehicle_descr : number ;
turret_id : number ;
gun_id : number ;
chassis_id : number ;
squad_index : number ;
damage_blocked : number ;
}
function stat ( value : string | number , flex = 2 ) {
return (
< div
style = {{
flex ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
}}
>
< span style = {{ fontSize : 16 }}>{ value }</ span >
</ div >
);
}
async function playerListing (
player : WotInspectorReplayPlayerData ,
protagonistTeam : boolean ,
2024-04-26 12:34:59 -05:00
winningTeam : number ,
2024-04-24 16:22:48 -05:00
recordingPlayer = false ,
) {
2024-04-26 12:34:59 -05:00
const awaitedTankAverages = await tankAverages ;
2024-04-24 16:22:48 -05:00
const awaitedTankDefinitions = await tankDefinitions ;
const tank = awaitedTankDefinitions [ player . vehicle_descr ];
const blockAccent = recordingPlayer ? '_amber' : '' ;
const healthAccent = protagonistTeam
? recordingPlayer
? '_amber'
: '_green'
: '_red' ;
// our turret ids are always off by 1 or 2 lol so imma just grab the closest
// TODO: 💀
const turret = tank . turrets . sort (
( a , b ) =>
Math . abs ( a . id - player . turret_id ) - Math . abs ( b . id - player . turret_id ),
)[ 0 ];
const totalHealth = turret . health + tank . health ;
const healthLeft = clamp ( player . hitpoints_left / totalHealth , 0 , 1 );
2024-04-26 12:34:59 -05:00
const tankAveragesEntry = awaitedTankAverages [ player . vehicle_descr ];
const wn8 =
tankAveragesEntry === undefined
? - 1
: calculateWN8 ( tankAveragesEntry . all , {
battles : 1 ,
damage_dealt : player.damage_made ,
dropped_capture_points : player.base_defend_points ,
frags : player.enemies_destroyed ,
spotted : player.enemies_spotted ,
wins : winningTeam === player . team ? 1 : 0 ,
});
2024-04-24 16:22:48 -05:00
return (
< div
key = { player . entity_id }
style = {{
2024-04-30 22:20:31 -05:00
display : 'flex' ,
alignItems : 'center' ,
gap : 8 ,
2024-04-24 16:22:48 -05:00
backgroundColor : theme.colors [ `appBackground2 ${ blockAccent } ` ],
padding : 8 ,
}}
>
< div
style = {{
display : 'flex' ,
gap : 8 ,
2024-04-30 22:20:31 -05:00
flex : 1 ,
overflow : 'hidden' ,
alignItems : 'center' ,
2024-04-24 16:22:48 -05:00
}}
>
2024-04-30 22:20:31 -05:00
< img
2024-05-04 16:28:12 -05:00
alt = { tank . name }
2024-04-30 22:20:31 -05:00
src = { await iconPng ( tankIcon ( tank . id ))}
style = {{
width : 32 ,
height : 32 ,
objectFit : 'contain' ,
}}
/>
2024-04-24 16:22:48 -05:00
< div
style = {{
display : 'flex' ,
2024-04-30 22:20:31 -05:00
flexDirection : 'column' ,
// align
2024-04-24 16:22:48 -05:00
}}
>
< div
style = {{
display : 'flex' ,
2024-04-30 22:20:31 -05:00
gap : 4 ,
alignItems : 'center' ,
2024-04-24 16:22:48 -05:00
}}
>
< span
style = {{
color : theme.colors [ `textLowContrast ${ blockAccent } ` ],
2024-04-30 22:20:31 -05:00
fontSize : 12 ,
2024-04-24 16:22:48 -05:00
}}
>
2024-04-30 22:20:31 -05:00
{ TIER_ROMAN_NUMERALS [ tank . tier ]}
</ span >
< span
style = {{
color : theme.colors [ `textHighContrast ${ blockAccent } ` ],
fontSize : 16 ,
}}
>
{ tank . name }
2024-04-24 16:22:48 -05:00
</ span >
</ div >
2024-04-30 22:20:31 -05:00
< span
style = {{
fontSize : 16 ,
color : theme.colors [ `textLowContrast ${ blockAccent } ` ],
whiteSpace : 'nowrap' ,
}}
>
{ player . name }
{ player . clan_tag ? ` [ ${ player . clan_tag } ]` : '' }
</ span >
2024-04-24 16:22:48 -05:00
</ div >
</ div >
< div
style = {{
2024-04-30 22:20:31 -05:00
display : 'flex' ,
flex : 1 ,
}}
>
{ stat ( player . damage_made . toLocaleString ())}
{ stat ( wn8 === - 1 ? '--' : Math . round ( wn8 ). toLocaleString ())}
{ stat ( player . exp . toLocaleString ())}
{ stat ( player . enemies_destroyed , 1 )}
</ div >
< div
style = {{
2024-04-24 16:22:48 -05:00
borderRadius : 2 ,
backgroundColor : theme.colors [ `componentInteractive ${ healthAccent } ` ],
display : 'flex' ,
2024-04-30 22:20:31 -05:00
width : 4 ,
height : '100%' ,
transform : 'scaleY(-1)' ,
2024-04-24 16:22:48 -05:00
}}
>
< div
style = {{
2024-04-30 22:20:31 -05:00
width : '100%' ,
height : ` ${ healthLeft * 100 } %` ,
2024-04-24 16:22:48 -05:00
borderRadius : 2 ,
backgroundColor : theme.colors [ `solidBackground ${ healthAccent } ` ],
}}
/>
</ div >
</ div >
);
}
2024-04-26 12:34:59 -05:00
function Header() {
return (
< div
style = {{
padding : 8 ,
display : 'flex' ,
alignItems : 'center' ,
gap : 8 ,
}}
>
< div style = {{ flex : 1 }} />
< div
style = {{
display : 'flex' ,
flex : 1 ,
}}
>
< div
style = {{
flex : 2 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
}}
>
< span
style = {{
fontSize : 16 ,
color : theme.colors.textLowContrast ,
}}
>
Damage
</ span >
</ div >
< div
style = {{
flex : 2 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
}}
>
< span
style = {{
fontSize : 16 ,
color : theme.colors.textLowContrast ,
}}
>
WN8
</ span >
</ div >
< div
style = {{
flex : 2 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
}}
>
< span
style = {{
fontSize : 16 ,
color : theme.colors.textLowContrast ,
}}
>
XP
</ span >
</ div >
< div
style = {{
flex : 1 ,
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
}}
>
< span
style = {{
fontSize : 16 ,
color : theme.colors.textLowContrast ,
}}
>
Kills
</ span >
</ div >
</ div >
</ div >
);
}
2024-04-23 16:07:59 -05:00
export const replayCommand = new Promise < CommandRegistry >(( resolve ) => {
resolve ({
command : createLocalizedCommand ( 'replay' ). addAttachmentOption (( option ) =>
option . setName ( 'file' ). setDescription ( 'Replay file' ). setRequired ( true ),
),
async handler ( interaction ) {
2024-04-27 11:07:58 -05:00
const awaitedMapDefinitions = await mapDefinitions ;
2024-04-23 16:07:59 -05:00
const file = interaction . options . getAttachment ( 'file' ) ! ;
2024-04-24 16:22:48 -05:00
// const file = {
// url: 'https://cdn.discordapp.com/ephemeral-attachments/1232434891652861972/1232781496016834581/20240328_1530__TresAbhi_M60_1157057448155406708.wotbreplay?ex=662ab4bc&is=6629633c&hm=c784cd10e80232350ddaf291fa987b879c4d0a8083b78298c9eee4fa0d507f6c&',
// };
const submissionData = ( await fetch (
`https://wotinspector.com/api/replay/upload/?url= ${ encodeURIComponent (
file . url ,
) } ` ,
). then (( response ) => response . json ())) as WotInspectorReplaySubmission ;
2024-04-23 16:07:59 -05:00
2024-04-24 16:22:48 -05:00
if ( submissionData . status === 'error' ) {
return `# Uh oh! WoT Inspector didn't like that. \ n \ nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).` ;
2024-04-23 16:07:59 -05:00
}
2024-04-24 16:22:48 -05:00
const replayId = submissionData . data . download_url . split ( '/' ). at ( - 1 ) ! ;
const replayData = ( await fetch (
`https://api.wotinspector.com/v2/blitz/replays/ ${ replayId } /` ,
). then (( response ) => response . json ())) as WotInspectorReplay ;
2024-04-27 11:07:58 -05:00
const antagonistTeamId = 3 - replayData . protagonist_team ;
2024-04-24 16:22:48 -05:00
const players = replayData . players_data . sort (
( a , b ) => b . damage_made - a . damage_made ,
);
2024-04-27 11:07:58 -05:00
const clan = (
await getClanAccountInfo (
idToRegion ( replayData . protagonist ),
replayData . protagonist ,
[ 'clan' ],
)
) ? . clan ;
const clanImage = clan ? emblemURL ( clan . emblem_set_id ) : undefined ;
2024-04-24 16:22:48 -05:00
return [
new RenderConfiguration ({ width : 960 }),
< CommandWrapper >
< TitleBar
title = { replayData . player_name }
2024-04-27 11:07:58 -05:00
image = { clanImage }
description = { ` ${
replayData . protagonist_team === replayData . winner_team
? 'Victory'
: 'Defeat'
} • ${ awaitedMapDefinitions [ replayData . map_id ]. name } ` }
2024-04-24 16:22:48 -05:00
/>
< div
style = {{
display : 'flex' ,
gap : 8 ,
}}
>
< div
style = {{
flex : 1 ,
display : 'flex' ,
flexDirection : 'column' ,
gap : 4 ,
}}
>
2024-04-26 12:34:59 -05:00
< Header />
2024-04-24 16:22:48 -05:00
{ await Promise . all (
players
2024-04-27 11:07:58 -05:00
. filter (
( player ) => player . team === replayData . protagonist_team ,
)
2024-04-24 16:22:48 -05:00
. map (( player ) =>
playerListing (
player ,
true ,
2024-04-26 12:34:59 -05:00
replayData . winner_team ,
2024-04-24 16:22:48 -05:00
player . dbid === replayData . protagonist ,
),
),
)}
</ div >
< div
style = {{
flex : 1 ,
display : 'flex' ,
flexDirection : 'column' ,
gap : 4 ,
}}
>
2024-04-26 12:34:59 -05:00
< Header />
2024-04-24 16:22:48 -05:00
{ await Promise . all (
players
. filter (( player ) => player . team === antagonistTeamId )
2024-04-26 12:34:59 -05:00
. map (( player ) =>
playerListing ( player , false , replayData . winner_team ),
),
2024-04-24 16:22:48 -05:00
)}
</ div >
</ div >
</ CommandWrapper >,
buttonLink ( submissionData . data . view_url , 'WoT Inspector' ),
buttonLink ( submissionData . data . download_url , 'Download' ),
];
2024-04-23 16:07:59 -05:00
},
});
});