feat: villager Professions
This commit is contained in:
parent
daa0111009
commit
5f60d23804
@ -28,6 +28,16 @@ pub fn build() -> TokenStream {
|
|||||||
let i = n.as_i64().unwrap();
|
let i = n.as_i64().unwrap();
|
||||||
(quote! { Int }, quote! { i64 }, quote! { #i })
|
(quote! { Int }, quote! { i64 }, quote! { #i })
|
||||||
}
|
}
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let default_val = obj.get("default").expect("Object game rule missing default value");
|
||||||
|
match default_val {
|
||||||
|
Value::Number(n) if n.is_i64() => {
|
||||||
|
let i = n.as_i64().unwrap();
|
||||||
|
(quote! { Int }, quote! { i64 }, quote! { #i })
|
||||||
|
}
|
||||||
|
_ => panic!("Unsupported default value type in object for key '{raw_name}'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => panic!("Unsupported value type for key '{raw_name}'"),
|
_ => panic!("Unsupported value type for key '{raw_name}'"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,11 @@
|
|||||||
use crate::attributes::Attributes;
|
use crate::attributes::Attributes;
|
||||||
use crate::data_component::DataComponent;
|
use crate::data_component::DataComponent;
|
||||||
use crate::data_component::DataComponent::{
|
use crate::data_component::DataComponent::{
|
||||||
AttributeModifiers, BlocksAttacks, ChargedProjectiles, Consumable, CustomData, CustomName,
|
AttributeModifiers, BlockEntityData, BlocksAttacks, BundleContents, ChargedProjectiles,
|
||||||
Damage, DamageResistant, DeathProtection, Enchantable, Enchantments, Equippable,
|
Consumable, Container, CustomData, CustomName, Damage, DamageResistant, DeathProtection,
|
||||||
FireworkExplosion, Fireworks, Food, ItemModel, ItemName, JukeboxPlayable, MapId, MaxDamage,
|
Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, ItemName,
|
||||||
MaxStackSize, PotionContents, StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon,
|
JukeboxPlayable, MapId, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool,
|
||||||
|
Unbreakable, UseCooldown, Weapon,
|
||||||
};
|
};
|
||||||
use crate::effect::{self, StatusEffect};
|
use crate::effect::{self, StatusEffect};
|
||||||
use crate::entity_type::EntityType;
|
use crate::entity_type::EntityType;
|
||||||
@ -70,6 +71,15 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
|
|||||||
DataComponent::ChargedProjectiles => {
|
DataComponent::ChargedProjectiles => {
|
||||||
Some(ChargedProjectilesImpl::read_data(data)?.to_dyn())
|
Some(ChargedProjectilesImpl::read_data(data)?.to_dyn())
|
||||||
}
|
}
|
||||||
|
DataComponent::BlockEntityData => Some(BlockEntityDataImpl::read_data(data)?.to_dyn()),
|
||||||
|
DataComponent::BundleContents => Some(BundleContentsImpl::read_data(data)?.to_dyn()),
|
||||||
|
DataComponent::Container => Some(ContainerImpl::read_data(data)?.to_dyn()),
|
||||||
|
DataComponent::WrittenBookContent => {
|
||||||
|
Some(WrittenBookContentImpl::read_data(data)?.to_dyn())
|
||||||
|
}
|
||||||
|
DataComponent::WritableBookContent => {
|
||||||
|
Some(WritableBookContentImpl::read_data(data)?.to_dyn())
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1559,8 +1569,79 @@ impl DataComponentImpl for ChargedProjectilesImpl {
|
|||||||
default_impl!(ChargedProjectiles);
|
default_impl!(ChargedProjectiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone)]
|
||||||
pub struct BundleContentsImpl;
|
pub struct BundleContentsImpl {
|
||||||
|
pub items: Vec<crate::item_stack::ItemStack>,
|
||||||
|
}
|
||||||
|
impl PartialEq for BundleContentsImpl {
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for BundleContentsImpl {}
|
||||||
|
impl std::fmt::Debug for BundleContentsImpl {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "BundleContentsImpl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl BundleContentsImpl {
|
||||||
|
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
if let NbtTag::List(l) = tag {
|
||||||
|
for item_tag in l {
|
||||||
|
if let NbtTag::Compound(c) = item_tag
|
||||||
|
&& let Some(stack) = crate::item_stack::ItemStack::read_item_stack(c)
|
||||||
|
{
|
||||||
|
items.push(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Self { items })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_weight(&self) -> u32 {
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.item_count as u32 * (64 / item.get_max_stack_size() as u32).max(1))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_insert(&mut self, stack: &mut crate::item_stack::ItemStack) -> bool {
|
||||||
|
if stack.is_empty() || stack.get_data_component::<BundleContentsImpl>().is_some() {
|
||||||
|
return false; // Can't put bundles in bundles
|
||||||
|
}
|
||||||
|
let weight_per_item = (64 / stack.get_max_stack_size() as u32).max(1);
|
||||||
|
let mut inserted_anything = false;
|
||||||
|
|
||||||
|
while stack.item_count > 0 && self.get_weight() + weight_per_item <= 64 {
|
||||||
|
if let Some(top) = self.items.first_mut()
|
||||||
|
&& crate::item_stack::ItemStack::are_items_and_components_equal(top, stack)
|
||||||
|
&& top.item_count < top.get_max_stack_size()
|
||||||
|
{
|
||||||
|
top.item_count += 1;
|
||||||
|
stack.item_count -= 1;
|
||||||
|
inserted_anything = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.items.insert(0, stack.copy_with_count(1));
|
||||||
|
stack.item_count -= 1;
|
||||||
|
inserted_anything = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inserted_anything
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_extract(&mut self) -> Option<crate::item_stack::ItemStack> {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.items.remove(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DataComponentImpl for BundleContentsImpl {
|
||||||
|
default_impl!(BundleContents);
|
||||||
|
}
|
||||||
/// Status effect instance for potion contents
|
/// Status effect instance for potion contents
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct StatusEffectInstance {
|
pub struct StatusEffectInstance {
|
||||||
@ -1898,9 +1979,53 @@ pub struct PotionDurationScaleImpl;
|
|||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct SuspiciousStewEffectsImpl;
|
pub struct SuspiciousStewEffectsImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct WritableBookContentImpl;
|
pub struct WritableBookContentImpl {
|
||||||
|
pub pages: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WritableBookContentImpl {
|
||||||
|
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||||
|
let mut pages = Vec::new();
|
||||||
|
if let NbtTag::Compound(c) = tag
|
||||||
|
&& let Some(NbtTag::List(l)) = c.get("pages")
|
||||||
|
{
|
||||||
|
for _ in l {
|
||||||
|
pages.push(String::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Self { pages })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::data_component::DataComponent::{WritableBookContent, WrittenBookContent};
|
||||||
|
|
||||||
|
impl DataComponentImpl for WritableBookContentImpl {
|
||||||
|
default_impl!(WritableBookContent);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct WrittenBookContentImpl;
|
pub struct WrittenBookContentImpl {
|
||||||
|
pub pages: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrittenBookContentImpl {
|
||||||
|
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||||
|
let mut pages = Vec::new();
|
||||||
|
if let NbtTag::Compound(c) = tag
|
||||||
|
&& let Some(NbtTag::List(l)) = c.get("pages")
|
||||||
|
{
|
||||||
|
for _ in l {
|
||||||
|
pages.push(String::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Self { pages })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataComponentImpl for WrittenBookContentImpl {
|
||||||
|
default_impl!(WrittenBookContent);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct TrimImpl;
|
pub struct TrimImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
@ -1909,8 +2034,22 @@ pub struct DebugStickStateImpl;
|
|||||||
pub struct EntityDataImpl;
|
pub struct EntityDataImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct BucketEntityDataImpl;
|
pub struct BucketEntityDataImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct BlockEntityDataImpl;
|
pub struct BlockEntityDataImpl {
|
||||||
|
pub nbt: pumpkin_nbt::compound::NbtCompound,
|
||||||
|
}
|
||||||
|
impl BlockEntityDataImpl {
|
||||||
|
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||||
|
if let NbtTag::Compound(c) = tag {
|
||||||
|
Some(Self { nbt: c.clone() })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DataComponentImpl for BlockEntityDataImpl {
|
||||||
|
default_impl!(BlockEntityData);
|
||||||
|
}
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct InstrumentImpl;
|
pub struct InstrumentImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
@ -2129,8 +2268,42 @@ pub struct BannerPatternsImpl;
|
|||||||
pub struct BaseColorImpl;
|
pub struct BaseColorImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct PotDecorationsImpl;
|
pub struct PotDecorationsImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone)]
|
||||||
pub struct ContainerImpl;
|
pub struct ContainerImpl {
|
||||||
|
pub items: Vec<(u8, crate::item_stack::ItemStack)>,
|
||||||
|
}
|
||||||
|
impl PartialEq for ContainerImpl {
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for ContainerImpl {}
|
||||||
|
impl std::fmt::Debug for ContainerImpl {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "ContainerImpl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ContainerImpl {
|
||||||
|
pub fn read_data(tag: &NbtTag) -> Option<Self> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
if let NbtTag::List(l) = tag {
|
||||||
|
for item_tag in l {
|
||||||
|
if let NbtTag::Compound(c) = item_tag
|
||||||
|
&& let Some(slot) = c.get_int("slot")
|
||||||
|
&& let Some(item_compound) = c.get_compound("item")
|
||||||
|
&& let Some(stack) =
|
||||||
|
crate::item_stack::ItemStack::read_item_stack(item_compound)
|
||||||
|
{
|
||||||
|
items.push((slot as u8, stack));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Self { items })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DataComponentImpl for ContainerImpl {
|
||||||
|
default_impl!(Container);
|
||||||
|
}
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct BlockStateImpl;
|
pub struct BlockStateImpl;
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
|||||||
@ -18,6 +18,7 @@ pub struct MerchantScreenHandler {
|
|||||||
behaviour: ScreenHandlerBehaviour,
|
behaviour: ScreenHandlerBehaviour,
|
||||||
selected_offer: usize,
|
selected_offer: usize,
|
||||||
pub offers: Vec<pumpkin_protocol::java::client::play::MerchantOffer>,
|
pub offers: Vec<pumpkin_protocol::java::client::play::MerchantOffer>,
|
||||||
|
pub on_trade: Option<Box<dyn Fn(usize) + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MerchantScreenHandler {
|
impl MerchantScreenHandler {
|
||||||
@ -32,6 +33,7 @@ impl MerchantScreenHandler {
|
|||||||
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Merchant)),
|
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Merchant)),
|
||||||
selected_offer: 0,
|
selected_offer: 0,
|
||||||
offers,
|
offers,
|
||||||
|
on_trade: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
inventory.on_open().await;
|
inventory.on_open().await;
|
||||||
@ -183,21 +185,26 @@ impl ScreenHandler for MerchantScreenHandler {
|
|||||||
let result_stack = result_slot.get_cloned_stack().await;
|
let result_stack = result_slot.get_cloned_stack().await;
|
||||||
if !result_stack.is_empty() {
|
if !result_stack.is_empty() {
|
||||||
// Consume inputs
|
// Consume inputs
|
||||||
let offer = &self.offers[self.selected_offer];
|
let (count_a, count_b, offer_xp) = {
|
||||||
|
let offer = &mut self.offers[self.selected_offer];
|
||||||
|
offer.uses += 1;
|
||||||
|
let count_b = offer.cost_b.as_ref().map(|c| c.0.item_count);
|
||||||
|
(offer.base_cost_a.0.item_count, count_b, offer.xp)
|
||||||
|
};
|
||||||
|
|
||||||
let input_a = self.inventory.get_stack(0).await;
|
let input_a = self.inventory.get_stack(0).await;
|
||||||
let mut input_a = input_a.lock().await;
|
let mut input_a = input_a.lock().await;
|
||||||
input_a.decrement(offer.base_cost_a.0.item_count);
|
input_a.decrement(count_a);
|
||||||
if input_a.is_empty() {
|
if input_a.is_empty() {
|
||||||
*input_a = ItemStack::EMPTY.clone();
|
*input_a = ItemStack::EMPTY.clone();
|
||||||
}
|
}
|
||||||
drop(input_a);
|
drop(input_a);
|
||||||
self.get_behaviour().slots[0].mark_dirty().await;
|
self.get_behaviour().slots[0].mark_dirty().await;
|
||||||
|
|
||||||
if let Some(cost_b) = &offer.cost_b {
|
if let Some(count_b) = count_b {
|
||||||
let input_b = self.inventory.get_stack(1).await;
|
let input_b = self.inventory.get_stack(1).await;
|
||||||
let mut input_b = input_b.lock().await;
|
let mut input_b = input_b.lock().await;
|
||||||
input_b.decrement(cost_b.0.item_count);
|
input_b.decrement(count_b);
|
||||||
if input_b.is_empty() {
|
if input_b.is_empty() {
|
||||||
*input_b = ItemStack::EMPTY.clone();
|
*input_b = ItemStack::EMPTY.clone();
|
||||||
}
|
}
|
||||||
@ -206,7 +213,11 @@ impl ScreenHandler for MerchantScreenHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Award XP
|
// Award XP
|
||||||
player.award_experience(offer.xp).await;
|
player.award_experience(offer_xp).await;
|
||||||
|
|
||||||
|
if let Some(on_trade) = &self.on_trade {
|
||||||
|
on_trade(self.selected_offer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,6 +121,7 @@ pub type PlayerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
|||||||
///
|
///
|
||||||
/// Implementors are typically player entities that can open containers.
|
/// Implementors are typically player entities that can open containers.
|
||||||
pub trait InventoryPlayer: Send + Sync {
|
pub trait InventoryPlayer: Send + Sync {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any;
|
||||||
/// Drops an item into the world.
|
/// Drops an item into the world.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -1059,6 +1060,56 @@ pub trait ScreenHandler: Send + Sync {
|
|||||||
let slot_stack = slot.get_cloned_stack().await;
|
let slot_stack = slot.get_cloned_stack().await;
|
||||||
let mut cursor_stack = self.get_behaviour().cursor_stack.lock().await;
|
let mut cursor_stack = self.get_behaviour().cursor_stack.lock().await;
|
||||||
|
|
||||||
|
if click_type == MouseClick::Right {
|
||||||
|
let mut intercepted = false;
|
||||||
|
|
||||||
|
if !cursor_stack.is_empty() {
|
||||||
|
let stack_guard = slot.get_stack().await;
|
||||||
|
let mut inner_slot_stack = stack_guard.lock().await;
|
||||||
|
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
|
||||||
|
&& bundle.try_insert(&mut cursor_stack) {
|
||||||
|
intercepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !intercepted && !slot_stack.is_empty()
|
||||||
|
&& let Some(bundle) = cursor_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() {
|
||||||
|
let stack_guard = slot.get_stack().await;
|
||||||
|
let mut inner_slot_stack = stack_guard.lock().await;
|
||||||
|
if bundle.try_insert(&mut inner_slot_stack) {
|
||||||
|
if inner_slot_stack.item_count == 0 {
|
||||||
|
*inner_slot_stack = ItemStack::EMPTY.clone();
|
||||||
|
}
|
||||||
|
intercepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !intercepted && cursor_stack.is_empty() {
|
||||||
|
let stack_guard = slot.get_stack().await;
|
||||||
|
let mut inner_slot_stack = stack_guard.lock().await;
|
||||||
|
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
|
||||||
|
&& let Some(extracted) = bundle.try_extract() {
|
||||||
|
*cursor_stack = extracted;
|
||||||
|
intercepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !intercepted && slot_stack.is_empty()
|
||||||
|
&& let Some(bundle) = cursor_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
|
||||||
|
&& let Some(extracted) = bundle.try_extract() {
|
||||||
|
slot.set_stack(extracted).await;
|
||||||
|
intercepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if intercepted {
|
||||||
|
if cursor_stack.item_count == 0 {
|
||||||
|
*cursor_stack = ItemStack::EMPTY.clone();
|
||||||
|
}
|
||||||
|
slot.mark_dirty().await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let equipment_slot = cursor_stack
|
let equipment_slot = cursor_stack
|
||||||
.get_data_component::<EquippableImpl>()
|
.get_data_component::<EquippableImpl>()
|
||||||
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
|
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
|
||||||
|
|||||||
@ -7,9 +7,9 @@ use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct MerchantOffer {
|
pub struct MerchantOffer {
|
||||||
pub base_cost_a: ItemStackSerializer<'static>,
|
pub base_cost_a: ItemStackSerializer<'static>, // TODO: item cost
|
||||||
pub output: ItemStackSerializer<'static>,
|
pub output: ItemStackSerializer<'static>,
|
||||||
pub cost_b: Option<ItemStackSerializer<'static>>,
|
pub cost_b: Option<ItemStackSerializer<'static>>, // TODO: item cost
|
||||||
pub is_disabled: bool,
|
pub is_disabled: bool,
|
||||||
pub uses: i32,
|
pub uses: i32,
|
||||||
pub max_uses: i32,
|
pub max_uses: i32,
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::sync::Arc;
|
|||||||
use crate::block::entities::lectern::LecternBlockEntity;
|
use crate::block::entities::lectern::LecternBlockEntity;
|
||||||
use crate::block::registry::BlockActionResult;
|
use crate::block::registry::BlockActionResult;
|
||||||
use crate::block::{
|
use crate::block::{
|
||||||
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
|
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
|
||||||
UseWithItemArgs,
|
PlacedArgs, UseWithItemArgs,
|
||||||
};
|
};
|
||||||
use crate::entity::Entity;
|
use crate::entity::Entity;
|
||||||
use crate::entity::item::ItemEntity;
|
use crate::entity::item::ItemEntity;
|
||||||
@ -148,4 +148,41 @@ impl BlockBehaviour for LecternBlock {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_comparator_output<'a>(
|
||||||
|
&'a self,
|
||||||
|
args: GetComparatorOutputArgs<'a>,
|
||||||
|
) -> BlockFuture<'a, Option<u8>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||||
|
&& let Some(lectern_entity) =
|
||||||
|
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||||
|
{
|
||||||
|
let book_guard = lectern_entity.book.lock().await;
|
||||||
|
if book_guard.is_empty() {
|
||||||
|
return Some(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = lectern_entity
|
||||||
|
.page
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed) as f32;
|
||||||
|
let mut pages = 1.0;
|
||||||
|
if let Some(comp) = book_guard.get_data_component::<pumpkin_data::data_component_impl::WrittenBookContentImpl>() {
|
||||||
|
pages = comp.pages.len().max(1) as f32;
|
||||||
|
} else if let Some(comp) = book_guard.get_data_component::<pumpkin_data::data_component_impl::WritableBookContentImpl>() {
|
||||||
|
pages = comp.pages.len().max(1) as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output: f32 = if pages > 1.0 {
|
||||||
|
1.0 + 14.0 * page / (pages - 1.0)
|
||||||
|
} else {
|
||||||
|
15.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(output.floor() as u8)
|
||||||
|
} else {
|
||||||
|
Some(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,11 @@ use pumpkin_nbt::compound::NbtCompound;
|
|||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
future::Future,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@ -17,6 +18,7 @@ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
|||||||
pub struct LecternBlockEntity {
|
pub struct LecternBlockEntity {
|
||||||
pub position: BlockPos,
|
pub position: BlockPos,
|
||||||
pub book: Arc<Mutex<ItemStack>>,
|
pub book: Arc<Mutex<ItemStack>>,
|
||||||
|
pub page: AtomicUsize,
|
||||||
pub dirty: AtomicBool,
|
pub dirty: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +43,12 @@ impl BlockEntity for LecternBlockEntity {
|
|||||||
|stack| Arc::new(Mutex::new(stack)),
|
|stack| Arc::new(Mutex::new(stack)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let page = nbt.get_int("Page").unwrap_or(0).max(0) as usize;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
book,
|
book,
|
||||||
|
page: AtomicUsize::new(page),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,6 +64,7 @@ impl BlockEntity for LecternBlockEntity {
|
|||||||
book.write_item_stack(&mut book_nbt);
|
book.write_item_stack(&mut book_nbt);
|
||||||
nbt.put_compound("Book", book_nbt);
|
nbt.put_compound("Book", book_nbt);
|
||||||
}
|
}
|
||||||
|
nbt.put_int("Page", self.page.load(Ordering::Relaxed) as i32);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +93,7 @@ impl LecternBlockEntity {
|
|||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
book: Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
|
book: Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
|
||||||
|
page: AtomicUsize::new(0),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -397,6 +397,14 @@ pub trait Mob: EntityBase + Send + Sync {
|
|||||||
|
|
||||||
fn get_mob_entity(&self) -> &MobEntity;
|
fn get_mob_entity(&self) -> &MobEntity;
|
||||||
|
|
||||||
|
fn get_job_site(&self) -> Option<BlockPos> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home(&self) -> Option<BlockPos> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn get_path_aware_entity(&self) -> Option<&dyn PathAwareEntity> {
|
fn get_path_aware_entity(&self) -> Option<&dyn PathAwareEntity> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -675,6 +683,14 @@ impl<T: Mob + Send + 'static> EntityBase for T {
|
|||||||
.is_some_and(PathAwareEntity::is_panicking)
|
.is_some_and(PathAwareEntity::is_panicking)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_job_site_pos(&self) -> Option<pumpkin_util::math::position::BlockPos> {
|
||||||
|
<T as Mob>::get_job_site(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home_pos(&self) -> Option<pumpkin_util::math::position::BlockPos> {
|
||||||
|
<T as Mob>::get_home(self)
|
||||||
|
}
|
||||||
|
|
||||||
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,6 +147,14 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_job_site_pos(&self) -> Option<pumpkin_util::math::position::BlockPos> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home_pos(&self) -> Option<pumpkin_util::math::position::BlockPos> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any
|
fn as_any(&self) -> &dyn std::any::Any
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
|||||||
@ -26,23 +26,30 @@ pub enum GossipType {
|
|||||||
Trading = 4,
|
Trading = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
pub struct VillagerData {
|
pub struct VillagerData {
|
||||||
pub r#type: VillagerType,
|
pub r#type: VarInt,
|
||||||
pub profession: VillagerProfession,
|
pub profession: VarInt,
|
||||||
pub level: i32,
|
pub level: VarInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for VillagerData {
|
impl VillagerData {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
#[must_use]
|
||||||
where
|
pub const fn new(r#type: VillagerType, profession: VillagerProfession, level: i32) -> Self {
|
||||||
S: serde::Serializer,
|
Self {
|
||||||
{
|
r#type: VarInt(r#type as i32),
|
||||||
use serde::ser::SerializeStruct;
|
profession: VarInt(profession as i32),
|
||||||
let mut state = serializer.serialize_struct("VillagerData", 3)?;
|
level: VarInt(level),
|
||||||
state.serialize_field("type", &VarInt(self.r#type as i32))?;
|
}
|
||||||
state.serialize_field("profession", &VarInt(self.profession as i32))?;
|
}
|
||||||
state.serialize_field("level", &VarInt(self.level))?;
|
|
||||||
state.end()
|
#[must_use]
|
||||||
|
pub fn type_enum(&self) -> VillagerType {
|
||||||
|
VillagerType::from_i32(self.r#type.0).unwrap_or(VillagerType::Plains)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn profession_enum(&self) -> VillagerProfession {
|
||||||
|
VillagerProfession::from_i32(self.profession.0).unwrap_or(VillagerProfession::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,24 @@ use std::sync::atomic::{AtomicI32, AtomicI64, Ordering};
|
|||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use pumpkin_data::entity::EntityType;
|
use crate::block::blocks::bed::BedBlock;
|
||||||
|
use pumpkin_data::Block;
|
||||||
|
use pumpkin_data::block_properties::{
|
||||||
|
BedPart, BlockProperties, WhiteBedLikeProperties as BedProperties,
|
||||||
|
};
|
||||||
|
use pumpkin_data::entity::{EntityPose, EntityType};
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
use pumpkin_data::meta_data_type::MetaDataType;
|
||||||
|
use pumpkin_data::tag::Taggable;
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
use pumpkin_data::tracked_data::TrackedData;
|
||||||
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
||||||
use pumpkin_inventory::screen_handler::{
|
use pumpkin_inventory::screen_handler::{
|
||||||
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
|
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
|
||||||
};
|
};
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_protocol::java::client::play::Metadata;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
use pumpkin_protocol::java::client::play::{CMerchantOffers, Metadata};
|
||||||
|
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
use pumpkin_world::inventory::SimpleInventory;
|
use pumpkin_world::inventory::SimpleInventory;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@ -44,16 +52,15 @@ pub struct VillagerEntity {
|
|||||||
pub inventory: Arc<Mutex<Vec<Arc<Mutex<ItemStack>>>>>,
|
pub inventory: Arc<Mutex<Vec<Arc<Mutex<ItemStack>>>>>,
|
||||||
pub merchant_inventory: Arc<SimpleInventory>,
|
pub merchant_inventory: Arc<SimpleInventory>,
|
||||||
pub offers: Mutex<Vec<pumpkin_protocol::java::client::play::MerchantOffer>>,
|
pub offers: Mutex<Vec<pumpkin_protocol::java::client::play::MerchantOffer>>,
|
||||||
|
pub job_site: std::sync::Mutex<Option<BlockPos>>,
|
||||||
|
pub home_pos: std::sync::Mutex<Option<BlockPos>>,
|
||||||
|
pub self_weak: std::sync::Mutex<Option<Weak<Self>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VillagerEntity {
|
impl VillagerEntity {
|
||||||
pub fn new(entity: Entity) -> Arc<Self> {
|
pub fn new(entity: Entity) -> Arc<Self> {
|
||||||
let mob_entity = MobEntity::new(entity);
|
let mob_entity = MobEntity::new(entity);
|
||||||
let villager_data = VillagerData {
|
let villager_data = VillagerData::new(VillagerType::Plains, VillagerProfession::None, 1);
|
||||||
r#type: VillagerType::Plains,
|
|
||||||
profession: VillagerProfession::None,
|
|
||||||
level: 1,
|
|
||||||
};
|
|
||||||
let inventory = Arc::new(Mutex::new(
|
let inventory = Arc::new(Mutex::new(
|
||||||
(0..8)
|
(0..8)
|
||||||
.map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
.map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||||
@ -71,8 +78,12 @@ impl VillagerEntity {
|
|||||||
inventory,
|
inventory,
|
||||||
merchant_inventory: Arc::new(SimpleInventory::new(3)),
|
merchant_inventory: Arc::new(SimpleInventory::new(3)),
|
||||||
offers: Mutex::new(Vec::new()),
|
offers: Mutex::new(Vec::new()),
|
||||||
|
job_site: std::sync::Mutex::new(None),
|
||||||
|
home_pos: std::sync::Mutex::new(None),
|
||||||
|
self_weak: std::sync::Mutex::new(None),
|
||||||
};
|
};
|
||||||
let mob_arc = Arc::new(villager);
|
let mob_arc = Arc::new(villager);
|
||||||
|
*mob_arc.self_weak.lock().unwrap() = Some(Arc::downgrade(&mob_arc));
|
||||||
let mob_weak: Weak<dyn Mob> = {
|
let mob_weak: Weak<dyn Mob> = {
|
||||||
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
|
||||||
Arc::downgrade(&mob_arc)
|
Arc::downgrade(&mob_arc)
|
||||||
@ -193,9 +204,12 @@ impl VillagerEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_villager_data(&self, data: VillagerData) {
|
pub async fn set_villager_data(&self, data: VillagerData) {
|
||||||
|
let old_profession = {
|
||||||
let mut villager_data = self.villager_data.lock().await;
|
let mut villager_data = self.villager_data.lock().await;
|
||||||
let old_profession = villager_data.profession;
|
let old_profession = villager_data.profession;
|
||||||
*villager_data = data;
|
*villager_data = data;
|
||||||
|
old_profession
|
||||||
|
};
|
||||||
self.get_entity().send_meta_data(&[Metadata::new(
|
self.get_entity().send_meta_data(&[Metadata::new(
|
||||||
TrackedData::VILLAGER_DATA,
|
TrackedData::VILLAGER_DATA,
|
||||||
MetaDataType::VILLAGER_DATA,
|
MetaDataType::VILLAGER_DATA,
|
||||||
@ -203,20 +217,20 @@ impl VillagerEntity {
|
|||||||
)]);
|
)]);
|
||||||
|
|
||||||
if old_profession != data.profession {
|
if old_profession != data.profession {
|
||||||
self.generate_trades(data.profession, data.level).await;
|
self.generate_trades(data.profession_enum(), data.level.0)
|
||||||
if let Some(sound) = data.profession.work_sound() {
|
.await;
|
||||||
|
if let Some(sound) = data.profession_enum().work_sound() {
|
||||||
self.get_entity().play_sound(sound);
|
self.get_entity().play_sound(sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_trades(&self, profession: VillagerProfession, level: i32) {
|
pub async fn add_trades(&self, profession: VillagerProfession, level: i32) {
|
||||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
let mut offers = self.offers.lock().await;
|
let mut offers = self.offers.lock().await;
|
||||||
offers.clear();
|
|
||||||
|
|
||||||
if let Some(trade_set) = profession.trade_set(level) {
|
if let Some(trade_set) = profession.trade_set(level) {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
@ -247,6 +261,11 @@ impl VillagerEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn generate_trades(&self, profession: VillagerProfession, level: i32) {
|
||||||
|
self.offers.lock().await.clear();
|
||||||
|
self.add_trades(profession, level).await;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_unhappy(&self) {
|
pub fn set_unhappy(&self) {
|
||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
entity
|
entity
|
||||||
@ -256,23 +275,27 @@ impl VillagerEntity {
|
|||||||
entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo);
|
entity.play_sound(pumpkin_data::sound::Sound::EntityVillagerNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn open_trading_screen(&self, _player: &Arc<Player>) {
|
pub async fn open_trading_screen(&self, player: &Arc<Player>) {
|
||||||
// let self_weak = self.self_arc.lock().await;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
// if let Some(self_arc) = self_weak.as_ref().and_then(std::sync::Weak::upgrade) {
|
use pumpkin_protocol::java::client::play::CMerchantOffers;
|
||||||
// player.open_handled_screen(&*self_arc, None);
|
|
||||||
|
|
||||||
// let offers = self.offers.lock().await;
|
// Open the merchant screen and then send the current offers packet
|
||||||
// let villager_data = self.villager_data.lock().await;
|
if let Some(sync_id) = player.open_handled_screen(self, None).await {
|
||||||
|
let offers = self.offers.lock().await.clone();
|
||||||
|
let villager_data = self.villager_data.lock().await;
|
||||||
|
|
||||||
// player.client.enqueue_packet(&CMerchantOffers::new(
|
player
|
||||||
// player.screen_handler_sync_id.load(Ordering::Relaxed).into(),
|
.client
|
||||||
// offers.clone(),
|
.enqueue_packet(&CMerchantOffers::new(
|
||||||
// VarInt(villager_data.level),
|
VarInt(sync_id as i32),
|
||||||
// VarInt(self.xp.load(Ordering::Relaxed)),
|
offers,
|
||||||
// true,
|
villager_data.level,
|
||||||
// true,
|
VarInt(self.xp.load(Ordering::Relaxed)),
|
||||||
// ));
|
true,
|
||||||
// }
|
true,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,17 +304,92 @@ impl ScreenHandlerFactory for VillagerEntity {
|
|||||||
&'a self,
|
&'a self,
|
||||||
sync_id: u8,
|
sync_id: u8,
|
||||||
player_inventory: &'a Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
|
player_inventory: &'a Arc<pumpkin_inventory::player::player_inventory::PlayerInventory>,
|
||||||
_player: &'a dyn InventoryPlayer,
|
player: &'a dyn InventoryPlayer,
|
||||||
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
|
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let offers = self.offers.lock().await;
|
let offers = self.offers.lock().await;
|
||||||
let handler = MerchantScreenHandler::new(
|
let self_weak = self.self_weak.lock().unwrap().clone().unwrap();
|
||||||
|
let player_uuid = player
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<crate::entity::player::Player>()
|
||||||
|
.map_or_else(uuid::Uuid::nil, |p| p.get_entity().entity_uuid);
|
||||||
|
let world = self.get_entity().world.load().clone();
|
||||||
|
|
||||||
|
let mut handler = MerchantScreenHandler::new(
|
||||||
sync_id,
|
sync_id,
|
||||||
player_inventory,
|
player_inventory,
|
||||||
self.merchant_inventory.clone(),
|
self.merchant_inventory.clone(),
|
||||||
offers.clone(),
|
offers.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
handler.on_trade = Some(Box::new(move |offer_index| {
|
||||||
|
if let Some(villager) = self_weak.upgrade() {
|
||||||
|
let world = world.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Some(player) = world.get_player_by_uuid(player_uuid) {
|
||||||
|
let mut offers = villager.offers.lock().await;
|
||||||
|
if offer_index < offers.len() {
|
||||||
|
let offer = &mut offers[offer_index];
|
||||||
|
offer.uses += 1;
|
||||||
|
|
||||||
|
let xp_gain = offer.xp;
|
||||||
|
let current_xp =
|
||||||
|
villager.xp.fetch_add(xp_gain, Ordering::Relaxed) + xp_gain;
|
||||||
|
|
||||||
|
let mut data = villager.villager_data.lock().await;
|
||||||
|
let current_level = data.level.0;
|
||||||
|
if current_level < 5 {
|
||||||
|
let max_xp = match current_level {
|
||||||
|
1 => 10,
|
||||||
|
2 => 70,
|
||||||
|
3 => 150,
|
||||||
|
4 => 250,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
if current_xp >= max_xp {
|
||||||
|
data.level.0 += 1;
|
||||||
|
let new_level = data.level.0;
|
||||||
|
let prof = data.profession_enum();
|
||||||
|
drop(data);
|
||||||
|
|
||||||
|
// Level up! Add new trades for the new level
|
||||||
|
villager.add_trades(prof, new_level).await;
|
||||||
|
|
||||||
|
// Play sound & particles for level up!
|
||||||
|
let entity = villager.get_entity();
|
||||||
|
entity.world.load().send_entity_status(
|
||||||
|
entity,
|
||||||
|
pumpkin_data::entity::EntityStatus::VillagerHappy,
|
||||||
|
);
|
||||||
|
entity.play_sound(
|
||||||
|
pumpkin_data::sound::Sound::EntityVillagerCelebrate,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
drop(data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drop(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_level = villager.villager_data.lock().await.level;
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.enqueue_packet(&CMerchantOffers::new(
|
||||||
|
VarInt(sync_id as i32),
|
||||||
|
offers.clone(),
|
||||||
|
current_level,
|
||||||
|
VarInt(current_xp),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
|
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -308,9 +406,9 @@ impl NBTStorage for VillagerEntity {
|
|||||||
self.mob_entity.living_entity.entity.write_nbt(nbt).await;
|
self.mob_entity.living_entity.entity.write_nbt(nbt).await;
|
||||||
let data = self.villager_data.lock().await;
|
let data = self.villager_data.lock().await;
|
||||||
let mut villager_data_nbt = NbtCompound::new();
|
let mut villager_data_nbt = NbtCompound::new();
|
||||||
villager_data_nbt.put_int("Type", data.r#type as i32);
|
villager_data_nbt.put_int("Type", data.r#type.0);
|
||||||
villager_data_nbt.put_int("Profession", data.profession as i32);
|
villager_data_nbt.put_int("Profession", data.profession.0);
|
||||||
villager_data_nbt.put_int("Level", data.level);
|
villager_data_nbt.put_int("Level", data.level.0);
|
||||||
nbt.put_compound("VillagerData", villager_data_nbt);
|
nbt.put_compound("VillagerData", villager_data_nbt);
|
||||||
|
|
||||||
nbt.put_int("FoodLevel", self.food_level.load(Ordering::Relaxed));
|
nbt.put_int("FoodLevel", self.food_level.load(Ordering::Relaxed));
|
||||||
@ -321,6 +419,58 @@ impl NBTStorage for VillagerEntity {
|
|||||||
);
|
);
|
||||||
nbt.put_int("RestocksToday", self.restocks_today.load(Ordering::Relaxed));
|
nbt.put_int("RestocksToday", self.restocks_today.load(Ordering::Relaxed));
|
||||||
|
|
||||||
|
let job_site_pos = *self.job_site.lock().unwrap();
|
||||||
|
if let Some(pos) = job_site_pos {
|
||||||
|
nbt.put_int("JobSiteX", pos.0.x);
|
||||||
|
nbt.put_int("JobSiteY", pos.0.y);
|
||||||
|
nbt.put_int("JobSiteZ", pos.0.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
let home_pos = *self.home_pos.lock().unwrap();
|
||||||
|
if let Some(pos) = home_pos {
|
||||||
|
nbt.put_int("HomeX", pos.0.x);
|
||||||
|
nbt.put_int("HomeY", pos.0.y);
|
||||||
|
nbt.put_int("HomeZ", pos.0.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Offers
|
||||||
|
{
|
||||||
|
let offers = self.offers.lock().await;
|
||||||
|
let mut recipes = Vec::new();
|
||||||
|
for offer in offers.iter() {
|
||||||
|
let mut recipe = NbtCompound::new();
|
||||||
|
|
||||||
|
let mut buy = NbtCompound::new();
|
||||||
|
offer.base_cost_a.0.write_item_stack(&mut buy);
|
||||||
|
recipe.put_compound("buy", buy);
|
||||||
|
|
||||||
|
if let Some(cost_b) = &offer.cost_b
|
||||||
|
&& !cost_b.0.is_empty()
|
||||||
|
{
|
||||||
|
let mut buy_b = NbtCompound::new();
|
||||||
|
cost_b.0.write_item_stack(&mut buy_b);
|
||||||
|
recipe.put_compound("buyB", buy_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sell_item = NbtCompound::new();
|
||||||
|
offer.output.0.write_item_stack(&mut sell_item);
|
||||||
|
recipe.put_compound("sell", sell_item);
|
||||||
|
|
||||||
|
recipe.put_int("uses", offer.uses);
|
||||||
|
recipe.put_int("maxUses", offer.max_uses);
|
||||||
|
recipe.put_bool("rewardExp", !offer.is_disabled);
|
||||||
|
recipe.put_int("xp", offer.xp);
|
||||||
|
recipe.put_float("priceMultiplier", offer.price_multiplier);
|
||||||
|
recipe.put_int("specialPrice", offer.special_price);
|
||||||
|
recipe.put_int("demand", offer.demand);
|
||||||
|
|
||||||
|
recipes.push(pumpkin_nbt::tag::NbtTag::Compound(recipe));
|
||||||
|
}
|
||||||
|
let mut offers_compound = NbtCompound::new();
|
||||||
|
offers_compound.put("Recipes", pumpkin_nbt::tag::NbtTag::List(recipes));
|
||||||
|
nbt.put_compound("Offers", offers_compound);
|
||||||
|
};
|
||||||
|
|
||||||
// Inventory
|
// Inventory
|
||||||
let inventory = self.inventory.lock().await;
|
let inventory = self.inventory.lock().await;
|
||||||
let mut inventory_list = Vec::new();
|
let mut inventory_list = Vec::new();
|
||||||
@ -359,6 +509,7 @@ impl NBTStorage for VillagerEntity {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> crate::entity::NbtFuture<'a, ()> {
|
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> crate::entity::NbtFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
self.mob_entity
|
self.mob_entity
|
||||||
@ -369,14 +520,13 @@ impl NBTStorage for VillagerEntity {
|
|||||||
if let Some(villager_data_nbt) = nbt.get_compound("VillagerData") {
|
if let Some(villager_data_nbt) = nbt.get_compound("VillagerData") {
|
||||||
let mut data = self.villager_data.lock().await;
|
let mut data = self.villager_data.lock().await;
|
||||||
if let Some(t) = villager_data_nbt.get_int("Type") {
|
if let Some(t) = villager_data_nbt.get_int("Type") {
|
||||||
data.r#type = VillagerType::try_from(t).unwrap_or(VillagerType::Plains);
|
data.r#type = VarInt(t);
|
||||||
}
|
}
|
||||||
if let Some(p) = villager_data_nbt.get_int("Profession") {
|
if let Some(p) = villager_data_nbt.get_int("Profession") {
|
||||||
data.profession =
|
data.profession = VarInt(p);
|
||||||
VillagerProfession::try_from(p).unwrap_or(VillagerProfession::None);
|
|
||||||
}
|
}
|
||||||
if let Some(l) = villager_data_nbt.get_int("Level") {
|
if let Some(l) = villager_data_nbt.get_int("Level") {
|
||||||
data.level = l;
|
data.level = VarInt(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +543,70 @@ impl NBTStorage for VillagerEntity {
|
|||||||
self.restocks_today.store(today, Ordering::Relaxed);
|
self.restocks_today.store(today, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (Some(x), Some(y), Some(z)) = (
|
||||||
|
nbt.get_int("JobSiteX"),
|
||||||
|
nbt.get_int("JobSiteY"),
|
||||||
|
nbt.get_int("JobSiteZ"),
|
||||||
|
) {
|
||||||
|
*self.job_site.lock().unwrap() = Some(BlockPos::new(x, y, z));
|
||||||
|
} else {
|
||||||
|
*self.job_site.lock().unwrap() = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(x), Some(y), Some(z)) = (
|
||||||
|
nbt.get_int("HomeX").or_else(|| nbt.get_int("BedX")),
|
||||||
|
nbt.get_int("HomeY").or_else(|| nbt.get_int("BedY")),
|
||||||
|
nbt.get_int("HomeZ").or_else(|| nbt.get_int("BedZ")),
|
||||||
|
) {
|
||||||
|
*self.home_pos.lock().unwrap() = Some(BlockPos::new(x, y, z));
|
||||||
|
} else {
|
||||||
|
*self.home_pos.lock().unwrap() = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(offers_compound) = nbt.get_compound("Offers")
|
||||||
|
&& let Some(recipes) = offers_compound.get_list("Recipes")
|
||||||
|
{
|
||||||
|
let mut offers = self.offers.lock().await;
|
||||||
|
offers.clear();
|
||||||
|
for tag in recipes {
|
||||||
|
if let Some(recipe) = tag.extract_compound() {
|
||||||
|
let buy = recipe
|
||||||
|
.get_compound("buy")
|
||||||
|
.and_then(ItemStack::read_item_stack);
|
||||||
|
let buy_b = recipe
|
||||||
|
.get_compound("buyB")
|
||||||
|
.and_then(ItemStack::read_item_stack);
|
||||||
|
let sell_item = recipe
|
||||||
|
.get_compound("sell")
|
||||||
|
.and_then(ItemStack::read_item_stack);
|
||||||
|
|
||||||
|
if let (Some(buy), Some(sell_item)) = (buy, sell_item) {
|
||||||
|
let uses = recipe.get_int("uses").unwrap_or(0);
|
||||||
|
let max_uses = recipe.get_int("maxUses").unwrap_or(12);
|
||||||
|
let reward_exp = recipe.get_bool("rewardExp").unwrap_or(true);
|
||||||
|
let xp = recipe.get_int("xp").unwrap_or(2);
|
||||||
|
let price_multiplier =
|
||||||
|
recipe.get_float("priceMultiplier").unwrap_or(0.05);
|
||||||
|
let special_price = recipe.get_int("specialPrice").unwrap_or(0);
|
||||||
|
let demand = recipe.get_int("demand").unwrap_or(0);
|
||||||
|
|
||||||
|
offers.push(pumpkin_protocol::java::client::play::MerchantOffer {
|
||||||
|
base_cost_a: buy.into(),
|
||||||
|
output: sell_item.into(),
|
||||||
|
cost_b: buy_b.map(Into::into),
|
||||||
|
is_disabled: !reward_exp,
|
||||||
|
uses,
|
||||||
|
max_uses,
|
||||||
|
xp,
|
||||||
|
special_price,
|
||||||
|
price_multiplier,
|
||||||
|
demand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Inventory
|
// Inventory
|
||||||
if let Some(inventory_list) = nbt.get_list("Inventory") {
|
if let Some(inventory_list) = nbt.get_list("Inventory") {
|
||||||
let mut inventory = self.inventory.lock().await;
|
let mut inventory = self.inventory.lock().await;
|
||||||
@ -442,11 +656,349 @@ impl NBTStorage for VillagerEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_to_profession(block: &Block) -> Option<VillagerProfession> {
|
||||||
|
if block == &Block::COMPOSTER {
|
||||||
|
Some(VillagerProfession::Farmer)
|
||||||
|
} else if block == &Block::LECTERN {
|
||||||
|
Some(VillagerProfession::Librarian)
|
||||||
|
} else if block == &Block::BLAST_FURNACE {
|
||||||
|
Some(VillagerProfession::Armorer)
|
||||||
|
} else if block == &Block::SMOKER {
|
||||||
|
Some(VillagerProfession::Butcher)
|
||||||
|
} else if block == &Block::CARTOGRAPHY_TABLE {
|
||||||
|
Some(VillagerProfession::Cartographer)
|
||||||
|
} else if block == &Block::BREWING_STAND {
|
||||||
|
Some(VillagerProfession::Cleric)
|
||||||
|
} else if block == &Block::BARREL {
|
||||||
|
Some(VillagerProfession::Fisherman)
|
||||||
|
} else if block == &Block::FLETCHING_TABLE {
|
||||||
|
Some(VillagerProfession::Fletcher)
|
||||||
|
} else if block == &Block::CAULDRON
|
||||||
|
|| block == &Block::WATER_CAULDRON
|
||||||
|
|| block == &Block::LAVA_CAULDRON
|
||||||
|
|| block == &Block::POWDER_SNOW_CAULDRON
|
||||||
|
{
|
||||||
|
Some(VillagerProfession::Leatherworker)
|
||||||
|
} else if block == &Block::STONECUTTER {
|
||||||
|
Some(VillagerProfession::Mason)
|
||||||
|
} else if block == &Block::LOOM {
|
||||||
|
Some(VillagerProfession::Shepherd)
|
||||||
|
} else if block == &Block::SMITHING_TABLE {
|
||||||
|
Some(VillagerProfession::Toolsmith)
|
||||||
|
} else if block == &Block::GRINDSTONE {
|
||||||
|
Some(VillagerProfession::Weaponsmith)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profession_matches_block(profession: VillagerProfession, block: &Block) -> bool {
|
||||||
|
match profession {
|
||||||
|
VillagerProfession::Farmer => block == &Block::COMPOSTER,
|
||||||
|
VillagerProfession::Librarian => block == &Block::LECTERN,
|
||||||
|
VillagerProfession::Armorer => block == &Block::BLAST_FURNACE,
|
||||||
|
VillagerProfession::Butcher => block == &Block::SMOKER,
|
||||||
|
VillagerProfession::Cartographer => block == &Block::CARTOGRAPHY_TABLE,
|
||||||
|
VillagerProfession::Cleric => block == &Block::BREWING_STAND,
|
||||||
|
VillagerProfession::Fisherman => block == &Block::BARREL,
|
||||||
|
VillagerProfession::Fletcher => block == &Block::FLETCHING_TABLE,
|
||||||
|
VillagerProfession::Leatherworker => {
|
||||||
|
block == &Block::CAULDRON
|
||||||
|
|| block == &Block::WATER_CAULDRON
|
||||||
|
|| block == &Block::LAVA_CAULDRON
|
||||||
|
|| block == &Block::POWDER_SNOW_CAULDRON
|
||||||
|
}
|
||||||
|
VillagerProfession::Mason => block == &Block::STONECUTTER,
|
||||||
|
VillagerProfession::Shepherd => block == &Block::LOOM,
|
||||||
|
VillagerProfession::Toolsmith => block == &Block::SMITHING_TABLE,
|
||||||
|
VillagerProfession::Weaponsmith => block == &Block::GRINDSTONE,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Mob for VillagerEntity {
|
impl Mob for VillagerEntity {
|
||||||
fn get_mob_entity(&self) -> &MobEntity {
|
fn get_mob_entity(&self) -> &MobEntity {
|
||||||
&self.mob_entity
|
&self.mob_entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_job_site(&self) -> Option<BlockPos> {
|
||||||
|
*self.job_site.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_home(&self) -> Option<BlockPos> {
|
||||||
|
*self.home_pos.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn mob_tick<'a>(
|
||||||
|
&'a self,
|
||||||
|
_caller: &'a Arc<dyn EntityBase>,
|
||||||
|
) -> crate::entity::EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let age = self.get_entity().age.load(Ordering::Relaxed);
|
||||||
|
if age % 20 != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let world = self.get_entity().world.load();
|
||||||
|
|
||||||
|
// 1. Bed / Sleeping logic (for all villagers: babies, nitwits, adults)
|
||||||
|
let is_sleeping = self.get_entity().pose.load() == EntityPose::Sleeping;
|
||||||
|
|
||||||
|
// Check if current bed is still valid
|
||||||
|
if let Some(current_home) = self.get_home_pos() {
|
||||||
|
let (block, state) = world.get_block_and_state(¤t_home);
|
||||||
|
let valid = if block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEDS) {
|
||||||
|
let bed_props = BedProperties::from_state_id(state.id, block);
|
||||||
|
bed_props.part == BedPart::Head
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
*self.home_pos.lock().unwrap() = None;
|
||||||
|
if is_sleeping {
|
||||||
|
// Wake up if bed was broken
|
||||||
|
self.get_entity().set_pose(EntityPose::Standing);
|
||||||
|
self.get_entity().send_meta_data(&[Metadata::new(
|
||||||
|
TrackedData::SLEEPING_POS_ID,
|
||||||
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
|
None::<BlockPos>,
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no bed, search for one
|
||||||
|
if self.get_home_pos().is_none() {
|
||||||
|
let pos = self.get_entity().block_pos.load();
|
||||||
|
let start = BlockPos::new(pos.0.x - 16, pos.0.y - 4, pos.0.z - 16);
|
||||||
|
let end = BlockPos::new(pos.0.x + 16, pos.0.y + 4, pos.0.z + 16);
|
||||||
|
|
||||||
|
let aabb = BoundingBox::new(
|
||||||
|
Vector3::new(
|
||||||
|
pos.0.x as f64 - 32.0,
|
||||||
|
pos.0.y as f64 - 16.0,
|
||||||
|
pos.0.z as f64 - 32.0,
|
||||||
|
),
|
||||||
|
Vector3::new(
|
||||||
|
pos.0.x as f64 + 32.0,
|
||||||
|
pos.0.y as f64 + 16.0,
|
||||||
|
pos.0.z as f64 + 32.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let nearby_entities = world.get_all_at_box(&aabb);
|
||||||
|
|
||||||
|
let mut claimed_homes = Vec::new();
|
||||||
|
for entity in nearby_entities {
|
||||||
|
if entity.get_entity().entity_id != self.get_entity().entity_id
|
||||||
|
&& entity.get_entity().entity_type
|
||||||
|
== &pumpkin_data::entity::EntityType::VILLAGER
|
||||||
|
&& let Some(home) = entity.get_home_pos()
|
||||||
|
{
|
||||||
|
claimed_homes.push(home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut best_home = None;
|
||||||
|
let mut best_dist = f64::MAX;
|
||||||
|
|
||||||
|
for p in BlockPos::iterate(start, end) {
|
||||||
|
let (block, state) = world.get_block_and_state(&p);
|
||||||
|
if block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEDS) {
|
||||||
|
let bed_props = BedProperties::from_state_id(state.id, block);
|
||||||
|
let bed_head_pos = if bed_props.part == BedPart::Head {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
p.offset(bed_props.facing.to_offset())
|
||||||
|
};
|
||||||
|
|
||||||
|
if claimed_homes.contains(&bed_head_pos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dist = bed_head_pos
|
||||||
|
.to_f64()
|
||||||
|
.squared_distance_to_vec(&self.get_entity().pos.load());
|
||||||
|
if dist < best_dist {
|
||||||
|
best_dist = dist;
|
||||||
|
best_home = Some(bed_head_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(home) = best_home {
|
||||||
|
*self.home_pos.lock().unwrap() = Some(home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Sleeping/Waking up based on time
|
||||||
|
let is_sleeping = self.get_entity().pose.load() == EntityPose::Sleeping;
|
||||||
|
if let Some(home_pos) = self.get_home_pos() {
|
||||||
|
let time = world.level_time.lock().await.time_of_day;
|
||||||
|
let is_night = (12000..=23000).contains(&time);
|
||||||
|
|
||||||
|
if is_night {
|
||||||
|
if !is_sleeping {
|
||||||
|
// Check distance to bed. If close enough, go to sleep
|
||||||
|
let dist = home_pos
|
||||||
|
.to_f64()
|
||||||
|
.squared_distance_to_vec(&self.get_entity().pos.load());
|
||||||
|
if dist <= 4.0 {
|
||||||
|
// Within 2 blocks (squared distance 4.0)
|
||||||
|
let (block, state) = world.get_block_and_state(&home_pos);
|
||||||
|
if block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEDS) {
|
||||||
|
let bed_props = BedProperties::from_state_id(state.id, block);
|
||||||
|
if !bed_props.occupied {
|
||||||
|
// Make bed occupied
|
||||||
|
BedBlock::set_occupied(
|
||||||
|
true, &world, block, &home_pos, state.id,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self.get_entity().set_pose(EntityPose::Sleeping);
|
||||||
|
self.get_entity().send_meta_data(&[Metadata::new(
|
||||||
|
TrackedData::SLEEPING_POS_ID,
|
||||||
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
|
Some(home_pos),
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if is_sleeping {
|
||||||
|
// It is day, wake up!
|
||||||
|
let (block, state) = world.get_block_and_state(&home_pos);
|
||||||
|
if block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BEDS) {
|
||||||
|
let bed_props = BedProperties::from_state_id(state.id, block);
|
||||||
|
if bed_props.occupied {
|
||||||
|
BedBlock::set_occupied(false, &world, block, &home_pos, state.id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.get_entity().set_pose(EntityPose::Standing);
|
||||||
|
self.get_entity().send_meta_data(&[Metadata::new(
|
||||||
|
TrackedData::SLEEPING_POS_ID,
|
||||||
|
MetaDataType::OPTIONAL_BLOCK_POS,
|
||||||
|
None::<BlockPos>,
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Job / Profession logic (skip for Nitwits and babies)
|
||||||
|
let data = self.villager_data.lock().await;
|
||||||
|
let is_adult = self.get_entity().age.load(Ordering::Relaxed) >= 0;
|
||||||
|
let xp = self.xp.load(Ordering::Relaxed);
|
||||||
|
let profession = data.profession_enum();
|
||||||
|
drop(data);
|
||||||
|
|
||||||
|
if profession == VillagerProfession::Nitwit || !is_adult {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(current_site) = self.get_job_site() {
|
||||||
|
let (block, _state) = world.get_block_and_state(¤t_site);
|
||||||
|
let valid = if profession == VillagerProfession::None {
|
||||||
|
block_to_profession(block).is_some()
|
||||||
|
} else {
|
||||||
|
profession_matches_block(profession, block)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
*self.job_site.lock().unwrap() = None;
|
||||||
|
if xp == 0 && profession != VillagerProfession::None {
|
||||||
|
let r#type = self.villager_data.lock().await.type_enum();
|
||||||
|
self.set_villager_data(VillagerData::new(
|
||||||
|
r#type,
|
||||||
|
VillagerProfession::None,
|
||||||
|
1,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
self.offers.lock().await.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.get_job_site().is_none() {
|
||||||
|
let pos = self.get_entity().block_pos.load();
|
||||||
|
let start = BlockPos::new(pos.0.x - 10, pos.0.y - 4, pos.0.z - 10);
|
||||||
|
let end = BlockPos::new(pos.0.x + 10, pos.0.y + 4, pos.0.z + 10);
|
||||||
|
|
||||||
|
let aabb = BoundingBox::new(
|
||||||
|
Vector3::new(
|
||||||
|
pos.0.x as f64 - 32.0,
|
||||||
|
pos.0.y as f64 - 16.0,
|
||||||
|
pos.0.z as f64 - 32.0,
|
||||||
|
),
|
||||||
|
Vector3::new(
|
||||||
|
pos.0.x as f64 + 32.0,
|
||||||
|
pos.0.y as f64 + 16.0,
|
||||||
|
pos.0.z as f64 + 32.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let nearby_entities = world.get_all_at_box(&aabb);
|
||||||
|
|
||||||
|
let mut claimed_sites = Vec::new();
|
||||||
|
for entity in nearby_entities {
|
||||||
|
if entity.get_entity().entity_id != self.get_entity().entity_id
|
||||||
|
&& entity.get_entity().entity_type
|
||||||
|
== &pumpkin_data::entity::EntityType::VILLAGER
|
||||||
|
&& let Some(site) = entity.get_job_site_pos()
|
||||||
|
{
|
||||||
|
claimed_sites.push(site);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut best_site = None;
|
||||||
|
let mut best_dist = f64::MAX;
|
||||||
|
let mut best_profession = VillagerProfession::None;
|
||||||
|
|
||||||
|
for p in BlockPos::iterate(start, end) {
|
||||||
|
if claimed_sites.contains(&p) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (block, _state) = world.get_block_and_state(&p);
|
||||||
|
if let Some(prof) = block_to_profession(block) {
|
||||||
|
if profession != VillagerProfession::None && prof != profession {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dist = p
|
||||||
|
.to_f64()
|
||||||
|
.squared_distance_to_vec(&self.get_entity().pos.load());
|
||||||
|
if dist < best_dist {
|
||||||
|
best_dist = dist;
|
||||||
|
best_site = Some(p);
|
||||||
|
best_profession = prof;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(site) = best_site {
|
||||||
|
*self.job_site.lock().unwrap() = Some(site);
|
||||||
|
if profession == VillagerProfession::None {
|
||||||
|
let r#type = self.villager_data.lock().await.type_enum();
|
||||||
|
self.set_villager_data(VillagerData::new(r#type, best_profession, 1))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let current_prof = self.villager_data.lock().await.profession_enum();
|
||||||
|
if current_prof == VillagerProfession::None
|
||||||
|
&& let Some(site) = self.get_job_site()
|
||||||
|
{
|
||||||
|
let (block, _state) = world.get_block_and_state(&site);
|
||||||
|
if let Some(prof) = block_to_profession(block) {
|
||||||
|
let r#type = self.villager_data.lock().await.type_enum();
|
||||||
|
self.set_villager_data(VillagerData::new(r#type, prof, 1))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn mob_interact<'a>(
|
fn mob_interact<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
player: &'a Arc<Player>,
|
player: &'a Arc<Player>,
|
||||||
@ -459,7 +1011,23 @@ impl Mob for VillagerEntity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let offers = self.offers.lock().await;
|
let mut offers = self.offers.lock().await;
|
||||||
|
if offers.is_empty() {
|
||||||
|
let data = self.villager_data.lock().await;
|
||||||
|
if data.profession_enum() != VillagerProfession::None
|
||||||
|
&& data.profession_enum() != VillagerProfession::Nitwit
|
||||||
|
{
|
||||||
|
let prof = data.profession_enum();
|
||||||
|
let level = data.level.0;
|
||||||
|
drop(data);
|
||||||
|
drop(offers);
|
||||||
|
self.generate_trades(prof, level).await;
|
||||||
|
offers = self.offers.lock().await;
|
||||||
|
} else {
|
||||||
|
drop(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if offers.is_empty() {
|
if offers.is_empty() {
|
||||||
self.set_unhappy();
|
self.set_unhappy();
|
||||||
return true;
|
return true;
|
||||||
@ -474,7 +1042,7 @@ impl Mob for VillagerEntity {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.open_trading_screen(&player);
|
self.open_trading_screen(&player).await;
|
||||||
|
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4796,6 +4796,10 @@ impl MessageCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InventoryPlayer for Player {
|
impl InventoryPlayer for Player {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn drop_item(&self, item: ItemStack, _retain_ownership: bool) -> PlayerFuture<'_, ()> {
|
fn drop_item(&self, item: ItemStack, _retain_ownership: bool) -> PlayerFuture<'_, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
self.drop_item(item).await;
|
self.drop_item(item).await;
|
||||||
|
|||||||
@ -150,15 +150,13 @@ impl EntityBase for MinecartEntity {
|
|||||||
&pos,
|
&pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.vehicle.get_hurt_time() == 0 {
|
||||||
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);
|
||||||
// TODO: Send entity status
|
// TODO: Send entity status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if is_powered_rail {
|
} else if is_powered_rail {
|
||||||
let mut velocity = self.vehicle.entity.velocity.load();
|
let mut velocity = self.vehicle.entity.velocity.load();
|
||||||
velocity = velocity.multiply(0.5, 0.5, 0.5);
|
velocity = velocity.multiply(0.5, 0.5, 0.5);
|
||||||
|
|||||||
@ -1775,13 +1775,15 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
ActionType::Interact | ActionType::InteractAt => {
|
ActionType::Interact | ActionType::InteractAt => {
|
||||||
let held = player.inventory.held_item();
|
let held = player.inventory.held_item();
|
||||||
let mut stack = held.lock().await;
|
let mut stack = held.lock().await.clone();
|
||||||
if !event.target.interact(player, &mut stack).await {
|
let interacted = event.target.interact(player, &mut stack).await;
|
||||||
|
if !interacted {
|
||||||
server
|
server
|
||||||
.item_registry
|
.item_registry
|
||||||
.use_on_entity(&mut stack, player, event.target)
|
.use_on_entity(&mut stack, player, event.target)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
*held.lock().await = stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user