steel_core/entity/
monster.rs1use steel_math::fast_floor;
4use steel_protocol::packets::game::SoundSource;
5use steel_registry::dimension_type::MonsterSpawnLightLevel;
6use steel_registry::entity_type::EntityTypeRef;
7use steel_registry::sound_event::SoundEventRef;
8use steel_registry::sound_events;
9use steel_registry::vanilla_game_rules::MOB_DROPS;
10use steel_utils::BlockPos;
11use steel_utils::random::Random;
12use steel_utils::types::Difficulty;
13
14use crate::chunk::light::{LightLayer, MAX_LIGHT_LEVEL};
15use crate::entity::damage::DamageSource;
16use crate::entity::mob::LIGHT_MAGIC_VALUE_BRIGHTNESS_GATE;
17use crate::entity::{EntitySpawnReason, Mob, PathfinderMob, SharedEntity};
18use crate::physics::MoveResult;
19use crate::player::Player;
20use crate::world::{LevelReader, World};
21
22pub const DEFAULT_XP_REWARD: i32 = 5;
23
24const THUNDERING_SKY_DARKENING: u8 = 10;
25const BRIGHT_LIGHT_NO_ACTION_TIME_INCREMENT: i32 = 2;
26const SKY_LIGHT_SPAWN_ROLL_BOUND: i32 = 32;
27
28pub trait Enemy {}
31
32impl<T: Monster + ?Sized> Enemy for T {}
33
34pub trait Monster: PathfinderMob + Enemy {
37 fn default_xp_reward_monster(&self) -> i32 {
38 DEFAULT_XP_REWARD
39 }
40
41 fn sound_source_monster(&self) -> SoundSource {
42 SoundSource::Hostile
43 }
44
45 fn hurt_sound_monster(&self, _source: &DamageSource) -> Option<SoundEventRef> {
46 Some(&sound_events::ENTITY_HOSTILE_HURT)
47 }
48
49 fn death_sound_monster(&self) -> Option<SoundEventRef> {
50 Some(&sound_events::ENTITY_HOSTILE_DEATH)
51 }
52
53 fn fall_sounds_monster(&self) -> (SoundEventRef, SoundEventRef) {
54 (
55 &sound_events::ENTITY_HOSTILE_SMALL_FALL,
56 &sound_events::ENTITY_HOSTILE_BIG_FALL,
57 )
58 }
59
60 fn swim_splash_sound_monster(&self) -> SoundEventRef {
62 &sound_events::ENTITY_HOSTILE_SPLASH
63 }
64
65 fn swim_sound_monster(&self) -> SoundEventRef {
67 &sound_events::ENTITY_HOSTILE_SWIM
68 }
69
70 fn update_no_action_time(&self) {
72 let Some(world) = self.level() else {
73 return;
74 };
75 let eye_pos = BlockPos::new(
76 fast_floor(self.position().x),
77 fast_floor(self.get_eye_y()),
78 fast_floor(self.position().z),
79 );
80 if world.light_level_dependent_magic_value(eye_pos) > LIGHT_MAGIC_VALUE_BRIGHTNESS_GATE {
81 self.set_no_action_time(self.no_action_time() + BRIGHT_LIGHT_NO_ACTION_TIME_INCREMENT);
82 }
83 }
84
85 fn monster_ai_step(&self, entity: &SharedEntity) -> Option<MoveResult> {
86 self.update_no_action_time();
87 self.mob_ai_step(entity)
88 }
89
90 fn get_walk_target_value_monster(&self, pos: BlockPos) -> f32 {
92 let Some(world) = self.level() else {
93 return 0.0;
94 };
95 -world.pathfinding_cost_from_light_levels(pos)
96 }
97
98 fn is_preventing_player_rest(&self, _level: &World, _player: &Player) -> bool {
99 true
100 }
101
102 fn should_drop_experience_monster(&self) -> bool {
103 true
104 }
105
106 fn should_drop_loot_monster(&self, world: &World) -> bool {
108 world.get_game_rule(&MOB_DROPS)
109 }
110
111 fn is_dark_enough_to_spawn(level: &World, pos: BlockPos, random: &mut impl Random) -> bool
113 where
114 Self: Sized,
115 {
116 if i32::from(level.light_value_at(LightLayer::Sky, pos))
117 > random.next_i32_bounded(SKY_LIGHT_SPAWN_ROLL_BOUND)
118 {
119 return false;
120 }
121
122 let dimension_type = &level.dimension_type;
123 let block_light_limit = dimension_type.monster_spawn_block_light_limit;
124 if block_light_limit < i32::from(MAX_LIGHT_LEVEL)
125 && i32::from(level.light_value_at(LightLayer::Block, pos)) > block_light_limit
126 {
127 return false;
128 }
129
130 let sky_darkening = if level.is_thundering() {
131 THUNDERING_SKY_DARKENING
132 } else {
133 level.sky_darkening()
134 };
135 let brightness = level.max_local_raw_brightness(pos, sky_darkening);
136 i32::from(brightness)
137 <= sample_monster_spawn_light_test(&dimension_type.monster_spawn_light_level, random)
138 }
139
140 fn check_monster_spawn_rules(
142 entity_type: EntityTypeRef,
143 level: &World,
144 spawn_reason: EntitySpawnReason,
145 pos: BlockPos,
146 random: &mut impl Random,
147 ) -> bool
148 where
149 Self: Sized,
150 {
151 is_spawn_allowed_on_difficulty(entity_type, level)
152 && (spawn_reason.ignores_light_requirements()
153 || Self::is_dark_enough_to_spawn(level, pos, random))
154 && <Self as Mob>::check_mob_spawn_rules(entity_type, level, spawn_reason, pos)
155 }
156
157 fn check_any_light_monster_spawn_rules(
159 entity_type: EntityTypeRef,
160 level: &World,
161 spawn_reason: EntitySpawnReason,
162 pos: BlockPos,
163 ) -> bool
164 where
165 Self: Sized,
166 {
167 is_spawn_allowed_on_difficulty(entity_type, level)
168 && <Self as Mob>::check_mob_spawn_rules(entity_type, level, spawn_reason, pos)
169 }
170
171 fn check_surface_monsters_spawn_rules(
173 entity_type: EntityTypeRef,
174 level: &World,
175 spawn_reason: EntitySpawnReason,
176 pos: BlockPos,
177 random: &mut impl Random,
178 ) -> bool
179 where
180 Self: Sized,
181 {
182 Self::check_monster_spawn_rules(entity_type, level, spawn_reason, pos, random)
183 && (spawn_reason.is_spawner() || level.can_see_sky(pos))
184 }
185}
186
187fn is_spawn_allowed_on_difficulty(entity_type: EntityTypeRef, level: &World) -> bool {
190 entity_type.allowed_in_peaceful || level.difficulty() != Difficulty::Peaceful
191}
192
193fn sample_monster_spawn_light_test(
196 level: &MonsterSpawnLightLevel,
197 random: &mut impl Random,
198) -> i32 {
199 match level {
200 MonsterSpawnLightLevel::Simple(level) => *level,
201 MonsterSpawnLightLevel::Complex {
202 distribution_type,
203 min_inclusive,
204 max_inclusive,
205 } => {
206 if *distribution_type == "minecraft:uniform" {
207 random.next_i32_between(*min_inclusive, *max_inclusive)
208 } else {
209 *min_inclusive
210 }
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests;