fix: daylight sensor

This commit is contained in:
Alexander Medvedev 2026-09-04 20:08:43 +02:00
parent 38fd0c2714
commit 8d0d0d3117
40 changed files with 4717 additions and 258 deletions

View File

@ -26,6 +26,7 @@ default = [
"game_rules",
"entity",
"dimension",
"environment_attribute",
"enchantment",
"world",
"scoreboard",
@ -54,9 +55,11 @@ default = [
"map_color",
"map_decoration",
"dye_color",
"block_transformer"
"block_transformer",
"trial_spawner",
]
trial_spawner = []
block_transformer = ["block", "sound", "world", "tag"]
map_color = []
map_decoration = []
@ -83,6 +86,7 @@ game_event = []
game_rules = []
entity = ["entity_pose", "entity_status", "entity_type", "spawn_egg"]
dimension = []
environment_attribute = []
enchantment = []
world = ["world_event", "message_type"]
scoreboard = []

View File

@ -4,11 +4,44 @@ use pumpkin_util::math::int_provider::{
IntProvider, NormalIntProvider, TrapezoidIntProvider, UniformIntProvider, WeightedEntry,
WeightedListIntProvider,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BedRuleOption {
Always,
WhenDark,
Never,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BedRule {
pub can_sleep: BedRuleOption,
pub can_set_spawn: BedRuleOption,
pub explodes: bool,
}
impl BedRule {
#[inline]
#[must_use]
pub const fn can_sleep(&self, is_dark_outside: bool) -> bool {
match self.can_sleep {
BedRuleOption::Always => true,
BedRuleOption::WhenDark => is_dark_outside,
BedRuleOption::Never => false,
}
}
#[inline]
#[must_use]
pub const fn can_set_spawn(&self, is_dark_outside: bool) -> bool {
match self.can_set_spawn {
BedRuleOption::Always => true,
BedRuleOption::WhenDark => is_dark_outside,
BedRuleOption::Never => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Dimension {
pub id: u8,
pub minecraft_name: &'static str,
pub fixed_time: Option<i64>,
pub has_fixed_time: bool,
pub has_skylight: bool,
pub has_ceiling: bool,
pub coordinate_scale: f64,
@ -22,6 +55,21 @@ pub struct Dimension {
pub sky_color: Option<i32>,
pub fog_color: Option<i32>,
pub cloud_color: Option<i32>,
pub ambient_light_color: Option<i32>,
pub sky_light_color: Option<i32>,
pub sky_light_factor: Option<f32>,
pub fog_start_distance: Option<f32>,
pub fog_end_distance: Option<f32>,
pub cloud_height: Option<f32>,
pub sky_light_level: Option<f32>,
pub water_evaporates: bool,
pub fast_lava: bool,
pub respawn_anchor_works: bool,
pub piglins_zombify: bool,
pub snow_golem_melts: bool,
pub can_start_raid: bool,
pub nether_portal_spawns_piglin: bool,
pub bed_rule: BedRule,
pub timelines: Option<&'static str>,
}
impl Dimension {
@ -29,6 +77,7 @@ impl Dimension {
id: 0u8,
minecraft_name: "minecraft:overworld",
fixed_time: None,
has_fixed_time: false,
has_skylight: true,
has_ceiling: false,
coordinate_scale: 1f64,
@ -46,13 +95,33 @@ impl Dimension {
monster_spawn_block_light_limit: 0u8,
sky_color: Some(7907327i32),
fog_color: Some(12638463i32),
cloud_color: None,
cloud_color: Some(-855638017i32),
ambient_light_color: Some(657930i32),
sky_light_color: None,
sky_light_factor: None,
fog_start_distance: None,
fog_end_distance: None,
cloud_height: Some(192.33f32),
sky_light_level: None,
water_evaporates: false,
fast_lava: false,
respawn_anchor_works: false,
piglins_zombify: true,
snow_golem_melts: false,
can_start_raid: true,
nether_portal_spawns_piglin: true,
bed_rule: BedRule {
can_sleep: BedRuleOption::WhenDark,
can_set_spawn: BedRuleOption::Always,
explodes: false,
},
timelines: Some("#minecraft:in_overworld"),
};
pub const OVERWORLD_CAVES: Self = Self {
id: 1u8,
minecraft_name: "minecraft:overworld_caves",
fixed_time: None,
has_fixed_time: false,
has_skylight: true,
has_ceiling: true,
coordinate_scale: 1f64,
@ -70,13 +139,33 @@ impl Dimension {
monster_spawn_block_light_limit: 0u8,
sky_color: Some(7907327i32),
fog_color: Some(12638463i32),
cloud_color: None,
cloud_color: Some(-855638017i32),
ambient_light_color: Some(657930i32),
sky_light_color: None,
sky_light_factor: None,
fog_start_distance: None,
fog_end_distance: None,
cloud_height: Some(192.33f32),
sky_light_level: None,
water_evaporates: false,
fast_lava: false,
respawn_anchor_works: false,
piglins_zombify: true,
snow_golem_melts: false,
can_start_raid: true,
nether_portal_spawns_piglin: true,
bed_rule: BedRule {
can_sleep: BedRuleOption::WhenDark,
can_set_spawn: BedRuleOption::Always,
explodes: false,
},
timelines: Some("#minecraft:in_overworld"),
};
pub const THE_END: Self = Self {
id: 2u8,
minecraft_name: "minecraft:the_end",
fixed_time: None,
has_fixed_time: true,
has_skylight: true,
has_ceiling: false,
coordinate_scale: 1f64,
@ -90,12 +179,32 @@ impl Dimension {
sky_color: Some(0i32),
fog_color: Some(1577752i32),
cloud_color: None,
ambient_light_color: Some(4147007i32),
sky_light_color: Some(11296973i32),
sky_light_factor: Some(0f32),
fog_start_distance: None,
fog_end_distance: None,
cloud_height: None,
sky_light_level: None,
water_evaporates: false,
fast_lava: false,
respawn_anchor_works: false,
piglins_zombify: true,
snow_golem_melts: false,
can_start_raid: true,
nether_portal_spawns_piglin: false,
bed_rule: BedRule {
can_sleep: BedRuleOption::Never,
can_set_spawn: BedRuleOption::Never,
explodes: true,
},
timelines: Some("#minecraft:in_end"),
};
pub const THE_NETHER: Self = Self {
id: 3u8,
minecraft_name: "minecraft:the_nether",
fixed_time: None,
has_fixed_time: true,
has_skylight: false,
has_ceiling: true,
coordinate_scale: 8f64,
@ -109,6 +218,25 @@ impl Dimension {
sky_color: None,
fog_color: None,
cloud_color: None,
ambient_light_color: Some(3156001i32),
sky_light_color: Some(8026879i32),
sky_light_factor: Some(0f32),
fog_start_distance: Some(10f32),
fog_end_distance: Some(96f32),
cloud_height: None,
sky_light_level: Some(4f32),
water_evaporates: true,
fast_lava: true,
respawn_anchor_works: true,
piglins_zombify: false,
snow_golem_melts: true,
can_start_raid: false,
nether_portal_spawns_piglin: false,
bed_rule: BedRule {
can_sleep: BedRuleOption::Never,
can_set_spawn: BedRuleOption::Never,
explodes: true,
},
timelines: Some("#minecraft:in_nether"),
};
pub fn from_name(name: &str) -> Option<&'static Self> {
@ -120,6 +248,17 @@ impl Dimension {
_ => None,
}
}
#[inline]
#[must_use]
pub const fn effective_sky_light_level(&self) -> f32 {
if let Some(level) = self.sky_light_level {
level
} else if self.has_skylight {
15.0
} else {
0.0
}
}
}
impl PartialEq for Dimension {
fn eq(&self, other: &Self) -> bool {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,855 @@
/* This file is generated. Do not edit manually. */
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TrialSpawnerConfigData {
pub spawn_range: i32,
pub total_mobs: f32,
pub simultaneous_mobs: f32,
pub total_mobs_added_per_player: f32,
pub simultaneous_mobs_added_per_player: f32,
pub ticks_between_spawn: i32,
pub spawn_potentials: &'static [WeightedSpawnData],
pub loot_tables_to_eject: &'static [WeightedLootTable],
pub items_to_drop_when_ominous: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WeightedSpawnData {
pub entity_id: &'static str,
pub equipment_loot_table: Option<&'static str>,
pub equipment_slot_drop_chances: Option<f32>,
pub weight: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WeightedLootTable {
pub loot_table: &'static str,
pub weight: i32,
}
pub const TRIAL_CHAMBER_BREEZE_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 2f32,
simultaneous_mobs: 1f32,
total_mobs_added_per_player: 1f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:breeze",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_BREEZE_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 4f32,
simultaneous_mobs: 2f32,
total_mobs_added_per_player: 1f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:breeze",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_HUSK_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:husk",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_HUSK_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:husk",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_melee"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_SPIDER_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:spider",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_SPIDER_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 12f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:spider",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_ZOMBIE_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:zombie",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_MELEE_ZOMBIE_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:zombie",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_melee"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_POISON_SKELETON_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:bogged",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_POISON_SKELETON_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:bogged",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_SKELETON_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:skeleton",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_SKELETON_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:skeleton",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_STRAY_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:stray",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_RANGED_STRAY_OMINOUS: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:stray",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:bogged",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:bogged",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_SKELETON_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:skeleton",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_SKELETON_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:skeleton",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_STRAY_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:stray",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SLOW_RANGED_STRAY_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 2f32,
ticks_between_spawn: 160i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:stray",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_ranged"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 2f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:zombie",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 2f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:zombie",
equipment_loot_table: Some("minecraft:equipment/trial_chamber_melee"),
equipment_slot_drop_chances: Some(0f32),
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:cave_spider",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 12f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:cave_spider",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_NORMAL: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:silverfish",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 12f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[WeightedSpawnData {
entity_id: "minecraft:silverfish",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
}],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_SLIME_NORMAL: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 6f32,
simultaneous_mobs: 3f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[
WeightedSpawnData {
entity_id: "minecraft:slime",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 3i32,
},
WeightedSpawnData {
entity_id: "minecraft:slime",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
},
],
loot_tables_to_eject: &[],
items_to_drop_when_ominous: None,
};
pub const TRIAL_CHAMBER_SMALL_MELEE_SLIME_OMINOUS: TrialSpawnerConfigData =
TrialSpawnerConfigData {
spawn_range: 4i32,
total_mobs: 12f32,
simultaneous_mobs: 4f32,
total_mobs_added_per_player: 2f32,
simultaneous_mobs_added_per_player: 0.5f32,
ticks_between_spawn: 20i32,
spawn_potentials: &[
WeightedSpawnData {
entity_id: "minecraft:slime",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 3i32,
},
WeightedSpawnData {
entity_id: "minecraft:slime",
equipment_loot_table: None,
equipment_slot_drop_chances: None,
weight: 1i32,
},
],
loot_tables_to_eject: &[
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/key",
weight: 3i32,
},
WeightedLootTable {
loot_table: "minecraft:spawners/ominous/trial_chamber/consumables",
weight: 7i32,
},
],
items_to_drop_when_ominous: None,
};
#[must_use]
pub fn get_trial_spawner_config(key: &str) -> Option<&'static TrialSpawnerConfigData> {
match key {
"trial_chamber/breeze/normal" | "minecraft:trial_chamber/breeze/normal" => {
Some(&TRIAL_CHAMBER_BREEZE_NORMAL)
}
"trial_chamber/breeze/ominous" | "minecraft:trial_chamber/breeze/ominous" => {
Some(&TRIAL_CHAMBER_BREEZE_OMINOUS)
}
"trial_chamber/melee/husk/normal" | "minecraft:trial_chamber/melee/husk/normal" => {
Some(&TRIAL_CHAMBER_MELEE_HUSK_NORMAL)
}
"trial_chamber/melee/husk/ominous" | "minecraft:trial_chamber/melee/husk/ominous" => {
Some(&TRIAL_CHAMBER_MELEE_HUSK_OMINOUS)
}
"trial_chamber/melee/spider/normal" | "minecraft:trial_chamber/melee/spider/normal" => {
Some(&TRIAL_CHAMBER_MELEE_SPIDER_NORMAL)
}
"trial_chamber/melee/spider/ominous" | "minecraft:trial_chamber/melee/spider/ominous" => {
Some(&TRIAL_CHAMBER_MELEE_SPIDER_OMINOUS)
}
"trial_chamber/melee/zombie/normal" | "minecraft:trial_chamber/melee/zombie/normal" => {
Some(&TRIAL_CHAMBER_MELEE_ZOMBIE_NORMAL)
}
"trial_chamber/melee/zombie/ominous" | "minecraft:trial_chamber/melee/zombie/ominous" => {
Some(&TRIAL_CHAMBER_MELEE_ZOMBIE_OMINOUS)
}
"trial_chamber/ranged/poison_skeleton/normal"
| "minecraft:trial_chamber/ranged/poison_skeleton/normal" => {
Some(&TRIAL_CHAMBER_RANGED_POISON_SKELETON_NORMAL)
}
"trial_chamber/ranged/poison_skeleton/ominous"
| "minecraft:trial_chamber/ranged/poison_skeleton/ominous" => {
Some(&TRIAL_CHAMBER_RANGED_POISON_SKELETON_OMINOUS)
}
"trial_chamber/ranged/skeleton/normal"
| "minecraft:trial_chamber/ranged/skeleton/normal" => {
Some(&TRIAL_CHAMBER_RANGED_SKELETON_NORMAL)
}
"trial_chamber/ranged/skeleton/ominous"
| "minecraft:trial_chamber/ranged/skeleton/ominous" => {
Some(&TRIAL_CHAMBER_RANGED_SKELETON_OMINOUS)
}
"trial_chamber/ranged/stray/normal" | "minecraft:trial_chamber/ranged/stray/normal" => {
Some(&TRIAL_CHAMBER_RANGED_STRAY_NORMAL)
}
"trial_chamber/ranged/stray/ominous" | "minecraft:trial_chamber/ranged/stray/ominous" => {
Some(&TRIAL_CHAMBER_RANGED_STRAY_OMINOUS)
}
"trial_chamber/slow_ranged/poison_skeleton/normal"
| "minecraft:trial_chamber/slow_ranged/poison_skeleton/normal" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_NORMAL)
}
"trial_chamber/slow_ranged/poison_skeleton/ominous"
| "minecraft:trial_chamber/slow_ranged/poison_skeleton/ominous" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_OMINOUS)
}
"trial_chamber/slow_ranged/skeleton/normal"
| "minecraft:trial_chamber/slow_ranged/skeleton/normal" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_SKELETON_NORMAL)
}
"trial_chamber/slow_ranged/skeleton/ominous"
| "minecraft:trial_chamber/slow_ranged/skeleton/ominous" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_SKELETON_OMINOUS)
}
"trial_chamber/slow_ranged/stray/normal"
| "minecraft:trial_chamber/slow_ranged/stray/normal" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_STRAY_NORMAL)
}
"trial_chamber/slow_ranged/stray/ominous"
| "minecraft:trial_chamber/slow_ranged/stray/ominous" => {
Some(&TRIAL_CHAMBER_SLOW_RANGED_STRAY_OMINOUS)
}
"trial_chamber/small_melee/baby_zombie/normal"
| "minecraft:trial_chamber/small_melee/baby_zombie/normal" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_NORMAL)
}
"trial_chamber/small_melee/baby_zombie/ominous"
| "minecraft:trial_chamber/small_melee/baby_zombie/ominous" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_OMINOUS)
}
"trial_chamber/small_melee/cave_spider/normal"
| "minecraft:trial_chamber/small_melee/cave_spider/normal" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_NORMAL)
}
"trial_chamber/small_melee/cave_spider/ominous"
| "minecraft:trial_chamber/small_melee/cave_spider/ominous" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_OMINOUS)
}
"trial_chamber/small_melee/silverfish/normal"
| "minecraft:trial_chamber/small_melee/silverfish/normal" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_NORMAL)
}
"trial_chamber/small_melee/silverfish/ominous"
| "minecraft:trial_chamber/small_melee/silverfish/ominous" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_OMINOUS)
}
"trial_chamber/small_melee/slime/normal"
| "minecraft:trial_chamber/small_melee/slime/normal" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_SLIME_NORMAL)
}
"trial_chamber/small_melee/slime/ominous"
| "minecraft:trial_chamber/small_melee/slime/ominous" => {
Some(&TRIAL_CHAMBER_SMALL_MELEE_SLIME_OMINOUS)
}
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TrialSpawnerKey {
Breeze,
MeleeHusk,
MeleeSpider,
MeleeZombie,
RangedPoisonSkeleton,
RangedSkeleton,
RangedStray,
SlowRangedPoisonSkeleton,
SlowRangedSkeleton,
SlowRangedStray,
SmallMeleeBabyZombie,
SmallMeleeCaveSpider,
SmallMeleeSilverfish,
SmallMeleeSlime,
}
impl TrialSpawnerKey {
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Breeze => "trial_chamber/breeze",
Self::MeleeHusk => "trial_chamber/melee/husk",
Self::MeleeSpider => "trial_chamber/melee/spider",
Self::MeleeZombie => "trial_chamber/melee/zombie",
Self::RangedPoisonSkeleton => "trial_chamber/ranged/poison_skeleton",
Self::RangedSkeleton => "trial_chamber/ranged/skeleton",
Self::RangedStray => "trial_chamber/ranged/stray",
Self::SlowRangedPoisonSkeleton => "trial_chamber/slow_ranged/poison_skeleton",
Self::SlowRangedSkeleton => "trial_chamber/slow_ranged/skeleton",
Self::SlowRangedStray => "trial_chamber/slow_ranged/stray",
Self::SmallMeleeBabyZombie => "trial_chamber/small_melee/baby_zombie",
Self::SmallMeleeCaveSpider => "trial_chamber/small_melee/cave_spider",
Self::SmallMeleeSilverfish => "trial_chamber/small_melee/silverfish",
Self::SmallMeleeSlime => "trial_chamber/small_melee/slime",
}
}
#[must_use]
pub const fn normal_config(&self) -> &'static TrialSpawnerConfigData {
match self {
Self::Breeze => &TRIAL_CHAMBER_BREEZE_NORMAL,
Self::MeleeHusk => &TRIAL_CHAMBER_MELEE_HUSK_NORMAL,
Self::MeleeSpider => &TRIAL_CHAMBER_MELEE_SPIDER_NORMAL,
Self::MeleeZombie => &TRIAL_CHAMBER_MELEE_ZOMBIE_NORMAL,
Self::RangedPoisonSkeleton => &TRIAL_CHAMBER_RANGED_POISON_SKELETON_NORMAL,
Self::RangedSkeleton => &TRIAL_CHAMBER_RANGED_SKELETON_NORMAL,
Self::RangedStray => &TRIAL_CHAMBER_RANGED_STRAY_NORMAL,
Self::SlowRangedPoisonSkeleton => &TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_NORMAL,
Self::SlowRangedSkeleton => &TRIAL_CHAMBER_SLOW_RANGED_SKELETON_NORMAL,
Self::SlowRangedStray => &TRIAL_CHAMBER_SLOW_RANGED_STRAY_NORMAL,
Self::SmallMeleeBabyZombie => &TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_NORMAL,
Self::SmallMeleeCaveSpider => &TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_NORMAL,
Self::SmallMeleeSilverfish => &TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_NORMAL,
Self::SmallMeleeSlime => &TRIAL_CHAMBER_SMALL_MELEE_SLIME_NORMAL,
}
}
#[must_use]
pub const fn ominous_config(&self) -> &'static TrialSpawnerConfigData {
match self {
Self::Breeze => &TRIAL_CHAMBER_BREEZE_OMINOUS,
Self::MeleeHusk => &TRIAL_CHAMBER_MELEE_HUSK_OMINOUS,
Self::MeleeSpider => &TRIAL_CHAMBER_MELEE_SPIDER_OMINOUS,
Self::MeleeZombie => &TRIAL_CHAMBER_MELEE_ZOMBIE_OMINOUS,
Self::RangedPoisonSkeleton => &TRIAL_CHAMBER_RANGED_POISON_SKELETON_OMINOUS,
Self::RangedSkeleton => &TRIAL_CHAMBER_RANGED_SKELETON_OMINOUS,
Self::RangedStray => &TRIAL_CHAMBER_RANGED_STRAY_OMINOUS,
Self::SlowRangedPoisonSkeleton => &TRIAL_CHAMBER_SLOW_RANGED_POISON_SKELETON_OMINOUS,
Self::SlowRangedSkeleton => &TRIAL_CHAMBER_SLOW_RANGED_SKELETON_OMINOUS,
Self::SlowRangedStray => &TRIAL_CHAMBER_SLOW_RANGED_STRAY_OMINOUS,
Self::SmallMeleeBabyZombie => &TRIAL_CHAMBER_SMALL_MELEE_BABY_ZOMBIE_OMINOUS,
Self::SmallMeleeCaveSpider => &TRIAL_CHAMBER_SMALL_MELEE_CAVE_SPIDER_OMINOUS,
Self::SmallMeleeSilverfish => &TRIAL_CHAMBER_SMALL_MELEE_SILVERFISH_OMINOUS,
Self::SmallMeleeSlime => &TRIAL_CHAMBER_SMALL_MELEE_SLIME_OMINOUS,
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name {
"trial_chamber/breeze" | "minecraft:trial_chamber/breeze" => Some(Self::Breeze),
"trial_chamber/melee/husk" | "minecraft:trial_chamber/melee/husk" => {
Some(Self::MeleeHusk)
}
"trial_chamber/melee/spider" | "minecraft:trial_chamber/melee/spider" => {
Some(Self::MeleeSpider)
}
"trial_chamber/melee/zombie" | "minecraft:trial_chamber/melee/zombie" => {
Some(Self::MeleeZombie)
}
"trial_chamber/ranged/poison_skeleton"
| "minecraft:trial_chamber/ranged/poison_skeleton" => Some(Self::RangedPoisonSkeleton),
"trial_chamber/ranged/skeleton" | "minecraft:trial_chamber/ranged/skeleton" => {
Some(Self::RangedSkeleton)
}
"trial_chamber/ranged/stray" | "minecraft:trial_chamber/ranged/stray" => {
Some(Self::RangedStray)
}
"trial_chamber/slow_ranged/poison_skeleton"
| "minecraft:trial_chamber/slow_ranged/poison_skeleton" => {
Some(Self::SlowRangedPoisonSkeleton)
}
"trial_chamber/slow_ranged/skeleton"
| "minecraft:trial_chamber/slow_ranged/skeleton" => Some(Self::SlowRangedSkeleton),
"trial_chamber/slow_ranged/stray" | "minecraft:trial_chamber/slow_ranged/stray" => {
Some(Self::SlowRangedStray)
}
"trial_chamber/small_melee/baby_zombie"
| "minecraft:trial_chamber/small_melee/baby_zombie" => Some(Self::SmallMeleeBabyZombie),
"trial_chamber/small_melee/cave_spider"
| "minecraft:trial_chamber/small_melee/cave_spider" => Some(Self::SmallMeleeCaveSpider),
"trial_chamber/small_melee/silverfish"
| "minecraft:trial_chamber/small_melee/silverfish" => Some(Self::SmallMeleeSilverfish),
"trial_chamber/small_melee/slime" | "minecraft:trial_chamber/small_melee/slime" => {
Some(Self::SmallMeleeSlime)
}
_ => None,
}
}
}

