@@ -2,11 +2,23 @@ import zlib from 'zlib';
22import Long from 'long' ;
33import pbjs from 'protobufjs/minimal.js' ;
44import fs from 'fs' ;
5+ import path from 'path' ;
56import pb from './blueprotobuf-esm' ;
6- import type { Logger } from 'winston ' ;
7+ import type { Logger } from '../src/types ' ;
78import type { UserDataManager } from '../src/server/dataManager' ;
89import { initialize , TRACKED_MONSTER_IDS } from '../src/utils/bpTimer' ;
910
11+ const TRANSLATIONS_DIR = path . join ( __dirname , "../main/translations" ) ;
12+ let monsterNames : Record < string , string > = JSON . parse ( fs . readFileSync ( path . join ( TRANSLATIONS_DIR , "en.json" ) , "utf-8" ) ) . monsters ;
13+
14+ export function reloadMonsterTranslations ( language : string ) : void {
15+ monsterNames = JSON . parse ( fs . readFileSync ( path . join ( TRANSLATIONS_DIR , `${ language } .json` ) , "utf-8" ) ) . monsters ;
16+ }
17+
18+ export function getMonsterNames ( ) : Record < string , string > {
19+ return monsterNames ;
20+ }
21+
1022class BinaryReader {
1123 public buffer : Buffer ;
1224 public offset : number ;
@@ -288,25 +300,10 @@ class PacketProcessor {
288300 if ( attrCollection && attrCollection . Attrs ) {
289301 if ( isTargetPlayer ) {
290302 this . #processPlayerAttrs( targetUuid . toNumber ( ) , attrCollection . Attrs ) ;
303+ this . #processPositionAttrs( targetUuid . toNumber ( ) , 'player' , attrCollection . Attrs ) ;
291304 } else if ( isTargetMonster ) {
292305 this . #processEnemyAttrs( targetUuid . toNumber ( ) , attrCollection . Attrs ) ;
293- }
294-
295- for ( const attr of attrCollection . Attrs ) {
296- if ( ( attr . Id === 52 || attr . Id === 53 ) && attr . RawData ) {
297- try {
298- const position = pb . Position . decode ( attr . RawData ) ;
299- const x = position . X ?? 0 ;
300- const y = position . Y ?? 0 ;
301- const z = position . Z ?? 0 ;
302-
303- if ( isTargetMonster ) {
304- this . userDataManager . enemyCache . position . set ( targetUuid . toNumber ( ) , { x, y, z } ) ;
305- } else if ( isTargetPlayer && targetUuid . toNumber ( ) === this . userDataManager . localPlayerUid ) {
306- this . userDataManager . setLocalPlayerPosition ( { x, y, z } ) ;
307- }
308- } catch ( e ) { }
309- }
306+ this . #processPositionAttrs( targetUuid . toNumber ( ) , 'monster' , attrCollection . Attrs ) ;
310307 }
311308 }
312309
@@ -393,16 +390,30 @@ class PacketProcessor {
393390 }
394391
395392 if ( isDead ) {
396- const enemyUid = ` ${ targetUuid . toNumber ( ) } ` ;
393+ const enemyUid = targetUuid . toNumber ( ) ;
397394 const monsterId = this . userDataManager . enemyCache . monsterId . get ( enemyUid ) ;
398- if ( monsterId && TRACKED_MONSTER_IDS . has ( String ( monsterId ) ) ) {
399- if ( this . userDataManager . globalSettings . enableBPTimerSubmission !== false ) {
400- const line = this . userDataManager . getCurrentLineId ( ) ;
401- const bpTimer = initialize ( this . logger , this . userDataManager . globalSettings ) ;
402- setTimeout ( ( ) => {
403- bpTimer . resetMonster ( monsterId , line ) ;
404- this . logger . debug ( `[BPTimer] Reset tracking for monster ${ monsterId } on line ${ line } (isDead flag)` ) ;
405- } , 3000 ) ;
395+ const monsterName = this . userDataManager . enemyCache . name . get ( enemyUid ) ;
396+
397+ if ( ! this . userDataManager . enemyCache . isDead . get ( enemyUid ) ) {
398+ this . userDataManager . enemyCache . isDead . set ( enemyUid , true ) ;
399+ this . userDataManager . enemyCache . hp . set ( enemyUid , 0 ) ;
400+
401+ if ( monsterId && TRACKED_MONSTER_IDS . has ( String ( monsterId ) ) ) {
402+ this . logger . debug ( `[BPTimer] Tracked boss ${ monsterName || 'Unknown' } [ID: ${ monsterId } ] (${ enemyUid } ) died on line ${ this . userDataManager . getCurrentLineId ( ) } ` ) ;
403+
404+ if ( this . userDataManager . globalSettings . enableBPTimerSubmission !== false ) {
405+ const line = this . userDataManager . getCurrentLineId ( ) ;
406+ const bpTimer = initialize ( this . logger , this . userDataManager . globalSettings ) ;
407+
408+ bpTimer . createHpReport ( monsterId , 0 , line ) . catch ( err => {
409+ this . logger . error ( `[BPTimer] Failed to report death: ${ err . message } ` ) ;
410+ } ) ;
411+
412+ setTimeout ( ( ) => {
413+ bpTimer . resetMonster ( monsterId , line ) ;
414+ this . logger . debug ( `[BPTimer] Reset tracking for monster ${ monsterId } on line ${ line } (isDead flag)` ) ;
415+ } , 3000 ) ;
416+ }
406417 }
407418 }
408419 }
@@ -457,7 +468,7 @@ class PacketProcessor {
457468 `EXT: ${ extra . join ( '|' ) } ` ,
458469 ] ;
459470 const dmgLog = dmgLogArr . join ( ' ' ) ;
460- this . logger . info ( dmgLog ) ;
471+ this . logger . debug ( dmgLog ) ;
461472 this . userDataManager . addLog ( dmgLog ) ;
462473 }
463474 }
@@ -626,6 +637,31 @@ class PacketProcessor {
626637 // this.logger.debug(syncContainerDirtyData.VData.Buffer.toString('hex'));
627638 }
628639
640+ #processPositionAttrs( targetUID : number , targetType : 'monster' | 'player' , attrs : any [ ] ) {
641+ for ( const attr of attrs ) {
642+ if ( ! attr . Id || ! attr . RawData ) continue ;
643+
644+ switch ( attr . Id ) {
645+ case 52 : // X
646+ case 53 : // Y
647+ case 54 : // Z
648+ const position = pb . Position . decode ( attr . RawData ) ;
649+ const x = position . X ?? 0 ;
650+ const y = position . Y ?? 0 ;
651+ const z = position . Z ?? 0 ;
652+
653+ if ( targetType === 'monster' ) {
654+ this . userDataManager . enemyCache . position . set ( targetUID , { x, y, z } ) ;
655+ } else if ( targetType === 'player' && targetUID === this . userDataManager . localPlayerUid ) {
656+ this . userDataManager . setLocalPlayerPosition ( { x, y, z } ) ;
657+ }
658+ break ;
659+ default :
660+ break ;
661+ }
662+ }
663+ }
664+
629665 #processPlayerAttrs( playerUid : number , attrs : any [ ] ) {
630666 for ( const attr of attrs ) {
631667 if ( ! attr . Id || ! attr . RawData ) continue ;
@@ -694,25 +730,35 @@ class PacketProcessor {
694730 for ( const attr of attrs ) {
695731 if ( ! attr . Id || ! attr . RawData ) continue ;
696732 const reader = pbjs . Reader . create ( attr . RawData ) ;
697- const base64Data = attr . RawData . toString ( 'base64' ) ;
698733
699734 switch ( attr . Id ) {
700735 case AttrType . AttrName :
701736 const enemyName = reader . string ( ) ;
702737 this . userDataManager . enemyCache . name . set ( enemyUid , enemyName ) ;
703738 this . userDataManager . enemyCache . lastSeen . set ( enemyUid , Date . now ( ) ) ;
704- this . logger . info ( `Found monster name ${ enemyName } for id ${ enemyUid } ` ) ;
739+ this . logger . debug ( `Found monster name ${ enemyName } for id ${ enemyUid } ` ) ;
705740 break ;
706741 case AttrType . AttrId :
707742 const attrId = reader . int32 ( ) ;
708- this . logger . info ( `Found monster attrId ${ attrId } for id ${ enemyUid } ` ) ;
743+ this . logger . debug ( `Found monster attrId ${ attrId } for id ${ enemyUid } ` ) ;
709744 this . userDataManager . enemyCache . monsterId . set ( enemyUid , attrId ) ;
710745 this . userDataManager . enemyCache . lastSeen . set ( enemyUid , Date . now ( ) ) ;
746+
747+ const translatedName = monsterNames [ String ( attrId ) ] ;
748+ if ( translatedName && ! this . userDataManager . enemyCache . name . has ( enemyUid ) ) {
749+ this . userDataManager . enemyCache . name . set ( enemyUid , translatedName ) ;
750+ this . logger . debug ( `Set monster name from translation: ${ translatedName } for id ${ enemyUid } ` ) ;
751+ }
711752 break ;
712753 case AttrType . AttrHp :
713754 const enemyHp = reader . int32 ( ) ;
714755 this . userDataManager . enemyCache . hp . set ( enemyUid , enemyHp ) ;
715756 this . userDataManager . enemyCache . lastSeen . set ( enemyUid , Date . now ( ) ) ;
757+
758+ if ( enemyHp > 0 && this . userDataManager . enemyCache . isDead . get ( enemyUid ) ) {
759+ this . userDataManager . enemyCache . isDead . delete ( enemyUid ) ;
760+ }
761+
716762 this . #reportBossHpThreshold( enemyUid , enemyHp ) ;
717763 break ;
718764 case AttrType . AttrMaxHp :
@@ -733,6 +779,11 @@ class PacketProcessor {
733779 return ;
734780 }
735781
782+ // Don't report if monster is already marked as dead
783+ if ( this . userDataManager . enemyCache . isDead . get ( enemyUid ) ) {
784+ return ;
785+ }
786+
736787 const monsterId = this . userDataManager . enemyCache . monsterId . get ( enemyUid ) ;
737788 const maxHp = this . userDataManager . enemyCache . maxHp . get ( enemyUid ) ;
738789
@@ -744,12 +795,23 @@ class PacketProcessor {
744795 return ;
745796 }
746797
798+ // If HP reaches 0 but isDead wasn't set yet, report it here as fallback
747799 if ( currentHp === 0 || currentHp <= maxHp * 0.001 ) {
748800 const line = this . userDataManager . getCurrentLineId ( ) ;
749801 const bpTimer = initialize ( this . logger , this . userDataManager . globalSettings ) ;
802+
803+ // Report 0% HP (death) as fallback
804+ bpTimer . createHpReport ( monsterId , 0 , line ) . catch ( err => {
805+ this . logger . debug ( `[BPTimer] Failed to report death (fallback): ${ err . message } ` ) ;
806+ } ) ;
807+
808+ // Mark as dead to prevent duplicate reports
809+ this . userDataManager . enemyCache . isDead . set ( enemyUid , true ) ;
810+
811+ // Reset tracking after reporting death
750812 setTimeout ( ( ) => {
751813 bpTimer . resetMonster ( monsterId , line ) ;
752- this . logger . debug ( `[BPTimer] Reset tracking for monster ${ monsterId } on line ${ line } (HP reached 0)` ) ;
814+ this . logger . debug ( `[BPTimer] Reset tracking for monster ${ monsterId } on line ${ line } (HP reached 0 fallback )` ) ;
753815 } , 3000 ) ;
754816 return ;
755817 }
0 commit comments