steel_core/entity/damage/
source.rs1use std::fmt;
2use std::sync::Arc;
3
4use glam::DVec3;
5use steel_registry::{
6 REGISTRY, TaggedRegistryExt, damage_type::DamageScaling, damage_type::DamageType,
7 vanilla_damage_type_tags,
8};
9
10use crate::entity::SharedEntity;
11use crate::player::Player;
12
13#[derive(Clone)]
18pub struct DamageSource {
19 pub damage_type: &'static DamageType,
21 causing_entity: Option<SharedEntity>,
22 direct_entity: Option<SharedEntity>,
23 source_position: Option<DVec3>,
24}
25
26impl DamageSource {
27 #[must_use]
29 pub const fn environment(damage_type: &'static DamageType) -> Self {
30 Self {
31 damage_type,
32 causing_entity: None,
33 direct_entity: None,
34 source_position: None,
35 }
36 }
37
38 #[must_use]
40 pub fn direct(damage_type: &'static DamageType, entity: SharedEntity) -> Self {
41 Self {
42 damage_type,
43 causing_entity: Some(Arc::clone(&entity)),
44 direct_entity: Some(entity),
45 source_position: None,
46 }
47 }
48
49 #[must_use]
51 pub fn with_causing_entity(mut self, entity: SharedEntity) -> Self {
52 self.causing_entity = Some(entity);
53 self
54 }
55
56 #[must_use]
58 pub fn with_direct_entity(mut self, entity: SharedEntity) -> Self {
59 self.direct_entity = Some(entity);
60 self
61 }
62
63 #[must_use]
65 pub const fn causing_entity(&self) -> Option<&SharedEntity> {
66 self.causing_entity.as_ref()
67 }
68
69 #[must_use]
71 pub const fn direct_entity(&self) -> Option<&SharedEntity> {
72 self.direct_entity.as_ref()
73 }
74
75 #[must_use]
78 pub fn source_position(&self) -> Option<DVec3> {
79 self.source_position
80 .or_else(|| self.direct_entity().map(|entity| entity.position()))
81 }
82
83 #[must_use]
86 pub const fn source_position_raw(&self) -> Option<DVec3> {
87 self.source_position
88 }
89
90 #[must_use]
92 pub fn is_creative_player(&self) -> bool {
93 self.causing_entity()
94 .and_then(|entity| entity.as_player())
95 .is_some_and(Player::has_infinite_materials)
96 }
97
98 #[must_use]
100 pub const fn with_source_position(mut self, source_position: DVec3) -> Self {
101 self.source_position = Some(source_position);
102 self
103 }
104
105 #[must_use]
107 pub fn bypasses_invulnerability(&self) -> bool {
108 self.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_INVULNERABILITY)
109 }
110
111 #[must_use]
113 pub fn is(&self, tag: &steel_utils::Identifier) -> bool {
114 REGISTRY.damage_types.is_in_tag(self.damage_type, tag)
115 }
116
117 #[must_use]
120 pub fn is_direct(&self) -> bool {
121 match (self.causing_entity(), self.direct_entity()) {
122 (None, None) => true,
123 (Some(cause), Some(direct)) => Arc::ptr_eq(cause, direct),
124 _ => false,
125 }
126 }
127
128 #[expect(clippy::unused_self, reason = "this is an api function")]
133 #[must_use]
134 pub const fn bypasses_cooldown(&self) -> bool {
135 false
136 }
137
138 #[must_use]
140 pub fn scales_with_difficulty(&self) -> bool {
141 let causing_entity = self.causing_entity();
142 match self.damage_type.scaling {
143 DamageScaling::Never => false,
144 DamageScaling::WhenCausedByLivingNonPlayer => causing_entity.is_some_and(|entity| {
145 entity.as_living_entity().is_some() && entity.as_player().is_none()
146 }),
147 DamageScaling::Always => true,
148 }
149 }
150}
151
152impl fmt::Debug for DamageSource {
153 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154 formatter
155 .debug_struct("DamageSource")
156 .field("damage_type", &self.damage_type)
157 .field(
158 "causing_entity",
159 &self
160 .causing_entity
161 .as_ref()
162 .map(|entity| entity.generation()),
163 )
164 .field(
165 "direct_entity",
166 &self
167 .direct_entity
168 .as_ref()
169 .map(|entity| entity.generation()),
170 )
171 .field("source_position", &self.source_position)
172 .finish()
173 }
174}