View File

@ -179,6 +179,14 @@ mod spawn_egg;
#[path = "generated/dimension.rs"]
pub mod dimension;
#[cfg(feature = "environment_attribute")]
#[rustfmt::skip]
#[path = "generated/environment_attribute.rs"]
pub mod environment_attribute;
#[cfg(feature = "environment_attribute")]
pub use environment_attribute::*;
#[cfg(feature = "enchantment")]
#[rustfmt::skip]
#[path = "generated/enchantment.rs"]
@ -456,3 +464,8 @@ pub mod dye_color;
#[rustfmt::skip]
#[path = "generated/block_transformer.rs"]
pub mod block_transformer;
#[cfg(feature = "trial_spawner")]
#[rustfmt::skip]
#[path = "generated/trial_spawner.rs"]
pub mod trial_spawner;

@ -1 +1 @@
Subproject commit f481f5677dd0c36788288378b7b1f650ed2266e9
Subproject commit 1ad73fff1e0a9e21b99255816df5f99f6260c1b9

View File

@ -55,8 +55,8 @@ impl WorldPortalExt for BlockRegistry {
}
}
fn make_world_gen() -> Box<WorldGenerator> {
get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
fn make_world_gen() -> WorldGenerator {
*get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
}
fn setup_cache(

View File

@ -259,12 +259,12 @@ mod test {
#[test]
fn flat_world_has_a_single_fixed_biome() {
let world_gen = WorldGenerator::Flat(FlatGenerator::new(
let world_gen = WorldGenerator::Flat(Box::new(FlatGenerator::new(
Seed(0),
Dimension::OVERWORLD,
Vec::new(),
"minecraft:plains".to_string(),
));
)));
let origin = BlockPos::new(17, 64, -3);
let (pos, biome) = find_closest_biome_3d(

View File

@ -79,7 +79,7 @@ pub trait CustomChunkGenerator: Send + Sync {
pub enum WorldGenerator {
Noise(Box<VanillaGenerator>),
Flat(flat::FlatGenerator),
Flat(Box<flat::FlatGenerator>),
Custom(Arc<dyn CustomChunkGenerator>),
}

View File

@ -56,7 +56,7 @@ impl UniformHeightProvider {
let max = self.max_inclusive.get_y(min_y as i16, height);
if min > max {
warn!("Empty height range: min={min}, max={max}");
// TODO: investigate why min > max occurs (e.g. min=136, max=127)
return min;
}

View File

@ -34,9 +34,9 @@ pub fn get_world_gen(
flat_biome: String,
) -> Box<generator::WorldGenerator> {
if is_flat {
Box::new(generator::WorldGenerator::Flat(
Box::new(generator::WorldGenerator::Flat(Box::new(
generator::flat::FlatGenerator::new(seed, dimension, flat_layers, flat_biome),
))
)))
} else {
Box::new(generator::WorldGenerator::Noise(Box::new(
VanillaGenerator::new(seed, dimension),

View File

@ -2,7 +2,6 @@ use std::sync::Arc;
use crate::block::entities::bed::BedBlockEntity;
use pumpkin_data::block_properties::BedPart;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::translation;
use pumpkin_data::{Block, BlockState, BlockStateId};
@ -213,8 +212,8 @@ impl BedBlock {
(position.offset(bed_props.facing.to_offset()), *position)
};
// Explode if not in the overworld
if world.dimension != Dimension::OVERWORLD {
// Explode if bed rule explodes (EnvironmentAttributes.BED_RULE)
if world.dimension.bed_rule.explodes {
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
@ -227,6 +226,21 @@ impl BedBlock {
return BlockActionResult::SuccessServer;
}
let is_dark = world.is_dark_outside();
let can_sleep = world.dimension.bed_rule.can_sleep(is_dark);
let can_set_spawn = world.dimension.bed_rule.can_set_spawn(is_dark);
if !can_set_spawn && !can_sleep {
player.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
translation::bedrock::TILE_BED_NOSLEEP
),
true,
);
return BlockActionResult::SuccessServer;
}
// Make sure the bed is not obstructed
if world.get_block_state(&bed_head_pos.up()).is_solid()
|| world.get_block_state(&bed_foot_pos.up()).is_solid()
@ -274,13 +288,15 @@ impl BedBlock {
}
// Set respawn point
if player.set_respawn_point(
if can_set_spawn
&& player.set_respawn_point(
world.dimension.clone(),
bed_head_pos,
player.get_entity().yaw.load(),
player.get_entity().pitch.load(),
false,
) {
)
{
player.send_system_message(&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
translation::bedrock::TILE_BED_RESPAWNSET
@ -288,7 +304,7 @@ impl BedBlock {
}
// Make sure the time and weather allows sleep
if !can_sleep(world) {
if !can_sleep {
player.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
@ -381,25 +397,6 @@ impl BedBlock {
}
}
fn can_sleep(world: &Arc<World>) -> bool {
let time = world
.level_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let weather = world
.weather
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if weather.thundering {
true
} else if weather.raining {
time.time_of_day > 12010 && time.time_of_day < 23991
} else {
time.time_of_day > 12542 && time.time_of_day < 23459
}
}
fn entity_prevents_sleep(entity: &Entity) -> bool {
NO_SLEEP_IDS.contains(&entity.entity_type.id)
}

View File

@ -1,6 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, PathComputationType, RandomTickArgs, UseWithItemArgs};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::flower_pot_transformations::get_potted_item;
use pumpkin_data::{Block, BlockId, BlockState};
use pumpkin_macros::pumpkin_block_from_tag;
@ -45,23 +44,24 @@ impl BlockBehaviour for FlowerPotBlock {
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
&& args.world.get_time_of_day() % 24000 > 14500
{
args.world.set_block_state(
args.position,
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
BlockFlags::NOTIFY_ALL,
);
let is_open_potted = args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM);
let is_closed_potted = args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM);
if !is_open_potted && !is_closed_potted {
return;
}
if args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM)
&& args.world.get_time_of_day() % 24000 <= 14500
{
let is_open = is_open_potted;
let should_be_open = args.world.eyeblossom_open(args.position).unwrap_or(is_open);
if is_open != should_be_open {
let next_block = if should_be_open {
&Block::POTTED_OPEN_EYEBLOSSOM
} else {
&Block::POTTED_CLOSED_EYEBLOSSOM
};
args.world.set_block_state(
args.position,
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
next_block.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}

View File

@ -14,9 +14,9 @@ use crate::block::{
};
use crate::world::World;
/// Melts ice at the given position into water (or removes it in ultrawarm dimensions like the Nether).
/// Melts ice at the given position into water (or removes it where water evaporates).
pub fn melt(world: &Arc<World>, position: &BlockPos) {
if world.dimension == Dimension::THE_NETHER {
if world.dimension.water_evaporates {
world.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
} else {
world.set_block_state(
@ -68,7 +68,7 @@ impl BlockBehaviour for IceBlock {
let held_item = args.player.inventory().held_item();
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
if !has_silk_touch {
if args.world.dimension == Dimension::THE_NETHER {
if args.world.dimension.water_evaporates {
args.world.set_block_state(
args.position,
BlockStateId::AIR,

View File

@ -73,8 +73,7 @@ impl BlockBehaviour for NetherPortalBlock {
let difficulty = level_info.difficulty;
if !level_info.game_rules.spawn_mobs
|| difficulty == Difficulty::Peaceful
|| (args.world.dimension != Dimension::OVERWORLD
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
|| !args.world.dimension.nether_portal_spawns_piglin
{
return;
}

View File

@ -2,7 +2,6 @@ use std::sync::Arc;
use pumpkin_data::{
Block, BlockId, BlockStateId,
dimension::Dimension,
effect::StatusEffect,
entity::EntityType,
particle::Particle,
@ -117,17 +116,7 @@ impl PlantBlockBase for EyeblossomBlock {}
pub fn try_changing_state(world: &Arc<World>, current_block: &Block, pos: &BlockPos) -> bool {
let is_open = current_block == &Block::OPEN_EYEBLOSSOM;
let should_be_open = if world.dimension == Dimension::OVERWORLD
|| world.dimension == Dimension::OVERWORLD_CAVES
{
world
.level_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_night()
} else {
is_open
};
let should_be_open = world.eyeblossom_open(pos).unwrap_or(is_open);
if should_be_open == is_open {
return false;

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::block::entities::daylight_detector::DaylightDetectorBlockEntity;
use pumpkin_data::Block;
use pumpkin_data::game_event::GameEvent;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags;
@ -38,11 +38,18 @@ impl BlockBehaviour for DaylightDetectorBlock {
}
let state = args.world.get_block_state(args.position);
let props = DaylightDetectorProperties::from_state_id(state.id);
let mut props = DaylightDetectorProperties::from_state_id(state.id);
props.inverted = !props.inverted;
Self::update_inverted(props, args.world, args.position, args.block);
let new_state = props.to_state_id(args.block);
args.world
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_LISTENERS);
args.world.emit_game_event(
GameEvent::BlockChange.name(),
args.position.to_centered_f64(),
);
DaylightDetectorBlockEntity::update_power(args.world, args.position);
Self::update_signal_strength(args.world, args.position);
BlockActionResult::Success
}
@ -58,16 +65,43 @@ impl BlockBehaviour for DaylightDetectorBlock {
}
impl DaylightDetectorBlock {
fn update_inverted(
mut props: DaylightDetectorProperties,
world: &Arc<World>,
block_pos: &BlockPos,
block: &Block,
) {
props.inverted = !props.inverted;
#[must_use]
pub fn calculate_signal_strength(
effective_sky_brightness: i32,
sun_angle_radians: f32,
is_inverted: bool,
) -> u8 {
let mut target = effective_sky_brightness;
let mut sun_angle = sun_angle_radians;
if is_inverted {
target = 15 - target;
} else if target > 0 {
let offset = if sun_angle < std::f32::consts::PI {
0.0
} else {
std::f32::consts::PI * 2.0
};
sun_angle += (offset - sun_angle) * 0.2;
target = ((target as f32 * sun_angle.cos()) + 0.5).floor() as i32;
}
let state = props.to_state_id(block);
target.clamp(0, 15) as u8
}
world.set_block_state(block_pos, state, BlockFlags::NOTIFY_LISTENERS);
pub fn update_signal_strength(world: &Arc<World>, block_pos: &BlockPos) {
let (block, state) = world.get_block_and_state(block_pos);
let mut props = DaylightDetectorProperties::from_state_id(state.id);
let target = Self::calculate_signal_strength(
world.get_effective_sky_brightness(block_pos),
world.get_sun_angle(block_pos),
props.inverted,
);
if props.power != target {
props.power = target;
let new_state = props.to_state_id(block);
world.set_block_state(block_pos, new_state, BlockFlags::NOTIFY_ALL);
}
}
}

View File

@ -1,5 +1,4 @@
use pumpkin_data::block_properties::RespawnAnchorLikeProperties;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::item::Item;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::{BlockState, translation};
@ -49,7 +48,7 @@ impl BlockBehaviour for RespawnAnchorBlock {
let state_id = args.world.get_block_state_id(args.position);
let props = RespawnAnchorLikeProperties::from_state_id(state_id);
if args.world.dimension != Dimension::THE_NETHER {
if !args.world.dimension.respawn_anchor_works {
args.world
.break_block(args.position, None, BlockFlags::SKIP_DROPS);
let center_pos = args.position.to_centered_f64();

View File

@ -4,7 +4,6 @@ use std::collections::{HashSet, VecDeque};
use std::sync::Arc;
use crate::block::{BlockBehaviour, OnNeighborUpdateArgs, PlacedArgs};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::particle::Particle;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::{Block, BlockStateId};
@ -112,8 +111,8 @@ pub struct WetSpongeBlock;
impl BlockBehaviour for WetSpongeBlock {
fn placed(&self, args: PlacedArgs<'_>) {
{
// Check if placed in Nether, if so, dry out
if args.world.dimension == Dimension::THE_NETHER {
// Check if placed where water evaporates (EnvironmentAttributes.WATER_EVAPORATES), if so, dry out
if args.world.dimension.water_evaporates {
args.world.set_block_state(
args.position,
Block::SPONGE.default_state.id,

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use pumpkin_macros::pumpkin_block;
use crate::block::entities::trial_spawner::TrialSpawnerBlockEntity;
use crate::block::{BlockBehaviour, PlacedArgs};
use crate::block::{BlockBehaviour, BrokenArgs, PlacedArgs};
#[pumpkin_block("minecraft:trial_spawner")]
pub struct TrialSpawnerBlock;
@ -13,4 +13,8 @@ impl BlockBehaviour for TrialSpawnerBlock {
let entity = TrialSpawnerBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
}
fn broken(&self, args: BrokenArgs<'_>) {
args.world.remove_block_entity(args.position);
}
}

View File

@ -3,12 +3,11 @@ use std::sync::Arc;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos;
use crate::world::{BlockFlags, World};
use crate::block::blocks::redstone::daylight_detector::DaylightDetectorBlock;
use crate::world::World;
use super::BlockEntity;
type DaylightDetectorProperties = pumpkin_data::block_properties::DaylightDetectorLikeProperties;
pub struct DaylightDetectorBlockEntity {
pub position: BlockPos,
}
@ -37,7 +36,7 @@ impl BlockEntity for DaylightDetectorBlockEntity {
fn tick(&self, world: &Arc<World>) {
if world.get_world_age() % 20 == 0 && world.dimension.has_skylight {
Self::update_power(world, &self.position);
DaylightDetectorBlock::update_signal_strength(world, &self.position);
}
}
}
@ -51,65 +50,6 @@ impl DaylightDetectorBlockEntity {
}
pub fn update_power(world: &Arc<World>, block_pos: &BlockPos) {
use std::f32::consts::PI;
let (block, state) = world.get_block_and_state(block_pos);
let mut props = DaylightDetectorProperties::from_state_id(state.id);
let level = world.level.clone();
let inverted = props.inverted;
// TODO: finish power signal calculation
// see: https://minecraft.wiki/w/Light#Internal_sky_light
/*
* THIS IS A PLACE HOLDER
*
* For now the light engine is not finished and we do not have access to such things as
* getAmbientDarkness() or EnvironmentAttributes.SUN_ANGLE_VISUAL as in java code from yarn:
* see: net.minecraft.blocks.DaylightDetectorBlock.java
*
* so this is a fake implementation using the time of day...
* is not as accurate as vanilla and doesnt consider weather
*/
let time_of_day = world.get_time_of_day();
// Sun Angle
let sun_angle_fraction = (time_of_day as f32 / 24000.0) - 0.25;
let mut sun_angle_radians = sun_angle_fraction * (PI * 2.0);
// Ambient darkness (considering only night, not weather)
let cos_angle = sun_angle_radians.cos();
let ambient_darkness = if cos_angle < 0.0 {
// night time = ~11 darkness
(cos_angle.abs() * 11.0) as u8
} else {
0 // full daylight
};
let sky_light_level = level.light_engine.get_sky_light_level(&level, block_pos);
let mut power = sky_light_level - ambient_darkness;
if inverted {
power = 15 - power;
} else if power > 0 {
let transition_offset = if sun_angle_radians < PI {
0.0
} else {
PI * 2.0
};
sun_angle_radians += (transition_offset - sun_angle_radians) * 0.2;
power = (power as f32 * sun_angle_radians.cos()).round() as u8;
}
let power = power.clamp(0, 15);
if power != props.power {
props.power = power;
let state = props.to_state_id(block);
world.set_block_state(block_pos, state, BlockFlags::NOTIFY_ALL);
}
DaylightDetectorBlock::update_signal_strength(world, block_pos);
}
}

View File

@ -2,10 +2,14 @@ use rustc_hash::FxHashSet;
use std::sync::{Arc, Mutex};
use pumpkin_data::block_properties::{TrialSpawnerLikeProperties, TrialSpawnerState};
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::effect::StatusEffect;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::potion::Effect;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::trial_spawner::TrialSpawnerKey;
use pumpkin_data::world::WorldEvent;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
@ -203,7 +207,91 @@ impl Default for TrialSpawnerConfig {
}
}
impl From<&pumpkin_data::trial_spawner::TrialSpawnerConfigData> for TrialSpawnerConfig {
fn from(data: &pumpkin_data::trial_spawner::TrialSpawnerConfigData) -> Self {
let spawn_potentials = data
.spawn_potentials
.iter()
.map(|sp| {
let entity_type = EntityType::from_name(
sp.entity_id
.strip_prefix("minecraft:")
.unwrap_or(sp.entity_id),
);
let equipment = sp.equipment_loot_table.map(|loot_table| {
let mut cmp = NbtCompound::new();
cmp.put_string("loot_table", loot_table.to_string());
if let Some(chances) = sp.equipment_slot_drop_chances {
cmp.put_float("slot_drop_chances", chances);
}
cmp
});
WeightedSpawnData {
data: SpawnData {
entity_type,
custom_spawn_rules: None,
equipment,
raw_entity_nbt: None,
},
weight: sp.weight,
}
})
.collect();
let loot_tables_to_eject = data
.loot_tables_to_eject
.iter()
.map(|lt| WeightedLootTable {
data: lt.loot_table.to_string(),
weight: lt.weight,
})
.collect();
Self {
spawn_range: data.spawn_range,
total_mobs: data.total_mobs,
simultaneous_mobs: data.simultaneous_mobs,
total_mobs_added_per_player: data.total_mobs_added_per_player,
simultaneous_mobs_added_per_player: data.simultaneous_mobs_added_per_player,
ticks_between_spawn: data.ticks_between_spawn,
spawn_potentials,
loot_tables_to_eject,
items_to_drop_when_ominous: data.items_to_drop_when_ominous.map(ToString::to_string),
}
}
}
impl TrialSpawnerConfig {
#[must_use]
pub fn from_key(key: &str) -> Option<Self> {
if let Some(cfg) = pumpkin_data::trial_spawner::get_trial_spawner_config(key) {
return Some(Self::from(cfg));
}
if let Some(trial_key) = TrialSpawnerKey::from_name(key) {
return Some(Self::from(trial_key.normal_config()));
}
let with_normal = format!("{key}/normal");
if let Some(cfg) = pumpkin_data::trial_spawner::get_trial_spawner_config(&with_normal) {
return Some(Self::from(cfg));
}
None
}
#[must_use]
pub fn from_ominous_key(key: &str) -> Option<Self> {
if let Some(cfg) = pumpkin_data::trial_spawner::get_trial_spawner_config(key) {
return Some(Self::from(cfg));
}
if let Some(trial_key) = TrialSpawnerKey::from_name(key) {
return Some(Self::from(trial_key.ominous_config()));
}
let with_ominous = format!("{key}/ominous");
if let Some(cfg) = pumpkin_data::trial_spawner::get_trial_spawner_config(&with_ominous) {
return Some(Self::from(cfg));
}
None
}
#[must_use]
pub fn calculate_target_total_mobs(&self, player_count: usize) -> i32 {
let additional = player_count.saturating_sub(1) as f32 * self.total_mobs_added_per_player;
@ -419,6 +507,16 @@ impl Default for TrialSpawnerFullConfig {
}
impl TrialSpawnerFullConfig {
#[must_use]
pub fn from_key(key: &pumpkin_data::trial_spawner::TrialSpawnerKey) -> Self {
Self {
normal: TrialSpawnerConfig::from(key.normal_config()),
ominous: TrialSpawnerConfig::from(key.ominous_config()),
target_cooldown_length: 36000,
required_player_range: 14,
}
}
#[must_use]
pub fn override_entity(&self, entity_type: &'static EntityType) -> Self {
Self {
@ -431,13 +529,27 @@ impl TrialSpawnerFullConfig {
#[must_use]
pub fn from_nbt(nbt: &NbtCompound) -> Self {
if let Some(key_str) = nbt.get_string("key").or_else(|| nbt.get_string("config"))
&& let Some(trial_key) = TrialSpawnerKey::from_name(key_str)
{
return Self::from_key(&trial_key);
}
let normal = nbt
.get_compound("normal_config")
.map(TrialSpawnerConfig::from_nbt)
.or_else(|| {
nbt.get_string("normal_config")
.and_then(TrialSpawnerConfig::from_key)
})
.unwrap_or_default();
let ominous = nbt
.get_compound("ominous_config")
.map(TrialSpawnerConfig::from_nbt)
.or_else(|| {
nbt.get_string("ominous_config")
.and_then(TrialSpawnerConfig::from_ominous_key)
})
.unwrap_or_default();
let target_cooldown_length = nbt
@ -713,7 +825,78 @@ impl TrialSpawner {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.query_gametime();
for &id in &self.data.current_mobs {
if let Some(entity) = world.get_entity_by_uuid(id) {
let ent = entity.get_entity();
let block_pos =
BlockPos::floored(ent.pos.load().x, ent.pos.load().y, ent.pos.load().z);
world.sync_world_event(
WorldEvent::ParticlesTrialSpawnerSpawnMobAt,
block_pos,
TrialSpawnerFlameParticle::Normal.encode(),
);
world.remove_entity(entity.as_ref());
}
}
self.data.reset_after_becoming_ominous(game_time);
self.data.next_mob_spawns_at =
game_time + i64::from(self.active_config().ticks_between_spawn);
}
pub fn try_detect_players(&mut self, world: &Arc<World>, spawner_pos: BlockPos) -> bool {
let detected =
self.player_detector
.detect(world, spawner_pos, self.config.required_player_range);
if !self.is_ominous {
let mut ominous_player = None;
for &uuid in &detected {
if let Some(player) = world.get_player_by_uuid(uuid) {
if player.has_effect(&StatusEffect::TRIAL_OMEN) {
ominous_player = Some((player, false));
break;
}
if player.has_effect(&StatusEffect::BAD_OMEN) {
ominous_player = Some((player, true));
}
}
}
if let Some((player, has_bad_omen)) = ominous_player {
if has_bad_omen && let Some(bad_omen) = player.get_effect(&StatusEffect::BAD_OMEN) {
let amplifier = i32::from(bad_omen.amplifier) + 1;
let duration = 18000 * amplifier;
player.remove_effect(&StatusEffect::BAD_OMEN);
player.add_effect(Effect {
effect_type: &StatusEffect::TRIAL_OMEN,
duration,
amplifier: 0,
ambient: false,
show_particles: true,
show_icon: true,
blend: true,
});
}
let eye_pos = player.eye_position();
let eye_block_pos = BlockPos::floored(eye_pos.x, eye_pos.y, eye_pos.z);
world.sync_world_event(
WorldEvent::ParticlesTrialSpawnerBecomeOminous,
eye_block_pos,
0,
);
self.apply_ominous(world, spawner_pos);
}
}
let mut newly_detected = false;
for uuid in detected {
if self.data.registered_players.insert(uuid) {
newly_detected = true;
}
}
newly_detected
}
pub fn remove_ominous(&mut self, world: &Arc<World>, spawner_pos: BlockPos) {
@ -793,8 +976,9 @@ impl TrialSpawner {
level_info.game_rules.spawn_mobs
}
#[allow(clippy::too_many_lines)]
pub fn spawn_mob(&mut self, world: &Arc<World>, spawner_pos: BlockPos) -> Option<Uuid> {
let (entity_type, spawn_range) = {
let (entity_type, spawn_range, equipment_loot_table, is_baby, slime_size) = {
let active_cfg = if self.is_ominous {
&self.config.ominous
} else {
@ -802,7 +986,25 @@ impl TrialSpawner {
};
let spawn_data = self.data.get_or_create_next_spawn_data(active_cfg);
let ent_type = spawn_data.and_then(|sd| sd.entity_type)?;
(ent_type, active_cfg.spawn_range)
let equip = spawn_data
.and_then(|sd| {
sd.equipment
.as_ref()
.and_then(|eq| eq.get_string("loot_table"))
})
.map(ToString::to_string);
let raw = spawn_data.and_then(|sd| sd.raw_entity_nbt.as_ref());
let baby = raw
.and_then(|r| {
r.get_bool("IsBaby")
.or_else(|| r.get_byte("IsBaby").map(|b| b != 0))
})
.unwrap_or(false);
let size = raw.and_then(|r| {
r.get_byte("Size")
.or_else(|| r.get_int("Size").map(|i| i as i8))
});
(ent_type, active_cfg.spawn_range, equip, baby, size)
};
let mut rng = rand::rng();
@ -840,6 +1042,40 @@ impl TrialSpawner {
let yaw = rng.random::<f32>() * 360.0;
entity.get_entity().set_rotation(yaw, 0.0);
if is_baby {
let ent = entity.get_entity();
ent.age.store(-24000, std::sync::atomic::Ordering::Relaxed);
ent.set_synced_data(pumpkin_data::tracked_data::ageable_mob::DATA_BABY_ID, true);
}
if let Some(size) = slime_size {
entity
.get_entity()
.set_synced_data(pumpkin_data::tracked_data::slime::ID_SIZE, i32::from(size));
}
if let Some(equip) = equipment_loot_table
&& let Some(loot_table) = pumpkin_data::loot_table::get_loot_table(&equip)
&& let Some(living) = entity.get_living_entity()
{
let seed = rand::random::<i64>();
let items = crate::world::loot::generate_loot(loot_table, seed);
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut equipment_changes = Vec::new();
for item in items {
let slot = item
.get_data_component::<pumpkin_data::data_component_impl::EquippableImpl>()
.map_or(EquipmentSlot::MAIN_HAND, |eq| eq.slot.clone());
equipment.put(&slot, item.clone());
equipment_changes.push((slot, item));
}
drop(equipment);
living.send_equipment_changes(&equipment_changes);
}
let mut event =
crate::plugin::api::events::entity::trial_spawner_spawn::TrialSpawnerSpawnEvent::new(
entity.get_entity().entity_id,
@ -975,12 +1211,7 @@ impl TrialSpawner {
return TrialSpawnerState::Inactive;
}
let newly_detected = self.data.try_detect_players(
world,
spawner_pos,
self.config.required_player_range,
&self.player_detector,
);
let newly_detected = self.try_detect_players(world, spawner_pos);
if self.data.registered_players.is_empty() {
TrialSpawnerState::WaitingForPlayers
@ -1013,12 +1244,7 @@ impl TrialSpawner {
return TrialSpawnerState::Inactive;
}
self.data.try_detect_players(
world,
spawner_pos,
self.config.required_player_range,
&self.player_detector,
);
self.try_detect_players(world, spawner_pos);
let (total_mobs_needed, max_simultaneous, ticks_between_spawn) = {
let active_config = self.active_config();
@ -1055,7 +1281,7 @@ impl TrialSpawner {
TrialSpawnerState::WaitingForRewardEjection => {
if game_time >= self.data.next_mob_spawns_at {
world.play_sound(
Sound::BlockTrialSpawnerSpawnItemBegin,
Sound::BlockTrialSpawnerOpenShutter,
SoundCategory::Blocks,
&spawner_pos.to_f64(),
);
@ -1067,12 +1293,18 @@ impl TrialSpawner {
}
TrialSpawnerState::EjectingReward => {
if game_time >= self.data.next_mob_spawns_at {
self.eject_reward(world, spawner_pos, self.data.ejecting_loot_table.as_deref());
if let Some(player_id) = self.data.registered_players.iter().copied().next() {
self.data.registered_players.remove(&player_id);
let loot_table = self.active_config().pick_loot_table();
self.eject_reward(world, spawner_pos, loot_table.as_deref());
world.play_sound(
Sound::BlockTrialSpawnerEjectItem,
SoundCategory::Blocks,
&spawner_pos.to_f64(),
);
self.data.next_mob_spawns_at = game_time + 20;
TrialSpawnerState::EjectingReward
} else {
world.play_sound(
Sound::BlockTrialSpawnerCloseShutter,
SoundCategory::Blocks,
@ -1080,11 +1312,21 @@ impl TrialSpawner {
);
self.data.next_mob_spawns_at = game_time + 20;
TrialSpawnerState::Cooldown
}
} else {
TrialSpawnerState::EjectingReward
}
}
TrialSpawnerState::Cooldown => {
if !self.is_ominous {
self.try_detect_players(world, spawner_pos);
if self.is_ominous {
self.data.total_mobs_spawned = 0;
self.data.next_mob_spawns_at = 0;
return TrialSpawnerState::Active;
}
}
if game_time >= self.data.cooldown_ends_at {
self.data.reset();
if self.is_ominous {
@ -1187,6 +1429,33 @@ impl TrialSpawnerBlockEntity {
trial_spawner: Mutex::new(TrialSpawner::default()),
}
}
#[must_use]
pub fn with_config(position: BlockPos, config: TrialSpawnerFullConfig) -> Self {
Self {
position,
trial_spawner: Mutex::new(TrialSpawner::new(config, PlayerDetector::default())),
}
}
#[must_use]
pub fn from_key(position: BlockPos, key: &TrialSpawnerKey) -> Self {
Self::with_config(position, TrialSpawnerFullConfig::from_key(key))
}
pub fn set_entity_type(&self, entity_type: &'static EntityType, world: &Arc<World>) {
if let Ok(mut spawner) = self.trial_spawner.lock() {
spawner.override_entity_to_spawn(entity_type, world, self.position);
}
}
pub fn set_trial_spawner_key(&self, key: &TrialSpawnerKey, world: &Arc<World>) {
if let Ok(mut spawner) = self.trial_spawner.lock() {
spawner.config = TrialSpawnerFullConfig::from_key(key);
spawner.data.reset();
spawner.set_state(world, self.position, TrialSpawnerState::Inactive);
}
}
}
#[cfg(test)]

View File

@ -7,7 +7,6 @@ use crate::{
use pumpkin_data::{
Block, BlockDirection, BlockState, BlockStateId,
damage::DamageType,
dimension::Dimension,
fluid::{Falling, Fluid, FluidProperties, Level},
world::WorldEvent,
};
@ -244,17 +243,12 @@ impl FluidBehaviour for FlowingLava {
impl FlowingFluid for FlowingLava {
fn get_level_decrease_per_block(&self, world: &World) -> i32 {
// Ultrawarm logic
if world.dimension == Dimension::THE_NETHER {
1
} else {
2
}
if world.dimension.fast_lava { 1 } else { 2 }
}
fn get_flow_speed(&self, world: &World) -> u8 {
// Ultrawarm logic - lava flows faster in the Nether
if world.dimension == Dimension::THE_NETHER {
// EnvironmentAttributes.FAST_LAVA
if world.dimension.fast_lava {
LAVA_FLOW_SPEED_NETHER
} else {
LAVA_FLOW_SPEED_SLOW
@ -262,12 +256,7 @@ impl FlowingFluid for FlowingLava {
}
fn get_max_flow_distance(&self, world: &World) -> i32 {
// Ultrawarm logic
if world.dimension == Dimension::THE_NETHER {
5
} else {
3
}
if world.dimension.fast_lava { 5 } else { 3 }
}
/// Determines if lava can convert to source blocks based on game rules.

View File

@ -24,6 +24,7 @@ use crate::server::Server;
use pumpkin_data::BlockDirection;
use pumpkin_data::block_rotation::{Mirror, Rotation};
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_inventory::screen_handler::ScreenHandlerFactory;
use pumpkin_protocol::java::server::play::SUseItemOn;
@ -460,7 +461,11 @@ pub fn drop_loot(
let key = format!("minecraft:blocks/{}", block.name);
if let Some(loot_table) = pumpkin_data::loot_table::get_loot_table(&key) {
let seed: i64 = rand::random();
let items = crate::world::loot::generate_loot_with_context(loot_table, seed, params);
let mut items = crate::world::loot::generate_loot_with_context(loot_table, seed, params);
if block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
// TODO: Re-enable apple and stick drops for leaves once table bonus/drop chances are properly implemented
items.retain(|stack| stack.item != &Item::APPLE && stack.item != &Item::STICK);
}
if !items.is_empty() {
let mut event = crate::plugin::block::block_drop_item::BlockDropItemEvent {
block_pos: *pos,

View File

@ -10,6 +10,9 @@ impl MobEffect for RaidOmenMobEffect {
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
let world = living.entity.world.load();
if !world.dimension.can_start_raid {
return;
}
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
&& let Some(player) = entity.get_player()
&& !player.is_spectator()

View File

@ -2,7 +2,6 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Weak};
use pumpkin_data::attributes::Attributes;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_nbt::compound::NbtCompound;
@ -122,7 +121,7 @@ impl HoglinEntity {
pub fn is_converting(&self, world: &World) -> bool {
!self.is_immune_to_zombification()
&& !self.mob_entity.is_no_ai()
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
&& world.dimension.piglins_zombify
}
fn convert_to_zoglin(&self) {

View File

@ -89,17 +89,6 @@ pub struct MobEntity {
last_sent_pitch: AtomicU8,
last_sent_head_yaw: AtomicU8,
}
/// Tick boundaries (both inclusive) when monsters do not burn in sunlight (26.1).
///
/// Sourced from `data/minecraft/timeline/day.json` — `monsters_burn` keyframes:
/// `value=false` at tick 12542 (dusk), `value=true` at tick 23460 (dawn).
///
/// TODO: Replace with `EnvironmentAttributes::MONSTERS_BURN` lookup once the
/// `EnvironmentAttributeSystem` is implemented in `pumpkin-data`.
pub(crate) const NIGHT_START: i64 = 12542;
pub(crate) const NIGHT_END: i64 = 23459;
impl MobEntity {
const AI_DISABLED_FLAG: u8 = 1;
const LEFT_HANDED_FLAG: u8 = 2;
@ -535,22 +524,16 @@ impl MobEntity {
let world_arc = entity.world.load();
let world = world_arc.as_ref();
// Night boundary from data/minecraft/timeline/day.json — monsters_burn keyframes:
// value=false at tick 12542 (dusk), value=true at tick 23460 (dawn).
// TODO: read directly from EnvironmentAttributes::MONSTERS_BURN once implemented.
let day_time = world.get_time_of_day() % 24000;
if (NIGHT_START..=NIGHT_END).contains(&day_time) {
let eye_block_pos = entity.get_eye_pos().to_block_pos();
if !world.monsters_burn(&eye_block_pos) {
return false;
}
// Vanilla: getLightLevelDependentMagicValue() — sky light at eye pos, scaled 0–1.
let eye_block_pos = entity.get_eye_pos();
let brightness = world
.level
.light_engine
.get_sky_light_level(&world.level, &eye_block_pos.to_block_pos())
as f32
.get_sky_light_level(&world.level, &eye_block_pos) as f32
/ 15.0;
if brightness <= 0.5 {

View File

@ -5,7 +5,6 @@ use std::sync::{
use pumpkin_data::Block;
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
@ -180,7 +179,7 @@ impl PiglinEntity {
pub fn is_converting(&self, world: &World) -> bool {
!self.is_immune_to_zombification()
&& !self.mob_entity.is_no_ai()
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
&& world.dimension.piglins_zombify
}
#[must_use]

View File

@ -4,7 +4,6 @@ use std::sync::{
};
use pumpkin_data::Block;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::tracked_data;
@ -107,7 +106,7 @@ impl PiglinBruteEntity {
pub fn is_converting(&self, world: &World) -> bool {
!self.is_immune_to_zombification()
&& !self.mob_entity.is_no_ai()
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
&& world.dimension.piglins_zombify
}
#[must_use]

View File

@ -2030,7 +2030,7 @@ impl Entity {
.on_entity_collision_fluid(fluid, caller);
}
let lava_speed = if world.dimension == Dimension::THE_NETHER {
let lava_speed = if world.dimension.fast_lava {
0.007
} else {
0.002_333_333

View File

@ -43,7 +43,7 @@ use crate::entity::ai::goal::wander_around::WanderAroundGoal;
use crate::entity::ai::goal::{Controls, Goal};
use crate::entity::ai::pathfinder::NavigatorGoal;
use crate::entity::experience_orb::ExperienceOrbEntity;
use crate::entity::mob::{Mob, MobEntity, NIGHT_END, NIGHT_START};
use crate::entity::mob::{Mob, MobEntity};
use crate::entity::player::Player;
use crate::entity::{Entity, EntityBase};
use crate::world::World;
@ -1103,8 +1103,8 @@ impl Goal for WanderingTraderUseItemGoal {
return false;
}
let world = trader.mob_entity.living_entity.entity.world.load();
let day_time = world.get_time_of_day() % 24000;
let is_dark = (NIGHT_START..=NIGHT_END).contains(&day_time);
let is_dark = world.is_dark_outside();
let is_bright = world.is_bright_outside();
let is_invisible = trader
.mob_entity
.living_entity
@ -1114,7 +1114,7 @@ impl Goal for WanderingTraderUseItemGoal {
self.goal_type = Some(PotionGoalType::Invisibility);
return true;
}
if !is_dark && is_invisible {
if is_bright && is_invisible {
self.goal_type = Some(PotionGoalType::Milk);
return true;
}

View File

@ -4176,6 +4176,9 @@ impl Player {
&& !self.has_effect(&StatusEffect::RAID_OMEN)
{
let world = self.world();
if !world.dimension.can_start_raid {
return;
}
let player_pos = self.living_entity.entity.block_pos.load();
let pos_f64 = self.living_entity.entity.pos.load();

View File

@ -7,7 +7,6 @@ use crate::{
};
use pumpkin_data::{
Block, BlockDirection,
dimension::Dimension,
entity::EntityType,
fluid::Fluid,
item::Item,
@ -204,10 +203,10 @@ fn try_pickup_bucket_item(
Some(&Item::WATER_BUCKET)
}
pub(crate) fn should_evaporate_in_nether(item: &Item, world: &World) -> bool {
pub(crate) const fn should_evaporate_in_nether(item: &Item, world: &World) -> bool {
item.id != Item::LAVA_BUCKET.id
&& item.id != Item::POWDER_SNOW_BUCKET.id
&& world.dimension == Dimension::THE_NETHER
&& world.dimension.water_evaporates
}
pub(crate) fn play_bucket_evaporation(world: &Arc<World>, position: &Vector3<f64>) {

View File

@ -133,8 +133,8 @@ impl ItemBehaviour for SpawnEggItem {
if let Some(entity_type) = entity_from_egg(item.item.id) {
let world = player.world();
if let Some(block_entity) = player.world().get_block_entity(&location)
&& let Some(spawner) = block_entity
if let Some(block_entity) = player.world().get_block_entity(&location) {
if let Some(spawner) = block_entity
.as_any()
.downcast_ref::<MobSpawnerBlockEntity>()
{
@ -143,6 +143,16 @@ impl ItemBehaviour for SpawnEggItem {
item.decrement_unless_creative(player.gamemode.load(), 1);
return;
}
if let Some(trial_spawner) = block_entity
.as_any()
.downcast_ref::<crate::block::entities::trial_spawner::TrialSpawnerBlockEntity>()
{
trial_spawner.set_entity_type(entity_type, &world);
world.update_block_entity(&block_entity);
item.decrement_unless_creative(player.gamemode.load(), 1);
return;
}
}
let target_state = world.get_block_state(&location);
let target_block = world.get_block(&location);

View File

@ -0,0 +1,238 @@
use pumpkin_data::dimension::BedRule;
use pumpkin_data::environment_attribute::{
Activity, DayTimeline, EarlyGameTimeline, EnvironmentAttribute, MoonPhase, MoonTimeline,
VillagerScheduleTimeline, sample_activity_track, sample_bool_track, sample_float_track,
sample_moon_phase_track, sample_step_float_track, sample_unbounded_bool_track,
};
use pumpkin_util::math::position::BlockPos;
use super::World;
/// Environment attribute accessor for a world, matching vanilla's `EnvironmentAttributeSystem`.
pub struct EnvironmentAttributes<'a> {
world: &'a World,
}
impl<'a> EnvironmentAttributes<'a> {
#[must_use]
pub const fn new(world: &'a World) -> Self {
Self { world }
}
/// Evaluates a float environment attribute at the dimension level (e.g. `EnvironmentAttributes.SKY_LIGHT_LEVEL`).
#[must_use]
pub fn get_dimension_value_f32(&self, attribute: EnvironmentAttribute) -> f32 {
match attribute {
EnvironmentAttribute::GameplaySkyLightLevel => {
let base = self.world.dimension.effective_sky_light_level();
let multiplier =
if self.world.dimension.has_skylight && !self.world.dimension.has_fixed_time {
sample_float_track(
DayTimeline::SKY_LIGHT_LEVEL_KEYFRAMES,
DayTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
} else {
1.0
};
let mut level = base * multiplier;
// Weather modifier (WeatherAttributes.RAIN and THUNDER)
if self.world.dimension.has_skylight {
let (rain_level, thunder_level) = {
let weather = self
.world
.weather
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
(weather.rain_level, weather.thunder_level)
};
let rain_adj = (rain_level - thunder_level).max(0.0);
if rain_adj > 0.0 {
let rain_target = 4.0;
level += rain_adj * 0.3125 * (rain_target - level);
}
if thunder_level > 0.0 {
let thunder_target = 4.0;
level += thunder_level * 0.52734375 * (thunder_target - level);
}
}
level.clamp(0.0, 15.0)
}
EnvironmentAttribute::GameplaySurfaceSlimeSpawnChance => {
if self.world.dimension.has_skylight && !self.world.dimension.has_fixed_time {
sample_step_float_track(
MoonTimeline::SURFACE_SLIME_SPAWN_CHANCE_KEYFRAMES,
MoonTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
} else {
0.0
}
}
EnvironmentAttribute::VisualSunAngle => {
super::calculate_celestial_angle(self.world.get_time_of_day()) * 360.0
}
EnvironmentAttribute::VisualMoonAngle => {
(super::calculate_celestial_angle(self.world.get_time_of_day()) * 360.0 + 180.0)
.rem_euclid(360.0)
}
_ => 0.0,
}
}
/// Evaluates a boolean environment attribute at the dimension level.
#[must_use]
pub fn get_dimension_value_bool(&self, attribute: EnvironmentAttribute) -> bool {
match attribute {
EnvironmentAttribute::GameplayFastLava => self.world.dimension.fast_lava,
EnvironmentAttribute::GameplayWaterEvaporates => self.world.dimension.water_evaporates,
EnvironmentAttribute::GameplayRespawnAnchorWorks => {
self.world.dimension.respawn_anchor_works
}
EnvironmentAttribute::GameplayPiglinsZombify => self.world.dimension.piglins_zombify,
EnvironmentAttribute::GameplaySnowGolemMelts => self.world.dimension.snow_golem_melts,
EnvironmentAttribute::GameplayCanStartRaid => self.world.dimension.can_start_raid,
EnvironmentAttribute::GameplayNetherPortalSpawnsPiglin => {
self.world.dimension.nether_portal_spawns_piglin
}
EnvironmentAttribute::GameplayCanPillagerPatrolSpawn => {
if self.world.dimension.has_skylight && !self.world.dimension.has_fixed_time {
sample_unbounded_bool_track(
EarlyGameTimeline::CAN_PILLAGER_PATROL_SPAWN_KEYFRAMES,
self.world.get_time_of_day(),
)
} else {
false
}
}
EnvironmentAttribute::GameplayMonstersBurn => {
self.world.dimension.has_skylight
&& !self.world.dimension.has_fixed_time
&& sample_bool_track(
DayTimeline::MONSTERS_BURN_KEYFRAMES,
DayTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}
EnvironmentAttribute::GameplayBeesStayInHive => {
let raining = {
let weather = self
.world
.weather
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
weather.raining
};
raining
|| !self.world.dimension.has_skylight
|| sample_bool_track(
DayTimeline::BEES_STAY_IN_HIVE_KEYFRAMES,
DayTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}
EnvironmentAttribute::GameplayCreakingActive => {
self.world.dimension.has_skylight
&& !self.world.dimension.has_fixed_time
&& sample_bool_track(
DayTimeline::CREAKING_ACTIVE_KEYFRAMES,
DayTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}
_ => false,
}
}
/// Evaluates a `BedRule` environment attribute at the dimension level.
#[must_use]
pub const fn get_dimension_value_bed_rule(&self) -> BedRule {
self.world.dimension.bed_rule
}
/// Evaluates a `TriState` environment attribute at the dimension level.
#[must_use]
pub fn get_dimension_value_tri_state(&self, attribute: EnvironmentAttribute) -> Option<bool> {
match attribute {
EnvironmentAttribute::GameplayEyeblossomOpen => (self.world.dimension.has_skylight
&& !self.world.dimension.has_fixed_time)
.then(|| {
sample_bool_track(
DayTimeline::EYEBLOSSOM_OPEN_KEYFRAMES,
DayTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}),
_ => None,
}
}
/// Evaluates the current `MoonPhase` from the moon timeline.
#[must_use]
pub fn get_dimension_value_moon_phase(&self) -> MoonPhase {
sample_moon_phase_track(
MoonTimeline::MOON_PHASE_KEYFRAMES,
MoonTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}
/// Evaluates the current villager `Activity` from the villager schedule timeline.
#[must_use]
pub fn get_dimension_value_activity(&self, baby: bool) -> Activity {
if baby {
sample_activity_track(
VillagerScheduleTimeline::BABY_VILLAGER_ACTIVITY_KEYFRAMES,
VillagerScheduleTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
} else {
sample_activity_track(
VillagerScheduleTimeline::VILLAGER_ACTIVITY_KEYFRAMES,
VillagerScheduleTimeline::PERIOD_TICKS,
self.world.get_time_of_day(),
)
}
}
/// Evaluates a boolean environment attribute at a position.
#[must_use]
pub fn get_value_bool(&self, attribute: EnvironmentAttribute, _pos: &BlockPos) -> bool {
self.get_dimension_value_bool(attribute)
}
/// Evaluates a float environment attribute at a position.
#[must_use]
pub fn get_value_f32(&self, attribute: EnvironmentAttribute, _pos: &BlockPos) -> f32 {
self.get_dimension_value_f32(attribute)
}
/// Evaluates a `BedRule` environment attribute at a position.
#[must_use]
pub const fn get_value_bed_rule(&self, _pos: &BlockPos) -> BedRule {
self.get_dimension_value_bed_rule()
}
/// Evaluates a `TriState` environment attribute at a position.
#[must_use]
pub fn get_value_tri_state(
&self,
attribute: EnvironmentAttribute,
_pos: &BlockPos,
) -> Option<bool> {
self.get_dimension_value_tri_state(attribute)
}
/// Evaluates the current `MoonPhase` at a position.
#[must_use]
pub fn get_value_moon_phase(&self, _pos: &BlockPos) -> MoonPhase {
self.get_dimension_value_moon_phase()
}
/// Evaluates the current villager `Activity` at a position.
#[must_use]
pub fn get_value_activity(&self, baby: bool, _pos: &BlockPos) -> Activity {
self.get_dimension_value_activity(baby)
}
}

View File

@ -169,10 +169,14 @@ pub mod custom_bossbar;
pub mod dragon_fight;
pub mod end_podium;
pub mod entity_tracker;
pub mod environment;
pub mod natural_spawner;
pub mod scoreboard;
pub mod weather;
pub use environment::EnvironmentAttributes;
pub use pumpkin_data::environment_attribute::{Activity, MoonPhase};
use crate::world::natural_spawner::{SpawnState, spawn_for_chunk};
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::effect::StatusEffect;
@ -5556,10 +5560,111 @@ impl World {
Some(self.set_block_state(position, new_state_id, flags))
}
#[must_use]
pub const fn environment_attributes(&self) -> EnvironmentAttributes<'_> {
EnvironmentAttributes::new(self)
}
#[must_use]
pub fn get_sky_darken(&self) -> i32 {
let sky_light_level = self.environment_attributes().get_dimension_value_f32(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplaySkyLightLevel,
);
(15.0 - sky_light_level).clamp(0.0, 15.0) as i32
}
#[must_use]
pub fn is_bright_outside(&self) -> bool {
!self.dimension.has_fixed_time && self.get_sky_darken() < 4
}
#[must_use]
pub fn is_dark_outside(&self) -> bool {
!self.dimension.has_fixed_time && !self.is_bright_outside()
}
/// Checks if daylight burns undead monsters (`EnvironmentAttributes.MONSTERS_BURN`).
#[must_use]
pub fn monsters_burn(&self, pos: &BlockPos) -> bool {
self.environment_attributes().get_value_bool(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplayMonstersBurn,
pos,
)
}
/// Checks if bees should stay inside beehives/nests (`EnvironmentAttributes.BEES_STAY_IN_HIVE`).
#[must_use]
pub fn bees_stay_in_hive(&self, pos: &BlockPos) -> bool {
self.environment_attributes().get_value_bool(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplayBeesStayInHive,
pos,
)
}
/// Checks if a creaking heart is active (`EnvironmentAttributes.CREAKING_ACTIVE`).
#[must_use]
pub fn creaking_active(&self, pos: &BlockPos) -> bool {
self.environment_attributes().get_value_bool(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplayCreakingActive,
pos,
)
}
/// Checks if an eyeblossom flower should be open (`EnvironmentAttributes.EYEBLOSSOM_OPEN`).
#[must_use]
pub fn eyeblossom_open(&self, pos: &BlockPos) -> Option<bool> {
self.environment_attributes().get_value_tri_state(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplayEyeblossomOpen,
pos,
)
}
#[must_use]
pub fn get_effective_sky_brightness(&self, pos: &BlockPos) -> i32 {
let sky_light = self.get_sky_light_level(pos) as i32;
sky_light - self.get_sky_darken()
}
#[must_use]
pub fn get_sun_angle(&self, pos: &BlockPos) -> f32 {
let sun_angle_deg = self.environment_attributes().get_value_f32(
pumpkin_data::environment_attribute::EnvironmentAttribute::VisualSunAngle,
pos,
);
sun_angle_deg * (std::f32::consts::PI / 180.0)
}
#[must_use]
pub fn get_moon_phase(&self) -> MoonPhase {
self.environment_attributes()
.get_dimension_value_moon_phase()
}
#[must_use]
pub fn can_pillager_patrol_spawn(&self, pos: &BlockPos) -> bool {
self.environment_attributes().get_value_bool(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplayCanPillagerPatrolSpawn,
pos,
)
}
#[must_use]
pub fn surface_slime_spawn_chance(&self, pos: &BlockPos) -> f32 {
self.environment_attributes().get_value_f32(
pumpkin_data::environment_attribute::EnvironmentAttribute::GameplaySurfaceSlimeSpawnChance,
pos,
)
}
#[must_use]
pub fn villager_activity(&self, pos: &BlockPos, baby: bool) -> Activity {
self.environment_attributes().get_value_activity(baby, pos)
}
pub fn get_max_local_raw_brightness(&self, pos: &BlockPos) -> u8 {
let sky_light = self.get_sky_light_level(pos);
let sky_light = (self.get_sky_light_level(pos) as i32 - self.get_sky_darken()).max(0) as u8;
let block_light = self.get_block_light_level(pos).unwrap_or(0);
sky_light.max(block_light) // TODO: getSkyDarken
sky_light.max(block_light)
}
pub fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> {
@ -7351,6 +7456,82 @@ impl WorldPortalExt for WorldPortal {
}
}
struct CubicCurve {
a: f32,
b: f32,
c: f32,
}
impl CubicCurve {
fn new(v1: f32, v2: f32) -> Self {
Self {
a: 3.0 * v1 - 3.0 * v2 + 1.0,
b: -6.0 * v1 + 3.0 * v2,
c: 3.0 * v1,
}
}
fn sample(&self, t: f32) -> f32 {
((self.a * t + self.b) * t + self.c) * t
}
fn sample_gradient(&self, t: f32) -> f32 {
(3.0 * self.a * t + 2.0 * self.b) * t + self.c
}
}
/// Calculates the celestial (sun) angle fraction in `[0.0, 1.0]`.
/// Matches vanilla 26.2 `EnvironmentAttributes.SUN_ANGLE` easing with `symmetricCubicBezier(0.362, 0.241)`.
#[must_use]
pub fn calculate_celestial_angle(time_of_day: i64) -> f32 {
let ticks = time_of_day.rem_euclid(24000);
let alpha = if ticks < 6000 {
(ticks + 18000) as f32 / 24000.0
} else {
(ticks - 6000) as f32 / 24000.0
};
let x_curve = CubicCurve::new(0.362, 0.638);
let y_curve = CubicCurve::new(0.241, 0.759);
let mut t = alpha;
let mut solved = false;
for _ in 0..4 {
let error = x_curve.sample(t) - alpha;
if error.abs() < 1e-5 {
solved = true;
break;
}
let gradient = x_curve.sample_gradient(t);
if gradient < 1e-5 {
break;
}
t -= (error / gradient).clamp(-0.25, 0.25);
}
if !solved {
let mut t0 = 0.0f32;
let mut t1 = 1.0f32;
for _ in 0..64 {
if t0 >= t1 {
break;
}
let error = x_curve.sample(t) - alpha;
if error.abs() < 1e-5 {
break;
}
if error < 0.0 {
t0 = t;
} else {
t1 = t;
}
t = f32::midpoint(t1, t0);
}
}
y_curve.sample(t)
}
#[cfg(test)]
mod tests {
use pumpkin_data::{

View File

@ -6,13 +6,13 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use serde::Deserialize;
/// Parses a CSS-style hex color string (e.g. `"#78a7ff"`) into a signed 32-bit integer.
/// Parses a CSS-style hex color string (e.g. `"#78a7ff"` or `"#ccffffff"`) into a signed 32-bit integer.
///
/// # Returns
/// The color as an `i32`, or `None` if the input is not a valid hex color.
fn parse_hex_color(s: &str) -> Option<i32> {
if let Some(stripped) = s.strip_prefix('#') {
i32::from_str_radix(stripped, 16).ok()
u32::from_str_radix(stripped, 16).ok().map(|v| v as i32)
} else {
None
}
@ -42,7 +42,10 @@ pub struct Dimension {
/// Fixed day-time value in this dimension, or `None` if time progresses normally.
#[serde(rename = "fixed_time")]
pub fixed_time: Option<i64>,
/// Optional bedrock-style visual attributes map (sky color, fog color, etc.).
/// Whether time is fixed in this dimension (modern 26.2 field).
#[serde(default, rename = "has_fixed_time")]
pub has_fixed_time: Option<bool>,
/// Environment attributes map (visual, gameplay, audio).
#[serde(default)]
pub attributes: Option<serde_json::Value>,
/// Optional timeline resource key controlling day/night progression.
@ -84,34 +87,120 @@ pub fn build() -> TokenStream {
.to_shouty_snake_case()
);
// convert optional hex colors from attributes into ints
let sky_color = dim
.attributes
.as_ref()
let attrs = dim.attributes.as_ref();
// Visual environment attributes
let sky_color = attrs
.and_then(|a| a.get("minecraft:visual/sky_color"))
.and_then(|v| v.as_str())
.and_then(parse_hex_color);
let fog_color = dim
.attributes
.as_ref()
let fog_color = attrs
.and_then(|a| a.get("minecraft:visual/fog_color"))
.and_then(|v| v.as_str())
.and_then(parse_hex_color);
let cloud_color = dim
.attributes
.as_ref()
let cloud_color = attrs
.and_then(|a| a.get("minecraft:visual/cloud_color"))
.and_then(|v| v.as_str())
.and_then(parse_hex_color);
let ambient_light_color = attrs
.and_then(|a| a.get("minecraft:visual/ambient_light_color"))
.and_then(|v| v.as_str())
.and_then(parse_hex_color);
let sky_light_color = attrs
.and_then(|a| a.get("minecraft:visual/sky_light_color"))
.and_then(|v| v.as_str())
.and_then(parse_hex_color);
let sky_light_factor = attrs
.and_then(|a| a.get("minecraft:visual/sky_light_factor"))
.and_then(|v| v.as_f64())
.map(|f| f as f32);
let fog_start_distance = attrs
.and_then(|a| a.get("minecraft:visual/fog_start_distance"))
.and_then(|v| v.as_f64())
.map(|f| f as f32);
let fog_end_distance = attrs
.and_then(|a| a.get("minecraft:visual/fog_end_distance"))
.and_then(|v| v.as_f64())
.map(|f| f as f32);
let cloud_height = attrs
.and_then(|a| a.get("minecraft:visual/cloud_height"))
.and_then(|v| v.as_f64())
.map(|f| f as f32);
// Gameplay environment attributes
let sky_light_level = attrs
.and_then(|a| a.get("minecraft:gameplay/sky_light_level"))
.and_then(|v| v.as_f64())
.map(|f| f as f32);
let water_evaporates = attrs
.and_then(|a| a.get("minecraft:gameplay/water_evaporates"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let fast_lava = attrs
.and_then(|a| a.get("minecraft:gameplay/fast_lava"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let respawn_anchor_works = attrs
.and_then(|a| a.get("minecraft:gameplay/respawn_anchor_works"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let piglins_zombify = attrs
.and_then(|a| a.get("minecraft:gameplay/piglins_zombify"))
.and_then(|v| v.as_bool())
.unwrap_or(true);
let snow_golem_melts = attrs
.and_then(|a| a.get("minecraft:gameplay/snow_golem_melts"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let can_start_raid = attrs
.and_then(|a| a.get("minecraft:gameplay/can_start_raid"))
.and_then(|v| v.as_bool())
.unwrap_or(true);
let nether_portal_spawns_piglin = attrs
.and_then(|a| a.get("minecraft:gameplay/nether_portal_spawns_piglin"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let bed_rule_obj = attrs.and_then(|a| a.get("minecraft:gameplay/bed_rule"));
let (can_sleep_token, can_set_spawn_token, explodes) = if let Some(b) = bed_rule_obj {
let can_sleep = b
.get("can_sleep")
.and_then(|v| v.as_str())
.unwrap_or("when_dark");
let can_set_spawn = b
.get("can_set_spawn")
.and_then(|v| v.as_str())
.unwrap_or("always");
let explodes = b.get("explodes").and_then(|v| v.as_bool()).unwrap_or(false);
let sleep_ident = match can_sleep {
"always" => quote!(BedRuleOption::Always),
"never" => quote!(BedRuleOption::Never),
_ => quote!(BedRuleOption::WhenDark),
};
let spawn_ident = match can_set_spawn {
"when_dark" => quote!(BedRuleOption::WhenDark),
"never" => quote!(BedRuleOption::Never),
_ => quote!(BedRuleOption::Always),
};
(sleep_ident, spawn_ident, explodes)
} else {
(
quote!(BedRuleOption::WhenDark),
quote!(BedRuleOption::Always),
false,
)
};
let fixed_time = if let Some(t) = dim.fixed_time {
quote! { Some(#t) }
} else {
quote! { None }
};
let has_fixed_time = dim.has_fixed_time.unwrap_or(dim.fixed_time.is_some());
let monster_spawn_light_level = value_to_int_provider(&dim.monster_spawn_light_level);
let monster_spawn_block_light_limit = dim.monster_spawn_block_light_limit;
let ambient_light = dim.ambient_light;
let coordinate_scale = dim.coordinate_scale;
@ -120,6 +209,7 @@ pub fn build() -> TokenStream {
let logical_height = dim.logical_height;
let has_skylight = dim.has_skylight;
let has_ceiling = dim.has_ceiling;
// normalize infiniburn to always have namespace
let infiniburn = if dim.infiniburn.contains(':') {
dim.infiniburn.clone()
@ -155,6 +245,41 @@ pub fn build() -> TokenStream {
} else {
quote! { None }
};
let ambient_light_color_literal = if let Some(c) = ambient_light_color {
quote! { Some(#c) }
} else {
quote! { None }
};
let sky_light_color_literal = if let Some(c) = sky_light_color {
quote! { Some(#c) }
} else {
quote! { None }
};
let sky_light_factor_literal = if let Some(f) = sky_light_factor {
quote! { Some(#f) }
} else {
quote! { None }
};
let fog_start_distance_literal = if let Some(f) = fog_start_distance {
quote! { Some(#f) }
} else {
quote! { None }
};
let fog_end_distance_literal = if let Some(f) = fog_end_distance {
quote! { Some(#f) }
} else {
quote! { None }
};
let cloud_height_literal = if let Some(f) = cloud_height {
quote! { Some(#f) }
} else {
quote! { None }
};
let sky_light_level_literal = if let Some(f) = sky_light_level {
quote! { Some(#f) }
} else {
quote! { None }
};
let timelines_literal = if let Some(t) = timelines.clone() {
quote! { Some(#t) }
} else {
@ -166,6 +291,7 @@ pub fn build() -> TokenStream {
id: #id,
minecraft_name: #minecraft_name,
fixed_time: #fixed_time,
has_fixed_time: #has_fixed_time,
has_skylight: #has_skylight,
has_ceiling: #has_ceiling,
coordinate_scale: #coordinate_scale,
@ -179,6 +305,25 @@ pub fn build() -> TokenStream {
sky_color: #sky_color_literal,
fog_color: #fog_color_literal,
cloud_color: #cloud_color_literal,
ambient_light_color: #ambient_light_color_literal,
sky_light_color: #sky_light_color_literal,
sky_light_factor: #sky_light_factor_literal,
fog_start_distance: #fog_start_distance_literal,
fog_end_distance: #fog_end_distance_literal,
cloud_height: #cloud_height_literal,
sky_light_level: #sky_light_level_literal,
water_evaporates: #water_evaporates,
fast_lava: #fast_lava,
respawn_anchor_works: #respawn_anchor_works,
piglins_zombify: #piglins_zombify,
snow_golem_melts: #snow_golem_melts,
can_start_raid: #can_start_raid,
nether_portal_spawns_piglin: #nether_portal_spawns_piglin,
bed_rule: BedRule {
can_sleep: #can_sleep_token,
can_set_spawn: #can_set_spawn_token,
explodes: #explodes,
},
timelines: #timelines_literal,
};
});
@ -195,11 +340,48 @@ pub fn build() -> TokenStream {
WeightedEntry, WeightedListIntProvider,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BedRuleOption {
Always,
WhenDark,
Never,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BedRule {
pub can_sleep: BedRuleOption,
pub can_set_spawn: BedRuleOption,
pub explodes: bool,
}
impl BedRule {
#[inline]
#[must_use]
pub const fn can_sleep(&self, is_dark_outside: bool) -> bool {
match self.can_sleep {
BedRuleOption::Always => true,
BedRuleOption::WhenDark => is_dark_outside,
BedRuleOption::Never => false,
}
}
#[inline]
#[must_use]
pub const fn can_set_spawn(&self, is_dark_outside: bool) -> bool {
match self.can_set_spawn {
BedRuleOption::Always => true,
BedRuleOption::WhenDark => is_dark_outside,
BedRuleOption::Never => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Dimension {
pub id: u8,
pub minecraft_name: &'static str,
pub fixed_time: Option<i64>,
pub has_fixed_time: bool,
pub has_skylight: bool,
pub has_ceiling: bool,
pub coordinate_scale: f64,
@ -213,6 +395,21 @@ pub fn build() -> TokenStream {
pub sky_color: Option<i32>,
pub fog_color: Option<i32>,
pub cloud_color: Option<i32>,
pub ambient_light_color: Option<i32>,
pub sky_light_color: Option<i32>,
pub sky_light_factor: Option<f32>,
pub fog_start_distance: Option<f32>,
pub fog_end_distance: Option<f32>,
pub cloud_height: Option<f32>,
pub sky_light_level: Option<f32>,
pub water_evaporates: bool,
pub fast_lava: bool,
pub respawn_anchor_works: bool,
pub piglins_zombify: bool,
pub snow_golem_melts: bool,
pub can_start_raid: bool,
pub nether_portal_spawns_piglin: bool,
pub bed_rule: BedRule,
pub timelines: Option<&'static str>,
}
@ -225,6 +422,18 @@ pub fn build() -> TokenStream {
_ => None
}
}
#[inline]
#[must_use]
pub const fn effective_sky_light_level(&self) -> f32 {
if let Some(level) = self.sky_light_level {
level
} else if self.has_skylight {
15.0
} else {
0.0
}
}
}
impl PartialEq for Dimension {
fn eq(&self, other: &Self) -> bool {

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,7 @@ mod enchantments;
mod entity_pose;
mod entity_status;
mod entity_type;
mod environment_attribute;
mod flower_pot_transformations;
mod fluid;
mod fuels;
@ -83,6 +84,7 @@ mod structures;
mod tag;
mod tracked_data;
mod translations;
mod trial_spawner;
mod version;
mod villager;
mod wit;
@ -153,6 +155,7 @@ pub fn main() {
(fuels::build, "fuels.rs"),
(data_component::build, "data_component.rs"),
(attributes::build, "attributes.rs"),
(environment_attribute::build, "environment_attribute.rs"),
(effect::build, "effect.rs"),
(potion::build, "potion.rs"),
(potion_brewing::build, "potion_brewing.rs"),
@ -170,6 +173,7 @@ pub fn main() {
(map_decoration::build, "map_decoration.rs"),
(dye_color::build, "dye_color.rs"),
(block_transformer::build, "block_transformer.rs"),
(trial_spawner::build, "trial_spawner.rs"),
];
build_functions.extend(remap::build());

View File

@ -0,0 +1,377 @@
use heck::{ToPascalCase, ToShoutySnakeCase};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use serde::Deserialize;
use std::fs;
use std::path::Path;
const fn default_spawn_range() -> i32 {
4
}
const fn default_total_mobs() -> f32 {
6.0
}
const fn default_simultaneous_mobs() -> f32 {
2.0
}
const fn default_total_mobs_added_per_player() -> f32 {
2.0
}
const fn default_simultaneous_mobs_added_per_player() -> f32 {
1.0
}
const fn default_ticks_between_spawn() -> i32 {
40
}
const fn default_weight() -> i32 {
1
}
#[derive(Deserialize, Debug)]
struct TrialSpawnerJson {
#[serde(default = "default_spawn_range")]
spawn_range: i32,
#[serde(default = "default_total_mobs")]
total_mobs: f32,
#[serde(default = "default_simultaneous_mobs")]
simultaneous_mobs: f32,
#[serde(default = "default_total_mobs_added_per_player")]
total_mobs_added_per_player: f32,
#[serde(default = "default_simultaneous_mobs_added_per_player")]
simultaneous_mobs_added_per_player: f32,
#[serde(default = "default_ticks_between_spawn")]
ticks_between_spawn: i32,
#[serde(default)]
spawn_potentials: Vec<WeightedSpawnPotentialJson>,
#[serde(default)]
loot_tables_to_eject: Vec<WeightedLootTableJson>,
#[serde(default)]
items_to_drop_when_ominous: Option<String>,
}
#[derive(Deserialize, Debug)]
struct WeightedSpawnPotentialJson {
data: SpawnDataJson,
#[serde(default = "default_weight")]
weight: i32,
}
#[derive(Deserialize, Debug)]
struct SpawnDataJson {
entity: EntityRefJson,
#[serde(default)]
equipment: Option<EquipmentJson>,
}
#[derive(Deserialize, Debug)]
struct EntityRefJson {
id: String,
}
#[derive(Deserialize, Debug)]
struct EquipmentJson {
loot_table: String,
#[serde(default)]
slot_drop_chances: Option<f32>,
}
#[derive(Deserialize, Debug)]
struct WeightedLootTableJson {
data: String,
#[serde(default = "default_weight")]
weight: i32,
}
fn collect_json_files(dir: &Path, base_dir: &Path, results: &mut Vec<(String, TrialSpawnerJson)>) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_json_files(&path, base_dir, results);
} else if path.extension().and_then(|s| s.to_str()) == Some("json") {
if let Ok(relative) = path.strip_prefix(base_dir) {
let mut rel_str = relative.to_string_lossy().replace('\\', "/");
if let Some(stripped) = rel_str.strip_suffix(".json") {
rel_str = stripped.to_string();
}
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(parsed) = serde_json::from_str::<TrialSpawnerJson>(&content) {
results.push((rel_str, parsed));
}
}
}
}
}
}
}
pub fn build() -> TokenStream {
let base_dir = Path::new("../../assets/datapacks/26_2/data/minecraft/trial_spawner");
let mut files = Vec::new();
collect_json_files(base_dir, base_dir, &mut files);
files.sort_by(|a, b| a.0.cmp(&b.0));
let mut const_defs = TokenStream::new();
let mut match_arms = TokenStream::new();
let chamber_keys = [
("breeze", "Breeze", "trial_chamber/breeze"),
("melee_husk", "MeleeHusk", "trial_chamber/melee/husk"),
("melee_spider", "MeleeSpider", "trial_chamber/melee/spider"),
("melee_zombie", "MeleeZombie", "trial_chamber/melee/zombie"),
(
"ranged_poison_skeleton",
"RangedPoisonSkeleton",
"trial_chamber/ranged/poison_skeleton",
),
(
"ranged_skeleton",
"RangedSkeleton",
"trial_chamber/ranged/skeleton",
),
("ranged_stray", "RangedStray", "trial_chamber/ranged/stray"),
(
"slow_ranged_poison_skeleton",
"SlowRangedPoisonSkeleton",
"trial_chamber/slow_ranged/poison_skeleton",
),
(
"slow_ranged_skeleton",
"SlowRangedSkeleton",
"trial_chamber/slow_ranged/skeleton",
),
(
"slow_ranged_stray",
"SlowRangedStray",
"trial_chamber/slow_ranged/stray",
),
(
"small_melee_baby_zombie",
"SmallMeleeBabyZombie",
"trial_chamber/small_melee/baby_zombie",
),
(
"small_melee_cave_spider",
"SmallMeleeCaveSpider",
"trial_chamber/small_melee/cave_spider",
),
(
"small_melee_silverfish",
"SmallMeleeSilverfish",
"trial_chamber/small_melee/silverfish",
),
(
"small_melee_slime",
"SmallMeleeSlime",
"trial_chamber/small_melee/slime",
),
];
for (rel_key, cfg) in &files {
let const_name = rel_key.replace('/', "_").to_shouty_snake_case();
let const_ident = format_ident!("{}", const_name);
let spawn_range = cfg.spawn_range;
let total_mobs = cfg.total_mobs;
let simultaneous_mobs = cfg.simultaneous_mobs;
let total_mobs_added_per_player = cfg.total_mobs_added_per_player;
let simultaneous_mobs_added_per_player = cfg.simultaneous_mobs_added_per_player;
let ticks_between_spawn = cfg.ticks_between_spawn;
let spawn_potentials: Vec<_> = cfg
.spawn_potentials
.iter()
.map(|sp| {
let entity_id = &sp.data.entity.id;
let weight = sp.weight;
let (has_eq, eq_table, has_chance, eq_chance) = match &sp.data.equipment {
Some(eq) => (
true,
eq.loot_table.as_str(),
eq.slot_drop_chances.is_some(),
eq.slot_drop_chances.unwrap_or(0.0),
),
None => (false, "", false, 0.0),
};
let eq_loot_stream = if has_eq {
quote! { Some(#eq_table) }
} else {
quote! { None }
};
let eq_chance_stream = if has_chance {
quote! { Some(#eq_chance) }
} else {
quote! { None }
};
quote! {
WeightedSpawnData {
entity_id: #entity_id,
equipment_loot_table: #eq_loot_stream,
equipment_slot_drop_chances: #eq_chance_stream,
weight: #weight,
}
}
})
.collect();
let loot_tables: Vec<_> = cfg
.loot_tables_to_eject
.iter()
.map(|lt| {
let table = &lt.data;
let weight = lt.weight;
quote! {
WeightedLootTable {
loot_table: #table,
weight: #weight,
}
}
})
.collect();
let ominous_items = match &cfg.items_to_drop_when_ominous {
Some(s) => quote! { Some(#s) },
None => quote! { None },
};
const_defs.extend(quote! {
pub const #const_ident: TrialSpawnerConfigData = TrialSpawnerConfigData {
spawn_range: #spawn_range,
total_mobs: #total_mobs,
simultaneous_mobs: #simultaneous_mobs,
total_mobs_added_per_player: #total_mobs_added_per_player,
simultaneous_mobs_added_per_player: #simultaneous_mobs_added_per_player,
ticks_between_spawn: #ticks_between_spawn,
spawn_potentials: &[
#(#spawn_potentials),*
],
loot_tables_to_eject: &[
#(#loot_tables),*
],
items_to_drop_when_ominous: #ominous_items,
};
});
let mc_key = format!("minecraft:{rel_key}");
match_arms.extend(quote! {
#rel_key | #mc_key => Some(&#const_ident),
});
}
let mut key_variants = TokenStream::new();
let mut key_name_match = TokenStream::new();
let mut key_normal_match = TokenStream::new();
let mut key_ominous_match = TokenStream::new();
let mut key_from_name_match = TokenStream::new();
for (_, variant_name, path_str) in chamber_keys {
let variant_ident = format_ident!("{}", variant_name);
let normal_ident = format_ident!(
"{}_NORMAL",
path_str.replace('/', "_").to_shouty_snake_case()
);
let ominous_ident = format_ident!(
"{}_OMINOUS",
path_str.replace('/', "_").to_shouty_snake_case()
);
let mc_path = format!("minecraft:{path_str}");
key_variants.extend(quote! {
#variant_ident,
});
key_name_match.extend(quote! {
Self::#variant_ident => #path_str,
});
key_normal_match.extend(quote! {
Self::#variant_ident => &#normal_ident,
});
key_ominous_match.extend(quote! {
Self::#variant_ident => &#ominous_ident,
});
key_from_name_match.extend(quote! {
#path_str | #mc_path => Some(Self::#variant_ident),
});
}
quote! {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TrialSpawnerConfigData {
pub spawn_range: i32,
pub total_mobs: f32,
pub simultaneous_mobs: f32,
pub total_mobs_added_per_player: f32,
pub simultaneous_mobs_added_per_player: f32,
pub ticks_between_spawn: i32,
pub spawn_potentials: &'static [WeightedSpawnData],
pub loot_tables_to_eject: &'static [WeightedLootTable],
pub items_to_drop_when_ominous: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WeightedSpawnData {
pub entity_id: &'static str,
pub equipment_loot_table: Option<&'static str>,
pub equipment_slot_drop_chances: Option<f32>,
pub weight: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WeightedLootTable {
pub loot_table: &'static str,
pub weight: i32,
}
#const_defs
#[must_use]
pub fn get_trial_spawner_config(key: &str) -> Option<&'static TrialSpawnerConfigData> {
match key {
#match_arms
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TrialSpawnerKey {
#key_variants
}
impl TrialSpawnerKey {
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
#key_name_match
}
}
#[must_use]
pub const fn normal_config(&self) -> &'static TrialSpawnerConfigData {
match self {
#key_normal_match
}
}
#[must_use]
pub const fn ominous_config(&self) -> &'static TrialSpawnerConfigData {
match self {
#key_ominous_match
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name {
#key_from_name_match
_ => None,
}
}
}
}
}