use glam::{IVec3, DVec3, Vec3};
use crate::entity::{Arrow, Entity, Snowball, Tnt, Bobber, BaseKind, ProjectileKind, Item};
use crate::inventory::InventoryHandle;
use crate::gen::tree::TreeGenerator;
use crate::block::sapling::TreeKind;
use crate::item::{ItemStack, self};
use crate::geom::Face;
use crate::block;
use super::World;
use super::bound::RayTraceKind;
impl World {
pub fn use_stack(&mut self, inv: &mut InventoryHandle, index: usize, pos: IVec3, face: Face, entity_id: u32) {
let stack = inv.get(index);
if stack.is_empty() {
return;
}
let success = match stack.id {
0 => false,
1..=255 => self.use_block_stack(stack.id as u8, stack.damage as u8, pos, face, entity_id),
item::SUGAR_CANES => self.use_block_stack(block::SUGAR_CANES, 0, pos, face, entity_id),
item::CAKE => self.use_block_stack(block::CAKE, 0, pos, face, entity_id),
item::REPEATER => self.use_block_stack(block::REPEATER, 0, pos, face, entity_id),
item::REDSTONE => self.use_block_stack(block::REDSTONE, 0, pos, face, entity_id),
item::WOOD_DOOR => self.use_door_stack(block::WOOD_DOOR, pos, face, entity_id),
item::IRON_DOOR => self.use_door_stack(block::IRON_DOOR, pos, face, entity_id),
item::BED => self.use_bed_stack(pos, face, entity_id),
item::DIAMOND_HOE |
item::IRON_HOE |
item::STONE_HOE |
item::GOLD_HOE |
item::WOOD_HOE => self.use_hoe_stack(pos, face),
item::WHEAT_SEEDS => self.use_wheat_seeds_stack(pos, face),
item::DYE if stack.damage == 15 => self.use_bone_meal_stack(pos),
item::FLINT_AND_STEEL => self.use_flint_and_steel(pos, face),
_ => false
};
if success {
inv.set(index, stack.inc_damage(1));
}
}
pub fn use_raw_stack(&mut self, inv: &mut InventoryHandle, index: usize, entity_id: u32) {
let stack = inv.get(index);
if stack.is_empty() {
return;
}
match stack.id {
item::BUCKET |
item::WATER_BUCKET |
item::LAVA_BUCKET => self.use_bucket_stack(inv, index, entity_id),
item::BOW => self.use_bow_stack(inv, index, entity_id),
item::SNOWBALL => self.use_snowball_stack(inv, index, entity_id),
item::FISHING_ROD => self.use_fishing_rod_stack(inv, index, entity_id),
_ => ()
}
}
fn use_block_stack(&mut self, id: u8, metadata: u8, mut pos: IVec3, mut face: Face, entity_id: u32) -> bool {
let look = self.get_entity(entity_id).unwrap().0.look;
if let Some((block::SNOW, _)) = self.get_block(pos) {
face = Face::NegY;
} else {
pos += face.delta();
face = face.opposite();
}
match id {
block::WOOD_STAIR | block::COBBLESTONE_STAIR |
block::REPEATER | block::REPEATER_LIT => {
face = Face::from_yaw(look.x);
}
block::DISPENSER |
block::FURNACE | block::FURNACE_LIT |
block::PUMPKIN | block::PUMPKIN_LIT => {
face = Face::from_yaw(look.x).opposite();
}
block::PISTON => {
face = Face::from_look(look.x, look.y).opposite();
}
_ => {}
}
if pos.y >= 127 && block::material::get_material(id).is_solid() {
return false;
} if !self.can_place_block(pos, face, id) {
return false;
}
self.place_block(pos, face, id, metadata);
true
}
fn use_door_stack(&mut self, block_id: u8, mut pos: IVec3, face: Face, entity_id: u32) -> bool {
if face != Face::PosY {
return false;
} else {
pos += IVec3::Y;
}
if pos.y >= 127 {
return false;
} else if !self.can_place_block(pos, face.opposite(), block_id) {
return false;
}
let look = self.get_entity(entity_id).unwrap().0.look;
let mut door_face = Face::from_yaw(look.x).opposite();
let mut flip = false;
let left_pos = pos + door_face.rotate_left().delta();
let right_pos = pos + door_face.rotate_right().delta();
let is_door_block = |pos| {
self.get_block(pos).map(|(id, _)| id == block_id).unwrap_or(false)
};
let left_door = is_door_block(left_pos) || is_door_block(left_pos + IVec3::Y);
let right_door = is_door_block(right_pos) || is_door_block(right_pos + IVec3::Y);
if right_door && !left_door {
flip = true;
} else {
let left_count =
self.is_block_opaque_cube(left_pos) as u8 +
self.is_block_opaque_cube(left_pos + IVec3::Y) as u8;
let right_count =
self.is_block_opaque_cube(right_pos) as u8 +
self.is_block_opaque_cube(right_pos + IVec3::Y) as u8;
if left_count > right_count {
flip = true;
}
}
let mut metadata = 0;
if flip {
block::door::set_open(&mut metadata, true);
door_face = door_face.rotate_left();
}
block::door::set_face(&mut metadata, door_face);
self.set_block_notify(pos, block_id, metadata);
block::door::set_upper(&mut metadata, true);
self.set_block_notify(pos + IVec3::Y, block_id, metadata);
true
}
fn use_bed_stack(&mut self, mut pos: IVec3, face: Face, entity_id: u32) -> bool {
if face != Face::PosY {
return false;
} else {
pos += IVec3::Y;
}
let look = self.get_entity(entity_id).unwrap().0.look;
let bed_face = Face::from_yaw(look.x);
let head_pos = pos + bed_face.delta();
if !matches!(self.get_block(pos), Some((block::AIR, _))) {
return false;
} else if !matches!(self.get_block(head_pos), Some((block::AIR, _))) {
return false;
} else if !self.is_block_opaque_cube(pos - IVec3::Y) || !self.is_block_opaque_cube(head_pos - IVec3::Y) {
return false;
}
let mut metadata = 0;
block::bed::set_face(&mut metadata, bed_face);
self.set_block_notify(pos, block::BED, metadata);
block::bed::set_head(&mut metadata, true);
self.set_block_notify(head_pos, block::BED, metadata);
true
}
fn use_hoe_stack(&mut self, pos: IVec3, face: Face) -> bool {
if let Some((id, _)) = self.get_block(pos) {
if let Some((above_id, _)) = self.get_block(pos + IVec3::Y) {
if (face != Face::NegY && above_id == block::AIR && id == block::GRASS) || id == block::DIRT {
self.set_block_notify(pos, block::FARMLAND, 0);
return true;
}
}
}
false
}
fn use_wheat_seeds_stack(&mut self, pos: IVec3, face: Face) -> bool {
if face == Face::PosY {
if let Some((block::FARMLAND, _)) = self.get_block(pos) {
if let Some((block::AIR, _)) = self.get_block(pos + IVec3::Y) {
self.set_block_notify(pos + IVec3::Y, block::WHEAT, 0);
return true;
}
}
}
false
}
fn use_bone_meal_stack(&mut self, pos: IVec3) -> bool {
let Some((block, metadata)) = self.get_block(pos) else { return false };
if block == block::SAPLING {
let mut gen = match block::sapling::get_kind(metadata) {
TreeKind::Oak if self.get_rand_mut().next_int_bounded(10) == 0 => TreeGenerator::new_big(),
TreeKind::Oak => TreeGenerator::new_oak(),
TreeKind::Birch => TreeGenerator::new_birch(),
TreeKind::Spruce => TreeGenerator::new_spruce2(),
};
gen.generate_from_sapling(self, pos);
true
} else {
false
}
}
fn use_flint_and_steel(&mut self, pos: IVec3, face: Face) -> bool {
if self.is_block(pos, block::TNT) {
self.spawn_entity(Tnt::new_with(|new_base, new_tnt| {
new_base.pos = pos.as_dvec3() + 0.5;
new_tnt.fuse_time = 80;
}));
self.set_block_notify(pos, block::AIR, 0);
} else {
let fire_pos = pos + face.delta();
if self.is_block_air(fire_pos) {
self.set_block_notify(fire_pos, block::FIRE, 0);
}
}
true
}
fn use_bucket_stack(&mut self, inv: &mut InventoryHandle, index: usize, entity_id: u32) {
let stack = inv.get(index);
let fluid_id = match stack.id {
item::BUCKET => block::AIR,
item::WATER_BUCKET => block::WATER_MOVING,
item::LAVA_BUCKET => block::LAVA_MOVING,
_ => unimplemented!()
};
let entity = self.get_entity(entity_id).unwrap();
let origin = entity.0.pos + DVec3::new(0.0, 1.62, 0.0);
let yaw_dx = -entity.0.look.x.sin();
let yaw_dz = entity.0.look.x.cos();
let pitch_dy = -entity.0.look.y.sin();
let pitch_h = entity.0.look.y.cos();
let ray = Vec3::new(yaw_dx * pitch_h, pitch_dy, yaw_dz * pitch_h).as_dvec3() * 5.0;
let kind = if fluid_id == block::AIR {
RayTraceKind::OverlayWithFluid
} else {
RayTraceKind::Overlay
};
let Some(hit) = self.ray_trace_blocks(origin, ray, kind) else {
return
};
let mut new_stack;
if fluid_id == block::AIR {
let Some((id, metadata)) = self.get_block(hit.pos) else { return };
if !block::fluid::is_source(metadata) {
return;
}
new_stack = match id {
block::WATER_MOVING | block::WATER_STILL => ItemStack::new_single(item::WATER_BUCKET, 0),
block::LAVA_MOVING | block::LAVA_STILL => ItemStack::new_single(item::LAVA_BUCKET, 0),
_ => return
};
self.set_block_notify(hit.pos, block::AIR, 0);
} else {
let pos = hit.pos + hit.face.delta();
let Some((id, _)) = self.get_block(pos) else { return };
if id == block::AIR || !block::material::get_material(id).is_solid() {
self.set_block_notify(pos, fluid_id, 0);
}
new_stack = ItemStack::new_single(item::BUCKET, 0);
}
if stack.size > 1 {
inv.push_front(&mut new_stack);
if new_stack.is_empty() {
inv.set(index, stack.with_size(stack.size - 1));
}
} else {
inv.set(index, new_stack);
}
}
fn use_bow_stack(&mut self, inv: &mut InventoryHandle, _index: usize, entity_id: u32) {
if !inv.consume(ItemStack::new_single(item::ARROW, 0)) {
return;
}
let Entity(base, _) = self.get_entity(entity_id).unwrap();
let arrow = Arrow::new_with(|arrow_base, arrow_projectile, arrow| {
arrow_base.pos = base.pos;
arrow_base.pos.y += base.eye_height as f64;
arrow_base.look = base.look;
let (yaw_sin, yaw_cos) = arrow_base.look.x.sin_cos();
let (pitch_sin, pitch_cos) = arrow_base.look.y.sin_cos();
arrow_base.vel.x = (-yaw_sin * pitch_cos) as f64;
arrow_base.vel.z = (yaw_cos * pitch_cos) as f64;
arrow_base.vel.y = (-pitch_sin) as f64;
arrow_base.vel += arrow_base.rand.next_gaussian_vec() * 0.0075;
arrow_base.vel *= 1.5;
arrow_projectile.owner_id = Some(entity_id);
arrow.from_player = true;
});
self.spawn_entity(arrow);
}
fn use_snowball_stack(&mut self, inv: &mut InventoryHandle, index: usize, entity_id: u32) {
let stack = inv.get(index);
inv.set(index, stack.inc_damage(1));
let Entity(base, _) = self.get_entity(entity_id).unwrap();
let snowball = Snowball::new_with(|throw_base, throw_projectile, _| {
throw_base.pos = base.pos;
throw_base.pos.y += base.eye_height as f64 - 0.1;
throw_base.look = base.look;
let (yaw_sin, yaw_cos) = throw_base.look.x.sin_cos();
let (pitch_sin, pitch_cos) = throw_base.look.y.sin_cos();
throw_base.vel.x = (-yaw_sin * pitch_cos) as f64;
throw_base.vel.z = (yaw_cos * pitch_cos) as f64;
throw_base.vel.y = (-pitch_sin) as f64;
throw_base.pos.x += throw_base.vel.x * 0.16;
throw_base.pos.z += throw_base.vel.z * 0.16;
throw_base.vel += throw_base.rand.next_gaussian_vec() * 0.0075;
throw_base.vel *= 1.5;
throw_projectile.owner_id = Some(entity_id);
});
self.spawn_entity(snowball);
}
fn use_fishing_rod_stack(&mut self, inv: &mut InventoryHandle, index: usize, entity_id: u32) {
let Entity(base, _) = self.get_entity_mut(entity_id).unwrap();
let base_pos = base.pos;
let base_look = base.look;
let mut new_bobber_id = base.bobber_id;
let mut item_damage = 0;
if let Some(bobber_id) = new_bobber_id {
if let Some(Entity(bobber_base, BaseKind::Projectile(bobber_projectile, ProjectileKind::Bobber(bobber)))) = self.get_entity(bobber_id) {
let bobber_pos = bobber_base.pos;
let bobber_delta = base_pos - bobber_pos;
let bobber_dist = bobber_delta.length();
let mut bobber_accel = bobber_delta * 0.1;
bobber_accel.y += bobber_dist.sqrt() * 0.08;
if let Some(attached_id) = bobber.attached_id {
if let Some(Entity(attached_base, _)) = self.get_entity_mut(attached_id) {
attached_base.vel += bobber_accel;
item_damage = 3;
}
} else if bobber.catch_time > 0 {
self.spawn_entity(Item::new_with(|item_base, item| {
item_base.persistent = true;
item_base.pos = bobber_pos;
item_base.vel = bobber_accel;
item.stack = ItemStack::new_single(item::RAW_FISH, 0);
}));
item_damage = 1;
} else if bobber_projectile.state.is_some() {
item_damage = 2;
}
}
self.remove_entity(bobber_id, "bobber retracted");
new_bobber_id = None;
} else {
let bobber = Bobber::new_with(|throw_base, throw_projectile, _| {
throw_base.pos = base_pos;
throw_base.pos.y += 1.62 - 0.1;
throw_base.look = base_look;
let (yaw_sin, yaw_cos) = throw_base.look.x.sin_cos();
let (pitch_sin, pitch_cos) = throw_base.look.y.sin_cos();
throw_base.vel.x = (-yaw_sin * pitch_cos) as f64;
throw_base.vel.z = (yaw_cos * pitch_cos) as f64;
throw_base.vel.y = (-pitch_sin) as f64;
throw_base.pos.x += throw_base.vel.x * 0.16;
throw_base.pos.z += throw_base.vel.z * 0.16;
throw_base.vel += throw_base.rand.next_gaussian_vec() * 0.0075;
throw_base.vel *= 1.5;
throw_projectile.owner_id = Some(entity_id);
});
new_bobber_id = Some(self.spawn_entity(bobber));
}
let Entity(base, _) = self.get_entity_mut(entity_id).unwrap();
base.bobber_id = new_bobber_id;
let stack = inv.get(index);
inv.set(index, stack.inc_damage(item_damage));
}
}