fix: many non event firing
This commit is contained in:
parent
feba9acb25
commit
f8d1627697
@ -359,19 +359,24 @@ impl ClientPacket for CSpawnLivingEntity {
|
||||
self.velocity.write_legacy(&mut write)?;
|
||||
|
||||
if !v1_15 {
|
||||
if v1_9 {
|
||||
if let Some(metadata) = &self.metadata {
|
||||
write.write_slice(metadata)?;
|
||||
} else if v1_9 {
|
||||
write.write_u8(0xFF)?;
|
||||
} else {
|
||||
// In <= 1.8 (specifically 1.7.10), DataWatcher requires at least one entry
|
||||
// so that `readWatchedObjectsFromPacketBuffer` returns a non-null List,
|
||||
// avoiding a NullPointerException in `S0FPacketSpawnMob.func_149027_c()`.
|
||||
// (0 << 5) | 0 = 0 (type: byte, index: 0 flags), 0 (value), 127 (terminator)
|
||||
write.write_slice(&[0x00, 0x00, 127])?;
|
||||
write.write_u8(0xFF)?;
|
||||
}
|
||||
} else if let Some(metadata) = &self.metadata
|
||||
&& metadata.last().copied() == Some(127)
|
||||
{
|
||||
write.write_slice(metadata)?;
|
||||
} else {
|
||||
// In <= 1.8, write the 127 (0x7F) terminator byte for the DataWatcher list,
|
||||
// matching vanilla and PacketEvents.
|
||||
write.write_u8(127)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,20 @@ impl BlockBehaviour for FarmlandBlock {
|
||||
// TODO: add rain check. Remember to check which one is most optimized.
|
||||
if is_water_nearby(args.world, args.position) {
|
||||
let mut props = FarmlandProperties::default(args.block);
|
||||
props.moisture = 7;
|
||||
let mut new_moisture = 7;
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::moisture_change::MoistureChangeEvent::new(
|
||||
*args.position,
|
||||
args.world.clone(),
|
||||
new_moisture,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
new_moisture = event.new_moisture;
|
||||
}
|
||||
props.moisture = new_moisture.clamp(0, 7) as u8;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
@ -80,7 +93,20 @@ impl BlockBehaviour for FarmlandBlock {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
props.moisture = (props.moisture as i32 - 1).clamp(0, 7) as u8;
|
||||
let mut new_moisture = (props.moisture as i32 - 1).clamp(0, 7);
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::moisture_change::MoistureChangeEvent::new(
|
||||
*args.position,
|
||||
args.world.clone(),
|
||||
new_moisture,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
new_moisture = event.new_moisture;
|
||||
}
|
||||
props.moisture = new_moisture.clamp(0, 7) as u8;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
|
||||
@ -144,6 +144,17 @@ impl FireBlock {
|
||||
let block = world.get_block(pos);
|
||||
let odds = Self::get_burn_odds(block);
|
||||
if rand::rng().random_range(0..chance) < odds {
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::block_burn::BlockBurnEvent {
|
||||
igniting_block: &Block::FIRE,
|
||||
block,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let old_block = block;
|
||||
if rand::rng().random_range(0..(age + 10) as i32) < 5
|
||||
&& !Self::is_near_rain(world.as_ref(), pos)
|
||||
@ -416,10 +427,24 @@ impl BlockBehaviour for FireBlock {
|
||||
let mut new_fire_props =
|
||||
FireProperties::from_state_id(fire_state_id);
|
||||
new_fire_props.age = spread_age;
|
||||
let new_state_id = new_fire_props.to_state_id(&Block::FIRE);
|
||||
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::block_spread::BlockSpreadEvent::new(
|
||||
*pos,
|
||||
offset_pos,
|
||||
world.clone(),
|
||||
new_state_id,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
world.set_block_state(
|
||||
&offset_pos,
|
||||
new_fire_props.to_state_id(&Block::FIRE),
|
||||
new_state_id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
|
||||
@ -101,6 +101,16 @@ impl BlockBehaviour for LeavesBlock {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = OakLeavesLikeProperties::from_state_id(state_id);
|
||||
if !props.persistent && props.distance == DECAY_DISTANCE {
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::leaves_decay::LeavesDecayEvent::new(
|
||||
*args.position,
|
||||
args.world.clone(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
|
||||
@ -73,11 +73,27 @@ trait CropBlockBase: PlantBlockBase {
|
||||
let f = get_available_moisture(world, pos, block);
|
||||
if rand::rng().random_range(0..=(25.0 / f).floor() as i64) == 0 {
|
||||
let new_state_id = self.state_with_age(block, state, age + 1);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::block_grow::BlockGrowEvent::new(
|
||||
world.clone(),
|
||||
block,
|
||||
state,
|
||||
block,
|
||||
new_state_id,
|
||||
*pos,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
world.set_block_state(pos, event.new_state_id, BlockFlags::NOTIFY_LISTENERS);
|
||||
} else {
|
||||
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_LISTENERS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_moisture(world: &World, pos: &BlockPos, block: &Block) -> f32 {
|
||||
let mut moisture = 1.0;
|
||||
|
||||
@ -37,12 +37,28 @@ impl BlockBehaviour for SweetBerryBushBlock {
|
||||
match props.age {
|
||||
2 | 3 => {
|
||||
let index = props.age;
|
||||
props.age = 1;
|
||||
let count: u8 = rand::rng().random_range((index - 1)..=(index));
|
||||
for _ in 0..count {
|
||||
let mut drops = vec![ItemStack::new(count, &Item::SWEET_BERRIES)];
|
||||
if let Some(player_arc) = args.world.get_player_by_uuid(args.player.gameprofile.id)
|
||||
&& let Some(server) = args.world.server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_harvest_block::PlayerHarvestBlockEvent {
|
||||
player: player_arc,
|
||||
block_pos: *args.position,
|
||||
harvested_items: drops.clone(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
drops = event.harvested_items;
|
||||
}
|
||||
props.age = 1;
|
||||
for stack in drops {
|
||||
args.world.drop_stack(
|
||||
args.position,
|
||||
ItemStack::new(1, &Item::SWEET_BERRIES), //
|
||||
stack,
|
||||
);
|
||||
}
|
||||
args.world.set_block_state(
|
||||
|
||||
@ -133,6 +133,18 @@ impl CrafterBlock {
|
||||
world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS);
|
||||
|
||||
let mut result_stack = ItemStack::new(recipe_result.count, item);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::crafter_craft::CrafterCraftEvent::new(
|
||||
*pos,
|
||||
world.clone(),
|
||||
result_stack.clone(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
result_stack = event.result;
|
||||
}
|
||||
Self::dispense_item(world, pos, crafter, &mut result_stack, props.orientation);
|
||||
|
||||
for i in 0..CrafterBlockEntity::INVENTORY_SIZE {
|
||||
|
||||
@ -106,6 +106,20 @@ impl BrewingStandBlockEntity {
|
||||
|
||||
let ingredient_id = ingredient.get_item().id;
|
||||
|
||||
// Fire BrewEvent before mutating items
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut brew_event = crate::plugin::api::events::inventory::brew::BrewEvent::new(
|
||||
self.position,
|
||||
self.fuel.load(Ordering::Relaxed) as u8,
|
||||
);
|
||||
server
|
||||
.plugin_manager
|
||||
.fire_blocking(&server, &mut brew_event);
|
||||
if brew_event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Brew potion slots
|
||||
let mut ingredient_used = false;
|
||||
{
|
||||
@ -171,17 +185,6 @@ impl BrewingStandBlockEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire BrewEvent
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut brew_event = crate::plugin::api::events::inventory::brew::BrewEvent::new(
|
||||
self.position,
|
||||
self.fuel.load(Ordering::Relaxed) as u8,
|
||||
);
|
||||
server
|
||||
.plugin_manager
|
||||
.fire_blocking(&server, &mut brew_event);
|
||||
}
|
||||
|
||||
// Check if remaining ingredient matches or clear it
|
||||
if let Ok(items) = self.items.read() {
|
||||
let remaining = &items[3];
|
||||
@ -441,6 +444,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn tick(&self, world: &Arc<crate::world::World>) {
|
||||
// Refill fuel counter from fuel item if needed
|
||||
let fuel_refilled = self.fuel.load(Ordering::Relaxed) <= 0
|
||||
@ -458,10 +462,18 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
server
|
||||
.plugin_manager
|
||||
.fire_blocking(&server, &mut fuel_event);
|
||||
if fuel_event.cancelled {
|
||||
false
|
||||
} else {
|
||||
self.fuel.store(fuel_event.fuel_power as i32, Ordering::Relaxed);
|
||||
items[4].decrement(1);
|
||||
true
|
||||
}
|
||||
} else {
|
||||
self.fuel.store(20, Ordering::Relaxed);
|
||||
items[4].decrement(1);
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@ -493,9 +505,24 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
self.mark_dirty();
|
||||
}
|
||||
} else if brewable && self.fuel.load(Ordering::Relaxed) > 0 {
|
||||
let brew_time = if let Some(server) = world.server.upgrade() {
|
||||
let mut start_event = crate::plugin::api::events::block::brewing_start::BrewingStartEvent::new(
|
||||
self.position,
|
||||
world.clone(),
|
||||
400,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut start_event);
|
||||
if start_event.cancelled {
|
||||
return;
|
||||
}
|
||||
start_event.brewing_time
|
||||
} else {
|
||||
400
|
||||
};
|
||||
|
||||
// Start new brewing cycle
|
||||
self.fuel.fetch_sub(1, Ordering::Relaxed);
|
||||
self.brew_time.store(400, Ordering::Relaxed);
|
||||
self.brew_time.store(brew_time, Ordering::Relaxed);
|
||||
*self
|
||||
.ingredient_item
|
||||
.lock()
|
||||
|
||||
@ -108,7 +108,22 @@ impl BlockEntity for CampfireBlockEntity {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if *total <= 0 {
|
||||
*total = recipe.cookingtime;
|
||||
let mut cooking_time = recipe.cookingtime;
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::campfire_start::CampfireStartEvent::new(
|
||||
self.position,
|
||||
world.clone(),
|
||||
stack.clone(),
|
||||
slot as u8,
|
||||
cooking_time,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
continue;
|
||||
}
|
||||
cooking_time = event.cooking_time;
|
||||
}
|
||||
*total = cooking_time;
|
||||
}
|
||||
*total
|
||||
};
|
||||
@ -130,7 +145,20 @@ impl BlockEntity for CampfireBlockEntity {
|
||||
continue;
|
||||
};
|
||||
|
||||
let result = ItemStack::new(recipe.result.count, result_item);
|
||||
let mut result = ItemStack::new(recipe.result.count, result_item);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::block_cook::BlockCookEvent::new(
|
||||
self.position,
|
||||
world.clone(),
|
||||
stack.clone(),
|
||||
result.clone(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
continue;
|
||||
}
|
||||
result = event.result;
|
||||
}
|
||||
Self::spawn_cooked_item(world, self.position, result);
|
||||
|
||||
*self.items[slot]
|
||||
|
||||
@ -398,6 +398,8 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
base_fuel_ticks
|
||||
};
|
||||
|
||||
let mut burn_ticks = adjusted_fuel_ticks;
|
||||
let mut burn_cancelled = false;
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut burn_event = $crate::plugin::api::events::inventory::furnace_burn::FurnaceBurnEvent::new(
|
||||
self.position,
|
||||
@ -405,9 +407,15 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
adjusted_fuel_ticks as u32,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut burn_event);
|
||||
if burn_event.cancelled {
|
||||
burn_cancelled = true;
|
||||
} else {
|
||||
burn_ticks = burn_event.burn_time as u16;
|
||||
}
|
||||
self.set_lit_time_remaining(adjusted_fuel_ticks);
|
||||
self.set_lit_total_time(adjusted_fuel_ticks);
|
||||
}
|
||||
if !burn_cancelled {
|
||||
self.set_lit_time_remaining(burn_ticks);
|
||||
self.set_lit_total_time(burn_ticks);
|
||||
|
||||
if self.is_burning() {
|
||||
is_dirty = true;
|
||||
@ -428,8 +436,10 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_burning() && can_accept_output {
|
||||
let mut start_cancelled = false;
|
||||
if self.get_cooking_time_spent() == 0 {
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut start_event = $crate::plugin::api::events::inventory::furnace_start_smelt::FurnaceStartSmeltEvent::new(
|
||||
@ -438,8 +448,14 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
self.get_cooking_total_time() as u32,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut start_event);
|
||||
if start_event.cancelled {
|
||||
start_cancelled = true;
|
||||
} else {
|
||||
self.set_cooking_total_time(start_event.cooking_time as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !start_cancelled {
|
||||
self.cooking_time_spent.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
if self.get_cooking_time_spent() == self.get_cooking_total_time() {
|
||||
@ -448,6 +464,7 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
let cooking_total_time = cooking_recipe.cookingtime;
|
||||
self.set_cooking_total_time(cooking_total_time as u16);
|
||||
|
||||
let mut smelt_cancelled = false;
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut smelt_event = $crate::plugin::api::events::inventory::furnace_smelt::FurnaceSmeltEvent::new(
|
||||
self.position,
|
||||
@ -455,11 +472,17 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
cooking_recipe.result.id.to_string(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut smelt_event);
|
||||
if smelt_event.cancelled {
|
||||
smelt_cancelled = true;
|
||||
}
|
||||
}
|
||||
if !smelt_cancelled {
|
||||
self.craft_recipe(Some(cooking_recipe));
|
||||
is_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.set_cooking_time_spent(0);
|
||||
}
|
||||
|
||||
@ -526,6 +526,18 @@ impl BlockRegistry {
|
||||
return false;
|
||||
}
|
||||
if behaviour.is_bonemeal_success(args) {
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::block::block_fertilize::BlockFertilizeEvent::new(
|
||||
*position,
|
||||
world.clone(),
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
behaviour.perform_bonemeal(args);
|
||||
}
|
||||
true
|
||||
|
||||
@ -155,6 +155,9 @@ impl Goal for BreakDoorGoal {
|
||||
);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
world.set_block_state(&door_pos, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
|
||||
world.sync_world_event(
|
||||
|
||||
@ -58,14 +58,11 @@ impl BreathManager {
|
||||
}
|
||||
|
||||
let in_water = Self::is_eye_in_water(player);
|
||||
let prev = self.air_supply.load(Ordering::Relaxed);
|
||||
|
||||
if in_water {
|
||||
let prev = self
|
||||
.air_supply
|
||||
.fetch_sub(AIR_DEPLETION_RATE, Ordering::Relaxed);
|
||||
let new_air = (prev - AIR_DEPLETION_RATE).max(0);
|
||||
let mut new_air = (prev - AIR_DEPLETION_RATE).max(0);
|
||||
if new_air != prev {
|
||||
self.air_supply.store(new_air, Ordering::Relaxed);
|
||||
let server = player.world().server.upgrade();
|
||||
if let Some(server) = server {
|
||||
let mut event = crate::plugin::api::events::entity::entity_air_change::EntityAirChangeEvent::new(
|
||||
@ -73,7 +70,12 @@ impl BreathManager {
|
||||
new_air,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
new_air = event.amount.clamp(0, MAX_AIR);
|
||||
}
|
||||
self.air_supply.store(new_air, Ordering::Relaxed);
|
||||
self.send_air_supply(player);
|
||||
}
|
||||
|
||||
@ -88,9 +90,20 @@ impl BreathManager {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let prev = self.air_supply.load(Ordering::Relaxed);
|
||||
let new_air = (prev + AIR_RECOVERY_RATE).min(MAX_AIR);
|
||||
let mut new_air = (prev + AIR_RECOVERY_RATE).min(MAX_AIR);
|
||||
if new_air != prev {
|
||||
let server = player.world().server.upgrade();
|
||||
if let Some(server) = server {
|
||||
let mut event = crate::plugin::api::events::entity::entity_air_change::EntityAirChangeEvent::new(
|
||||
player.entity_id(),
|
||||
new_air,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
new_air = event.amount.clamp(0, MAX_AIR);
|
||||
}
|
||||
self.air_supply.store(new_air, Ordering::Relaxed);
|
||||
self.send_air_supply(player);
|
||||
}
|
||||
|
||||
@ -407,6 +407,7 @@ impl CombatTracker {
|
||||
pub fn get_death_message(
|
||||
&self,
|
||||
victim_name: pumpkin_util::text::TextComponent,
|
||||
kill_credit_name: Option<pumpkin_util::text::TextComponent>,
|
||||
) -> pumpkin_util::text::TextComponent {
|
||||
if self.entries.is_empty() {
|
||||
return pumpkin_util::text::TextComponent::translate_cross(
|
||||
@ -456,7 +457,7 @@ impl CombatTracker {
|
||||
)
|
||||
}
|
||||
pumpkin_data::damage::DeathMessageType::Default => {
|
||||
self.get_default_death_message(victim_name, killing_blow)
|
||||
self.get_default_death_message(victim_name, killing_blow, kill_credit_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -585,6 +586,7 @@ impl CombatTracker {
|
||||
&self,
|
||||
victim_name: pumpkin_util::text::TextComponent,
|
||||
killing_blow: &CombatEntry,
|
||||
kill_credit_name: Option<pumpkin_util::text::TextComponent>,
|
||||
) -> pumpkin_util::text::TextComponent {
|
||||
let damage_type = killing_blow.damage_type;
|
||||
let msg_id = damage_type.message_id;
|
||||
@ -598,18 +600,18 @@ impl CombatTracker {
|
||||
)
|
||||
} else {
|
||||
pumpkin_util::text::TextComponent::translate_cross(
|
||||
format!("death.attack.{msg_id}.player"),
|
||||
format!("death.attack.{msg_id}.player"),
|
||||
format!("death.attack.{msg_id}"),
|
||||
format!("death.attack.{msg_id}"),
|
||||
[victim_name, attacker_name.clone()],
|
||||
)
|
||||
}
|
||||
} else if let Some(killer) = self.get_killer_entry()
|
||||
&& let Some(killer_name) = &killer.attacker_name
|
||||
{
|
||||
} else if let Some(killer_name) = kill_credit_name.or_else(|| {
|
||||
self.get_killer_entry().and_then(|k| k.attacker_name.clone())
|
||||
}) {
|
||||
pumpkin_util::text::TextComponent::translate_cross(
|
||||
format!("death.attack.{msg_id}.player"),
|
||||
format!("death.attack.{msg_id}.player"),
|
||||
[victim_name, killer_name.clone()],
|
||||
[victim_name, killer_name],
|
||||
)
|
||||
} else {
|
||||
pumpkin_util::text::TextComponent::translate_cross(
|
||||
|
||||
@ -14,10 +14,9 @@ use pumpkin_util::Hand;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{
|
||||
AtomicBool, AtomicU8,
|
||||
AtomicBool, AtomicI32, AtomicI64, AtomicU8,
|
||||
Ordering::{Relaxed, SeqCst},
|
||||
};
|
||||
use tracing::warn;
|
||||
@ -119,6 +118,15 @@ pub struct LivingEntity {
|
||||
/// Tracks combat entries, assisted falls, kill credit, and death messages.
|
||||
pub combat_tracker: std::sync::Mutex<CombatTracker>,
|
||||
|
||||
/// The ID of the player that last hurt this entity.
|
||||
pub last_hurt_by_player_id: AtomicI32,
|
||||
/// The tick at which this entity was last hurt by a player.
|
||||
pub last_hurt_by_player_time: AtomicI64,
|
||||
/// The ID of the mob/entity that last hurt this entity.
|
||||
pub last_hurt_by_mob_id: AtomicI32,
|
||||
/// The tick at which this entity was last hurt by a mob/entity.
|
||||
pub last_hurt_by_mob_time: AtomicI64,
|
||||
|
||||
water_movement_speed_multiplier: f32,
|
||||
livings_flags: AtomicU8,
|
||||
|
||||
@ -231,12 +239,56 @@ impl LivingEntity {
|
||||
last_attacking_id: AtomicI32::new(0),
|
||||
last_attack_time: AtomicI32::new(0),
|
||||
combat_tracker: std::sync::Mutex::new(CombatTracker::new()),
|
||||
last_hurt_by_player_id: AtomicI32::new(0),
|
||||
last_hurt_by_player_time: AtomicI64::new(0),
|
||||
last_hurt_by_mob_id: AtomicI32::new(0),
|
||||
last_hurt_by_mob_time: AtomicI64::new(0),
|
||||
movement_input: AtomicCell::new(Vector3::default()),
|
||||
water_movement_speed_multiplier,
|
||||
last_block_pos: AtomicCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the entity that should receive kill credit for this entity's death.
|
||||
/// Following vanilla Java logic (`LivingEntity.getKillCredit`):
|
||||
/// 1. Prioritize `last_hurt_by_player` if hurt within the last 100 ticks (5 seconds).
|
||||
/// 2. Then `last_hurt_by_mob` if hurt within the last 100 ticks.
|
||||
/// 3. Fall back to combat tracker's killer entry if available.
|
||||
pub fn get_kill_credit(&self) -> Option<Arc<dyn EntityBase>> {
|
||||
let world = self.entity.world.load();
|
||||
let current_tick = world.level_info.load().day_time;
|
||||
|
||||
let player_id = self.last_hurt_by_player_id.load(Relaxed);
|
||||
let player_time = self.last_hurt_by_player_time.load(Relaxed);
|
||||
if player_id != 0
|
||||
&& (current_tick - player_time).abs() <= 100
|
||||
&& let Some(player) = world.get_entity_by_id(player_id)
|
||||
{
|
||||
return Some(player);
|
||||
}
|
||||
|
||||
let mob_id = self.last_hurt_by_mob_id.load(Relaxed);
|
||||
let mob_time = self.last_hurt_by_mob_time.load(Relaxed);
|
||||
if mob_id != 0
|
||||
&& (current_tick - mob_time).abs() <= 100
|
||||
&& let Some(mob) = world.get_entity_by_id(mob_id)
|
||||
{
|
||||
return Some(mob);
|
||||
}
|
||||
|
||||
let tracker = self
|
||||
.combat_tracker
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(killer) = tracker.get_killer_entry()
|
||||
&& let Some(killer_id) = killer.attacker_id
|
||||
{
|
||||
return world.get_entity_by_id(killer_id);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Triggers location-based enchantment effects (e.g. Frost Walker) when the entity's block position changes.
|
||||
pub fn on_changed_block(&self, caller: &dyn EntityBase, _pos: BlockPos) {
|
||||
let pos_f64 = self.entity.pos.load();
|
||||
@ -1607,28 +1659,40 @@ impl LivingEntity {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_closure_for_method_calls)]
|
||||
pub fn get_death_message(
|
||||
dyn_self: &dyn EntityBase,
|
||||
damage_type: DamageType,
|
||||
source: Option<&dyn EntityBase>,
|
||||
cause: Option<&dyn EntityBase>,
|
||||
) -> TextComponent {
|
||||
let kill_credit = dyn_self
|
||||
.get_living_entity()
|
||||
.and_then(Self::get_kill_credit);
|
||||
let kill_credit_name = kill_credit.as_ref().map(|c| c.get_display_name());
|
||||
|
||||
if let Some(living) = dyn_self.get_living_entity() {
|
||||
let tracker = living
|
||||
.combat_tracker
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
return tracker.get_death_message(dyn_self.get_display_name());
|
||||
return tracker.get_death_message(dyn_self.get_display_name(), kill_credit_name);
|
||||
}
|
||||
|
||||
if let Some(cause) = cause
|
||||
&& source.is_some()
|
||||
{
|
||||
TextComponent::translate_cross(
|
||||
format!("death.attack.{}.player", damage_type.message_id),
|
||||
format!("death.attack.{}.player", damage_type.message_id),
|
||||
format!("death.attack.{}", damage_type.message_id),
|
||||
format!("death.attack.{}", damage_type.message_id),
|
||||
[dyn_self.get_display_name(), cause.get_display_name()],
|
||||
)
|
||||
} else if let Some(killer) = cause.or(source).map(|c| c.get_display_name()).or(kill_credit_name) {
|
||||
TextComponent::translate_cross(
|
||||
format!("death.attack.{}.player", damage_type.message_id),
|
||||
format!("death.attack.{}.player", damage_type.message_id),
|
||||
[dyn_self.get_display_name(), killer],
|
||||
)
|
||||
} else {
|
||||
TextComponent::translate_cross(
|
||||
format!("death.attack.{}", damage_type.message_id),
|
||||
@ -1656,7 +1720,10 @@ impl LivingEntity {
|
||||
self.movement_input.store(Vector3::default());
|
||||
self.jumping.store(false, Relaxed);
|
||||
|
||||
self.update_death_stats(&*dyn_self, cause.or(source));
|
||||
let kill_credit = self.get_kill_credit();
|
||||
let killer = cause.or(source).or(kill_credit.as_deref());
|
||||
|
||||
self.update_death_stats(&*dyn_self, killer);
|
||||
|
||||
// Plays the death sound
|
||||
world.send_entity_status(&self.entity, EntityStatus::Death, Some(ActorEventID::Death));
|
||||
@ -1686,7 +1753,7 @@ impl LivingEntity {
|
||||
let is_thundering = world.is_thundering();
|
||||
|
||||
let has_player_kill =
|
||||
cause.is_some_and(|c| c.get_entity().entity_type == &EntityType::PLAYER) || {
|
||||
killer.is_some_and(|c| c.get_entity().entity_type == &EntityType::PLAYER) || {
|
||||
let tracker = self
|
||||
.combat_tracker
|
||||
.lock()
|
||||
@ -1697,7 +1764,7 @@ impl LivingEntity {
|
||||
let params = LootContextParameters {
|
||||
killed_by_player: Some(has_player_kill),
|
||||
this_entity: Some(self.entity.entity_type),
|
||||
killer_entity: cause.map(|c| c.get_entity().entity_type),
|
||||
killer_entity: killer.map(|c| c.get_entity().entity_type),
|
||||
direct_killer_entity: source.map(|s| s.get_entity().entity_type),
|
||||
position: Some(self.entity.pos.load()),
|
||||
world_time: world.level_info.load().day_time as u64,
|
||||
@ -1721,7 +1788,7 @@ impl LivingEntity {
|
||||
if params.killed_by_player.unwrap_or(false)
|
||||
&& world.level_info.load().game_rules.mob_drops
|
||||
{
|
||||
let amount = dyn_self.get_experience_reward(cause);
|
||||
let amount = dyn_self.get_experience_reward(killer);
|
||||
if amount > 0 {
|
||||
ExperienceOrbEntity::spawn(&world, self.entity.pos.load(), amount);
|
||||
}
|
||||
@ -1813,14 +1880,42 @@ impl LivingEntity {
|
||||
) {
|
||||
let world = self.entity.world.load();
|
||||
let show_death_messages = { world.level_info.load().game_rules.show_death_messages };
|
||||
if self.entity.entity_type == &EntityType::PLAYER && show_death_messages {
|
||||
//TODO: KillCredit
|
||||
if self.entity.entity_type == &EntityType::PLAYER {
|
||||
let death_message = Self::get_death_message(dyn_self, damage_type, source, cause);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut final_death_message = death_message;
|
||||
if let Some(player) = dyn_self.get_player() {
|
||||
if let Some(player_arc) = world.get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = world.server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::entity::entity_death::PlayerDeathEvent::new(
|
||||
player_arc,
|
||||
final_death_message.clone(),
|
||||
0,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
final_death_message = event.death_message;
|
||||
}
|
||||
|
||||
player.handle_killed(&final_death_message);
|
||||
}
|
||||
|
||||
if show_death_messages
|
||||
&& let Some(server) = world.server.upgrade()
|
||||
{
|
||||
for player in server.get_all_players() {
|
||||
player.send_system_message(&death_message);
|
||||
player.send_system_message(&final_death_message);
|
||||
}
|
||||
}
|
||||
} else if self.entity.custom_name.load().is_some() {
|
||||
let death_message = Self::get_death_message(dyn_self, damage_type, source, cause);
|
||||
tracing::info!(
|
||||
"Named entity {} died: {}",
|
||||
dyn_self.get_display_name().to_pretty_console(),
|
||||
death_message.to_pretty_console()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1894,11 +1989,6 @@ impl LivingEntity {
|
||||
);
|
||||
}
|
||||
}
|
||||
killer_player.increment_stat(
|
||||
StatisticCategory::Killed,
|
||||
self.entity.entity_type.id as i32,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2521,6 +2611,54 @@ impl LivingEntity {
|
||||
}
|
||||
amount = damage_event.damage;
|
||||
|
||||
if let Some(damager) = source.or(cause) {
|
||||
let mut by_entity_event =
|
||||
crate::plugin::api::events::entity::entity_damage_by_entity::EntityDamageByEntityEvent {
|
||||
entity_id: self.entity.entity_id,
|
||||
damager_id: damager.get_entity().entity_id,
|
||||
damage: amount,
|
||||
cause: format!("{damage_type:?}"),
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = self.entity.world.load().server.upgrade() {
|
||||
server
|
||||
.plugin_manager
|
||||
.fire_blocking(&server, &mut by_entity_event);
|
||||
}
|
||||
if by_entity_event.cancelled {
|
||||
return false;
|
||||
}
|
||||
amount = by_entity_event.damage;
|
||||
} else if position.is_some()
|
||||
|| matches!(
|
||||
damage_type,
|
||||
DamageType::CACTUS
|
||||
| DamageType::SWEET_BERRY_BUSH
|
||||
| DamageType::CAMPFIRE
|
||||
| DamageType::HOT_FLOOR
|
||||
| DamageType::STALAGMITE
|
||||
)
|
||||
{
|
||||
let damager_pos = position.map(|p| BlockPos(Vector3::new(p.x.floor() as i32, p.y.floor() as i32, p.z.floor() as i32)));
|
||||
let mut by_block_event =
|
||||
crate::plugin::api::events::entity::entity_damage_by_block::EntityDamageByBlockEvent {
|
||||
entity_id: self.entity.entity_id,
|
||||
damager_pos,
|
||||
damage: amount,
|
||||
cause: format!("{damage_type:?}"),
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = self.entity.world.load().server.upgrade() {
|
||||
server
|
||||
.plugin_manager
|
||||
.fire_blocking(&server, &mut by_block_event);
|
||||
}
|
||||
if by_block_event.cancelled {
|
||||
return false;
|
||||
}
|
||||
amount = by_block_event.damage;
|
||||
}
|
||||
|
||||
let world = self.entity.world.load();
|
||||
let is_fire_damage = damage_type.has_tag(&tag::DamageType::MINECRAFT_IS_FIRE);
|
||||
|
||||
@ -2758,10 +2896,20 @@ impl LivingEntity {
|
||||
}
|
||||
|
||||
if let Some(attacker) = cause.or(source) {
|
||||
let attacker_id = attacker.get_entity().entity_id;
|
||||
self.last_attacker_id
|
||||
.store(attacker.get_entity().entity_id, Relaxed);
|
||||
.store(attacker_id, Relaxed);
|
||||
self.last_attacked_time
|
||||
.store(self.entity.age.load(Relaxed), Relaxed);
|
||||
|
||||
let current_tick = world.level_info.load().day_time;
|
||||
if attacker.get_player().is_some() {
|
||||
self.last_hurt_by_player_id.store(attacker_id, Relaxed);
|
||||
self.last_hurt_by_player_time.store(current_tick, Relaxed);
|
||||
} else if attacker.get_living_entity().is_some() {
|
||||
self.last_hurt_by_mob_id.store(attacker_id, Relaxed);
|
||||
self.last_hurt_by_mob_time.store(current_tick, Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -305,6 +305,14 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
||||
}
|
||||
|
||||
fn java_spawn_metadata(&self, version: JavaMinecraftVersion) -> Option<Box<[u8]>> {
|
||||
if version < JavaMinecraftVersion::V_1_9 {
|
||||
let entity = self.get_entity();
|
||||
let shared_flags = entity.flags.load(Ordering::Relaxed);
|
||||
return (shared_flags != 0).then(|| {
|
||||
// (0 << 5) | 0 = 0 (type: byte, index: 0 flags), value, 127 (terminator)
|
||||
Box::<[u8]>::from([0x00u8, shared_flags as u8, 127u8])
|
||||
});
|
||||
}
|
||||
self.get_mob().map_or_else(
|
||||
|| {
|
||||
let entity = self.get_entity();
|
||||
@ -382,7 +390,9 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||
client.try_enqueue_packet(data);
|
||||
}
|
||||
if let Some(meta) = metadata {
|
||||
if let Some(meta) = metadata
|
||||
&& (version >= JavaMinecraftVersion::V_1_9 || meta.last().copied() == Some(127))
|
||||
{
|
||||
let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), meta);
|
||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||
client.try_enqueue_packet(meta_data);
|
||||
@ -3356,6 +3366,23 @@ impl Entity {
|
||||
|
||||
pub fn leash_to(&self, holder: Arc<dyn EntityBase>) {
|
||||
let holder_entity_id = holder.get_entity().entity_id;
|
||||
let world = self.world.load();
|
||||
if let Some(server) = world.server.upgrade()
|
||||
&& let Some(player) = holder.get_player()
|
||||
&& let Some(player_arc) = world.get_player_by_uuid(player.gameprofile.id)
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_leash_entity::PlayerLeashEntityEvent {
|
||||
player: player_arc,
|
||||
entity_id: self.entity_id,
|
||||
holder_id: holder_entity_id,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*self
|
||||
.leashed_to
|
||||
.lock()
|
||||
@ -3387,6 +3414,18 @@ impl Entity {
|
||||
}
|
||||
|
||||
pub fn unleash(&self) {
|
||||
let world = self.world.load();
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::entity::entity_unleash::EntityUnleashEvent::new(
|
||||
self.entity_id,
|
||||
"unleashed".to_string(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let old_holder = self
|
||||
.leashed_to
|
||||
.lock()
|
||||
@ -3396,6 +3435,19 @@ impl Entity {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(holder) = &old_holder
|
||||
&& let Some(server) = world.server.upgrade()
|
||||
&& let Some(player) = holder.get_player()
|
||||
&& let Some(player_arc) = world.get_player_by_uuid(player.gameprofile.id)
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_unleash_entity::PlayerUnleashEntityEvent {
|
||||
player: player_arc,
|
||||
entity_id: self.entity_id,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
}
|
||||
|
||||
let je_packet =
|
||||
pumpkin_protocol::java::client::play::CSetEntityLink::new(self.entity_id, -1, true);
|
||||
let be_packet = pumpkin_protocol::bedrock::client::CSetActorLink {
|
||||
|
||||
@ -296,9 +296,21 @@ impl Mob for AxolotlEntity {
|
||||
let item = item_stack.get_item();
|
||||
|
||||
if item == &Item::WATER_BUCKET {
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
let entity = self.get_entity();
|
||||
let world = entity.world.load();
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::player::player_bucket_entity::PlayerBucketEntityEvent {
|
||||
player: player.clone(),
|
||||
entity_id: entity.entity_id,
|
||||
bucket_item: "axolotl_bucket".to_string(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
let pos = entity.pos.load();
|
||||
world.play_sound(Sound::ItemBucketFillAxolotl, SoundCategory::Neutral, &pos);
|
||||
entity.remove();
|
||||
|
||||
@ -127,9 +127,21 @@ impl Mob for TadpoleEntity {
|
||||
}
|
||||
|
||||
if item_stack.get_item() == &Item::WATER_BUCKET {
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
let entity = self.get_entity();
|
||||
let world = entity.world.load();
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::player::player_bucket_entity::PlayerBucketEntityEvent {
|
||||
player: player.clone(),
|
||||
entity_id: entity.entity_id,
|
||||
bucket_item: "tadpole_bucket".to_string(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
let pos = entity.pos.load();
|
||||
world.play_sound(Sound::ItemBucketFillTadpole, SoundCategory::Neutral, &pos);
|
||||
entity.remove();
|
||||
|
||||
@ -2141,6 +2141,9 @@ impl Mob for VillagerEntity {
|
||||
}
|
||||
|
||||
fn mob_java_spawn_metadata(&self, version: JavaMinecraftVersion) -> Option<Box<[u8]>> {
|
||||
if version < JavaMinecraftVersion::V_1_9 {
|
||||
return None;
|
||||
}
|
||||
let mut metadata = Vec::new();
|
||||
Metadata::new(
|
||||
tracked_data::villager::VILLAGER_DATA,
|
||||
|
||||
@ -1603,19 +1603,38 @@ impl Player {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut final_block_pos = block_pos;
|
||||
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_spawn_change::PlayerSpawnChangeEvent {
|
||||
player: player_arc,
|
||||
new_spawn: Some(block_pos),
|
||||
forced,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
if let Some(pos) = event.new_spawn {
|
||||
final_block_pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
let bedrock_dimension = match dimension.minecraft_name {
|
||||
"minecraft:the_nether" => 1,
|
||||
"minecraft:the_end" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
self.client.try_enqueue_packet_editioned(
|
||||
&CPlayerSpawnPosition::new(block_pos, yaw, pitch, dimension.minecraft_name.to_owned()),
|
||||
&CPlayerSpawnPosition::new(final_block_pos, yaw, pitch, dimension.minecraft_name.to_owned()),
|
||||
&pumpkin_protocol::bedrock::client::CSetSpawnPosition {
|
||||
spawn_position_type:
|
||||
pumpkin_protocol::bedrock::client::SpawnPositionType::PlayerRespawn,
|
||||
block_position: block_pos,
|
||||
block_position: final_block_pos,
|
||||
dimension_type: bedrock_dimension.into(),
|
||||
spawn_block_pos: block_pos,
|
||||
spawn_block_pos: final_block_pos,
|
||||
},
|
||||
);
|
||||
|
||||
@ -1624,7 +1643,7 @@ impl Player {
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(RespawnPoint {
|
||||
dimension,
|
||||
position: block_pos,
|
||||
position: final_block_pos,
|
||||
yaw,
|
||||
force: forced,
|
||||
});
|
||||
@ -2293,11 +2312,39 @@ impl Player {
|
||||
}
|
||||
|
||||
pub fn open_sign_editor(&self, location: BlockPos, is_front_text: bool) {
|
||||
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_open_sign::PlayerOpenSignEvent {
|
||||
player: player_arc,
|
||||
block_pos: location,
|
||||
is_front: is_front_text,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let packet = COpenSignEditor::new(location, is_front_text);
|
||||
self.try_send_client_packet(&packet);
|
||||
}
|
||||
|
||||
pub fn set_velocity(&self, velocity: Vector3<f64>) {
|
||||
pub fn set_velocity(&self, mut velocity: Vector3<f64>) {
|
||||
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_velocity::PlayerVelocityEvent {
|
||||
player: player_arc,
|
||||
velocity,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
velocity = event.velocity;
|
||||
}
|
||||
self.living_entity.entity.set_velocity(velocity);
|
||||
self.try_send_client_packet(&CEntityVelocity::new(self.entity_id().into(), velocity));
|
||||
}
|
||||
@ -2340,6 +2387,19 @@ impl Player {
|
||||
|
||||
/// Sends custom server links to the player (displayed in the client Esc pause menu).
|
||||
pub fn set_server_links(&self, links: &[pumpkin_protocol::Link<'_>]) {
|
||||
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let link_strings = links.iter().map(|l| l.url.clone()).collect();
|
||||
let mut event = crate::plugin::api::events::player::player_links_send::PlayerLinksSendEvent::new(
|
||||
player_arc,
|
||||
link_strings,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let packet = CPlayServerLinks::new(links);
|
||||
self.try_send_client_packet(&packet);
|
||||
}
|
||||
@ -3029,8 +3089,25 @@ impl Player {
|
||||
}
|
||||
|
||||
pub fn increment_stat(&self, category: statistics::StatisticCategory, stat: i32, amount: i32) {
|
||||
let final_amount = if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_statistic_increment::PlayerStatisticIncrementEvent {
|
||||
player: player_arc,
|
||||
statistic_id: format!("{category:?}:{stat}"),
|
||||
amount,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
event.amount
|
||||
} else {
|
||||
amount
|
||||
};
|
||||
if let Ok(mut stats) = self.stats.try_lock() {
|
||||
stats.increment(category, stat, amount);
|
||||
stats.increment(category, stat, final_amount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3729,6 +3806,14 @@ impl Player {
|
||||
}
|
||||
|
||||
player.request_teleport(position, yaw, pitch);
|
||||
|
||||
let mut changed_world_event = crate::plugin::api::events::player::player_changed_world::PlayerChangedWorldEvent {
|
||||
player: player.clone(),
|
||||
from_world: current_world,
|
||||
to_world: new_world,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire(&server, &mut changed_world_event).await;
|
||||
}
|
||||
}}
|
||||
}
|
||||
@ -4364,8 +4449,19 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn handle_killed(&self, death_msg: TextComponent) {
|
||||
pub fn send_combat_death(&self, death_msg: &TextComponent) {
|
||||
self.try_enqueue_packet_editioned(
|
||||
&CCombatDeath::new(self.entity_id().into(), death_msg),
|
||||
&SActorEvent {
|
||||
target_runtime_id: VarULong(self.entity_id() as u64),
|
||||
event_id: ActorEventID::Death,
|
||||
data: VarInt(0),
|
||||
fire_at_position: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn handle_killed(&self, death_msg: &TextComponent) {
|
||||
self.trigger_advancement(
|
||||
crate::entity::player::advancement::trigger::AdvancementTrigger::PlayerKilled,
|
||||
);
|
||||
@ -4402,17 +4498,7 @@ impl Player {
|
||||
if matches!(self.client.as_ref(), ClientPlatform::Java(_)) {
|
||||
self.set_client_loaded(false);
|
||||
}
|
||||
self.client
|
||||
.send_packet_now_editioned(
|
||||
&CCombatDeath::new(self.entity_id().into(), &death_msg),
|
||||
&SActorEvent {
|
||||
target_runtime_id: VarULong(self.entity_id() as u64),
|
||||
event_id: ActorEventID::Death,
|
||||
data: VarInt(0),
|
||||
fire_at_position: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
self.send_combat_death(death_msg);
|
||||
self.send_health();
|
||||
self.send_bedrock_respawn_state(RespawnState::SearchingForSpawn);
|
||||
}
|
||||
@ -5137,10 +5223,27 @@ impl Player {
|
||||
return xp;
|
||||
}
|
||||
|
||||
let xp_used = (repaired + 1) / 2;
|
||||
|
||||
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
&& let Some(server) = self.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_item_mend::PlayerItemMendEvent {
|
||||
player: player_arc,
|
||||
item_name: stack.item.registry_key.to_string(),
|
||||
repair_amount: repaired,
|
||||
exp_consumed: xp_used,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return xp;
|
||||
}
|
||||
}
|
||||
|
||||
let updated_stack = stack.clone();
|
||||
self.inventory.set_slot(slot_index, updated_stack.clone());
|
||||
|
||||
let xp_used = (repaired + 1) / 2;
|
||||
xp = xp.saturating_sub(xp_used);
|
||||
|
||||
self.try_send_slot_set_packet(&CSetPlayerInventory::new(
|
||||
|
||||
@ -1013,6 +1013,19 @@ impl EntityBase for ArrowEntity {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = player.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_pickup_arrow::PlayerPickupArrowEvent::new(
|
||||
player_arc,
|
||||
self.entity.entity_id,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to insert an arrow into the player's inventory
|
||||
let item_stack = self
|
||||
.item_stack
|
||||
|
||||
@ -70,6 +70,16 @@ impl FireworkRocketEntity {
|
||||
|
||||
pub fn explode_and_remove(&self, world: &World) {
|
||||
let entity = self.get_entity();
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::entity::firework_explode::FireworkExplodeEvent {
|
||||
entity_id: entity.entity_id,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
world.send_entity_status(
|
||||
entity,
|
||||
EntityStatus::FireworksExplode,
|
||||
@ -97,6 +107,21 @@ impl EntityBase for FireworkRocketEntity {
|
||||
let shooter = shooter.get_entity();
|
||||
|
||||
if shooter.is_fall_flying() {
|
||||
let mut boost_cancelled = false;
|
||||
if let Some(player) = world.get_player_by_id(shooter_id)
|
||||
&& let Some(server) = world.server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_elytra_boost::PlayerElytraBoostEvent {
|
||||
player,
|
||||
firework_id: entity.entity_id,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
boost_cancelled = true;
|
||||
}
|
||||
}
|
||||
if !boost_cancelled {
|
||||
let rotation = shooter.rotation().to_f64();
|
||||
let shooter_vel = shooter.velocity.load();
|
||||
|
||||
@ -109,6 +134,7 @@ impl EntityBase for FireworkRocketEntity {
|
||||
entity.set_velocity(new_shooter_vel);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
velocity.x *= 1.15;
|
||||
velocity.z *= 1.15;
|
||||
|
||||
@ -182,6 +182,18 @@ impl EntityBase for LingeringPotionEntity {
|
||||
// Spawn and configure an `AreaEffectCloud` entity
|
||||
extinguish_fire_if_water_potion(&world, hit_pos, &stack);
|
||||
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::entity::lingering_potion_splash::LingeringPotionSplashEvent::new(
|
||||
self.get_entity().entity_id,
|
||||
block_pos,
|
||||
stack.item.registry_key.to_string(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let cloud_entity = crate::entity::Entity::from_uuid(
|
||||
Uuid::new_v4(),
|
||||
world.clone(),
|
||||
|
||||
@ -132,6 +132,7 @@ impl EntityBase for SplashPotionEntity {
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn on_hit(&self, hit: crate::entity::projectile::ProjectileHit) {
|
||||
let world = self.get_entity().world.load();
|
||||
let hit_pos = hit.hit_pos();
|
||||
@ -228,20 +229,38 @@ impl EntityBase for SplashPotionEntity {
|
||||
candidates.push(p.clone() as Arc<dyn EntityBase>);
|
||||
}
|
||||
|
||||
let mut affected: Vec<(Arc<dyn EntityBase>, f32)> = Vec::new();
|
||||
for cand in candidates {
|
||||
if let Some(living) = cand.get_living_entity() {
|
||||
if cand.get_living_entity().is_some() {
|
||||
let pos = cand.get_entity().pos.load();
|
||||
let dx = pos.x - hit_pos.x;
|
||||
let dy = pos.y - hit_pos.y;
|
||||
let dz = pos.z - hit_pos.z;
|
||||
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
|
||||
if dist > radius {
|
||||
continue;
|
||||
if dist <= radius {
|
||||
let scale = (1.0f32 - (dist as f32 / radius as f32)).max(0.0);
|
||||
affected.push((cand, scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Distance scaling
|
||||
let scale = (1.0f32 - (dist as f32 / radius as f32)).max(0.0);
|
||||
let affected_ids: Vec<i32> = affected.iter().map(|(c, _)| c.get_entity().entity_id).collect();
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = crate::plugin::api::events::entity::potion_splash::PotionSplashEvent::new(
|
||||
self.get_entity().entity_id,
|
||||
block_pos,
|
||||
stack.item.registry_key.to_string(),
|
||||
affected_ids,
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
affected.retain(|(c, _)| event.affected_entities.contains(&c.get_entity().entity_id));
|
||||
}
|
||||
|
||||
for (cand, scale) in affected {
|
||||
if let Some(living) = cand.get_living_entity() {
|
||||
crate::item::potion::PotionContents::apply_effects_to(
|
||||
living,
|
||||
effects.clone(),
|
||||
|
||||
@ -117,6 +117,21 @@ impl ItemBehaviour for BrushItem {
|
||||
);
|
||||
|
||||
if is_sand || is_gravel {
|
||||
if let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = player.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::block::block_brush::BlockBrushEvent::new(
|
||||
location,
|
||||
world.clone(),
|
||||
player_arc,
|
||||
player.inventory().held_item(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let current_state_id = world.get_block_state_id(&location);
|
||||
let current_stage = get_dusted_stage(block, current_state_id);
|
||||
|
||||
|
||||
@ -76,6 +76,20 @@ impl ItemBehaviour for ShearsItem {
|
||||
.downcast_ref::<crate::entity::passive::sheep::SheepEntity>()
|
||||
&& !sheep.is_sheared()
|
||||
{
|
||||
if let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = player.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_shear_entity::PlayerShearEntityEvent {
|
||||
player: player_arc,
|
||||
entity_id: sheep.mob_entity.living_entity.entity.entity_id,
|
||||
hand: 0,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
sheep.set_sheared(true);
|
||||
let world = player.world();
|
||||
let pos = sheep.mob_entity.living_entity.entity.pos.load();
|
||||
@ -176,6 +190,23 @@ fn handle_beehive(
|
||||
});
|
||||
|
||||
if let Some(new_state_id) = action {
|
||||
let mut drops = vec![ItemStack::new(3, &Item::HONEYCOMB)];
|
||||
if let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = player.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_harvest_block::PlayerHarvestBlockEvent {
|
||||
player: player_arc,
|
||||
block_pos: *location,
|
||||
harvested_items: drops.clone(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
drops = event.harvested_items;
|
||||
}
|
||||
|
||||
world.set_block_state(location, new_state_id, BlockFlags::NOTIFY_ALL);
|
||||
world.play_sound(
|
||||
Sound::BlockBeehiveShear,
|
||||
@ -188,11 +219,13 @@ fn handle_beehive(
|
||||
f64::from(location.0.y) + 0.5,
|
||||
f64::from(location.0.z) + 0.5,
|
||||
);
|
||||
for item in drops {
|
||||
let item_entity = Arc::new(ItemEntity::new(
|
||||
Entity::new(world.clone(), drop_pos, &EntityType::ITEM),
|
||||
ItemStack::new(3, &Item::HONEYCOMB),
|
||||
item,
|
||||
));
|
||||
world.spawn_entity(item_entity);
|
||||
}
|
||||
player.damage_held_item(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -65,6 +65,21 @@ impl ItemBehaviour for TridentItem {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(player_arc) = world.get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = world.server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_riptide::PlayerRiptideEvent {
|
||||
player: player_arc,
|
||||
item_name: "trident".to_string(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
player.living_entity.clear_active_hand();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let f = f64::from(riptide_level);
|
||||
let (yaw, pitch) = player.rotation();
|
||||
let f_yaw = f32::to_radians(yaw);
|
||||
|
||||
@ -160,7 +160,9 @@ impl ChunkSender {
|
||||
epoch: u32,
|
||||
version: JavaMinecraftVersion,
|
||||
) -> Option<PreparedBatch> {
|
||||
if self.in_flight_batches >= self.max_in_flight {
|
||||
if version >= JavaMinecraftVersion::V_1_20_2
|
||||
&& self.in_flight_batches >= self.max_in_flight
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -294,9 +296,9 @@ impl ChunkSender {
|
||||
&& let ClientPlatform::Java(java_client) = client
|
||||
{
|
||||
java_client.try_send_packet(&CChunkBatchEnd::new(sent_count as u16));
|
||||
self.in_flight_batches = self.in_flight_batches.saturating_add(1);
|
||||
}
|
||||
|
||||
self.in_flight_batches = self.in_flight_batches.saturating_add(1);
|
||||
self.send_quota -= sent_count as f32;
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ impl PendingConnection {
|
||||
|
||||
pub async fn handle_encryption_response(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
encryption_response: SEncryptionResponse,
|
||||
) -> Option<PacketHandlerResult> {
|
||||
debug!("Handling encryption");
|
||||
@ -146,9 +146,22 @@ impl PendingConnection {
|
||||
|
||||
pub(super) async fn finish_login(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
profile: &GameProfile,
|
||||
) -> Option<PacketHandlerResult> {
|
||||
let mut pre_login_event = crate::plugin::api::events::player::async_player_pre_login::AsyncPlayerPreLoginEvent {
|
||||
player_name: profile.name.clone(),
|
||||
player_uuid: profile.id,
|
||||
ip_address: self.address,
|
||||
kick_message: TextComponent::text("Disconnected"),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire(server, &mut pre_login_event).await;
|
||||
if pre_login_event.cancelled {
|
||||
self.kick(pre_login_event.kick_message).await;
|
||||
return Some(PacketHandlerResult::Stop);
|
||||
}
|
||||
|
||||
let props = profile.properties.load();
|
||||
let packet = CLoginSuccess::new(
|
||||
&profile.id,
|
||||
|
||||
@ -4,7 +4,7 @@ use super::*;
|
||||
impl PendingConnection {
|
||||
pub async fn handle_login_start(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
login_start: SLoginStart,
|
||||
) -> Option<PacketHandlerResult> {
|
||||
debug!("login start");
|
||||
|
||||
@ -4,7 +4,7 @@ use super::*;
|
||||
impl PendingConnection {
|
||||
pub async fn handle_plugin_response(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
plugin_response: SLoginPluginResponse,
|
||||
) -> Option<PacketHandlerResult> {
|
||||
debug!("Handling plugin");
|
||||
|
||||
@ -1148,12 +1148,45 @@ impl JavaClient {
|
||||
}
|
||||
id if id == SCustomPayload::to_id(version) => {
|
||||
let payload = SCustomPayload::read(&mut payload, &version)?;
|
||||
let channel_str = payload.channel.to_string();
|
||||
let mut event = PlayerCustomPayloadEvent::new(
|
||||
player.clone(),
|
||||
payload.channel.to_string(),
|
||||
channel_str.clone(),
|
||||
Bytes::copy_from_slice(payload.data),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(server, &mut event);
|
||||
|
||||
if channel_str == "minecraft:register" {
|
||||
if let Ok(channels_data) = std::str::from_utf8(payload.data) {
|
||||
for ch in channels_data.split('\0') {
|
||||
if !ch.is_empty() {
|
||||
let mut reg_event = crate::plugin::api::events::player::player_register_channel::PlayerRegisterChannelEvent::new(
|
||||
player.clone(),
|
||||
ch.to_string(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(server, &mut reg_event);
|
||||
let mut ch_event = crate::plugin::api::events::player::player_channel::PlayerChannelEvent {
|
||||
player: player.clone(),
|
||||
channel: ch.to_string(),
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(server, &mut ch_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if channel_str == "minecraft:unregister"
|
||||
&& let Ok(channels_data) = std::str::from_utf8(payload.data)
|
||||
{
|
||||
for ch in channels_data.split('\0') {
|
||||
if !ch.is_empty() {
|
||||
let mut unreg_event = crate::plugin::api::events::player::player_unregister_channel::PlayerUnregisterChannelEvent::new(
|
||||
player.clone(),
|
||||
ch.to_string(),
|
||||
);
|
||||
server.plugin_manager.fire_blocking(server, &mut unreg_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
id if id == SRecipeBookChangeSettings::to_id(version) => {
|
||||
self.handle_recipe_book_change_settings(
|
||||
|
||||
@ -313,7 +313,7 @@ impl PendingConnection {
|
||||
|
||||
async fn handle_login_packet(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
packet: &RawPacket,
|
||||
) -> Result<Option<PacketHandlerResult>, ReadingError> {
|
||||
debug!("Handling login group");
|
||||
|
||||
@ -8,12 +8,34 @@ impl JavaClient {
|
||||
return;
|
||||
}
|
||||
|
||||
let pages: Vec<String> = packet.pages.iter().map(|p| (*p).to_string()).collect();
|
||||
let mut pages: Vec<String> = packet.pages.iter().map(|p| (*p).to_string()).collect();
|
||||
let mut title = packet.title.map(std::string::ToString::to_string);
|
||||
let signing = title.is_some();
|
||||
let slot = player.inventory().get_selected_slot() as u32;
|
||||
|
||||
if let Some(title) = packet.title {
|
||||
if let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
&& let Some(server) = player.world().server.upgrade()
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_edit_book::PlayerEditBookEvent {
|
||||
player: player_arc,
|
||||
slot,
|
||||
pages: pages.clone(),
|
||||
title: title.clone(),
|
||||
signing,
|
||||
cancelled: false,
|
||||
};
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
pages = event.pages;
|
||||
title = event.title;
|
||||
}
|
||||
|
||||
if let Some(title) = title {
|
||||
let mut written_book = ItemStack::new(1, &Item::WRITTEN_BOOK);
|
||||
let content = WrittenBookContentImpl {
|
||||
title: title.to_string(),
|
||||
title,
|
||||
author: player.gameprofile.name.clone(),
|
||||
pages,
|
||||
};
|
||||
|
||||
@ -160,9 +160,20 @@ impl JavaClient {
|
||||
self.update_sequence(player_action.sequence.0);
|
||||
return;
|
||||
}
|
||||
player.mining.store(false, Ordering::Relaxed);
|
||||
let entity = &player.get_entity();
|
||||
entity.world.load().set_block_breaking(
|
||||
let world = entity.world.load_full();
|
||||
if let Some(server_arc) = world.server.upgrade() {
|
||||
let mut abort_event = crate::plugin::api::events::block::block_damage_abort::BlockDamageAbortEvent::new(
|
||||
player.clone(),
|
||||
player_action.position,
|
||||
world.clone(),
|
||||
player.inventory().held_item(),
|
||||
);
|
||||
server_arc.plugin_manager.fire_blocking(&server_arc, &mut abort_event);
|
||||
}
|
||||
|
||||
player.mining.store(false, Ordering::Relaxed);
|
||||
world.set_block_breaking(
|
||||
entity,
|
||||
player_action.position,
|
||||
BlockBreakingProgress::Stop,
|
||||
|
||||
@ -79,22 +79,7 @@ impl HostEntity for PluginHostState {
|
||||
) -> wasmtime::Result<entity_types::EntityType> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let original_name = entity.get_entity().entity_type.resource_name;
|
||||
|
||||
let mut names: Vec<String> = serde_json::from_str::<
|
||||
std::collections::BTreeMap<String, serde_json::Value>,
|
||||
>(&std::fs::read_to_string("assets/entities.json")?)?
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
names.sort();
|
||||
|
||||
let index = names
|
||||
.iter()
|
||||
.position(|n| n == original_name)
|
||||
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown entity type: {original_name}")))?;
|
||||
|
||||
// SAFETY: The WIT enum is generated from the sorted keys of assets/entities.json.
|
||||
Ok(unsafe { std::mem::transmute::<u8, entity_types::EntityType>(index as u8) })
|
||||
to_wit_entity_type(original_name)
|
||||
}
|
||||
|
||||
async fn get_position(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> {
|
||||
@ -925,3 +910,24 @@ impl HostEntity for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_wit_entity_type(name: &str) -> wasmtime::Result<entity_types::EntityType> {
|
||||
let name = name.strip_prefix("minecraft:").unwrap_or(name);
|
||||
let index = pumpkin_data::entity::EntityType::ALL
|
||||
.binary_search_by_key(&name, |e| e.resource_name)
|
||||
.map_err(|_| wasmtime::Error::msg(format!("Unknown entity type: {name}")))?;
|
||||
|
||||
// SAFETY: The WIT enum is generated with variants matching EntityType::ALL in alphabetical order.
|
||||
Ok(unsafe { std::mem::transmute::<u8, entity_types::EntityType>(index as u8) })
|
||||
}
|
||||
|
||||
pub(crate) fn from_wit_entity_type(
|
||||
entity_type: entity_types::EntityType,
|
||||
) -> wasmtime::Result<&'static pumpkin_data::entity::EntityType> {
|
||||
let index = entity_type as usize;
|
||||
pumpkin_data::entity::EntityType::ALL
|
||||
.get(index)
|
||||
.copied()
|
||||
.ok_or_else(|| wasmtime::Error::msg(format!("Invalid entity type index: {index}")))
|
||||
}
|
||||
|
||||
|
||||
@ -281,21 +281,7 @@ impl PluginHostState {
|
||||
fn get_wit_biome(
|
||||
biome: &pumpkin_data::biome::Biome,
|
||||
) -> wasmtime::Result<pumpkin::plugin::biomes::Biome> {
|
||||
let mut names: Vec<String> = serde_json::from_str::<
|
||||
std::collections::BTreeMap<String, serde_json::Value>,
|
||||
>(&std::fs::read_to_string("assets/biome.json")?)?
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
names.sort();
|
||||
|
||||
let index = names
|
||||
.iter()
|
||||
.position(|n| n.strip_prefix("minecraft:").unwrap_or(n) == biome.registry_id)
|
||||
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown biome: {}", biome.registry_id)))?;
|
||||
|
||||
// SAFETY: The WIT enum is generated from the sorted keys of assets/biome.json.
|
||||
Ok(unsafe { std::mem::transmute::<u8, pumpkin::plugin::biomes::Biome>(index as u8) })
|
||||
to_wit_biome(biome)
|
||||
}
|
||||
|
||||
fn get_wit_block_entity(
|
||||
@ -1038,20 +1024,8 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
let world_ref = self.get_world_res(&world)?;
|
||||
let world_provider = world_ref.provider.clone();
|
||||
|
||||
let mut names: Vec<String> = serde_json::from_str::<
|
||||
std::collections::BTreeMap<String, serde_json::Value>,
|
||||
>(&std::fs::read_to_string("assets/entities.json")?)?
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
names.sort();
|
||||
|
||||
let type_name = names.get(entity_type as usize).ok_or_else(|| {
|
||||
wasmtime::Error::msg(format!("Invalid entity type index: {}", entity_type as u8))
|
||||
})?;
|
||||
|
||||
let internal_type = pumpkin_data::entity::EntityType::from_name(type_name)
|
||||
.ok_or_else(|| wasmtime::Error::msg(format!("Invalid entity type: {type_name}")))?;
|
||||
let internal_type =
|
||||
super::entity::from_wit_entity_type(entity_type)?;
|
||||
|
||||
let internal_pos = pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2);
|
||||
let entity = crate::entity::r#type::from_type(
|
||||
@ -2340,9 +2314,24 @@ pub const fn from_wit_sound_category(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_wit_biome(
|
||||
biome: &pumpkin_data::biome::Biome,
|
||||
) -> wasmtime::Result<pumpkin::plugin::biomes::Biome> {
|
||||
let name = biome
|
||||
.registry_id
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(biome.registry_id);
|
||||
let index = pumpkin_data::biome::Biome::ALL
|
||||
.binary_search_by_key(&name, |b| b.registry_id)
|
||||
.map_err(|_| wasmtime::Error::msg(format!("Unknown biome: {}", biome.registry_id)))?;
|
||||
|
||||
// SAFETY: The WIT enum is generated with variants matching Biome::ALL in alphabetical order.
|
||||
Ok(unsafe { std::mem::transmute::<u8, pumpkin::plugin::biomes::Biome>(index as u8) })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::pumpkin;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wit_particle_ids_match_internal_particle_ids() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user