steel_core/entity/
damage.rs1use glam::DVec3;
4use steel_registry::{
5 REGISTRY, TaggedRegistryExt, damage_type::DamageScaling, damage_type::DamageType,
6 vanilla_damage_type_tags,
7};
8
9use crate::entity::Entity;
10
11#[derive(Debug, Clone)]
13pub struct DamageSource {
14 pub damage_type: &'static DamageType,
16 pub causing_entity_id: Option<i32>,
18 pub direct_entity_id: Option<i32>,
20 pub source_position: Option<DVec3>,
22}
23
24impl DamageSource {
25 #[must_use]
27 pub const fn environment(damage_type: &'static DamageType) -> Self {
28 Self {
29 damage_type,
30 causing_entity_id: None,
31 direct_entity_id: None,
32 source_position: None,
33 }
34 }
35
36 #[must_use]
38 pub const fn with_causing_entity(mut self, entity_id: i32) -> Self {
39 self.causing_entity_id = Some(entity_id);
40 self
41 }
42
43 #[must_use]
45 pub const fn with_direct_entity(mut self, entity_id: i32) -> Self {
46 self.direct_entity_id = Some(entity_id);
47 self
48 }
49
50 #[must_use]
52 pub const fn with_source_position(mut self, source_position: DVec3) -> Self {
53 self.source_position = Some(source_position);
54 self
55 }
56
57 #[must_use]
59 pub fn bypasses_invulnerability(&self) -> bool {
60 self.is(&vanilla_damage_type_tags::DamageTypeTag::BYPASSES_INVULNERABILITY)
61 }
62
63 #[must_use]
65 pub fn is(&self, tag: &steel_utils::Identifier) -> bool {
66 REGISTRY.damage_types.is_in_tag(self.damage_type, tag)
67 }
68
69 #[must_use]
71 pub fn is_direct(&self) -> bool {
72 self.causing_entity_id == self.direct_entity_id
73 }
74
75 #[expect(clippy::unused_self, reason = "this is an api function")]
80 #[must_use]
81 pub const fn bypasses_cooldown(&self) -> bool {
82 false
83 }
84
85 #[must_use]
90 pub fn scales_with_difficulty(&self, causing_entity: Option<&dyn Entity>) -> bool {
91 match self.damage_type.scaling {
92 DamageScaling::Never => false,
93 DamageScaling::WhenCausedByLivingNonPlayer => causing_entity.is_some_and(|entity| {
94 entity.as_living_entity().is_some() && entity.as_player().is_none()
95 }),
96 DamageScaling::Always => true,
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use std::sync::Weak;
104
105 use glam::DVec3;
106 use steel_registry::{init_vanilla_registry, vanilla_damage_types, vanilla_entities};
107
108 use crate::entity::entities::{FireworkRocketEntity, PigEntity};
109
110 use super::*;
111
112 #[test]
113 fn conditional_difficulty_scaling_requires_a_resolved_living_non_player() {
114 init_vanilla_registry();
115 let source = DamageSource::environment(&vanilla_damage_types::FIREWORKS);
116 let pig = PigEntity::new(&vanilla_entities::PIG, 1, DVec3::ZERO, Weak::new());
117 let rocket = FireworkRocketEntity::new(
118 &vanilla_entities::FIREWORK_ROCKET,
119 2,
120 DVec3::ZERO,
121 Weak::new(),
122 );
123
124 assert!(source.scales_with_difficulty(Some(&pig)));
125 assert!(!source.scales_with_difficulty(Some(&rocket)));
126 assert!(!source.scales_with_difficulty(None));
127 }
128}