Revert "feat: Add Mineshaft Generation (#2740)"
This reverts commit 09d23bd15f.
This commit is contained in:
parent
b55feecd74
commit
a748aae07e
@ -122,16 +122,6 @@ impl BlockState {
|
|||||||
self.state_flags & IS_SOLID_BLOCK != 0
|
self.state_flags & IS_SOLID_BLOCK != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `isSolidRender()` from Java Edition.
|
|
||||||
///
|
|
||||||
/// The cached opacity matches that predicate for every block state except
|
|
||||||
/// tinted glass, which deliberately blocks all light without being a solid
|
|
||||||
/// render block.
|
|
||||||
#[must_use]
|
|
||||||
pub const fn is_solid_render(&self) -> bool {
|
|
||||||
self.opacity == 15 && self.id.to_block().id.as_u16() != Block::TINTED_GLASS.id.as_u16()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn has_random_ticks(&self) -> bool {
|
pub const fn has_random_ticks(&self) -> bool {
|
||||||
self.state_flags & HAS_RANDOM_TICKS != 0
|
self.state_flags & HAS_RANDOM_TICKS != 0
|
||||||
@ -292,18 +282,3 @@ const WEST_SIDE_SOLID: u8 = 1 << 4;
|
|||||||
const EAST_SIDE_SOLID: u8 = 1 << 5;
|
const EAST_SIDE_SOLID: u8 = 1 << 5;
|
||||||
const DOWN_CENTER_SOLID: u8 = 1 << 6;
|
const DOWN_CENTER_SOLID: u8 = 1 << 6;
|
||||||
const UP_CENTER_SOLID: u8 = 1 << 7;
|
const UP_CENTER_SOLID: u8 = 1 << 7;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Block;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn solid_render_distinguishes_occlusion_from_collision_and_conduction() {
|
|
||||||
assert!(Block::STONE.default_state.is_solid_render());
|
|
||||||
assert!(Block::GLOWSTONE.default_state.is_solid_render());
|
|
||||||
assert!(Block::REDSTONE_BLOCK.default_state.is_solid_render());
|
|
||||||
assert!(!Block::GLASS.default_state.is_solid_render());
|
|
||||||
assert!(!Block::SLIME_BLOCK.default_state.is_solid_render());
|
|
||||||
assert!(!Block::TINTED_GLASS.default_state.is_solid_render());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use legacy_rand::{LegacyRand, LegacySplitter};
|
use legacy_rand::{LegacyRand, LegacySplitter};
|
||||||
use xoroshiro128::{WorldgenXoroshiro, Xoroshiro, XoroshiroSplitter};
|
use xoroshiro128::{Xoroshiro, XoroshiroSplitter};
|
||||||
|
|
||||||
mod gaussian;
|
mod gaussian;
|
||||||
pub mod legacy_rand;
|
pub mod legacy_rand;
|
||||||
@ -35,8 +35,6 @@ pub enum RandomGenerator {
|
|||||||
Xoroshiro(Xoroshiro),
|
Xoroshiro(Xoroshiro),
|
||||||
/// Legacy random number generator (compatible with older Minecraft versions).
|
/// Legacy random number generator (compatible with older Minecraft versions).
|
||||||
Legacy(LegacyRand),
|
Legacy(LegacyRand),
|
||||||
/// Xoroshiro source accessed through Vanilla's `WorldgenRandom` bit API.
|
|
||||||
WorldgenXoroshiro(WorldgenXoroshiro),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unified random number deriver enum for creating child RNGs.
|
/// Unified random number deriver enum for creating child RNGs.
|
||||||
|
|||||||
@ -211,102 +211,6 @@ impl RandomImpl for Xoroshiro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Xoroshiro source exposed through Java's `WorldgenRandom`/`BitRandomSource`
|
|
||||||
/// adapter.
|
|
||||||
///
|
|
||||||
/// World decoration deliberately uses this API, whose integer and bounded-number
|
|
||||||
/// methods consume different bits than `XoroshiroRandomSource` itself.
|
|
||||||
pub struct WorldgenXoroshiro {
|
|
||||||
source: Xoroshiro,
|
|
||||||
internal_next_gaussian: Option<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorldgenXoroshiro {
|
|
||||||
population_seed_fn!();
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub const fn from_seed(seed: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
source: Xoroshiro::from_seed(seed),
|
|
||||||
internal_next_gaussian: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn next(&mut self, bits: u64) -> i32 {
|
|
||||||
(self.source.next_random() >> (64 - bits)) as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GaussianGenerator for WorldgenXoroshiro {
|
|
||||||
fn stored_next_gaussian(&self) -> Option<f64> {
|
|
||||||
self.internal_next_gaussian
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
|
|
||||||
self.internal_next_gaussian = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomImpl for WorldgenXoroshiro {
|
|
||||||
fn split(&mut self) -> Self {
|
|
||||||
Self {
|
|
||||||
source: Xoroshiro::new(self.source.next_random(), self.source.next_random()),
|
|
||||||
internal_next_gaussian: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_splitter(&mut self) -> RandomDeriver {
|
|
||||||
RandomDeriver::Xoroshiro(self.source.next_splitter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_i32(&mut self) -> i32 {
|
|
||||||
self.next(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_bounded_i32(&mut self, bound: i32) -> i32 {
|
|
||||||
if (bound & bound.wrapping_sub(1)) == 0 {
|
|
||||||
(i64::from(bound).wrapping_mul(i64::from(self.next(31))) >> 31) as i32
|
|
||||||
} else {
|
|
||||||
loop {
|
|
||||||
let value = self.next(31);
|
|
||||||
let result = value % bound;
|
|
||||||
if value
|
|
||||||
.wrapping_sub(result)
|
|
||||||
.wrapping_add(bound.wrapping_sub(1))
|
|
||||||
>= 0
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_i64(&mut self) -> i64 {
|
|
||||||
let high = self.next_i32();
|
|
||||||
let low = self.next_i32();
|
|
||||||
(i64::from(high) << 32).wrapping_add(i64::from(low))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_bool(&mut self) -> bool {
|
|
||||||
self.next(1) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_f32(&mut self) -> f32 {
|
|
||||||
self.next(24) as f32 * 5.960_464_5E-8f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_f64(&mut self) -> f64 {
|
|
||||||
let high = self.next(26);
|
|
||||||
let low = self.next(27);
|
|
||||||
let bits = (i64::from(high) << 27).wrapping_add(i64::from(low));
|
|
||||||
bits as f64 * 1.110_223_024_625_156_5E-16
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_gaussian(&mut self) -> f64 {
|
|
||||||
self.calculate_gaussian()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A splitter for creating derived Xoroshiro random generators.
|
/// A splitter for creating derived Xoroshiro random generators.
|
||||||
///
|
///
|
||||||
/// This struct allows for deterministic creation of multiple independent
|
/// This struct allows for deterministic creation of multiple independent
|
||||||
@ -361,23 +265,10 @@ impl RandomDeriverImpl for XoroshiroSplitter {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::random::{RandomDeriverImpl, RandomImpl};
|
use crate::random::{RandomDeriverImpl, RandomImpl};
|
||||||
|
|
||||||
use super::{WorldgenXoroshiro, Xoroshiro, mix_stafford_13};
|
use super::{Xoroshiro, mix_stafford_13};
|
||||||
|
|
||||||
// Values checked against results from the equivalent Java source
|
// Values checked against results from the equivalent Java source
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn worldgen_adapter_matches_java_bit_random_source() {
|
|
||||||
assert_eq!(
|
|
||||||
WorldgenXoroshiro::get_population_seed(123_456_789, -37 * 16, 84 * 16),
|
|
||||||
8_245_768_125_123_811_941
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut random = WorldgenXoroshiro::from_seed(8_245_768_125_123_841_941);
|
|
||||||
assert_eq!(random.next_i32(), -618_189_195);
|
|
||||||
assert!((random.next_f32() - 0.066_260_1).abs() < f32::EPSILON);
|
|
||||||
assert_eq!(random.next_i64(), -8_355_384_254_887_254_362);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mix_stafford_13_test() {
|
fn mix_stafford_13_test() {
|
||||||
let values: [(u64, i64); 31] = [
|
let values: [(u64, i64); 31] = [
|
||||||
|
|||||||
@ -10,12 +10,12 @@ use pumpkin_data::structures::{
|
|||||||
use pumpkin_data::tag::RegistryKey;
|
use pumpkin_data::tag::RegistryKey;
|
||||||
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
|
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
|
||||||
use pumpkin_data::{BlockId, BlockStateId, tag};
|
use pumpkin_data::{BlockId, BlockStateId, tag};
|
||||||
use pumpkin_util::random::RandomImpl;
|
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
|
||||||
use pumpkin_util::random::xoroshiro128::{WorldgenXoroshiro, XoroshiroSplitter};
|
use pumpkin_util::random::{RandomImpl, get_carver_seed};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
HeightMap,
|
HeightMap,
|
||||||
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||||
random::{RandomGenerator, get_decorator_seed},
|
random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro},
|
||||||
};
|
};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
@ -66,18 +66,6 @@ enum ActiveSupplier {
|
|||||||
End(TheEndBiomeSupplier),
|
End(TheEndBiomeSupplier),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn structures_for_step(step: usize) -> Vec<StructureKeys> {
|
|
||||||
let mut structures = StructureKeys::all_names()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| StructureKeys::from_name(name))
|
|
||||||
.filter(|key| Structure::get(key).step.ordinal() == step)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
// The enum discriminants preserve the registry order emitted by the data
|
|
||||||
// extractor. Vanilla uses that per-step order as the feature-seed index.
|
|
||||||
structures.sort_unstable_by_key(|key| *key as usize);
|
|
||||||
structures
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait GenerationCache: HeightLimitView + BlockAccessor {
|
pub trait GenerationCache: HeightLimitView + BlockAccessor {
|
||||||
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
|
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
|
||||||
fn get_center_chunk(&self) -> &ProtoChunk;
|
fn get_center_chunk(&self) -> &ProtoChunk;
|
||||||
@ -1085,11 +1073,8 @@ impl ProtoChunk {
|
|||||||
let start_block_z = chunk_pos::start_block_z(center_z);
|
let start_block_z = chunk_pos::start_block_z(center_z);
|
||||||
let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z);
|
let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z);
|
||||||
|
|
||||||
let population_seed = WorldgenXoroshiro::get_population_seed(
|
let population_seed =
|
||||||
random_config.seed,
|
Xoroshiro::get_population_seed(random_config.seed, start_block_x, start_block_z);
|
||||||
start_block_x,
|
|
||||||
start_block_z,
|
|
||||||
);
|
|
||||||
|
|
||||||
for step in 0..11 {
|
for step in 0..11 {
|
||||||
Self::generate_structure_step(
|
Self::generate_structure_step(
|
||||||
@ -1117,9 +1102,8 @@ impl ProtoChunk {
|
|||||||
for (p, feature_enum) in features_to_run.into_iter().enumerate() {
|
for (p, feature_enum) in features_to_run.into_iter().enumerate() {
|
||||||
if let Some(feature) = PLACED_FEATURES.get(&feature_enum) {
|
if let Some(feature) = PLACED_FEATURES.get(&feature_enum) {
|
||||||
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
|
||||||
let mut random = RandomGenerator::WorldgenXoroshiro(
|
let mut random =
|
||||||
WorldgenXoroshiro::from_seed(decorator_seed),
|
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||||
);
|
|
||||||
|
|
||||||
feature.generate(
|
feature.generate(
|
||||||
cache,
|
cache,
|
||||||
@ -1144,7 +1128,7 @@ impl ProtoChunk {
|
|||||||
population_seed: u64,
|
population_seed: u64,
|
||||||
world_seed: i64,
|
world_seed: i64,
|
||||||
) {
|
) {
|
||||||
let mut tasks = FxHashMap::<StructureKeys, Vec<_>>::default();
|
let mut tasks = Vec::new();
|
||||||
{
|
{
|
||||||
let center_chunk = cache.get_center_chunk();
|
let center_chunk = cache.get_center_chunk();
|
||||||
let center_x = center_chunk.x;
|
let center_x = center_chunk.x;
|
||||||
@ -1156,16 +1140,14 @@ impl ProtoChunk {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let collector = match instance {
|
match instance {
|
||||||
StructureInstance::Start(pos) => pos.collector.clone(),
|
StructureInstance::Start(pos) => tasks.push(pos.collector.clone()),
|
||||||
StructureInstance::Reference(collector) => collector.clone(),
|
StructureInstance::Reference(collector) => {
|
||||||
};
|
let collector_arc = collector.clone();
|
||||||
let structure_tasks = tasks.entry(*id).or_default();
|
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
|
||||||
if !structure_tasks
|
tasks.push(collector_arc);
|
||||||
.iter()
|
}
|
||||||
.any(|task| Arc::ptr_eq(task, &collector))
|
}
|
||||||
{
|
|
||||||
structure_tasks.push(collector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1186,26 +1168,28 @@ impl ProtoChunk {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let collector = match instance {
|
match instance {
|
||||||
StructureInstance::Start(pos) => {
|
StructureInstance::Start(pos) => {
|
||||||
let start_x = chunk_pos::start_block_x(center_x);
|
let start_x = chunk_pos::start_block_x(center_x);
|
||||||
let start_z = chunk_pos::start_block_z(center_z);
|
let start_z = chunk_pos::start_block_z(center_z);
|
||||||
let end_x = start_x + 15;
|
let end_x = start_x + 15;
|
||||||
let end_z = start_z + 15;
|
let end_z = start_z + 15;
|
||||||
|
|
||||||
pos.get_bounding_box()
|
if pos
|
||||||
|
.get_bounding_box()
|
||||||
.intersects_raw_xz(start_x, start_z, end_x, end_z)
|
.intersects_raw_xz(start_x, start_z, end_x, end_z)
|
||||||
.then(|| pos.collector.clone())
|
|
||||||
}
|
|
||||||
StructureInstance::Reference(collector) => Some(collector.clone()),
|
|
||||||
};
|
|
||||||
if let Some(collector) = collector {
|
|
||||||
let structure_tasks = tasks.entry(*id).or_default();
|
|
||||||
if !structure_tasks
|
|
||||||
.iter()
|
|
||||||
.any(|task| Arc::ptr_eq(task, &collector))
|
|
||||||
{
|
{
|
||||||
structure_tasks.push(collector);
|
let collector_arc = pos.collector.clone();
|
||||||
|
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
|
||||||
|
tasks.push(collector_arc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StructureInstance::Reference(collector) => {
|
||||||
|
let collector_arc = collector.clone();
|
||||||
|
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
|
||||||
|
tasks.push(collector_arc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1214,20 +1198,15 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
|
||||||
|
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
|
||||||
|
|
||||||
let chunk = cache.get_center_chunk_mut();
|
let chunk = cache.get_center_chunk_mut();
|
||||||
for (index, key) in structures_for_step(step).into_iter().enumerate() {
|
for collector_arc in tasks {
|
||||||
let Some(structure_tasks) = tasks.remove(&key) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let decorator_seed = get_decorator_seed(population_seed, index as u64, step as u64);
|
|
||||||
let mut random =
|
|
||||||
RandomGenerator::WorldgenXoroshiro(WorldgenXoroshiro::from_seed(decorator_seed));
|
|
||||||
for collector_arc in structure_tasks {
|
|
||||||
let mut collector = collector_arc.lock().unwrap();
|
let mut collector = collector_arc.lock().unwrap();
|
||||||
collector.generate_in_chunk(chunk, block_registry, &mut random, world_seed);
|
collector.generate_in_chunk(chunk, block_registry, &mut random, world_seed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_allowed_biomes(set: &StructureSet) -> Vec<u16> {
|
pub fn get_allowed_biomes(set: &StructureSet) -> Vec<u16> {
|
||||||
@ -1291,7 +1270,9 @@ impl ProtoChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut candidates = set.structures.to_vec();
|
let mut candidates = set.structures.to_vec();
|
||||||
let mut random = create_chunk_random(seed as i64, self.x, self.z);
|
let carver_seed = get_carver_seed(seed, self.x, self.z);
|
||||||
|
let mut random: RandomGenerator =
|
||||||
|
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
|
||||||
|
|
||||||
let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum();
|
let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum();
|
||||||
|
|
||||||
@ -1626,48 +1607,3 @@ impl GenerationCache for ProtoChunk {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::structures_for_step;
|
|
||||||
use pumpkin_data::structures::{GenerationStep, StructureKeys};
|
|
||||||
use pumpkin_util::random::{
|
|
||||||
RandomGenerator, RandomImpl, get_decorator_seed, xoroshiro128::WorldgenXoroshiro,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn structure_decoration_order_matches_the_vanilla_registry() {
|
|
||||||
assert_eq!(
|
|
||||||
structures_for_step(GenerationStep::UndergroundStructures.ordinal()),
|
|
||||||
[
|
|
||||||
StructureKeys::Mineshaft,
|
|
||||||
StructureKeys::MineshaftMesa,
|
|
||||||
StructureKeys::BuriedTreasure,
|
|
||||||
StructureKeys::TrailRuins,
|
|
||||||
StructureKeys::TrialChambers,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mineshaft_decoration_streams_match_vanilla_26_2() {
|
|
||||||
let population_seed =
|
|
||||||
WorldgenXoroshiro::get_population_seed(123_456_789, -37 * 16, 84 * 16);
|
|
||||||
assert_eq!(population_seed, 8_245_768_125_123_811_941);
|
|
||||||
|
|
||||||
for (index, expected_int, expected_float, expected_long) in [
|
|
||||||
(0, -618_189_195, 0.066_260_1, -8_355_384_254_887_254_362),
|
|
||||||
(1, 1_059_888_632, 0.664_247_5, 6_337_689_326_094_498_930),
|
|
||||||
] {
|
|
||||||
let seed = get_decorator_seed(
|
|
||||||
population_seed,
|
|
||||||
index,
|
|
||||||
GenerationStep::UndergroundStructures.ordinal() as u64,
|
|
||||||
);
|
|
||||||
let mut random = RandomGenerator::WorldgenXoroshiro(WorldgenXoroshiro::from_seed(seed));
|
|
||||||
assert_eq!(random.next_i32(), expected_int);
|
|
||||||
assert!((random.next_f32() - expected_float).abs() < f32::EPSILON);
|
|
||||||
assert_eq!(random.next_i64(), expected_long);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -11,23 +11,14 @@ use crate::{
|
|||||||
noise::router::multi_noise_sampler::MultiNoiseSampler,
|
noise::router::multi_noise_sampler::MultiNoiseSampler,
|
||||||
structure::structures::{
|
structure::structures::{
|
||||||
StructureGenerator, StructureGeneratorContext, StructurePosition,
|
StructureGenerator, StructureGeneratorContext, StructurePosition,
|
||||||
buried_treasure::BuriedTreasureGenerator,
|
buried_treasure::BuriedTreasureGenerator, create_chunk_random,
|
||||||
create_chunk_random,
|
desert_pyramid::DesertPyramidGenerator, end_city::EndCityGenerator,
|
||||||
desert_pyramid::DesertPyramidGenerator,
|
igloo::IglooGenerator, jigsaw::JigsawGenerator, jungle_temple::JungleTempleGenerator,
|
||||||
end_city::EndCityGenerator,
|
mansion::MansionGenerator, mineshaft::MineshaftGenerator,
|
||||||
igloo::IglooGenerator,
|
nether_fortress::NetherFortressGenerator, nether_fossil::NetherFossilGenerator,
|
||||||
jigsaw::JigsawGenerator,
|
ocean_monument::OceanMonumentGenerator, ocean_ruin::OceanRuinGenerator,
|
||||||
jungle_temple::JungleTempleGenerator,
|
ruined_portal::RuinedPortalGenerator, shipwreck::ShipwreckGenerator,
|
||||||
mansion::MansionGenerator,
|
stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator,
|
||||||
mineshaft::{MineshaftGenerator, MineshaftType},
|
|
||||||
nether_fortress::NetherFortressGenerator,
|
|
||||||
nether_fossil::NetherFossilGenerator,
|
|
||||||
ocean_monument::OceanMonumentGenerator,
|
|
||||||
ocean_ruin::OceanRuinGenerator,
|
|
||||||
ruined_portal::RuinedPortalGenerator,
|
|
||||||
shipwreck::ShipwreckGenerator,
|
|
||||||
stronghold::StrongholdGenerator,
|
|
||||||
swamp_hut::SwampHutGenerator,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -127,11 +118,7 @@ pub fn generate_structure_position(
|
|||||||
StructureKeys::Monument => OceanMonumentGenerator.get_structure_position(context),
|
StructureKeys::Monument => OceanMonumentGenerator.get_structure_position(context),
|
||||||
StructureKeys::Mineshaft | StructureKeys::MineshaftMesa => {
|
StructureKeys::Mineshaft | StructureKeys::MineshaftMesa => {
|
||||||
let generator = MineshaftGenerator {
|
let generator = MineshaftGenerator {
|
||||||
mineshaft_type: if *key == StructureKeys::MineshaftMesa {
|
is_mesa: *key == StructureKeys::MineshaftMesa,
|
||||||
MineshaftType::Mesa
|
|
||||||
} else {
|
|
||||||
MineshaftType::Normal
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
generator.get_structure_position(context)
|
generator.get_structure_position(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use pumpkin_data::structures::{
|
|||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::floor_div,
|
math::floor_div,
|
||||||
random::{
|
random::{
|
||||||
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
|
RandomGenerator, RandomImpl, get_carver_seed, get_region_seed, legacy_rand::LegacyRand,
|
||||||
xoroshiro128::Xoroshiro,
|
xoroshiro128::Xoroshiro,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -17,7 +17,7 @@ use crate::ProtoChunk;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use pumpkin_data::structures::StructureKeys;
|
use pumpkin_data::structures::StructureKeys;
|
||||||
|
|
||||||
use super::structures::{StructurePosition, create_chunk_random};
|
use super::structures::StructurePosition;
|
||||||
/// A thread-safe global cache for structures that require world-wide placement calculations
|
/// A thread-safe global cache for structures that require world-wide placement calculations
|
||||||
/// rather than localized chunk-based math (e.g., Strongholds using Concentric Rings).
|
/// rather than localized chunk-based math (e.g., Strongholds using Concentric Rings).
|
||||||
///
|
///
|
||||||
@ -267,7 +267,8 @@ fn should_generate_frequency(
|
|||||||
random.next_f32() < frequency
|
random.next_f32() < frequency
|
||||||
}
|
}
|
||||||
FrequencyReductionMethod::LegacyType3 => {
|
FrequencyReductionMethod::LegacyType3 => {
|
||||||
let mut random = create_chunk_random(seed, chunk_x, chunk_z);
|
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z);
|
||||||
|
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
|
||||||
random.next_f64() < f64::from(frequency)
|
random.next_f64() < f64::from(frequency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,10 +354,7 @@ fn is_start_chunk_random_spread(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use pumpkin_data::{
|
use pumpkin_data::{
|
||||||
dimension::Dimension,
|
dimension::Dimension,
|
||||||
structures::{
|
structures::{RandomSpreadStructurePlacement, StructurePlacementCalculator, StructureSet},
|
||||||
FrequencyReductionMethod, RandomSpreadStructurePlacement, StructurePlacementCalculator,
|
|
||||||
StructureSet,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use pumpkin_util::random::{
|
use pumpkin_util::random::{
|
||||||
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
|
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
|
||||||
@ -369,7 +367,7 @@ mod tests {
|
|||||||
get_world_gen,
|
get_world_gen,
|
||||||
structure::placement::{
|
structure::placement::{
|
||||||
GlobalStructureCache, apply_frequency_reduction, get_start_chunk_random_spread,
|
GlobalStructureCache, apply_frequency_reduction, get_start_chunk_random_spread,
|
||||||
is_start_chunk, should_generate_frequency, should_generate_structure,
|
is_start_chunk, should_generate_structure,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -393,26 +391,6 @@ mod tests {
|
|||||||
assert_eq!(z, 4);
|
assert_eq!(z, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn legacy_type_3_matches_vanilla_large_feature_random() {
|
|
||||||
assert!(!should_generate_frequency(
|
|
||||||
FrequencyReductionMethod::LegacyType3,
|
|
||||||
123_456_789,
|
|
||||||
-37,
|
|
||||||
84,
|
|
||||||
0,
|
|
||||||
0.507_83,
|
|
||||||
));
|
|
||||||
assert!(should_generate_frequency(
|
|
||||||
FrequencyReductionMethod::LegacyType3,
|
|
||||||
123_456_789,
|
|
||||||
-37,
|
|
||||||
84,
|
|
||||||
99,
|
|
||||||
0.507_831,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pillager_outposts_respect_the_village_exclusion_zone() {
|
fn pillager_outposts_respect_the_village_exclusion_zone() {
|
||||||
let seed = 0;
|
let seed = 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -579,9 +579,7 @@ impl StructurePiecesCollector {
|
|||||||
let chunk_z = start_block_z(chunk.z);
|
let chunk_z = start_block_z(chunk.z);
|
||||||
let chunk_box = BlockBox::new(
|
let chunk_box = BlockBox::new(
|
||||||
chunk_x,
|
chunk_x,
|
||||||
// Vanilla's writable structure area deliberately excludes the
|
chunk.bottom_y() as i32,
|
||||||
// dimension's bottommost block layer.
|
|
||||||
chunk.bottom_y() as i32 + 1,
|
|
||||||
chunk_z,
|
chunk_z,
|
||||||
chunk_x + 15,
|
chunk_x + 15,
|
||||||
chunk.bottom_y() as i32 + chunk.height() as i32 - 1,
|
chunk.bottom_y() as i32 + chunk.height() as i32 - 1,
|
||||||
|
|||||||
@ -3192,28 +3192,6 @@ impl Entity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn remove_passenger_on_disconnect(&self, passenger_id: i32) {
|
|
||||||
let mut passengers = self.passengers.lock().await;
|
|
||||||
if let Some(index) = passengers
|
|
||||||
.iter()
|
|
||||||
.position(|passenger| passenger.get_entity().entity_id == passenger_id)
|
|
||||||
{
|
|
||||||
let passenger = passengers.remove(index);
|
|
||||||
*passenger.get_entity().vehicle.lock().await = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let passenger_ids: Vec<VarInt> = passengers
|
|
||||||
.iter()
|
|
||||||
.map(|passenger| VarInt(passenger.get_entity().entity_id))
|
|
||||||
.collect();
|
|
||||||
drop(passengers);
|
|
||||||
|
|
||||||
self.world.load().broadcast_to_chunk(
|
|
||||||
self.chunk_pos.load(),
|
|
||||||
&CSetPassengers::new(VarInt(self.entity_id), &passenger_ids),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub async fn remove_passenger(&self, passenger_id: i32) {
|
pub async fn remove_passenger(&self, passenger_id: i32) {
|
||||||
let mut passengers = self.passengers.lock().await;
|
let mut passengers = self.passengers.lock().await;
|
||||||
|
|||||||
@ -120,45 +120,6 @@ use pumpkin_world::chunk_system::ChunkLoading;
|
|||||||
const MAX_CACHED_SIGNATURES: u8 = 128; // Vanilla: 128
|
const MAX_CACHED_SIGNATURES: u8 = 128; // Vanilla: 128
|
||||||
const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
|
const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
|
||||||
|
|
||||||
fn write_root_vehicle(nbt: &mut NbtCompound, uuid: Uuid) {
|
|
||||||
let value = uuid.as_u128();
|
|
||||||
let mut root_vehicle = NbtCompound::new();
|
|
||||||
root_vehicle.put(
|
|
||||||
"Attach",
|
|
||||||
NbtTag::IntArray(vec![
|
|
||||||
(value >> 96) as i32,
|
|
||||||
(value >> 64) as i32,
|
|
||||||
(value >> 32) as i32,
|
|
||||||
value as i32,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
nbt.put("RootVehicle", NbtTag::Compound(root_vehicle));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_root_vehicle(nbt: &NbtCompound) -> Option<Uuid> {
|
|
||||||
let root_vehicle = nbt.get_compound("RootVehicle")?;
|
|
||||||
let uuid = if let Some([most, more, less, least]) = root_vehicle.get_int_array("Attach") {
|
|
||||||
[*most, *more, *less, *least]
|
|
||||||
} else {
|
|
||||||
let [most, more, less, least] = root_vehicle.get_list("Attach")? else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
[
|
|
||||||
most.extract_int()?,
|
|
||||||
more.extract_int()?,
|
|
||||||
less.extract_int()?,
|
|
||||||
least.extract_int()?,
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Uuid::from_u128(
|
|
||||||
(uuid[0] as u32 as u128) << 96
|
|
||||||
| (uuid[1] as u32 as u128) << 64
|
|
||||||
| (uuid[2] as u32 as u128) << 32
|
|
||||||
| uuid[3] as u32 as u128,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DATA_VERSION: i32 = 4903; // 26.2
|
pub const DATA_VERSION: i32 = 4903; // 26.2
|
||||||
|
|
||||||
/// Food exhaustion applied for every block a player mines.
|
/// Food exhaustion applied for every block a player mines.
|
||||||
@ -551,7 +512,6 @@ pub struct Player {
|
|||||||
pub experience_pick_up_delay: Mutex<u32>,
|
pub experience_pick_up_delay: Mutex<u32>,
|
||||||
pub chunk_manager: Mutex<ChunkManager>,
|
pub chunk_manager: Mutex<ChunkManager>,
|
||||||
pub has_played_before: AtomicBool,
|
pub has_played_before: AtomicBool,
|
||||||
root_vehicle_uuid: AtomicCell<Option<Uuid>>,
|
|
||||||
pub chat_session: Arc<Mutex<ChatSession>>,
|
pub chat_session: Arc<Mutex<ChatSession>>,
|
||||||
pub signature_cache: Mutex<MessageCache>,
|
pub signature_cache: Mutex<MessageCache>,
|
||||||
pub player_screen_handler: Arc<Mutex<PlayerScreenHandler>>,
|
pub player_screen_handler: Arc<Mutex<PlayerScreenHandler>>,
|
||||||
@ -790,7 +750,6 @@ impl Player {
|
|||||||
last_food_saturation: AtomicBool::new(true),
|
last_food_saturation: AtomicBool::new(true),
|
||||||
subscribed_debug_sample: AtomicBool::new(false),
|
subscribed_debug_sample: AtomicBool::new(false),
|
||||||
has_played_before: AtomicBool::new(false),
|
has_played_before: AtomicBool::new(false),
|
||||||
root_vehicle_uuid: AtomicCell::new(None),
|
|
||||||
chat_session: Arc::new(Mutex::new(ChatSession::default())), // Placeholder value until the player actually sets their session id
|
chat_session: Arc::new(Mutex::new(ChatSession::default())), // Placeholder value until the player actually sets their session id
|
||||||
signature_cache: Mutex::new(MessageCache::default()),
|
signature_cache: Mutex::new(MessageCache::default()),
|
||||||
player_screen_handler: player_screen_handler.clone(),
|
player_screen_handler: player_screen_handler.clone(),
|
||||||
@ -959,16 +918,6 @@ impl Player {
|
|||||||
|
|
||||||
/// Removes the [`Player`] out of the current [`World`].
|
/// Removes the [`Player`] out of the current [`World`].
|
||||||
pub async fn remove(self: &Arc<Self>) {
|
pub async fn remove(self: &Arc<Self>) {
|
||||||
let vehicle = self.living_entity.entity.vehicle.lock().await.clone();
|
|
||||||
if let Some(vehicle) = vehicle {
|
|
||||||
self.root_vehicle_uuid
|
|
||||||
.store(Some(vehicle.get_entity().entity_uuid));
|
|
||||||
vehicle
|
|
||||||
.get_entity()
|
|
||||||
.remove_passenger_on_disconnect(self.entity_id())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.stats
|
self.stats
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
@ -1015,21 +964,6 @@ impl Player {
|
|||||||
//self.world().level.list_cached();
|
//self.world().level.list_cached();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn try_restore_vehicle(self: &Arc<Self>, vehicle: &Arc<dyn EntityBase>) {
|
|
||||||
let Some(expected_uuid) = self.root_vehicle_uuid.swap(None) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if vehicle.get_entity().entity_uuid != expected_uuid {
|
|
||||||
self.root_vehicle_uuid.store(Some(expected_uuid));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicle
|
|
||||||
.get_entity()
|
|
||||||
.add_passenger(vehicle.clone(), self.clone())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
|
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
@ -4733,18 +4667,6 @@ impl NBTStorage for Player {
|
|||||||
nbt.put_bool("SpawnForced", respawn.force);
|
nbt.put_bool("SpawnForced", respawn.force);
|
||||||
}
|
}
|
||||||
nbt.put_int("XpSeed", self.enchantment_seed.load(Ordering::Relaxed));
|
nbt.put_int("XpSeed", self.enchantment_seed.load(Ordering::Relaxed));
|
||||||
let vehicle_uuid = self
|
|
||||||
.living_entity
|
|
||||||
.entity
|
|
||||||
.vehicle
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.map(|vehicle| vehicle.get_entity().entity_uuid)
|
|
||||||
.or_else(|| self.root_vehicle_uuid.load());
|
|
||||||
if let Some(vehicle_uuid) = vehicle_uuid {
|
|
||||||
write_root_vehicle(nbt, vehicle_uuid);
|
|
||||||
}
|
|
||||||
self.stats.lock().await.write_nbt(nbt);
|
self.stats.lock().await.write_nbt(nbt);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -4815,7 +4737,6 @@ impl NBTStorage for Player {
|
|||||||
nbt.get_int("XpSeed").unwrap_or(rand::random()),
|
nbt.get_int("XpSeed").unwrap_or(rand::random()),
|
||||||
Ordering::Relaxed,
|
Ordering::Relaxed,
|
||||||
);
|
);
|
||||||
self.root_vehicle_uuid.store(read_root_vehicle(nbt));
|
|
||||||
self.stats.lock().await.read_nbt(nbt);
|
self.stats.lock().await.read_nbt(nbt);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -5742,45 +5663,3 @@ impl InventoryPlayer for Player {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{read_root_vehicle, write_root_vehicle};
|
|
||||||
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn root_vehicle_uuid_round_trips_with_vanilla_shape() {
|
|
||||||
let expected = Uuid::from_u128(0xFEDC_BA98_7654_3210_89AB_CDEF_0123_4567);
|
|
||||||
let mut nbt = NbtCompound::new();
|
|
||||||
|
|
||||||
write_root_vehicle(&mut nbt, expected);
|
|
||||||
|
|
||||||
assert_eq!(read_root_vehicle(&nbt), Some(expected));
|
|
||||||
assert!(
|
|
||||||
nbt.get_compound("RootVehicle")
|
|
||||||
.and_then(|root| root.get_int_array("Attach"))
|
|
||||||
.is_some()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn root_vehicle_uuid_accepts_integer_lists() {
|
|
||||||
let expected = Uuid::from_u128(0xFEDC_BA98_7654_3210_89AB_CDEF_0123_4567);
|
|
||||||
let value = expected.as_u128();
|
|
||||||
let mut root_vehicle = NbtCompound::new();
|
|
||||||
root_vehicle.put(
|
|
||||||
"Attach",
|
|
||||||
NbtTag::List(vec![
|
|
||||||
NbtTag::Int((value >> 96) as i32),
|
|
||||||
NbtTag::Int((value >> 64) as i32),
|
|
||||||
NbtTag::Int((value >> 32) as i32),
|
|
||||||
NbtTag::Int(value as i32),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
let mut nbt = NbtCompound::new();
|
|
||||||
nbt.put("RootVehicle", NbtTag::Compound(root_vehicle));
|
|
||||||
|
|
||||||
assert_eq!(read_root_vehicle(&nbt), Some(expected));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -440,7 +440,6 @@ impl EntityBase for ArrowEntity {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
|
||||||
fn on_hit(&self, hit: ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
fn on_hit(&self, hit: ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
@ -505,14 +504,7 @@ impl EntityBase for ArrowEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let damage_succeeded = target
|
let damage_succeeded = target
|
||||||
.damage_with_context(
|
.damage(&*target, damage as f32, DamageType::ARROW)
|
||||||
&*target,
|
|
||||||
damage as f32,
|
|
||||||
DamageType::ARROW,
|
|
||||||
Some(hit_pos),
|
|
||||||
None,
|
|
||||||
Some(self),
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(living) = target.get_living_entity() {
|
if let Some(living) = target.get_living_entity() {
|
||||||
|
|||||||
@ -1,43 +1,22 @@
|
|||||||
mod chest;
|
|
||||||
mod container;
|
|
||||||
mod furnace;
|
|
||||||
mod hopper;
|
|
||||||
mod rideable;
|
|
||||||
mod tnt;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
|
|
||||||
use pumpkin_protocol::java::server::play::SPlayerInput;
|
use pumpkin_protocol::java::server::play::SPlayerInput;
|
||||||
use rand::RngExt;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity::{
|
entity::{
|
||||||
Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, living::LivingEntity,
|
Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity, player::Player,
|
||||||
player::Player,
|
|
||||||
},
|
},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::block_properties::{BlockProperties, PoweredRailLikeProperties};
|
use pumpkin_data::block_properties::{BlockProperties, PoweredRailLikeProperties};
|
||||||
use pumpkin_data::damage::DamageType;
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::item::Item;
|
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::tag::{self, Taggable};
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_util::GameMode;
|
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use pumpkin_world::inventory::Inventory;
|
|
||||||
|
|
||||||
use crate::entity::vehicle::vehicle::VehicleEntity;
|
use crate::entity::vehicle::vehicle::VehicleEntity;
|
||||||
use chest::ChestMinecart;
|
|
||||||
use container::MinecartInventory;
|
|
||||||
use furnace::FurnaceMinecart;
|
|
||||||
use hopper::HopperMinecart;
|
|
||||||
use rideable::RideableMinecart;
|
|
||||||
use tnt::TntMinecart;
|
|
||||||
|
|
||||||
const fn get_exits(
|
const fn get_exits(
|
||||||
shape: pumpkin_data::block_properties::RailShape,
|
shape: pumpkin_data::block_properties::RailShape,
|
||||||
@ -59,96 +38,21 @@ const fn get_exits(
|
|||||||
|
|
||||||
pub struct MinecartEntity {
|
pub struct MinecartEntity {
|
||||||
pub vehicle: VehicleEntity,
|
pub vehicle: VehicleEntity,
|
||||||
kind: MinecartKind,
|
pub active: AtomicBool,
|
||||||
}
|
pub tnt_fuse: AtomicI32,
|
||||||
|
|
||||||
enum MinecartKind {
|
|
||||||
Rideable(RideableMinecart),
|
|
||||||
Chest(ChestMinecart),
|
|
||||||
Furnace(FurnaceMinecart),
|
|
||||||
Hopper(HopperMinecart),
|
|
||||||
Tnt(TntMinecart),
|
|
||||||
Other,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MinecartEntity {
|
impl MinecartEntity {
|
||||||
const DEFAULT_GRAVITY: f64 = 0.04;
|
pub const fn new(entity: Entity) -> Self {
|
||||||
const WATER_GRAVITY: f64 = 0.005;
|
|
||||||
|
|
||||||
pub fn new(entity: Entity) -> Self {
|
|
||||||
let kind = match entity.entity_type.id {
|
|
||||||
id if id == EntityType::MINECART.id => MinecartKind::Rideable(RideableMinecart),
|
|
||||||
id if id == EntityType::CHEST_MINECART.id => MinecartKind::Chest(ChestMinecart::new()),
|
|
||||||
id if id == EntityType::FURNACE_MINECART.id => {
|
|
||||||
MinecartKind::Furnace(FurnaceMinecart::new())
|
|
||||||
}
|
|
||||||
id if id == EntityType::HOPPER_MINECART.id => {
|
|
||||||
MinecartKind::Hopper(HopperMinecart::new())
|
|
||||||
}
|
|
||||||
id if id == EntityType::TNT_MINECART.id => MinecartKind::Tnt(TntMinecart::new()),
|
|
||||||
_ => MinecartKind::Other,
|
|
||||||
};
|
|
||||||
Self {
|
Self {
|
||||||
vehicle: VehicleEntity::new(entity),
|
vehicle: VehicleEntity::new(entity),
|
||||||
kind,
|
active: AtomicBool::new(false),
|
||||||
|
tnt_fuse: AtomicI32::new(-1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn container(&self) -> Option<&Arc<MinecartInventory>> {
|
impl NBTStorage for MinecartEntity {}
|
||||||
match &self.kind {
|
|
||||||
MinecartKind::Chest(minecart) => Some(minecart.inventory()),
|
|
||||||
MinecartKind::Hopper(minecart) => Some(minecart.inventory()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn drop_item(&self) -> Option<&'static Item> {
|
|
||||||
match &self.kind {
|
|
||||||
MinecartKind::Chest(_) => Some(&Item::CHEST_MINECART),
|
|
||||||
MinecartKind::Furnace(_) => Some(&Item::FURNACE_MINECART),
|
|
||||||
MinecartKind::Hopper(_) => Some(&Item::HOPPER_MINECART),
|
|
||||||
MinecartKind::Tnt(_) => Some(&Item::TNT_MINECART),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn apply_gravity(mut velocity: Vector3<f64>, touching_water: bool) -> Vector3<f64> {
|
|
||||||
velocity.y -= if touching_water {
|
|
||||||
Self::WATER_GRAVITY
|
|
||||||
} else {
|
|
||||||
Self::DEFAULT_GRAVITY
|
|
||||||
};
|
|
||||||
velocity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NBTStorage for MinecartEntity {
|
|
||||||
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
self.vehicle.entity.write_nbt(nbt).await;
|
|
||||||
match &self.kind {
|
|
||||||
MinecartKind::Chest(minecart) => minecart.write_nbt(nbt).await,
|
|
||||||
MinecartKind::Furnace(minecart) => minecart.write_nbt(nbt),
|
|
||||||
MinecartKind::Hopper(minecart) => minecart.write_nbt(nbt).await,
|
|
||||||
MinecartKind::Tnt(minecart) => minecart.write_nbt(nbt),
|
|
||||||
MinecartKind::Rideable(_) | MinecartKind::Other => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
self.vehicle.entity.read_nbt_non_mut(nbt).await;
|
|
||||||
match &self.kind {
|
|
||||||
MinecartKind::Chest(minecart) => minecart.read_nbt(nbt),
|
|
||||||
MinecartKind::Furnace(minecart) => minecart.read_nbt(nbt),
|
|
||||||
MinecartKind::Hopper(minecart) => minecart.read_nbt(nbt),
|
|
||||||
MinecartKind::Tnt(minecart) => minecart.read_nbt(nbt),
|
|
||||||
MinecartKind::Rideable(_) | MinecartKind::Other => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EntityBase for MinecartEntity {
|
impl EntityBase for MinecartEntity {
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
@ -159,9 +63,6 @@ impl EntityBase for MinecartEntity {
|
|||||||
) -> EntityBaseFuture<'a, ()> {
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
self.vehicle.tick();
|
self.vehicle.tick();
|
||||||
if let MinecartKind::Furnace(minecart) = &self.kind {
|
|
||||||
minecart.tick(&self.vehicle.entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
let world = self.vehicle.entity.world.load();
|
let world = self.vehicle.entity.world.load();
|
||||||
let pos = self.vehicle.entity.pos.load();
|
let pos = self.vehicle.entity.pos.load();
|
||||||
@ -203,21 +104,11 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_on_rails {
|
|
||||||
let velocity = Self::apply_gravity(
|
|
||||||
self.vehicle.entity.velocity.load(),
|
|
||||||
self.vehicle.entity.touching_water.load(Ordering::Relaxed),
|
|
||||||
);
|
|
||||||
self.vehicle.entity.velocity.store(velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_powered_rail || is_activator_rail {
|
if is_powered_rail || is_activator_rail {
|
||||||
let props = PoweredRailLikeProperties::from_state_id(state_id, block);
|
let props = PoweredRailLikeProperties::from_state_id(state_id, block);
|
||||||
let powered = props.powered;
|
let powered = props.powered;
|
||||||
|
|
||||||
if is_activator_rail && let MinecartKind::Hopper(minecart) = &self.kind {
|
self.active.store(powered, Ordering::Relaxed);
|
||||||
minecart.set_enabled(!powered);
|
|
||||||
}
|
|
||||||
|
|
||||||
if powered {
|
if powered {
|
||||||
if is_powered_rail {
|
if is_powered_rail {
|
||||||
@ -243,27 +134,27 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
self.vehicle.entity.send_velocity();
|
self.vehicle.entity.send_velocity();
|
||||||
} else if is_activator_rail {
|
} else if is_activator_rail {
|
||||||
match &self.kind {
|
let passengers = self.vehicle.entity.passengers.lock().await.clone();
|
||||||
MinecartKind::Tnt(minecart) => {
|
|
||||||
minecart.prime(&self.vehicle.entity, 80);
|
|
||||||
}
|
|
||||||
MinecartKind::Rideable(_) => {
|
|
||||||
let passengers =
|
|
||||||
self.vehicle.entity.passengers.lock().await.clone();
|
|
||||||
for passenger in passengers {
|
for passenger in passengers {
|
||||||
self.vehicle
|
let passenger_id = passenger.get_entity().entity_id;
|
||||||
.entity
|
self.vehicle.entity.remove_passenger(passenger_id).await;
|
||||||
.remove_passenger(passenger.get_entity().entity_id)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if self.vehicle.get_hurt_time() == 0 {
|
|
||||||
|
if self.vehicle.entity.entity_type.id == EntityType::TNT_MINECART.id {
|
||||||
|
let fuse = self.tnt_fuse.load(Ordering::Relaxed);
|
||||||
|
if fuse == -1 {
|
||||||
|
self.tnt_fuse.store(80, Ordering::Relaxed);
|
||||||
|
world.play_sound(
|
||||||
|
pumpkin_data::sound::Sound::EntityTntPrimed,
|
||||||
|
pumpkin_data::sound::SoundCategory::Blocks,
|
||||||
|
&pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if self.vehicle.get_hurt_time() == 0 {
|
||||||
self.vehicle.set_hurt_dir(-self.vehicle.get_hurt_dir());
|
self.vehicle.set_hurt_dir(-self.vehicle.get_hurt_dir());
|
||||||
self.vehicle.set_hurt_time(10);
|
self.vehicle.set_hurt_time(10);
|
||||||
self.vehicle.set_damage(50.0);
|
self.vehicle.set_damage(50.0);
|
||||||
self.vehicle.send_wobble_metadata();
|
// TODO: Send entity status
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_powered_rail {
|
} else if is_powered_rail {
|
||||||
@ -277,10 +168,27 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let MinecartKind::Tnt(minecart) = &self.kind
|
if self.vehicle.entity.entity_type.id == EntityType::TNT_MINECART.id {
|
||||||
&& minecart.tick(&self.vehicle.entity).await
|
let fuse = self.tnt_fuse.load(Ordering::Relaxed);
|
||||||
{
|
if fuse > 0 {
|
||||||
return;
|
let new_fuse = fuse - 1;
|
||||||
|
self.tnt_fuse.store(new_fuse, Ordering::Relaxed);
|
||||||
|
|
||||||
|
if new_fuse % 2 == 0 {
|
||||||
|
world.spawn_particle(
|
||||||
|
pos,
|
||||||
|
Vector3::new(0.0, 0.0, 0.0),
|
||||||
|
0.0,
|
||||||
|
1,
|
||||||
|
pumpkin_data::particle::Particle::Smoke,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_fuse == 0 {
|
||||||
|
self.vehicle.entity.remove().await;
|
||||||
|
world.explode(pos, 4.0).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut velocity = self.vehicle.entity.velocity.load();
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
@ -431,24 +339,6 @@ impl EntityBase for MinecartEntity {
|
|||||||
|
|
||||||
if velocity.length() > 0.001 {
|
if velocity.length() > 0.001 {
|
||||||
self.move_entity(caller, velocity).await;
|
self.move_entity(caller, velocity).await;
|
||||||
|
|
||||||
if let MinecartKind::Tnt(minecart) = &self.kind
|
|
||||||
&& self
|
|
||||||
.vehicle
|
|
||||||
.entity
|
|
||||||
.horizontal_collision
|
|
||||||
.load(Ordering::Relaxed)
|
|
||||||
&& velocity.x.mul_add(velocity.x, velocity.z * velocity.z) >= 0.01
|
|
||||||
{
|
|
||||||
minecart
|
|
||||||
.explode(
|
|
||||||
&self.vehicle.entity,
|
|
||||||
velocity.x.mul_add(velocity.x, velocity.z * velocity.z),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_pos = self.vehicle.entity.pos.load();
|
let new_pos = self.vehicle.entity.pos.load();
|
||||||
|
|
||||||
let passengers = self.vehicle.entity.passengers.lock().await;
|
let passengers = self.vehicle.entity.passengers.lock().await;
|
||||||
@ -460,7 +350,7 @@ impl EntityBase for MinecartEntity {
|
|||||||
self.vehicle.entity.send_pos_rot();
|
self.vehicle.entity.send_pos_rot();
|
||||||
|
|
||||||
#[allow(clippy::useless_let_if_seq)]
|
#[allow(clippy::useless_let_if_seq)]
|
||||||
let mut friction = 0.95; // Vanilla minecart air drag
|
let mut friction = 0.98; // Default off-rail air drag
|
||||||
|
|
||||||
if is_on_rails {
|
if is_on_rails {
|
||||||
let passengers = self.vehicle.entity.passengers.lock().await;
|
let passengers = self.vehicle.entity.passengers.lock().await;
|
||||||
@ -489,23 +379,12 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut next_vel =
|
let mut next_vel = velocity.multiply(friction, friction, friction);
|
||||||
if is_on_rails && let MinecartKind::Furnace(minecart) = &self.kind {
|
|
||||||
minecart.velocity(&self.vehicle.entity, velocity)
|
|
||||||
} else if is_on_rails && let Some(inventory) = self.container() {
|
|
||||||
container::velocity(&self.vehicle.entity, inventory, velocity).await
|
|
||||||
} else {
|
|
||||||
velocity.multiply(friction, friction, friction)
|
|
||||||
};
|
|
||||||
if next_vel.length() < 0.005 {
|
if next_vel.length() < 0.005 {
|
||||||
next_vel = Vector3::new(0.0, 0.0, 0.0);
|
next_vel = Vector3::new(0.0, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
self.vehicle.entity.velocity.store(next_vel);
|
self.vehicle.entity.velocity.store(next_vel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let MinecartKind::Hopper(minecart) = &self.kind {
|
|
||||||
minecart.tick(&self.vehicle.entity).await;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -669,9 +548,6 @@ impl EntityBase for MinecartEntity {
|
|||||||
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
self.vehicle.send_wobble_metadata();
|
self.vehicle.send_wobble_metadata();
|
||||||
if let MinecartKind::Furnace(minecart) = &self.kind {
|
|
||||||
minecart.init_data_tracker(&self.vehicle.entity);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,108 +559,47 @@ impl EntityBase for MinecartEntity {
|
|||||||
&'a self,
|
&'a self,
|
||||||
_caller: &'a dyn EntityBase,
|
_caller: &'a dyn EntityBase,
|
||||||
amount: f32,
|
amount: f32,
|
||||||
damage_type: DamageType,
|
_damage_type: pumpkin_data::damage::DamageType,
|
||||||
_position: Option<Vector3<f64>>,
|
_position: Option<Vector3<f64>>,
|
||||||
source: Option<&'a dyn EntityBase>,
|
source: Option<&'a dyn EntityBase>,
|
||||||
cause: Option<&'a dyn EntityBase>,
|
_cause: Option<&'a dyn EntityBase>,
|
||||||
) -> EntityBaseFuture<'a, bool> {
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move { self.vehicle.damage_with_context(amount, source).await })
|
||||||
let creative = source
|
|
||||||
.and_then(EntityBase::get_player)
|
|
||||||
.is_some_and(|player| player.gamemode.load() == GameMode::Creative);
|
|
||||||
|
|
||||||
if let MinecartKind::Tnt(minecart) = &self.kind
|
|
||||||
&& damage_type == DamageType::ARROW
|
|
||||||
&& self.vehicle.entity.fire_ticks.load(Ordering::Relaxed) > 0
|
|
||||||
{
|
|
||||||
let projectile_speed_squared = cause
|
|
||||||
.map(|entity| entity.get_entity().velocity.load().length_squared())
|
|
||||||
.unwrap_or_default();
|
|
||||||
minecart
|
|
||||||
.explode(&self.vehicle.entity, projectile_speed_squared)
|
|
||||||
.await;
|
|
||||||
if self.vehicle.entity.is_removed() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let will_break = self.vehicle.entity.is_alive()
|
|
||||||
&& (creative || self.vehicle.get_damage() + amount * 10.0 > 40.0);
|
|
||||||
|
|
||||||
if let MinecartKind::Tnt(minecart) = &self.kind
|
|
||||||
&& will_break
|
|
||||||
&& !creative
|
|
||||||
{
|
|
||||||
let velocity = self.vehicle.entity.velocity.load();
|
|
||||||
let speed_squared = velocity.x.mul_add(velocity.x, velocity.z * velocity.z);
|
|
||||||
let ignites = damage_type.has_tag(&tag::DamageType::MINECRAFT_IS_FIRE)
|
|
||||||
|| damage_type.has_tag(&tag::DamageType::MINECRAFT_IS_EXPLOSION)
|
|
||||||
|| self.vehicle.entity.fire_ticks.load(Ordering::Relaxed) > 0;
|
|
||||||
if ignites || speed_squared >= 0.01 {
|
|
||||||
self.vehicle.apply_damage_wobble(amount);
|
|
||||||
let fuse = rand::rng().random_range(0..20) + rand::rng().random_range(0..20);
|
|
||||||
if self
|
|
||||||
.vehicle
|
|
||||||
.entity
|
|
||||||
.world
|
|
||||||
.load()
|
|
||||||
.level_info
|
|
||||||
.load()
|
|
||||||
.game_rules
|
|
||||||
.tnt_explodes
|
|
||||||
{
|
|
||||||
minecart.prime(&self.vehicle.entity, fuse);
|
|
||||||
} else {
|
|
||||||
minecart.set_fuse(fuse);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let damaged = self.vehicle.damage_with_context(amount, source).await;
|
|
||||||
|
|
||||||
if will_break && !creative && self.vehicle.entity.is_removed() {
|
|
||||||
let world = self.vehicle.entity.world.load();
|
|
||||||
if world.level_info.load().game_rules.entity_drops {
|
|
||||||
let position = self.vehicle.entity.block_pos.load();
|
|
||||||
if let Some(container) = self.container()
|
|
||||||
&& container.claim_drops()
|
|
||||||
{
|
|
||||||
container.unpack_loot().await;
|
|
||||||
let inventory: Arc<dyn Inventory> = container.clone();
|
|
||||||
world.scatter_inventory(&position, &inventory).await;
|
|
||||||
}
|
|
||||||
if let Some(item) = self.drop_item() {
|
|
||||||
world.drop_stack(&position, ItemStack::new(1, item)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
damaged
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interact<'a>(
|
fn interact<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
player: &'a Arc<Player>,
|
player: &'a Arc<Player>,
|
||||||
item_stack: &'a mut ItemStack,
|
_item_stack: &'a mut ItemStack,
|
||||||
) -> EntityBaseFuture<'a, bool> {
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match &self.kind {
|
if player.get_entity().is_sneaking() {
|
||||||
MinecartKind::Chest(minecart) => {
|
return false;
|
||||||
minecart.interact(&self.vehicle.entity, player).await
|
|
||||||
}
|
}
|
||||||
MinecartKind::Furnace(minecart) => {
|
|
||||||
minecart.interact(&self.vehicle.entity, player, item_stack)
|
if !self.vehicle.entity.passengers.lock().await.is_empty() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
MinecartKind::Hopper(minecart) => {
|
|
||||||
minecart.interact(&self.vehicle.entity, player).await
|
if player.get_entity().has_vehicle().await {
|
||||||
}
|
return false;
|
||||||
MinecartKind::Rideable(minecart) => {
|
|
||||||
minecart.interact(&self.vehicle.entity, player).await
|
|
||||||
}
|
|
||||||
MinecartKind::Tnt(_) | MinecartKind::Other => false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let world = self.vehicle.entity.world.load();
|
||||||
|
let Some(vehicle) = world.get_entity_by_id(self.vehicle.entity.entity_id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(passenger) = world.get_player_by_id(player.entity_id()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.vehicle
|
||||||
|
.entity
|
||||||
|
.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -860,22 +675,3 @@ impl EntityBase for MinecartEntity {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
|
|
||||||
use super::MinecartEntity;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn minecart_gravity_matches_vanilla() {
|
|
||||||
assert_eq!(
|
|
||||||
MinecartEntity::apply_gravity(Vector3::new(0.4, 0.0, 0.0), false),
|
|
||||||
Vector3::new(0.4, -0.04, 0.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
MinecartEntity::apply_gravity(Vector3::new(0.4, 0.0, 0.0), true),
|
|
||||||
Vector3::new(0.4, -0.005, 0.0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use pumpkin_data::translation;
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_util::text::TextComponent;
|
|
||||||
|
|
||||||
use crate::entity::{Entity, player::Player};
|
|
||||||
|
|
||||||
use super::container::{self, MinecartInventory};
|
|
||||||
|
|
||||||
pub(super) struct ChestMinecart {
|
|
||||||
inventory: Arc<MinecartInventory>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChestMinecart {
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
inventory: Arc::new(MinecartInventory::new(27)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) const fn inventory(&self) -> &Arc<MinecartInventory> {
|
|
||||||
&self.inventory
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn interact(&self, entity: &Entity, player: &Arc<Player>) -> bool {
|
|
||||||
container::open(
|
|
||||||
entity,
|
|
||||||
player,
|
|
||||||
&self.inventory,
|
|
||||||
TextComponent::translate_cross(
|
|
||||||
translation::java::ENTITY_MINECRAFT_CHEST_MINECART,
|
|
||||||
translation::bedrock::ENTITY_CHEST_MINECART_NAME,
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn write_nbt(&self, nbt: &mut NbtCompound) {
|
|
||||||
self.inventory.write_nbt(nbt).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn read_nbt(&self, nbt: &NbtCompound) {
|
|
||||||
self.inventory.read_nbt(nbt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,283 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Mutex as StdMutex};
|
|
||||||
|
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
|
||||||
use pumpkin_inventory::generic_container_screen_handler::{create_generic_9x3, create_hopper};
|
|
||||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
|
||||||
use pumpkin_inventory::screen_handler::{
|
|
||||||
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
|
|
||||||
};
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
use pumpkin_util::text::TextComponent;
|
|
||||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, split_stack};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::entity::{Entity, EntityBase, player::Player};
|
|
||||||
use crate::world::loot::fill_chest_inventory;
|
|
||||||
use pumpkin_data::chest_loot_table::get_chest_loot_table;
|
|
||||||
|
|
||||||
pub(super) struct MinecartInventory {
|
|
||||||
items: Box<[Arc<Mutex<ItemStack>>]>,
|
|
||||||
loot_table: StdMutex<Option<(String, i64)>>,
|
|
||||||
drops_claimed: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MinecartInventory {
|
|
||||||
pub(super) fn new(size: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
items: (0..size)
|
|
||||||
.map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
|
||||||
.collect(),
|
|
||||||
loot_table: StdMutex::new(None),
|
|
||||||
drops_claimed: AtomicBool::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn claim_drops(&self) -> bool {
|
|
||||||
!self.drops_claimed.swap(true, Ordering::AcqRel)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn read_nbt(&self, nbt: &NbtCompound) {
|
|
||||||
let loot_table = nbt.get_string("LootTable").map(|loot_table| {
|
|
||||||
(
|
|
||||||
loot_table.to_owned(),
|
|
||||||
nbt.get_long("LootTableSeed").unwrap_or(0),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let has_loot_table = loot_table.is_some();
|
|
||||||
*self.loot_table.lock().expect("Loot table mutex poisoned") = loot_table;
|
|
||||||
|
|
||||||
if !has_loot_table {
|
|
||||||
self.read_data(nbt, &self.items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn write_nbt(&self, nbt: &mut NbtCompound) {
|
|
||||||
let loot_table = self
|
|
||||||
.loot_table
|
|
||||||
.lock()
|
|
||||||
.expect("Loot table mutex poisoned")
|
|
||||||
.clone();
|
|
||||||
if let Some((loot_table, seed)) = loot_table {
|
|
||||||
nbt.put_string("LootTable", loot_table);
|
|
||||||
if seed != 0 {
|
|
||||||
nbt.put_long("LootTableSeed", seed);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.write_inventory_nbt(nbt, true).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn has_loot_table(&self) -> bool {
|
|
||||||
self.loot_table
|
|
||||||
.lock()
|
|
||||||
.expect("Loot table mutex poisoned")
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn unpack_loot(self: &Arc<Self>) {
|
|
||||||
let loot_table = self
|
|
||||||
.loot_table
|
|
||||||
.lock()
|
|
||||||
.expect("Loot table mutex poisoned")
|
|
||||||
.take();
|
|
||||||
let Some((loot_table, seed)) = loot_table else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(table) = get_chest_loot_table(&loot_table) else {
|
|
||||||
*self.loot_table.lock().expect("Loot table mutex poisoned") = Some((loot_table, seed));
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let inventory: Arc<dyn Inventory> = self.clone();
|
|
||||||
fill_chest_inventory(&inventory, table, seed).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inventory for MinecartInventory {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
self.items.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
|
||||||
Box::pin(async move {
|
|
||||||
for slot in &self.items {
|
|
||||||
if !slot.lock().await.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
|
||||||
Box::pin(async move { self.items[slot].clone() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut removed = ItemStack::EMPTY.clone();
|
|
||||||
std::mem::swap(&mut removed, &mut *self.items[slot].lock().await);
|
|
||||||
removed
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
|
||||||
Box::pin(async move { split_stack(&self.items, slot, amount).await })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
*self.items[slot].lock().await = stack;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clearable for MinecartInventory {
|
|
||||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
|
||||||
Box::pin(async move {
|
|
||||||
for slot in &self.items {
|
|
||||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MinecartScreenFactory {
|
|
||||||
inventory: Arc<MinecartInventory>,
|
|
||||||
title: TextComponent,
|
|
||||||
hopper: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScreenHandlerFactory for MinecartScreenFactory {
|
|
||||||
fn create_screen_handler<'a>(
|
|
||||||
&'a self,
|
|
||||||
sync_id: u8,
|
|
||||||
player_inventory: &'a Arc<PlayerInventory>,
|
|
||||||
_player: &'a dyn InventoryPlayer,
|
|
||||||
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let inventory: Arc<dyn Inventory> = self.inventory.clone();
|
|
||||||
let handler = if self.hopper {
|
|
||||||
create_hopper(sync_id, player_inventory, inventory).await
|
|
||||||
} else {
|
|
||||||
create_generic_9x3(sync_id, player_inventory, inventory).await
|
|
||||||
};
|
|
||||||
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_display_name(&self) -> TextComponent {
|
|
||||||
self.title.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn open(
|
|
||||||
entity: &Entity,
|
|
||||||
player: &Arc<Player>,
|
|
||||||
inventory: &Arc<MinecartInventory>,
|
|
||||||
title: TextComponent,
|
|
||||||
hopper: bool,
|
|
||||||
) -> bool {
|
|
||||||
if player.is_spectator() && inventory.has_loot_table() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if !player.is_spectator() {
|
|
||||||
inventory.unpack_loot().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
player
|
|
||||||
.open_handled_screen(
|
|
||||||
&MinecartScreenFactory {
|
|
||||||
inventory: inventory.clone(),
|
|
||||||
title: entity.custom_name.load().as_ref().clone().unwrap_or(title),
|
|
||||||
hopper,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn velocity(
|
|
||||||
entity: &Entity,
|
|
||||||
inventory: &MinecartInventory,
|
|
||||||
velocity: Vector3<f64>,
|
|
||||||
) -> Vector3<f64> {
|
|
||||||
let signal = crate::block::calculate_comparator_output(inventory).await;
|
|
||||||
let mut friction = if inventory.has_loot_table() {
|
|
||||||
0.98
|
|
||||||
} else {
|
|
||||||
0.98 + f64::from(15 - signal) * 0.001
|
|
||||||
};
|
|
||||||
if entity
|
|
||||||
.touching_water
|
|
||||||
.load(std::sync::atomic::Ordering::Relaxed)
|
|
||||||
{
|
|
||||||
friction *= 0.95;
|
|
||||||
}
|
|
||||||
velocity.multiply(friction, 0.0, friction)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::MinecartInventory;
|
|
||||||
use pumpkin_data::item::Item;
|
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_world::inventory::Inventory;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn deferred_mineshaft_loot_is_preserved_until_unpacked() {
|
|
||||||
let inventory = std::sync::Arc::new(MinecartInventory::new(27));
|
|
||||||
let mut source = NbtCompound::new();
|
|
||||||
source.put_string(
|
|
||||||
"LootTable",
|
|
||||||
"minecraft:chests/abandoned_mineshaft".to_string(),
|
|
||||||
);
|
|
||||||
source.put_long("LootTableSeed", 1234);
|
|
||||||
inventory.read_nbt(&source);
|
|
||||||
|
|
||||||
let mut deferred = NbtCompound::new();
|
|
||||||
inventory.write_nbt(&mut deferred).await;
|
|
||||||
assert_eq!(
|
|
||||||
deferred.get_string("LootTable"),
|
|
||||||
Some("minecraft:chests/abandoned_mineshaft")
|
|
||||||
);
|
|
||||||
assert_eq!(deferred.get_long("LootTableSeed"), Some(1234));
|
|
||||||
assert!(deferred.get_list("Items").is_none());
|
|
||||||
|
|
||||||
inventory.unpack_loot().await;
|
|
||||||
assert!(!inventory.is_empty().await);
|
|
||||||
|
|
||||||
let mut unpacked = NbtCompound::new();
|
|
||||||
inventory.write_nbt(&mut unpacked).await;
|
|
||||||
assert!(unpacked.get_string("LootTable").is_none());
|
|
||||||
assert!(unpacked.get_list("Items").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn chest_minecart_items_round_trip_through_nbt() {
|
|
||||||
let inventory = MinecartInventory::new(27);
|
|
||||||
inventory
|
|
||||||
.set_stack(8, ItemStack::new(3, &Item::POWERED_RAIL))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut nbt = NbtCompound::new();
|
|
||||||
inventory.write_nbt(&mut nbt).await;
|
|
||||||
|
|
||||||
let restored = MinecartInventory::new(27);
|
|
||||||
restored.read_nbt(&nbt);
|
|
||||||
let stack = restored.get_stack(8).await;
|
|
||||||
let stack = stack.lock().await;
|
|
||||||
assert_eq!(stack.get_item().id, Item::POWERED_RAIL.id);
|
|
||||||
assert_eq!(stack.item_count, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,160 +0,0 @@
|
|||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
|
||||||
|
|
||||||
use crossbeam::atomic::AtomicCell;
|
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
|
||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
|
||||||
use pumpkin_data::particle::Particle;
|
|
||||||
use pumpkin_data::tag::{self, Taggable};
|
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_protocol::java::client::play::Metadata;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
use rand::RngExt;
|
|
||||||
|
|
||||||
use crate::entity::{Entity, EntityBase, player::Player};
|
|
||||||
|
|
||||||
pub(super) struct FurnaceMinecart {
|
|
||||||
fuel: AtomicI32,
|
|
||||||
push: AtomicCell<Vector3<f64>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FurnaceMinecart {
|
|
||||||
const FUEL_PER_ITEM: i32 = 3_600;
|
|
||||||
const MAX_FUEL: i32 = 32_000;
|
|
||||||
|
|
||||||
pub(super) const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
fuel: AtomicI32::new(0),
|
|
||||||
push: AtomicCell::new(Vector3::new(0.0, 0.0, 0.0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_fueled(entity: &Entity, fueled: bool) {
|
|
||||||
entity.send_meta_data(
|
|
||||||
&[Metadata::new(
|
|
||||||
TrackedData::ID_FUEL,
|
|
||||||
MetaDataType::BOOLEAN,
|
|
||||||
fueled,
|
|
||||||
)],
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn tick(&self, entity: &Entity) {
|
|
||||||
let fuel = self.fuel.load(Ordering::Relaxed);
|
|
||||||
if fuel <= 0 {
|
|
||||||
self.push.store(Vector3::new(0.0, 0.0, 0.0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let remaining = fuel - 1;
|
|
||||||
self.fuel.store(remaining, Ordering::Relaxed);
|
|
||||||
if remaining == 0 {
|
|
||||||
self.push.store(Vector3::new(0.0, 0.0, 0.0));
|
|
||||||
Self::set_fueled(entity, false);
|
|
||||||
} else if rand::rng().random_range(0..4) == 0 {
|
|
||||||
let mut pos = entity.pos.load();
|
|
||||||
pos.y += 0.8;
|
|
||||||
entity.world.load().spawn_particle(
|
|
||||||
pos,
|
|
||||||
Vector3::new(0.0, 0.0, 0.0),
|
|
||||||
0.0,
|
|
||||||
1,
|
|
||||||
Particle::LargeSmoke,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn velocity(&self, entity: &Entity, velocity: Vector3<f64>) -> Vector3<f64> {
|
|
||||||
let mut push = self.push.load();
|
|
||||||
let push_length_squared = push.x.mul_add(push.x, push.z * push.z);
|
|
||||||
let velocity_length_squared = velocity.x.mul_add(velocity.x, velocity.z * velocity.z);
|
|
||||||
|
|
||||||
let mut next = if push_length_squared > 1.0e-7 {
|
|
||||||
if push_length_squared > 1.0e-4 && velocity_length_squared > 0.001 {
|
|
||||||
let velocity_direction = Vector3::new(velocity.x, 0.0, velocity.z).normalize();
|
|
||||||
let push_length = push.length();
|
|
||||||
let push_scale = if velocity_direction.dot(&push) < 0.0 {
|
|
||||||
-push_length
|
|
||||||
} else {
|
|
||||||
push_length
|
|
||||||
};
|
|
||||||
push = velocity_direction.multiply(push_scale, 0.0, push_scale);
|
|
||||||
self.push.store(push);
|
|
||||||
}
|
|
||||||
velocity.multiply(0.8, 0.0, 0.8).add(&push)
|
|
||||||
} else {
|
|
||||||
velocity.multiply(0.98, 0.0, 0.98)
|
|
||||||
};
|
|
||||||
|
|
||||||
let in_water = entity.touching_water.load(Ordering::Relaxed);
|
|
||||||
if in_water {
|
|
||||||
next = next.multiply(0.1, 0.0, 0.1);
|
|
||||||
}
|
|
||||||
let slowdown = 0.96 * if in_water { 0.95 } else { 1.0 };
|
|
||||||
next = next.multiply(slowdown, 0.0, slowdown);
|
|
||||||
|
|
||||||
let max_speed = if in_water { 0.3 } else { 0.2 };
|
|
||||||
let speed = next.x.hypot(next.z);
|
|
||||||
if speed > max_speed {
|
|
||||||
next.x = next.x / speed * max_speed;
|
|
||||||
next.z = next.z / speed * max_speed;
|
|
||||||
}
|
|
||||||
next
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn interact(
|
|
||||||
&self,
|
|
||||||
entity: &Entity,
|
|
||||||
player: &Player,
|
|
||||||
item_stack: &mut ItemStack,
|
|
||||||
) -> bool {
|
|
||||||
let fuel = self.fuel.load(Ordering::Relaxed);
|
|
||||||
if item_stack
|
|
||||||
.get_item()
|
|
||||||
.has_tag(&tag::Item::MINECRAFT_FURNACE_MINECART_FUEL)
|
|
||||||
&& fuel <= Self::MAX_FUEL - Self::FUEL_PER_ITEM
|
|
||||||
{
|
|
||||||
self.fuel
|
|
||||||
.store(fuel + Self::FUEL_PER_ITEM, Ordering::Relaxed);
|
|
||||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
|
||||||
if fuel <= 0 {
|
|
||||||
Self::set_fueled(entity, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.fuel.load(Ordering::Relaxed) > 0 {
|
|
||||||
let cart_pos = entity.pos.load();
|
|
||||||
let player_pos = player.get_entity().pos.load();
|
|
||||||
self.push.store(Vector3::new(
|
|
||||||
cart_pos.x - player_pos.x,
|
|
||||||
0.0,
|
|
||||||
cart_pos.z - player_pos.z,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn init_data_tracker(&self, entity: &Entity) {
|
|
||||||
Self::set_fueled(entity, self.fuel.load(Ordering::Relaxed) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn write_nbt(&self, nbt: &mut NbtCompound) {
|
|
||||||
let push = self.push.load();
|
|
||||||
nbt.put_double("PushX", push.x);
|
|
||||||
nbt.put_double("PushZ", push.z);
|
|
||||||
nbt.put_short("Fuel", self.fuel.load(Ordering::Relaxed) as i16);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn read_nbt(&self, nbt: &NbtCompound) {
|
|
||||||
self.push.store(Vector3::new(
|
|
||||||
nbt.get_double("PushX").unwrap_or(0.0),
|
|
||||||
0.0,
|
|
||||||
nbt.get_double("PushZ").unwrap_or(0.0),
|
|
||||||
));
|
|
||||||
self.fuel.store(
|
|
||||||
i32::from(nbt.get_short("Fuel").unwrap_or(0)),
|
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use crate::block::entities::hopper::HopperBlockEntity;
|
|
||||||
use crate::entity::{Entity, player::Player};
|
|
||||||
use pumpkin_data::translation;
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
|
||||||
use pumpkin_util::math::position::BlockPos;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
use pumpkin_util::text::TextComponent;
|
|
||||||
|
|
||||||
use super::container::{self, MinecartInventory};
|
|
||||||
|
|
||||||
pub(super) struct HopperMinecart {
|
|
||||||
enabled: AtomicBool,
|
|
||||||
inventory: Arc<MinecartInventory>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HopperMinecart {
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
enabled: AtomicBool::new(true),
|
|
||||||
inventory: Arc::new(MinecartInventory::new(5)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) const fn inventory(&self) -> &Arc<MinecartInventory> {
|
|
||||||
&self.inventory
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_enabled(&self, enabled: bool) {
|
|
||||||
self.enabled.store(enabled, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn tick(&self, entity: &Entity) {
|
|
||||||
if !self.enabled.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let world = entity.world.load();
|
|
||||||
let pos = entity.pos.load();
|
|
||||||
let source_pos = BlockPos::floored(pos.x, pos.y + 1.5, pos.z);
|
|
||||||
if let Some(block_entity) = world.get_block_entity(&source_pos)
|
|
||||||
&& let Some(source) = block_entity.get_inventory()
|
|
||||||
{
|
|
||||||
for slot in 0..source.size() {
|
|
||||||
let stack = source.get_stack(slot).await;
|
|
||||||
let mut stack = stack.lock().await;
|
|
||||||
if stack.is_empty()
|
|
||||||
|| !source.can_transfer_to(self.inventory.as_ref(), slot, &stack)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let backup = stack.clone();
|
|
||||||
let one = stack.split(1);
|
|
||||||
if HopperBlockEntity::add_one_item(source.as_ref(), self.inventory.as_ref(), one)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*stack = backup;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let suction_box = BoundingBox::new(
|
|
||||||
Vector3::new(pos.x - 0.5, pos.y + 0.6875, pos.z - 0.5),
|
|
||||||
Vector3::new(pos.x + 0.5, pos.y + 2.0, pos.z + 0.5),
|
|
||||||
);
|
|
||||||
if self.pick_up_item(entity, &suction_box).await {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let cart_box = entity.bounding_box.load().expand(0.25, 0.0, 0.25);
|
|
||||||
self.pick_up_item(entity, &cart_box).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn pick_up_item(&self, entity: &Entity, search_box: &BoundingBox) -> bool {
|
|
||||||
let world = entity.world.load();
|
|
||||||
for entity in world.get_entities_at_box(search_box) {
|
|
||||||
let Some(item) = entity.get_item_entity() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let mut stack = item.get_item_stack().lock().await;
|
|
||||||
if stack.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let backup = stack.clone();
|
|
||||||
let one = stack.split(1);
|
|
||||||
if HopperBlockEntity::add_one_item(
|
|
||||||
self.inventory.as_ref(),
|
|
||||||
self.inventory.as_ref(),
|
|
||||||
one,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if stack.is_empty() {
|
|
||||||
item.get_entity().remove().await;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*stack = backup;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn interact(&self, entity: &Entity, player: &Arc<Player>) -> bool {
|
|
||||||
container::open(
|
|
||||||
entity,
|
|
||||||
player,
|
|
||||||
&self.inventory,
|
|
||||||
TextComponent::translate_cross(
|
|
||||||
translation::java::ENTITY_MINECRAFT_HOPPER_MINECART,
|
|
||||||
translation::bedrock::ENTITY_HOPPER_MINECART_NAME,
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn write_nbt(&self, nbt: &mut NbtCompound) {
|
|
||||||
self.inventory.write_nbt(nbt).await;
|
|
||||||
nbt.put_bool("Enabled", self.enabled.load(Ordering::Relaxed));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn read_nbt(&self, nbt: &NbtCompound) {
|
|
||||||
self.inventory.read_nbt(nbt);
|
|
||||||
self.enabled
|
|
||||||
.store(nbt.get_bool("Enabled").unwrap_or(true), Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::HopperMinecart;
|
|
||||||
use pumpkin_world::inventory::Inventory;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hopper_minecart_inventory_has_five_slots() {
|
|
||||||
assert_eq!(HopperMinecart::new().inventory.size(), 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::entity::{Entity, EntityBase, player::Player};
|
|
||||||
|
|
||||||
pub(super) struct RideableMinecart;
|
|
||||||
|
|
||||||
impl RideableMinecart {
|
|
||||||
pub(super) async fn interact(&self, entity: &Entity, player: &Arc<Player>) -> bool {
|
|
||||||
if player.get_entity().is_sneaking()
|
|
||||||
|| !entity.passengers.lock().await.is_empty()
|
|
||||||
|| player.get_entity().has_vehicle().await
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let world = entity.world.load();
|
|
||||||
let Some(vehicle) = world.get_entity_by_id(entity.entity_id) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let Some(passenger) = world.get_player_by_id(player.entity_id()) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
entity
|
|
||||||
.add_passenger(vehicle, passenger as Arc<dyn EntityBase>)
|
|
||||||
.await;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
|
||||||
|
|
||||||
use crossbeam::atomic::AtomicCell;
|
|
||||||
use pumpkin_data::entity::EntityStatus;
|
|
||||||
use pumpkin_data::particle::Particle;
|
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
use rand::RngExt;
|
|
||||||
|
|
||||||
use crate::entity::Entity;
|
|
||||||
|
|
||||||
pub(super) struct TntMinecart {
|
|
||||||
fuse: AtomicI32,
|
|
||||||
explosion_power: AtomicCell<f32>,
|
|
||||||
explosion_speed_factor: AtomicCell<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TntMinecart {
|
|
||||||
pub(super) const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
fuse: AtomicI32::new(-1),
|
|
||||||
explosion_power: AtomicCell::new(4.0),
|
|
||||||
explosion_speed_factor: AtomicCell::new(1.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn prime(&self, entity: &Entity, fuse: i32) {
|
|
||||||
if self.fuse.load(Ordering::Relaxed) >= 0
|
|
||||||
|| !entity
|
|
||||||
.world
|
|
||||||
.load()
|
|
||||||
.level_info
|
|
||||||
.load()
|
|
||||||
.game_rules
|
|
||||||
.tnt_explodes
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fuse.store(fuse, Ordering::Relaxed);
|
|
||||||
let world = entity.world.load();
|
|
||||||
world.send_entity_status(entity, EntityStatus::TntPrime);
|
|
||||||
world.play_sound(
|
|
||||||
Sound::EntityTntPrimed,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&entity.pos.load(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn tick(&self, entity: &Entity) -> bool {
|
|
||||||
let fuse = self.fuse.load(Ordering::Relaxed);
|
|
||||||
if fuse > 0 {
|
|
||||||
self.fuse.store(fuse - 1, Ordering::Relaxed);
|
|
||||||
let mut smoke_pos = entity.pos.load();
|
|
||||||
smoke_pos.y += 0.5;
|
|
||||||
entity.world.load().spawn_particle(
|
|
||||||
smoke_pos,
|
|
||||||
Vector3::new(0.0, 0.0, 0.0),
|
|
||||||
0.0,
|
|
||||||
1,
|
|
||||||
Particle::Smoke,
|
|
||||||
);
|
|
||||||
} else if fuse == 0 {
|
|
||||||
let velocity = entity.velocity.load();
|
|
||||||
self.explode(
|
|
||||||
entity,
|
|
||||||
velocity.x.mul_add(velocity.x, velocity.z * velocity.z),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn explode(&self, entity: &Entity, horizontal_speed_squared: f64) {
|
|
||||||
let world = entity.world.load();
|
|
||||||
if !world.level_info.load().game_rules.tnt_explodes {
|
|
||||||
if self.fuse.load(Ordering::Relaxed) > -1 {
|
|
||||||
entity.remove().await;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let power = Self::explosion_strength(
|
|
||||||
self.explosion_power.load(),
|
|
||||||
self.explosion_speed_factor.load(),
|
|
||||||
horizontal_speed_squared,
|
|
||||||
rand::rng().random_range(0.0..1.0),
|
|
||||||
);
|
|
||||||
let pos = entity.pos.load();
|
|
||||||
let primed = self.fuse.load(Ordering::Relaxed) > -1;
|
|
||||||
entity.remove().await;
|
|
||||||
if primed {
|
|
||||||
world.explode_tnt_minecart(pos, power).await;
|
|
||||||
} else {
|
|
||||||
world.explode(pos, power).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_fuse(&self, fuse: i32) {
|
|
||||||
self.fuse.store(fuse, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn write_nbt(&self, nbt: &mut NbtCompound) {
|
|
||||||
nbt.put_int("fuse", self.fuse.load(Ordering::Relaxed));
|
|
||||||
let power = self.explosion_power.load();
|
|
||||||
if power != 4.0 {
|
|
||||||
nbt.put_float("explosion_power", power);
|
|
||||||
}
|
|
||||||
let speed_factor = self.explosion_speed_factor.load();
|
|
||||||
if speed_factor != 1.0 {
|
|
||||||
nbt.put_float("explosion_speed_factor", speed_factor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn read_nbt(&self, nbt: &NbtCompound) {
|
|
||||||
self.fuse
|
|
||||||
.store(nbt.get_int("fuse").unwrap_or(-1), Ordering::Relaxed);
|
|
||||||
self.explosion_power.store(
|
|
||||||
nbt.get_float("explosion_power")
|
|
||||||
.unwrap_or(4.0)
|
|
||||||
.clamp(0.0, 128.0),
|
|
||||||
);
|
|
||||||
self.explosion_speed_factor.store(
|
|
||||||
nbt.get_float("explosion_speed_factor")
|
|
||||||
.unwrap_or(1.0)
|
|
||||||
.clamp(0.0, 128.0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn explosion_strength(
|
|
||||||
base: f32,
|
|
||||||
speed_factor: f32,
|
|
||||||
horizontal_speed_squared: f64,
|
|
||||||
random: f32,
|
|
||||||
) -> f32 {
|
|
||||||
let speed = horizontal_speed_squared.sqrt().min(5.0) as f32;
|
|
||||||
base + speed_factor * random * 1.5 * speed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::TntMinecart;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tnt_minecart_explosion_bonus_is_speed_capped() {
|
|
||||||
assert_eq!(TntMinecart::explosion_strength(4.0, 1.0, 100.0, 1.0), 11.5);
|
|
||||||
assert_eq!(TntMinecart::explosion_strength(4.0, 1.0, 100.0, 0.0), 4.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,7 +8,6 @@ use crate::entity::EntityBase;
|
|||||||
use crate::world::loot::{LootContextParameters, LootTableExt};
|
use crate::world::loot::{LootContextParameters, LootTableExt};
|
||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
use pumpkin_data::meta_data_type::MetaDataType;
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
use pumpkin_data::tracked_data::TrackedData;
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
|
||||||
use pumpkin_util::GameMode;
|
use pumpkin_util::GameMode;
|
||||||
|
|
||||||
pub struct VehicleEntity {
|
pub struct VehicleEntity {
|
||||||
@ -67,42 +66,25 @@ impl VehicleEntity {
|
|||||||
pub fn send_wobble_metadata(&self) {
|
pub fn send_wobble_metadata(&self) {
|
||||||
self.entity.send_meta_data(
|
self.entity.send_meta_data(
|
||||||
&[
|
&[
|
||||||
Metadata::new(
|
|
||||||
TrackedData::DAMAGE_WOBBLE_TICKS,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(self.get_hurt_time()),
|
|
||||||
),
|
|
||||||
Metadata::new(
|
|
||||||
TrackedData::DAMAGE_WOBBLE_SIDE,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(self.get_hurt_dir()),
|
|
||||||
),
|
|
||||||
Metadata::new(
|
Metadata::new(
|
||||||
TrackedData::ID_HURT,
|
TrackedData::ID_HURT,
|
||||||
MetaDataType::INT,
|
MetaDataType::INTEGER,
|
||||||
VarInt(self.get_hurt_time()),
|
self.get_hurt_time(),
|
||||||
),
|
),
|
||||||
Metadata::new(
|
Metadata::new(
|
||||||
TrackedData::ID_HURTDIR,
|
TrackedData::ID_HURTDIR,
|
||||||
MetaDataType::INT,
|
MetaDataType::INTEGER,
|
||||||
VarInt(self.get_hurt_dir()),
|
self.get_hurt_dir(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
self.entity.send_meta_data(
|
self.entity.send_meta_data(
|
||||||
&[
|
&[Metadata::new(
|
||||||
Metadata::new(
|
|
||||||
TrackedData::DAMAGE_WOBBLE_STRENGTH,
|
|
||||||
MetaDataType::FLOAT,
|
|
||||||
self.get_damage(),
|
|
||||||
),
|
|
||||||
Metadata::new(
|
|
||||||
TrackedData::ID_DAMAGE,
|
TrackedData::ID_DAMAGE,
|
||||||
MetaDataType::FLOAT,
|
MetaDataType::FLOAT,
|
||||||
self.get_damage(),
|
self.get_damage(),
|
||||||
),
|
)],
|
||||||
],
|
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,7 +116,16 @@ impl VehicleEntity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_strength = self.apply_damage_wobble(amount);
|
let current_side = self.get_hurt_dir();
|
||||||
|
self.set_hurt_dir(-current_side);
|
||||||
|
self.set_hurt_time(10);
|
||||||
|
self.entity.velocity_dirty.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
let current_strength = self.get_damage();
|
||||||
|
let new_strength = current_strength + amount * 10.0;
|
||||||
|
self.set_damage(new_strength);
|
||||||
|
|
||||||
|
self.send_wobble_metadata();
|
||||||
|
|
||||||
let is_creative = source
|
let is_creative = source
|
||||||
.and_then(|s| s.get_player())
|
.and_then(|s| s.get_player())
|
||||||
@ -150,64 +141,4 @@ impl VehicleEntity {
|
|||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the standard minecart damage wobble without destroying the vehicle.
|
|
||||||
/// TNT minecarts use this before deciding whether damage primes or breaks them.
|
|
||||||
pub fn apply_damage_wobble(&self, amount: f32) -> f32 {
|
|
||||||
let current_side = self.get_hurt_dir();
|
|
||||||
self.set_hurt_dir(-current_side);
|
|
||||||
self.set_hurt_time(10);
|
|
||||||
self.entity.velocity_dirty.store(true, Ordering::SeqCst);
|
|
||||||
|
|
||||||
let current_strength = self.get_damage();
|
|
||||||
let new_strength = current_strength + amount * 10.0;
|
|
||||||
self.set_damage(new_strength);
|
|
||||||
|
|
||||||
self.send_wobble_metadata();
|
|
||||||
new_strength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
|
||||||
use pumpkin_protocol::java::client::play::Metadata;
|
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
|
||||||
|
|
||||||
fn wobble_integer_metadata(version: JavaMinecraftVersion) -> Vec<u8> {
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
for metadata in [
|
|
||||||
Metadata::new(
|
|
||||||
TrackedData::DAMAGE_WOBBLE_TICKS,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(10),
|
|
||||||
),
|
|
||||||
Metadata::new(
|
|
||||||
TrackedData::DAMAGE_WOBBLE_SIDE,
|
|
||||||
MetaDataType::INTEGER,
|
|
||||||
VarInt(-1),
|
|
||||||
),
|
|
||||||
Metadata::new(TrackedData::ID_HURT, MetaDataType::INT, VarInt(10)),
|
|
||||||
Metadata::new(TrackedData::ID_HURTDIR, MetaDataType::INT, VarInt(-1)),
|
|
||||||
] {
|
|
||||||
metadata.write(&mut bytes, &version).unwrap();
|
|
||||||
}
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn wobble_integers_serialize_for_legacy_and_current_clients() {
|
|
||||||
let expected = vec![8, 1, 10, 9, 1, 0xff, 0xff, 0xff, 0xff, 0x0f];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
wobble_integer_metadata(JavaMinecraftVersion::V_1_21_11),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
wobble_integer_metadata(JavaMinecraftVersion::V_26_2),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,6 @@ impl ItemBehaviour for MinecartItem {
|
|||||||
);
|
);
|
||||||
let minecart_entity = Arc::new(MinecartEntity::new(entity));
|
let minecart_entity = Arc::new(MinecartEntity::new(entity));
|
||||||
world.spawn_entity(minecart_entity).await;
|
world.spawn_entity(minecart_entity).await;
|
||||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,34 +18,12 @@ use super::{BlockFlags, World};
|
|||||||
pub struct Explosion {
|
pub struct Explosion {
|
||||||
power: f32,
|
power: f32,
|
||||||
pos: Vector3<f64>,
|
pos: Vector3<f64>,
|
||||||
preserve_rails: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Explosion {
|
impl Explosion {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(power: f32, pos: Vector3<f64>) -> Self {
|
pub const fn new(power: f32, pos: Vector3<f64>) -> Self {
|
||||||
Self {
|
Self { power, pos }
|
||||||
power,
|
|
||||||
pos,
|
|
||||||
preserve_rails: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub const fn preserving_rails(mut self) -> Self {
|
|
||||||
self.preserve_rails = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn protects_rail(&self, world: &World, pos: &BlockPos, block: &Block) -> bool {
|
|
||||||
self.preserve_rails && (Self::is_rail(block) || Self::is_rail(world.get_block(&pos.up())))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_rail(block: &Block) -> bool {
|
|
||||||
block.id == Block::RAIL.id
|
|
||||||
|| block.id == Block::POWERED_RAIL.id
|
|
||||||
|| block.id == Block::DETECTOR_RAIL.id
|
|
||||||
|| block.id == Block::ACTIVATOR_RAIL.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_blocks_to_destroy(
|
fn get_blocks_to_destroy(
|
||||||
@ -135,16 +113,12 @@ impl Explosion {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if !state.is_air() || !fluid_state.is_empty {
|
if !state.is_air() || !fluid_state.is_empty {
|
||||||
let protects_rail = self.protects_rail(world, &block_pos, block);
|
let resistance =
|
||||||
let resistance = if protects_rail {
|
fluid_state.blast_resistance.max(block.blast_resistance);
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
fluid_state.blast_resistance.max(block.blast_resistance)
|
|
||||||
};
|
|
||||||
|
|
||||||
h -= (resistance + 0.3) * 0.3;
|
h -= (resistance + 0.3) * 0.3;
|
||||||
|
|
||||||
if h > 0.0 && !protects_rail {
|
if h > 0.0 {
|
||||||
map.insert(block_pos, (block, state));
|
map.insert(block_pos, (block, state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,22 +307,3 @@ impl Explosion {
|
|||||||
blocks.len() as u32
|
blocks.len() as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Explosion;
|
|
||||||
use pumpkin_data::Block;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tnt_minecart_rail_protection_covers_every_rail_type() {
|
|
||||||
for rail in [
|
|
||||||
&Block::RAIL,
|
|
||||||
&Block::POWERED_RAIL,
|
|
||||||
&Block::DETECTOR_RAIL,
|
|
||||||
&Block::ACTIVATOR_RAIL,
|
|
||||||
] {
|
|
||||||
assert!(Explosion::is_rail(rail));
|
|
||||||
}
|
|
||||||
assert!(!Explosion::is_rail(&Block::STONE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ use crate::{
|
|||||||
{OnNeighborUpdateArgs, OnScheduledTickArgs},
|
{OnNeighborUpdateArgs, OnScheduledTickArgs},
|
||||||
},
|
},
|
||||||
command::client_suggestions,
|
command::client_suggestions,
|
||||||
entity::{Entity, EntityBase, NBTStorage, RemovalReason, player::Player, r#type::from_type},
|
entity::{Entity, EntityBase, NBTStorage, player::Player, r#type::from_type},
|
||||||
error::PumpkinError,
|
error::PumpkinError,
|
||||||
net::{ClientPlatform, java::JavaClient},
|
net::{ClientPlatform, java::JavaClient},
|
||||||
plugin::{
|
plugin::{
|
||||||
@ -391,11 +391,7 @@ impl World {
|
|||||||
/// currently in; the chunk is rewritten from scratch every unload cycle, so
|
/// currently in; the chunk is rewritten from scratch every unload cycle, so
|
||||||
/// there is nothing stale to deduplicate.
|
/// there is nothing stale to deduplicate.
|
||||||
async fn save_entity(&self, entity: &Arc<dyn EntityBase>) {
|
async fn save_entity(&self, entity: &Arc<dyn EntityBase>) {
|
||||||
let base_entity = entity.get_entity();
|
let current_chunk = entity.get_entity().block_pos.load().chunk_position();
|
||||||
if base_entity.is_removed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let current_chunk = base_entity.block_pos.load().chunk_position();
|
|
||||||
let mut nbt = NbtCompound::new();
|
let mut nbt = NbtCompound::new();
|
||||||
entity.write_nbt(&mut nbt).await;
|
entity.write_nbt(&mut nbt).await;
|
||||||
let chunk = self.level.get_entity_chunk(current_chunk).await;
|
let chunk = self.level.get_entity_chunk(current_chunk).await;
|
||||||
@ -3348,20 +3344,6 @@ impl World {
|
|||||||
|
|
||||||
pub async fn explode(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
|
pub async fn explode(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
|
||||||
let explosion = Explosion::new(power, position);
|
let explosion = Explosion::new(power, position);
|
||||||
self.run_explosion(explosion, position, power).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn explode_tnt_minecart(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
|
|
||||||
let explosion = Explosion::new(power, position).preserving_rails();
|
|
||||||
self.run_explosion(explosion, position, power).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_explosion(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
explosion: Explosion,
|
|
||||||
position: Vector3<f64>,
|
|
||||||
power: f32,
|
|
||||||
) {
|
|
||||||
let block_count = explosion.explode(self).await;
|
let block_count = explosion.explode(self).await;
|
||||||
let particle = if power < 2.0 {
|
let particle = if power < 2.0 {
|
||||||
Particle::Explosion
|
Particle::Explosion
|
||||||
@ -3793,7 +3775,6 @@ impl World {
|
|||||||
.client
|
.client
|
||||||
.enqueue_packet(&base_entity.create_spawn_packet())
|
.enqueue_packet(&base_entity.create_spawn_packet())
|
||||||
.await;
|
.await;
|
||||||
player.try_restore_vehicle(&entity).await;
|
|
||||||
entities_to_add.push(entity);
|
entities_to_add.push(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3815,7 +3796,6 @@ impl World {
|
|||||||
.client
|
.client
|
||||||
.enqueue_packet(&base_entity.create_spawn_packet())
|
.enqueue_packet(&base_entity.create_spawn_packet())
|
||||||
.await;
|
.await;
|
||||||
player.try_restore_vehicle(entity).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4299,15 +4279,6 @@ impl World {
|
|||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn remove_entity(&self, entity: &dyn EntityBase) {
|
pub async fn remove_entity(&self, entity: &dyn EntityBase) {
|
||||||
let base_entity = entity.get_entity();
|
let base_entity = entity.get_entity();
|
||||||
if base_entity
|
|
||||||
.removal_reason
|
|
||||||
.swap(Some(RemovalReason::Discarded))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
base_entity.removed.store(true, Ordering::Release);
|
|
||||||
|
|
||||||
self.spawn_state.load().remove_entity(self, entity);
|
self.spawn_state.load().remove_entity(self, entity);
|
||||||
self.entities.rcu(|current_entities| {
|
self.entities.rcu(|current_entities| {
|
||||||
let mut new_entities = (**current_entities).clone();
|
let mut new_entities = (**current_entities).clone();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user