1+ import type { DamageSourceCause , DamageSourceMetadata } from '../../types/raw-events'
2+
13import { definePerceptionEvent } from '..'
24
35interface DamageTakenExtract {
46 amount : number
7+ damageSource : DamageSourceMetadata
58}
69
710let lastHealth : number | null = null
811
912let pendingDamageAmount : number | null = null
1013
14+ function inferDamageSource ( ctx : { bot : { entity ?: any , entities ?: Record < string , any > } , distanceTo : ( entity : any ) => number | null , maxDistance : number , isSelf : ( entity : any ) => boolean , entityId : ( entity : any ) => string } ) : DamageSourceMetadata {
15+ const entity = ctx . bot . entity as any
16+ const isInLava = Boolean ( entity ?. isInLava )
17+ if ( isInLava )
18+ return { cause : 'lava' }
19+
20+ const isInWater = Boolean ( entity ?. isInWater )
21+ if ( isInWater )
22+ return { cause : 'drown' }
23+
24+ const isOnFire = Boolean ( entity ?. isOnFire ?? entity ?. fire )
25+ if ( isOnFire )
26+ return { cause : 'fire' }
27+
28+ const velocityY = typeof entity ?. velocity ?. y === 'number' ? entity . velocity . y : null
29+ const onGround = typeof entity ?. onGround === 'boolean' ? entity . onGround : null
30+ if ( velocityY !== null && velocityY < - 0.2 && onGround === false )
31+ return { cause : 'gravity' }
32+
33+ const entities = Object . values ( ctx . bot . entities ?? { } )
34+ let nearest : any | null = null
35+ let nearestDistance : number | null = null
36+
37+ for ( const candidate of entities ) {
38+ if ( ! candidate || ctx . isSelf ( candidate ) )
39+ continue
40+
41+ const distance = ctx . distanceTo ( candidate )
42+ if ( distance === null || distance > ctx . maxDistance )
43+ continue
44+
45+ if ( nearestDistance === null || distance < nearestDistance ) {
46+ nearest = candidate
47+ nearestDistance = distance
48+ }
49+ }
50+
51+ if ( nearest ) {
52+ const entityType = nearest . type === 'player' ? 'player' : nearest . type === 'mob' ? 'mob' : null
53+ if ( entityType ) {
54+ return {
55+ cause : entityType ,
56+ name : nearest . username ?? nearest . displayName ?? nearest . name ,
57+ entityId : ctx . entityId ( nearest ) ,
58+ distance : nearestDistance ?? undefined ,
59+ }
60+ }
61+
62+ const name = String ( nearest . name ?? '' ) . toLowerCase ( )
63+ const cause = inferCauseFromName ( name )
64+ if ( cause !== 'unknown' ) {
65+ return {
66+ cause,
67+ name : nearest . name ,
68+ entityId : ctx . entityId ( nearest ) ,
69+ distance : nearestDistance ?? undefined ,
70+ }
71+ }
72+ }
73+
74+ return { cause : 'unknown' }
75+ }
76+
77+ function inferCauseFromName ( name : string ) : DamageSourceCause {
78+ if ( ! name )
79+ return 'unknown'
80+ if ( name . includes ( 'anvil' ) )
81+ return 'anvil'
82+ if ( name . includes ( 'tnt' ) || name . includes ( 'creeper' ) || name . includes ( 'explosion' ) )
83+ return 'explosion'
84+ if ( name . includes ( 'arrow' ) || name . includes ( 'trident' ) || name . includes ( 'snowball' ) )
85+ return 'projectile'
86+ return 'unknown'
87+ }
88+
89+ function describeDamageTaken ( extracted : DamageTakenExtract ) : string {
90+ const amount = extracted . amount
91+ const ds = extracted . damageSource
92+ const cause = ds ?. cause ?? 'unknown'
93+ const name = ds ?. name
94+ const distance = ds ?. distance
95+
96+ const details = [
97+ `amount=${ amount } ` ,
98+ `cause=${ cause } ` ,
99+ typeof name === 'string' && name . length > 0 ? `name=${ name } ` : null ,
100+ typeof distance === 'number' ? `distance=${ distance . toFixed ( 1 ) } ` : null ,
101+ ] . filter ( Boolean ) . join ( ', ' )
102+
103+ return `Taken damage (${ details } ).`
104+ }
105+
11106export const damageTakenEvent = definePerceptionEvent < [ ] , DamageTakenExtract > ( {
12107 id : 'damage_taken' ,
13108 modality : 'felt' ,
@@ -39,6 +134,7 @@ export const damageTakenEvent = definePerceptionEvent<[], DamageTakenExtract>({
39134 const prev = lastHealth ?? current
40135 return {
41136 amount : pendingDamageAmount ?? Math . max ( 0 , prev - current ) ,
137+ damageSource : inferDamageSource ( ctx ) ,
42138 }
43139 } ,
44140 } ,
@@ -50,11 +146,12 @@ export const damageTakenEvent = definePerceptionEvent<[], DamageTakenExtract>({
50146
51147 signal : {
52148 type : 'saliency_high' ,
53- description : ( ) => 'Taken damage!' ,
149+ description : extracted => describeDamageTaken ( extracted ) ,
54150 metadata : extracted => ( {
55151 kind : 'felt' ,
56152 action : 'damage' ,
57153 amount : extracted . amount ,
154+ damageSource : extracted . damageSource ,
58155 } ) ,
59156 } ,
60157
0 commit comments