fix: daylight sensor
This commit is contained in:
parent
38fd0c2714
commit
8d0d0d3117
@ -26,6 +26,7 @@ default = [
|
|||||||
"game_rules",
|
"game_rules",
|
||||||
"entity",
|
"entity",
|
||||||
"dimension",
|
"dimension",
|
||||||
|
"environment_attribute",
|
||||||
"enchantment",
|
"enchantment",
|
||||||
"world",
|
"world",
|
||||||
"scoreboard",
|
"scoreboard",
|
||||||
@ -54,9 +55,11 @@ default = [
|
|||||||
"map_color",
|
"map_color",
|
||||||
"map_decoration",
|
"map_decoration",
|
||||||
"dye_color",
|
"dye_color",
|
||||||
"block_transformer"
|
"block_transformer",
|
||||||
|
"trial_spawner",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
trial_spawner = []
|
||||||
block_transformer = ["block", "sound", "world", "tag"]
|
block_transformer = ["block", "sound", "world", "tag"]
|
||||||
map_color = []
|
map_color = []
|
||||||
map_decoration = []
|
map_decoration = []
|
||||||
@ -83,6 +86,7 @@ game_event = []
|
|||||||
game_rules = []
|
game_rules = []
|
||||||
entity = ["entity_pose", "entity_status", "entity_type", "spawn_egg"]
|
entity = ["entity_pose", "entity_status", "entity_type", "spawn_egg"]
|
||||||
dimension = []
|
dimension = []
|
||||||
|
environment_attribute = []
|
||||||
enchantment = []
|
enchantment = []
|
||||||
world = ["world_event", "message_type"]
|
world = ["world_event", "message_type"]
|
||||||
scoreboard = []
|
scoreboard = []
|
||||||
|
|||||||
@ -4,11 +4,44 @@ use pumpkin_util::math::int_provider::{
|
|||||||
IntProvider, NormalIntProvider, TrapezoidIntProvider, UniformIntProvider, WeightedEntry,
|
IntProvider, NormalIntProvider, TrapezoidIntProvider, UniformIntProvider, WeightedEntry,
|
||||||
WeightedListIntProvider,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Dimension {
|
pub struct Dimension {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub minecraft_name: &'static str,
|
pub minecraft_name: &'static str,
|
||||||
pub fixed_time: Option<i64>,
|
pub fixed_time: Option<i64>,
|
||||||
|
pub has_fixed_time: bool,
|
||||||
pub has_skylight: bool,
|
pub has_skylight: bool,
|
||||||
pub has_ceiling: bool,
|
pub has_ceiling: bool,
|
||||||
pub coordinate_scale: f64,
|
pub coordinate_scale: f64,
|
||||||
@ -22,6 +55,21 @@ pub struct Dimension {
|
|||||||
pub sky_color: Option<i32>,
|
pub sky_color: Option<i32>,
|
||||||
pub fog_color: Option<i32>,
|
pub fog_color: Option<i32>,
|
||||||
pub cloud_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>,
|
pub timelines: Option<&'static str>,
|
||||||
}
|
}
|
||||||
impl Dimension {
|
impl Dimension {
|
||||||
@ -29,6 +77,7 @@ impl Dimension {
|
|||||||
id: 0u8,
|
id: 0u8,
|
||||||
minecraft_name: "minecraft:overworld",
|
minecraft_name: "minecraft:overworld",
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
|
has_fixed_time: false,
|
||||||
has_skylight: true,
|
has_skylight: true,
|
||||||
has_ceiling: false,
|
has_ceiling: false,
|
||||||
coordinate_scale: 1f64,
|
coordinate_scale: 1f64,
|
||||||
@ -46,13 +95,33 @@ impl Dimension {
|
|||||||
monster_spawn_block_light_limit: 0u8,
|
monster_spawn_block_light_limit: 0u8,
|
||||||
sky_color: Some(7907327i32),
|
sky_color: Some(7907327i32),
|
||||||
fog_color: Some(12638463i32),
|
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"),
|
timelines: Some("#minecraft:in_overworld"),
|
||||||
};
|
};
|
||||||
pub const OVERWORLD_CAVES: Self = Self {
|
pub const OVERWORLD_CAVES: Self = Self {
|
||||||
id: 1u8,
|
id: 1u8,
|
||||||
minecraft_name: "minecraft:overworld_caves",
|
minecraft_name: "minecraft:overworld_caves",
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
|
has_fixed_time: false,
|
||||||
has_skylight: true,
|
has_skylight: true,
|
||||||
has_ceiling: true,
|
has_ceiling: true,
|
||||||
coordinate_scale: 1f64,
|
coordinate_scale: 1f64,
|
||||||
@ -70,13 +139,33 @@ impl Dimension {
|
|||||||
monster_spawn_block_light_limit: 0u8,
|
monster_spawn_block_light_limit: 0u8,
|
||||||
sky_color: Some(7907327i32),
|
sky_color: Some(7907327i32),
|
||||||
fog_color: Some(12638463i32),
|
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"),
|
timelines: Some("#minecraft:in_overworld"),
|
||||||
};
|
};
|
||||||
pub const THE_END: Self = Self {
|
pub const THE_END: Self = Self {
|
||||||
id: 2u8,
|
id: 2u8,
|
||||||
minecraft_name: "minecraft:the_end",
|
minecraft_name: "minecraft:the_end",
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
|
has_fixed_time: true,
|
||||||
has_skylight: true,
|
has_skylight: true,
|
||||||
has_ceiling: false,
|
has_ceiling: false,
|
||||||
coordinate_scale: 1f64,
|
coordinate_scale: 1f64,
|
||||||
@ -90,12 +179,32 @@ impl Dimension {
|
|||||||
sky_color: Some(0i32),
|
sky_color: Some(0i32),
|
||||||
fog_color: Some(1577752i32),
|
fog_color: Some(1577752i32),
|
||||||
cloud_color: None,
|
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"),
|
timelines: Some("#minecraft:in_end"),
|
||||||
};
|
};
|
||||||
pub const THE_NETHER: Self = Self {
|
pub const THE_NETHER: Self = Self {
|
||||||
id: 3u8,
|
id: 3u8,
|
||||||
minecraft_name: "minecraft:the_nether",
|
minecraft_name: "minecraft:the_nether",
|
||||||
fixed_time: None,
|
fixed_time: None,
|
||||||
|
has_fixed_time: true,
|
||||||
has_skylight: false,
|
has_skylight: false,
|
||||||
has_ceiling: true,
|
has_ceiling: true,
|
||||||
coordinate_scale: 8f64,
|
coordinate_scale: 8f64,
|
||||||
@ -109,6 +218,25 @@ impl Dimension {
|
|||||||
sky_color: None,
|
sky_color: None,
|
||||||
fog_color: None,
|
fog_color: None,
|
||||||
cloud_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"),
|
timelines: Some("#minecraft:in_nether"),
|
||||||
};
|
};
|
||||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||||
@ -120,6 +248,17 @@ impl Dimension {
|
|||||||
_ => None,
|
_ => 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 {
|
impl PartialEq for Dimension {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
|||||||
1050
crates/pumpkin-data/src/generated/environment_attribute.rs
Normal file
1050
crates/pumpkin-data/src/generated/environment_attribute.rs
Normal file
File diff suppressed because it is too large
Load Diff
855
crates/pumpkin-data/src/generated/trial_spawner.rs
Normal file
855
crates/pumpkin-data/src/generated/trial_spawner.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -179,6 +179,14 @@ mod spawn_egg;
|
|||||||
#[path = "generated/dimension.rs"]
|
#[path = "generated/dimension.rs"]
|
||||||
pub mod dimension;
|
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")]
|
#[cfg(feature = "enchantment")]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[path = "generated/enchantment.rs"]
|
#[path = "generated/enchantment.rs"]
|
||||||
@ -456,3 +464,8 @@ pub mod dye_color;
|
|||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[path = "generated/block_transformer.rs"]
|
#[path = "generated/block_transformer.rs"]
|
||||||
pub mod block_transformer;
|
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
|
||||||
@ -55,8 +55,8 @@ impl WorldPortalExt for BlockRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_world_gen() -> Box<WorldGenerator> {
|
fn make_world_gen() -> WorldGenerator {
|
||||||
get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
|
*get_world_gen(SEED, Dimension::OVERWORLD, false, Vec::new(), String::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_cache(
|
fn setup_cache(
|
||||||
|
|||||||
@ -259,12 +259,12 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flat_world_has_a_single_fixed_biome() {
|
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),
|
Seed(0),
|
||||||
Dimension::OVERWORLD,
|
Dimension::OVERWORLD,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
"minecraft:plains".to_string(),
|
"minecraft:plains".to_string(),
|
||||||
));
|
)));
|
||||||
|
|
||||||
let origin = BlockPos::new(17, 64, -3);
|
let origin = BlockPos::new(17, 64, -3);
|
||||||
let (pos, biome) = find_closest_biome_3d(
|
let (pos, biome) = find_closest_biome_3d(
|
||||||
|
|||||||
@ -79,7 +79,7 @@ pub trait CustomChunkGenerator: Send + Sync {
|
|||||||
|
|
||||||
pub enum WorldGenerator {
|
pub enum WorldGenerator {
|
||||||
Noise(Box<VanillaGenerator>),
|
Noise(Box<VanillaGenerator>),
|
||||||
Flat(flat::FlatGenerator),
|
Flat(Box<flat::FlatGenerator>),
|
||||||
Custom(Arc<dyn CustomChunkGenerator>),
|
Custom(Arc<dyn CustomChunkGenerator>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ impl UniformHeightProvider {
|
|||||||
let max = self.max_inclusive.get_y(min_y as i16, height);
|
let max = self.max_inclusive.get_y(min_y as i16, height);
|
||||||
|
|
||||||
if min > max {
|
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;
|
return min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,9 +34,9 @@ pub fn get_world_gen(
|
|||||||
flat_biome: String,
|
flat_biome: String,
|
||||||
) -> Box<generator::WorldGenerator> {
|
) -> Box<generator::WorldGenerator> {
|
||||||
if is_flat {
|
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),
|
generator::flat::FlatGenerator::new(seed, dimension, flat_layers, flat_biome),
|
||||||
))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Box::new(generator::WorldGenerator::Noise(Box::new(
|
Box::new(generator::WorldGenerator::Noise(Box::new(
|
||||||
VanillaGenerator::new(seed, dimension),
|
VanillaGenerator::new(seed, dimension),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::block::entities::bed::BedBlockEntity;
|
use crate::block::entities::bed::BedBlockEntity;
|
||||||
use pumpkin_data::block_properties::BedPart;
|
use pumpkin_data::block_properties::BedPart;
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::translation;
|
use pumpkin_data::translation;
|
||||||
use pumpkin_data::{Block, BlockState, BlockStateId};
|
use pumpkin_data::{Block, BlockState, BlockStateId};
|
||||||
@ -213,8 +212,8 @@ impl BedBlock {
|
|||||||
(position.offset(bed_props.facing.to_offset()), *position)
|
(position.offset(bed_props.facing.to_offset()), *position)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Explode if not in the overworld
|
// Explode if bed rule explodes (EnvironmentAttributes.BED_RULE)
|
||||||
if world.dimension != Dimension::OVERWORLD {
|
if world.dimension.bed_rule.explodes {
|
||||||
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
|
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
|
||||||
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
|
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
|
||||||
|
|
||||||
@ -227,6 +226,21 @@ impl BedBlock {
|
|||||||
return BlockActionResult::SuccessServer;
|
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
|
// Make sure the bed is not obstructed
|
||||||
if world.get_block_state(&bed_head_pos.up()).is_solid()
|
if world.get_block_state(&bed_head_pos.up()).is_solid()
|
||||||
|| world.get_block_state(&bed_foot_pos.up()).is_solid()
|
|| world.get_block_state(&bed_foot_pos.up()).is_solid()
|
||||||
@ -274,13 +288,15 @@ impl BedBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set respawn point
|
// Set respawn point
|
||||||
if player.set_respawn_point(
|
if can_set_spawn
|
||||||
|
&& player.set_respawn_point(
|
||||||
world.dimension.clone(),
|
world.dimension.clone(),
|
||||||
bed_head_pos,
|
bed_head_pos,
|
||||||
player.get_entity().yaw.load(),
|
player.get_entity().yaw.load(),
|
||||||
player.get_entity().pitch.load(),
|
player.get_entity().pitch.load(),
|
||||||
false,
|
false,
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
player.send_system_message(&pumpkin_macros::translate_cross!(
|
player.send_system_message(&pumpkin_macros::translate_cross!(
|
||||||
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
|
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
|
||||||
translation::bedrock::TILE_BED_RESPAWNSET
|
translation::bedrock::TILE_BED_RESPAWNSET
|
||||||
@ -288,7 +304,7 @@ impl BedBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the time and weather allows sleep
|
// Make sure the time and weather allows sleep
|
||||||
if !can_sleep(world) {
|
if !can_sleep {
|
||||||
player.send_system_message_raw(
|
player.send_system_message_raw(
|
||||||
&pumpkin_macros::translate_cross!(
|
&pumpkin_macros::translate_cross!(
|
||||||
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
|
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 {
|
fn entity_prevents_sleep(entity: &Entity) -> bool {
|
||||||
NO_SLEEP_IDS.contains(&entity.entity_type.id)
|
NO_SLEEP_IDS.contains(&entity.entity_type.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use crate::block::registry::BlockActionResult;
|
use crate::block::registry::BlockActionResult;
|
||||||
use crate::block::{BlockBehaviour, PathComputationType, RandomTickArgs, UseWithItemArgs};
|
use crate::block::{BlockBehaviour, PathComputationType, RandomTickArgs, UseWithItemArgs};
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::flower_pot_transformations::get_potted_item;
|
use pumpkin_data::flower_pot_transformations::get_potted_item;
|
||||||
use pumpkin_data::{Block, BlockId, BlockState};
|
use pumpkin_data::{Block, BlockId, BlockState};
|
||||||
use pumpkin_macros::pumpkin_block_from_tag;
|
use pumpkin_macros::pumpkin_block_from_tag;
|
||||||
@ -45,23 +44,24 @@ impl BlockBehaviour for FlowerPotBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||||
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|
let is_open_potted = args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM);
|
||||||
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
|
let is_closed_potted = args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM);
|
||||||
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
|
if !is_open_potted && !is_closed_potted {
|
||||||
&& args.world.get_time_of_day() % 24000 > 14500
|
return;
|
||||||
{
|
|
||||||
args.world.set_block_state(
|
|
||||||
args.position,
|
|
||||||
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
|
|
||||||
BlockFlags::NOTIFY_ALL,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
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.world.set_block_state(
|
||||||
args.position,
|
args.position,
|
||||||
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
|
next_block.default_state.id,
|
||||||
BlockFlags::NOTIFY_ALL,
|
BlockFlags::NOTIFY_ALL,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ use crate::block::{
|
|||||||
};
|
};
|
||||||
use crate::world::World;
|
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) {
|
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);
|
world.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
|
||||||
} else {
|
} else {
|
||||||
world.set_block_state(
|
world.set_block_state(
|
||||||
@ -68,7 +68,7 @@ impl BlockBehaviour for IceBlock {
|
|||||||
let held_item = args.player.inventory().held_item();
|
let held_item = args.player.inventory().held_item();
|
||||||
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
|
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
|
||||||
if !has_silk_touch {
|
if !has_silk_touch {
|
||||||
if args.world.dimension == Dimension::THE_NETHER {
|
if args.world.dimension.water_evaporates {
|
||||||
args.world.set_block_state(
|
args.world.set_block_state(
|
||||||
args.position,
|
args.position,
|
||||||
BlockStateId::AIR,
|
BlockStateId::AIR,
|
||||||
|
|||||||
@ -73,8 +73,7 @@ impl BlockBehaviour for NetherPortalBlock {
|
|||||||
let difficulty = level_info.difficulty;
|
let difficulty = level_info.difficulty;
|
||||||
if !level_info.game_rules.spawn_mobs
|
if !level_info.game_rules.spawn_mobs
|
||||||
|| difficulty == Difficulty::Peaceful
|
|| difficulty == Difficulty::Peaceful
|
||||||
|| (args.world.dimension != Dimension::OVERWORLD
|
|| !args.world.dimension.nether_portal_spawns_piglin
|
||||||
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
Block, BlockId, BlockStateId,
|
Block, BlockId, BlockStateId,
|
||||||
dimension::Dimension,
|
|
||||||
effect::StatusEffect,
|
effect::StatusEffect,
|
||||||
entity::EntityType,
|
entity::EntityType,
|
||||||
particle::Particle,
|
particle::Particle,
|
||||||
@ -117,17 +116,7 @@ impl PlantBlockBase for EyeblossomBlock {}
|
|||||||
|
|
||||||
pub fn try_changing_state(world: &Arc<World>, current_block: &Block, pos: &BlockPos) -> bool {
|
pub fn try_changing_state(world: &Arc<World>, current_block: &Block, pos: &BlockPos) -> bool {
|
||||||
let is_open = current_block == &Block::OPEN_EYEBLOSSOM;
|
let is_open = current_block == &Block::OPEN_EYEBLOSSOM;
|
||||||
let should_be_open = if world.dimension == Dimension::OVERWORLD
|
let should_be_open = world.eyeblossom_open(pos).unwrap_or(is_open);
|
||||||
|| world.dimension == Dimension::OVERWORLD_CAVES
|
|
||||||
{
|
|
||||||
world
|
|
||||||
.level_time
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.is_night()
|
|
||||||
} else {
|
|
||||||
is_open
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_be_open == is_open {
|
if should_be_open == is_open {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::block::entities::daylight_detector::DaylightDetectorBlockEntity;
|
use crate::block::entities::daylight_detector::DaylightDetectorBlockEntity;
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::game_event::GameEvent;
|
||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_world::world::BlockFlags;
|
use pumpkin_world::world::BlockFlags;
|
||||||
@ -38,11 +38,18 @@ impl BlockBehaviour for DaylightDetectorBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let state = args.world.get_block_state(args.position);
|
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
|
BlockActionResult::Success
|
||||||
}
|
}
|
||||||
@ -58,16 +65,43 @@ impl BlockBehaviour for DaylightDetectorBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DaylightDetectorBlock {
|
impl DaylightDetectorBlock {
|
||||||
fn update_inverted(
|
#[must_use]
|
||||||
mut props: DaylightDetectorProperties,
|
pub fn calculate_signal_strength(
|
||||||
world: &Arc<World>,
|
effective_sky_brightness: i32,
|
||||||
block_pos: &BlockPos,
|
sun_angle_radians: f32,
|
||||||
block: &Block,
|
is_inverted: bool,
|
||||||
) {
|
) -> u8 {
|
||||||
props.inverted = !props.inverted;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use pumpkin_data::block_properties::RespawnAnchorLikeProperties;
|
use pumpkin_data::block_properties::RespawnAnchorLikeProperties;
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
use pumpkin_data::{BlockState, translation};
|
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 state_id = args.world.get_block_state_id(args.position);
|
||||||
let props = RespawnAnchorLikeProperties::from_state_id(state_id);
|
let props = RespawnAnchorLikeProperties::from_state_id(state_id);
|
||||||
|
|
||||||
if args.world.dimension != Dimension::THE_NETHER {
|
if !args.world.dimension.respawn_anchor_works {
|
||||||
args.world
|
args.world
|
||||||
.break_block(args.position, None, BlockFlags::SKIP_DROPS);
|
.break_block(args.position, None, BlockFlags::SKIP_DROPS);
|
||||||
let center_pos = args.position.to_centered_f64();
|
let center_pos = args.position.to_centered_f64();
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use std::collections::{HashSet, VecDeque};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::block::{BlockBehaviour, OnNeighborUpdateArgs, PlacedArgs};
|
use crate::block::{BlockBehaviour, OnNeighborUpdateArgs, PlacedArgs};
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::particle::Particle;
|
use pumpkin_data::particle::Particle;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
use pumpkin_data::{Block, BlockStateId};
|
use pumpkin_data::{Block, BlockStateId};
|
||||||
@ -112,8 +111,8 @@ pub struct WetSpongeBlock;
|
|||||||
impl BlockBehaviour for WetSpongeBlock {
|
impl BlockBehaviour for WetSpongeBlock {
|
||||||
fn placed(&self, args: PlacedArgs<'_>) {
|
fn placed(&self, args: PlacedArgs<'_>) {
|
||||||
{
|
{
|
||||||
// Check if placed in Nether, if so, dry out
|
// Check if placed where water evaporates (EnvironmentAttributes.WATER_EVAPORATES), if so, dry out
|
||||||
if args.world.dimension == Dimension::THE_NETHER {
|
if args.world.dimension.water_evaporates {
|
||||||
args.world.set_block_state(
|
args.world.set_block_state(
|
||||||
args.position,
|
args.position,
|
||||||
Block::SPONGE.default_state.id,
|
Block::SPONGE.default_state.id,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
|
|
||||||
use crate::block::entities::trial_spawner::TrialSpawnerBlockEntity;
|
use crate::block::entities::trial_spawner::TrialSpawnerBlockEntity;
|
||||||
use crate::block::{BlockBehaviour, PlacedArgs};
|
use crate::block::{BlockBehaviour, BrokenArgs, PlacedArgs};
|
||||||
|
|
||||||
#[pumpkin_block("minecraft:trial_spawner")]
|
#[pumpkin_block("minecraft:trial_spawner")]
|
||||||
pub struct TrialSpawnerBlock;
|
pub struct TrialSpawnerBlock;
|
||||||
@ -13,4 +13,8 @@ impl BlockBehaviour for TrialSpawnerBlock {
|
|||||||
let entity = TrialSpawnerBlockEntity::new(*args.position);
|
let entity = TrialSpawnerBlockEntity::new(*args.position);
|
||||||
args.world.add_block_entity(Arc::new(entity));
|
args.world.add_block_entity(Arc::new(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn broken(&self, args: BrokenArgs<'_>) {
|
||||||
|
args.world.remove_block_entity(args.position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,11 @@ use std::sync::Arc;
|
|||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
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;
|
use super::BlockEntity;
|
||||||
|
|
||||||
type DaylightDetectorProperties = pumpkin_data::block_properties::DaylightDetectorLikeProperties;
|
|
||||||
|
|
||||||
pub struct DaylightDetectorBlockEntity {
|
pub struct DaylightDetectorBlockEntity {
|
||||||
pub position: BlockPos,
|
pub position: BlockPos,
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ impl BlockEntity for DaylightDetectorBlockEntity {
|
|||||||
|
|
||||||
fn tick(&self, world: &Arc<World>) {
|
fn tick(&self, world: &Arc<World>) {
|
||||||
if world.get_world_age() % 20 == 0 && world.dimension.has_skylight {
|
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) {
|
pub fn update_power(world: &Arc<World>, block_pos: &BlockPos) {
|
||||||
use std::f32::consts::PI;
|
DaylightDetectorBlock::update_signal_strength(world, block_pos);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,14 @@ use rustc_hash::FxHashSet;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use pumpkin_data::block_properties::{TrialSpawnerLikeProperties, TrialSpawnerState};
|
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::entity::EntityType;
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
|
use pumpkin_data::potion::Effect;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
|
use pumpkin_data::trial_spawner::TrialSpawnerKey;
|
||||||
use pumpkin_data::world::WorldEvent;
|
use pumpkin_data::world::WorldEvent;
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_nbt::tag::NbtTag;
|
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 {
|
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]
|
#[must_use]
|
||||||
pub fn calculate_target_total_mobs(&self, player_count: usize) -> i32 {
|
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;
|
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 {
|
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]
|
#[must_use]
|
||||||
pub fn override_entity(&self, entity_type: &'static EntityType) -> Self {
|
pub fn override_entity(&self, entity_type: &'static EntityType) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -431,13 +529,27 @@ impl TrialSpawnerFullConfig {
|
|||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_nbt(nbt: &NbtCompound) -> Self {
|
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
|
let normal = nbt
|
||||||
.get_compound("normal_config")
|
.get_compound("normal_config")
|
||||||
.map(TrialSpawnerConfig::from_nbt)
|
.map(TrialSpawnerConfig::from_nbt)
|
||||||
|
.or_else(|| {
|
||||||
|
nbt.get_string("normal_config")
|
||||||
|
.and_then(TrialSpawnerConfig::from_key)
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let ominous = nbt
|
let ominous = nbt
|
||||||
.get_compound("ominous_config")
|
.get_compound("ominous_config")
|
||||||
.map(TrialSpawnerConfig::from_nbt)
|
.map(TrialSpawnerConfig::from_nbt)
|
||||||
|
.or_else(|| {
|
||||||
|
nbt.get_string("ominous_config")
|
||||||
|
.and_then(TrialSpawnerConfig::from_ominous_key)
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let target_cooldown_length = nbt
|
let target_cooldown_length = nbt
|
||||||
@ -713,7 +825,78 @@ impl TrialSpawner {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.query_gametime();
|
.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.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) {
|
pub fn remove_ominous(&mut self, world: &Arc<World>, spawner_pos: BlockPos) {
|
||||||
@ -793,8 +976,9 @@ impl TrialSpawner {
|
|||||||
level_info.game_rules.spawn_mobs
|
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> {
|
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 {
|
let active_cfg = if self.is_ominous {
|
||||||
&self.config.ominous
|
&self.config.ominous
|
||||||
} else {
|
} else {
|
||||||
@ -802,7 +986,25 @@ impl TrialSpawner {
|
|||||||
};
|
};
|
||||||
let spawn_data = self.data.get_or_create_next_spawn_data(active_cfg);
|
let spawn_data = self.data.get_or_create_next_spawn_data(active_cfg);
|
||||||
let ent_type = spawn_data.and_then(|sd| sd.entity_type)?;
|
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();
|
let mut rng = rand::rng();
|
||||||
@ -840,6 +1042,40 @@ impl TrialSpawner {
|
|||||||
let yaw = rng.random::<f32>() * 360.0;
|
let yaw = rng.random::<f32>() * 360.0;
|
||||||
entity.get_entity().set_rotation(yaw, 0.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 =
|
let mut event =
|
||||||
crate::plugin::api::events::entity::trial_spawner_spawn::TrialSpawnerSpawnEvent::new(
|
crate::plugin::api::events::entity::trial_spawner_spawn::TrialSpawnerSpawnEvent::new(
|
||||||
entity.get_entity().entity_id,
|
entity.get_entity().entity_id,
|
||||||
@ -975,12 +1211,7 @@ impl TrialSpawner {
|
|||||||
return TrialSpawnerState::Inactive;
|
return TrialSpawnerState::Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newly_detected = self.data.try_detect_players(
|
let newly_detected = self.try_detect_players(world, spawner_pos);
|
||||||
world,
|
|
||||||
spawner_pos,
|
|
||||||
self.config.required_player_range,
|
|
||||||
&self.player_detector,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.data.registered_players.is_empty() {
|
if self.data.registered_players.is_empty() {
|
||||||
TrialSpawnerState::WaitingForPlayers
|
TrialSpawnerState::WaitingForPlayers
|
||||||
@ -1013,12 +1244,7 @@ impl TrialSpawner {
|
|||||||
return TrialSpawnerState::Inactive;
|
return TrialSpawnerState::Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.data.try_detect_players(
|
self.try_detect_players(world, spawner_pos);
|
||||||
world,
|
|
||||||
spawner_pos,
|
|
||||||
self.config.required_player_range,
|
|
||||||
&self.player_detector,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (total_mobs_needed, max_simultaneous, ticks_between_spawn) = {
|
let (total_mobs_needed, max_simultaneous, ticks_between_spawn) = {
|
||||||
let active_config = self.active_config();
|
let active_config = self.active_config();
|
||||||
@ -1055,7 +1281,7 @@ impl TrialSpawner {
|
|||||||
TrialSpawnerState::WaitingForRewardEjection => {
|
TrialSpawnerState::WaitingForRewardEjection => {
|
||||||
if game_time >= self.data.next_mob_spawns_at {
|
if game_time >= self.data.next_mob_spawns_at {
|
||||||
world.play_sound(
|
world.play_sound(
|
||||||
Sound::BlockTrialSpawnerSpawnItemBegin,
|
Sound::BlockTrialSpawnerOpenShutter,
|
||||||
SoundCategory::Blocks,
|
SoundCategory::Blocks,
|
||||||
&spawner_pos.to_f64(),
|
&spawner_pos.to_f64(),
|
||||||
);
|
);
|
||||||
@ -1067,12 +1293,18 @@ impl TrialSpawner {
|
|||||||
}
|
}
|
||||||
TrialSpawnerState::EjectingReward => {
|
TrialSpawnerState::EjectingReward => {
|
||||||
if game_time >= self.data.next_mob_spawns_at {
|
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(
|
world.play_sound(
|
||||||
Sound::BlockTrialSpawnerEjectItem,
|
Sound::BlockTrialSpawnerEjectItem,
|
||||||
SoundCategory::Blocks,
|
SoundCategory::Blocks,
|
||||||
&spawner_pos.to_f64(),
|
&spawner_pos.to_f64(),
|
||||||
);
|
);
|
||||||
|
self.data.next_mob_spawns_at = game_time + 20;
|
||||||
|
TrialSpawnerState::EjectingReward
|
||||||
|
} else {
|
||||||
world.play_sound(
|
world.play_sound(
|
||||||
Sound::BlockTrialSpawnerCloseShutter,
|
Sound::BlockTrialSpawnerCloseShutter,
|
||||||
SoundCategory::Blocks,
|
SoundCategory::Blocks,
|
||||||
@ -1080,11 +1312,21 @@ impl TrialSpawner {
|
|||||||
);
|
);
|
||||||
self.data.next_mob_spawns_at = game_time + 20;
|
self.data.next_mob_spawns_at = game_time + 20;
|
||||||
TrialSpawnerState::Cooldown
|
TrialSpawnerState::Cooldown
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TrialSpawnerState::EjectingReward
|
TrialSpawnerState::EjectingReward
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TrialSpawnerState::Cooldown => {
|
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 {
|
if game_time >= self.data.cooldown_ends_at {
|
||||||
self.data.reset();
|
self.data.reset();
|
||||||
if self.is_ominous {
|
if self.is_ominous {
|
||||||
@ -1187,6 +1429,33 @@ impl TrialSpawnerBlockEntity {
|
|||||||
trial_spawner: Mutex::new(TrialSpawner::default()),
|
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)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -7,7 +7,6 @@ use crate::{
|
|||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
Block, BlockDirection, BlockState, BlockStateId,
|
Block, BlockDirection, BlockState, BlockStateId,
|
||||||
damage::DamageType,
|
damage::DamageType,
|
||||||
dimension::Dimension,
|
|
||||||
fluid::{Falling, Fluid, FluidProperties, Level},
|
fluid::{Falling, Fluid, FluidProperties, Level},
|
||||||
world::WorldEvent,
|
world::WorldEvent,
|
||||||
};
|
};
|
||||||
@ -244,17 +243,12 @@ impl FluidBehaviour for FlowingLava {
|
|||||||
|
|
||||||
impl FlowingFluid for FlowingLava {
|
impl FlowingFluid for FlowingLava {
|
||||||
fn get_level_decrease_per_block(&self, world: &World) -> i32 {
|
fn get_level_decrease_per_block(&self, world: &World) -> i32 {
|
||||||
// Ultrawarm logic
|
if world.dimension.fast_lava { 1 } else { 2 }
|
||||||
if world.dimension == Dimension::THE_NETHER {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_flow_speed(&self, world: &World) -> u8 {
|
fn get_flow_speed(&self, world: &World) -> u8 {
|
||||||
// Ultrawarm logic - lava flows faster in the Nether
|
// EnvironmentAttributes.FAST_LAVA
|
||||||
if world.dimension == Dimension::THE_NETHER {
|
if world.dimension.fast_lava {
|
||||||
LAVA_FLOW_SPEED_NETHER
|
LAVA_FLOW_SPEED_NETHER
|
||||||
} else {
|
} else {
|
||||||
LAVA_FLOW_SPEED_SLOW
|
LAVA_FLOW_SPEED_SLOW
|
||||||
@ -262,12 +256,7 @@ impl FlowingFluid for FlowingLava {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_max_flow_distance(&self, world: &World) -> i32 {
|
fn get_max_flow_distance(&self, world: &World) -> i32 {
|
||||||
// Ultrawarm logic
|
if world.dimension.fast_lava { 5 } else { 3 }
|
||||||
if world.dimension == Dimension::THE_NETHER {
|
|
||||||
5
|
|
||||||
} else {
|
|
||||||
3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines if lava can convert to source blocks based on game rules.
|
/// Determines if lava can convert to source blocks based on game rules.
|
||||||
|
|||||||
@ -24,6 +24,7 @@ use crate::server::Server;
|
|||||||
use pumpkin_data::BlockDirection;
|
use pumpkin_data::BlockDirection;
|
||||||
use pumpkin_data::block_rotation::{Mirror, Rotation};
|
use pumpkin_data::block_rotation::{Mirror, Rotation};
|
||||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||||
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_inventory::screen_handler::ScreenHandlerFactory;
|
use pumpkin_inventory::screen_handler::ScreenHandlerFactory;
|
||||||
use pumpkin_protocol::java::server::play::SUseItemOn;
|
use pumpkin_protocol::java::server::play::SUseItemOn;
|
||||||
@ -460,7 +461,11 @@ pub fn drop_loot(
|
|||||||
let key = format!("minecraft:blocks/{}", block.name);
|
let key = format!("minecraft:blocks/{}", block.name);
|
||||||
if let Some(loot_table) = pumpkin_data::loot_table::get_loot_table(&key) {
|
if let Some(loot_table) = pumpkin_data::loot_table::get_loot_table(&key) {
|
||||||
let seed: i64 = rand::random();
|
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() {
|
if !items.is_empty() {
|
||||||
let mut event = crate::plugin::block::block_drop_item::BlockDropItemEvent {
|
let mut event = crate::plugin::block::block_drop_item::BlockDropItemEvent {
|
||||||
block_pos: *pos,
|
block_pos: *pos,
|
||||||
|
|||||||
@ -10,6 +10,9 @@ impl MobEffect for RaidOmenMobEffect {
|
|||||||
|
|
||||||
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
|
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
|
||||||
let world = living.entity.world.load();
|
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)
|
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
|
||||||
&& let Some(player) = entity.get_player()
|
&& let Some(player) = entity.get_player()
|
||||||
&& !player.is_spectator()
|
&& !player.is_spectator()
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
|||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use pumpkin_data::attributes::Attributes;
|
use pumpkin_data::attributes::Attributes;
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
@ -122,7 +121,7 @@ impl HoglinEntity {
|
|||||||
pub fn is_converting(&self, world: &World) -> bool {
|
pub fn is_converting(&self, world: &World) -> bool {
|
||||||
!self.is_immune_to_zombification()
|
!self.is_immune_to_zombification()
|
||||||
&& !self.mob_entity.is_no_ai()
|
&& !self.mob_entity.is_no_ai()
|
||||||
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
|
&& world.dimension.piglins_zombify
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_to_zoglin(&self) {
|
fn convert_to_zoglin(&self) {
|
||||||
|
|||||||
@ -89,17 +89,6 @@ pub struct MobEntity {
|
|||||||
last_sent_pitch: AtomicU8,
|
last_sent_pitch: AtomicU8,
|
||||||
last_sent_head_yaw: 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 {
|
impl MobEntity {
|
||||||
const AI_DISABLED_FLAG: u8 = 1;
|
const AI_DISABLED_FLAG: u8 = 1;
|
||||||
const LEFT_HANDED_FLAG: u8 = 2;
|
const LEFT_HANDED_FLAG: u8 = 2;
|
||||||
@ -535,22 +524,16 @@ impl MobEntity {
|
|||||||
let world_arc = entity.world.load();
|
let world_arc = entity.world.load();
|
||||||
let world = world_arc.as_ref();
|
let world = world_arc.as_ref();
|
||||||
|
|
||||||
// Night boundary from data/minecraft/timeline/day.json — monsters_burn keyframes:
|
let eye_block_pos = entity.get_eye_pos().to_block_pos();
|
||||||
// value=false at tick 12542 (dusk), value=true at tick 23460 (dawn).
|
if !world.monsters_burn(&eye_block_pos) {
|
||||||
// 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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vanilla: getLightLevelDependentMagicValue() — sky light at eye pos, scaled 0–1.
|
// Vanilla: getLightLevelDependentMagicValue() — sky light at eye pos, scaled 0–1.
|
||||||
let eye_block_pos = entity.get_eye_pos();
|
|
||||||
let brightness = world
|
let brightness = world
|
||||||
.level
|
.level
|
||||||
.light_engine
|
.light_engine
|
||||||
.get_sky_light_level(&world.level, &eye_block_pos.to_block_pos())
|
.get_sky_light_level(&world.level, &eye_block_pos) as f32
|
||||||
as f32
|
|
||||||
/ 15.0;
|
/ 15.0;
|
||||||
|
|
||||||
if brightness <= 0.5 {
|
if brightness <= 0.5 {
|
||||||
|
|||||||
@ -5,7 +5,6 @@ use std::sync::{
|
|||||||
|
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
@ -180,7 +179,7 @@ impl PiglinEntity {
|
|||||||
pub fn is_converting(&self, world: &World) -> bool {
|
pub fn is_converting(&self, world: &World) -> bool {
|
||||||
!self.is_immune_to_zombification()
|
!self.is_immune_to_zombification()
|
||||||
&& !self.mob_entity.is_no_ai()
|
&& !self.mob_entity.is_no_ai()
|
||||||
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
|
&& world.dimension.piglins_zombify
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use std::sync::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::dimension::Dimension;
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
use pumpkin_data::tracked_data;
|
use pumpkin_data::tracked_data;
|
||||||
@ -107,7 +106,7 @@ impl PiglinBruteEntity {
|
|||||||
pub fn is_converting(&self, world: &World) -> bool {
|
pub fn is_converting(&self, world: &World) -> bool {
|
||||||
!self.is_immune_to_zombification()
|
!self.is_immune_to_zombification()
|
||||||
&& !self.mob_entity.is_no_ai()
|
&& !self.mob_entity.is_no_ai()
|
||||||
&& world.dimension.minecraft_name != Dimension::THE_NETHER.minecraft_name
|
&& world.dimension.piglins_zombify
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@ -2030,7 +2030,7 @@ impl Entity {
|
|||||||
.on_entity_collision_fluid(fluid, caller);
|
.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
|
0.007
|
||||||
} else {
|
} else {
|
||||||
0.002_333_333
|
0.002_333_333
|
||||||
|
|||||||
@ -43,7 +43,7 @@ use crate::entity::ai::goal::wander_around::WanderAroundGoal;
|
|||||||
use crate::entity::ai::goal::{Controls, Goal};
|
use crate::entity::ai::goal::{Controls, Goal};
|
||||||
use crate::entity::ai::pathfinder::NavigatorGoal;
|
use crate::entity::ai::pathfinder::NavigatorGoal;
|
||||||
use crate::entity::experience_orb::ExperienceOrbEntity;
|
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::player::Player;
|
||||||
use crate::entity::{Entity, EntityBase};
|
use crate::entity::{Entity, EntityBase};
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
@ -1103,8 +1103,8 @@ impl Goal for WanderingTraderUseItemGoal {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let world = trader.mob_entity.living_entity.entity.world.load();
|
let world = trader.mob_entity.living_entity.entity.world.load();
|
||||||
let day_time = world.get_time_of_day() % 24000;
|
let is_dark = world.is_dark_outside();
|
||||||
let is_dark = (NIGHT_START..=NIGHT_END).contains(&day_time);
|
let is_bright = world.is_bright_outside();
|
||||||
let is_invisible = trader
|
let is_invisible = trader
|
||||||
.mob_entity
|
.mob_entity
|
||||||
.living_entity
|
.living_entity
|
||||||
@ -1114,7 +1114,7 @@ impl Goal for WanderingTraderUseItemGoal {
|
|||||||
self.goal_type = Some(PotionGoalType::Invisibility);
|
self.goal_type = Some(PotionGoalType::Invisibility);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if !is_dark && is_invisible {
|
if is_bright && is_invisible {
|
||||||
self.goal_type = Some(PotionGoalType::Milk);
|
self.goal_type = Some(PotionGoalType::Milk);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4176,6 +4176,9 @@ impl Player {
|
|||||||
&& !self.has_effect(&StatusEffect::RAID_OMEN)
|
&& !self.has_effect(&StatusEffect::RAID_OMEN)
|
||||||
{
|
{
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
|
if !world.dimension.can_start_raid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let player_pos = self.living_entity.entity.block_pos.load();
|
let player_pos = self.living_entity.entity.block_pos.load();
|
||||||
let pos_f64 = self.living_entity.entity.pos.load();
|
let pos_f64 = self.living_entity.entity.pos.load();
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
Block, BlockDirection,
|
Block, BlockDirection,
|
||||||
dimension::Dimension,
|
|
||||||
entity::EntityType,
|
entity::EntityType,
|
||||||
fluid::Fluid,
|
fluid::Fluid,
|
||||||
item::Item,
|
item::Item,
|
||||||
@ -204,10 +203,10 @@ fn try_pickup_bucket_item(
|
|||||||
Some(&Item::WATER_BUCKET)
|
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::LAVA_BUCKET.id
|
||||||
&& item.id != Item::POWDER_SNOW_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>) {
|
pub(crate) fn play_bucket_evaporation(world: &Arc<World>, position: &Vector3<f64>) {
|
||||||
|
|||||||
@ -133,8 +133,8 @@ impl ItemBehaviour for SpawnEggItem {
|
|||||||
if let Some(entity_type) = entity_from_egg(item.item.id) {
|
if let Some(entity_type) = entity_from_egg(item.item.id) {
|
||||||
let world = player.world();
|
let world = player.world();
|
||||||
|
|
||||||
if let Some(block_entity) = player.world().get_block_entity(&location)
|
if let Some(block_entity) = player.world().get_block_entity(&location) {
|
||||||
&& let Some(spawner) = block_entity
|
if let Some(spawner) = block_entity
|
||||||
.as_any()
|
.as_any()
|
||||||
.downcast_ref::<MobSpawnerBlockEntity>()
|
.downcast_ref::<MobSpawnerBlockEntity>()
|
||||||
{
|
{
|
||||||
@ -143,6 +143,16 @@ impl ItemBehaviour for SpawnEggItem {
|
|||||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||||
return;
|
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_state = world.get_block_state(&location);
|
||||||
let target_block = world.get_block(&location);
|
let target_block = world.get_block(&location);
|
||||||
|
|||||||
238
crates/pumpkin/src/world/environment.rs
Normal file
238
crates/pumpkin/src/world/environment.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -169,10 +169,14 @@ pub mod custom_bossbar;
|
|||||||
pub mod dragon_fight;
|
pub mod dragon_fight;
|
||||||
pub mod end_podium;
|
pub mod end_podium;
|
||||||
pub mod entity_tracker;
|
pub mod entity_tracker;
|
||||||
|
pub mod environment;
|
||||||
pub mod natural_spawner;
|
pub mod natural_spawner;
|
||||||
pub mod scoreboard;
|
pub mod scoreboard;
|
||||||
pub mod weather;
|
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 crate::world::natural_spawner::{SpawnState, spawn_for_chunk};
|
||||||
use pumpkin_config::lighting::LightingEngineConfig;
|
use pumpkin_config::lighting::LightingEngineConfig;
|
||||||
use pumpkin_data::effect::StatusEffect;
|
use pumpkin_data::effect::StatusEffect;
|
||||||
@ -5556,10 +5560,111 @@ impl World {
|
|||||||
Some(self.set_block_state(position, new_state_id, flags))
|
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 {
|
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);
|
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> {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
|
|||||||
@ -6,13 +6,13 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use serde::Deserialize;
|
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
|
/// # Returns
|
||||||
/// The color as an `i32`, or `None` if the input is not a valid hex color.
|
/// The color as an `i32`, or `None` if the input is not a valid hex color.
|
||||||
fn parse_hex_color(s: &str) -> Option<i32> {
|
fn parse_hex_color(s: &str) -> Option<i32> {
|
||||||
if let Some(stripped) = s.strip_prefix('#') {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -42,7 +42,10 @@ pub struct Dimension {
|
|||||||
/// Fixed day-time value in this dimension, or `None` if time progresses normally.
|
/// Fixed day-time value in this dimension, or `None` if time progresses normally.
|
||||||
#[serde(rename = "fixed_time")]
|
#[serde(rename = "fixed_time")]
|
||||||
pub fixed_time: Option<i64>,
|
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)]
|
#[serde(default)]
|
||||||
pub attributes: Option<serde_json::Value>,
|
pub attributes: Option<serde_json::Value>,
|
||||||
/// Optional timeline resource key controlling day/night progression.
|
/// Optional timeline resource key controlling day/night progression.
|
||||||
@ -84,34 +87,120 @@ pub fn build() -> TokenStream {
|
|||||||
.to_shouty_snake_case()
|
.to_shouty_snake_case()
|
||||||
);
|
);
|
||||||
|
|
||||||
// convert optional hex colors from attributes into ints
|
let attrs = dim.attributes.as_ref();
|
||||||
let sky_color = dim
|
|
||||||
.attributes
|
// Visual environment attributes
|
||||||
.as_ref()
|
let sky_color = attrs
|
||||||
.and_then(|a| a.get("minecraft:visual/sky_color"))
|
.and_then(|a| a.get("minecraft:visual/sky_color"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.and_then(parse_hex_color);
|
.and_then(parse_hex_color);
|
||||||
let fog_color = dim
|
let fog_color = attrs
|
||||||
.attributes
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|a| a.get("minecraft:visual/fog_color"))
|
.and_then(|a| a.get("minecraft:visual/fog_color"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.and_then(parse_hex_color);
|
.and_then(parse_hex_color);
|
||||||
let cloud_color = dim
|
let cloud_color = attrs
|
||||||
.attributes
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|a| a.get("minecraft:visual/cloud_color"))
|
.and_then(|a| a.get("minecraft:visual/cloud_color"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.and_then(parse_hex_color);
|
.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 {
|
let fixed_time = if let Some(t) = dim.fixed_time {
|
||||||
quote! { Some(#t) }
|
quote! { Some(#t) }
|
||||||
} else {
|
} else {
|
||||||
quote! { None }
|
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_light_level = value_to_int_provider(&dim.monster_spawn_light_level);
|
||||||
|
|
||||||
let monster_spawn_block_light_limit = dim.monster_spawn_block_light_limit;
|
let monster_spawn_block_light_limit = dim.monster_spawn_block_light_limit;
|
||||||
let ambient_light = dim.ambient_light;
|
let ambient_light = dim.ambient_light;
|
||||||
let coordinate_scale = dim.coordinate_scale;
|
let coordinate_scale = dim.coordinate_scale;
|
||||||
@ -120,6 +209,7 @@ pub fn build() -> TokenStream {
|
|||||||
let logical_height = dim.logical_height;
|
let logical_height = dim.logical_height;
|
||||||
let has_skylight = dim.has_skylight;
|
let has_skylight = dim.has_skylight;
|
||||||
let has_ceiling = dim.has_ceiling;
|
let has_ceiling = dim.has_ceiling;
|
||||||
|
|
||||||
// normalize infiniburn to always have namespace
|
// normalize infiniburn to always have namespace
|
||||||
let infiniburn = if dim.infiniburn.contains(':') {
|
let infiniburn = if dim.infiniburn.contains(':') {
|
||||||
dim.infiniburn.clone()
|
dim.infiniburn.clone()
|
||||||
@ -155,6 +245,41 @@ pub fn build() -> TokenStream {
|
|||||||
} else {
|
} else {
|
||||||
quote! { None }
|
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() {
|
let timelines_literal = if let Some(t) = timelines.clone() {
|
||||||
quote! { Some(#t) }
|
quote! { Some(#t) }
|
||||||
} else {
|
} else {
|
||||||
@ -166,6 +291,7 @@ pub fn build() -> TokenStream {
|
|||||||
id: #id,
|
id: #id,
|
||||||
minecraft_name: #minecraft_name,
|
minecraft_name: #minecraft_name,
|
||||||
fixed_time: #fixed_time,
|
fixed_time: #fixed_time,
|
||||||
|
has_fixed_time: #has_fixed_time,
|
||||||
has_skylight: #has_skylight,
|
has_skylight: #has_skylight,
|
||||||
has_ceiling: #has_ceiling,
|
has_ceiling: #has_ceiling,
|
||||||
coordinate_scale: #coordinate_scale,
|
coordinate_scale: #coordinate_scale,
|
||||||
@ -179,6 +305,25 @@ pub fn build() -> TokenStream {
|
|||||||
sky_color: #sky_color_literal,
|
sky_color: #sky_color_literal,
|
||||||
fog_color: #fog_color_literal,
|
fog_color: #fog_color_literal,
|
||||||
cloud_color: #cloud_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,
|
timelines: #timelines_literal,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -195,11 +340,48 @@ pub fn build() -> TokenStream {
|
|||||||
WeightedEntry, WeightedListIntProvider,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Dimension {
|
pub struct Dimension {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub minecraft_name: &'static str,
|
pub minecraft_name: &'static str,
|
||||||
pub fixed_time: Option<i64>,
|
pub fixed_time: Option<i64>,
|
||||||
|
pub has_fixed_time: bool,
|
||||||
pub has_skylight: bool,
|
pub has_skylight: bool,
|
||||||
pub has_ceiling: bool,
|
pub has_ceiling: bool,
|
||||||
pub coordinate_scale: f64,
|
pub coordinate_scale: f64,
|
||||||
@ -213,6 +395,21 @@ pub fn build() -> TokenStream {
|
|||||||
pub sky_color: Option<i32>,
|
pub sky_color: Option<i32>,
|
||||||
pub fog_color: Option<i32>,
|
pub fog_color: Option<i32>,
|
||||||
pub cloud_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>,
|
pub timelines: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +422,18 @@ pub fn build() -> TokenStream {
|
|||||||
_ => None
|
_ => 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 {
|
impl PartialEq for Dimension {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
|||||||
1170
tools/pumpkin-codegen/src/environment_attribute.rs
Normal file
1170
tools/pumpkin-codegen/src/environment_attribute.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -47,6 +47,7 @@ mod enchantments;
|
|||||||
mod entity_pose;
|
mod entity_pose;
|
||||||
mod entity_status;
|
mod entity_status;
|
||||||
mod entity_type;
|
mod entity_type;
|
||||||
|
mod environment_attribute;
|
||||||
mod flower_pot_transformations;
|
mod flower_pot_transformations;
|
||||||
mod fluid;
|
mod fluid;
|
||||||
mod fuels;
|
mod fuels;
|
||||||
@ -83,6 +84,7 @@ mod structures;
|
|||||||
mod tag;
|
mod tag;
|
||||||
mod tracked_data;
|
mod tracked_data;
|
||||||
mod translations;
|
mod translations;
|
||||||
|
mod trial_spawner;
|
||||||
mod version;
|
mod version;
|
||||||
mod villager;
|
mod villager;
|
||||||
mod wit;
|
mod wit;
|
||||||
@ -153,6 +155,7 @@ pub fn main() {
|
|||||||
(fuels::build, "fuels.rs"),
|
(fuels::build, "fuels.rs"),
|
||||||
(data_component::build, "data_component.rs"),
|
(data_component::build, "data_component.rs"),
|
||||||
(attributes::build, "attributes.rs"),
|
(attributes::build, "attributes.rs"),
|
||||||
|
(environment_attribute::build, "environment_attribute.rs"),
|
||||||
(effect::build, "effect.rs"),
|
(effect::build, "effect.rs"),
|
||||||
(potion::build, "potion.rs"),
|
(potion::build, "potion.rs"),
|
||||||
(potion_brewing::build, "potion_brewing.rs"),
|
(potion_brewing::build, "potion_brewing.rs"),
|
||||||
@ -170,6 +173,7 @@ pub fn main() {
|
|||||||
(map_decoration::build, "map_decoration.rs"),
|
(map_decoration::build, "map_decoration.rs"),
|
||||||
(dye_color::build, "dye_color.rs"),
|
(dye_color::build, "dye_color.rs"),
|
||||||
(block_transformer::build, "block_transformer.rs"),
|
(block_transformer::build, "block_transformer.rs"),
|
||||||
|
(trial_spawner::build, "trial_spawner.rs"),
|
||||||
];
|
];
|
||||||
build_functions.extend(remap::build());
|
build_functions.extend(remap::build());
|
||||||
|
|
||||||
|
|||||||
377
tools/pumpkin-codegen/src/trial_spawner.rs
Normal file
377
tools/pumpkin-codegen/src/trial_spawner.rs
Normal 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 = <.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user