Skip to content

Commit fc24f6e

Browse files
shinohara-rinnekomeowww
authored andcommitted
feat(minecraft): hurt source
limitations: the yaml rule doesn't support conditioning...
1 parent e31d5e8 commit fc24f6e

File tree

4 files changed

+151
-14
lines changed

4 files changed

+151
-14
lines changed

services/minecraft/src/cognitive/perception/events/definitions/damage-taken.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,108 @@
1+
import type { DamageSourceCause, DamageSourceMetadata } from '../../types/raw-events'
2+
13
import { definePerceptionEvent } from '..'
24

35
interface DamageTakenExtract {
46
amount: number
7+
damageSource: DamageSourceMetadata
58
}
69

710
let lastHealth: number | null = null
811

912
let 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+
11106
export 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

services/minecraft/src/cognitive/perception/rules/danger/damage.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ accumulator:
1111

1212
signal:
1313
type: saliency_high
14-
description: 'Taken damage!'
14+
description: 'Taken damage (amount={{ amount }}, cause={{ damageSource.cause }}).'
1515
confidence: 1.0
1616
metadata:
1717
action: damage
18+
amount: '{{ amount }}'
19+
cause: '{{ damageSource.cause }}'
20+
source_name: '{{ damageSource.name }}'
21+
source_distance: '{{ damageSource.distance }}'

services/minecraft/src/cognitive/perception/saliency-rules.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,34 @@ export const SALIENCY_RULES: SaliencyRuleBook = {
144144
damage_taken: {
145145
threshold: 1, // Damage is immediately salient
146146
key: 'felt:damage',
147-
buildSignal: () => ({
148-
type: 'saliency_high',
149-
description: 'Taken damage!',
150-
confidence: 1.0,
151-
timestamp: Date.now(),
152-
metadata: {
153-
kind: 'felt',
154-
action: 'damage',
155-
},
156-
}),
147+
buildSignal: (event) => {
148+
const e = event as Extract<RawPerceptionEvent, { modality: 'felt', kind: 'damage_taken' }>
149+
const ds = (e as any).damageSource as any
150+
const cause = typeof ds?.cause === 'string' ? ds.cause : 'unknown'
151+
const name = typeof ds?.name === 'string' && ds.name.length > 0 ? ds.name : undefined
152+
const distance = typeof ds?.distance === 'number' ? ds.distance : undefined
153+
const amount = typeof (e as any).amount === 'number' ? (e as any).amount : undefined
154+
155+
const details = [
156+
amount !== undefined ? `amount=${amount}` : null,
157+
`cause=${cause}`,
158+
name ? `name=${name}` : null,
159+
distance !== undefined ? `distance=${distance.toFixed(1)}` : null,
160+
].filter(Boolean).join(', ')
161+
162+
return {
163+
type: 'saliency_high',
164+
description: `Taken damage (${details}).`,
165+
confidence: 1.0,
166+
timestamp: Date.now(),
167+
metadata: {
168+
kind: 'felt',
169+
action: 'damage',
170+
amount: e.amount,
171+
damageSource: e.damageSource,
172+
},
173+
}
174+
},
157175
},
158176
item_collected: {
159177
threshold: 3,

services/minecraft/src/cognitive/perception/types/raw-events.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,26 @@ export interface FeltDamageTakenEvent extends RawPerceptionEventBase {
5757
modality: 'felt'
5858
kind: 'damage_taken'
5959
amount?: number
60-
attackerEntityType?: 'player' | 'mob'
61-
attackerEntityId?: string
60+
damageSource?: DamageSourceMetadata
61+
}
62+
63+
export type DamageSourceCause
64+
= | 'player'
65+
| 'mob'
66+
| 'fall'
67+
| 'lava'
68+
| 'fire'
69+
| 'drown'
70+
| 'anvil'
71+
| 'gravity'
72+
| 'explosion'
73+
| 'projectile'
74+
| 'unknown'
75+
76+
export interface DamageSourceMetadata {
77+
cause: DamageSourceCause
78+
name?: string
79+
entityId?: string
6280
distance?: number
6381
}
6482

0 commit comments

Comments
 (0)